From 84b753956f5f434d628c0ecd042ad1a6d55278de Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 Mar 2019 16:44:46 +0100 Subject: [PATCH 001/414] refactor: use native Object.values --- lib/associations/mixin.js | 2 +- lib/data-types.js | 2 +- lib/dialects/abstract/query.js | 2 +- lib/dialects/postgres/query-generator.js | 2 +- lib/dialects/postgres/query.js | 2 +- lib/dialects/sqlite/query-generator.js | 2 +- lib/instance-validator.js | 2 +- lib/query-interface.js | 2 +- lib/utils.js | 2 +- test/integration/associations/self.test.js | 16 +++++++--------- 10 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/associations/mixin.js b/lib/associations/mixin.js index 60601d77d82b..062de0edea92 100644 --- a/lib/associations/mixin.js +++ b/lib/associations/mixin.js @@ -75,7 +75,7 @@ const Mixin = { }, getAssociations(target) { - return _.values(this.associations).filter(association => association.target.name === target.name); + return Object.values(this.associations).filter(association => association.target.name === target.name); }, getAssociationForAlias(target, alias) { diff --git a/lib/data-types.js b/lib/data-types.js index 39dbccef7ab6..3e43a50495b2 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -1034,7 +1034,7 @@ dialectMap.mariadb = require('./dialects/mariadb/data-types')(DataTypes); dialectMap.sqlite = require('./dialects/sqlite/data-types')(DataTypes); dialectMap.mssql = require('./dialects/mssql/data-types')(DataTypes); -const dialectList = _.values(dialectMap); +const dialectList = Object.values(dialectMap); for (const dataTypes of dialectList) { _.each(dataTypes, (DataType, key) => { diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 6cd6b8b0289d..4b3c538fba78 100755 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -206,7 +206,7 @@ class AbstractQuery { } handleShowTablesQuery(results) { - return _.flatten(results.map(resultSet => _.values(resultSet))); + return _.flatten(results.map(resultSet => Object.values(resultSet))); } isShowIndexesQuery() { diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 53511c017caa..edc2d4fb72e6 100755 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -383,7 +383,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { throw new Error('Cannot LIMIT delete without a model.'); } - const pks = _.values(model.primaryKeys).map(pk => this.quoteIdentifier(pk.field)).join(','); + const pks = Object.values(model.primaryKeys).map(pk => this.quoteIdentifier(pk.field)).join(','); primaryKeys = model.primaryKeyAttributes.length > 1 ? `(${pks})` : pks; primaryKeysSelection = pks; diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index df7c6e573c8c..5f0b60ab9b15 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -105,7 +105,7 @@ class Query extends AbstractQuery { })); } if (isTableNameQuery) { - return rows.map(row => _.values(row)); + return rows.map(row => Object.values(row)); } if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index a4f734934ec6..9fa931bec208 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -23,7 +23,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { options = options || {}; const primaryKeys = []; - const needsMultiplePrimaryKeys = _.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; + const needsMultiplePrimaryKeys = Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; const attrArray = []; for (const attr in attributes) { diff --git a/lib/instance-validator.js b/lib/instance-validator.js index ad14acc2421f..ae0e0e4fa25d 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -337,7 +337,7 @@ class InstanceValidator { */ _validateSchema(rawAttribute, field, value) { if (rawAttribute.allowNull === false && (value === null || value === undefined)) { - const association = _.values(this.modelInstance.constructor.associations).find(association => association instanceof BelongsTo && association.foreignKey === rawAttribute.fieldName); + const association = Object.values(this.modelInstance.constructor.associations).find(association => association instanceof BelongsTo && association.foreignKey === rawAttribute.fieldName); if (!association || !this.modelInstance.get(association.associationAccessor)) { const validators = this.modelInstance.validators[field]; const errMsg = _.get(validators, 'notNull.msg', `${this.modelInstance.constructor.name}.${field} cannot be null`); diff --git a/lib/query-interface.js b/lib/query-interface.js index 21762227c759..913a8fd03a93 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -555,7 +555,7 @@ class QueryInterface { const attributes = {}; options = options || {}; - if (_.values(DataTypes).includes(dataTypeOrOptions)) { + if (Object.values(DataTypes).includes(dataTypeOrOptions)) { attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true }; } else { attributes[attributeName] = dataTypeOrOptions; diff --git a/lib/utils.js b/lib/utils.js index 551fc20e1ddc..e0dae46ab7cc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,7 +7,7 @@ const uuidv1 = require('uuid/v1'); const uuidv4 = require('uuid/v4'); const Promise = require('./promise'); const operators = require('./operators'); -const operatorsSet = new Set(_.values(operators)); +const operatorsSet = new Set(Object.values(operators)); let inflection = require('inflection'); diff --git a/test/integration/associations/self.test.js b/test/integration/associations/self.test.js index 69c5988b62b8..7d1835b3cd2b 100644 --- a/test/integration/associations/self.test.js +++ b/test/integration/associations/self.test.js @@ -1,12 +1,10 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise, - _ = require('lodash'); +const { expect } = require('chai'); +const Support = require('../support'); +const DataTypes = require('../../../lib/data-types'); +const Sequelize = require('../../../index'); +const Promise = Sequelize.Promise; describe(Support.getTestDialectTeaser('Self'), () => { it('supports freezeTableName', function() { @@ -52,7 +50,7 @@ describe(Support.getTestDialectTeaser('Self'), () => { Person.belongsToMany(Person, { as: 'Parents', through: 'Family', foreignKey: 'ChildId', otherKey: 'PersonId' }); Person.belongsToMany(Person, { as: 'Childs', through: 'Family', foreignKey: 'PersonId', otherKey: 'ChildId' }); - const foreignIdentifiers = _.values(Person.associations).map(v => v.foreignIdentifier); + const foreignIdentifiers = Object.values(Person.associations).map(v => v.foreignIdentifier); const rawAttributes = Object.keys(this.sequelize.models.Family.rawAttributes); expect(foreignIdentifiers.length).to.equal(2); @@ -94,7 +92,7 @@ describe(Support.getTestDialectTeaser('Self'), () => { Person.belongsToMany(Person, { as: 'Parents', through: Family, foreignKey: 'preexisting_child', otherKey: 'preexisting_parent' }); Person.belongsToMany(Person, { as: 'Children', through: Family, foreignKey: 'preexisting_parent', otherKey: 'preexisting_child' }); - const foreignIdentifiers = _.values(Person.associations).map(v => v.foreignIdentifier); + const foreignIdentifiers = Object.values(Person.associations).map(v => v.foreignIdentifier); const rawAttributes = Object.keys(Family.rawAttributes); expect(foreignIdentifiers.length).to.equal(2); From 755e1578701fc029b61464021ab3285952aa47bb Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 Mar 2019 16:48:12 +0100 Subject: [PATCH 002/414] refactor(errors): remove redundant stack trace capturing --- lib/errors/association-error.js | 1 - lib/errors/base-error.js | 1 - lib/errors/bulk-record-error.js | 1 - lib/errors/connection-error.js | 1 - lib/errors/connection/access-denied-error.js | 1 - lib/errors/connection/connection-acquire-timeout-error.js | 1 - lib/errors/connection/connection-refused-error.js | 1 - lib/errors/connection/connection-timed-out-error.js | 1 - lib/errors/connection/host-not-found-error.js | 1 - lib/errors/connection/host-not-reachable-error.js | 1 - lib/errors/connection/invalid-connection-error.js | 1 - lib/errors/database-error.js | 1 - lib/errors/database/exclusion-constraint-error.js | 1 - lib/errors/database/foreign-key-constraint-error.js | 1 - lib/errors/database/timeout-error.js | 1 - lib/errors/database/unknown-constraint-error.js | 1 - lib/errors/eager-loading-error.js | 1 - lib/errors/empty-result-error.js | 1 - lib/errors/instance-error.js | 1 - lib/errors/optimistic-lock-error.js | 1 - lib/errors/query-error.js | 1 - lib/errors/sequelize-scope-error.js | 1 - lib/errors/validation-error.js | 1 - lib/errors/validation/unique-constraint-error.js | 1 - 24 files changed, 24 deletions(-) diff --git a/lib/errors/association-error.js b/lib/errors/association-error.js index 0a273d7c7f59..d4ef83f9ae52 100644 --- a/lib/errors/association-error.js +++ b/lib/errors/association-error.js @@ -9,7 +9,6 @@ class AssociationError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeAssociationError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/base-error.js b/lib/errors/base-error.js index 15f0cd80a245..ef0060113513 100644 --- a/lib/errors/base-error.js +++ b/lib/errors/base-error.js @@ -11,7 +11,6 @@ class BaseError extends Error { constructor(message) { super(message); this.name = 'SequelizeBaseError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js index 51d1494f1bfa..b5968080b9e2 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -15,7 +15,6 @@ class BulkRecordError extends BaseError { this.name = 'SequelizeBulkRecordError'; this.errors = error; this.record = record; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection-error.js b/lib/errors/connection-error.js index 2d34ce856d59..7605786f529d 100644 --- a/lib/errors/connection-error.js +++ b/lib/errors/connection-error.js @@ -15,7 +15,6 @@ class ConnectionError extends BaseError { */ this.parent = parent; this.original = parent; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/access-denied-error.js b/lib/errors/connection/access-denied-error.js index bfa5f8e85b6e..c6bc2e8f72f8 100644 --- a/lib/errors/connection/access-denied-error.js +++ b/lib/errors/connection/access-denied-error.js @@ -9,7 +9,6 @@ class AccessDeniedError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeAccessDeniedError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/connection-acquire-timeout-error.js b/lib/errors/connection/connection-acquire-timeout-error.js index 0d9e63b1d2ab..661707b93116 100644 --- a/lib/errors/connection/connection-acquire-timeout-error.js +++ b/lib/errors/connection/connection-acquire-timeout-error.js @@ -9,7 +9,6 @@ class ConnectionAcquireTimeoutError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeConnectionAcquireTimeoutError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/connection-refused-error.js b/lib/errors/connection/connection-refused-error.js index 9b121b82f793..0c689c11aab6 100644 --- a/lib/errors/connection/connection-refused-error.js +++ b/lib/errors/connection/connection-refused-error.js @@ -9,7 +9,6 @@ class ConnectionRefusedError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeConnectionRefusedError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/connection-timed-out-error.js b/lib/errors/connection/connection-timed-out-error.js index 84171801d162..2a2004b9ab70 100644 --- a/lib/errors/connection/connection-timed-out-error.js +++ b/lib/errors/connection/connection-timed-out-error.js @@ -9,7 +9,6 @@ class ConnectionTimedOutError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeConnectionTimedOutError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/host-not-found-error.js b/lib/errors/connection/host-not-found-error.js index ac2600130469..c0493aff9280 100644 --- a/lib/errors/connection/host-not-found-error.js +++ b/lib/errors/connection/host-not-found-error.js @@ -9,7 +9,6 @@ class HostNotFoundError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeHostNotFoundError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/host-not-reachable-error.js b/lib/errors/connection/host-not-reachable-error.js index eee27beb4cc1..a6bab854f143 100644 --- a/lib/errors/connection/host-not-reachable-error.js +++ b/lib/errors/connection/host-not-reachable-error.js @@ -9,7 +9,6 @@ class HostNotReachableError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeHostNotReachableError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/invalid-connection-error.js b/lib/errors/connection/invalid-connection-error.js index 21c50973b96e..538303f31c38 100644 --- a/lib/errors/connection/invalid-connection-error.js +++ b/lib/errors/connection/invalid-connection-error.js @@ -9,7 +9,6 @@ class InvalidConnectionError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeInvalidConnectionError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database-error.js b/lib/errors/database-error.js index 7fcb67a5e166..5a21f6f3d710 100644 --- a/lib/errors/database-error.js +++ b/lib/errors/database-error.js @@ -27,7 +27,6 @@ class DatabaseError extends BaseError { * @type {Array} */ this.parameters = parent.parameters; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/exclusion-constraint-error.js b/lib/errors/database/exclusion-constraint-error.js index 581b36efbfb4..f77e52aab3ea 100644 --- a/lib/errors/database/exclusion-constraint-error.js +++ b/lib/errors/database/exclusion-constraint-error.js @@ -17,7 +17,6 @@ class ExclusionConstraintError extends DatabaseError { this.constraint = options.constraint; this.fields = options.fields; this.table = options.table; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/foreign-key-constraint-error.js b/lib/errors/database/foreign-key-constraint-error.js index ba50973c8557..9bdf02ad0c14 100644 --- a/lib/errors/database/foreign-key-constraint-error.js +++ b/lib/errors/database/foreign-key-constraint-error.js @@ -19,7 +19,6 @@ class ForeignKeyConstraintError extends DatabaseError { this.value = options.value; this.index = options.index; this.reltype = options.reltype; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/timeout-error.js b/lib/errors/database/timeout-error.js index 59dc600a060e..b67933b50f77 100644 --- a/lib/errors/database/timeout-error.js +++ b/lib/errors/database/timeout-error.js @@ -9,7 +9,6 @@ class TimeoutError extends DatabaseError { constructor(parent) { super(parent); this.name = 'SequelizeTimeoutError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/unknown-constraint-error.js b/lib/errors/database/unknown-constraint-error.js index 33d6f42985b7..e11fa666c7ef 100644 --- a/lib/errors/database/unknown-constraint-error.js +++ b/lib/errors/database/unknown-constraint-error.js @@ -17,7 +17,6 @@ class UnknownConstraintError extends DatabaseError { this.constraint = options.constraint; this.fields = options.fields; this.table = options.table; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/eager-loading-error.js b/lib/errors/eager-loading-error.js index 6963e21d5bf2..928af9c447bd 100644 --- a/lib/errors/eager-loading-error.js +++ b/lib/errors/eager-loading-error.js @@ -9,7 +9,6 @@ class EagerLoadingError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeEagerLoadingError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/empty-result-error.js b/lib/errors/empty-result-error.js index ffb2fd1bcff5..c967817d0690 100644 --- a/lib/errors/empty-result-error.js +++ b/lib/errors/empty-result-error.js @@ -9,7 +9,6 @@ class EmptyResultError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeEmptyResultError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/instance-error.js b/lib/errors/instance-error.js index 6f20e36ec729..913ce1e3b3b7 100644 --- a/lib/errors/instance-error.js +++ b/lib/errors/instance-error.js @@ -9,7 +9,6 @@ class InstanceError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeInstanceError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/optimistic-lock-error.js b/lib/errors/optimistic-lock-error.js index a745028bc203..779015a4beee 100644 --- a/lib/errors/optimistic-lock-error.js +++ b/lib/errors/optimistic-lock-error.js @@ -26,7 +26,6 @@ class OptimisticLockError extends BaseError { * @type {object} */ this.where = options.where; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/query-error.js b/lib/errors/query-error.js index c48617ac1571..3ec05cc3712a 100644 --- a/lib/errors/query-error.js +++ b/lib/errors/query-error.js @@ -9,7 +9,6 @@ class QueryError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeQueryError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/sequelize-scope-error.js b/lib/errors/sequelize-scope-error.js index f3ffd58ed7dc..f7a40ad58c71 100644 --- a/lib/errors/sequelize-scope-error.js +++ b/lib/errors/sequelize-scope-error.js @@ -9,7 +9,6 @@ class SequelizeScopeError extends BaseError { constructor(parent) { super(parent); this.name = 'SequelizeScopeError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 40c4fa5386e6..9d9a98821666 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -30,7 +30,6 @@ class ValidationError extends BaseError { } else if (this.errors.length > 0 && this.errors[0].message) { this.message = this.errors.map(err => `${err.type || err.origin}: ${err.message}`).join(',\n'); } - Error.captureStackTrace(this, this.constructor); } /** diff --git a/lib/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js index c02f842ea781..a509e88ff4fb 100644 --- a/lib/errors/validation/unique-constraint-error.js +++ b/lib/errors/validation/unique-constraint-error.js @@ -19,7 +19,6 @@ class UniqueConstraintError extends ValidationError { this.parent = options.parent; this.original = options.parent; this.sql = options.parent.sql; - Error.captureStackTrace(this, this.constructor); } } From ab5fae8ffdf2d12882d5f4f71276d55870bcbfd1 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 Mar 2019 16:57:57 +0100 Subject: [PATCH 003/414] refactor: use native padStart --- lib/dialects/postgres/query-generator.js | 4 ---- test/integration/model/create.test.js | 9 ++------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index edc2d4fb72e6..5b1d66594f05 100755 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -839,10 +839,6 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return matches.slice(0, -1); } - padInt(i) { - return i < 10 ? `0${i.toString()}` : i.toString(); - } - dataTypeMapping(tableName, attr, dataType) { if (dataType.includes('PRIMARY KEY')) { dataType = dataType.replace('PRIMARY KEY', ''); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 4118de9578a0..181403a40a98 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -913,13 +913,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { return userWithDefaults.sync({ force: true }).then(() => { return userWithDefaults.create({}).then(user => { return userWithDefaults.findByPk(user.id).then(user => { - const now = new Date(), - pad = function(number) { - if (number > 9) { - return number; - } - return `0${number}`; - }; + const now = new Date(); + const pad = number => number.toString().padStart(2, '0'); expect(user.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); }); From cd65ca3833dd8b1595cd71d23225de6b54b3c872 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 Mar 2019 20:39:06 +0100 Subject: [PATCH 004/414] refactor: replace lodash clone with spread --- .eslintrc.json | 2 +- lib/associations/belongs-to-many.js | 4 +-- lib/associations/has-many.js | 2 +- lib/dialects/postgres/query-interface.js | 7 ++--- lib/dialects/sqlite/query-interface.js | 3 +- lib/instance-validator.js | 2 +- lib/model.js | 22 +++++++------- lib/query-interface.js | 12 ++++---- lib/sequelize.js | 2 +- lib/utils.js | 7 +++-- .../abstract/connection-manager.test.js | 29 +++++++++---------- test/unit/model/destroy.test.js | 14 ++++----- test/unit/model/update.test.js | 14 ++++----- test/unit/sql/create-table.test.js | 22 +++++++++----- 14 files changed, 71 insertions(+), 71 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index ebc1c2990586..2c5351e7c6d0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -94,7 +94,7 @@ "no-case-declarations": "off" }, "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": 9, "sourceType": "script" }, "plugins": ["mocha", "jsdoc"], diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index a96e00e2268f..3f70d332d8f3 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -419,7 +419,7 @@ class BelongsToMany extends Association { let throughWhere; if (this.scope) { - scopeWhere = _.clone(this.scope); + scopeWhere = { ...this.scope }; } options.where = { @@ -660,7 +660,7 @@ class BelongsToMany extends Association { // If newInstances is null or undefined, no-op if (!newInstances) return Utils.Promise.resolve(); - options = _.clone(options) || {}; + options = { ...options }; const association = this; const sourceKey = association.sourceKey; diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 9350de9eda1f..c9a15da9127c 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -114,7 +114,7 @@ class HasMany extends Association { _injectAttributes() { const newAttributes = {}; // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m - const constraintOptions = _.clone(this.options); + const constraintOptions = { ...this.options }; newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 0375e5ca7bdf..60c40bb5b256 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -3,8 +3,6 @@ const DataTypes = require('../../data-types'); const Promise = require('../../promise'); const QueryTypes = require('../../query-types'); -const _ = require('lodash'); - /** Returns an object that handles Postgres special needs to do certain queries. @@ -56,9 +54,8 @@ function ensureEnums(qi, tableName, attributes, options, model) { // This little function allows us to re-use the same code that prepends or appends new value to enum array const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { - const valueOptions = _.clone(options); - valueOptions.before = null; - valueOptions.after = null; + // reset out after/before options since it's for every enum value + const valueOptions = { ...options, before: null, after: null }; switch (position) { case 'after': diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 150fa6373fd8..830bab25e649 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -1,6 +1,5 @@ 'use strict'; -const _ = require('lodash'); const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const QueryTypes = require('../../query-types'); @@ -89,7 +88,7 @@ function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options) { options = options || {}; return qi.describeTable(tableName, options).then(fields => { - fields[attrNameAfter] = _.clone(fields[attrNameBefore]); + fields[attrNameAfter] = { ...fields[attrNameBefore] }; delete fields[attrNameBefore]; const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); diff --git a/lib/instance-validator.js b/lib/instance-validator.js index ae0e0e4fa25d..b3ccc3d28d71 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -18,7 +18,7 @@ const validator = require('./utils/validator-extras').validator; */ class InstanceValidator { constructor(modelInstance, options) { - options = _.clone(options) || {}; + options = { ...options }; if (options.fields && !options.skip) { options.skip = _.difference(Object.keys(modelInstance.constructor.rawAttributes), options.fields); diff --git a/lib/model.js b/lib/model.js index 0c2a61a6a3dd..a2bcbee4018c 100644 --- a/lib/model.js +++ b/lib/model.js @@ -122,7 +122,7 @@ class Model { let defaults; let key; - values = values && _.clone(values) || {}; + values = values && { ...values }; if (options.isNewRecord) { defaults = {}; @@ -273,7 +273,7 @@ class Model { }; } - const existingAttributes = _.clone(this.rawAttributes); + const existingAttributes = { ...this.rawAttributes }; this.rawAttributes = {}; _.each(head, (value, attr) => { @@ -1070,7 +1070,7 @@ class Model { ['get', 'set'].forEach(type => { const opt = `${type}terMethods`; - const funcs = _.clone(_.isObject(this.options[opt]) ? this.options[opt] : {}); + const funcs = { ...this.options[opt] }; const _custom = type === 'get' ? this.prototype._customGetters : this.prototype._customSetters; _.each(funcs, (method, attribute) => { @@ -2248,7 +2248,7 @@ class Model { return this.findOne(options).then(instance => { if (instance === null) { - values = _.clone(options.defaults) || {}; + values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2321,7 +2321,7 @@ class Model { return [instance, false]; } - values = _.clone(options.defaults) || {}; + values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2396,7 +2396,7 @@ class Model { ); } - let values = _.clone(options.defaults) || {}; + let values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2598,7 +2598,7 @@ class Model { // Validate if (options.validate) { const errors = new Promise.AggregateError(); - const validateOptions = _.clone(options); + const validateOptions = { ...options }; validateOptions.hooks = options.individualHooks; return Promise.map(instances, instance => @@ -2616,7 +2616,7 @@ class Model { if (options.individualHooks) { // Create each instance individually return Promise.map(instances, instance => { - const individualOptions = _.clone(options); + const individualOptions = { ...options }; delete individualOptions.fields; delete individualOptions.individualHooks; delete individualOptions.ignoreDuplicates; @@ -3187,7 +3187,7 @@ class Model { // Hooks change values in a different way for each record // Do not run original query but save each record individually return Promise.map(instances, instance => { - const individualOptions = _.clone(options); + const individualOptions = { ...options }; delete individualOptions.individualHooks; individualOptions.hooks = false; individualOptions.validate = false; @@ -3609,7 +3609,7 @@ class Model { this.dataValues = values; } // If raw, .changed() shouldn't be true - this._previousDataValues = _.clone(this.dataValues); + this._previousDataValues = { ...this.dataValues }; } else { // Loop and call set if (options.attributes) { @@ -3636,7 +3636,7 @@ class Model { if (options.raw) { // If raw, .changed() shouldn't be true - this._previousDataValues = _.clone(this.dataValues); + this._previousDataValues = { ...this.dataValues }; } } return this; diff --git a/lib/query-interface.js b/lib/query-interface.js index 913a8fd03a93..59b322216687 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -194,7 +194,7 @@ class QueryInterface { let sql = ''; let promise; - options = _.clone(options) || {}; + options = { ...options }; if (options && options.uniqueKeys) { _.forOwn(options.uniqueKeys, uniqueKey => { @@ -246,7 +246,7 @@ class QueryInterface { */ dropTable(tableName, options) { // if we're forcing we should be cascading unless explicitly stated otherwise - options = _.clone(options) || {}; + options = { ...options }; options.cascade = options.cascade || options.force || false; let sql = this.QueryGenerator.dropTableQuery(tableName, options); @@ -906,7 +906,7 @@ class QueryInterface { let indexes = []; let indexFields; - options = _.clone(options); + options = { ...options }; if (!Utils.isWhereEmpty(where)) { wheres.push(where); @@ -991,7 +991,7 @@ class QueryInterface { * @returns {Promise} */ bulkInsert(tableName, records, options, attributes) { - options = _.clone(options) || {}; + options = { ...options }; options.type = QueryTypes.INSERT; return this.sequelize.query( @@ -1001,7 +1001,7 @@ class QueryInterface { } update(instance, tableName, values, identifier, options) { - options = _.clone(options || {}); + options = { ...options || {} }; options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); @@ -1048,7 +1048,7 @@ class QueryInterface { const cascades = []; const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); - options = _.clone(options) || {}; + options = { ...options }; // Check for a restrict field if (!!instance.constructor && !!instance.constructor.associations) { diff --git a/lib/sequelize.js b/lib/sequelize.js index 491b30cd9e6b..f0ea12f48c67 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -781,7 +781,7 @@ class Sequelize { * @returns {Promise} */ sync(options) { - options = _.clone(options) || {}; + options = { ...options }; options.hooks = options.hooks === undefined ? true : !!options.hooks; options = _.defaults(options, this.options.sync, this.options); diff --git a/lib/utils.js b/lib/utils.js index e0dae46ab7cc..83321027832e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -265,8 +265,11 @@ function toDefaultValue(value, dialect) { if (value instanceof DataTypes.NOW) { return now(dialect); } - if (_.isPlainObject(value) || Array.isArray(value)) { - return _.clone(value); + if (Array.isArray(value)) { + return value.slice(0); + } + if (_.isPlainObject(value)) { + return { ...value }; } return value; } diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index d735c4394b7f..4b0785a83c85 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -1,13 +1,12 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('../../support'), - sinon = require('sinon'), - Config = require('../../../config/config'), - ConnectionManager = require('../../../../lib/dialects/abstract/connection-manager'), - Pool = require('sequelize-pool').Pool, - _ = require('lodash'); +const chai = require('chai'); +const expect = chai.expect; +const Support = require('../../support'); +const sinon = require('sinon'); +const Config = require('../../../config/config'); +const ConnectionManager = require('../../../../lib/dialects/abstract/connection-manager'); +const Pool = require('sequelize-pool').Pool; const baseConf = Config[Support.getTestDialect()]; const poolEntry = { @@ -44,8 +43,8 @@ describe('Connection Manager', () => { it('should initialize a multiple pools with replication', () => { const options = { replication: { - write: _.clone(poolEntry), - read: [_.clone(poolEntry), _.clone(poolEntry)] + write: { ...poolEntry }, + read: [{ ...poolEntry }, { ...poolEntry }] } }; const sequelize = Support.createSequelizeInstance(options); @@ -61,14 +60,14 @@ describe('Connection Manager', () => { return; } - const slave1 = _.clone(poolEntry); - const slave2 = _.clone(poolEntry); + const slave1 = { ...poolEntry }; + const slave2 = { ...poolEntry }; slave1.host = 'slave1'; slave2.host = 'slave2'; const options = { replication: { - write: _.clone(poolEntry), + write: { ...poolEntry }, read: [slave1, slave2] } }; @@ -107,13 +106,13 @@ describe('Connection Manager', () => { }); it('should allow forced reads from the write pool', () => { - const master = _.clone(poolEntry); + const master = { ...poolEntry }; master.host = 'the-boss'; const options = { replication: { write: master, - read: [_.clone(poolEntry)] + read: [{ ...poolEntry }] } }; const sequelize = Support.createSequelizeInstance(options); diff --git a/test/unit/model/destroy.test.js b/test/unit/model/destroy.test.js index 52728552da36..e39e23696545 100644 --- a/test/unit/model/destroy.test.js +++ b/test/unit/model/destroy.test.js @@ -1,12 +1,10 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'), - _ = require('lodash'); +const { expect } = require('chai'); +const Support = require('../support'); +const current = Support.sequelize; +const sinon = require('sinon'); +const DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { @@ -22,7 +20,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.deloptions = { where: { secretValue: '1' } }; - this.cloneOptions = _.clone(this.deloptions); + this.cloneOptions = { ...this.deloptions }; this.stubDelete.resetHistory(); }); diff --git a/test/unit/model/update.test.js b/test/unit/model/update.test.js index 0dad10210842..8b6717d15d33 100644 --- a/test/unit/model/update.test.js +++ b/test/unit/model/update.test.js @@ -1,12 +1,10 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'), - _ = require('lodash'); +const { expect } = require('chai'); +const Support = require('../support'); +const current = Support.sequelize; +const sinon = require('sinon'); +const DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { describe('method update', () => { @@ -20,7 +18,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.stubUpdate = sinon.stub(current.getQueryInterface(), 'bulkUpdate').resolves([]); this.updates = { name: 'Batman', secretValue: '7' }; - this.cloneUpdates = _.clone(this.updates); + this.cloneUpdates = { ...this.updates }; }); afterEach(function() { diff --git a/test/unit/sql/create-table.test.js b/test/unit/sql/create-table.test.js index 11db2b323d03..d9a56adb0ca0 100644 --- a/test/unit/sql/create-table.test.js +++ b/test/unit/sql/create-table.test.js @@ -86,23 +86,29 @@ describe(Support.getTestDialectTeaser('SQL'), () => { if (current.dialect.name === 'postgres') { describe('IF NOT EXISTS version check', () => { - const modifiedSQL = _.clone(sql); - const createTableQueryModified = sql.createTableQuery.bind(modifiedSQL); + let backup; + before(() => { + backup = sql.sequelize.options.databaseVersion; + }); + + after(() => { + sql.sequelize.options.databaseVersion = backup; + }); it('it will not have IF NOT EXISTS for version 9.0 or below', () => { - modifiedSQL.sequelize.options.databaseVersion = '9.0.0'; - expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { + sql.sequelize.options.databaseVersion = '9.0.0'; + expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { postgres: 'CREATE TABLE "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' }); }); it('it will have IF NOT EXISTS for version 9.1 or above', () => { - modifiedSQL.sequelize.options.databaseVersion = '9.1.0'; - expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { + sql.sequelize.options.databaseVersion = '9.1.0'; + expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' }); }); it('it will have IF NOT EXISTS for default version', () => { - modifiedSQL.sequelize.options.databaseVersion = 0; - expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { + sql.sequelize.options.databaseVersion = 0; + expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' }); }); From cf10ca48d6047fce8b5959cfe204b953a5f6dd33 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 Mar 2019 22:16:58 +0100 Subject: [PATCH 005/414] refactor: replace _.defaults with spread --- lib/associations/belongs-to-many.js | 74 ++++++++++----------- lib/associations/belongs-to.js | 13 ++-- lib/associations/has-many.js | 27 +++++--- lib/associations/has-one.js | 7 +- lib/dialects/abstract/connection-manager.js | 20 ++++-- lib/dialects/abstract/query-generator.js | 18 +++-- lib/dialects/postgres/query-generator.js | 5 +- lib/dialects/sqlite/query-generator.js | 21 ++++-- lib/instance-validator.js | 7 +- lib/model-manager.js | 15 ++--- lib/model.js | 73 +++++++++++--------- lib/query-interface.js | 16 +++-- lib/sequelize.js | 14 ++-- test/support.js | 8 +-- 14 files changed, 180 insertions(+), 138 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 3f70d332d8f3..595b5be884d3 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -284,8 +284,11 @@ class BelongsToMany extends Association { const targetKey = this.target.rawAttributes[this.targetKey]; const targetKeyType = targetKey.type; const targetKeyField = this.targetKeyField; - const sourceAttribute = _.defaults({}, this.foreignKeyAttribute, { type: sourceKeyType }); - const targetAttribute = _.defaults({}, this.otherKeyAttribute, { type: targetKeyType }); + const sourceAttribute = { + type: sourceKeyType, + ...this.foreignKeyAttribute + }; + const targetAttribute = { type: targetKeyType, ...this.otherKeyAttribute }; if (this.primaryKeyDeleted === true) { targetAttribute.primaryKey = sourceAttribute.primaryKey = true; @@ -587,7 +590,7 @@ class BelongsToMany extends Association { throughAttributes = {}; } - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).length) { promises.push( @@ -609,25 +612,22 @@ class BelongsToMany extends Association { [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]) }, this.through.scope); promises.push( - this.through.model.destroy(_.defaults({ + this.through.model.destroy({ + ...options, where - }, options)) + }) ); } if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { - let attributes = {}; - - attributes[identifier] = sourceInstance.get(sourceKey); - attributes[foreignIdentifier] = unassociatedObject.get(targetKey); - - attributes = _.defaults(attributes, unassociatedObject[this.through.model.name], defaultAttributes); - - Object.assign(attributes, this.through.scope); - attributes = Object.assign(attributes, this.through.scope); - - return attributes; + return { + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: unassociatedObject.get(targetKey), + ...defaultAttributes, + ...unassociatedObject[this.through.model.name], + ...this.through.scope + }; }); promises.push(this.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); @@ -636,7 +636,7 @@ class BelongsToMany extends Association { return Utils.Promise.all(promises); }; - return this.through.model.findAll(_.defaults({ where, raw: true }, options)) + return this.through.model.findAll({ ...options, where, raw: true }) .then(currentRows => updateAssociations(currentRows)) .catch(error => { if (error instanceof EmptyResultError) return updateAssociations([]); @@ -689,7 +689,7 @@ class BelongsToMany extends Association { unassociatedObjects.push(obj); } else { const throughAttributes = obj[association.through.model.name]; - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).some(attribute => attributes[attribute] !== existingAssociation[attribute])) { changedAssociations.push(obj); @@ -700,39 +700,33 @@ class BelongsToMany extends Association { if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { const throughAttributes = unassociatedObject[association.through.model.name]; - const attributes = _.defaults({}, throughAttributes, defaultAttributes); - - attributes[identifier] = sourceInstance.get(sourceKey); - attributes[foreignIdentifier] = unassociatedObject.get(targetKey); - - Object.assign(attributes, association.through.scope); - - return attributes; + return { + ...defaultAttributes, + ...throughAttributes, + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: unassociatedObject.get(targetKey), + ...association.through.scope + }; }); promises.push(association.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); } for (const assoc of changedAssociations) { - let throughAttributes = assoc[association.through.model.name]; - const attributes = _.defaults({}, throughAttributes, defaultAttributes); - // Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object) - if (throughAttributes instanceof association.through.model) { - throughAttributes = {}; - } - const where = { - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: assoc.get(targetKey) - }; - + const attributes = { ...defaultAttributes, ...assoc[association.through.model.name] }; - promises.push(association.through.model.update(attributes, Object.assign(options, { where }))); + promises.push(association.through.model.update(attributes, Object.assign(options, { + where: { + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: assoc.get(targetKey) + } + }))); } return Utils.Promise.all(promises); }; - return association.through.model.findAll(_.defaults({ where, raw: true }, options)) + return association.through.model.findAll({ ...options, where, raw: true }) .then(currentRows => updateAssociations(currentRows)) .then(([associations]) => associations) .catch(error => { @@ -762,7 +756,7 @@ class BelongsToMany extends Association { [association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.targetKey)) }; - return association.through.model.destroy(_.defaults({ where }, options)); + return association.through.model.destroy({ ...options, where }); } /** diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index eafeedb45684..7bb12acde163 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -79,12 +79,13 @@ class BelongsTo extends Association { // the id is in the source table _injectAttributes() { - const newAttributes = {}; - - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { - type: this.options.keyType || this.target.rawAttributes[this.targetKey].type, - allowNull: true - }); + const newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.target.rawAttributes[this.targetKey].type, + allowNull: true, + ...this.foreignKeyAttribute + } + }; if (this.options.constraints !== false) { const source = this.source.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index c9a15da9127c..5f0e6c5d7d05 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -116,10 +116,11 @@ class HasMany extends Association { // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m const constraintOptions = { ...this.options }; - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { + newAttributes[this.foreignKey] = { type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, - allowNull: true - }); + allowNull: true, + ...this.foreignKeyAttribute + }; if (this.options.constraints !== false) { const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; @@ -325,7 +326,11 @@ class HasMany extends Association { targetInstances = this.toInstanceArray(targetInstances); } - return this.get(sourceInstance, _.defaults({ scope: false, raw: true }, options)).then(oldAssociations => { + return this.get(sourceInstance, { + ...options, + scope: false, + raw: true + }).then(oldAssociations => { const promises = []; const obsoleteAssociations = oldAssociations.filter(old => !targetInstances.find(obj => @@ -353,9 +358,10 @@ class HasMany extends Association { promises.push(this.target.unscoped().update( update, - _.defaults({ + { + ...options, where: updateWhere - }, options) + } )); } @@ -372,9 +378,10 @@ class HasMany extends Association { promises.push(this.target.unscoped().update( update, - _.defaults({ + { + ...options, where: updateWhere - }, options) + } )); } @@ -408,7 +415,7 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).return(sourceInstance); + return this.target.unscoped().update(update, { ...options, where }).return(sourceInstance); } /** @@ -434,7 +441,7 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).return(this); + return this.target.unscoped().update(update, { ...options, where }).return(this); } /** diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index b955fb5a39ff..0320fd563ce0 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -80,10 +80,11 @@ class HasOne extends Association { _injectAttributes() { const newAttributes = {}; - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { + newAttributes[this.foreignKey] = { type: this.options.keyType || this.source.rawAttributes[this.sourceKey].type, - allowNull: true - }); + allowNull: true, + ...this.foreignKeyAttribute + }; if (this.options.constraints !== false) { const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index b37d412e0a2c..af1aa3f44b23 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -30,14 +30,15 @@ class ConnectionManager { throw new Error('Support for pool:false was removed in v4.0'); } - config.pool = _.defaults(config.pool || {}, { + config.pool = { max: 5, min: 0, idle: 10000, acquire: 60000, evict: 1000, - validate: this._validate.bind(this) - }); + validate: this._validate.bind(this), + ...config.pool + }; this.initPools(); } @@ -148,12 +149,19 @@ class ConnectionManager { config.replication.read = [config.replication.read]; } + const configWithoutReplication = _.omit(this.config, 'replication'); + // Map main connection config - config.replication.write = _.defaults(config.replication.write, _.omit(config, 'replication')); + config.replication.write = { + ...configWithoutReplication, + ...config.replication.write + }; // Apply defaults to each read config - config.replication.read = config.replication.read.map(readConfig => - _.defaults(readConfig, _.omit(this.config, 'replication')) + config.replication.read = config.replication.read.map(readConfig => ({ + ...configWithoutReplication, + readConfig + }) ); // custom pooling for replication (original author @janmeier) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 441b4119246f..bd7bff3f8357 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -98,8 +98,10 @@ class QueryGenerator { * @private */ insertQuery(table, valueHash, modelAttributes, options) { - options = options || {}; - _.defaults(options, this.options); + options = { + ...this.options, + ...options + }; const modelAttributeMap = {}; const fields = []; @@ -338,6 +340,7 @@ class QueryGenerator { * @private */ updateQuery(tableName, attrValueHash, where, options, attributes) { + // TODO: Mutates argument, should be fixed! options = options || {}; _.defaults(options, this.options); @@ -426,7 +429,10 @@ class QueryGenerator { } } - const whereOptions = _.defaults({ bindParam }, options); + const whereOptions = { + ...options, + bindParam + }; if (values.length === 0) { return ''; @@ -452,8 +458,10 @@ class QueryGenerator { * @param {Object} attributes */ arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes) { - options = options || {}; - _.defaults(options, { returning: true }); + options = { + returning: true, + ...options + }; attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull); diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 5b1d66594f05..6f14837dc6d0 100755 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -343,7 +343,10 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { upsertQuery(tableName, insertValues, updateValues, where, model, options) { const primaryField = this.quoteIdentifier(model.primaryKeyField); - const upsertOptions = _.defaults({ bindParam: false }, options); + const upsertOptions = { + ...options, + bindParam: false + }; const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 9fa931bec208..cd131bc69ca4 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -179,9 +179,11 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { options.ignoreDuplicates = true; const bind = []; - const bindParam = this.bindParam(bind); - const upsertOptions = _.defaults({ bindParam }, options); + const upsertOptions = { + ...options, + bindParam: this.bindParam(bind) + }; const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); @@ -191,8 +193,10 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { } updateQuery(tableName, attrValueHash, where, options, attributes) { - options = options || {}; - _.defaults(options, this.options); + options = { + ...this.options, + ...options + }; attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options); @@ -221,7 +225,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { } let query; - const whereOptions = _.defaults({ bindParam }, options); + const whereOptions = { ...options, bindParam }; if (options.limit) { query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${this.whereQuery(where, whereOptions)} LIMIT ${this.escape(options.limit)})`; @@ -239,8 +243,11 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { ].join(''); } - deleteQuery(tableName, where, options = {}, model) { - _.defaults(options, this.options); + deleteQuery(tableName, where, options, model) { + options = { + ...this.options, + ...options + }; let whereClause = this.getWhereConditions(where, null, model, options); diff --git a/lib/instance-validator.js b/lib/instance-validator.js index b3ccc3d28d71..385294f545f7 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -25,10 +25,11 @@ class InstanceValidator { } // assign defined and default options - this.options = _.defaults(options, { + this.options = { skip: [], - hooks: true - }); + hooks: true, + ...options + }; this.modelInstance = modelInstance; diff --git a/lib/model-manager.js b/lib/model-manager.js index 383735e5c19c..f0ef52e26a46 100644 --- a/lib/model-manager.js +++ b/lib/model-manager.js @@ -22,12 +22,8 @@ class ModelManager { delete this.sequelize.models[modelToRemove.name]; } - getModel(against, options) { - options = _.defaults(options || {}, { - attribute: 'name' - }); - - return this.models.find(model => model[options.attribute] === against); + getModel(against, { attribute = 'name' } = {}) { + return this.models.find(model => model[attribute] === against); } get all() { @@ -48,9 +44,10 @@ class ModelManager { let sorted; let dep; - options = _.defaults(options || {}, { - reverse: true - }); + options = { + reverse: true, + ...options + }; for (const model of this.models) { let deps = []; diff --git a/lib/model.js b/lib/model.js index a2bcbee4018c..a6901b7c493e 100644 --- a/lib/model.js +++ b/lib/model.js @@ -626,7 +626,7 @@ class Model { if (!include.include) include.include = []; const through = include.association.through; - include.through = _.defaults(include.through || {}, { + include.through = _.defaults(include.through, { model: through.model, as: through.model.name, association: { @@ -636,7 +636,6 @@ class Model { parent: include }); - if (through.scope) { include.through.where = include.through.where ? { [Op.and]: [include.through.where, through.scope] } : through.scope; } @@ -763,10 +762,11 @@ class Model { throw new Error('Missing "fields" property for index definition'); } - index = _.defaults(index, { + index = { type: '', - parser: null - }); + parser: null, + ...index + }; if (index.type && index.type.toLowerCase() === 'unique') { index.unique = true; @@ -1693,9 +1693,10 @@ class Model { const tableNames = {}; tableNames[this.getTableName(options)] = true; - options = Utils.cloneDeep(options); - - _.defaults(options, { hooks: true }); + options = { + hooks: true, + ...Utils.cloneDeep(options) + }; // set rejectOnEmpty option, defaults to model options options.rejectOnEmpty = Object.prototype.hasOwnProperty.call(options, 'rejectOnEmpty') @@ -1920,9 +1921,10 @@ class Model { } // Bypass a possible overloaded findAll. - return this.findAll(_.defaults(options, { - plain: true - })); + return this.findAll({ + plain: true, + ...options + }); } /** @@ -2019,9 +2021,11 @@ class Model { */ static count(options) { return Promise.try(() => { - options = Utils.cloneDeep(options); - options = _.defaults(options, { hooks: true }); - options.raw = true; + options = { + hooks: true, + ...options, + raw: true + }; if (options.hooks) { return this.runHooks('beforeCount', options); } @@ -2896,14 +2900,14 @@ class Model { throw new Error('Expected plain object, array or sequelize method in the options.where parameter of model.destroy.'); } - options = _.defaults(options, { + options = { hooks: true, individualHooks: false, force: false, cascade: false, - restartIdentity: false - }); - + restartIdentity: false, + ...options + }; options.type = QueryTypes.BULKDELETE; Utils.mapOptionFieldNames(options, this); @@ -3051,14 +3055,15 @@ class Model { this._injectScope(options); this._optionsMustContainWhere(options); - options = this._paranoidClause(this, _.defaults(options, { + options = this._paranoidClause(this, { validate: true, hooks: true, individualHooks: false, returning: false, force: false, - sideEffects: true - })); + sideEffects: true, + ...options + }); options.type = QueryTypes.BULKUPDATE; @@ -3415,9 +3420,11 @@ class Model { * @returns {Promise} returns an array of affected rows and affected count with `options.returning: true`, whenever supported by dialect */ static decrement(fields, options) { - options = _.defaults({ increment: false }, options, { - by: 1 - }); + options = { + by: 1, + ...options, + increment: false + }; return this.increment(fields, options); } @@ -3827,11 +3834,11 @@ class Model { throw new Error('The second argument was removed in favor of the options object.'); } - options = Utils.cloneDeep(options); - options = _.defaults(options, { + options = { hooks: true, - validate: true - }); + validate: true, + ...Utils.cloneDeep(options) + }; if (!options.fields) { if (this.isNewRecord) { @@ -4216,7 +4223,7 @@ class Model { this.setDataValue(attributeName, new Date()); } - return this.save(_.defaults({ hooks: false }, options)); + return this.save({ ...options, hooks: false }); } return this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options)); }).tap(() => { @@ -4355,9 +4362,11 @@ class Model { * @returns {Promise} */ decrement(fields, options) { - options = _.defaults({ increment: false }, options, { - by: 1 - }); + options = { + by: 1, + ...options, + increment: false + }; return this.increment(fields, options); } diff --git a/lib/query-interface.js b/lib/query-interface.js index 59b322216687..db7e75637145 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -1097,8 +1097,10 @@ class QueryInterface { * @returns {Promise} */ bulkDelete(tableName, where, options, model) { - options = Utils.cloneDeep(options); - options = _.defaults(options, { limit: null }); + options = { + limit: null, + ...Utils.cloneDeep(options) + }; if (options.truncate === true) { return this.sequelize.query( @@ -1107,7 +1109,7 @@ class QueryInterface { ); } - if (typeof identifier === 'object') where = Utils.cloneDeep(where); + if (typeof where === 'object') where = Utils.cloneDeep(where); return this.sequelize.query( this.QueryGenerator.deleteQuery(tableName, where, options, model), @@ -1147,12 +1149,12 @@ class QueryInterface { } rawSelect(tableName, options, attributeSelector, Model) { - options = Utils.cloneDeep(options); - options = _.defaults(options, { + options = { raw: true, plain: true, - type: QueryTypes.SELECT - }); + type: QueryTypes.SELECT, + ...Utils.cloneDeep(options) + }; const sql = this.QueryGenerator.selectQuery(tableName, options, Model); diff --git a/lib/sequelize.js b/lib/sequelize.js index f0ea12f48c67..d4cea6ffc1ea 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -548,11 +548,12 @@ class Sequelize { options.fieldMap = _.get(options, 'model.fieldAttributeMap', {}); } - options = _.defaults(options, { + options = { // eslint-disable-next-line no-console logging: Object.prototype.hasOwnProperty.call(this.options, 'logging') ? this.options.logging : console.log, - searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT' - }); + searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT', + ...options + }; if (!options.type) { if (options.model || options.nest || options.plain) { @@ -781,9 +782,12 @@ class Sequelize { * @returns {Promise} */ sync(options) { - options = { ...options }; + options = { + ...this.options, + ...this.options.sync, + ...options + }; options.hooks = options.hooks === undefined ? true : !!options.hooks; - options = _.defaults(options, this.options.sync, this.options); if (options.match) { if (!options.match.test(this.config.database)) { diff --git a/test/support.js b/test/support.js index 83975b9621ac..9508aaf15917 100644 --- a/test/support.js +++ b/test/support.js @@ -2,7 +2,6 @@ const fs = require('fs'); const path = require('path'); -const _ = require('lodash'); const Sequelize = require('../index'); const Config = require('./config/config'); const chai = require('chai'); @@ -55,15 +54,16 @@ const Support = { const config = Config[options.dialect]; - const sequelizeOptions = _.defaults(options, { + const sequelizeOptions = { host: options.host || config.host, logging: process.env.SEQ_LOG ? console.log : false, dialect: options.dialect, port: options.port || process.env.SEQ_PORT || config.port, pool: config.pool, dialectOptions: options.dialectOptions || config.dialectOptions || {}, - minifyAliases: options.minifyAliases || config.minifyAliases - }); + minifyAliases: options.minifyAliases || config.minifyAliases, + ...options + }; if (process.env.DIALECT === 'postgres-native') { sequelizeOptions.native = true; From 8410b5debf14078903376c3bb97268e6691c6837 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 Mar 2019 23:45:46 +0100 Subject: [PATCH 006/414] build: nodejs v8 and up BREAKING CHANGE: Only NodeJS v8 and greater are now supported --- .travis.yml | 14 +++++++------- appveyor.yml | 2 +- docs/index.md | 2 +- docs/upgrade-to-v6.md | 9 +++++++++ 4 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 docs/upgrade-to-v6.md diff --git a/.travis.yml b/.travis.yml index 9d2fc64f9057..5c38aeb69f48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,22 +48,22 @@ jobs: - npm run lint-docs - npm run test-typings - stage: test - node_js: '6' - env: DIALECT=sqlite + node_js: '8' + env: DIALECT=sqlite TSC=true - stage: test - node_js: '6' + node_js: '8' sudo: required env: MARIADB_VER=mariadb-103 SEQ_MARIADB_PORT=8960 DIALECT=mariadb - stage: test - node_js: '6' + node_js: '8' sudo: required env: MYSQL_VER=mysql-57 SEQ_MYSQL_PORT=8980 DIALECT=mysql - stage: test - node_js: '6' + node_js: '8' sudo: required env: POSTGRES_VER=postgres-10 SEQ_PG_PORT=8991 DIALECT=postgres - stage: test - node_js: '6' + node_js: '8' sudo: required env: POSTGRES_VER=postgres-10 SEQ_PG_PORT=8991 SEQ_PG_MINIFY_ALIASES=1 DIALECT=postgres script: @@ -73,7 +73,7 @@ jobs: sudo: required env: POSTGRES_VER=postgres-95 SEQ_PG_PORT=8990 DIALECT=postgres-native - stage: release - node_js: '8' + node_js: '10' script: - npm run semantic-release before_deploy: diff --git a/appveyor.yml b/appveyor.yml index 5a0826006d61..b8c55b21153f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ shallow_clone: true environment: matrix: - - { NODE_VERSION: 6, DIALECT: mssql, COVERAGE: true } + - { NODE_VERSION: 8, DIALECT: mssql, COVERAGE: true } install: - ps: Install-Product node $env:NODE_VERSION x64 diff --git a/docs/index.md b/docs/index.md index bdf8af2276f0..15d093bd625d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. -Sequelize follows [SEMVER](http://semver.org). Supports Node v6 and above to use ES6 features. +Sequelize follows [SEMVER](http://semver.org). Supports Node v8 and above to use ES2018 features. **Sequelize v5** was released on March 13, 2019. [Official TypeScript typings are now included](manual/typescript). diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md new file mode 100644 index 000000000000..d587cf0e9032 --- /dev/null +++ b/docs/upgrade-to-v6.md @@ -0,0 +1,9 @@ +# Upgrade to v5 + +Sequelize v5 is the next major release after v4 + +## Breaking Changes + +### Support for Node 8 and up + +Sequelize v6 will only support Node 8 and up From 076a7622a2d8177b406b75d6d7b303177271fe40 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sun, 24 Mar 2019 04:09:27 +0100 Subject: [PATCH 007/414] refactor: use default parameters --- README.md | 9 ++- docs/upgrade-to-v6.md | 4 +- lib/associations/belongs-to-many.js | 64 +++++++-------- lib/associations/belongs-to.js | 5 +- lib/associations/has-one.js | 5 +- lib/associations/helpers.js | 4 +- lib/dialects/abstract/connection-manager.js | 5 +- lib/dialects/abstract/query-generator.js | 38 +++------ .../abstract/query-generator/helpers/quote.js | 4 +- lib/dialects/abstract/query.js | 5 +- lib/dialects/mariadb/query-generator.js | 7 +- lib/dialects/mssql/query-generator.js | 14 ++-- lib/dialects/mssql/query-interface.js | 4 +- lib/dialects/mysql/query-generator.js | 12 +-- lib/dialects/mysql/query-interface.js | 4 +- lib/dialects/postgres/query-generator.js | 17 ++-- lib/dialects/sqlite/connection-manager.js | 3 +- lib/dialects/sqlite/query-generator.js | 4 +- lib/dialects/sqlite/query-interface.js | 12 +-- .../database/exclusion-constraint-error.js | 3 +- .../database/foreign-key-constraint-error.js | 3 +- .../database/unknown-constraint-error.js | 3 +- lib/errors/optimistic-lock-error.js | 3 +- .../validation/unique-constraint-error.js | 3 +- lib/hooks.js | 4 +- lib/model.js | 26 +++---- lib/query-interface.js | 77 +++++++------------ lib/sequelize.js | 2 +- lib/transaction.js | 4 +- lib/utils.js | 6 +- test/integration/sequelize/deferrable.test.js | 4 +- test/support.js | 6 +- 32 files changed, 136 insertions(+), 228 deletions(-) diff --git a/README.md b/README.md index 2ec354f6a412..3f79f023ce6c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,11 @@ Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite an Sequelize follows [SEMVER](http://semver.org). Supports Node v6 and above to use ES6 features. -New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.org/master). You might also be interested in the [API Reference](https://sequelize.org/master/identifiers). +New to Sequelize? Take a look at the [Tutorials and Guides](http://docs.sequelizejs.com/). You might also be interested in the [API Reference](https://docs.sequelizejs.com/identifiers). + +## v6 Release + +You can find the upgrade guide and changelog [here](http://docs.sequelizejs.com/manual/upgrade-to-v6.html). ## Table of Contents - [Installation](#installation) @@ -28,7 +32,7 @@ New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.or ## Installation ```bash -$ npm install --save sequelize # This will install v5 +$ npm install --save sequelize # This will install v6 # And one of the following: $ npm install --save pg pg-hstore # Postgres @@ -39,6 +43,7 @@ $ npm install --save tedious # Microsoft SQL Server ``` ## Documentation +- [v6 Documentation](http://docs.sequelizejs.com) - [v5 Documentation](https://sequelize.org/master) - [v4 Documentation](https://sequelize.org/v4) - [v3 Documentation](https://sequelize.org/v3) diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md index d587cf0e9032..a24ad4fc87cd 100644 --- a/docs/upgrade-to-v6.md +++ b/docs/upgrade-to-v6.md @@ -1,6 +1,6 @@ -# Upgrade to v5 +# Upgrade to v6 -Sequelize v5 is the next major release after v4 +Sequelize v6 is the next major release after v4 ## Breaking Changes diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 595b5be884d3..c590aa21bc14 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -414,8 +414,8 @@ class BelongsToMany extends Association { * * @returns {Promise>} */ - get(instance, options) { - options = Utils.cloneDeep(options) || {}; + get(instance, options = {}) { + options = Utils.cloneDeep(options); const through = this.through; let scopeWhere; @@ -551,7 +551,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - set(sourceInstance, newAssociatedObjects, options) { + set(sourceInstance, newAssociatedObjects, options = {}) { options = options || {}; const sourceKey = this.sourceKey; @@ -662,21 +662,20 @@ class BelongsToMany extends Association { options = { ...options }; - const association = this; - const sourceKey = association.sourceKey; - const targetKey = association.targetKey; - const identifier = association.identifier; - const foreignIdentifier = association.foreignIdentifier; + const sourceKey = this.sourceKey; + const targetKey = this.targetKey; + const identifier = this.identifier; + const foreignIdentifier = this.foreignIdentifier; const defaultAttributes = options.through || {}; - newInstances = association.toInstanceArray(newInstances); + newInstances = this.toInstanceArray(newInstances); const where = { [identifier]: sourceInstance.get(sourceKey), [foreignIdentifier]: newInstances.map(newInstance => newInstance.get(targetKey)) }; - Object.assign(where, association.through.scope); + Object.assign(where, this.through.scope); const updateAssociations = currentRows => { const promises = []; @@ -688,7 +687,7 @@ class BelongsToMany extends Association { if (!existingAssociation) { unassociatedObjects.push(obj); } else { - const throughAttributes = obj[association.through.model.name]; + const throughAttributes = obj[this.through.model.name]; const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).some(attribute => attributes[attribute] !== existingAssociation[attribute])) { @@ -699,23 +698,23 @@ class BelongsToMany extends Association { if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { - const throughAttributes = unassociatedObject[association.through.model.name]; + const throughAttributes = unassociatedObject[this.through.model.name]; return { ...defaultAttributes, ...throughAttributes, [identifier]: sourceInstance.get(sourceKey), [foreignIdentifier]: unassociatedObject.get(targetKey), - ...association.through.scope + ...this.through.scope }; }); - promises.push(association.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); + promises.push(this.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); } for (const assoc of changedAssociations) { - const attributes = { ...defaultAttributes, ...assoc[association.through.model.name] }; + const attributes = { ...defaultAttributes, ...assoc[this.through.model.name] }; - promises.push(association.through.model.update(attributes, Object.assign(options, { + promises.push(this.through.model.update(attributes, Object.assign(options, { where: { [identifier]: sourceInstance.get(sourceKey), [foreignIdentifier]: assoc.get(targetKey) @@ -726,7 +725,7 @@ class BelongsToMany extends Association { return Utils.Promise.all(promises); }; - return association.through.model.findAll({ ...options, where, raw: true }) + return this.through.model.findAll({ ...options, where, raw: true }) .then(currentRows => updateAssociations(currentRows)) .then(([associations]) => associations) .catch(error => { @@ -744,19 +743,15 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - remove(sourceInstance, oldAssociatedObjects, options) { - const association = this; - - options = options || {}; - - oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects); + remove(sourceInstance, oldAssociatedObjects, options = {}) { + oldAssociatedObjects = this.toInstanceArray(oldAssociatedObjects); const where = { - [association.identifier]: sourceInstance.get(association.sourceKey), - [association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.targetKey)) + [this.identifier]: sourceInstance.get(this.sourceKey), + [this.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(this.targetKey)) }; - return association.through.model.destroy({ ...options, where }); + return this.through.model.destroy({ ...options, where }); } /** @@ -769,28 +764,23 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - create(sourceInstance, values, options) { - const association = this; - - options = options || {}; - values = values || {}; - + create(sourceInstance, values = {}, options = {}) { if (Array.isArray(options)) { options = { fields: options }; } - if (association.scope) { - Object.assign(values, association.scope); + if (this.scope) { + Object.assign(values, this.scope); if (options.fields) { - options.fields = options.fields.concat(Object.keys(association.scope)); + options.fields = options.fields.concat(Object.keys(this.scope)); } } // Create the related model instance - return association.target.create(values, options).then(newAssociatedObject => - sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject) + return this.target.create(values, options).then(newAssociatedObject => + sourceInstance[this.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject) ); } diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index 7bb12acde163..aba6d0a05214 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -225,10 +225,7 @@ class BelongsTo extends Association { * * @returns {Promise} The created target model */ - create(sourceInstance, values, options) { - values = values || {}; - options = options || {}; - + create(sourceInstance, values = {}, options = {}) { return this.target.create(values, options) .then(newAssociatedObject => sourceInstance[this.accessors.set](newAssociatedObject, options) .then(() => newAssociatedObject) diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index 0320fd563ce0..b1893d419bc1 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -244,10 +244,7 @@ class HasOne extends Association { * * @returns {Promise} The created target model */ - create(sourceInstance, values, options) { - values = values || {}; - options = options || {}; - + create(sourceInstance, values = {}, options = {}) { if (this.scope) { for (const attribute of Object.keys(this.scope)) { values[attribute] = this.scope[attribute]; diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js index 45c68915fdd7..0849e36f325e 100644 --- a/lib/associations/helpers.js +++ b/lib/associations/helpers.js @@ -52,9 +52,7 @@ exports.addForeignKeyConstraints = addForeignKeyConstraints; * @param {Object} aliases Mapping between model and association method names * */ -function mixinMethods(association, obj, methods, aliases) { - aliases = aliases || {}; - +function mixinMethods(association, obj, methods, aliases = {}) { for (const method of methods) { // don't override custom methods if (!Object.prototype.hasOwnProperty.call(obj, association.accessors[method])) { diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index af1aa3f44b23..e5f92342871a 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -160,7 +160,7 @@ class ConnectionManager { // Apply defaults to each read config config.replication.read = config.replication.read.map(readConfig => ({ ...configWithoutReplication, - readConfig + ...readConfig }) ); @@ -244,8 +244,7 @@ class ConnectionManager { * * @returns {Promise} */ - getConnection(options) { - options = options || {}; + getConnection(options = {}) { let promise; if (this.sequelize.options.databaseVersion === 0) { diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index bd7bff3f8357..b875465678b3 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -38,9 +38,7 @@ class QueryGenerator { this._dialect = options._dialect; } - extractTableDetails(tableName, options) { - options = options || {}; - tableName = tableName || {}; + extractTableDetails(tableName = {}, options = {}) { return { schema: tableName.schema || options.schema || 'public', tableName: _.isPlainObject(tableName) ? tableName.tableName : tableName, @@ -263,10 +261,7 @@ class QueryGenerator { * * @private */ - bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { - options = options || {}; - fieldMappedAttributes = fieldMappedAttributes || {}; - + bulkInsertQuery(tableName, fieldValueHashes, options = {}, fieldMappedAttributes = {}) { const tuples = []; const serials = {}; const allAttributes = []; @@ -457,7 +452,7 @@ class QueryGenerator { * @param {Object} options * @param {Object} attributes */ - arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes) { + arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes = {}) { options = { returning: true, ...options @@ -483,7 +478,6 @@ class QueryGenerator { values.push(`${this.quoteIdentifier(key)}=${this.quoteIdentifier(key)}${operator} ${this.escape(value)}`); } - attributes = attributes || {}; for (const key in attributes) { const value = attributes[key]; values.push(`${this.quoteIdentifier(key)}=${this.escape(value)}`); @@ -511,9 +505,7 @@ class QueryGenerator { - rawTablename, the name of the table, without schema. Used to create the name of the index @private */ - addIndexQuery(tableName, attributes, options, rawTablename) { - options = options || {}; - + addIndexQuery(tableName, attributes, options = {}, rawTablename) { if (!Array.isArray(attributes)) { options = attributes; attributes = undefined; @@ -612,8 +604,7 @@ class QueryGenerator { return _.compact(ind).join(' '); } - addConstraintQuery(tableName, options) { - options = options || {}; + addConstraintQuery(tableName, options = {}) { const constraintSnippet = this.getConstraintSnippet(tableName, options); if (typeof tableName === 'string') { @@ -967,9 +958,7 @@ class QueryGenerator { Escape a value (e.g. a string, number or date) @private */ - escape(value, field, options) { - options = options || {}; - + escape(value, field, options = {}) { if (value !== null && value !== undefined) { if (value instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(value); @@ -1005,9 +994,7 @@ class QueryGenerator { Returns a bind parameter representation of a value (e.g. a string, number or date) @private */ - format(value, field, options, bindParam) { - options = options || {}; - + format(value, field, options = {}, bindParam) { if (value !== null && value !== undefined) { if (value instanceof Utils.SequelizeMethod) { throw new Error('Cannot pass SequelizeMethod as a bind parameter - use escape instead'); @@ -1122,8 +1109,7 @@ class QueryGenerator { - offset -> An offset value to start from. Only useable with limit! @private */ - selectQuery(tableName, options, model) { - options = options || {}; + selectQuery(tableName, options = {}, model) { const limit = options.limit; const mainQueryItems = []; const subQueryItems = []; @@ -2568,7 +2554,7 @@ class QueryGenerator { Takes something and transforms it into values of a where condition. @private */ - getWhereConditions(smth, tableName, factory, options, prepend) { + getWhereConditions(smth, tableName, factory, options = {}, prepend = true) { const where = {}; if (Array.isArray(tableName)) { @@ -2578,12 +2564,6 @@ class QueryGenerator { } } - options = options || {}; - - if (prepend === undefined) { - prepend = true; - } - if (smth && smth instanceof Utils.SequelizeMethod) { // Checking a property is cheaper than a lot of instanceof calls return this.handleSequelizeMethod(smth, tableName, factory, options, prepend); } diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js index 0c1561d2ab7a..8fd0bca54341 100644 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ b/lib/dialects/abstract/query-generator/helpers/quote.js @@ -32,10 +32,10 @@ const postgresReservedWords = 'all,analyse,analyze,and,any,array,as,asc,asymmetr * @returns {string} * @private */ -function quoteIdentifier(dialect, identifier, options) { +function quoteIdentifier(dialect, identifier, options = {}) { if (identifier === '*') return identifier; - options = Utils.defaults(options || {}, { + options = Utils.defaults(options, { force: false, quoteIdentifiers: true }); diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 4b3c538fba78..68122156cc38 100755 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -20,7 +20,7 @@ class AbstractQuery { raw: false, // eslint-disable-next-line no-console logging: console.log - }, options || {}); + }, options); this.checkLoggingOption(); } @@ -44,12 +44,11 @@ class AbstractQuery { * @param {Object} [options] * @private */ - static formatBindParameters(sql, values, dialect, replacementFunc, options) { + static formatBindParameters(sql, values, dialect, replacementFunc, options = {}) { if (!values) { return [sql, []]; } - options = options || {}; if (typeof replacementFunc !== 'function') { options = replacementFunc || {}; replacementFunc = undefined; diff --git a/lib/dialects/mariadb/query-generator.js b/lib/dialects/mariadb/query-generator.js index 3422dbeb9a04..d0c0ea5b1076 100644 --- a/lib/dialects/mariadb/query-generator.js +++ b/lib/dialects/mariadb/query-generator.js @@ -4,10 +4,11 @@ const MySQLQueryGenerator = require('../mysql/query-generator'); class MariaDBQueryGenerator extends MySQLQueryGenerator { createSchema(schema, options) { - options = Object.assign({ + options = { charset: null, - collate: null - }, options || {}); + collate: null, + ...options + }; const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 0edb8dc774bc..5b69a1efbbd2 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -16,10 +16,10 @@ const throwMethodUndefined = function(methodName) { class MSSQLQueryGenerator extends AbstractQueryGenerator { createDatabaseQuery(databaseName, options) { - options = Object.assign({ - collate: null - }, options || {}); - + options = { + collate: null, + ...options + }; const collation = options.collate ? `COLLATE ${this.escape(options.collate)}` : ''; return [ @@ -301,17 +301,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { return `EXEC sp_rename '${this.quoteTable(tableName)}.${attrBefore}', '${newName}', 'COLUMN';`; } - bulkInsertQuery(tableName, attrValueHashes, options, attributes) { + bulkInsertQuery(tableName, attrValueHashes, options = {}, attributes = {}) { const quotedTable = this.quoteTable(tableName); - options = options || {}; - attributes = attributes || {}; const tuples = []; const allAttributes = []; const allQueries = []; - - let needIdentityInsertWrapper = false, outputFragment = ''; diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index 631cd19caeb3..a34eed2c6797 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -20,8 +20,8 @@ @private */ -const removeColumn = function(qi, tableName, attributeName, options) { - options = Object.assign({ raw: true }, options || {}); +const removeColumn = function(qi, tableName, attributeName, options = {}) { + options = Object.assign({ raw: true }, options); const findConstraintSql = qi.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); return qi.sequelize.query(findConstraintSql, options) diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index f6f23f1a0752..2454f02f29e5 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -35,11 +35,11 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { }); } - createDatabaseQuery(databaseName, options) { + createDatabaseQuery(databaseName, options = {}) { options = Object.assign({ charset: null, collate: null - }, options || {}); + }, options); const database = this.quoteIdentifier(databaseName); const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; @@ -64,12 +64,12 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { return 'SELECT VERSION() as `version`'; } - createTableQuery(tableName, attributes, options) { + createTableQuery(tableName, attributes, options = {}) { options = Object.assign({ engine: 'InnoDB', charset: null, rowFormat: null - }, options || {}); + }, options); const primaryKeys = []; const foreignKeys = {}; @@ -299,8 +299,8 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { return query + limit; } - showIndexesQuery(tableName, options) { - return `SHOW INDEX FROM ${this.quoteTable(tableName)}${(options || {}).database ? ` FROM \`${options.database}\`` : ''}`; + showIndexesQuery(tableName, options = {}) { + return `SHOW INDEX FROM ${this.quoteTable(tableName)}${options.database ? ` FROM \`${options.database}\`` : ''}`; } showConstraintsQuery(table, constraintName) { diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 48e4aba3bfdd..5f8289d3af18 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -21,9 +21,7 @@ const sequelizeErrors = require('../../errors'); @private */ -function removeColumn(qi, tableName, columnName, options) { - options = options || {}; - +function removeColumn(qi, tableName, columnName, options = {}) { return qi.sequelize.query( qi.QueryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { tableName, diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 6f14837dc6d0..a7d1c41d8639 100755 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -12,11 +12,11 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return `SET search_path to ${searchPath};`; } - createDatabaseQuery(databaseName, options) { + createDatabaseQuery(databaseName, options = {}) { options = Object.assign({ encoding: null, collate: null - }, options || {}); + }, options); const values = { database: this.quoteTable(databaseName), @@ -55,8 +55,8 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return 'SHOW SERVER_VERSION'; } - createTableQuery(tableName, attributes, options) { - options = Object.assign({}, options || {}); + createTableQuery(tableName, attributes, options = {}) { + options = Object.assign({}, options); //Postgres 9.0 does not support CREATE TABLE IF NOT EXISTS, 9.1 and above do const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0); @@ -109,8 +109,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${quotedTable} (${attributesClause})${comments}${columnComments};`; } - dropTableQuery(tableName, options) { - options = options || {}; + dropTableQuery(tableName, options = {}) { return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)}${options.cascade ? ' CASCADE' : ''};`; } @@ -761,9 +760,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { }).join(' OR '); } - pgEnumName(tableName, attr, options) { - options = options || {}; - + pgEnumName(tableName, attr, options = {}) { const tableDetails = this.extractTableDetails(tableName, options); let enumName = Utils.addTicks(Utils.generateEnumName(tableDetails.tableName, attr), '"'); @@ -775,7 +772,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return enumName; } - pgListEnums(tableName, attrName, options) { + pgListEnums(tableName, attrName, options = {}) { let enumName = ''; const tableDetails = this.extractTableDetails(tableName, options); diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 5c7f11679c9d..7626b34e8106 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -41,8 +41,7 @@ class ConnectionManager extends AbstractConnectionManager { parserStore.clear(); } - getConnection(options) { - options = options || {}; + getConnection(options = {}) { options.uuid = options.uuid || 'default'; options.inMemory = (this.sequelize.options.storage || this.sequelize.options.host || ':memory:') === ':memory:' ? 1 : 0; diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index cd131bc69ca4..8be660d5e67c 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -19,9 +19,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { return 'SELECT sqlite_version() as `version`'; } - createTableQuery(tableName, attributes, options) { - options = options || {}; - + createTableQuery(tableName, attributes, options = {}) { const primaryKeys = []; const needsMultiplePrimaryKeys = Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; const attrArray = []; diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 830bab25e649..730a5e126e52 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -26,9 +26,7 @@ const QueryTypes = require('../../query-types'); @since 1.6.0 @private */ -function removeColumn(qi, tableName, attributeName, options) { - options = options || {}; - +function removeColumn(qi, tableName, attributeName, options = {}) { return qi.describeTable(tableName, options).then(fields => { delete fields[attributeName]; @@ -54,10 +52,8 @@ exports.removeColumn = removeColumn; @since 1.6.0 @private */ -function changeColumn(qi, tableName, attributes, options) { +function changeColumn(qi, tableName, attributes, options = {}) { const attributeName = Object.keys(attributes)[0]; - options = options || {}; - return qi.describeTable(tableName, options).then(fields => { fields[attributeName] = attributes[attributeName]; @@ -84,9 +80,7 @@ exports.changeColumn = changeColumn; @since 1.6.0 @private */ -function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options) { - options = options || {}; - +function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options = {}) { return qi.describeTable(tableName, options).then(fields => { fields[attrNameAfter] = { ...fields[attrNameBefore] }; delete fields[attrNameBefore]; diff --git a/lib/errors/database/exclusion-constraint-error.js b/lib/errors/database/exclusion-constraint-error.js index f77e52aab3ea..280399a55746 100644 --- a/lib/errors/database/exclusion-constraint-error.js +++ b/lib/errors/database/exclusion-constraint-error.js @@ -6,8 +6,7 @@ const DatabaseError = require('./../database-error'); * Thrown when an exclusion constraint is violated in the database */ class ExclusionConstraintError extends DatabaseError { - constructor(options) { - options = options || {}; + constructor(options = {}) { options.parent = options.parent || { sql: '' }; super(options.parent); diff --git a/lib/errors/database/foreign-key-constraint-error.js b/lib/errors/database/foreign-key-constraint-error.js index 9bdf02ad0c14..5e053fddd8f9 100644 --- a/lib/errors/database/foreign-key-constraint-error.js +++ b/lib/errors/database/foreign-key-constraint-error.js @@ -6,8 +6,7 @@ const DatabaseError = require('./../database-error'); * Thrown when a foreign key constraint is violated in the database */ class ForeignKeyConstraintError extends DatabaseError { - constructor(options) { - options = options || {}; + constructor(options = {}) { options.parent = options.parent || { sql: '' }; super(options.parent); diff --git a/lib/errors/database/unknown-constraint-error.js b/lib/errors/database/unknown-constraint-error.js index e11fa666c7ef..94e5d10bc2ae 100644 --- a/lib/errors/database/unknown-constraint-error.js +++ b/lib/errors/database/unknown-constraint-error.js @@ -6,8 +6,7 @@ const DatabaseError = require('./../database-error'); * Thrown when constraint name is not found in the database */ class UnknownConstraintError extends DatabaseError { - constructor(options) { - options = options || {}; + constructor(options = {}) { options.parent = options.parent || { sql: '' }; super(options.parent); diff --git a/lib/errors/optimistic-lock-error.js b/lib/errors/optimistic-lock-error.js index 779015a4beee..f9633c8cc77c 100644 --- a/lib/errors/optimistic-lock-error.js +++ b/lib/errors/optimistic-lock-error.js @@ -6,8 +6,7 @@ const BaseError = require('./base-error'); * Thrown when attempting to update a stale model instance */ class OptimisticLockError extends BaseError { - constructor(options) { - options = options || {}; + constructor(options = {}) { options.message = options.message || `Attempting to update a stale model instance: ${options.modelName}`; super(options.message); this.name = 'SequelizeOptimisticLockError'; diff --git a/lib/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js index a509e88ff4fb..59a13f5c24f6 100644 --- a/lib/errors/validation/unique-constraint-error.js +++ b/lib/errors/validation/unique-constraint-error.js @@ -6,8 +6,7 @@ const ValidationError = require('./../validation-error'); * Thrown when a unique constraint is violated in the database */ class UniqueConstraintError extends ValidationError { - constructor(options) { - options = options || {}; + constructor(options = {}) { options.parent = options.parent || { sql: '' }; options.message = options.message || options.parent.message || 'Validation Error'; options.errors = options.errors || {}; diff --git a/lib/hooks.js b/lib/hooks.js index 2599bf44f4cd..9caeb589d5b9 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -81,9 +81,9 @@ const Hooks = { * @memberof Sequelize * @memberof Sequelize.Model */ - _setupHooks(hooks) { + _setupHooks(hooks = {}) { this.options.hooks = {}; - _.map(hooks || {}, (hooksArray, hookName) => { + _.map(hooks, (hooksArray, hookName) => { if (!Array.isArray(hooksArray)) hooksArray = [hooksArray]; hooksArray.forEach(hookFn => this.addHook(hookName, hookFn)); }); diff --git a/lib/model.js b/lib/model.js index a6901b7c493e..2512f4a31c5a 100644 --- a/lib/model.js +++ b/lib/model.js @@ -88,7 +88,7 @@ class Model { isNewRecord: true, _schema: this.constructor._schema, _schemaDelimiter: this.constructor._schemaDelimiter - }, options || {}); + }, options); if (options.attributes) { options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); @@ -106,7 +106,7 @@ class Model { this._previousDataValues = {}; this._changed = {}; this._modelOptions = this.constructor.options; - this._options = options || {}; + this._options = options; /** * Returns true if this instance has not yet been persisted to the database @@ -492,10 +492,9 @@ class Model { })(this, includes); } - static _validateIncludedElements(options, tableNames) { + static _validateIncludedElements(options, tableNames = {}) { if (!options.model) options.model = this; - tableNames = tableNames || {}; options.includeNames = []; options.includeMap = {}; @@ -1874,7 +1873,7 @@ class Model { return Promise.resolve(null); } - options = Utils.cloneDeep(options) || {}; + options = Utils.cloneDeep(options); if (typeof param === 'number' || typeof param === 'string' || Buffer.isBuffer(param)) { options.where = { @@ -2173,7 +2172,7 @@ class Model { static bulkBuild(valueSets, options) { options = Object.assign({ isNewRecord: true - }, options || {}); + }, options); if (!options.includeValidated) { this._conformIncludes(options, this); @@ -2218,7 +2217,7 @@ class Model { * */ static create(values, options) { - options = Utils.cloneDeep(options || {}); + options = Utils.cloneDeep(options); return this.build(values, { isNewRecord: true, @@ -2444,7 +2443,7 @@ class Model { hooks: true, returning: false, validate: true - }, Utils.cloneDeep(options || {})); + }, Utils.cloneDeep(options)); options.model = this; @@ -2864,7 +2863,7 @@ class Model { * {@link Model.destroy} for more information */ static truncate(options) { - options = Utils.cloneDeep(options) || {}; + options = Utils.cloneDeep(options); options.truncate = true; return this.destroy(options); } @@ -2980,7 +2979,7 @@ class Model { options = Object.assign({ hooks: true, individualHooks: false - }, options || {}); + }, options); options.type = QueryTypes.RAW; options.model = this; @@ -3332,9 +3331,7 @@ class Model { * * @returns {Promise} returns an array of affected rows and affected count with `options.returning: true`, whenever supported by dialect */ - static increment(fields, options) { - options = options || {}; - + static increment(fields, options = {}) { this._injectScope(options); this._optionsMustContainWhere(options); @@ -4159,13 +4156,12 @@ class Model { * * @returns {Promise} */ - update(values, options) { + update(values, options = {}) { // Clone values so it doesn't get modified for caller scope and ignore undefined values values = _.omitBy(values, value => value === undefined); const changedBefore = this.changed() || []; - options = options || {}; if (Array.isArray(options)) options = { fields: options }; options = Utils.cloneDeep(options); diff --git a/lib/query-interface.js b/lib/query-interface.js index db7e75637145..8cde8cf058f5 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -37,8 +37,7 @@ class QueryInterface { * * @returns {Promise} */ - createDatabase(database, options) { - options = options || {}; + createDatabase(database, options = {}) { const sql = this.QueryGenerator.createDatabaseQuery(database, options); return this.sequelize.query(sql, options); } @@ -51,8 +50,7 @@ class QueryInterface { * * @returns {Promise} */ - dropDatabase(database, options) { - options = options || {}; + dropDatabase(database, options = {}) { const sql = this.QueryGenerator.dropDatabaseQuery(database); return this.sequelize.query(sql, options); } @@ -65,8 +63,7 @@ class QueryInterface { * * @returns {Promise} */ - createSchema(schema, options) { - options = options || {}; + createSchema(schema, options = {}) { const sql = this.QueryGenerator.createSchema(schema); return this.sequelize.query(sql, options); } @@ -79,8 +76,7 @@ class QueryInterface { * * @returns {Promise} */ - dropSchema(schema, options) { - options = options || {}; + dropSchema(schema, options = {}) { const sql = this.QueryGenerator.dropSchema(schema); return this.sequelize.query(sql, options); } @@ -92,9 +88,7 @@ class QueryInterface { * * @returns {Promise} */ - dropAllSchemas(options) { - options = options || {}; - + dropAllSchemas(options = {}) { if (!this.QueryGenerator._dialect.supports.schemas) { return this.sequelize.drop(options); } @@ -287,8 +281,7 @@ class QueryInterface { * * @returns {Promise} */ - dropAllTables(options) { - options = options || {}; + dropAllTables(options = {}) { const skip = options.skip || []; const dropAllTables = tableNames => Promise.each(tableNames, tableName => { @@ -340,13 +333,11 @@ class QueryInterface { * @returns {Promise} * @private */ - dropEnum(enumName, options) { + dropEnum(enumName, options = {}) { if (this.sequelize.getDialect() !== 'postgres') { return Promise.resolve(); } - options = options || {}; - return this.sequelize.query( this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(enumName)), Object.assign({}, options, { raw: true }) @@ -361,14 +352,12 @@ class QueryInterface { * @returns {Promise} * @private */ - dropAllEnums(options) { + dropAllEnums(options = {}) { if (this.sequelize.getDialect() !== 'postgres') { return Promise.resolve(); } - options = options || {}; - - return this.pgListEnums(null, options).map(result => this.sequelize.query( + return this.pgListEnums(undefined, options).map(result => this.sequelize.query( this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), Object.assign({}, options, { raw: true }) )); @@ -383,8 +372,7 @@ class QueryInterface { * @returns {Promise} * @private */ - pgListEnums(tableName, options) { - options = options || {}; + pgListEnums(tableName, options = {}) { const sql = this.QueryGenerator.pgListEnums(tableName); return this.sequelize.query(sql, Object.assign({}, options, { plain: false, raw: true, type: QueryTypes.SELECT })); } @@ -398,8 +386,7 @@ class QueryInterface { * * @returns {Promise} */ - renameTable(before, after, options) { - options = options || {}; + renameTable(before, after, options = {}) { const sql = this.QueryGenerator.renameTableQuery(before, after); return this.sequelize.query(sql, options); } @@ -504,12 +491,11 @@ class QueryInterface { * * @returns {Promise} */ - addColumn(table, key, attribute, options) { + addColumn(table, key, attribute, options = {}) { if (!table || !key || !attribute) { throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); } - options = options || {}; attribute = this.sequelize.normalizeAttribute(attribute); return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options); } @@ -523,8 +509,7 @@ class QueryInterface { * * @returns {Promise} */ - removeColumn(tableName, attributeName, options) { - options = options || {}; + removeColumn(tableName, attributeName, options = {}) { switch (this.sequelize.options.dialect) { case 'sqlite': // sqlite needs some special treatment as it cannot drop a column @@ -551,9 +536,8 @@ class QueryInterface { * * @returns {Promise} */ - changeColumn(tableName, attributeName, dataTypeOrOptions, options) { + changeColumn(tableName, attributeName, dataTypeOrOptions, options = {}) { const attributes = {}; - options = options || {}; if (Object.values(DataTypes).includes(dataTypeOrOptions)) { attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true }; @@ -586,8 +570,7 @@ class QueryInterface { * * @returns {Promise} */ - renameColumn(tableName, attrNameBefore, attrNameAfter, options) { - options = options || {}; + renameColumn(tableName, attrNameBefore, attrNameAfter, options = {}) { return this.describeTable(tableName, options).then(data => { if (!data[attrNameBefore]) { throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`); @@ -678,7 +661,7 @@ class QueryInterface { return Promise.resolve({}); } - options = Object.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS }); + options = Object.assign({}, options, { type: QueryTypes.FOREIGNKEYS }); return Promise.map(tableNames, tableName => this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options) @@ -750,8 +733,7 @@ class QueryInterface { * * @returns {Promise} */ - removeIndex(tableName, indexNameOrAttributes, options) { - options = options || {}; + removeIndex(tableName, indexNameOrAttributes, options = {}) { const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); return this.sequelize.query(sql, options); } @@ -858,8 +840,7 @@ class QueryInterface { * * @returns {Promise} */ - removeConstraint(tableName, constraintName, options) { - options = options || {}; + removeConstraint(tableName, constraintName, options = {}) { switch (this.sequelize.options.dialect) { case 'mysql': @@ -1001,7 +982,7 @@ class QueryInterface { } update(instance, tableName, values, identifier, options) { - options = { ...options || {} }; + options = { ...options }; options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); @@ -1089,7 +1070,7 @@ class QueryInterface { * @param {string} tableName table name from where to delete records * @param {Object} where where conditions to find records to delete * @param {Object} [options] options - * @param {boolean} [options.truncate] Use truncate table command + * @param {boolean} [options.truncate] Use truncate table command * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table. * @param {Model} [model] Model @@ -1192,18 +1173,16 @@ class QueryInterface { }); } - createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) { + createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options = {}) { const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); - options = options || {}; if (sql) { return this.sequelize.query(sql, options); } return Promise.resolve(); } - dropTrigger(tableName, triggerName, options) { + dropTrigger(tableName, triggerName, options = {}) { const sql = this.QueryGenerator.dropTrigger(tableName, triggerName); - options = options || {}; if (sql) { return this.sequelize.query(sql, options); @@ -1211,9 +1190,8 @@ class QueryInterface { return Promise.resolve(); } - renameTrigger(tableName, oldTriggerName, newTriggerName, options) { + renameTrigger(tableName, oldTriggerName, newTriggerName, options = {}) { const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); - options = options || {}; if (sql) { return this.sequelize.query(sql, options); @@ -1258,9 +1236,8 @@ class QueryInterface { * * @returns {Promise} */ - createFunction(functionName, params, returnType, language, body, optionsArray, options) { + createFunction(functionName, params, returnType, language, body, optionsArray, options = {}) { const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options); - options = options || {}; if (sql) { return this.sequelize.query(sql, options); @@ -1286,9 +1263,8 @@ class QueryInterface { * * @returns {Promise} */ - dropFunction(functionName, params, options) { + dropFunction(functionName, params, options = {}) { const sql = this.QueryGenerator.dropFunction(functionName, params); - options = options || {}; if (sql) { return this.sequelize.query(sql, options); @@ -1316,9 +1292,8 @@ class QueryInterface { * * @returns {Promise} */ - renameFunction(oldFunctionName, params, newFunctionName, options) { + renameFunction(oldFunctionName, params, newFunctionName, options = {}) { const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName); - options = options || {}; if (sql) { return this.sequelize.query(sql, options); diff --git a/lib/sequelize.js b/lib/sequelize.js index d4cea6ffc1ea..c5bc50a643ef 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -259,7 +259,7 @@ class Sequelize { benchmark: false, minifyAliases: false, logQueryParameters: false - }, options || {}); + }, options); if (!this.options.dialect) { throw new Error('Dialect needs to be explicitly supplied as of v4.0.0'); diff --git a/lib/transaction.js b/lib/transaction.js index 39f11e2e6845..41af5500f035 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -20,7 +20,7 @@ class Transaction { * @param {string} [options.isolationLevel] Sets the isolation level of the transaction. * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. PostgreSQL only */ - constructor(sequelize, options) { + constructor(sequelize, options = {}) { this.sequelize = sequelize; this.savepoints = []; this._afterCommitHooks = []; @@ -32,7 +32,7 @@ class Transaction { type: sequelize.options.transactionType, isolationLevel: sequelize.options.isolationLevel, readOnly: false - }, options || {}); + }, options); this.parent = this.options.transaction; diff --git a/lib/utils.js b/lib/utils.js index 83321027832e..a8f8e09352cd 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -124,8 +124,7 @@ function formatNamedParameters(sql, parameters, dialect) { exports.formatNamedParameters = formatNamedParameters; function cloneDeep(obj, onlyPlain) { - obj = obj || {}; - return _.cloneDeepWith(obj, elem => { + return _.cloneDeepWith(obj || {}, elem => { // Do not try to customize cloning of arrays or POJOs if (Array.isArray(elem) || _.isPlainObject(elem)) { return undefined; @@ -296,10 +295,9 @@ function defaultValueSchemable(value) { } exports.defaultValueSchemable = defaultValueSchemable; -function removeNullValuesFromHash(hash, omitNull, options) { +function removeNullValuesFromHash(hash, omitNull, options = {}) { let result = hash; - options = options || {}; options.allowNull = options.allowNull || []; if (omitNull) { diff --git a/test/integration/sequelize/deferrable.test.js b/test/integration/sequelize/deferrable.test.js index 54286d0b90db..d24dff864028 100644 --- a/test/integration/sequelize/deferrable.test.js +++ b/test/integration/sequelize/deferrable.test.js @@ -14,9 +14,7 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) { describe(Support.getTestDialectTeaser('Sequelize'), () => { describe('Deferrable', () => { beforeEach(function() { - this.run = function(deferrable, options) { - options = options || {}; - + this.run = function(deferrable, options = {}) { const taskTableName = options.taskTableName || `tasks_${config.rand()}`; const transactionOptions = Object.assign({}, { deferrable: Sequelize.Deferrable.SET_DEFERRED }, options); const userTableName = `users_${config.rand()}`; diff --git a/test/support.js b/test/support.js index 9508aaf15917..c3d9dcddbf7e 100644 --- a/test/support.js +++ b/test/support.js @@ -48,8 +48,7 @@ const Support = { return Sequelize.Promise.resolve(sequelize); }, - createSequelizeInstance(options) { - options = options || {}; + createSequelizeInstance(options = {}) { options.dialect = this.getTestDialect(); const config = Config[options.dialect]; @@ -84,8 +83,7 @@ const Support = { return config; }, - getSequelizeInstance(db, user, pass, options) { - options = options || {}; + getSequelizeInstance(db, user, pass, options = {}) { options.dialect = options.dialect || this.getTestDialect(); return new Sequelize(db, user, pass, options); }, From 94e16adcb684f185b7e7d770ef04fb33f2ef5ebd Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Mon, 22 Apr 2019 21:48:19 +0200 Subject: [PATCH 008/414] ci: build v6 as well for now --- .travis.yml | 1 + appveyor.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5c38aeb69f48..636a62552ba0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ language: node_js branches: only: - master + - v6 - /^greenkeeper/.*$/ except: - /^v\d+\.\d+\.\d+$/ diff --git a/appveyor.yml b/appveyor.yml index b8c55b21153f..091be3f53b1f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,4 +40,5 @@ after_test: branches: only: - master + - v6 - /^greenkeeper/.*$/ From 3d4d747ee2b1a6b171257da3bb66ea1e70524db4 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Tue, 23 Apr 2019 14:12:34 +0200 Subject: [PATCH 009/414] refactor(querying): remove operatorAliases support (#10818) BREAKING CHANGE: Operator aliases were soft deprecated via the `opt-in` option `operatorAlises` in v5 they have been entirely removed. Please refer to previous changelogs for the migration guide. --- docs/manual/querying.md | 78 ------------------- docs/upgrade-to-v6.md | 6 ++ lib/dialects/abstract/query-generator.js | 4 - .../abstract/query-generator/operators.js | 37 --------- lib/sequelize.js | 7 +- lib/utils/deprecations.js | 3 +- test/integration/sequelize.test.js | 9 --- .../dialects/abstract/query-generator.test.js | 44 ----------- types/lib/sequelize.d.ts | 17 ---- 9 files changed, 9 insertions(+), 196 deletions(-) diff --git a/docs/manual/querying.md b/docs/manual/querying.md index 3a5069ee83e4..7b3b50bd5aed 100644 --- a/docs/manual/querying.md +++ b/docs/manual/querying.md @@ -244,84 +244,6 @@ const Op = Sequelize.Op; // title LIKE 'Boat%' OR description LIKE '%boat%' ``` -#### Operators Aliases - -Sequelize allows setting specific strings as aliases for operators. With v5 this will give you deprecation warning. - -```js -const Op = Sequelize.Op; -const operatorsAliases = { - $gt: Op.gt -} -const connection = new Sequelize(db, user, pass, { operatorsAliases }) - -[Op.gt]: 6 // > 6 -$gt: 6 // same as using Op.gt (> 6) -``` - -#### Operators security - -By default Sequelize will use Symbol operators. Using Sequelize without any aliases improves security. Not having any string aliases will make it extremely unlikely that operators could be injected but you should always properly validate and sanitize user input. - -Some frameworks automatically parse user input into js objects and if you fail to sanitize your input it might be possible to inject an Object with string operators to Sequelize. - -For better security it is highly advised to use symbol operators from `Sequelize.Op` like `Op.and` / `Op.or` in your code and not depend on any string based operators like `$and` / `$or` at all. You can limit alias your application will need by setting `operatorsAliases` option, remember to sanitize user input especially when you are directly passing them to Sequelize methods. - -```js -const Op = Sequelize.Op; - -//use sequelize without any operators aliases -const connection = new Sequelize(db, user, pass, { operatorsAliases: false }); - -//use sequelize with only alias for $and => Op.and -const connection2 = new Sequelize(db, user, pass, { operatorsAliases: { $and: Op.and } }); -``` - -Sequelize will warn you if you're using the default aliases and not limiting them -if you want to keep using all default aliases (excluding legacy ones) without the warning you can pass the following operatorsAliases option - - -```js -const Op = Sequelize.Op; -const operatorsAliases = { - $eq: Op.eq, - $ne: Op.ne, - $gte: Op.gte, - $gt: Op.gt, - $lte: Op.lte, - $lt: Op.lt, - $not: Op.not, - $in: Op.in, - $notIn: Op.notIn, - $is: Op.is, - $like: Op.like, - $notLike: Op.notLike, - $iLike: Op.iLike, - $notILike: Op.notILike, - $regexp: Op.regexp, - $notRegexp: Op.notRegexp, - $iRegexp: Op.iRegexp, - $notIRegexp: Op.notIRegexp, - $between: Op.between, - $notBetween: Op.notBetween, - $overlap: Op.overlap, - $contains: Op.contains, - $contained: Op.contained, - $adjacent: Op.adjacent, - $strictLeft: Op.strictLeft, - $strictRight: Op.strictRight, - $noExtendRight: Op.noExtendRight, - $noExtendLeft: Op.noExtendLeft, - $and: Op.and, - $or: Op.or, - $any: Op.any, - $all: Op.all, - $values: Op.values, - $col: Op.col -}; - -const connection = new Sequelize(db, user, pass, { operatorsAliases }); -``` - ### JSON The JSON data type is supported by the PostgreSQL, SQLite, MySQL and MariaDB dialects only. diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md index a24ad4fc87cd..196718eb5734 100644 --- a/docs/upgrade-to-v6.md +++ b/docs/upgrade-to-v6.md @@ -7,3 +7,9 @@ Sequelize v6 is the next major release after v4 ### Support for Node 8 and up Sequelize v6 will only support Node 8 and up + +### Removed support for `operatorAliases` + +Operator aliases were soft deprecated via the `opt-in` option `operatorAlises` in v5 they have been entirely removed. + +Please refer to previous changelogs for the migration guide. diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index b875465678b3..679587274de6 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -2193,10 +2193,6 @@ class QueryGenerator { const isPlainObject = _.isPlainObject(value); const isArray = !isPlainObject && Array.isArray(value); - key = this.OperatorsAliasMap && this.OperatorsAliasMap[key] || key; - if (isPlainObject) { - value = this._replaceAliases(value); - } const valueKeys = isPlainObject && Utils.getComplexKeys(value); if (key === undefined) { diff --git a/lib/dialects/abstract/query-generator/operators.js b/lib/dialects/abstract/query-generator/operators.js index 0bff9bad7c74..98d51a123bcb 100644 --- a/lib/dialects/abstract/query-generator/operators.js +++ b/lib/dialects/abstract/query-generator/operators.js @@ -1,8 +1,6 @@ 'use strict'; -const _ = require('lodash'); const Op = require('../../../operators'); -const Utils = require('../../../utils'); const OperatorHelpers = { OperatorMap: { @@ -43,41 +41,6 @@ const OperatorHelpers = { [Op.or]: ' OR ', [Op.col]: 'COL', [Op.placeholder]: '$$PLACEHOLDER$$' - }, - - OperatorsAliasMap: {}, - - setOperatorsAliases(aliases) { - if (!aliases || _.isEmpty(aliases)) { - this.OperatorsAliasMap = false; - } else { - this.OperatorsAliasMap = Object.assign({}, aliases); - } - }, - - _replaceAliases(orig) { - const obj = {}; - if (!this.OperatorsAliasMap) { - return orig; - } - - Utils.getOperators(orig).forEach(op => { - const item = orig[op]; - if (_.isPlainObject(item)) { - obj[op] = this._replaceAliases(item); - } else { - obj[op] = item; - } - }); - - _.forOwn(orig, (item, prop) => { - prop = this.OperatorsAliasMap[prop] || prop; - if (_.isPlainObject(item)) { - item = this._replaceAliases(item); - } - obj[prop] = item; - }); - return obj; } }; diff --git a/lib/sequelize.js b/lib/sequelize.js index c5bc50a643ef..0e0e677cfe01 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -324,11 +324,8 @@ class Sequelize { this.dialect = new Dialect(this); this.dialect.QueryGenerator.typeValidation = options.typeValidation; - if (_.isPlainObject(this.options.operatorsAliases)) { - deprecations.noStringOperators(); - this.dialect.QueryGenerator.setOperatorsAliases(this.options.operatorsAliases); - } else if (typeof this.options.operatorsAliases === 'boolean') { - deprecations.noBoolOperatorAliases(); + if ('operatorsAliases' in this.options) { + throw new Error('operatorAliases support was removed in v6'); } this.queryInterface = new QueryInterface(this); diff --git a/lib/utils/deprecations.js b/lib/utils/deprecations.js index 1d0b5183d250..6d0cb467ecd7 100644 --- a/lib/utils/deprecations.js +++ b/lib/utils/deprecations.js @@ -6,6 +6,5 @@ const noop = () => {}; exports.noRawAttributes = deprecate(noop, 'Use sequelize.fn / sequelize.literal to construct attributes', 'SEQUELIZE0001'); exports.noTrueLogging = deprecate(noop, 'The logging-option should be either a function or false. Default: console.log', 'SEQUELIZE0002'); -exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://sequelize.org/master/manual/querying.html#operators', 'SEQUELIZE0003'); -exports.noBoolOperatorAliases = deprecate(noop, 'A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.', 'SEQUELIZE0004'); +exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://docs.sequelizejs.com/manual/querying.html#operators', 'SEQUELIZE0003'); exports.noDoubleNestedGroup = deprecate(noop, 'Passing a double nested nested array to `group` is unsupported and will be removed in v6.', 'SEQUELIZE0005'); diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index cdca083c3d4a..4771cda4d297 100755 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -37,15 +37,6 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(sequelize.config.host).to.equal('127.0.0.1'); }); - it('should set operators aliases on dialect QueryGenerator', () => { - const operatorsAliases = { fake: true }; - const sequelize = Support.createSequelizeInstance({ operatorsAliases }); - - expect(sequelize).to.have.property('dialect'); - expect(sequelize.dialect).to.have.property('QueryGenerator'); - expect(sequelize.dialect.QueryGenerator).to.have.property('OperatorsAliasMap'); - expect(sequelize.dialect.QueryGenerator.OperatorsAliasMap).to.be.eql(operatorsAliases); - }); if (dialect === 'sqlite') { it('should work with connection strings (1)', () => { diff --git a/test/unit/dialects/abstract/query-generator.test.js b/test/unit/dialects/abstract/query-generator.test.js index 0845a7e2bd77..30469516d9fc 100644 --- a/test/unit/dialects/abstract/query-generator.test.js +++ b/test/unit/dialects/abstract/query-generator.test.js @@ -43,50 +43,6 @@ describe('QueryGenerator', () => { .to.throw('Invalid value { \'$in\': [ 4 ] }'); }); - it('should parse set aliases strings as operators', function() { - const QG = getAbstractQueryGenerator(this.sequelize), - aliases = { - OR: Op.or, - '!': Op.not, - '^^': Op.gt - }; - - QG.setOperatorsAliases(aliases); - - QG.whereItemQuery('OR', [{ test: { '^^': 5 } }, { test: { '!': 3 } }, { test: { [Op.in]: [4] } }]) - .should.be.equal('(test > 5 OR test != 3 OR test IN (4))'); - - QG.whereItemQuery(Op.and, [{ test: { [Op.between]: [2, 5] } }, { test: { '!': 3 } }, { test: { '^^': 4 } }]) - .should.be.equal('(test BETWEEN 2 AND 5 AND test != 3 AND test > 4)'); - - expect(() => QG.whereItemQuery('OR', [{ test: { '^^': 5 } }, { test: { $not: 3 } }, { test: { [Op.in]: [4] } }])) - .to.throw('Invalid value { \'$not\': 3 }'); - - expect(() => QG.whereItemQuery('OR', [{ test: { $gt: 5 } }, { test: { '!': 3 } }, { test: { [Op.in]: [4] } }])) - .to.throw('Invalid value { \'$gt\': 5 }'); - - expect(() => QG.whereItemQuery('$or', [{ test: 5 }, { test: 3 }])) - .to.throw('Invalid value { test: 5 }'); - - expect(() => QG.whereItemQuery('$and', [{ test: 5 }, { test: 3 }])) - .to.throw('Invalid value { test: 5 }'); - - expect(() => QG.whereItemQuery('test', { $gt: 5 })) - .to.throw('Invalid value { \'$gt\': 5 }'); - - expect(() => QG.whereItemQuery('test', { $between: [2, 5] })) - .to.throw('Invalid value { \'$between\': [ 2, 5 ] }'); - - expect(() => QG.whereItemQuery('test', { $ne: 3 })) - .to.throw('Invalid value { \'$ne\': 3 }'); - - expect(() => QG.whereItemQuery('test', { $not: 3 })) - .to.throw('Invalid value { \'$not\': 3 }'); - - expect(() => QG.whereItemQuery('test', { $in: [4] })) - .to.throw('Invalid value { \'$in\': [ 4 ] }'); - }); - it('should correctly parse sequelize.where with .fn as logic', function() { const QG = getAbstractQueryGenerator(this.sequelize); QG.handleSequelizeMethod(this.sequelize.where(this.sequelize.col('foo'), 'LIKE', this.sequelize.col('bar'))) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 2936ce943123..ba220ca2d427 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -125,13 +125,6 @@ export interface ReplicationOptions { write: ConnectionOptions; } -/** - * Used to map operators to their Symbol representations - */ -export interface OperatorsAliases { - [K: string]: symbol; -} - /** * Final config options generated by sequelize. */ @@ -329,16 +322,6 @@ export interface Options extends Logging { */ typeValidation?: boolean; - /** - * Sets available operator aliases. - * See (https://sequelize.org/master/manual/querying.html#operators) for more information. - * WARNING: Setting this to boolean value was deprecated and is no-op. - * - * @default all aliases - */ - operatorsAliases?: OperatorsAliases; - - /** * The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. * WARNING: Setting this to false may expose vulnerabilities and is not recommended! From 7885e6c9f3be9c6c7ce93879fdd40e76d1d8dfaa Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 3 May 2019 15:51:40 -0700 Subject: [PATCH 010/414] refactor(operators): use namespaced symbol names for operators (#10830) BREAKING CHANGE: If you have relied on accessing sequelize operators via `Symbol.for('gt')` etc. you must now prefix them with `sequelize.operator` eg. `Symbol.for('sequelize.operator.gt')` --- docs/upgrade-to-v6.md | 5 +++ lib/operators.js | 78 +++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md index 196718eb5734..2a988570693a 100644 --- a/docs/upgrade-to-v6.md +++ b/docs/upgrade-to-v6.md @@ -13,3 +13,8 @@ Sequelize v6 will only support Node 8 and up Operator aliases were soft deprecated via the `opt-in` option `operatorAlises` in v5 they have been entirely removed. Please refer to previous changelogs for the migration guide. + +### Renamed operator symbols + +If you have relied on accessing sequelize operators via `Symbol.for('gt')` etc. you must now prefix them with `sequelize.operator` eg. +`Symbol.for('sequelize.operator.gt')` diff --git a/lib/operators.js b/lib/operators.js index a5b55f9b377a..f6c2d0e3440b 100644 --- a/lib/operators.js +++ b/lib/operators.js @@ -46,45 +46,45 @@ * @property join */ const Op = { - eq: Symbol.for('eq'), - ne: Symbol.for('ne'), - gte: Symbol.for('gte'), - gt: Symbol.for('gt'), - lte: Symbol.for('lte'), - lt: Symbol.for('lt'), - not: Symbol.for('not'), - is: Symbol.for('is'), - in: Symbol.for('in'), - notIn: Symbol.for('notIn'), - like: Symbol.for('like'), - notLike: Symbol.for('notLike'), - iLike: Symbol.for('iLike'), - notILike: Symbol.for('notILike'), - startsWith: Symbol.for('startsWith'), - endsWith: Symbol.for('endsWith'), - substring: Symbol.for('substring'), - regexp: Symbol.for('regexp'), - notRegexp: Symbol.for('notRegexp'), - iRegexp: Symbol.for('iRegexp'), - notIRegexp: Symbol.for('notIRegexp'), - between: Symbol.for('between'), - notBetween: Symbol.for('notBetween'), - overlap: Symbol.for('overlap'), - contains: Symbol.for('contains'), - contained: Symbol.for('contained'), - adjacent: Symbol.for('adjacent'), - strictLeft: Symbol.for('strictLeft'), - strictRight: Symbol.for('strictRight'), - noExtendRight: Symbol.for('noExtendRight'), - noExtendLeft: Symbol.for('noExtendLeft'), - and: Symbol.for('and'), - or: Symbol.for('or'), - any: Symbol.for('any'), - all: Symbol.for('all'), - values: Symbol.for('values'), - col: Symbol.for('col'), - placeholder: Symbol.for('placeholder'), - join: Symbol.for('join') + eq: Symbol.for('sequelize.operator.eq'), + ne: Symbol.for('sequelize.operator.ne'), + gte: Symbol.for('sequelize.operator.gte'), + gt: Symbol.for('sequelize.operator.gt'), + lte: Symbol.for('sequelize.operator.lte'), + lt: Symbol.for('sequelize.operator.lt'), + not: Symbol.for('sequelize.operator.not'), + is: Symbol.for('sequelize.operator.is'), + in: Symbol.for('sequelize.operator.in'), + notIn: Symbol.for('sequelize.operator.notIn'), + like: Symbol.for('sequelize.operator.like'), + notLike: Symbol.for('sequelize.operator.notLike'), + iLike: Symbol.for('sequelize.operator.iLike'), + notILike: Symbol.for('sequelize.operator.notILike'), + startsWith: Symbol.for('sequelize.operator.startsWith'), + endsWith: Symbol.for('sequelize.operator.endsWith'), + substring: Symbol.for('sequelize.operator.substring'), + regexp: Symbol.for('sequelize.operator.regexp'), + notRegexp: Symbol.for('sequelize.operator.notRegexp'), + iRegexp: Symbol.for('sequelize.operator.iRegexp'), + notIRegexp: Symbol.for('sequelize.operator.notIRegexp'), + between: Symbol.for('sequelize.operator.between'), + notBetween: Symbol.for('sequelize.operator.notBetween'), + overlap: Symbol.for('sequelize.operator.overlap'), + contains: Symbol.for('sequelize.operator.contains'), + contained: Symbol.for('sequelize.operator.contained'), + adjacent: Symbol.for('sequelize.operator.adjacent'), + strictLeft: Symbol.for('sequelize.operator.strictLeft'), + strictRight: Symbol.for('sequelize.operator.strictRight'), + noExtendRight: Symbol.for('sequelize.operator.noExtendRight'), + noExtendLeft: Symbol.for('sequelize.operator.noExtendLeft'), + and: Symbol.for('sequelize.operator.and'), + or: Symbol.for('sequelize.operator.or'), + any: Symbol.for('sequelize.operator.any'), + all: Symbol.for('sequelize.operator.all'), + values: Symbol.for('sequelize.operator.values'), + col: Symbol.for('sequelize.operator.col'), + placeholder: Symbol.for('sequelize.operator.placeholder'), + join: Symbol.for('sequelize.operator.join') }; module.exports = Op; From 57f8a01fe40476811c3a8d4715fb0b3a8dbbd7d3 Mon Sep 17 00:00:00 2001 From: Erik Seliger Date: Sun, 5 May 2019 00:16:56 +0200 Subject: [PATCH 011/414] refactor(model): remove build in favor or constructor usage (#10852) BREAKING CHANGE: `Model.build` has been removed. Use `Model.bulkBuild` or `new Model` instead. Co-authored-by: Simon Schick --- docs/manual/instances.md | 11 ++-- docs/manual/models-definition.md | 2 +- docs/upgrade-to-v6.md | 6 ++ lib/associations/base.js | 2 +- lib/associations/has-one.js | 2 +- lib/model.js | 43 ++++++--------- lib/sequelize.js | 2 +- .../associations/belongs-to-many.test.js | 14 ++--- test/integration/dialects/sqlite/dao.test.js | 2 +- test/integration/instance.test.js | 32 +++++------ test/integration/instance.validations.test.js | 55 +++++++++---------- test/integration/instance/destroy.test.js | 2 +- test/integration/instance/save.test.js | 12 ++-- test/integration/instance/to-json.test.js | 8 +-- test/integration/instance/update.test.js | 4 +- test/integration/instance/values.test.js | 36 ++++++------ test/integration/model.test.js | 40 +++++++------- test/integration/model/attributes.test.js | 2 +- .../model/attributes/types.test.js | 2 +- test/integration/model/schema.test.js | 16 +++--- test/integration/model/searchPath.test.js | 26 ++++----- test/integration/sequelize.test.js | 4 +- .../unit/associations/belongs-to-many.test.js | 8 +-- test/unit/associations/belongs-to.test.js | 2 +- test/unit/associations/has-many.test.js | 30 +++++----- test/unit/associations/has-one.test.js | 2 +- test/unit/instance-validator.test.js | 20 +++---- test/unit/instance/build.test.js | 10 ++-- test/unit/instance/changed.test.js | 22 ++++---- test/unit/instance/decrement.test.js | 2 +- test/unit/instance/destroy.test.js | 2 +- test/unit/instance/get.test.js | 4 +- test/unit/instance/increment.test.js | 2 +- test/unit/instance/is-soft-deleted.test.js | 4 +- test/unit/instance/previous.test.js | 2 +- test/unit/instance/reload.test.js | 2 +- test/unit/instance/restore.test.js | 2 +- test/unit/instance/save.test.js | 4 +- test/unit/instance/set.test.js | 22 ++++---- test/unit/instance/to-json.test.js | 4 +- test/unit/model/findall.test.js | 2 +- test/unit/model/overwriting-builtins.test.js | 2 +- test/unit/model/removeAttribute.test.js | 2 +- test/unit/model/validation.test.js | 14 ++--- types/lib/model.d.ts | 15 +---- 45 files changed, 243 insertions(+), 259 deletions(-) diff --git a/docs/manual/instances.md b/docs/manual/instances.md index d1a442c99d9e..b380eba24aa0 100644 --- a/docs/manual/instances.md +++ b/docs/manual/instances.md @@ -5,12 +5,12 @@ In order to create instances of defined classes just do as follows. You might recognize the syntax if you coded Ruby in the past. Using the `build`-method will return an unsaved object, which you explicitly have to save. ```js -const project = Project.build({ +const project = new Project({ title: 'my awesome project', description: 'woot woot. this will make me a rich man' }) -const task = Task.build({ +const task = new Task({ title: 'specify the project idea', description: 'bla', deadline: new Date() @@ -28,7 +28,7 @@ Task.init({ }, { sequelize, modelName: 'task' }); // now instantiate an object -const task = Task.build({title: 'very important task'}) +const task = new Task({title: 'very important task'}) task.title // ==> 'very important task' task.rating // ==> 3 @@ -46,8 +46,7 @@ task.save().catch(error => { }) // you can also build, save and access the object with chaining: -Task - .build({ title: 'foo', description: 'bar', deadline: new Date() }) +new Task({ title: 'foo', description: 'bar', deadline: new Date() }) .save() .then(anotherTask => { // you can now access the currently saved task with the variable anotherTask... nice! @@ -59,7 +58,7 @@ Task ## Creating persistent instances -While an instance created with `.build()` requires an explicit `.save()` call to be stored in the database, `.create()` omits that requirement altogether and automatically stores your instance's data once called. +While an instance created with `new` requires an explicit `.save()` call to be stored in the database, `.create()` omits that requirement altogether and automatically stores your instance's data once called. ```js Task.create({ title: 'foo', description: 'bar', deadline: new Date() }).then(task => { diff --git a/docs/manual/models-definition.md b/docs/manual/models-definition.md index 30bb5d62a650..94b367cbe8d0 100644 --- a/docs/manual/models-definition.md +++ b/docs/manual/models-definition.md @@ -676,7 +676,7 @@ class User extends Model { User.init({ firstname: Sequelize.STRING, lastname: Sequelize.STRING }, { sequelize }); // Example: -User.build({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar' +new User({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar' ``` ### Indexes diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md index 2a988570693a..0f2bd190c6ce 100644 --- a/docs/upgrade-to-v6.md +++ b/docs/upgrade-to-v6.md @@ -18,3 +18,9 @@ Please refer to previous changelogs for the migration guide. If you have relied on accessing sequelize operators via `Symbol.for('gt')` etc. you must now prefix them with `sequelize.operator` eg. `Symbol.for('sequelize.operator.gt')` + +### Removed `Model.build` + +`Model.build` has been acting as proxy for `bulkBuild` and `new Model` for a while. + +Use `Model.bulkBuild` or `new Model` instead. diff --git a/lib/associations/base.js b/lib/associations/base.js index a440e3ef84f1..106f62129e71 100644 --- a/lib/associations/base.js +++ b/lib/associations/base.js @@ -128,7 +128,7 @@ class Association { const tmpInstance = {}; tmpInstance[this.target.primaryKeyAttribute] = element; - return this.target.build(tmpInstance, { isNewRecord: false }); + return new this.target(tmpInstance, { isNewRecord: false }); }); } diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index b1893d419bc1..6738d65f0d9c 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -217,7 +217,7 @@ class HasOne extends Association { if (!(associatedInstance instanceof this.target)) { const tmpInstance = {}; tmpInstance[this.target.primaryKeyAttribute] = associatedInstance; - associatedInstance = this.target.build(tmpInstance, { + associatedInstance = new this.target(tmpInstance, { isNewRecord: false }); } diff --git a/lib/model.js b/lib/model.js index 2512f4a31c5a..cdf69ded20d0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2151,24 +2151,15 @@ class Model { } /** - * Builds a new model instance. + * Builds multiple models in one operation. * - * @param {Object|Array} values An object of key value pairs or an array of such. If an array, the function will return an array of instances. - * @param {Object} [options] Instance build options - * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. - * @param {boolean} [options.isNewRecord=true] Is this new record - * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` + * @param {Array} valueSets An object of key value pairs or an array of such. If an array, the function will return an array of instances. + * @param {Object} [options] Instance build options, + * @see + * {@link constructor} * - * @returns {Model|Array} + * @returns {Array} */ - static build(values, options) { - if (Array.isArray(values)) { - return this.bulkBuild(values, options); - } - - return new this(values, options); - } - static bulkBuild(valueSets, options) { options = Object.assign({ isNewRecord: true @@ -2186,14 +2177,14 @@ class Model { options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); } - return valueSets.map(values => this.build(values, options)); + return valueSets.map(values => new this(values, options)); } /** * Builds a new model instance and calls save on it. * @see - * {@link Model.build} + * {@link Model.constructor} * @see * {@link Model.save} * @@ -2219,7 +2210,7 @@ class Model { static create(values, options) { options = Utils.cloneDeep(options); - return this.build(values, { + return new this(values, { isNewRecord: true, attributes: options.fields, include: options.include, @@ -2256,12 +2247,12 @@ class Model { values = Utils.defaults(values, options.where); } - instance = this.build(values, options); + instance = new this(values, options); - return Promise.resolve([instance, true]); + return [instance, true]; } - return Promise.resolve([instance, false]); + return [instance, false]; }); } @@ -2450,7 +2441,7 @@ class Model { const createdAtAttr = this._timestampAttributes.createdAt; const updatedAtAttr = this._timestampAttributes.updatedAt; const hasPrimary = this.primaryKeyField in values || this.primaryKeyAttribute in values; - const instance = this.build(values); + const instance = new this(values); if (!options.fields) { options.fields = Object.keys(instance._changed); @@ -2550,7 +2541,7 @@ class Model { } } - const instances = records.map(values => this.build(values, { isNewRecord: true, include: options.include })); + const instances = records.map(values => new this(values, { isNewRecord: true, include: options.include })); const recursiveBulkCreate = (instances, options) => { options = Object.assign({ @@ -3096,7 +3087,7 @@ class Model { return Promise.try(() => { // Validate if (options.validate) { - const build = this.build(values); + const build = new this(values); build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); if (options.sideEffects) { @@ -3575,7 +3566,7 @@ class Model { * * Set can also be used to build instances for associations, if you have values for those. * When using set with associations you need to make sure the property key matches the alias of the association - * while also making sure that the proper include options have been set (from .build() or .findOne()) + * while also making sure that the proper include options have been set (from the constructor or .findOne()) * * If called with a dot.separated key on a JSON/JSONB attribute it will set the value nested and flag the entire object as changed. * @@ -3800,7 +3791,7 @@ class Model { value = value[0]; } isEmpty = value && value[primaryKeyAttribute] === null || value === null; - this[accessor] = this.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions); + this[accessor] = this.dataValues[accessor] = isEmpty ? null : new include.model(value, childOptions); } else { isEmpty = value[0] && value[0][primaryKeyAttribute] === null; this[accessor] = this.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions); diff --git a/lib/sequelize.js b/lib/sequelize.js index 0e0e677cfe01..d701eb17d9d1 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -526,7 +526,7 @@ class Sequelize { * * @returns {Promise} * - * @see {@link Model.build} for more information about instance option. + * @see {@link Model.constructor} for more information about instance option. */ query(sql, options) { diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 472ddfc5ab4e..3e6c0c6ec4c1 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -2828,8 +2828,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); it('correctly uses bId in A', function() { - const a1 = this.A.build({ name: 'a1' }), - b1 = this.B.build({ name: 'b1' }); + const a1 = new this.A({ name: 'a1' }), + b1 = new this.B({ name: 'b1' }); return a1 .save() @@ -2852,8 +2852,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); it('correctly uses bId in A', function() { - const a1 = this.A.build({ name: 'a1' }), - b1 = this.B.build({ name: 'b1' }); + const a1 = new this.A({ name: 'a1' }), + b1 = new this.B({ name: 'b1' }); return a1 .save() @@ -2950,9 +2950,9 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); it('correctly sets user and owner', function() { - const p1 = this.Project.build({ projectName: 'p1' }), - u1 = this.User.build({ name: 'u1' }), - u2 = this.User.build({ name: 'u2' }); + const p1 = new this.Project({ projectName: 'p1' }), + u1 = new this.User({ name: 'u1' }), + u2 = new this.User({ name: 'u2' }); return p1 .save() diff --git a/test/integration/dialects/sqlite/dao.test.js b/test/integration/dialects/sqlite/dao.test.js index 6c9b10666f20..ba5cd0d11af6 100644 --- a/test/integration/dialects/sqlite/dao.test.js +++ b/test/integration/dialects/sqlite/dao.test.js @@ -33,7 +33,7 @@ if (dialect === 'sqlite') { describe('findAll', () => { it('handles dates correctly', function() { - const user = this.User.build({ username: 'user' }); + const user = new this.User({ username: 'user' }); user.dataValues.createdAt = new Date(2011, 4, 4); diff --git a/test/integration/instance.test.js b/test/integration/instance.test.js index 29269bc7783a..854e117587be 100644 --- a/test/integration/instance.test.js +++ b/test/integration/instance.test.js @@ -72,13 +72,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('isNewRecord', () => { it('returns true for non-saved objects', function() { - const user = this.User.build({ username: 'user' }); + const user = new this.User({ username: 'user' }); expect(user.id).to.be.null; expect(user.isNewRecord).to.be.ok; }); it('returns false for saved objects', function() { - return this.User.build({ username: 'user' }).save().then(user => { + return new this.User({ username: 'user' }).save().then(user => { expect(user.isNewRecord).to.not.be.ok; }); }); @@ -119,19 +119,19 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('default values', () => { describe('uuid', () => { it('should store a string in uuidv1 and uuidv4', function() { - const user = this.User.build({ username: 'a user' }); + const user = new this.User({ username: 'a user' }); expect(user.uuidv1).to.be.a('string'); expect(user.uuidv4).to.be.a('string'); }); it('should store a string of length 36 in uuidv1 and uuidv4', function() { - const user = this.User.build({ username: 'a user' }); + const user = new this.User({ username: 'a user' }); expect(user.uuidv1).to.have.length(36); expect(user.uuidv4).to.have.length(36); }); it('should store a valid uuid in uuidv1 and uuidv4 that conforms to the UUID v1 and v4 specifications', function() { - const user = this.User.build({ username: 'a user' }); + const user = new this.User({ username: 'a user' }); expect(isUUID(user.uuidv1)).to.be.true; expect(isUUID(user.uuidv4, 4)).to.be.true; }); @@ -150,7 +150,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - const person = Person.build({}); + const person = new Person({}); expect(person.id1).to.be.ok; expect(person.id1).to.have.length(36); @@ -160,14 +160,14 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('current date', () => { it('should store a date in touchedAt', function() { - const user = this.User.build({ username: 'a user' }); + const user = new this.User({ username: 'a user' }); expect(user.touchedAt).to.be.instanceof(Date); }); it('should store the current date in touchedAt', function() { const clock = sinon.useFakeTimers(); clock.tick(5000); - const user = this.User.build({ username: 'a user' }); + const user = new this.User({ username: 'a user' }); clock.restore(); expect(+user.touchedAt).to.be.equal(5000); }); @@ -175,7 +175,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('allowNull date', () => { it('should be just "null" and not Date with Invalid Date', function() { - return this.User.build({ username: 'a user' }).save().then(() => { + return new this.User({ username: 'a user' }).save().then(() => { return this.User.findOne({ where: { username: 'a user' } }).then(user => { expect(user.dateAllowNullTrue).to.be.null; }); @@ -184,7 +184,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { it('should be the same valid date when saving the date', function() { const date = new Date(); - return this.User.build({ username: 'a user', dateAllowNullTrue: date }).save().then(() => { + return new this.User({ username: 'a user', dateAllowNullTrue: date }).save().then(() => { return this.User.findOne({ where: { username: 'a user' } }).then(user => { expect(user.dateAllowNullTrue.toString()).to.equal(date.toString()); }); @@ -194,7 +194,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('super user boolean', () => { it('should default to false', function() { - return this.User.build({ + return new this.User({ username: 'a user' }) .save() @@ -211,7 +211,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should override default when given truthy boolean', function() { - return this.User.build({ + return new this.User({ username: 'a user', isSuperUser: true }) @@ -229,7 +229,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should override default when given truthy boolean-string ("true")', function() { - return this.User.build({ + return new this.User({ username: 'a user', isSuperUser: 'true' }) @@ -247,7 +247,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should override default when given truthy boolean-int (1)', function() { - return this.User.build({ + return new this.User({ username: 'a user', isSuperUser: 1 }) @@ -267,7 +267,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { it('should throw error when given value of incorrect type', function() { let callCount = 0; - return this.User.build({ + return new this.User({ username: 'a user', isSuperUser: 'INCORRECT_VALUE_TYPE' }) @@ -579,7 +579,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { timestamps: false, logging: false }); return User.sync().then(() => { - const user = User.build({ username: 'foo' }); + const user = new User({ username: 'foo' }); expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); }); }); diff --git a/test/integration/instance.validations.test.js b/test/integration/instance.validations.test.js index 7fd178324ce9..9a6b451cadf7 100644 --- a/test/integration/instance.validations.test.js +++ b/test/integration/instance.validations.test.js @@ -279,8 +279,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', function() { - const user = this.User.build({ id: 'helloworld' }); + it('should emit an error when we try to enter in a string for an auto increment key through new Model().validate()', function() { + const user = new this.User({ id: 'helloworld' }); return expect(user.validate()).to.be.rejected.then(err => { expect(err.get('id')[0].message).to.equal('ID must be an integer!'); @@ -288,7 +288,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('should emit an error when we try to .save()', function() { - const user = this.User.build({ id: 'helloworld' }); + const user = new this.User({ id: 'helloworld' }); return user.save().catch(err => { expect(err).to.be.an.instanceOf(Error); expect(err.get('id')[0].message).to.equal('ID must be an integer!'); @@ -395,13 +395,13 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingUser = User.build({ name: '3' }); + const failingUser = new User({ name: '3' }); return expect(failingUser.validate()).to.be.rejected.then(error => { expect(error).to.be.an.instanceOf(Error); expect(error.get('name')[0].message).to.equal("name should equal '2'"); - const successfulUser = User.build({ name: '2' }); + const successfulUser = new User({ name: '2' }); return expect(successfulUser.validate()).not.to.be.rejected; }); }); @@ -424,11 +424,11 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); return User.sync().then(() => { - return expect(User.build({ name: 'error' }).validate()).to.be.rejected.then(error => { + return expect(new User({ name: 'error' }).validate()).to.be.rejected.then(error => { expect(error).to.be.instanceof(Sequelize.ValidationError); expect(error.get('name')[0].message).to.equal('Invalid username'); - return expect(User.build({ name: 'no error' }).validate()).not.to.be.rejected; + return expect(new User({ name: 'no error' }).validate()).not.to.be.rejected; }); }); }); @@ -444,8 +444,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User - .build({ age: -1 }) + return expect(new User({ age: -1 }) .validate()) .to.be.rejected .then(error => { @@ -473,15 +472,13 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(Foo - .build({ field1: null, field2: null }) + return expect(new Foo({ field1: null, field2: null }) .validate()) .to.be.rejected .then(error => { expect(error.get('xnor')[0].message).to.equal('xnor failed'); - return expect(Foo - .build({ field1: 33, field2: null }) + return expect(new Foo({ field1: 33, field2: null }) .validate()) .not.to.be.rejected; }); @@ -496,7 +493,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } } }), - foo = Foo.build({ bar: 'a' }); + foo = new Foo({ bar: 'a' }); return expect(foo.validate()).not.to.be.rejected.then(() => { return expect(foo.validate()).not.to.be.rejected; }); @@ -515,7 +512,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingBar = Bar.build({ field: 'value3' }); + const failingBar = new Bar({ field: 'value3' }); return expect(failingBar.validate()).to.be.rejected.then(errors => { expect(errors.get('field')).to.have.length(1); @@ -536,7 +533,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingBar = Bar.build({ field: 'value3' }); + const failingBar = new Bar({ field: 'value3' }); return expect(failingBar.validate({ skip: ['field'] })).not.to.be.rejected; }); @@ -554,7 +551,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingBar = Bar.build({ field: this.sequelize.literal('5 + 1') }); + const failingBar = new Bar({ field: this.sequelize.literal('5 + 1') }); return expect(failingBar.validate()).not.to.be.rejected; }); @@ -590,7 +587,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const user = User.build({ name: 'RedCat' }); + const user = new User({ name: 'RedCat' }); expect(user.getDataValue('name')).to.equal('RedCat'); user.setDataValue('name', 'YellowCat'); @@ -604,7 +601,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -616,7 +613,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -628,7 +625,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -640,7 +637,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -652,7 +649,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -664,7 +661,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -676,7 +673,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ email: null }).validate()).not.to.be.rejected; }); @@ -702,13 +699,13 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); return Sequelize.Promise.all([ - expect(User.build({ + expect(new User({ password: 'short', salt: '42' }).validate()).to.be.rejected.then(errors => { expect(errors.get('password')[0].message).to.equal('Please choose a longer password'); }), - expect(User.build({ + expect(new User({ password: 'loooooooong', salt: '42' }).validate()).not.to.be.rejected @@ -729,10 +726,10 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + return expect(new User({ name: 'abcdefg' }).validate()).not.to.be.rejected.then(() => { - return expect(User.build({ + return expect(new User({ name: 'a' }).validate()).to.be.rejected; }).then(errors => { diff --git a/test/integration/instance/destroy.test.js b/test/integration/instance/destroy.test.js index cc8715f515d0..c674e4106e07 100644 --- a/test/integration/instance/destroy.test.js +++ b/test/integration/instance/destroy.test.js @@ -412,7 +412,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { return this.sequelize.sync({ force: true }) .then(() => { - return Date.build({ date: Infinity }) + return new Date({ date: Infinity }) .save() .then(date => { return date.destroy(); diff --git a/test/integration/instance/save.test.js b/test/integration/instance/save.test.js index a07ceed8bad2..8d4cd508c4dc 100644 --- a/test/integration/instance/save.test.js +++ b/test/integration/instance/save.test.js @@ -64,7 +64,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = sequelize.define('User', { username: Support.Sequelize.STRING }); return User.sync({ force: true }).then(() => { return sequelize.transaction().then(t => { - return User.build({ username: 'foo' }).save({ transaction: t }).then(() => { + return new User({ username: 'foo' }).save({ transaction: t }).then(() => { return User.count().then(count1 => { return User.count({ transaction: t }).then(count2 => { expect(count1).to.equal(0); @@ -130,7 +130,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('only validates fields in passed array', function() { - return this.User.build({ + return new this.User({ validateTest: 'cake', // invalid, but not saved validateCustom: '1' }).save({ @@ -273,7 +273,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { it('stores an entry in the database', function() { const username = 'user', User = this.User, - user = this.User.build({ + user = new this.User({ username, touchedAt: new Date(1984, 8, 23) }); @@ -327,7 +327,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const now = new Date(); now.setMilliseconds(0); - const user = this.User.build({ username: 'user' }); + const user = new this.User({ username: 'user' }); this.clock.tick(1000); return user.save().then(savedUser => { @@ -520,7 +520,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should fail a validation upon building', function() { - return this.User.build({ aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' }).save() + return new this.User({ aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' }).save() .catch(err => { expect(err).to.exist; expect(err).to.be.instanceof(Object); @@ -545,7 +545,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('takes zero into account', function() { - return this.User.build({ aNumber: 0 }).save({ + return new this.User({ aNumber: 0 }).save({ fields: ['aNumber'] }).then(user => { expect(user.aNumber).to.equal(0); diff --git a/test/integration/instance/to-json.test.js b/test/integration/instance/to-json.test.js index cc4b4715ac54..59de8a33595a 100644 --- a/test/integration/instance/to-json.test.js +++ b/test/integration/instance/to-json.test.js @@ -67,9 +67,9 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); }); - describe('build', () => { + describe('constructor', () => { it('returns an object containing all values', function() { - const user = this.User.build({ + const user = new this.User({ username: 'Adam', age: 22, level: -1, @@ -88,7 +88,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('returns a response that can be stringified', function() { - const user = this.User.build({ + const user = new this.User({ username: 'test.user', age: 99, isAdmin: true, @@ -98,7 +98,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('returns a response that can be stringified and then parsed', function() { - const user = this.User.build({ username: 'test.user', age: 99, isAdmin: true }); + const user = new this.User({ username: 'test.user', age: 99, isAdmin: true }); expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ username: 'test.user', age: 99, isAdmin: true, isUser: false, id: null }); }); }); diff --git a/test/integration/instance/update.test.js b/test/integration/instance/update.test.js index c9b1328ca702..1aeadc4af577 100644 --- a/test/integration/instance/update.test.js +++ b/test/integration/instance/update.test.js @@ -169,7 +169,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should only save passed attributes', function() { - const user = this.User.build(); + const user = new this.User(); return user.save().then(() => { user.set('validateTest', 5); expect(user.changed('validateTest')).to.be.ok; @@ -187,7 +187,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should save attributes affected by setters', function() { - const user = this.User.build(); + const user = new this.User(); return user.update({ validateSideEffect: 5 }).then(() => { expect(user.validateSideEffect).to.be.equal(5); }).then(() => { diff --git a/test/integration/instance/values.test.js b/test/integration/instance/values.test.js index 1fac0f5b2017..4407bad3ee57 100644 --- a/test/integration/instance/values.test.js +++ b/test/integration/instance/values.test.js @@ -15,7 +15,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { name: { type: DataTypes.STRING } }); - const user = User.build({ id: 1, name: 'Mick' }); + const user = new User({ id: 1, name: 'Mick' }); expect(user.get('id')).to.equal(1); expect(user.get('name')).to.equal('Mick'); @@ -32,7 +32,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { identifier: { type: DataTypes.STRING, primaryKey: true } }); - const user = User.build({ identifier: 'identifier' }); + const user = new User({ identifier: 'identifier' }); expect(user.get('identifier')).to.equal('identifier'); user.set('identifier', 'another identifier'); @@ -44,7 +44,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { identifier: { type: DataTypes.STRING, primaryKey: true } }); - const user = User.build({}, { + const user = new User({}, { isNewRecord: false }); @@ -64,7 +64,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { underscored: true }); - const user = User.build({}, { + const user = new User({}, { isNewRecord: false }); @@ -89,7 +89,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const user = User.build(); + const user = new User(); user.set({ name: 'antonio banderaz', @@ -157,7 +157,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.hasMany(Tag); Product.belongsTo(User); - const product = Product.build({}, { + const product = new Product({}, { include: [ User, Tag @@ -200,7 +200,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.hasMany(Tag); Product.belongsTo(User); - const product = Product.build({}, { + const product = new Product({}, { include: [ User, Tag @@ -241,7 +241,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const product = Product.build({ + const product = new Product({ price: 10 }); expect(product.get('price')).to.equal(1000); @@ -260,7 +260,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const product = Product.build({ + const product = new Product({ priceInCents: 1000 }); expect(product.get('price')).to.equal(10); @@ -282,7 +282,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const product = Product.build({ + const product = new Product({ price: 10 }); expect(product.toJSON()).to.deep.equal({ withTaxes: 1250, price: 1000, id: null }); @@ -305,7 +305,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { }); return this.sequelize.sync().then(() => { - const contact = Contact.build({ + const contact = new Contact({ first: 'My', last: 'Name', tags: ['yes', 'no'] @@ -330,7 +330,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.belongsTo(User); - const product = Product.build({}, { + const product = new Product({}, { include: [ User ] @@ -357,7 +357,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { title: Sequelize.STRING }); - const product = Product.build({ + const product = new Product({ id: 1, title: 'Chair' }, { raw: true }); @@ -401,7 +401,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.belongsTo(User); - const product = Product.build({}, { + const product = new Product({}, { include: [ User ] @@ -455,7 +455,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { name: { type: DataTypes.STRING } }); - const user = User.build({ + const user = new User({ name: 'Jan Meier' }); user.set('name', 'Mick Hansen'); @@ -469,7 +469,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { }); return User.sync().then(() => { - const user = User.build({ + const user = new User({ name: 'Jan Meier' }); user.set('name', 'Mick Hansen'); @@ -518,7 +518,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { title: { type: DataTypes.STRING } }); - const user = User.build({ + const user = new User({ name: 'Jan Meier', title: 'Mr' }); @@ -534,7 +534,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { name: { type: DataTypes.STRING } }); - const user = User.build({ + const user = new User({ name: 'Jan Meier' }); user.set('name', 'Mick Hansen'); diff --git a/test/integration/model.test.js b/test/integration/model.test.js index f05b85954aaa..cb206b71913f 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -226,7 +226,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); return Task.sync({ force: true }).then(() => { - return Task.build().save().then(record => { + return new Task().save().then(record => { expect(record.title).to.be.a('string'); expect(record.title).to.equal(''); expect(titleSetter.notCalled).to.be.ok; // The setter method should not be invoked for default values @@ -463,9 +463,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - describe('build', () => { + describe('constructor', () => { it("doesn't create database entries", function() { - this.User.build({ username: 'John Wayne' }); + new this.User({ username: 'John Wayne' }); return this.User.findAll().then(users => { expect(users).to.have.length(0); }); @@ -480,11 +480,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { flag: { type: Sequelize.BOOLEAN, defaultValue: false } }); - expect(Task.build().title).to.equal('a task!'); - expect(Task.build().foo).to.equal(2); - expect(Task.build().bar).to.not.be.ok; - expect(Task.build().foobar).to.equal('asd'); - expect(Task.build().flag).to.be.false; + expect(new Task().title).to.equal('a task!'); + expect(new Task().foo).to.equal(2); + expect(new Task().bar).to.not.be.ok; + expect(new Task().foobar).to.equal('asd'); + expect(new Task().flag).to.be.false; }); it('fills the objects with default values', function() { @@ -495,11 +495,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { foobar: { type: Sequelize.TEXT, defaultValue: 'asd' }, flag: { type: Sequelize.BOOLEAN, defaultValue: false } }, { timestamps: false }); - expect(Task.build().title).to.equal('a task!'); - expect(Task.build().foo).to.equal(2); - expect(Task.build().bar).to.not.be.ok; - expect(Task.build().foobar).to.equal('asd'); - expect(Task.build().flag).to.be.false; + expect(new Task().title).to.equal('a task!'); + expect(new Task().foo).to.equal(2); + expect(new Task().bar).to.not.be.ok; + expect(new Task().foobar).to.equal('asd'); + expect(new Task().flag).to.be.false; }); it('attaches getter and setter methods from attribute definition', function() { @@ -515,9 +515,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - expect(Product.build({ price: 42 }).price).to.equal('answer = 84'); + expect(new Product({ price: 42 }).price).to.equal('answer = 84'); - const p = Product.build({ price: 1 }); + const p = new Product({ price: 1 }); expect(p.price).to.equal('answer = 43'); p.price = 0; @@ -544,8 +544,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - expect(Product.build({ price: 20 }).priceInCents).to.equal(20 * 100); - expect(Product.build({ priceInCents: 30 * 100 }).price).to.equal(`$${30}`); + expect(new Product({ price: 20 }).priceInCents).to.equal(20 * 100); + expect(new Product({ priceInCents: 30 * 100 }).price).to.equal(`$${30}`); }); it('attaches getter and setter methods from options only if not defined in attribute', function() { @@ -567,7 +567,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - const p = Product.build({ price1: 1, price2: 2 }); + const p = new Product({ price1: 1, price2: 2 }); expect(p.price1).to.equal(10); expect(p.price2).to.equal(20); @@ -589,7 +589,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.hasMany(Tag); Product.belongsTo(User); - const product = Product.build({ + const product = new Product({ id: 1, title: 'Chair', Tags: [ @@ -631,7 +631,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.belongsToMany(User, { as: 'followers', through: 'product_followers' }); User.belongsToMany(Product, { as: 'following', through: 'product_followers' }); - const product = Product.build({ + const product = new Product({ id: 1, title: 'Chair', categories: [ diff --git a/test/integration/model/attributes.test.js b/test/integration/model/attributes.test.js index 4255f4e86e19..38164addc8a6 100644 --- a/test/integration/model/attributes.test.js +++ b/test/integration/model/attributes.test.js @@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }) .then(() => { return Promise.join( - this.Student.build({ no: 1 }).getCourses({ where: { no: 100 } }), + new this.Student({ no: 1 }).getCourses({ where: { no: 100 } }), this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }) ); }) diff --git a/test/integration/model/attributes/types.test.js b/test/integration/model/attributes/types.test.js index 82dc166a68d0..da2da915b90b 100644 --- a/test/integration/model/attributes/types.test.js +++ b/test/integration/model/attributes/types.test.js @@ -53,7 +53,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should not be ignored in dataValues get', function() { - const user = this.User.build({ + const user = new this.User({ field1: 'field1_value', field2: 'field2_value' }); diff --git a/test/integration/model/schema.test.js b/test/integration/model/schema.test.js index 5bfbb040e120..c65db407269f 100644 --- a/test/integration/model/schema.test.js +++ b/test/integration/model/schema.test.js @@ -256,20 +256,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should be able to insert data into both schemas using instance.save and retrieve/count it', function() { //building and saving in random order to make sure calling // .schema doesn't impact model prototype - let restaurauntModel = this.RestaurantOne.build({ bar: 'one.1' }); + let restaurauntModel = new this.RestaurantOne({ bar: 'one.1' }); return restaurauntModel.save() .then(() => { - restaurauntModel = this.RestaurantTwo.build({ bar: 'two.1' }); + restaurauntModel = new this.RestaurantTwo({ bar: 'two.1' }); return restaurauntModel.save(); }).then(() => { - restaurauntModel = this.RestaurantOne.build({ bar: 'one.2' }); + restaurauntModel = new this.RestaurantOne({ bar: 'one.2' }); return restaurauntModel.save(); }).then(() => { - restaurauntModel = this.RestaurantTwo.build({ bar: 'two.2' }); + restaurauntModel = new this.RestaurantTwo({ bar: 'two.2' }); return restaurauntModel.save(); }).then(() => { - restaurauntModel = this.RestaurantTwo.build({ bar: 'two.3' }); + restaurauntModel = new this.RestaurantTwo({ bar: 'two.3' }); return restaurauntModel.save(); }).then(() => { return this.RestaurantOne.findAll(); @@ -484,12 +484,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should build and persist instances to 2 schemas concurrently in any order', function() { const Restaurant = this.Restaurant; - let restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.1' }); - const restaurauntModelSchema2 = Restaurant.schema(SCHEMA_TWO).build({ bar: 'two.1' }); + let restaurauntModelSchema1 = new (Restaurant.schema(SCHEMA_ONE))({ bar: 'one.1' }); + const restaurauntModelSchema2 = new (Restaurant.schema(SCHEMA_TWO))({ bar: 'two.1' }); return restaurauntModelSchema1.save() .then(() => { - restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.2' }); + restaurauntModelSchema1 = new (Restaurant.schema(SCHEMA_ONE))({ bar: 'one.2' }); return restaurauntModelSchema2.save(); }).then(() => { return restaurauntModelSchema1.save(); diff --git a/test/integration/model/searchPath.test.js b/test/integration/model/searchPath.test.js index 05e24283f03e..803c7b85e98e 100644 --- a/test/integration/model/searchPath.test.js +++ b/test/integration/model/searchPath.test.js @@ -156,20 +156,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', function() { const Restaurant = this.Restaurant; - let restaurauntModel = Restaurant.build({ bar: 'one.1' }); + let restaurauntModel = new Restaurant({ bar: 'one.1' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }) .then(() => { - restaurauntModel = Restaurant.build({ bar: 'one.2' }); + restaurauntModel = new Restaurant({ bar: 'one.2' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.1' }); + restaurauntModel = new Restaurant({ bar: 'two.1' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.2' }); + restaurauntModel = new Restaurant({ bar: 'two.2' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.3' }); + restaurauntModel = new Restaurant({ bar: 'two.3' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { return Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); @@ -210,19 +210,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', function() { const Restaurant = this.Restaurant; - let restaurauntModel = Restaurant.build({ bar: 'one.1' }); + let restaurauntModel = new Restaurant({ bar: 'one.1' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'one.2' }); + restaurauntModel = new Restaurant({ bar: 'one.2' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.1' }); + restaurauntModel = new Restaurant({ bar: 'two.1' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.2' }); + restaurauntModel = new Restaurant({ bar: 'two.2' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.3' }); + restaurauntModel = new Restaurant({ bar: 'two.3' }); return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { return Restaurant.findAll({ @@ -440,12 +440,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should build and persist instances to 2 schemas concurrently in any order', function() { const Restaurant = this.Restaurant; - let restaurauntModelSchema1 = Restaurant.build({ bar: 'one.1' }); - const restaurauntModelSchema2 = Restaurant.build({ bar: 'two.1' }); + let restaurauntModelSchema1 = new Restaurant({ bar: 'one.1' }); + const restaurauntModelSchema2 = new Restaurant({ bar: 'two.1' }); return restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }) .then(() => { - restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' }); + restaurauntModelSchema1 = new Restaurant({ bar: 'one.2' }); return restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); }).then(() => { return restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index 4771cda4d297..12878a65d408 100755 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -833,7 +833,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { 'override': overrideGetterMethod } }; - const testEntity = this.sequelize.define('TestEntity', {}, { + const TestEntity = this.sequelize.define('TestEntity', {}, { 'setterMethods': { 'custom': customSetterMethod, 'override': customOverrideSetterMethod @@ -845,7 +845,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); // Create Instance to test - const instance = testEntity.build(); + const instance = new TestEntity(); // Call Getters instance.default; diff --git a/test/unit/associations/belongs-to-many.test.js b/test/unit/associations/belongs-to-many.test.js index 672187f800f1..ce53626d881a 100644 --- a/test/unit/associations/belongs-to-many.test.js +++ b/test/unit/associations/belongs-to-many.test.js @@ -90,7 +90,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { User.belongsToMany(Task, { through: 'UserTasks', as: 'task' }); - const user = User.build(); + const user = new User(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); @@ -158,13 +158,13 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { User.belongsToMany(Task, { through: UserTasks }); Task.belongsToMany(User, { through: UserTasks }); - const user = User.build({ + const user = new User({ id: 42 }), - task1 = Task.build({ + task1 = new Task({ id: 15 }), - task2 = Task.build({ + task2 = new Task({ id: 16 }); diff --git a/test/unit/associations/belongs-to.test.js b/test/unit/associations/belongs-to.test.js index a431edfc0aa8..7a1f7926a605 100644 --- a/test/unit/associations/belongs-to.test.js +++ b/test/unit/associations/belongs-to.test.js @@ -45,7 +45,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { User.belongsTo(Task, { as: 'task' }); - const user = User.build(); + const user = new User(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js index e17a52e2df9d..ab5d424f49c6 100644 --- a/test/unit/associations/has-many.test.js +++ b/test/unit/associations/has-many.test.js @@ -27,13 +27,13 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { User.hasMany(Task); - const user = User.build({ + const user = new User({ id: 42 }), - task1 = Task.build({ + task1 = new Task({ id: 15 }), - task2 = Task.build({ + task2 = new Task({ id: 16 }); @@ -118,7 +118,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { User.hasMany(Task, { as: 'task' }); - const user = User.build(); + const user = new User(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); @@ -130,7 +130,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { Project.hasMany(Task); - const company = Project.build(); + const company = new Project(); expect(company.hasTasks).not.to.be.a('function'); }); @@ -146,12 +146,12 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { it('should fetch associations for a single instance', () => { const findAll = stub(Task, 'findAll').resolves([ - Task.build({}), - Task.build({}) + new Task({}), + new Task({}) ]); User.Tasks = User.hasMany(Task, { foreignKey }); - const actual = User.Tasks.get(User.build({ id: idA })); + const actual = User.Tasks.get(new User({ id: idA })); const where = { [foreignKey]: idA @@ -171,25 +171,25 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { it('should fetch associations for multiple source instances', () => { const findAll = stub(Task, 'findAll').returns( Promise.resolve([ - Task.build({ + new Task({ 'user_id': idA }), - Task.build({ + new Task({ 'user_id': idA }), - Task.build({ + new Task({ 'user_id': idA }), - Task.build({ + new Task({ 'user_id': idB }) ])); User.Tasks = User.hasMany(Task, { foreignKey }); const actual = User.Tasks.get([ - User.build({ id: idA }), - User.build({ id: idB }), - User.build({ id: idC }) + new User({ id: idA }), + new User({ id: idB }), + new User({ id: idC }) ]); expect(findAll).to.have.been.calledOnce; diff --git a/test/unit/associations/has-one.test.js b/test/unit/associations/has-one.test.js index de3f11c5ed62..9aa4d44a9eed 100644 --- a/test/unit/associations/has-one.test.js +++ b/test/unit/associations/has-one.test.js @@ -56,7 +56,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { User.hasOne(Task, { as: 'task' }); - const user = User.build(); + const user = new User(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); diff --git a/test/unit/instance-validator.test.js b/test/unit/instance-validator.test.js index 135c18742be5..6e045aabc8a1 100644 --- a/test/unit/instance-validator.test.js +++ b/test/unit/instance-validator.test.js @@ -30,7 +30,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('validate', () => { it('runs the validation sequence and hooks when the hooks option is true', function() { - const instanceValidator = new InstanceValidator(this.User.build(), { hooks: true }); + const instanceValidator = new InstanceValidator(new this.User(), { hooks: true }); const _validate = sinon.spy(instanceValidator, '_validate'); const _validateAndRunHooks = sinon.spy(instanceValidator, '_validateAndRunHooks'); @@ -41,7 +41,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('runs the validation sequence but skips hooks if the hooks option is false', function() { - const instanceValidator = new InstanceValidator(this.User.build(), { hooks: false }); + const instanceValidator = new InstanceValidator(new this.User(), { hooks: false }); const _validate = sinon.spy(instanceValidator, '_validate'); const _validateAndRunHooks = sinon.spy(instanceValidator, '_validateAndRunHooks'); @@ -52,14 +52,14 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('fulfills when validation is successful', function() { - const instanceValidator = new InstanceValidator(this.User.build()); + const instanceValidator = new InstanceValidator(new this.User()); const result = instanceValidator.validate(); return expect(result).to.be.fulfilled; }); it('rejects with a validation error when validation fails', function() { - const instanceValidator = new InstanceValidator(this.User.build({ fails: true })); + const instanceValidator = new InstanceValidator(new this.User({ fails: true })); const result = instanceValidator.validate(); return expect(result).to.be.rejectedWith(SequelizeValidationError); @@ -73,7 +73,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const instanceValidator = new InstanceValidator(User.build()); + const instanceValidator = new InstanceValidator(new User()); const result = instanceValidator.validate(); return expect(result).to.be.rejectedWith(SequelizeValidationError, /user\.name cannot be null/); @@ -82,7 +82,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('_validateAndRunHooks', () => { beforeEach(function() { - this.successfulInstanceValidator = new InstanceValidator(this.User.build()); + this.successfulInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(this.successfulInstanceValidator, '_validate').resolves(); }); @@ -99,7 +99,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('should run beforeValidate hook but not afterValidate hook when _validate is unsuccessful', function() { - const failingInstanceValidator = new InstanceValidator(this.User.build()); + const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const beforeValidate = sinon.spy(); const afterValidate = sinon.spy(); @@ -122,7 +122,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('validatedFailed hook', () => { it('should call validationFailed hook when validation fails', function() { - const failingInstanceValidator = new InstanceValidator(this.User.build()); + const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const validationFailedHook = sinon.spy(); this.User.validationFailed(validationFailedHook); @@ -133,7 +133,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('should not replace the validation error in validationFailed hook by default', function() { - const failingInstanceValidator = new InstanceValidator(this.User.build()); + const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().resolves(); this.User.validationFailed(validationFailedHook); @@ -144,7 +144,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('should replace the validation error if validationFailed hook creates a new error', function() { - const failingInstanceValidator = new InstanceValidator(this.User.build()); + const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().throws(new Error('validation failed hook error')); this.User.validationFailed(validationFailedHook); diff --git a/test/unit/instance/build.test.js b/test/unit/instance/build.test.js index e2b89e65f4b4..6bbaa569764a 100644 --- a/test/unit/instance/build.test.js +++ b/test/unit/instance/build.test.js @@ -37,7 +37,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { timestamp: false }), - instance = Model.build({ ip: '127.0.0.1', ip2: '0.0.0.0' }); + instance = new Model({ ip: '127.0.0.1', ip2: '0.0.0.0' }); expect(instance.get('created_time')).to.be.ok; expect(instance.get('created_time')).to.be.an.instanceof(Date); @@ -57,7 +57,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: DataTypes.UUIDV4 } }), - instance = Model.build({ + instance = new Model({ id: undefined }); @@ -76,7 +76,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: 2 } }), - instance = Model.build({ + instance = new Model({ number1: undefined }); @@ -93,11 +93,11 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: { foo: 'bar' } } }), - instance = Model.build(); + instance = new Model(); instance.data.foo = 'biz'; expect(instance.get('data')).to.eql({ foo: 'biz' }); - expect(Model.build().get('data')).to.eql({ foo: 'bar' }); + expect(new Model().get('data')).to.eql({ foo: 'bar' }); }); }); }); diff --git a/test/unit/instance/changed.test.js b/test/unit/instance/changed.test.js index 15893cd8fef2..99bb754f063f 100644 --- a/test/unit/instance/changed.test.js +++ b/test/unit/instance/changed.test.js @@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for changed primitive', function() { - const user = this.User.build({ + const user = new this.User({ name: 'a' }, { isNewRecord: false, @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return falsy for unchanged primitive', function() { - const user = this.User.build({ + const user = new this.User({ name: 'a', meta: null }, { @@ -48,7 +48,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for multiple changed values', function() { - const user = this.User.build({ + const user = new this.User({ name: 'a', birthday: new Date(new Date() - 10) }, { @@ -67,7 +67,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const firstDate = new Date(milliseconds); const secondDate = new Date(milliseconds); - const user = this.User.build({ + const user = new this.User({ birthday: firstDate }, { isNewRecord: false, @@ -79,7 +79,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for changed JSON with same object', function() { - const user = this.User.build({ + const user = new this.User({ meta: { city: 'Copenhagen' } @@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for JSON dot.separated key with changed values', function() { - const user = this.User.build({ + const user = new this.User({ meta: { city: 'Stockholm' } @@ -110,7 +110,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return false for JSON dot.separated key with same value', function() { - const user = this.User.build({ + const user = new this.User({ meta: { city: 'Gothenburg' } @@ -124,7 +124,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for JSON dot.separated key with object', function() { - const user = this.User.build({ + const user = new this.User({ meta: { address: { street: 'Main street', number: '40' } } @@ -138,7 +138,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return false for JSON dot.separated key with same object', function() { - const user = this.User.build({ + const user = new this.User({ meta: { address: { street: 'Main street', number: '40' } } @@ -157,7 +157,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { attributes[attr] = null; } - const user = this.User.build(attributes, { + const user = new this.User(attributes, { isNewRecord: false, raw: true }); @@ -173,7 +173,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('setDataValue', () => { it('should return falsy for unchanged primitive', function() { - const user = this.User.build({ + const user = new this.User({ name: 'a', meta: null }, { diff --git a/test/unit/instance/decrement.test.js b/test/unit/instance/decrement.test.js index 24fa2e991a7e..e648f17d0919 100644 --- a/test/unit/instance/decrement.test.js +++ b/test/unit/instance/decrement.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow decrements even if options are not given', () => { - instance = Model.build({ id: 3 }, { isNewRecord: false }); + instance = new Model({ id: 3 }, { isNewRecord: false }); expect(() => { instance.decrement(['id']); }).to.not.throw(); diff --git a/test/unit/instance/destroy.test.js b/test/unit/instance/destroy.test.js index 4932da5b473c..bcee8941f6c8 100644 --- a/test/unit/instance/destroy.test.js +++ b/test/unit/instance/destroy.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow destroies even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); + instance = new Model({ id: 1 }, { isNewRecord: false }); expect(() => { instance.destroy(); }).to.not.throw(); diff --git a/test/unit/instance/get.test.js b/test/unit/instance/get.test.js index 516c5a0763a5..e96885c32d5c 100644 --- a/test/unit/instance/get.test.js +++ b/test/unit/instance/get.test.js @@ -20,13 +20,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('invokes getter if raw: false', function() { - this.User.build().get('name'); + new this.User().get('name'); expect(this.getSpy).to.have.been.called; }); it('does not invoke getter if raw: true', function() { - this.User.build().get('name', { raw: true }); + new this.User().get('name', { raw: true }); expect(this.getSpy).not.to.have.been.called; }); diff --git a/test/unit/instance/increment.test.js b/test/unit/instance/increment.test.js index 891bb56b48c0..9811e04c9212 100644 --- a/test/unit/instance/increment.test.js +++ b/test/unit/instance/increment.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow increments even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); + instance = new Model({ id: 1 }, { isNewRecord: false }); expect(() => { instance.increment(['id']); }).to.not.throw(); diff --git a/test/unit/instance/is-soft-deleted.test.js b/test/unit/instance/is-soft-deleted.test.js index 6f49a1b63088..69e209ef29cd 100644 --- a/test/unit/instance/is-soft-deleted.test.js +++ b/test/unit/instance/is-soft-deleted.test.js @@ -31,14 +31,14 @@ describe(Support.getTestDialectTeaser('Instance'), () => { paranoid: true }); - this.paranoidUser = ParanoidUser.build({ + this.paranoidUser = new ParanoidUser({ name: 'a' }, { isNewRecord: false, raw: true }); - this.user = User.build({ + this.user = new User({ name: 'a' }, { isNewRecord: false, diff --git a/test/unit/instance/previous.test.js b/test/unit/instance/previous.test.js index 6b468a53414b..9d0d2846b14c 100644 --- a/test/unit/instance/previous.test.js +++ b/test/unit/instance/previous.test.js @@ -22,7 +22,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - const instance = Model.build({ text: 'a', textCustom: 'abc' }); + const instance = new Model({ text: 'a', textCustom: 'abc' }); expect(instance.previous('text')).to.be.not.ok; expect(instance.previous('textCustom')).to.be.not.ok; diff --git a/test/unit/instance/reload.test.js b/test/unit/instance/reload.test.js index e7ab006bc5a9..55f9b9de3265 100644 --- a/test/unit/instance/reload.test.js +++ b/test/unit/instance/reload.test.js @@ -38,7 +38,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow reloads even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); + instance = new Model({ id: 1 }, { isNewRecord: false }); expect(() => { instance.reload(); }).to.not.throw(); diff --git a/test/unit/instance/restore.test.js b/test/unit/instance/restore.test.js index b2b6083acee9..0799b39bb8f6 100644 --- a/test/unit/instance/restore.test.js +++ b/test/unit/instance/restore.test.js @@ -38,7 +38,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow restores even if options are not given', () => { - instance = Model.build({ id: 1 }, { isNewRecord: false }); + instance = new Model({ id: 1 }, { isNewRecord: false }); expect(() => { instance.restore(); }).to.not.throw(); diff --git a/test/unit/instance/save.test.js b/test/unit/instance/save.test.js index 2005eff9cc90..17e0488494ca 100644 --- a/test/unit/instance/save.test.js +++ b/test/unit/instance/save.test.js @@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const Model = current.define('User', { }), - instance = Model.build({}, { isNewRecord: false }); + instance = new Model({}, { isNewRecord: false }); expect(() => { instance.save(); @@ -44,7 +44,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow saves even if options are not given', () => { - instance = Model.build({}); + instance = new Model({}); expect(() => { instance.save(); }).to.not.throw(); diff --git a/test/unit/instance/set.test.js b/test/unit/instance/set.test.js index 80d952c3a65b..b12609dccc52 100644 --- a/test/unit/instance/set.test.js +++ b/test/unit/instance/set.test.js @@ -15,9 +15,9 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = current.define('User', { meta: DataTypes.JSONB }); - const user = User.build({ + const user = new User({ meta: { - location: 'Stockhollm' + location: 'Stockholm' } }, { isNewRecord: false, @@ -41,9 +41,9 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: {} } }); - const user1 = User.build({}); - user1.set('meta.location', 'Stockhollm'); - const user2 = User.build({}); + const user1 = new User({}); + user1.set('meta.location', 'Stockholm'); + const user2 = new User({}); expect(user2.get('meta')).to.deep.equal({}); }); @@ -54,7 +54,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { allowNull: true } }); - const user1 = User.build({ + const user1 = new User({ date: null }); user1.set('date', '1970-01-01'); @@ -66,7 +66,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = current.define('User', { date: DataTypes.DATE }); - const user = User.build({ + const user = new User({ date: ' ' }, { isNewRecord: false, @@ -106,7 +106,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('does not set field to changed if field is set to the same value with custom setter using primitive value', () => { - const user = User.build({ + const user = new User({ phoneNumber: '+1 234 567' }); return user.save().then(() => { @@ -118,7 +118,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('sets field to changed if field is set to the another value with custom setter using primitive value', () => { - const user = User.build({ + const user = new User({ phoneNumber: '+1 234 567' }); return user.save().then(() => { @@ -130,7 +130,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('does not set field to changed if field is set to the same value with custom setter using object', () => { - const user = User.build({ + const user = new User({ phoneNumber: '+1 234 567' }); return user.save().then(() => { @@ -142,7 +142,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('sets field to changed if field is set to the another value with custom setter using object', () => { - const user = User.build({ + const user = new User({ phoneNumber: '+1 234 567' }); return user.save().then(() => { diff --git a/test/unit/instance/to-json.test.js b/test/unit/instance/to-json.test.js index ef84e93388ff..821ee3dace21 100644 --- a/test/unit/instance/to-json.test.js +++ b/test/unit/instance/to-json.test.js @@ -12,7 +12,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = current.define('User', { name: DataTypes.STRING }); - const user = User.build({ name: 'my-name' }); + const user = new User({ name: 'my-name' }); const json1 = user.toJSON(); expect(json1).to.have.property('name').and.be.equal('my-name'); @@ -28,7 +28,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { name: DataTypes.STRING, permissions: DataTypes.JSON }); - const user = User.build({ name: 'my-name', permissions: { admin: true, special: 'foobar' } }); + const user = new User({ name: 'my-name', permissions: { admin: true, special: 'foobar' } }); const json = user.toJSON(); expect(json) diff --git a/test/unit/model/findall.test.js b/test/unit/model/findall.test.js index dac8db6e9afe..12180e22c157 100644 --- a/test/unit/model/findall.test.js +++ b/test/unit/model/findall.test.js @@ -45,7 +45,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, { timestamps: false }); before(function() { - this.stub = sinon.stub(current.getQueryInterface(), 'select').callsFake(() => Model.build({})); + this.stub = sinon.stub(current.getQueryInterface(), 'select').callsFake(() => new Model({})); this.warnOnInvalidOptionsStub = sinon.stub(Model, 'warnOnInvalidOptions'); }); diff --git a/test/unit/model/overwriting-builtins.test.js b/test/unit/model/overwriting-builtins.test.js index c547bb9b09a9..dac4cf177009 100644 --- a/test/unit/model/overwriting-builtins.test.js +++ b/test/unit/model/overwriting-builtins.test.js @@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { set: DataTypes.STRING }); - const user = User.build({ set: 'A' }); + const user = new User({ set: 'A' }); expect(user.get('set')).to.equal('A'); user.set('set', 'B'); expect(user.get('set')).to.equal('B'); diff --git a/test/unit/model/removeAttribute.test.js b/test/unit/model/removeAttribute.test.js index 918f834ccd6f..dddbab40cc5c 100644 --- a/test/unit/model/removeAttribute.test.js +++ b/test/unit/model/removeAttribute.test.js @@ -30,7 +30,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Model.removeAttribute('id'); - const instance = Model.build(); + const instance = new Model(); expect(instance.dataValues).not.to.include.keys('undefined'); }); }); diff --git a/test/unit/model/validation.test.js b/test/unit/model/validation.test.js index 37c3263ee936..f9d8bc971025 100644 --- a/test/unit/model/validation.test.js +++ b/test/unit/model/validation.test.js @@ -196,7 +196,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingUser = UserFail.build({ name: failingValue }); + const failingUser = new UserFail({ name: failingValue }); return expect(failingUser.validate()).to.be.rejected.then(_errors => { expect(_errors.get('name')[0].message).to.equal(message); @@ -227,7 +227,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { validate: validations } }); - const successfulUser = UserSuccess.build({ name: succeedingValue }); + const successfulUser = new UserSuccess({ name: succeedingValue }); return expect(successfulUser.validate()).not.to.be.rejected; }); }; @@ -273,7 +273,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').callsFake(() => new Promise.resolve([User.build({}), 1])); + this.stub = sinon.stub(current, 'query').callsFake(() => new Promise.resolve([new User({}), 1])); }); after(function() { @@ -480,7 +480,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').resolves([User.build(), 1]); + this.stub = sinon.stub(current, 'query').resolves([new User(), 1]); }); after(function() { @@ -555,7 +555,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').resolves([User.build(), 1]); + this.stub = sinon.stub(current, 'query').resolves([new User(), 1]); }); after(function() { @@ -624,7 +624,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - this.stub = sinon.stub(current, 'query').resolves([this.User.build(), 1]); + this.stub = sinon.stub(current, 'query').resolves([new this.User(), 1]); }); after(function() { @@ -693,7 +693,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - this.stub = sinon.stub(current, 'query').resolves([this.User.build(), 1]); + this.stub = sinon.stub(current, 'query').resolves([new this.User(), 1]); }); after(function() { diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 50679f75c68b..21fbdc72978f 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -614,7 +614,7 @@ export interface CountWithOptions extends CountOptions { export interface FindAndCountOptions extends CountOptions, FindOptions {} /** - * Options for Model.build method + * Options for Model constructor */ export interface BuildOptions { /** @@ -1905,16 +1905,7 @@ export abstract class Model extends Hooks { ): Promise; /** - * Builds a new model instance. Values is an object of key value pairs, must be defined but can be empty. - */ - public static build( - this: { new (): M } & typeof Model, - record?: object, - options?: BuildOptions - ): M; - - /** - * Undocumented bulkBuild + * Builds multiple model instances in one go. */ public static bulkBuild( this: { new (): M } & typeof Model, @@ -2582,7 +2573,7 @@ export abstract class Model extends Hooks { * * Set can also be used to build instances for associations, if you have values for those. * When using set with associations you need to make sure the property key matches the alias of the - * association while also making sure that the proper include options have been set (from .build() or + * association while also making sure that the proper include options have been set (from the constructor or * .findOne()) * * If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the From 65d9fca42173b649259f9684536990a1eb575e79 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 4 May 2019 20:43:42 -0700 Subject: [PATCH 012/414] refactor(cls): remove cls support (#10817) BREAKING CHANGE: All CLS support has been removed. Migration guide: If you do not use CLS, you can ignore this. Check all your usage of the `.transaction` method and make you sure explicitly pass the `transaction` object for each subsequent query. --- docs/manual/transactions.md | 56 +-------- docs/upgrade-to-v6.md | 33 +++++ lib/model.js | 7 -- lib/sequelize.js | 89 ++----------- lib/transaction.js | 26 +--- package.json | 2 - test/integration/cls.test.js | 165 ------------------------- test/unit/model/find-or-create.test.js | 74 ----------- types/lib/sequelize.d.ts | 20 --- types/test/sequelize.ts | 3 - 10 files changed, 46 insertions(+), 429 deletions(-) delete mode 100644 test/integration/cls.test.js delete mode 100644 test/unit/model/find-or-create.test.js diff --git a/docs/manual/transactions.md b/docs/manual/transactions.md index 2d403b9a8731..ee01b7c7c685 100644 --- a/docs/manual/transactions.md +++ b/docs/manual/transactions.md @@ -52,68 +52,16 @@ return sequelize.transaction(t => { }); ``` -### Automatically pass transactions to all queries - -In the examples above, the transaction is still manually passed, by passing `{ transaction: t }` as the second argument. To automatically pass the transaction to all queries you must install the [continuation local storage](https://github.com/othiym23/node-continuation-local-storage) (CLS) module and instantiate a namespace in your own code: - -```js -const cls = require('continuation-local-storage'); -const namespace = cls.createNamespace('my-very-own-namespace'); -``` - -To enable CLS you must tell sequelize which namespace to use by using a static method of the sequelize constructor: - -```js -const Sequelize = require('sequelize'); -Sequelize.useCLS(namespace); - -new Sequelize(....); -``` - -Notice, that the `useCLS()` method is on the *constructor*, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances. - -CLS works like a thread-local storage for callbacks. What this means in practice is that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the `transaction` property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time: - -```js -sequelize.transaction((t1) => { - namespace.get('transaction') === t1; // true -}); - -sequelize.transaction((t2) => { - namespace.get('transaction') === t2; // true -}); -``` - -In most case you won't need to access `namespace.get('transaction')` directly, since all queries will automatically look for a transaction on the namespace: - -```js -sequelize.transaction((t1) => { - // With CLS enabled, the user will be created inside the transaction - return User.create({ name: 'Alice' }); -}); -``` - -After you've used `Sequelize.useCLS()` all promises returned from sequelize will be patched to maintain CLS context. CLS is a complicated subject - more details in the docs for [cls-bluebird](https://www.npmjs.com/package/cls-bluebird), the patch used to make bluebird promises work with CLS. - -**Note:** _[CLS only supports async/await, at the moment, when using cls-hooked package](https://github.com/othiym23/node-continuation-local-storage/issues/98#issuecomment-323503807). Although, [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked/blob/master/README.md) relies on *experimental API* [async_hooks](https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md)_ - -## Concurrent/Partial transactions - -You can have concurrent transactions within a sequence of queries or have some of them excluded from any transactions. Use the `{transaction: }` option to control which transaction a query belong to: - -**Warning:** _SQLite does not support more than one transaction at the same time._ - -### Without CLS enabled +### Example ```js sequelize.transaction((t1) => { return sequelize.transaction((t2) => { - // With CLS enable, queries here will by default use t2 // Pass in the `transaction` option to define/alter the transaction they belong to. return Promise.all([ User.create({ name: 'Bob' }, { transaction: null }), User.create({ name: 'Mallory' }, { transaction: t1 }), - User.create({ name: 'John' }) // this would default to t2 + User.create({ name: 'John' }) // No transaction ]); }); }); diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md index 0f2bd190c6ce..728b6f6a1d05 100644 --- a/docs/upgrade-to-v6.md +++ b/docs/upgrade-to-v6.md @@ -24,3 +24,36 @@ If you have relied on accessing sequelize operators via `Symbol.for('gt')` etc. `Model.build` has been acting as proxy for `bulkBuild` and `new Model` for a while. Use `Model.bulkBuild` or `new Model` instead. + +### Removal of CLS + +CLS allowed implicit passing of the `transaction` property to query options inside of a transaction. +This feature was removed for 3 reasons. + +- It required hooking the promise implementation which is not sustainable for the future of sequelize. +- It's generally unsafe due to it's implicit nature. +- It wasn't always reliable when mixed promise implementations were used. + +Check all your usage of the `.transaction` method and make sure to explicitly pass the `transaction` object for each subsequent query. + +#### Example + +```js +db.transaction(async transaction => { + const mdl = await myModel.findByPk(1); + await mdl.update({ + a: 1; + }); +}); +``` + +should now be: + +```js +db.transaction(async transaction => { + const mdl = await myModel.findByPk(1, { transaction }); + await mdl.update({ + a: 1; + }, { transaction }); +}); +``` diff --git a/lib/model.js b/lib/model.js index cdf69ded20d0..5fefe7d2fde9 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2293,13 +2293,6 @@ class Model { } } - if (options.transaction === undefined && this.sequelize.constructor._cls) { - const t = this.sequelize.constructor._cls.get('transaction'); - if (t) { - options.transaction = t; - } - } - const internalTransaction = !options.transaction; let values; let transaction; diff --git a/lib/sequelize.js b/lib/sequelize.js index d701eb17d9d1..552d9c0f79ee 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -3,7 +3,6 @@ const url = require('url'); const path = require('path'); const retry = require('retry-as-promised'); -const clsBluebird = require('cls-bluebird'); const _ = require('lodash'); const Utils = require('./utils'); @@ -627,10 +626,6 @@ class Sequelize { const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); return Promise.resolve(retry(() => Promise.try(() => { - if (options.transaction === undefined && Sequelize._cls) { - options.transaction = Sequelize._cls.get('transaction'); - } - checkTransaction(); return options.transaction @@ -1051,8 +1046,6 @@ class Sequelize { /** * Start a transaction. When using transactions, you should pass the transaction in the options argument in order for the query to happen under that transaction @see {@link Transaction} * - * If you have [CLS](https://github.com/othiym23/node-continuation-local-storage) enabled, the transaction will automatically be passed to any query that runs within the callback - * * @example * sequelize.transaction().then(transaction => { * return User.findOne(..., {transaction}) @@ -1061,27 +1054,6 @@ class Sequelize { * .catch(() => transaction.rollback()); * }) * - * @example A syntax for automatically committing or rolling back based on the promise chain resolution is also supported - * - * sequelize.transaction(transaction => { // Note that we use a callback rather than a promise.then() - * return User.findOne(..., {transaction}) - * .then(user => user.update(..., {transaction})) - * }).then(() => { - * // Committed - * }).catch(err => { - * // Rolled back - * console.error(err); - * }); - * - * @example To enable CLS, add it do your project, create a namespace and set it on the sequelize constructor: - * - * const cls = require('continuation-local-storage'); - * const ns = cls.createNamespace('....'); - * const Sequelize = require('sequelize'); - * Sequelize.useCLS(ns); - * - * // Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace - * * @param {Object} [options] Transaction options * @param {string} [options.type='DEFERRED'] See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options @@ -1102,57 +1074,16 @@ class Sequelize { if (!autoCallback) return transaction.prepareEnvironment(false).return(transaction); // autoCallback provided - return Sequelize._clsRun(() => { - return transaction.prepareEnvironment() - .then(() => autoCallback(transaction)) - .tap(() => transaction.commit()) - .catch(err => { - // Rollback transaction if not already finished (commit, rollback, etc) - // and reject with original error (ignore any error in rollback) - return Promise.try(() => { - if (!transaction.finished) return transaction.rollback().catch(() => {}); - }).throw(err); - }); - }); - } - - /** - * Use CLS with Sequelize. - * CLS namespace provided is stored as `Sequelize._cls` - * and bluebird Promise is patched to use the namespace, using `cls-bluebird` module. - * - * @param {Object} ns CLS namespace - * @returns {Object} Sequelize constructor - */ - static useCLS(ns) { - // check `ns` is valid CLS namespace - if (!ns || typeof ns !== 'object' || typeof ns.bind !== 'function' || typeof ns.run !== 'function') throw new Error('Must provide CLS namespace'); - - // save namespace as `Sequelize._cls` - this._cls = ns; - - // patch bluebird to bind all promise callbacks to CLS namespace - clsBluebird(ns, Promise); - - // return Sequelize for chaining - return this; - } - - /** - * Run function in CLS context. - * If no CLS context in use, just runs the function normally - * - * @private - * @param {Function} fn Function to run - * @returns {*} Return value of function - */ - static _clsRun(fn) { - const ns = Sequelize._cls; - if (!ns) return fn(); - - let res; - ns.run(context => res = fn(context)); - return res; + return transaction.prepareEnvironment() + .then(() => autoCallback(transaction)) + .tap(() => transaction.commit()) + .catch(err => { + // Rollback transaction if not already finished (commit, rollback, etc) + // and reject with original error (ignore any error in rollback) + return Promise.try(() => { + if (!transaction.finished) return transaction.rollback().catch(() => {}); + }).throw(err); + }); } log(...args) { diff --git a/lib/transaction.js b/lib/transaction.js index 41af5500f035..bd0b8f3fefa8 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -57,8 +57,6 @@ class Transaction { return Promise.reject(new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`)); } - this._clearCls(); - return this .sequelize .getQueryInterface() @@ -90,8 +88,6 @@ class Transaction { return Promise.reject(new Error('Transaction cannot be rolled back because it never started')); } - this._clearCls(); - return this .sequelize .getQueryInterface() @@ -104,13 +100,9 @@ class Transaction { }); } - prepareEnvironment(useCLS) { + prepareEnvironment() { let connectionPromise; - if (useCLS === undefined) { - useCLS = true; - } - if (this.parent) { connectionPromise = Promise.resolve(this.parent.connection); } else { @@ -133,12 +125,6 @@ class Transaction { .catch(setupErr => this.rollback().finally(() => { throw setupErr; })); - }) - .tap(() => { - if (useCLS && this.sequelize.constructor._cls) { - this.sequelize.constructor._cls.set('transaction', this); - } - return null; }); } @@ -171,16 +157,6 @@ class Transaction { return res; } - _clearCls() { - const cls = this.sequelize.constructor._cls; - - if (cls) { - if (cls.get('transaction') === this) { - cls.set('transaction', null); - } - } - } - /** * A hook that is run after a transaction is committed * diff --git a/package.json b/package.json index 005e5c4fa736..e191e16703b5 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "license": "MIT", "dependencies": { "bluebird": "^3.5.0", - "cls-bluebird": "^2.1.0", "debug": "^4.1.1", "dottie": "^2.0.0", "inflection": "1.12.0", @@ -57,7 +56,6 @@ "chai-as-promised": "^7.x", "chai-datetime": "^1.x", "chai-spies": "^1.x", - "continuation-local-storage": "^3.x", "cross-env": "^5.2.1", "env-cmd": "^8.0.2", "esdoc": "^1.1.0", diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js deleted file mode 100644 index 3ae7c9b84dc1..000000000000 --- a/test/integration/cls.test.js +++ /dev/null @@ -1,165 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, - cls = require('continuation-local-storage'), - current = Support.sequelize; - -if (current.dialect.supports.transactions) { - describe(Support.getTestDialectTeaser('Continuation local storage'), () => { - before(function() { - this.thenOriginal = Promise.prototype.then; - Sequelize.useCLS(cls.createNamespace('sequelize')); - }); - - after(() => { - delete Sequelize._cls; - }); - - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.ns = cls.getNamespace('sequelize'); - - this.User = this.sequelize.define('user', { - name: Sequelize.STRING - }); - return this.sequelize.sync({ force: true }); - }); - }); - - describe('context', () => { - it('does not use continuation storage on manually managed transactions', function() { - return Sequelize._clsRun(() => { - return this.sequelize.transaction().then(transaction => { - expect(this.ns.get('transaction')).to.be.undefined; - return transaction.rollback(); - }); - }); - }); - - it('supports several concurrent transactions', function() { - let t1id, t2id; - return Promise.join( - this.sequelize.transaction(() => { - t1id = this.ns.get('transaction').id; - - return Promise.resolve(); - }), - this.sequelize.transaction(() => { - t2id = this.ns.get('transaction').id; - - return Promise.resolve(); - }), - () => { - expect(t1id).to.be.ok; - expect(t2id).to.be.ok; - expect(t1id).not.to.equal(t2id); - } - ); - }); - - it('supports nested promise chains', function() { - return this.sequelize.transaction(() => { - const tid = this.ns.get('transaction').id; - - return this.User.findAll().then(() => { - expect(this.ns.get('transaction').id).to.be.ok; - expect(this.ns.get('transaction').id).to.equal(tid); - }); - }); - }); - - it('does not leak variables to the outer scope', function() { - // This is a little tricky. We want to check the values in the outer scope, when the transaction has been successfully set up, but before it has been comitted. - // We can't just call another function from inside that transaction, since that would transfer the context to that function - exactly what we are trying to prevent; - - let transactionSetup = false, - transactionEnded = false; - - this.sequelize.transaction(() => { - transactionSetup = true; - - return Promise.delay(500).then(() => { - expect(this.ns.get('transaction')).to.be.ok; - transactionEnded = true; - }); - }); - - return new Promise(resolve => { - // Wait for the transaction to be setup - const interval = setInterval(() => { - if (transactionSetup) { - clearInterval(interval); - resolve(); - } - }, 200); - }).then(() => { - expect(transactionEnded).not.to.be.ok; - - expect(this.ns.get('transaction')).not.to.be.ok; - - // Just to make sure it didn't change between our last check and the assertion - expect(transactionEnded).not.to.be.ok; - }); - }); - - it('does not leak variables to the following promise chain', function() { - return this.sequelize.transaction(() => { - return Promise.resolve(); - }).then(() => { - expect(this.ns.get('transaction')).not.to.be.ok; - }); - }); - - it('does not leak outside findOrCreate', function() { - return this.User.findOrCreate({ - where: { - name: 'Kafka' - }, - logging(sql) { - if (/default/.test(sql)) { - throw new Error('The transaction was not properly assigned'); - } - } - }).then(() => { - return this.User.findAll(); - }); - }); - }); - - describe('sequelize.query integration', () => { - it('automagically uses the transaction in all calls', function() { - return this.sequelize.transaction(() => { - return this.User.create({ name: 'bob' }).then(() => { - return Promise.all([ - expect(this.User.findAll({ transaction: null })).to.eventually.have.length(0), - expect(this.User.findAll({})).to.eventually.have.length(1) - ]); - }); - }); - }); - }); - - it('bluebird patch is applied', function() { - expect(Promise.prototype.then).to.be.a('function'); - expect(this.thenOriginal).to.be.a('function'); - expect(Promise.prototype.then).not.to.equal(this.thenOriginal); - }); - - it('CLS namespace is stored in Sequelize._cls', function() { - expect(Sequelize._cls).to.equal(this.ns); - }); - - it('promises returned by sequelize.query are correctly patched', function() { - return this.sequelize.transaction(t => - this.sequelize.query('select 1', { type: Sequelize.QueryTypes.SELECT }) - .then(() => expect(this.ns.get('transaction')).to.equal(t)) - ); - }); - }); -} diff --git a/test/unit/model/find-or-create.test.js b/test/unit/model/find-or-create.test.js deleted file mode 100644 index 6de294039923..000000000000 --- a/test/unit/model/find-or-create.test.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('../support'), - current = Support.sequelize, - cls = require('continuation-local-storage'), - sinon = require('sinon'), - stub = sinon.stub; - -describe(Support.getTestDialectTeaser('Model'), () => { - - describe('method findOrCreate', () => { - - before(() => { - current.constructor.useCLS(cls.createNamespace('sequelize')); - }); - - after(() => { - delete current.constructor._cls; - }); - - beforeEach(function() { - this.User = current.define('User', {}, { - name: 'John' - }); - - this.transactionStub = stub(this.User.sequelize, 'transaction').rejects(new Error('abort')); - - this.clsStub = stub(current.constructor._cls, 'get').returns({ id: 123 }); - }); - - afterEach(function() { - this.transactionStub.restore(); - this.clsStub.restore(); - }); - - it('should use transaction from cls if available', function() { - - const options = { - where: { - name: 'John' - } - }; - - return this.User.findOrCreate(options) - .then(() => { - expect.fail('expected to fail'); - }) - .catch(/abort/, () => { - expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); - }); - - }); - - it('should not use transaction from cls if provided as argument', function() { - - const options = { - where: { - name: 'John' - }, - transaction: { id: 123 } - }; - - return this.User.findOrCreate(options) - .then(() => { - expect.fail('expected to fail'); - }) - .catch(/abort/, () => { - expect(this.clsStub.called).to.equal(false); - }); - }); - }); -}); diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index ba220ca2d427..16a99ef5904a 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -745,14 +745,6 @@ export class Sequelize extends Hooks { public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; public static afterSync(fn: (options: SyncOptions) => HookReturn): void; - /** - * Use CLS with Sequelize. - * CLS namespace provided is stored as `Sequelize._cls` - * and bluebird Promise is patched to use the namespace, using `cls-bluebird` module. - * - * @param namespace - */ - public static useCLS(namespace: object): typeof Sequelize; /** * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. @@ -1319,18 +1311,6 @@ export class Sequelize extends Hooks { * }); * ``` * - * If you have [CLS](https://github.com/othiym23/node-continuation-local-storage) enabled, the transaction - * will automatically be passed to any query that runs witin the callback. To enable CLS, add it do your - * project, create a namespace and set it on the sequelize constructor: - * - * ```js - * const cls = require('continuation-local-storage'), - * ns = cls.createNamespace('....'); - * const Sequelize = require('sequelize'); - * Sequelize.cls = ns; - * ``` - * Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace - * * @param options Transaction Options * @param autoCallback Callback for the transaction */ diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts index 09a2e61066f7..51866e317f00 100644 --- a/types/test/sequelize.ts +++ b/types/test/sequelize.ts @@ -1,9 +1,6 @@ import { Config, Sequelize, Model, QueryTypes } from 'sequelize'; import { Fn } from '../lib/utils'; -Sequelize.useCLS({ -}); - export const sequelize = new Sequelize({ hooks: { afterConnect: (connection, config: Config) => { From 10e0a288361da7543d19220b7be1b15a439361c0 Mon Sep 17 00:00:00 2001 From: Erik Seliger Date: Sun, 5 May 2019 18:56:42 +0200 Subject: [PATCH 013/414] fix: cleanup parser-store (#10895) --- lib/dialects/abstract/connection-manager.js | 11 ++++++++++ lib/dialects/abstract/parser-store.js | 16 ++++++++++++++ lib/dialects/mariadb/connection-manager.js | 23 ++++++--------------- lib/dialects/mssql/connection-manager.js | 9 -------- lib/dialects/mssql/query.js | 3 +-- lib/dialects/mysql/connection-manager.js | 23 ++++++--------------- lib/dialects/parserStore.js | 23 --------------------- lib/dialects/sqlite/connection-manager.js | 10 --------- lib/dialects/sqlite/query.js | 3 +-- 9 files changed, 41 insertions(+), 80 deletions(-) create mode 100644 lib/dialects/abstract/parser-store.js delete mode 100644 lib/dialects/parserStore.js diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index e5f92342871a..d846675cc8f5 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -7,6 +7,7 @@ const Promise = require('../../promise'); const errors = require('../../errors'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('pool'); +const { ParserStore } = require('./parser-store'); /** * Abstract Connection Manager @@ -40,6 +41,8 @@ class ConnectionManager { ...config.pool }; + this.parserStore = new ParserStore(this.dialectName); + this.initPools(); } @@ -103,6 +106,14 @@ class ConnectionManager { }); } + _refreshTypeParser(dataType) { + this.parserStore.refresh(dataType); + } + + _clearTypeParser() { + this.parserStore.clear(); + } + /** * Drain the pool and close it permanently * diff --git a/lib/dialects/abstract/parser-store.js b/lib/dialects/abstract/parser-store.js new file mode 100644 index 000000000000..15350e868a47 --- /dev/null +++ b/lib/dialects/abstract/parser-store.js @@ -0,0 +1,16 @@ +'use strict'; + +class ParserStore extends Map { + constructor(dialectName) { + super(); + this.dialectName = dialectName; + } + + refresh(dataType) { + for (const type of dataType.types[this.dialectName]) { + this.set(type, dataType.parse); + } + } +} + +module.exports.ParserStore = ParserStore; diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index f6533cde90d3..1fb08dc11816 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -7,7 +7,6 @@ const { logger } = require('../../utils/logger'); const DataTypes = require('../../data-types').mariadb; const momentTz = require('moment-timezone'); const debug = logger.debugContext('connection:mariadb'); -const parserStore = require('../parserStore')('mariadb'); /** * MariaDB Connection Manager @@ -29,21 +28,6 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(DataTypes); } - static _typecast(field, next) { - if (parserStore.get(field.type)) { - return parserStore.get(field.type)(field, this.sequelize.options, next); - } - return next(); - } - - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - /** * Connect with MariaDB database based on config, Handle any errors in connection * Set the pool handlers on connection.error @@ -66,7 +50,12 @@ class ConnectionManager extends AbstractConnectionManager { password: config.password, database: config.database, timezone: tzOffset, - typeCast: ConnectionManager._typecast.bind(this), + typeCast: (field, next) => { + if (this.parserStore.get(field.type)) { + return this.parserStore.get(field.type)(field, this.sequelize.options, next); + } + return next(); + }, bigNumberStrings: false, supportBigNumbers: true, foundRows: false diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index 9960f0d9f7e6..bbdeb689bdf9 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -6,7 +6,6 @@ const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const sequelizeErrors = require('../../errors'); const DataTypes = require('../../data-types').mssql; -const parserStore = require('../parserStore')('mssql'); const debug = logger.debugContext('connection:mssql'); const debugTedious = logger.debugContext('connection:mssql:tedious'); @@ -18,14 +17,6 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(DataTypes); } - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - connect(config) { const connectionConfig = { server: config.host, diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index b14c80391591..c36f864079b3 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -3,7 +3,6 @@ const Promise = require('../../promise'); const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); -const parserStore = require('../parserStore')('mssql'); const _ = require('lodash'); const { logger } = require('../../utils/logger'); @@ -88,7 +87,7 @@ class Query extends AbstractQuery { const row = {}; for (const column of columns) { const typeid = column.metadata.type.id; - const parse = parserStore.get(typeid); + const parse = this.sequelize.connectionManager.parserStore.get(typeid); let value = column.value; if (value !== null & !!parse) { diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 8a1414c31b59..285f4ea897db 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -7,7 +7,6 @@ const { logger } = require('../../utils/logger'); const DataTypes = require('../../data-types').mysql; const momentTz = require('moment-timezone'); const debug = logger.debugContext('connection:mysql'); -const parserStore = require('../parserStore')('mysql'); /** * MySQL Connection Manager @@ -29,21 +28,6 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(DataTypes); } - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - - static _typecast(field, next) { - if (parserStore.get(field.type)) { - return parserStore.get(field.type)(field, this.sequelize.options, next); - } - return next(); - } - /** * Connect with MySQL database based on config, Handle any errors in connection * Set the pool handlers on connection.error @@ -62,7 +46,12 @@ class ConnectionManager extends AbstractConnectionManager { password: config.password, database: config.database, timezone: this.sequelize.options.timezone, - typeCast: ConnectionManager._typecast.bind(this), + typeCast: (field, next) => { + if (this.parserStore.get(field.type)) { + return this.parserStore.get(field.type)(field, this.sequelize.options, next); + } + return next(); + }, bigNumberStrings: false, supportBigNumbers: true }, config.dialectOptions); diff --git a/lib/dialects/parserStore.js b/lib/dialects/parserStore.js deleted file mode 100644 index b8bbb8df6d15..000000000000 --- a/lib/dialects/parserStore.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const stores = new Map(); - -module.exports = dialect => { - if (!stores.has(dialect)) { - stores.set(dialect, new Map()); - } - - return { - clear() { - stores.get(dialect).clear(); - }, - refresh(dataType) { - for (const type of dataType.types[dialect]) { - stores.get(dialect).set(type, dataType.parse); - } - }, - get(type) { - return stores.get(dialect).get(type); - } - }; -}; diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 7626b34e8106..6000edea803f 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -6,7 +6,6 @@ const { logger } = require('../../utils/logger'); const debug = logger.debugContext('connection:sqlite'); const dataTypes = require('../../data-types').sqlite; const sequelizeErrors = require('../../errors'); -const parserStore = require('../parserStore')('sqlite'); class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { @@ -32,15 +31,6 @@ class ConnectionManager extends AbstractConnectionManager { .then(() => super._onProcessExit.call(this)); } - // Expose this as a method so that the parsing may be updated when the user has added additional, custom types - _refreshTypeParser(dataType) { - parserStore.refresh(dataType); - } - - _clearTypeParser() { - parserStore.clear(); - } - getConnection(options = {}) { options.uuid = options.uuid || 'default'; options.inMemory = (this.sequelize.options.storage || this.sequelize.options.host || ':memory:') === ':memory:' ? 1 : 0; diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 83e26c73cb32..3ea230cdc6b5 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -6,7 +6,6 @@ const Promise = require('../../promise'); const AbstractQuery = require('../abstract/query'); const QueryTypes = require('../../query-types'); const sequelizeErrors = require('../../errors'); -const parserStore = require('../parserStore')('sqlite'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('sql:sqlite'); @@ -357,7 +356,7 @@ class Query extends AbstractQuery { } type = type.replace('UNSIGNED', '').replace('ZEROFILL', ''); type = type.trim().toUpperCase(); - const parse = parserStore.get(type); + const parse = this.sequelize.connectionManager.parserStore.get(type); if (value !== null && parse) { return parse(value, { timezone: this.sequelize.options.timezone }); From 366c2647eb326eb9f0c6d196b656b7d327de7630 Mon Sep 17 00:00:00 2001 From: Erik Seliger Date: Sun, 5 May 2019 20:24:09 +0200 Subject: [PATCH 014/414] chore: modernize connections handling in sqlite (#10894) --- lib/dialects/sqlite/connection-manager.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 6000edea803f..9b8f6e9d804a 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -17,14 +17,16 @@ class ConnectionManager extends AbstractConnectionManager { delete this.sequelize.options.host; } - this.connections = {}; + this.connections = new Map(); this.lib = this._loadDialectModule('sqlite3').verbose(); this.refreshTypeParser(dataTypes); } _onProcessExit() { - const promises = Object.getOwnPropertyNames(this.connections) - .map(connection => Promise.fromCallback(callback => this.connections[connection].close(callback))); + const promises = []; + for (const conn of this.connections.values()) { + promises.push(Promise.fromCallback(callback => conn.close(callback))); + } return Promise .all(promises) @@ -38,20 +40,20 @@ class ConnectionManager extends AbstractConnectionManager { const dialectOptions = this.sequelize.options.dialectOptions; options.readWriteMode = dialectOptions && dialectOptions.mode; - if (this.connections[options.inMemory || options.uuid]) { - return Promise.resolve(this.connections[options.inMemory || options.uuid]); + if (this.connections.has(options.inMemory || options.uuid)) { + return Promise.resolve(this.connections.get(options.inMemory || options.uuid)); } return new Promise((resolve, reject) => { - this.connections[options.inMemory || options.uuid] = new this.lib.Database( + this.connections.set(options.inMemory || options.uuid, new this.lib.Database( this.sequelize.options.storage || this.sequelize.options.host || ':memory:', options.readWriteMode || this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE, // default mode err => { if (err) return reject(new sequelizeErrors.ConnectionError(err)); debug(`connection acquired ${options.uuid}`); - resolve(this.connections[options.inMemory || options.uuid]); + resolve(this.connections.get(options.inMemory || options.uuid)); } - ); + )); }).tap(connection => { if (this.sequelize.config.password) { // Make it possible to define and use password for sqlite encryption plugin like sqlcipher @@ -71,7 +73,7 @@ class ConnectionManager extends AbstractConnectionManager { if (connection.uuid) { connection.close(); debug(`connection released ${connection.uuid}`); - delete this.connections[connection.uuid]; + this.connections.delete(connection.uuid); } } } From ed5eab7cbb0866c0f03581ba13bbfc9bd7a9021e Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 11 May 2019 15:03:33 -0700 Subject: [PATCH 015/414] build: update dev dependencies and prep rc release --- .travis.yml | 4 + lib/associations/belongs-to-many.js | 30 +-- lib/associations/belongs-to.js | 8 +- lib/associations/has-many.js | 22 +- lib/associations/has-one.js | 8 +- lib/associations/helpers.js | 6 +- lib/data-types.js | 2 +- lib/dialects/abstract/connection-manager.js | 4 +- lib/dialects/abstract/query-generator.js | 32 +-- .../abstract/query-generator/helpers/quote.js | 2 +- .../abstract/query-generator/transaction.js | 2 +- lib/dialects/abstract/query.js | 12 +- lib/dialects/mariadb/connection-manager.js | 2 +- lib/dialects/mssql/data-types.js | 2 +- lib/dialects/mssql/query-generator.js | 2 +- lib/dialects/mssql/query-interface.js | 2 +- lib/dialects/mysql/connection-manager.js | 2 +- lib/dialects/mysql/query-generator.js | 4 +- lib/dialects/mysql/query-interface.js | 4 +- lib/dialects/postgres/data-types.js | 2 +- lib/dialects/postgres/query-interface.js | 4 +- lib/dialects/postgres/query.js | 2 +- lib/dialects/sqlite/data-types.js | 2 +- lib/dialects/sqlite/query-interface.js | 14 +- lib/dialects/sqlite/query.js | 4 +- lib/errors/bulk-record-error.js | 2 +- lib/errors/validation-error.js | 8 +- lib/hooks.js | 2 +- lib/instance-validator.js | 6 +- lib/model-manager.js | 2 +- lib/model.js | 212 +++++++++--------- lib/query-interface.js | 100 ++++----- lib/sequelize.js | 68 +++--- lib/transaction.js | 4 +- lib/utils.js | 26 +-- package.json | 32 +-- test/integration/model/count.test.js | 6 +- test/unit/sql/create-table.test.js | 3 +- 38 files changed, 326 insertions(+), 323 deletions(-) diff --git a/.travis.yml b/.travis.yml index 636a62552ba0..388d836a4043 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,6 +77,8 @@ jobs: node_js: '10' script: - npm run semantic-release + - stage: docs + node_js: '10' before_deploy: - npm run docs deploy: @@ -89,4 +91,6 @@ stages: - lint - test - name: release + if: (branch = master OR branch = beta) AND type = push AND fork = false + - name: docs if: branch = master AND type = push AND fork = false diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index c590aa21bc14..a4ebffe19b5f 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -407,8 +407,8 @@ class BelongsToMany extends Association { * {@link Model} for a full explanation of options * * @param {Model} instance instance - * @param {Object} [options] find options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @@ -476,8 +476,8 @@ class BelongsToMany extends Association { * Count everything currently associated with this, using an optional where clause. * * @param {Model} instance instance - * @param {Object} [options] find options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * * @returns {Promise} @@ -501,7 +501,7 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance source instance to check for an association with * @param {Model|Model[]|string[]|string|number[]|number} [instances] Can be an array of instances or their primary keys - * @param {Object} [options] Options passed to getAssociations + * @param {object} [options] Options passed to getAssociations * * @returns {Promise} */ @@ -545,9 +545,9 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance source instance to associate new instances with * @param {Model|Model[]|string[]|string|number[]|number} [newAssociatedObjects] A single instance or primary key, or a mixed array of persisted instances or primary keys - * @param {Object} [options] Options passed to `through.findAll`, `bulkCreate`, `update` and `destroy` - * @param {Object} [options.validate] Run validation for the join model - * @param {Object} [options.through] Additional attributes for the join table. + * @param {object} [options] Options passed to `through.findAll`, `bulkCreate`, `update` and `destroy` + * @param {object} [options.validate] Run validation for the join model + * @param {object} [options.through] Additional attributes for the join table. * * @returns {Promise} */ @@ -650,9 +650,9 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance source instance to associate new instances with * @param {Model|Model[]|string[]|string|number[]|number} [newInstances] A single instance or primary key, or a mixed array of persisted instances or primary keys - * @param {Object} [options] Options passed to `through.findAll`, `bulkCreate` and `update` - * @param {Object} [options.validate] Run validation for the join model. - * @param {Object} [options.through] Additional attributes for the join table. + * @param {object} [options] Options passed to `through.findAll`, `bulkCreate` and `update` + * @param {object} [options.validate] Run validation for the join model. + * @param {object} [options.through] Additional attributes for the join table. * * @returns {Promise} */ @@ -739,7 +739,7 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance instance to un associate instances with * @param {Model|Model[]|string|string[]|number|number[]} [oldAssociatedObjects] Can be an Instance or its primary key, or a mixed array of instances and primary keys - * @param {Object} [options] Options passed to `through.destroy` + * @param {object} [options] Options passed to `through.destroy` * * @returns {Promise} */ @@ -758,9 +758,9 @@ class BelongsToMany extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance source instance - * @param {Object} [values] values for target model - * @param {Object} [options] Options passed to create and add - * @param {Object} [options.through] Additional attributes for the join table + * @param {object} [values] values for target model + * @param {object} [options] Options passed to create and add + * @param {object} [options.through] Additional attributes for the join table * * @returns {Promise} */ diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index aba6d0a05214..a09df933d2a9 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -115,7 +115,7 @@ class BelongsTo extends Association { * Get the associated instance. * * @param {Model|Array} instances source instances - * @param {Object} [options] find options + * @param {object} [options] find options * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false. * @param {string} [options.schema] Apply a schema on the related model * @@ -187,7 +187,7 @@ class BelongsTo extends Association { * * @param {Model} sourceInstance the source instance * @param {?|string|number} [associatedInstance] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. - * @param {Object} [options={}] options passed to `this.save` + * @param {object} [options={}] options passed to `this.save` * @param {boolean} [options.save=true] Skip saving this after setting the foreign key if false. * * @returns {Promise} @@ -217,8 +217,8 @@ class BelongsTo extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance the source instance - * @param {Object} [values={}] values to create associated model instance with - * @param {Object} [options={}] Options passed to `target.create` and setAssociation. + * @param {object} [values={}] values to create associated model instance with + * @param {object} [options={}] Options passed to `target.create` and setAssociation. * * @see * {@link Model#create} for a full explanation of options diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 5f0e6c5d7d05..27c73033cfd7 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -159,8 +159,8 @@ class HasMany extends Association { * Get everything currently associated with this, using an optional where clause. * * @param {Model|Array} instances source instances - * @param {Object} [options] find options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @@ -244,8 +244,8 @@ class HasMany extends Association { * Count everything currently associated with this, using an optional where clause. * * @param {Model} instance the source instance - * @param {Object} [options] find & count options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find & count options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * * @returns {Promise} @@ -273,7 +273,7 @@ class HasMany extends Association { * * @param {Model} sourceInstance the source instance * @param {Model|Model[]|string[]|string|number[]|number} [targetInstances] Can be an array of instances or their primary keys - * @param {Object} [options] Options passed to getAssociations + * @param {object} [options] Options passed to getAssociations * * @returns {Promise} */ @@ -314,8 +314,8 @@ class HasMany extends Association { * * @param {Model} sourceInstance source instance to associate new instances with * @param {Model|Model[]|string[]|string|number[]|number} [targetInstances] An array of persisted instances or primary key of instances to associate with this. Pass `null` or `undefined` to remove all associations. - * @param {Object} [options] Options passed to `target.findAll` and `update`. - * @param {Object} [options.validate] Run validation for the join model + * @param {object} [options] Options passed to `target.findAll` and `update`. + * @param {object} [options.validate] Run validation for the join model * * @returns {Promise} */ @@ -395,7 +395,7 @@ class HasMany extends Association { * * @param {Model} sourceInstance the source instance * @param {Model|Model[]|string[]|string|number[]|number} [targetInstances] A single instance or primary key, or a mixed array of persisted instances or primary keys - * @param {Object} [options] Options passed to `target.update`. + * @param {object} [options] Options passed to `target.update`. * * @returns {Promise} */ @@ -423,7 +423,7 @@ class HasMany extends Association { * * @param {Model} sourceInstance instance to un associate instances with * @param {Model|Model[]|string|string[]|number|number[]} [targetInstances] Can be an Instance or its primary key, or a mixed array of instances and primary keys - * @param {Object} [options] Options passed to `target.update` + * @param {object} [options] Options passed to `target.update` * * @returns {Promise} */ @@ -448,8 +448,8 @@ class HasMany extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance source instance - * @param {Object} [values] values for target model instance - * @param {Object} [options] Options passed to `target.create` + * @param {object} [values] values for target model instance + * @param {object} [options] Options passed to `target.create` * * @returns {Promise} */ diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index 6738d65f0d9c..a2195a4155bd 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -114,7 +114,7 @@ class HasOne extends Association { * Get the associated instance. * * @param {Model|Array} instances source instances - * @param {Object} [options] find options + * @param {object} [options] find options * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @@ -187,7 +187,7 @@ class HasOne extends Association { * * @param {Model} sourceInstance the source instance * @param {?|string|number} [associatedInstance] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. - * @param {Object} [options] Options passed to getAssociation and `target.save` + * @param {object} [options] Options passed to getAssociation and `target.save` * * @returns {Promise} */ @@ -236,8 +236,8 @@ class HasOne extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance the source instance - * @param {Object} [values={}] values to create associated model instance with - * @param {Object} [options] Options passed to `target.create` and setAssociation. + * @param {object} [values={}] values to create associated model instance with + * @param {object} [options] Options passed to `target.create` and setAssociation. * * @see * {@link Model#create} for a full explanation of options diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js index 0849e36f325e..a04c265e29ff 100644 --- a/lib/associations/helpers.js +++ b/lib/associations/helpers.js @@ -46,10 +46,10 @@ exports.addForeignKeyConstraints = addForeignKeyConstraints; * * @private * - * @param {Object} association instance - * @param {Object} obj Model prototype + * @param {object} association instance + * @param {object} obj Model prototype * @param {Array} methods Method names to inject - * @param {Object} aliases Mapping between model and association method names + * @param {object} aliases Mapping between model and association method names * */ function mixinMethods(association, obj, methods, aliases = {}) { diff --git a/lib/data-types.js b/lib/data-types.js index 3e43a50495b2..256275e857a2 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -157,7 +157,7 @@ class CITEXT extends ABSTRACT { */ class NUMBER extends ABSTRACT { /** - * @param {Object} options type options + * @param {object} options type options * @param {string|number} [options.length] length of type, like `INT(4)` * @param {boolean} [options.zerofill] Is zero filled? * @param {boolean} [options.unsigned] Is unsigned? diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index d846675cc8f5..c5bd5925fdbb 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -65,7 +65,7 @@ class ConnectionManager { * @param {string} moduleName Name of dialect module to lookup * * @private - * @returns {Object} + * @returns {object} */ _loadDialectModule(moduleName) { try { @@ -249,7 +249,7 @@ class ConnectionManager { * Get connection from pool. It sets database version if it's not already set. * Call pool.acquire to get a connection * - * @param {Object} [options] Pool options + * @param {object} [options] Pool options * @param {string} [options.type] Set which replica to use. Available options are `read` and `write` * @param {boolean} [options.useMaster=false] Force master or write replica to get connection from * diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 679587274de6..49630274cc18 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -89,9 +89,9 @@ class QueryGenerator { * Returns an insert into command * * @param {string} table - * @param {Object} valueHash attribute value pairs - * @param {Object} modelAttributes - * @param {Object} [options] + * @param {object} valueHash attribute value pairs + * @param {object} modelAttributes + * @param {object} [options] * * @private */ @@ -255,9 +255,9 @@ class QueryGenerator { * Returns an insert into command for multiple values. * * @param {string} tableName - * @param {Object} fieldValueHashes - * @param {Object} options - * @param {Object} fieldMappedAttributes + * @param {object} fieldValueHashes + * @param {object} options + * @param {object} fieldMappedAttributes * * @private */ @@ -327,10 +327,10 @@ class QueryGenerator { * Returns an update query * * @param {string} tableName - * @param {Object} attrValueHash - * @param {Object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer - * @param {Object} options - * @param {Object} attributes + * @param {object} attrValueHash + * @param {object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {object} options + * @param {object} attributes * * @private */ @@ -447,10 +447,10 @@ class QueryGenerator { * * @param {string} operator String with the arithmetic operator (e.g. '+' or '-') * @param {string} tableName Name of the table - * @param {Object} attrValueHash A hash with attribute-value-pairs - * @param {Object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer - * @param {Object} options - * @param {Object} attributes + * @param {object} attrValueHash A hash with attribute-value-pairs + * @param {object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {object} options + * @param {object} attributes */ arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes = {}) { options = { @@ -916,7 +916,7 @@ class QueryGenerator { /** * Quote table name with optional alias and schema attribution * - * @param {string|Object} param table string or object + * @param {string|object} param table string or object * @param {string|boolean} alias alias name * * @returns {string} @@ -2022,7 +2022,7 @@ class QueryGenerator { /** * Returns an SQL fragment for adding result constraints. * - * @param {Object} options An object with selectQuery options. + * @param {object} options An object with selectQuery options. * @returns {string} The generated sql query. * @private */ diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js index 8fd0bca54341..a1968db20f4d 100644 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ b/lib/dialects/abstract/query-generator/helpers/quote.js @@ -25,7 +25,7 @@ const postgresReservedWords = 'all,analyse,analyze,and,any,array,as,asc,asymmetr * * @param {string} dialect Dialect name * @param {string} identifier Identifier to quote - * @param {Object} [options] + * @param {object} [options] * @param {boolean} [options.force=false] * @param {boolean} [options.quoteIdentifiers=true] * diff --git a/lib/dialects/abstract/query-generator/transaction.js b/lib/dialects/abstract/query-generator/transaction.js index 5557ce33276b..1cda86a6979a 100644 --- a/lib/dialects/abstract/query-generator/transaction.js +++ b/lib/dialects/abstract/query-generator/transaction.js @@ -7,7 +7,7 @@ const TransactionQueries = { * Returns a query that sets the transaction isolation level. * * @param {string} value The isolation level. - * @param {Object} options An object with options. + * @param {object} options An object with options. * @returns {string} The generated sql query. * @private */ diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 68122156cc38..1d13b41dd999 100755 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -38,10 +38,10 @@ class AbstractQuery { * skipValueReplace: bool, do not replace (but do unescape $$). Check correct syntax and if all values are available * * @param {string} sql - * @param {Object|Array} values + * @param {object|Array} values * @param {string} dialect * @param {Function} [replacementFunc] - * @param {Object} [options] + * @param {object} [options] * @private */ static formatBindParameters(sql, values, dialect, replacementFunc, options = {}) { @@ -323,7 +323,7 @@ class AbstractQuery { /** * @param {string} sql * @param {Function} debugContext - * @param {Array|Object} parameters + * @param {Array|object} parameters * @protected * @returns {Function} A function to call after the query was completed. */ @@ -333,7 +333,7 @@ class AbstractQuery { const logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters; const startTime = Date.now(); let logParameter = ''; - + if (logQueryParameters && parameters) { const delimiter = sql.endsWith(';') ? '' : ';'; let paramStr; @@ -396,8 +396,8 @@ class AbstractQuery { * ] * * @param {Array} rows - * @param {Object} includeOptions - * @param {Object} options + * @param {object} includeOptions + * @param {object} options * @private */ static _groupJoinData(rows, includeOptions, options) { diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index 1fb08dc11816..e2cb44098b54 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -33,7 +33,7 @@ class ConnectionManager extends AbstractConnectionManager { * Set the pool handlers on connection.error * Also set proper timezone once connection is connected. * - * @param {Object} config + * @param {object} config * @returns {Promise} * @private */ diff --git a/lib/dialects/mssql/data-types.js b/lib/dialects/mssql/data-types.js index 48adfe9ec68a..e2fdd43f139d 100644 --- a/lib/dialects/mssql/data-types.js +++ b/lib/dialects/mssql/data-types.js @@ -8,7 +8,7 @@ module.exports = BaseTypes => { /** * Removes unsupported MSSQL options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. * - * @param {Object} dataType The base integer data type. + * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 5b69a1efbbd2..4bdbe9e9de4c 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -695,7 +695,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { /** * Generates an SQL query that returns all foreign keys details of a table. * - * @param {string|Object} table + * @param {string|object} table * @param {string} catalogName database name * @returns {string} */ diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index a34eed2c6797..fed653063be3 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -15,7 +15,7 @@ @param {QueryInterface} qi @param {string} tableName The name of the table. @param {string} attributeName The name of the attribute that we want to remove. - @param {Object} options + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @private diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 285f4ea897db..74c91644c342 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -33,7 +33,7 @@ class ConnectionManager extends AbstractConnectionManager { * Set the pool handlers on connection.error * Also set proper timezone once connection is connected. * - * @param {Object} config + * @param {object} config * @returns {Promise} * @private */ diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index 2454f02f29e5..f6aa45d5cd27 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -488,7 +488,7 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { /** * Generates an SQL query that returns all foreign keys of a table. * - * @param {Object} table The table. + * @param {object} table The table. * @param {string} schemaName The name of the schema. * @returns {string} The generated sql query. * @private @@ -501,7 +501,7 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { /** * Generates an SQL query that returns the foreign key constraint of a given column. * - * @param {Object} table The table. + * @param {object} table The table. * @param {string} columnName The name of the column. * @returns {string} The generated sql query. * @private diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 5f8289d3af18..3e2ce91ccff9 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -17,7 +17,7 @@ const sequelizeErrors = require('../../errors'); @param {QueryInterface} qi @param {string} tableName The name of the table. @param {string} columnName The name of the attribute that we want to remove. - @param {Object} options + @param {object} options @private */ @@ -50,7 +50,7 @@ function removeColumn(qi, tableName, columnName, options = {}) { * @param {QueryInterface} qi * @param {string} tableName * @param {string} constraintName - * @param {Object} options + * @param {object} options * * @private */ diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js index 68ead0fa1397..3b5f222648a9 100644 --- a/lib/dialects/postgres/data-types.js +++ b/lib/dialects/postgres/data-types.js @@ -9,7 +9,7 @@ module.exports = BaseTypes => { /** * Removes unsupported Postgres options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. * - * @param {Object} dataType The base integer data type. + * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 60c40bb5b256..14df6910e0af 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -17,8 +17,8 @@ const QueryTypes = require('../../query-types'); * * @param {QueryInterface} qi * @param {string} tableName Name of table to create - * @param {Object} attributes Object representing a list of normalized table attributes - * @param {Object} [options] + * @param {object} attributes Object representing a list of normalized table attributes + * @param {object} [options] * @param {Model} [model] * * @returns {Promise} diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 5f0b60ab9b15..a9f0af150ad8 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -15,7 +15,7 @@ class Query extends AbstractQuery { * Rewrite query with parameters. * * @param {string} sql - * @param {Array|Object} values + * @param {Array|object} values * @param {string} dialect * @private */ diff --git a/lib/dialects/sqlite/data-types.js b/lib/dialects/sqlite/data-types.js index 765d5efa4191..30ef4d654023 100644 --- a/lib/dialects/sqlite/data-types.js +++ b/lib/dialects/sqlite/data-types.js @@ -6,7 +6,7 @@ module.exports = BaseTypes => { /** * Removes unsupported SQLite options, i.e., UNSIGNED and ZEROFILL, for the integer data types. * - * @param {Object} dataType The base integer data type. + * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 730a5e126e52..fc4cf705a86d 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -20,7 +20,7 @@ const QueryTypes = require('../../query-types'); @param {QueryInterface} qi @param {string} tableName The name of the table. @param {string} attributeName The name of the attribute that we want to remove. - @param {Object} options + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @since 1.6.0 @@ -45,8 +45,8 @@ exports.removeColumn = removeColumn; @param {QueryInterface} qi @param {string} tableName The name of the table. - @param {Object} attributes An object with the attribute's name as key and its options as value object. - @param {Object} options + @param {object} attributes An object with the attribute's name as key and its options as value object. + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @since 1.6.0 @@ -74,7 +74,7 @@ exports.changeColumn = changeColumn; @param {string} tableName The name of the table. @param {string} attrNameBefore The name of the attribute before it was renamed. @param {string} attrNameAfter The name of the attribute after it was renamed. - @param {Object} options + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @since 1.6.0 @@ -97,7 +97,7 @@ exports.renameColumn = renameColumn; * @param {QueryInterface} qi * @param {string} tableName * @param {string} constraintName - * @param {Object} options + * @param {object} options * * @private */ @@ -146,7 +146,7 @@ exports.removeConstraint = removeConstraint; /** * @param {QueryInterface} qi * @param {string} tableName - * @param {Object} options + * @param {object} options * * @private */ @@ -177,7 +177,7 @@ exports.addConstraint = addConstraint; /** * @param {QueryInterface} qi * @param {string} tableName - * @param {Object} options Query Options + * @param {object} options Query Options * * @private * @returns {Promise} diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 3ea230cdc6b5..bb0ec2053034 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -20,7 +20,7 @@ class Query extends AbstractQuery { * rewrite query with parameters. * * @param {string} sql - * @param {Array|Object} values + * @param {Array|object} values * @param {string} dialect * @private */ @@ -229,7 +229,7 @@ class Query extends AbstractQuery { } else { complete = this._logQuery(sql, debug, parameters); } - + return new Promise(resolve => { const columnTypes = {}; diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js index b5968080b9e2..4e30fe6d907e 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -7,7 +7,7 @@ const BaseError = require('./base-error'); * Used with Promise.AggregateError * * @param {Error} error Error for a given record/instance - * @param {Object} record DAO instance that error belongs to + * @param {object} record DAO instance that error belongs to */ class BulkRecordError extends BaseError { constructor(error, record) { diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 9d9a98821666..185a5d76c835 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -61,8 +61,8 @@ class ValidationErrorItem { * @param {string} type The type/origin of the validation error * @param {string} path The field that triggered the validation error * @param {string} value The value that generated the error - * @param {Object} [inst] the DAO instance that caused the validation error - * @param {Object} [validatorKey] a validation "key", used for identification + * @param {object} [inst] the DAO instance that caused the validation error + * @param {object} [validatorKey] a validation "key", used for identification * @param {string} [fnName] property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable * @param {string} [fnArgs] parameters used with the BUILT-IN validator function, if applicable */ @@ -179,7 +179,7 @@ class ValidationErrorItem { /** * An enum that defines valid ValidationErrorItem `origin` values * - * @type {Object} + * @type {object} * @property CORE {string} specifies errors that originate from the sequelize "core" * @property DB {string} specifies validation errors that originate from the storage engine * @property FUNCTION {string} specifies validation errors that originate from validator functions (both built-in and custom) defined for a given attribute @@ -195,7 +195,7 @@ ValidationErrorItem.Origins = { * that maps current `type` strings (as given to ValidationErrorItem.constructor()) to * our new `origin` values. * - * @type {Object} + * @type {object} */ ValidationErrorItem.TypeStringMap = { 'notnull violation': 'CORE', diff --git a/lib/hooks.js b/lib/hooks.js index 9caeb589d5b9..32a35c34f1e3 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -75,7 +75,7 @@ const Hooks = { /** * Process user supplied hooks definition * - * @param {Object} hooks hooks definition + * @param {object} hooks hooks definition * * @private * @memberof Sequelize diff --git a/lib/instance-validator.js b/lib/instance-validator.js index 385294f545f7..8b497d8eee4e 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -12,7 +12,7 @@ const validator = require('./utils/validator-extras').validator; * Instance Validator. * * @param {Instance} modelInstance The model instance. - * @param {Object} options A dictionary with options. + * @param {object} options A dictionary with options. * * @private */ @@ -281,7 +281,7 @@ class InstanceValidator { * @param {string} validatorType One of known to Sequelize validators. * @param {string} field The field that is being validated * - * @returns {Object} An object with specific keys to invoke the validator. + * @returns {object} An object with specific keys to invoke the validator. */ _invokeBuiltinValidator(value, test, validatorType, field) { return Promise.try(() => { @@ -330,7 +330,7 @@ class InstanceValidator { /** * Will validate a single field against its schema definition (isnull). * - * @param {Object} rawAttribute As defined in the Schema. + * @param {object} rawAttribute As defined in the Schema. * @param {string} field The field name. * @param {*} value anything. * diff --git a/lib/model-manager.js b/lib/model-manager.js index f0ef52e26a46..5d3d47753833 100644 --- a/lib/model-manager.js +++ b/lib/model-manager.js @@ -35,7 +35,7 @@ class ModelManager { * Will take foreign key constraints into account so that dependencies are visited before dependents. * * @param {Function} iterator method to execute on each model - * @param {Object} [options] iterator options + * @param {object} [options] iterator options * @private */ forEachModel(iterator, options) { diff --git a/lib/model.js b/lib/model.js index 5fefe7d2fde9..8de3659bf12f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -77,8 +77,8 @@ class Model { /** * Builds a new model instance. * - * @param {Object} [values={}] an object of key value pairs - * @param {Object} [options] instance construction options + * @param {object} [values={}] an object of key value pairs + * @param {object} [options] instance construction options * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. * @param {boolean} [options.isNewRecord=true] Is this a new record * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` @@ -865,8 +865,8 @@ class Model { * @see * {@link Hooks} * - * @param {Object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: - * @param {string|DataTypes|Object} attributes.column The description of a database column + * @param {object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: + * @param {string|DataTypes|object} attributes.column The description of a database column * @param {string|DataTypes} attributes.column.type A string or a data type * @param {boolean} [attributes.column.allowNull=true] If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved. * @param {any} [attributes.column.defaultValue=null] A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`) @@ -883,28 +883,28 @@ class Model { * @param {string} [attributes.column.onDelete] What should happen when the referenced key is deleted. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION * @param {Function} [attributes.column.get] Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying values. * @param {Function} [attributes.column.set] Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the underlying values. - * @param {Object} [attributes.column.validate] An object of validations to execute for this column every time the model is saved. Can be either the name of a validation provided by validator.js, a validation function provided by extending validator.js (see the `DAOValidator` property for more details), or a custom validation function. Custom validation functions are called with the value of the field and the instance itself as the `this` binding, and can possibly take a second callback argument, to signal that they are asynchronous. If the validator is sync, it should throw in the case of a failed validation; if it is async, the callback should be called with the error text. - * @param {Object} options These options are merged with the default define options provided to the Sequelize constructor - * @param {Object} options.sequelize Define the sequelize instance to attach to the new Model. Throw error if none is provided. + * @param {object} [attributes.column.validate] An object of validations to execute for this column every time the model is saved. Can be either the name of a validation provided by validator.js, a validation function provided by extending validator.js (see the `DAOValidator` property for more details), or a custom validation function. Custom validation functions are called with the value of the field and the instance itself as the `this` binding, and can possibly take a second callback argument, to signal that they are asynchronous. If the validator is sync, it should throw in the case of a failed validation; if it is async, the callback should be called with the error text. + * @param {object} options These options are merged with the default define options provided to the Sequelize constructor + * @param {object} options.sequelize Define the sequelize instance to attach to the new Model. Throw error if none is provided. * @param {string} [options.modelName] Set name of the model. By default its same as Class name. - * @param {Object} [options.defaultScope={}] Define the default search scope to use for this model. Scopes have the same form as the options passed to find / findAll - * @param {Object} [options.scopes] More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about how scopes are defined, and what you can do with them + * @param {object} [options.defaultScope={}] Define the default search scope to use for this model. Scopes have the same form as the options passed to find / findAll + * @param {object} [options.scopes] More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about how scopes are defined, and what you can do with them * @param {boolean} [options.omitNull] Don't persist null values. This means that all columns with null values will not be saved * @param {boolean} [options.timestamps=true] Adds createdAt and updatedAt timestamps to the model. * @param {boolean} [options.paranoid=false] Calling `destroy` will not delete the model, but instead set a `deletedAt` timestamp if this is true. Needs `timestamps=true` to work * @param {boolean} [options.underscored=false] Add underscored field to all attributes, this covers user defined attributes, timestamps and foreign keys. Will not affect attributes with explicitly set `field` option * @param {boolean} [options.freezeTableName=false] If freezeTableName is true, sequelize will not try to alter the model name to get the table name. Otherwise, the model name will be pluralized - * @param {Object} [options.name] An object with two attributes, `singular` and `plural`, which are used when this model is associated to others. + * @param {object} [options.name] An object with two attributes, `singular` and `plural`, which are used when this model is associated to others. * @param {string} [options.name.singular=Utils.singularize(modelName)] Singular name for model * @param {string} [options.name.plural=Utils.pluralize(modelName)] Plural name for model - * @param {Array} [options.indexes] indexes definitions + * @param {Array} [options.indexes] indexes definitions * @param {string} [options.indexes[].name] The name of the index. Defaults to model name + _ + fields concatenated * @param {string} [options.indexes[].type] Index type. Only used by mysql. One of `UNIQUE`, `FULLTEXT` and `SPATIAL` * @param {string} [options.indexes[].using] The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and postgres, and postgres additionally supports GIST and GIN. * @param {string} [options.indexes[].operator] Specify index operator. * @param {boolean} [options.indexes[].unique=false] Should the index by unique? Can also be triggered by setting type to `UNIQUE` * @param {boolean} [options.indexes[].concurrently=false] PostgresSQL will build the index without taking any write locks. Postgres only - * @param {Array} [options.indexes[].fields] An array of the fields to index. Each field can either be a string containing the name of the field, a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `attribute` (field name), `length` (create a prefix index of length chars), `order` (the direction the column should be sorted in), `collate` (the collation (sort order) for the column) + * @param {Array} [options.indexes[].fields] An array of the fields to index. Each field can either be a string containing the name of the field, a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `attribute` (field name), `length` (create a prefix index of length chars), `order` (the direction the column should be sorted in), `collate` (the collation (sort order) for the column) * @param {string|boolean} [options.createdAt] Override the name of the createdAt attribute if a string is provided, or disable it if false. Timestamps must be true. Underscored field will be set with underscored setting. * @param {string|boolean} [options.updatedAt] Override the name of the updatedAt attribute if a string is provided, or disable it if false. Timestamps must be true. Underscored field will be set with underscored setting. * @param {string|boolean} [options.deletedAt] Override the name of the deletedAt attribute if a string is provided, or disable it if false. Timestamps must be true. Underscored field will be set with underscored setting. @@ -915,8 +915,8 @@ class Model { * @param {string} [options.comment] Specify comment for model's table * @param {string} [options.collate] Specify collation for model's table * @param {string} [options.initialAutoIncrement] Set the initial AUTO_INCREMENT value for the table in MySQL. - * @param {Object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, beforeSave, afterDestroy, afterUpdate, afterBulkCreate, afterSave, afterBulkDestroy and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions. - * @param {Object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error. + * @param {object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, beforeSave, afterDestroy, afterUpdate, afterBulkCreate, afterSave, afterBulkDestroy and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions. + * @param {object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error. * * @returns {Model} */ @@ -1265,7 +1265,7 @@ class Model { /** * Sync this Model to the DB, that is create the table. * - * @param {Object} [options] sync options + * @param {object} [options] sync options * * @see * {@link Sequelize#sync} for options @@ -1376,7 +1376,7 @@ class Model { /** * Drop the table represented by this Model * - * @param {Object} [options] drop options + * @param {object} [options] drop options * @param {boolean} [options.cascade=false] Also drop all objects depending on this table, such as views. Only works in postgres * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). @@ -1402,7 +1402,7 @@ class Model { * for the model. * * @param {string} schema The name of the schema - * @param {Object} [options] schema options + * @param {object} [options] schema options * @param {string} [options.schemaDelimiter='.'] The character(s) that separates the schema name from the table name * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). @@ -1434,7 +1434,7 @@ class Model { * Get the table name of the model, taking schema into account. The method will return The name as a string if the model has no schema, * or an object with `tableName`, `schema` and `delimiter` properties. * - * @returns {string|Object} + * @returns {string|object} */ static getTableName() { return this.QueryGenerator.addSchema(this); @@ -1455,8 +1455,8 @@ class Model { * By default this will throw an error if a scope with that name already exists. Pass `override: true` in the options object to silence this error. * * @param {string} name The name of the scope. Use `defaultScope` to override the default scope - * @param {Object|Function} scope scope or options - * @param {Object} [options] scope options + * @param {object|Function} scope scope or options + * @param {object} [options] scope options * @param {boolean} [options.override=false] override old scope if already defined */ static addScope(name, scope, options) { @@ -1516,7 +1516,7 @@ class Model { * Model.scope({ method: ['complexFunction', 'dan@sequelize.com', 42]}).findAll() * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 * - * @param {?Array|Object|string} [option] The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes and scope functions with no arguments, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default. + * @param {?Array|object|string} [option] The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes and scope functions with no arguments, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default. * * @returns {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope. */ @@ -1637,37 +1637,37 @@ class Model { * * The promise is resolved with an array of Model instances if the query succeeds._ * - * @param {Object} [options] A hash of options to describe the scope of the search - * @param {Object} [options.where] A hash of attributes to describe your search. See above for examples. - * @param {Array|Object} [options.attributes] A list of the attributes that you want to select, or an object with `include` and `exclude` keys. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance + * @param {object} [options] A hash of options to describe the scope of the search + * @param {object} [options.where] A hash of attributes to describe your search. See above for examples. + * @param {Array|object} [options.attributes] A list of the attributes that you want to select, or an object with `include` and `exclude` keys. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance * @param {Array} [options.attributes.include] Select all the attributes of the model, plus some additional ones. Useful for aggregations, e.g. `{ attributes: { include: [[sequelize.fn('COUNT', sequelize.col('id')), 'total']] }` * @param {Array} [options.attributes.exclude] Select all the attributes of the model, except some few. Useful for security purposes e.g. `{ attributes: { exclude: ['password'] } }` * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be returned. If false, both deleted and non-deleted records will be returned. Only applies if `options.paranoid` is true for the model. - * @param {Array} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}` or `{ include: ['Alias']}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). + * @param {Array} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}` or `{ include: ['Alias']}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). * @param {Model} [options.include[].model] The model you want to eagerly load * @param {string} [options.include[].as] The alias of the relation, in case the model you want to eagerly load is aliased. For `hasOne` / `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural * @param {Association} [options.include[].association] The association you want to eagerly load. (This can be used instead of providing a model/as pair) - * @param {Object} [options.include[].where] Where clauses to apply to the child models. Note that this converts the eager load to an inner join, unless you explicitly set `required: false` + * @param {object} [options.include[].where] Where clauses to apply to the child models. Note that this converts the eager load to an inner join, unless you explicitly set `required: false` * @param {boolean} [options.include[].or=false] Whether to bind the ON and WHERE clause together by OR instead of AND. - * @param {Object} [options.include[].on] Supply your own ON condition for the join. + * @param {object} [options.include[].on] Supply your own ON condition for the join. * @param {Array} [options.include[].attributes] A list of attributes to select from the child model * @param {boolean} [options.include[].required] If true, converts to an inner join, which means that the parent model will only be loaded if it has any matching children. True if `include.where` is set, false otherwise. * @param {boolean} [options.include[].separate] If true, runs a separate query to fetch the associated instances, only supported for hasMany associations * @param {number} [options.include[].limit] Limit the joined rows, only supported with include.separate=true - * @param {Object} [options.include[].through.where] Filter on the join model for belongsToMany relations + * @param {object} [options.include[].through.where] Filter on the join model for belongsToMany relations * @param {Array} [options.include[].through.attributes] A list of attributes to select from the join model for belongsToMany relations - * @param {Array} [options.include[].include] Load further nested related models + * @param {Array} [options.include[].include] Load further nested related models * @param {boolean} [options.include[].duplicating] Mark the include as duplicating, will prevent a subquery from being used. * @param {Array|Sequelize.fn|Sequelize.col|Sequelize.literal} [options.order] Specifies an ordering. Using an array, you can provide several columns / functions to order by. Each element can be further wrapped in a two-element array. The first element is the column / function to order by, the second is the direction. For example: `order: [['name', 'DESC']]`. In this way the column will be escaped, but the direction will not. * @param {number} [options.limit] Limit for result * @param {number} [options.offset] Offset for result * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string|Object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. See [transaction.LOCK for an example](transaction#lock) + * @param {string|object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. See [transaction.LOCK for an example](transaction#lock) * @param {boolean} [options.skipLocked] Skip locked rows. Only supported in Postgres. * @param {boolean} [options.raw] Return raw result. See sequelize.query for more information. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Object} [options.having] Having options + * @param {object} [options.having] Having options * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean|Error} [options.rejectOnEmpty=false] Throws an error when no records found * @@ -1858,7 +1858,7 @@ class Model { * Search for a single instance by its primary key._ * * @param {number|string|Buffer} param The value of the desired instance's primary key. - * @param {Object} [options] find options + * @param {object} [options] find options * @param {Transaction} [options.transaction] Transaction to run query under * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @@ -1892,7 +1892,7 @@ class Model { * * __Alias__: _find_ * - * @param {Object} [options] A hash of options to describe the scope of the search + * @param {object} [options] A hash of options to describe the scope of the search * @param {Transaction} [options.transaction] Transaction to run query under * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @@ -1931,8 +1931,8 @@ class Model { * * @param {string} attribute The attribute to aggregate over. Can be a field name or * * @param {string} aggregateFunction The function to use for aggregation, e.g. sum, max etc. - * @param {Object} [options] Query options. See sequelize.query for full options - * @param {Object} [options.where] A hash of search attributes. + * @param {object} [options] Query options. See sequelize.query for full options + * @param {object} [options.where] A hash of search attributes. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). * @param {DataTypes|string} [options.dataType] The type of the result. If `field` is a field in this Model, the default will be the type of that field, otherwise defaults to float. @@ -1940,7 +1940,7 @@ class Model { * @param {Transaction} [options.transaction] Transaction to run query under * @param {boolean} [options.plain] When `true`, the first returned value of `aggregateFunction` is cast to `dataType` and returned. If additional attributes are specified, along with `group` clauses, set `plain` to `false` to return all values of all returned rows. Defaults to `true` * - * @returns {Promise} Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in which case the complete data result is returned. + * @returns {Promise} Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in which case the complete data result is returned. */ static aggregate(attribute, aggregateFunction, options) { options = Utils.cloneDeep(options); @@ -2003,9 +2003,9 @@ class Model { * * If you provide an `include` option, the number of matching associations will be counted instead. * - * @param {Object} [options] options - * @param {Object} [options.where] A hash of search attributes. - * @param {Object} [options.include] Include options. See `find` for details + * @param {object} [options] options + * @param {object} [options.where] A hash of search attributes. + * @param {object} [options.include] Include options. See `find` for details * @param {boolean} [options.paranoid=true] Set `true` to count only non-deleted records. Can be used on models with `paranoid` enabled * @param {boolean} [options.distinct] Apply COUNT(DISTINCT(col)) on primary key or on options.col. * @param {string} [options.col] Column on which COUNT() should be applied @@ -2075,7 +2075,7 @@ class Model { * * # Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted * - * @param {Object} [options] See findAll options + * @param {object} [options] See findAll options * * @see * {@link Model.findAll} for a specification of find and query options @@ -2109,7 +2109,7 @@ class Model { * Find the maximum value of field * * @param {string} field attribute / field name - * @param {Object} [options] See aggregate + * @param {object} [options] See aggregate * * @see * {@link Model.aggregate} for options @@ -2124,7 +2124,7 @@ class Model { * Find the minimum value of field * * @param {string} field attribute / field name - * @param {Object} [options] See aggregate + * @param {object} [options] See aggregate * * @see * {@link Model.aggregate} for options @@ -2139,7 +2139,7 @@ class Model { * Find the sum of field * * @param {string} field attribute / field name - * @param {Object} [options] See aggregate + * @param {object} [options] See aggregate * * @see * {@link Model.aggregate} for options @@ -2153,8 +2153,8 @@ class Model { /** * Builds multiple models in one operation. * - * @param {Array} valueSets An object of key value pairs or an array of such. If an array, the function will return an array of instances. - * @param {Object} [options] Instance build options, + * @param {Array} valueSets An object of key value pairs or an array of such. If an array, the function will return an array of instances. + * @param {object} [options] Instance build options, * @see * {@link constructor} * @@ -2188,8 +2188,8 @@ class Model { * @see * {@link Model.save} * - * @param {Object} values hash of data values to create new record with - * @param {Object} [options] build and query options + * @param {object} values hash of data values to create new record with + * @param {object} [options] build and query options * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. * @param {boolean} [options.isNewRecord=true] Is this new record * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` @@ -2223,10 +2223,10 @@ class Model { * Find a row that matches the query, or build (but don't save) the row if none is found. * The successful result of the promise will be (instance, built) * - * @param {Object} options find options - * @param {Object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. - * @param {Object} [options.defaults] Default values to use if building a new instance - * @param {Object} [options.transaction] Transaction to run query under + * @param {object} options find options + * @param {object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. + * @param {object} [options.defaults] Default values to use if building a new instance + * @param {object} [options.transaction] Transaction to run query under * * @returns {Promise} */ @@ -2267,9 +2267,9 @@ class Model { * @see * {@link Model.findAll} for a full specification of find and options * - * @param {Object} options find and create options - * @param {Object} options.where where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. - * @param {Object} [options.defaults] Default values to use if creating a new instance + * @param {object} options find and create options + * @param {object} options.where where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. + * @param {object} [options.defaults] Default values to use if creating a new instance * @param {Transaction} [options.transaction] Transaction to run query under * * @returns {Promise} @@ -2370,9 +2370,9 @@ class Model { * @see * {@link Model.findAll} for a full specification of find and options * - * @param {Object} options find options - * @param {Object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. - * @param {Object} [options.defaults] Default values to use if creating a new instance + * @param {object} options find options + * @param {object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. + * @param {object} [options.defaults] Default values to use if creating a new instance * * @returns {Promise} */ @@ -2409,8 +2409,8 @@ class Model { * * MSSQL - Implemented as a single query using `MERGE` and `WHEN (NOT) MATCHED THEN` * **Note** that SQLite returns undefined for created, no matter if the row was created or updated. This is because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know whether the row was inserted or not. * - * @param {Object} values hash of values to upsert - * @param {Object} [options] upsert options + * @param {object} values hash of values to upsert + * @param {object} [options] upsert options * @param {boolean} [options.validate=true] Run validations before the row is inserted * @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all changed fields * @param {boolean} [options.hooks=true] Run before / after upsert hooks? @@ -2501,7 +2501,7 @@ class Model { * If validation fails, the promise is rejected with an array-like [AggregateError](http://bluebirdjs.com/docs/api/aggregateerror.html) * * @param {Array} records List of objects (key/value pairs) to create instances from - * @param {Object} [options] Bulk create options + * @param {object} [options] Bulk create options * @param {Array} [options.fields] Fields to insert (defaults to all fields) * @param {boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation * @param {boolean} [options.hooks=true] Run before / after bulk create hooks? @@ -2833,7 +2833,7 @@ class Model { /** * Truncate all instances of the model. This is a convenient method for Model.destroy({ truncate: true }). * - * @param {Object} [options] The options passed to Model.destroy in addition to truncate + * @param {object} [options] The options passed to Model.destroy in addition to truncate * @param {boolean|Function} [options.cascade = false] Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. * @param {boolean} [options.restartIdentity=false] Automatically restart sequences owned by columns of the truncated table. * @param {Transaction} [options.transaction] Transaction to run query under @@ -2855,8 +2855,8 @@ class Model { /** * Delete multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. * - * @param {Object} options destroy options - * @param {Object} [options.where] Filter the destroy + * @param {object} options destroy options + * @param {object} [options.where] Filter the destroy * @param {boolean} [options.hooks=true] Run before / after bulk destroy hooks? * @param {boolean} [options.individualHooks=false] If set to true, destroy will SELECT all records matching the where parameter and will execute before / after destroy hooks on each row * @param {number} [options.limit] How many rows to delete @@ -2946,8 +2946,8 @@ class Model { /** * Restore multiple instances if `paranoid` is enabled. * - * @param {Object} options restore options - * @param {Object} [options.where] Filter the restore + * @param {object} options restore options + * @param {object} [options.where] Filter the restore * @param {boolean} [options.hooks=true] Run before / after bulk restore hooks? * @param {boolean} [options.individualHooks=false] If set to true, restore will find all records within the where parameter and will execute before / after bulkRestore hooks on each row * @param {number} [options.limit] How many rows to undelete (only for mysql) @@ -3012,9 +3012,9 @@ class Model { /** * Update multiple instances that match the where options. * - * @param {Object} values hash of values to update - * @param {Object} options update options - * @param {Object} options.where Options to describe the scope of the search. + * @param {object} values hash of values to update + * @param {object} options update options + * @param {object} options.where Options to describe the scope of the search. * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be updated. If false, both deleted and non-deleted records will be updated. Only applies if `options.paranoid` is true for the model. * @param {Array} [options.fields] Fields to update (defaults to all fields) * @param {boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation @@ -3237,7 +3237,7 @@ class Model { * Run a describe query on the table. * * @param {string} [schema] schema name to search table in - * @param {Object} [options] query options + * @param {object} [options] query options * * @returns {Promise} hash of attributes and their types */ @@ -3304,9 +3304,9 @@ class Model { * @see * {@link Model#reload} * - * @param {string|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {Object} options increment options - * @param {Object} options.where conditions hash + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} options increment options + * @param {object} options.where conditions hash * @param {number} [options.by=1] The number to increment by * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -3389,8 +3389,8 @@ class Model { * // `by` is ignored, since each column has its own value * Model.decrement({ answer: 42, tries: -1}, { by: 2, where: { foo: 'bar' } }); * - * @param {string|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {Object} options decrement options, similar to increment + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} options decrement options, similar to increment * * @see * {@link Model.increment} @@ -3421,7 +3421,7 @@ class Model { * * @param {boolean} [checkVersion=false] include version attribute in where hash * - * @returns {Object} + * @returns {object} */ where(checkVersion) { const where = this.constructor.primaryKeyAttributes.reduce((result, attribute) => { @@ -3477,11 +3477,11 @@ class Model { * If key is given and a field or virtual getter is present for the key it will call that getter - else it will return the value for key. * * @param {string} [key] key to get value of - * @param {Object} [options] get options + * @param {object} [options] get options * @param {boolean} [options.plain=false] If set to true, included instances will be returned as plain objects * @param {boolean} [options.raw=false] If set to true, field and virtual setters will be ignored * - * @returns {Object|any} + * @returns {object|any} */ get(key, options) { if (options === undefined && typeof key === 'object') { @@ -3566,9 +3566,9 @@ class Model { * @see * {@link Model.findAll} for more information about includes * - * @param {string|Object} key key to set, it can be string or object. When string it will set that key, for object it will loop over all object properties nd set them. + * @param {string|object} key key to set, it can be string or object. When string it will set that key, for object it will loop over all object properties nd set them. * @param {any} value value to set - * @param {Object} [options] set options + * @param {object} [options] set options * @param {boolean} [options.raw=false] If set to true, field and virtual setters will be ignored * @param {boolean} [options.reset=false] Clear all previously set data values * @@ -3798,7 +3798,7 @@ class Model { * On success, the callback will be called with this instance. On validation error, the callback will be called with an instance of `Sequelize.ValidationError`. * This error will have a property for each of the fields for which validation failed, with the error message for that field. * - * @param {Object} [options] save options + * @param {object} [options] save options * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {boolean} [options.validate=true] If false, validations won't be run. @@ -4079,7 +4079,7 @@ class Model { * @see * {@link Model.findAll} * - * @param {Object} [options] Options that are passed on to `Model.find` + * @param {object} [options] Options that are passed on to `Model.find` * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * * @returns {Promise} @@ -4115,7 +4115,7 @@ class Model { * * The promise fulfills if and only if validation successful; otherwise it rejects an Error instance containing { field name : [error msgs] } entries. * - * @param {Object} [options] Options that are passed to the validator + * @param {object} [options] Options that are passed to the validator * @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated * @param {Array} [options.fields] An array of strings. Only the properties that are in this array will be validated * @param {boolean} [options.hooks=true] Run before and after validate hooks @@ -4135,8 +4135,8 @@ class Model { * @see * {@link Model#save} * - * @param {Object} values See `set` - * @param {Object} options See `save` + * @param {object} values See `set` + * @param {object} options See `save` * * @returns {Promise} */ @@ -4168,7 +4168,7 @@ class Model { /** * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will either be completely deleted, or have its deletedAt timestamp set to the current time. * - * @param {Object} [options={}] destroy options + * @param {object} [options={}] destroy options * @param {boolean} [options.force=false] If set to true, paranoid models will actually be deleted * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {Transaction} [options.transaction] Transaction to run query under @@ -4237,7 +4237,7 @@ class Model { /** * Restore the row corresponding to this instance. Only available for paranoid models. * - * @param {Object} [options={}] restore options + * @param {object} [options={}] restore options * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {Transaction} [options.transaction] Transaction to run query under * @@ -4290,8 +4290,8 @@ class Model { * @see * {@link Model#reload} * - * @param {string|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {Object} [options] options + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} [options] options * @param {number} [options.by=1] The number to increment by * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -4330,8 +4330,8 @@ class Model { * * @see * {@link Model#reload} - * @param {string|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given - * @param {Object} [options] decrement options + * @param {string|Array|object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given + * @param {object} [options] decrement options * @param {number} [options.by=1] The number to decrement by * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -4393,7 +4393,7 @@ class Model { * @see * {@link Model#get} * - * @returns {Object} + * @returns {object} */ toJSON() { return _.cloneDeep( @@ -4408,12 +4408,12 @@ class Model { * The foreign key is added on the target. * * @param {Model} target Target model - * @param {Object} [options] hasMany association options + * @param {object} [options] hasMany association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks - * @param {string|Object} [options.as] The alias of this model. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key in the target table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source + * @param {string|object} [options.as] The alias of this model. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target + * @param {string|object} [options.foreignKey] The name of the foreign key in the target table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source * @param {string} [options.sourceKey] The name of the field to use as the key for the association in the source table. Defaults to the primary key of the source table - * @param {Object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) + * @param {object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) * @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise * @param {string} [options.onUpdate='CASCADE'] Set `ON UPDATE` * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. @@ -4429,16 +4429,16 @@ class Model { * Create an N:M association with a join table. Defining `through` is required. * * @param {Model} target Target model - * @param {Object} options belongsToMany association options + * @param {object} options belongsToMany association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks - * @param {Model|string|Object} options.through The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it. + * @param {Model|string|object} options.through The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it. * @param {Model} [options.through.model] The model used to join both sides of the N:M association. - * @param {Object} [options.through.scope] A key/value set that will be used for association create and find defaults on the through model. (Remember to add the attributes to the through model) + * @param {object} [options.through.scope] A key/value set that will be used for association create and find defaults on the through model. (Remember to add the attributes to the through model) * @param {boolean} [options.through.unique=true] If true a unique key will be generated from the foreign keys used (might want to turn this off and create specific unique keys when using scopes) - * @param {string|Object} [options.as] The alias of this association. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key in the join table (representing the source model) or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source - * @param {string|Object} [options.otherKey] The name of the foreign key in the join table (representing the target model) or an object representing the type definition for the other column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target - * @param {Object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) + * @param {string|object} [options.as] The alias of this association. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target + * @param {string|object} [options.foreignKey] The name of the foreign key in the join table (representing the source model) or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source + * @param {string|object} [options.otherKey] The name of the foreign key in the join table (representing the target model) or an object representing the type definition for the other column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target + * @param {object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) * @param {boolean} [options.timestamps=sequelize.options.timestamps] Should the join model have timestamps * @param {string} [options.onDelete='SET NULL|CASCADE'] Cascade if this is a n:m, and set null if it is a 1:m * @param {string} [options.onUpdate='CASCADE'] Sets `ON UPDATE` @@ -4464,10 +4464,10 @@ class Model { * Creates an association between this (the source) and the provided target. The foreign key is added on the target. * * @param {Model} target Target model - * @param {Object} [options] hasOne association options + * @param {object} [options] hasOne association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the singularized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key attribute in the target model or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source + * @param {string|object} [options.foreignKey] The name of the foreign key attribute in the target model or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source * @param {string} [options.sourceKey] The name of the attribute to use as the key for the association in the source table. Defaults to the primary key of the source table * @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise * @param {string} [options.onUpdate='CASCADE'] Sets 'ON UPDATE' @@ -4485,10 +4485,10 @@ class Model { * Creates an association between this (the source) and the provided target. The foreign key is added on the source. * * @param {Model} target The target model - * @param {Object} [options] belongsTo association options + * @param {object} [options] belongsTo association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the singularized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key attribute in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target + * @param {string|object} [options.foreignKey] The name of the foreign key attribute in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target * @param {string} [options.targetKey] The name of the attribute to use as the key for the association in the target table. Defaults to the primary key of the target table * @param {string} [options.onDelete='SET NULL|NO ACTION'] SET NULL if foreignKey allows nulls, NO ACTION if otherwise * @param {string} [options.onUpdate='CASCADE'] Sets 'ON UPDATE' diff --git a/lib/query-interface.js b/lib/query-interface.js index 8cde8cf058f5..5ba1f2d505c9 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -28,7 +28,7 @@ class QueryInterface { * Create a database * * @param {string} database Database name to create - * @param {Object} [options] Query options + * @param {object} [options] Query options * @param {string} [options.charset] Database default character set, MYSQL only * @param {string} [options.collate] Database default collation * @param {string} [options.encoding] Database default character set, PostgreSQL only @@ -46,7 +46,7 @@ class QueryInterface { * Drop a database * * @param {string} database Database name to drop - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -59,7 +59,7 @@ class QueryInterface { * Create a schema * * @param {string} schema Schema name to create - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -72,7 +72,7 @@ class QueryInterface { * Drop a schema * * @param {string} schema Schema name to drop - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -84,7 +84,7 @@ class QueryInterface { /** * Drop all schemas * - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -98,7 +98,7 @@ class QueryInterface { /** * Show all schemas * - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -118,7 +118,7 @@ class QueryInterface { /** * Return database version * - * @param {Object} [options] Query options + * @param {object} [options] Query options * @param {QueryType} [options.type] Query type * * @returns {Promise} @@ -178,8 +178,8 @@ class QueryInterface { * ``` * * @param {string} tableName Name of table to create - * @param {Object} attributes Object representing a list of table attributes to create - * @param {Object} [options] create table and query options + * @param {object} attributes Object representing a list of table attributes to create + * @param {object} [options] create table and query options * @param {Model} [model] model class * * @returns {Promise} @@ -234,7 +234,7 @@ class QueryInterface { * Drop a table from database * * @param {string} tableName Table name to drop - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} */ @@ -276,7 +276,7 @@ class QueryInterface { /** * Drop all tables from database * - * @param {Object} [options] query options + * @param {object} [options] query options * @param {Array} [options.skip] List of table to skip * * @returns {Promise} @@ -328,7 +328,7 @@ class QueryInterface { * Drop specified enum from database (Postgres only) * * @param {string} [enumName] Enum name to drop - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} * @private @@ -347,7 +347,7 @@ class QueryInterface { /** * Drop all enums from database (Postgres only) * - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} * @private @@ -367,7 +367,7 @@ class QueryInterface { * List all enums (Postgres only) * * @param {string} [tableName] Table whose enum to list - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} * @private @@ -382,7 +382,7 @@ class QueryInterface { * * @param {string} before Current name of table * @param {string} after New name from table - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -394,7 +394,7 @@ class QueryInterface { /** * Get all tables in current database * - * @param {Object} [options] Query options + * @param {object} [options] Query options * @param {boolean} [options.raw=true] Run query in raw mode * @param {QueryType} [options.type=QueryType.SHOWTABLE] query type * @@ -432,9 +432,9 @@ class QueryInterface { * ``` * * @param {string} tableName table name - * @param {Object} [options] Query options + * @param {object} [options] Query options * - * @returns {Promise} + * @returns {Promise} */ describeTable(tableName, options) { let schema = null; @@ -486,8 +486,8 @@ class QueryInterface { * * @param {string} table Table to add column to * @param {string} key Column name - * @param {Object} attribute Attribute definition - * @param {Object} [options] Query options + * @param {object} attribute Attribute definition + * @param {object} [options] Query options * * @returns {Promise} */ @@ -505,7 +505,7 @@ class QueryInterface { * * @param {string} tableName Table to remove column from * @param {string} attributeName Column name to remove - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -531,8 +531,8 @@ class QueryInterface { * * @param {string} tableName Table name to change from * @param {string} attributeName Column name - * @param {Object} dataTypeOrOptions Attribute definition for new column - * @param {Object} [options] Query options + * @param {object} dataTypeOrOptions Attribute definition for new column + * @param {object} [options] Query options * * @returns {Promise} */ @@ -566,7 +566,7 @@ class QueryInterface { * @param {string} tableName Table name whose column to rename * @param {string} attrNameBefore Current column name * @param {string} attrNameAfter New column name - * @param {Object} [options] Query option + * @param {object} [options] Query option * * @returns {Promise} */ @@ -608,9 +608,9 @@ class QueryInterface { /** * Add an index to a column * - * @param {string|Object} tableName Table name to add index on, can be a object with schema + * @param {string|object} tableName Table name to add index on, can be a object with schema * @param {Array} [attributes] Use options.fields instead, List of attributes to add index on - * @param {Object} options indexes options + * @param {object} options indexes options * @param {Array} options.fields List of attributes to add index on * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created * @param {boolean} [options.unique] Create a unique index @@ -618,7 +618,7 @@ class QueryInterface { * @param {string} [options.operator] Index operator * @param {string} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL * @param {string} [options.name] Name of the index. Default is __ - * @param {Object} [options.where] Where condition on index, for partial indexes + * @param {object} [options.where] Where condition on index, for partial indexes * @param {string} [rawTablename] table name, this is just for backward compatibiity * * @returns {Promise} @@ -646,7 +646,7 @@ class QueryInterface { * Show indexes on a table * * @param {string} tableName table name - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} * @private @@ -693,7 +693,7 @@ class QueryInterface { * Remind: constraint informations won't return if it's sqlite. * * @param {string} tableName table name - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -729,7 +729,7 @@ class QueryInterface { * * @param {string} tableName Table name to drop index from * @param {string} indexNameOrAttributes Index name - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -788,12 +788,12 @@ class QueryInterface { * * @param {string} tableName Table name where you want to add a constraint * @param {Array} attributes Array of column names to apply the constraint over - * @param {Object} options An object to define the constraint name, type etc + * @param {object} options An object to define the constraint name, type etc * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names * @param {string} [options.defaultValue] The value for the default constraint - * @param {Object} [options.where] Where clause/expression for the CHECK constraint - * @param {Object} [options.references] Object specifying target table, column name to create foreign key constraint + * @param {object} [options.where] Where clause/expression for the CHECK constraint + * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint * @param {string} [options.references.table] Target table name * @param {string} [options.references.field] Target column name * @param {string} [rawTablename] Table name, for backward compatibility @@ -836,7 +836,7 @@ class QueryInterface { * * @param {string} tableName Table name to drop constraint from * @param {string} constraintName Constraint name - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} */ @@ -873,11 +873,11 @@ class QueryInterface { * Upsert * * @param {string} tableName table to upsert on - * @param {Object} insertValues values to be inserted, mapped to field name - * @param {Object} updateValues values to be updated, mapped to field name - * @param {Object} where various conditions + * @param {object} insertValues values to be inserted, mapped to field name + * @param {object} updateValues values to be updated, mapped to field name + * @param {object} where various conditions * @param {Model} model Model to upsert on - * @param {Object} options query options + * @param {object} options query options * * @returns {Promise} Resolves an array with */ @@ -966,8 +966,8 @@ class QueryInterface { * * @param {string} tableName Table name to insert record to * @param {Array} records List of records to insert - * @param {Object} options Various options, please see Model.bulkCreate options - * @param {Object} attributes Various attributes mapped by field name + * @param {object} options Various options, please see Model.bulkCreate options + * @param {object} attributes Various attributes mapped by field name * * @returns {Promise} */ @@ -1005,10 +1005,10 @@ class QueryInterface { * ); * * @param {string} tableName Table name to update - * @param {Object} values Values to be inserted, mapped to field name - * @param {Object} identifier A hash with conditions OR an ID as integer OR a string with conditions - * @param {Object} [options] Various options, please see Model.bulkCreate options - * @param {Object} [attributes] Attributes on return objects if supported by SQL dialect + * @param {object} values Values to be inserted, mapped to field name + * @param {object} identifier A hash with conditions OR an ID as integer OR a string with conditions + * @param {object} [options] Various options, please see Model.bulkCreate options + * @param {object} [attributes] Attributes on return objects if supported by SQL dialect * * @returns {Promise} */ @@ -1068,8 +1068,8 @@ class QueryInterface { * Delete multiple records from a table * * @param {string} tableName table name from where to delete records - * @param {Object} where where conditions to find records to delete - * @param {Object} [options] options + * @param {object} where where conditions to find records to delete + * @param {object} [options] options * @param {boolean} [options.truncate] Use truncate table command * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table. @@ -1230,9 +1230,9 @@ class QueryInterface { * @param {string} language The name of the language that the function is implemented in * @param {string} body Source code of function * @param {Array} optionsArray Extra-options for creation - * @param {Object} [options] query options + * @param {object} [options] query options * @param {boolean} options.force If force is true, any existing functions with the same parameters will be replaced. For postgres, this means using `CREATE OR REPLACE FUNCTION` instead of `CREATE FUNCTION`. Default is false - * @param {Array} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well. + * @param {Array} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well. * * @returns {Promise} */ @@ -1259,7 +1259,7 @@ class QueryInterface { * * @param {string} functionName Name of SQL function to drop * @param {Array} params List of parameters declared for SQL function - * @param {Object} [options] query options + * @param {object} [options] query options * * @returns {Promise} */ @@ -1288,7 +1288,7 @@ class QueryInterface { * @param {string} oldFunctionName Current name of function * @param {Array} params List of parameters declared for SQL function * @param {string} newFunctionName New name of function - * @param {Object} [options] query options + * @param {object} [options] query options * * @returns {Promise} */ diff --git a/lib/sequelize.js b/lib/sequelize.js index 552d9c0f79ee..d249c9e36981 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -126,7 +126,7 @@ class Sequelize { * @param {string} [database] The name of the database * @param {string} [username=null] The username which is used to authenticate against the database. * @param {string} [password=null] The password which is used to authenticate against the database. Supports SQLCipher encryption for SQLite. - * @param {Object} [options={}] An object with options. + * @param {object} [options={}] An object with options. * @param {string} [options.host='localhost'] The host of the relational database. * @param {number} [options.port=] The port of the relational database. * @param {string} [options.username=null] The username which is used to authenticate against the database. @@ -135,14 +135,14 @@ class Sequelize { * @param {string} [options.dialect] The dialect of the database you are connecting to. One of mysql, postgres, sqlite and mssql. * @param {string} [options.dialectModule=null] If specified, use this dialect library. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify 'require("pg.js")' here * @param {string} [options.dialectModulePath=null] If specified, load the dialect library from this path. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify '/path/to/pg.js' here - * @param {Object} [options.dialectOptions] An object of additional options, which are passed directly to the connection library + * @param {object} [options.dialectOptions] An object of additional options, which are passed directly to the connection library * @param {string} [options.storage] Only used by sqlite. Defaults to ':memory:' * @param {string} [options.protocol='tcp'] The protocol of the relational database. - * @param {Object} [options.define={}] Default options for model definitions. See {@link Model.init}. - * @param {Object} [options.query={}] Default options for sequelize.query + * @param {object} [options.define={}] Default options for model definitions. See {@link Model.init}. + * @param {object} [options.query={}] Default options for sequelize.query * @param {string} [options.schema=null] A schema to use - * @param {Object} [options.set={}] Default options for sequelize.set - * @param {Object} [options.sync={}] Default options for sequelize.sync + * @param {object} [options.set={}] Default options for sequelize.set + * @param {object} [options.sync={}] Default options for sequelize.sync * @param {string} [options.timezone='+00:00'] The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. Will also accept string versions of timezones used by moment.js (e.g. 'America/Los_Angeles'); this is useful to capture daylight savings time changes. * @param {string|boolean} [options.clientMinMessages='warning'] The PostgreSQL `client_min_messages` session parameter. Set to `false` to not override the database's default. * @param {boolean} [options.standardConformingStrings=true] The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. WARNING: Setting this to false may expose vulnerabilities and is not recommended! @@ -151,7 +151,7 @@ class Sequelize { * @param {boolean} [options.omitNull=false] A flag that defines if null values should be passed to SQL queries or not. * @param {boolean} [options.native=false] A flag that defines if native library shall be used or not. Currently only has an effect for postgres * @param {boolean} [options.replication=false] Use read / write replication. To enable replication, pass an object, with two properties, read and write. Write should be an object (a single server for handling writes), and read an array of object (several servers to handle reads). Each read/write server can have the following properties: `host`, `port`, `username`, `password`, `database` - * @param {Object} [options.pool] sequelize connection pool configuration + * @param {object} [options.pool] sequelize connection pool configuration * @param {number} [options.pool.max=5] Maximum number of connection in pool * @param {number} [options.pool.min=0] Minimum number of connection in pool * @param {number} [options.pool.idle=10000] The maximum time, in milliseconds, that a connection can be idle before being released. @@ -161,12 +161,12 @@ class Sequelize { * @param {boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them. WARNING: Setting this to false may expose vulnerabilities and is not recommended! * @param {string} [options.transactionType='DEFERRED'] Set the default transaction type. See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] Set the default transaction isolation level. See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options. - * @param {Object} [options.retry] Set of flags that control when a query is automatically retried. + * @param {object} [options.retry] Set of flags that control when a query is automatically retried. * @param {Array} [options.retry.match] Only retry a query if the error matches one of these strings. * @param {number} [options.retry.max] How many times a failing query is automatically retried. Set to 0 to disable retrying on SQL_BUSY error. * @param {boolean} [options.typeValidation=false] Run built-in type validators on insert and update, and select with where clause, e.g. validate that arguments passed to integer fields are integer-like. - * @param {Object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. - * @param {Object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. + * @param {object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. + * @param {object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. * @param {boolean} [options.minifyAliases=false] A flag that defines if aliases should be minified (mostly useful to avoid Postgres alias character limit of 64) * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind patameters in log. */ @@ -384,8 +384,8 @@ class Sequelize { * The table columns are defined by the object that is given as the second argument. Each key of the object represents a column * * @param {string} modelName The name of the model. The model will be stored in `sequelize.models` under this name - * @param {Object} attributes An object, where each attribute is a column of the table. See {@link Model.init} - * @param {Object} [options] These options are merged with the default define options provided to the Sequelize constructor and passed to Model.init() + * @param {object} attributes An object, where each attribute is a column of the table. See {@link Model.init} + * @param {object} [options] These options are merged with the default define options provided to the Sequelize constructor and passed to Model.init() * * @see * {@link Model.init} for a more comprehensive specification of the `options` and `attributes` objects. @@ -503,25 +503,25 @@ class Sequelize { * ``` * * @param {string} sql - * @param {Object} [options={}] Query options. + * @param {object} [options={}] Query options. * @param {boolean} [options.raw] If true, sequelize will not try to format the results of the query, or build an instance of a model from the result * @param {Transaction} [options.transaction=null] The transaction that the query should be executed under * @param {QueryTypes} [options.type='RAW'] The type of query you are executing. The query type affects how results are formatted before they are passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. * @param {boolean} [options.nest=false] If true, transforms objects with `.` separated property names into nested objects using [dottie.js](https://github.com/mickhansen/dottie.js). For example { 'user.username': 'john' } becomes { user: { username: 'john' }}. When `nest` is true, the query type is assumed to be `'SELECT'`, unless otherwise specified * @param {boolean} [options.plain=false] Sets the query type to `SELECT` and return a single row - * @param {Object|Array} [options.replacements] Either an object of named parameter replacements in the format `:param` or an array of unnamed replacements to replace `?` in your SQL. - * @param {Object|Array} [options.bind] Either an object of named bind parameter in the format `_param` or an array of unnamed bind parameter to replace `$1, $2, ...` in your SQL. + * @param {object|Array} [options.replacements] Either an object of named parameter replacements in the format `:param` or an array of unnamed replacements to replace `?` in your SQL. + * @param {object|Array} [options.bind] Either an object of named bind parameter in the format `_param` or an array of unnamed bind parameter to replace `$1, $2, ...` in your SQL. * @param {boolean} [options.useMaster=false] Force the query to use the write pool, regardless of the query type. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {new Model()} [options.instance] A sequelize instance used to build the return instance * @param {Model} [options.model] A sequelize model used to build the returned model instances (used to be called callee) - * @param {Object} [options.retry] Set of flags that control when a query is automatically retried. + * @param {object} [options.retry] Set of flags that control when a query is automatically retried. * @param {Array} [options.retry.match] Only retry a query if the error matches one of these strings. * @param {Integer} [options.retry.max] How many times a failing query is automatically retried. * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean} [options.supportsSearchPath] If false do not prepend the query with the search_path (Postgres only) * @param {boolean} [options.mapToModel=false] Map returned fields to model's fields if `options.model` or `options.instance` is present. Mapping will occur before building the model instance. - * @param {Object} [options.fieldMap] Map returned fields to arbitrary names for `SELECT` query type. + * @param {object} [options.fieldMap] Map returned fields to arbitrary names for `SELECT` query type. * * @returns {Promise} * @@ -650,8 +650,8 @@ class Sequelize { * Execute a query which would set an environment or user variable. The variables are set per connection, so this function needs a transaction. * Only works for MySQL. * - * @param {Object} variables Object with multiple variables. - * @param {Object} [options] query options. + * @param {object} variables Object with multiple variables. + * @param {object} [options] query options. * @param {Transaction} [options.transaction] The transaction that the query should be executed under * * @memberof Sequelize @@ -704,7 +704,7 @@ class Sequelize { * {@link Model.schema} * * @param {string} schema Name of the schema - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -719,7 +719,7 @@ class Sequelize { * **Note:** this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), * not a database table. In mysql and sqlite, this will show all tables. * - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -735,7 +735,7 @@ class Sequelize { * not a database table. In mysql and sqlite, this drop a table matching the schema name * * @param {string} schema Name of the schema - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -750,7 +750,7 @@ class Sequelize { * **Note:** this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), * not a database table. In mysql and sqlite, this is the equivalent of drop all tables. * - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -762,7 +762,7 @@ class Sequelize { /** * Sync all defined models to the DB. * - * @param {Object} [options={}] sync options + * @param {object} [options={}] sync options * @param {boolean} [options.force=false] If force is true, each Model will run `DROP TABLE IF EXISTS`, before it tries to create its own table * @param {RegExp} [options.match] Match a regex against the database name before syncing, a safety check for cases where force: true is used in tests but not live code * @param {boolean|Function} [options.logging=console.log] A function that logs sql queries, or false for no logging @@ -823,7 +823,7 @@ class Sequelize { * Truncate all tables defined through the sequelize models. * This is done by calling `Model.truncate()` on each model. * - * @param {Object} [options] The options passed to Model.destroy in addition to truncate + * @param {object} [options] The options passed to Model.destroy in addition to truncate * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * @returns {Promise} * @@ -854,7 +854,7 @@ class Sequelize { * @see * {@link Model.drop} for options * - * @param {Object} [options] The options passed to each call to Model.drop + * @param {object} [options] The options passed to each call to Model.drop * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -874,7 +874,7 @@ class Sequelize { /** * Test the connection by trying to authenticate. It runs `SELECT 1+1 AS result` query. * - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * * @returns {Promise} */ @@ -981,7 +981,7 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {...string|Object} args Each argument will be joined by AND + * @param {...string|object} args Each argument will be joined by AND * @since v2.0.0-dev3 * @memberof Sequelize * @@ -997,7 +997,7 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {...string|Object} args Each argument will be joined by OR + * @param {...string|object} args Each argument will be joined by OR * @since v2.0.0-dev3 * @memberof Sequelize * @@ -1013,7 +1013,7 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {string|Object} conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres/sqlite/mysql json syntax. + * @param {string|object} conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres/sqlite/mysql json syntax. * @param {string|number|boolean} [value] An optional value to compare against. Produces a string of the form " = ''". * @memberof Sequelize * @@ -1034,9 +1034,9 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {Object} attr The attribute, which can be either an attribute object from `Model.rawAttributes` or a sequelize object, for example an instance of `sequelize.fn`. For simple string attributes, use the POJO syntax + * @param {object} attr The attribute, which can be either an attribute object from `Model.rawAttributes` or a sequelize object, for example an instance of `sequelize.fn`. For simple string attributes, use the POJO syntax * @param {Symbol} [comparator='Op.eq'] operator - * @param {string|Object} logic The condition. Can be both a simply type, or a further condition (`or`, `and`, `.literal` etc.) + * @param {string|object} logic The condition. Can be both a simply type, or a further condition (`or`, `and`, `.literal` etc.) * @since v2.0.0-dev3 */ static where(attr, comparator, logic) { @@ -1054,7 +1054,7 @@ class Sequelize { * .catch(() => transaction.rollback()); * }) * - * @param {Object} [options] Transaction options + * @param {object} [options] Transaction options * @param {string} [options.type='DEFERRED'] See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. See `Sequelize.Deferrable`. PostgreSQL Only @@ -1281,7 +1281,7 @@ Sequelize.prototype.Association = Sequelize.Association = Association; /** * Provide alternative version of `inflection` module to be used by `Utils.pluralize` etc. - * @param {Object} _inflection - `inflection` module + * @param {object} _inflection - `inflection` module */ Sequelize.useInflection = Utils.useInflection; diff --git a/lib/transaction.js b/lib/transaction.js index bd0b8f3fefa8..56d72ab513d6 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -15,7 +15,7 @@ class Transaction { * Creates a new transaction instance * * @param {Sequelize} sequelize A configured sequelize Instance - * @param {Object} options An object with options + * @param {object} options An object with options * @param {string} [options.type] Sets the type of the transaction. Sqlite only * @param {string} [options.isolationLevel] Sets the isolation level of the transaction. * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. PostgreSQL only @@ -263,7 +263,7 @@ class Transaction { * }); * # The query will now return any rows that aren't locked by another transaction * - * @returns {Object} + * @returns {object} * @property UPDATE * @property SHARE * @property KEY_SHARE Postgres 9.3+ only diff --git a/lib/utils.js b/lib/utils.js index a8f8e09352cd..e5442b6e0bbe 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -381,8 +381,8 @@ exports.removeTicks = removeTicks; * address.coordinates.longitude: 12.5964313 * } * - * @param {Object} value an Object - * @returns {Object} a flattened object + * @param {object} value an Object + * @returns {object} a flattened object * @private */ function flattenObjectDeep(value) { @@ -489,7 +489,7 @@ exports.Where = Where; /** * getOperators * - * @param {Object} obj + * @param {object} obj * @returns {Array} All operators properties of obj * @private */ @@ -501,7 +501,7 @@ exports.getOperators = getOperators; /** * getComplexKeys * - * @param {Object} obj + * @param {object} obj * @returns {Array} All keys including operators * @private */ @@ -513,7 +513,7 @@ exports.getComplexKeys = getComplexKeys; /** * getComplexSize * - * @param {Object|Array} obj + * @param {object|Array} obj * @returns {number} Length of object properties including operators if obj is array returns its length * @private */ @@ -525,7 +525,7 @@ exports.getComplexSize = getComplexSize; /** * Returns true if a where clause is empty, even with Symbols * - * @param {Object} obj + * @param {object} obj * @returns {boolean} * @private */ @@ -550,7 +550,7 @@ exports.generateEnumName = generateEnumName; /** * Returns an new Object which keys are camelized * - * @param {Object} obj + * @param {object} obj * @returns {string} * @private */ @@ -571,9 +571,9 @@ exports.camelizeObjectKeys = camelizeObjectKeys; * * **Note:** This method mutates `object`. * - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. + * @param {object} object The destination object. + * @param {...object} [sources] The source objects. + * @returns {object} Returns `object`. * @private */ function defaults(object, ...sources) { @@ -603,12 +603,12 @@ exports.defaults = defaults; /** * - * @param {Object} index + * @param {object} index * @param {Array} index.fields * @param {string} [index.name] - * @param {string|Object} tableName + * @param {string|object} tableName * - * @returns {Object} + * @returns {object} * @private */ function nameIndex(index, tableName) { diff --git a/package.json b/package.json index e191e16703b5..ac0ae3ab9474 100644 --- a/package.json +++ b/package.json @@ -30,13 +30,13 @@ ], "license": "MIT", "dependencies": { - "bluebird": "^3.5.0", + "bluebird": "^3.7.0", "debug": "^4.1.1", "dottie": "^2.0.0", "inflection": "1.12.0", "lodash": "^4.17.15", "moment": "^2.24.0", - "moment-timezone": "^0.5.21", + "moment-timezone": "^0.5.26", "retry-as-promised": "^3.2.0", "semver": "^6.3.0", "sequelize-pool": "^2.3.0", @@ -50,38 +50,38 @@ "@commitlint/config-angular": "^8.2.0", "@types/bluebird": "^3.5.26", "@types/node": "^12.7.8", - "@types/validator": "^10.11.0", + "@types/validator": "^10.11.3", "big-integer": "^1.6.45", "chai": "^4.x", "chai-as-promised": "^7.x", "chai-datetime": "^1.x", "chai-spies": "^1.x", - "cross-env": "^5.2.1", - "env-cmd": "^8.0.2", + "cross-env": "^6.0.3", + "env-cmd": "^10.0.1", "esdoc": "^1.1.0", "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", "eslint": "^6.4.0", - "eslint-plugin-jsdoc": "^4.1.1", - "eslint-plugin-mocha": "^5.2.1", + "eslint-plugin-jsdoc": "^8.7.0", + "eslint-plugin-mocha": "^6.1.1", "fs-jetpack": "^2.2.2", - "husky": "^1.3.1", + "husky": "^3.0.8", "js-combinatorics": "^0.5.4", "lcov-result-merger": "^3.0.0", - "lint-staged": "^8.1.5", + "lint-staged": "^9.4.2", "mariadb": "^2.1.1", "markdownlint-cli": "^0.18.0", - "mocha": "^6.1.4", - "mysql2": "^1.6.5", + "mocha": "^6.2.1", + "mysql2": "^1.7.0", "nyc": "^14.1.1", - "pg": "^7.8.1", + "pg": "^7.12.1", "pg-hstore": "^2.x", - "pg-types": "^2.0.0", - "rimraf": "^2.6.3", - "semantic-release": "^15.13.16", + "pg-types": "^2.2.0", + "rimraf": "^3.0.0", + "semantic-release": "^15.13.24", "sinon": "^7.5.0", "sinon-chai": "^3.3.0", - "sqlite3": "^4.0.6", + "sqlite3": "^4.1.0", "tedious": "6.0.0", "typescript": "^3.6.3" }, diff --git a/test/integration/model/count.test.js b/test/integration/model/count.test.js index 0d7a6d0f6387..2cae757b4948 100644 --- a/test/integration/model/count.test.js +++ b/test/integration/model/count.test.js @@ -15,10 +15,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Project = this.sequelize.define('Project', { name: DataTypes.STRING }); - + this.User.hasMany(this.Project); this.Project.belongsTo(this.User); - + return this.sequelize.sync({ force: true }); }); @@ -53,7 +53,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { { username: 'bar' }, { username: 'valak', - createdAt: (new Date()).setFullYear(2015) + createdAt: new Date().setFullYear(2015) } ]).then(() => this.User.count({ attributes: ['createdAt'], diff --git a/test/unit/sql/create-table.test.js b/test/unit/sql/create-table.test.js index d9a56adb0ca0..86af72afde72 100644 --- a/test/unit/sql/create-table.test.js +++ b/test/unit/sql/create-table.test.js @@ -4,8 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, - _ = require('lodash'); + sql = current.dialect.QueryGenerator; describe(Support.getTestDialectTeaser('SQL'), () => { describe('createTable', () => { From 46c8717976e775de09e84c909698dff93a2653b2 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Wed, 12 Jun 2019 09:57:05 -0700 Subject: [PATCH 016/414] refactor(hooks): remove method aliases for hooks (#10880) * refactor(hooks): remove method aliases for hooks BREAKING CHANGE: In order to streamline API: - All method style add hook functions have been removed in favor of a composition based approach. - Hook names have been removed, you can add and remove them by function reference instead which was supported before. - Another notable change that `this` inside of hooks no longer refers to the the the hook subject, it should not be used. This affects `Model`, `Sequelize` and `Transaction`. Before: `MyModel.beforeCreate(...)` After: `MyModel.hooks.add('beforeCreate', ...)` Before: `MyModel.addHook('beforeCreate', ...)` After: `MyModel.hooks.add('beforeCreate', ...)` Before: `MyModel.removeHook('beforeCreate', ...)` After: `MyModel.hooks.remove('beforeCreate', ...)` Before: `transaction.afterCommit(...)` After: `transaction.hooks.add('afterCommit', ...)` Before: ```js MyModel.addHook('beforeCreate', 'named', fn); MyModel.removeHook('beforeCreate', 'named'); ``` After: ```js MyModel.hooks.add('beforeCreate', fn); MyModel.hooks.remove('beforeCreate', fn); ``` Before: `MyModel.addHook('beforeCreate', function() { this.someMethod(); });` After: `MyModel.hooks.add('beforeCreate', () => { MyModel.someMethod(); });` --- .travis.yml | 2 +- docs/manual/hooks.md | 47 +- docs/manual/transactions.md | 8 +- docs/upgrade-to-v6.md | 47 +- lib/associations/mixin.js | 12 +- lib/dialects/abstract/connection-manager.js | 8 +- lib/hooks.js | 458 ++------------- lib/instance-validator.js | 8 +- lib/model.js | 68 +-- lib/sequelize.js | 27 +- lib/transaction.js | 23 +- test/integration/hooks/associations.test.js | 156 +++--- test/integration/hooks/bulkOperation.test.js | 98 ++-- test/integration/hooks/count.test.js | 6 +- test/integration/hooks/create.test.js | 38 +- test/integration/hooks/destroy.test.js | 14 +- test/integration/hooks/find.test.js | 26 +- test/integration/hooks/hooks.test.js | 86 ++- test/integration/hooks/restore.test.js | 12 +- .../hooks/updateAttributes.test.js | 34 +- test/integration/hooks/upsert.test.js | 14 +- test/integration/hooks/validate.test.js | 16 +- test/integration/instance/save.test.js | 8 +- test/integration/instance/update.test.js | 8 +- test/integration/instance/values.test.js | 2 +- test/integration/model.test.js | 2 +- test/integration/model/create.test.js | 2 +- test/integration/support.js | 4 +- test/integration/transaction.test.js | 71 +-- .../unit/associations/belongs-to-many.test.js | 8 +- test/unit/associations/belongs-to.test.js | 8 +- test/unit/associations/has-many.test.js | 8 +- test/unit/associations/has-one.test.js | 8 +- test/unit/connection-manager.test.js | 8 +- test/unit/hooks.test.js | 144 +++-- test/unit/instance-validator.test.js | 16 +- types/lib/hooks.d.ts | 75 +-- types/lib/model.d.ts | 314 +---------- types/lib/sequelize.d.ts | 527 +----------------- types/lib/transaction.d.ts | 13 +- types/test/connection.ts | 2 +- types/test/hooks.ts | 24 +- types/test/models/User.ts | 18 +- types/test/sequelize.ts | 15 +- types/test/transaction.ts | 2 +- 45 files changed, 617 insertions(+), 1878 deletions(-) diff --git a/.travis.yml b/.travis.yml index 388d836a4043..3b774b9b2c57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,7 @@ script: jobs: include: - stage: lint - node_js: '8' + node_js: '10' script: - npm run lint - npm run lint-docs diff --git a/docs/manual/hooks.md b/docs/manual/hooks.md index 22521395eedb..6b2a30cd3675 100644 --- a/docs/manual/hooks.md +++ b/docs/manual/hooks.md @@ -70,25 +70,14 @@ User.init({ sequelize }); -// Method 2 via the .addHook() method -User.addHook('beforeValidate', (user, options) => { +// Method 2 via the .hooks.add() method +User.hooks.add('beforeValidate', (user, options) => { user.mood = 'happy'; }); -User.addHook('afterValidate', 'someCustomName', (user, options) => { +User.hooks.add('afterValidate', (user, options) => { return Promise.reject(new Error("I'm afraid I can't let you do that!")); }); - -// Method 3 via the direct method -User.beforeCreate((user, options) => { - return hashPassword(user.password).then(hashedPw => { - user.password = hashedPw; - }); -}); - -User.afterValidate('myHookAfter', (user, options) => { - user.username = 'Toni'; -}); ``` ## Removing hooks @@ -101,14 +90,16 @@ Book.init({ title: DataTypes.STRING }, { sequelize }); -Book.addHook('afterCreate', 'notifyUsers', (book, options) => { - // ... -}); +function notifyUsers(book, options) { + +} + +Book.hooks.add('afterCreate', notifyUsers); -Book.removeHook('afterCreate', 'notifyUsers'); +Book.hooks.remove('afterCreate', notifyUsers); ``` -You can have many hooks with same name. Calling `.removeHook()` will remove all of them. +You can have many hooks with same name. Calling `.hooks.remove(()` will remove all of them. ## Global / universal hooks @@ -147,10 +138,10 @@ User.create() // Runs the global hook Project.create() // Runs its own hook (because the global hook is overwritten) ``` -### Permanent Hooks (Sequelize.addHook) +### Permanent Hooks (Sequelize.hooks.add) ```js -sequelize.addHook('beforeCreate', () => { +sequelize.hooks.add('beforeCreate', () => { // Do stuff }); ``` @@ -202,7 +193,7 @@ These hooks can be useful if you need to asynchronously obtain database credenti For example, we can asynchronously obtain a database password from a rotating token store, and mutate Sequelize's configuration object with the new credentials: ```js -sequelize.beforeConnect((config) => { +sequelize.hooks.add('beforeConnect', (config) => { return getAuthToken() .then((token) => { config.password = token; @@ -225,7 +216,7 @@ afterCreate / afterUpdate / afterSave / afterDestroy ```js // ...define ... -User.beforeCreate(user => { +User.hooks.add('beforeCreate', user => { if (user.accessLevel > 10 && user.username !== "Boss") { throw new Error("You can't grant this user an access level above 10!") } @@ -277,7 +268,7 @@ The `options` argument of hook method would be the second argument provided to t cloned and extended version. ```js -Model.beforeBulkCreate((records, {fields}) => { +Model.hooks.add('beforeBulkCreate', (records, {fields}) => { // records = the first argument sent to .bulkCreate // fields = one of the second argument fields sent to .bulkCreate }) @@ -288,14 +279,14 @@ Model.bulkCreate([ ], {fields: ['username']} // options parameter ) -Model.beforeBulkUpdate(({attributes, where}) => { +Model.hooks.add('beforeBulkUpdate', ({attributes, where}) => { // where - in one of the fields of the clone of second argument sent to .update // attributes - is one of the fields that the clone of second argument of .update would be extended with }) Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/) -Model.beforeBulkDestroy(({where, individualHooks}) => { +Model.hooks.add('beforeBulkDestroy', ({where, individualHooks}) => { // individualHooks - default of overridden value of extended clone of second argument sent to Model.destroy // where - in one of the fields of the clone of second argument sent to Model.destroy }) @@ -314,7 +305,7 @@ Users.bulkCreate([ updateOnDuplicate: ['isMember'] }); -User.beforeBulkCreate((users, options) => { +User.hooks.add('beforeBulkCreate', (users, options) => { for (const user of users) { if (user.isMember) { user.memberSince = new Date(); @@ -368,7 +359,7 @@ Note that many model operations in Sequelize allow you to specify a transaction ```js // Here we use the promise-style of async hooks rather than // the callback. -User.addHook('afterCreate', (user, options) => { +User.hooks.add('afterCreate', (user, options) => { // 'transaction' will be available in options.transaction // This operation will be part of the same transaction as the diff --git a/docs/manual/transactions.md b/docs/manual/transactions.md index ee01b7c7c685..fab97d2709d5 100644 --- a/docs/manual/transactions.md +++ b/docs/manual/transactions.md @@ -143,13 +143,13 @@ An `afterCommit` hook can be added to both managed and unmanaged transaction obj ```js sequelize.transaction(t => { - t.afterCommit((transaction) => { + t.hooks.add('afterCommit', (transaction) => { // Your logic }); }); sequelize.transaction().then(t => { - t.afterCommit((transaction) => { + t.hooks.add('afterCommit', (transaction) => { // Your logic }); @@ -168,11 +168,11 @@ You can use the `afterCommit` hook in conjunction with model hooks to know when of a transaction ```js -model.afterSave((instance, options) => { +model.hooks.add('afterSave', (instance, options) => { if (options.transaction) { // Save done within a transaction, wait until transaction is committed to // notify listeners the instance has been saved - options.transaction.afterCommit(() => /* Notify */) + options.transaction.hooks.add('afterCommit', () => /* Notify */) return; } // Save done outside a transaction, safe for callers to fetch the updated model diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md index 728b6f6a1d05..df85a81ce0bc 100644 --- a/docs/upgrade-to-v6.md +++ b/docs/upgrade-to-v6.md @@ -6,7 +6,7 @@ Sequelize v6 is the next major release after v4 ### Support for Node 8 and up -Sequelize v6 will only support Node 8 and up +Sequelize v6 will only support Node 8 and up. ### Removed support for `operatorAliases` @@ -57,3 +57,48 @@ db.transaction(async transaction => { }, { transaction }); }); ``` + +### Refactored hooks + +In order to streamline API: + +- All method style add hook functions have been removed in favor of a composition based approach. +- Hook names have been removed, you can add and remove them by function reference instead which was supported before. +- Another notable change that `this` inside of hooks no longer refers to the the the hook subject, it should not be used. + +This affects `Model`, `Sequelize` and `Transaction`. + +#### Composition + +Before: `MyModel.beforeCreate(...)` +After: `MyModel.hooks.add('beforeCreate', ...)` + +Before: `MyModel.addHook('beforeCreate', ...)` +After: `MyModel.hooks.add('beforeCreate', ...)` + +Before: `MyModel.removeHook('beforeCreate', ...)` +After: `MyModel.hooks.remove('beforeCreate', ...)` + +Before: `transaction.afterCommit(...)` +After: `transaction.hooks.add('afterCommit', ...)` + +#### Names + +Before: + +```js +MyModel.addHook('beforeCreate', 'named', fn); +MyModel.removeHook('beforeCreate', 'named'); +``` + +After: + +```js +MyModel.hooks.add('beforeCreate', fn); +MyModel.hooks.remove('beforeCreate', fn); +``` + +#### Scope + +Before: `MyModel.addHook('beforeCreate', function() { this.someMethod(); });` +After: `MyModel.hooks.add('beforeCreate', () => { MyModel.someMethod(); });` diff --git a/lib/associations/mixin.js b/lib/associations/mixin.js index 062de0edea92..1901c7af9b5a 100644 --- a/lib/associations/mixin.js +++ b/lib/associations/mixin.js @@ -27,7 +27,7 @@ const Mixin = { options = Object.assign(options, _.omit(source.options, ['hooks'])); if (options.useHooks) { - this.runHooks('beforeAssociate', { source, target, type: HasMany }, options); + this.hooks.run('beforeAssociate', { source, target, type: HasMany }, options); } // the id is in the foreign table or in a connecting table @@ -38,7 +38,7 @@ const Mixin = { association.mixin(source.prototype); if (options.useHooks) { - this.runHooks('afterAssociate', { source, target, type: HasMany, association }, options); + this.hooks.run('afterAssociate', { source, target, type: HasMany, association }, options); } return association; @@ -58,7 +58,7 @@ const Mixin = { options = Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope'])); if (options.useHooks) { - this.runHooks('beforeAssociate', { source, target, type: BelongsToMany }, options); + this.hooks.run('beforeAssociate', { source, target, type: BelongsToMany }, options); } // the id is in the foreign table or in a connecting table const association = new BelongsToMany(source, target, options); @@ -68,7 +68,7 @@ const Mixin = { association.mixin(source.prototype); if (options.useHooks) { - this.runHooks('afterAssociate', { source, target, type: BelongsToMany, association }, options); + this.hooks.run('afterAssociate', { source, target, type: BelongsToMany, association }, options); } return association; @@ -99,7 +99,7 @@ function singleLinked(Type) { options.useHooks = options.hooks; if (options.useHooks) { - source.runHooks('beforeAssociate', { source, target, type: Type }, options); + source.hooks.run('beforeAssociate', { source, target, type: Type }, options); } // the id is in the foreign table const association = new Type(source, target, Object.assign(options, source.options)); @@ -109,7 +109,7 @@ function singleLinked(Type) { association.mixin(source.prototype); if (options.useHooks) { - source.runHooks('afterAssociate', { source, target, type: Type, association }, options); + source.hooks.run('afterAssociate', { source, target, type: Type, association }, options); } return association; diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index c5bd5925fdbb..1f27005937ed 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -325,9 +325,9 @@ class ConnectionManager { * @returns {Promise} */ _connect(config) { - return this.sequelize.runHooks('beforeConnect', config) + return this.sequelize.hooks.run('beforeConnect', config) .then(() => this.dialect.connectionManager.connect(config)) - .then(connection => this.sequelize.runHooks('afterConnect', connection, config).return(connection)); + .then(connection => this.sequelize.hooks.run('afterConnect', connection, config).return(connection)); } /** @@ -338,9 +338,9 @@ class ConnectionManager { * @returns {Promise} */ _disconnect(connection) { - return this.sequelize.runHooks('beforeDisconnect', connection) + return this.sequelize.hooks.run('beforeDisconnect', connection) .then(() => this.dialect.connectionManager.disconnect(connection)) - .then(() => this.sequelize.runHooks('afterDisconnect', connection)); + .then(() => this.sequelize.hooks.run('afterDisconnect', connection)); } /** diff --git a/lib/hooks.js b/lib/hooks.js index 32a35c34f1e3..6ad3ff7fd6b5 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -46,10 +46,11 @@ const hookTypes = { afterDisconnect: { params: 1, noModel: true }, beforeSync: { params: 1 }, afterSync: { params: 1 }, - beforeBulkSync: { params: 1 }, - afterBulkSync: { params: 1 }, + beforeBulkSync: { params: 1, noModel: true }, + afterBulkSync: { params: 1, noModel: true }, beforeQuery: { params: 2 }, - afterQuery: { params: 2 } + afterQuery: { params: 2 }, + afterCommit: { params: 1 } }; exports.hooks = hookTypes; @@ -67,39 +68,40 @@ const getProxiedHooks = hookType => : [hookType] ; -function getHooks(hooked, hookType) { - return (hooked.options.hooks || {})[hookType] || []; -} - -const Hooks = { +class Hooks { + _getHooks(hookType) { + return this.hooks[hookType] || []; + } /** * Process user supplied hooks definition * * @param {object} hooks hooks definition - * + * @param {Hooks} parent * @private - * @memberof Sequelize - * @memberof Sequelize.Model */ - _setupHooks(hooks = {}) { - this.options.hooks = {}; + constructor(hooks, parent) { + this.hooks = {}; + this.parent = parent; + if (!hooks) { + return; + } _.map(hooks, (hooksArray, hookName) => { if (!Array.isArray(hooksArray)) hooksArray = [hooksArray]; - hooksArray.forEach(hookFn => this.addHook(hookName, hookFn)); + hooksArray.forEach(hookFn => this.add(hookName, hookFn)); }); - }, + } - runHooks(hooks, ...hookArgs) { - if (!hooks) throw new Error('runHooks requires at least 1 argument'); + run(hooks, ...hookArgs) { + if (!hooks) throw new Error('hooks.run requires at least 1 argument'); let hookType; if (typeof hooks === 'string') { hookType = hooks; - hooks = getHooks(this, hookType); + hooks = this._getHooks(hookType); - if (this.sequelize) { - hooks = hooks.concat(getHooks(this.sequelize, hookType)); + if (this.parent) { + hooks = hooks.concat(this.parent._getHooks(hookType)); } } @@ -109,70 +111,51 @@ const Hooks = { // synchronous hooks if (hookTypes[hookType] && hookTypes[hookType].sync) { - for (let hook of hooks) { - if (typeof hook === 'object') { - hook = hook.fn; - } - + for (const hook of hooks) { debug(`running hook(sync) ${hookType}`); - hook.apply(this, hookArgs); + hook.fn(...hookArgs); } return; } // asynchronous hooks (default) return Promise.each(hooks, hook => { - if (typeof hook === 'object') { - hook = hook.fn; - } - debug(`running hook ${hookType}`); - return hook.apply(this, hookArgs); + return hook.fn(...hookArgs); }).return(); - }, + } /** * Add a hook to the model * * @param {string} hookType hook name @see {@link hookTypes} - * @param {string|Function} [name] Provide a name for the hook function. It can be used to remove the hook later or to order hooks based on some sort of priority system in the future. * @param {Function} fn The hook function - * - * @memberof Sequelize - * @memberof Sequelize.Model */ - addHook(hookType, name, fn) { - if (typeof name === 'function') { - fn = name; - name = null; + add(hookType, fn) { + if (hookTypes[hookType] && hookTypes[hookType].noModel && this.parent) { + throw new Error(`${hookType} is only applicable on a sequelize instance or static`); } - debug(`adding hook ${hookType}`); // check for proxies, add them too hookType = getProxiedHooks(hookType); hookType.forEach(type => { - const hooks = getHooks(this, type); - hooks.push(name ? { name, fn } : fn); - this.options.hooks[type] = hooks; + const hooks = this._getHooks(type); + hooks.push({ fn }); + this.hooks[type] = hooks; }); return this; - }, + } /** * Remove hook from the model * * @param {string} hookType @see {@link hookTypes} - * @param {string|Function} name name of hook or function reference which was attached - * - * @memberof Sequelize - * @memberof Sequelize.Model + * @param {Function} fn name of hook or function reference which was attached */ - removeHook(hookType, name) { - const isReference = typeof name === 'function' ? true : false; - - if (!this.hasHook(hookType)) { + remove(hookType, fn) { + if (!this.has(hookType)) { return this; } @@ -182,376 +165,27 @@ const Hooks = { hookType = getProxiedHooks(hookType); for (const type of hookType) { - this.options.hooks[type] = this.options.hooks[type].filter(hook => { - if (isReference && typeof hook === 'function') { - return hook !== name; // check if same method - } - if (!isReference && typeof hook === 'object') { - return hook.name !== name; - } - return true; - }); + this.hooks[type] = this.hooks[type].filter(({ fn: hookFn }) => fn !== hookFn); } return this; - }, + } /** * Check whether the mode has any hooks of this type * * @param {string} hookType @see {@link hookTypes} - * - * @alias hasHooks - * - * @memberof Sequelize - * @memberof Sequelize.Model */ - hasHook(hookType) { - return this.options.hooks[hookType] && !!this.options.hooks[hookType].length; + has(hookType) { + return this.hooks[hookType] && !!this.hooks[hookType].length; } -}; -Hooks.hasHooks = Hooks.hasHook; - - -function applyTo(target, isModel = false) { - _.mixin(target, Hooks); - for (const hook of Object.keys(hookTypes)) { - if (isModel && hookTypes[hook].noModel) { - continue; - } - target[hook] = function(name, callback) { - return this.addHook(hook, name, callback); - }; + /** + * Removes all hooks + */ + removeAll() { + this.hooks = {}; } } -exports.applyTo = applyTo; -/** - * A hook that is run before validation - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name beforeValidate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after validation - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name afterValidate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run when validation fails - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options, error. Error is the - * SequelizeValidationError. If the callback throws an error, it will replace the original validation error. - * @name validationFailed - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before creating a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after creating a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name afterCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeSave - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before upserting - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeUpsert - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after upserting - * @param {string} name - * @param {Function} fn A callback function that is called with the result of upsert(), options - * @name afterUpsert - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name afterSave - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before destroying a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name beforeDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after destroying a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name afterDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before restoring a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name beforeRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after restoring a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * - * @name afterRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before updating a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name beforeUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after updating a single instance - * @param {string} name - * @param {Function} fn A callback function that is called with instance, options - * @name afterUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before creating instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with instances, options - * @name beforeBulkCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after creating instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with instances, options - * @name afterBulkCreate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before destroying instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name beforeBulkDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after destroying instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name afterBulkDestroy - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before restoring instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name beforeBulkRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after restoring instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with options - * - * @name afterBulkRestore - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before updating instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeBulkUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after updating instances in bulk - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name afterBulkUpdate - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a find (select) query - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeFind - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeFindAfterExpandIncludeAll - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a find (select) query, after all option parsing is complete - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeFindAfterOptions - * @memberof Sequelize.Model - */ - -/** - * A hook that is run after a find (select) query - * @param {string} name - * @param {Function} fn A callback function that is called with instance(s), options - * @name afterFind - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a count query - * @param {string} name - * @param {Function} fn A callback function that is called with options - * @name beforeCount - * @memberof Sequelize.Model - */ - -/** - * A hook that is run before a define call - * @param {string} name - * @param {Function} fn A callback function that is called with attributes, options - * @name beforeDefine - * @memberof Sequelize - */ - -/** - * A hook that is run after a define call - * @param {string} name - * @param {Function} fn A callback function that is called with factory - * @name afterDefine - * @memberof Sequelize - */ - -/** - * A hook that is run before Sequelize() call - * @param {string} name - * @param {Function} fn A callback function that is called with config, options - * @name beforeInit - * @memberof Sequelize - */ - -/** - * A hook that is run after Sequelize() call - * @param {string} name - * @param {Function} fn A callback function that is called with sequelize - * @name afterInit - * @memberof Sequelize - */ - -/** - * A hook that is run before a connection is created - * @param {string} name - * @param {Function} fn A callback function that is called with config passed to connection - * @name beforeConnect - * @memberof Sequelize - */ - -/** - * A hook that is run after a connection is created - * @param {string} name - * @param {Function} fn A callback function that is called with the connection object and the config passed to connection - * @name afterConnect - * @memberof Sequelize - */ - -/** - * A hook that is run before a connection is disconnected - * @param {string} name - * @param {Function} fn A callback function that is called with the connection object - * @name beforeDisconnect - * @memberof Sequelize - */ - -/** - * A hook that is run after a connection is disconnected - * @param {string} name - * @param {Function} fn A callback function that is called with the connection object - * @name afterDisconnect - * @memberof Sequelize - */ - -/** - * A hook that is run before Model.sync call - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to Model.sync - * @name beforeSync - * @memberof Sequelize - */ - -/** - * A hook that is run after Model.sync call - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to Model.sync - * @name afterSync - * @memberof Sequelize - */ - -/** - * A hook that is run before sequelize.sync call - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to sequelize.sync - * @name beforeBulkSync - * @memberof Sequelize - */ - -/** - * A hook that is run after sequelize.sync call - * @param {string} name - * @param {Function} fn A callback function that is called with options passed to sequelize.sync - * @name afterBulkSync - * @memberof Sequelize - */ +exports.Hooks = Hooks; diff --git a/lib/instance-validator.js b/lib/instance-validator.js index 8b497d8eee4e..01c682dfabd1 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -102,14 +102,14 @@ class InstanceValidator { * @private */ _validateAndRunHooks() { - const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor); - return runHooks('beforeValidate', this.modelInstance, this.options) + const { hooks } = this.modelInstance.constructor; + return hooks.run('beforeValidate', this.modelInstance, this.options) .then(() => this._validate() - .catch(error => runHooks('validationFailed', this.modelInstance, this.options, error) + .catch(error => hooks.run('validationFailed', this.modelInstance, this.options, error) .then(newError => { throw newError || error; })) ) - .then(() => runHooks('afterValidate', this.modelInstance, this.options)) + .then(() => hooks.run('afterValidate', this.modelInstance, this.options)) .return(this.modelInstance); } diff --git a/lib/model.js b/lib/model.js index 8de3659bf12f..d8005b98b6fd 100644 --- a/lib/model.js +++ b/lib/model.js @@ -15,7 +15,7 @@ const Promise = require('./promise'); const Association = require('./associations/base'); const HasMany = require('./associations/has-many'); const DataTypes = require('./data-types'); -const Hooks = require('./hooks'); +const { Hooks } = require('./hooks'); const associationsMixin = require('./associations/mixin'); const Op = require('./operators'); const { noDoubleNestedGroup } = require('./utils/deprecations'); @@ -945,7 +945,7 @@ class Model { schema: globalOptions.schema }, options); - this.sequelize.runHooks('beforeDefine', attributes, options); + this.sequelize.hooks.run('beforeDefine', attributes, options); if (options.modelName !== this.name) { Object.defineProperty(this, 'name', { value: options.modelName }); @@ -973,7 +973,6 @@ class Model { } this.associations = {}; - this._setupHooks(options.hooks); this.underscored = this.options.underscored; @@ -1056,7 +1055,9 @@ class Model { this._scopeNames = ['defaultScope']; this.sequelize.modelManager.addModel(this); - this.sequelize.runHooks('afterDefine', this); + this.sequelize.hooks.run('afterDefine', this); + + this.hooks = new Hooks(this.options.hooks, this.sequelize.hooks); return this; } @@ -1281,7 +1282,7 @@ class Model { return Promise.try(() => { if (options.hooks) { - return this.runHooks('beforeSync', options); + return this.hooks.run('beforeSync', options); } }).then(() => { if (options.force) { @@ -1368,7 +1369,7 @@ class Model { )); }).then(() => { if (options.hooks) { - return this.runHooks('afterSync', options); + return this.hooks.run('afterSync', options); } }).return(this); } @@ -1706,7 +1707,7 @@ class Model { this._injectScope(options); if (options.hooks) { - return this.runHooks('beforeFind', options); + return this.hooks.run('beforeFind', options); } }).then(() => { this._conformIncludes(options, this); @@ -1714,7 +1715,7 @@ class Model { this._expandIncludeAll(options); if (options.hooks) { - return this.runHooks('beforeFindAfterExpandIncludeAll', options); + return this.hooks.run('beforeFindAfterExpandIncludeAll', options); } }).then(() => { options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); @@ -1749,14 +1750,14 @@ class Model { options = this._paranoidClause(this, options); if (options.hooks) { - return this.runHooks('beforeFindAfterOptions', options); + return this.hooks.run('beforeFindAfterOptions', options); } }).then(() => { const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) }); return this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); }).tap(results => { if (options.hooks) { - return this.runHooks('afterFind', results, options); + return this.hooks.run('afterFind', results, options); } }).then(results => { @@ -2026,7 +2027,7 @@ class Model { raw: true }; if (options.hooks) { - return this.runHooks('beforeCount', options); + return this.hooks.run('beforeCount', options); } }).then(() => { let col = options.col || '*'; @@ -2470,7 +2471,7 @@ class Model { return Promise.try(() => { if (options.hooks) { - return this.runHooks('beforeUpsert', values, options); + return this.hooks.run('beforeUpsert', values, options); } }) .then(() => { @@ -2485,7 +2486,7 @@ class Model { }) .tap(result => { if (options.hooks) { - return this.runHooks('afterUpsert', result, options); + return this.hooks.run('afterUpsert', result, options); } }); }); @@ -2579,7 +2580,7 @@ class Model { return Promise.try(() => { // Run before hook if (options.hooks) { - return model.runHooks('beforeBulkCreate', instances, options); + return model.hooks.run('beforeBulkCreate', instances, options); } }).then(() => { // Validate @@ -2822,7 +2823,7 @@ class Model { // Run after hook if (options.hooks) { - return model.runHooks('afterBulkCreate', instances, options); + return model.hooks.run('afterBulkCreate', instances, options); } }).then(() => instances); }; @@ -2901,13 +2902,13 @@ class Model { return Promise.try(() => { // Run before hook if (options.hooks) { - return this.runHooks('beforeBulkDestroy', options); + return this.hooks.run('beforeBulkDestroy', options); } }).then(() => { // Get daos and run beforeDestroy hook on each record individually if (options.individualHooks) { return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }) - .map(instance => this.runHooks('beforeDestroy', instance, options).then(() => instance)) + .map(instance => this.hooks.run('beforeDestroy', instance, options).then(() => instance)) .then(_instances => { instances = _instances; }); @@ -2933,12 +2934,12 @@ class Model { }).tap(() => { // Run afterDestroy hook on each record individually if (options.individualHooks) { - return Promise.map(instances, instance => this.runHooks('afterDestroy', instance, options)); + return Promise.map(instances, instance => this.hooks.run('afterDestroy', instance, options)); } }).tap(() => { // Run after hook if (options.hooks) { - return this.runHooks('afterBulkDestroy', options); + return this.hooks.run('afterBulkDestroy', options); } }); } @@ -2975,13 +2976,13 @@ class Model { return Promise.try(() => { // Run before hook if (options.hooks) { - return this.runHooks('beforeBulkRestore', options); + return this.hooks.run('beforeBulkRestore', options); } }).then(() => { // Get daos and run beforeRestore hook on each record individually if (options.individualHooks) { return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }) - .map(instance => this.runHooks('beforeRestore', instance, options).then(() => instance)) + .map(instance => this.hooks.run('beforeRestore', instance, options).then(() => instance)) .then(_instances => { instances = _instances; }); @@ -2999,12 +3000,12 @@ class Model { }).tap(() => { // Run afterDestroy hook on each record individually if (options.individualHooks) { - return Promise.map(instances, instance => this.runHooks('afterRestore', instance, options)); + return Promise.map(instances, instance => this.hooks.run('afterRestore', instance, options)); } }).tap(() => { // Run after hook if (options.hooks) { - return this.runHooks('afterBulkRestore', options); + return this.hooks.run('afterBulkRestore', options); } }); } @@ -3102,7 +3103,7 @@ class Model { // Run before hook if (options.hooks) { options.attributes = values; - return this.runHooks('beforeBulkUpdate', options).then(() => { + return this.hooks.run('beforeBulkUpdate', options).then(() => { values = options.attributes; delete options.attributes; }); @@ -3141,7 +3142,7 @@ class Model { }); // Run beforeUpdate hook - return this.runHooks('beforeUpdate', instance, options).then(() => { + return this.hooks.run('beforeUpdate', instance, options).then(() => { if (!different) { const thisChangedValues = {}; _.forIn(instance.dataValues, (newValue, attr) => { @@ -3217,7 +3218,7 @@ class Model { }).tap(result => { if (options.individualHooks) { return Promise.map(instances, instance => { - return this.runHooks('afterUpdate', instance, options); + return this.hooks.run('afterUpdate', instance, options); }).then(() => { result[1] = instances; }); @@ -3226,7 +3227,7 @@ class Model { // Run after hook if (options.hooks) { options.attributes = values; - return this.runHooks('afterBulkUpdate', options).then(() => { + return this.hooks.run('afterBulkUpdate', options).then(() => { delete options.attributes; }); } @@ -3902,7 +3903,7 @@ class Model { ignoreChanged = _.without(ignoreChanged, updatedAtAttr); } - return this.constructor.runHooks(`before${hook}`, this, options) + return this.constructor.hooks.run(`before${hook}`, this, options) .then(() => { if (options.defaultFields && !this.isNewRecord) { afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged)); @@ -4057,7 +4058,7 @@ class Model { .tap(result => { // Run after hook if (options.hooks) { - return this.constructor.runHooks(`after${hook}`, result, options); + return this.constructor.hooks.run(`after${hook}`, result, options); } }) .then(result => { @@ -4185,7 +4186,7 @@ class Model { return Promise.try(() => { // Run before hook if (options.hooks) { - return this.constructor.runHooks('beforeDestroy', this, options); + return this.constructor.hooks.run('beforeDestroy', this, options); } }).then(() => { const where = this.where(true); @@ -4209,7 +4210,7 @@ class Model { }).tap(() => { // Run after hook if (options.hooks) { - return this.constructor.runHooks('afterDestroy', this, options); + return this.constructor.hooks.run('afterDestroy', this, options); } }); } @@ -4254,7 +4255,7 @@ class Model { return Promise.try(() => { // Run before hook if (options.hooks) { - return this.constructor.runHooks('beforeRestore', this, options); + return this.constructor.hooks.run('beforeRestore', this, options); } }).then(() => { const deletedAtCol = this.constructor._timestampAttributes.deletedAt; @@ -4266,7 +4267,7 @@ class Model { }).tap(() => { // Run after hook if (options.hooks) { - return this.constructor.runHooks('afterRestore', this, options); + return this.constructor.hooks.run('afterRestore', this, options); } }); } @@ -4503,6 +4504,5 @@ class Model { } Object.assign(Model, associationsMixin); -Hooks.applyTo(Model, true); module.exports = Model; diff --git a/lib/sequelize.js b/lib/sequelize.js index d249c9e36981..6c3c97411fcf 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -17,7 +17,7 @@ const TableHints = require('./table-hints'); const IndexHints = require('./index-hints'); const sequelizeErrors = require('./errors'); const Promise = require('./promise'); -const Hooks = require('./hooks'); +const { Hooks } = require('./hooks'); const Association = require('./associations/index'); const Validator = require('./utils/validator-extras').validator; const Op = require('./operators'); @@ -221,8 +221,9 @@ class Sequelize { options = options || {}; config = { database, username, password }; } + this.hooks = new Hooks(options.hooks); - Sequelize.runHooks('beforeInit', config, options); + Sequelize.hooks.run('beforeInit', config, options); this.options = Object.assign({ dialect: null, @@ -278,7 +279,6 @@ class Sequelize { this.options.logging = console.log; } - this._setupHooks(options.hooks); this.config = { database: config.database || this.options.database, @@ -338,7 +338,7 @@ class Sequelize { this.importCache = {}; - Sequelize.runHooks('afterInit', this); + Sequelize.hooks.run('afterInit', this); } /** @@ -633,10 +633,10 @@ class Sequelize { : this.connectionManager.getConnection(options); }).then(connection => { const query = new this.dialect.Query(connection, this, options); - return this.runHooks('beforeQuery', options, query) + return this.hooks.run('beforeQuery', options, query) .then(() => checkTransaction()) .then(() => query.run(sql, bindParameters)) - .finally(() => this.runHooks('afterQuery', options, query)) + .finally(() => this.hooks.run('afterQuery', options, query)) .finally(() => { if (!options.transaction) { return this.connectionManager.releaseConnection(connection); @@ -789,7 +789,7 @@ class Sequelize { return Promise.try(() => { if (options.hooks) { - return this.runHooks('beforeBulkSync', options); + return this.hooks.run('beforeBulkSync', options); } }).then(() => { if (options.force) { @@ -814,7 +814,7 @@ class Sequelize { return Promise.each(models, model => model.sync(options)); }).then(() => { if (options.hooks) { - return this.runHooks('afterBulkSync', options); + return this.hooks.run('afterBulkSync', options); } }).return(this); } @@ -1188,6 +1188,8 @@ class Sequelize { } } +Sequelize.hooks = new Hooks(); + // Aliases Sequelize.prototype.fn = Sequelize.fn; Sequelize.prototype.col = Sequelize.col; @@ -1204,8 +1206,6 @@ Sequelize.prototype.validate = Sequelize.prototype.authenticate; */ Sequelize.version = require('../package.json').version; -Sequelize.options = { hooks: {} }; - /** * @private */ @@ -1285,13 +1285,6 @@ Sequelize.prototype.Association = Sequelize.Association = Association; */ Sequelize.useInflection = Utils.useInflection; -/** - * Allow hooks to be defined on Sequelize + on sequelize instance as universal hooks to run on all models - * and on Sequelize/sequelize methods e.g. Sequelize(), Sequelize#define() - */ -Hooks.applyTo(Sequelize); -Hooks.applyTo(Sequelize.prototype); - /** * Expose various errors available */ diff --git a/lib/transaction.js b/lib/transaction.js index 56d72ab513d6..83647f025396 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,6 +1,7 @@ 'use strict'; const Promise = require('./promise'); +const { Hooks } = require('./hooks'); /** * The transaction object is used to identify a running transaction. @@ -21,9 +22,9 @@ class Transaction { * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. PostgreSQL only */ constructor(sequelize, options = {}) { + this.hooks = new Hooks(); this.sequelize = sequelize; this.savepoints = []; - this._afterCommitHooks = []; // get dialect specific transaction options const generateTransactionId = this.sequelize.dialect.QueryGenerator.generateTransactionId; @@ -67,11 +68,7 @@ class Transaction { return this.cleanup(); } return null; - }).tap( - () => Promise.each( - this._afterCommitHooks, - hook => Promise.resolve(hook.apply(this, [this]))) - ); + }).tap(() => this.hooks.run('afterCommit', this)); } /** @@ -157,20 +154,6 @@ class Transaction { return res; } - /** - * A hook that is run after a transaction is committed - * - * @param {Function} fn A callback function that is called with the committed transaction - * @name afterCommit - * @memberof Sequelize.Transaction - */ - afterCommit(fn) { - if (!fn || typeof fn !== 'function') { - throw new Error('"fn" must be a function'); - } - this._afterCommitHooks.push(fn); - } - /** * Types can be set per-transaction by passing `options.type` to `sequelize.transaction`. * Default to `DEFERRED` but you can override the default type by passing `options.transactionType` in `new Sequelize`. diff --git a/test/integration/hooks/associations.test.js b/test/integration/hooks/associations.test.js index 8497c73e852d..d5b959d44b28 100644 --- a/test/integration/hooks/associations.test.js +++ b/test/integration/hooks/associations.test.js @@ -60,12 +60,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { let beforeHook = false, afterHook = false; - this.Tasks.beforeUpdate(() => { + this.Tasks.hooks.add('beforeUpdate', () => { beforeHook = true; return Promise.resolve(); }); - this.Tasks.afterUpdate(() => { + this.Tasks.hooks.add('afterUpdate', () => { afterHook = true; return Promise.resolve(); }); @@ -83,7 +83,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('on error', function() { - this.Tasks.afterUpdate(() => { + this.Tasks.hooks.add('afterUpdate', () => { return Promise.reject(new Error('Whoops!')); }); @@ -120,10 +120,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeDestroy(beforeTask); - this.Tasks.afterDestroy(afterTask); + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeDestroy', beforeTask); + this.Tasks.hooks.add('afterDestroy', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -146,22 +146,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; return Promise.reject(new Error(CustomErrorText)); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; return Promise.resolve(); }); @@ -204,8 +204,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.Tasks.beforeUpdate(beforeHook); - this.Tasks.afterUpdate(afterHook); + this.Tasks.hooks.add('beforeUpdate', beforeHook); + this.Tasks.hooks.add('afterUpdate', afterHook); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -220,7 +220,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('on error', function() { - this.Tasks.afterUpdate(() => { + this.Tasks.hooks.add('afterUpdate', () => { throw new Error('Whoops!'); }); @@ -257,10 +257,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(beforeTask); - this.Tasks.afterUpdate(afterTask); + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeUpdate', beforeTask); + this.Tasks.hooks.add('afterUpdate', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -282,13 +282,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(() => { + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeUpdate', () => { beforeTask(); throw new Error('Whoops!'); }); - this.Tasks.afterUpdate(afterTask); + this.Tasks.hooks.add('afterUpdate', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -332,10 +332,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeDestroy(beforeTask); - this.Tasks.afterDestroy(afterTask); + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeDestroy', beforeTask); + this.Tasks.hooks.add('afterDestroy', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -357,22 +357,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; return Promise.reject(new Error('Whoops!')); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; return Promise.resolve(); }); @@ -417,10 +417,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(beforeTask); - this.Tasks.afterUpdate(afterTask); + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeUpdate', beforeTask); + this.Tasks.hooks.add('afterUpdate', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -442,22 +442,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeUpdate(() => { + this.Tasks.hooks.add('beforeUpdate', () => { beforeTask = true; return Promise.reject(new Error('Whoops!')); }); - this.Tasks.afterUpdate(() => { + this.Tasks.hooks.add('afterUpdate', () => { afterTask = true; return Promise.resolve(); }); @@ -502,10 +502,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeDestroy(beforeTask); - this.Tasks.afterDestroy(afterTask); + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeDestroy', beforeTask); + this.Tasks.hooks.add('afterDestroy', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -528,22 +528,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; return Promise.reject(new Error('Whoops!')); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; return Promise.resolve(); }); @@ -587,10 +587,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.beforeCreate(beforeProject); - this.Projects.afterCreate(afterProject); - this.Tasks.beforeUpdate(beforeTask); - this.Tasks.afterUpdate(afterTask); + this.Projects.hooks.add('beforeCreate', beforeProject); + this.Projects.hooks.add('afterCreate', afterProject); + this.Tasks.hooks.add('beforeUpdate', beforeTask); + this.Tasks.hooks.add('afterUpdate', afterTask); return this.Projects.create({ title: 'New Project' }).then(project => { return this.Tasks.create({ title: 'New Task' }).then(task => { @@ -612,22 +612,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeUpdate(() => { + this.Tasks.hooks.add('beforeUpdate', () => { beforeTask = true; return Promise.reject(new Error('Whoops!')); }); - this.Tasks.afterUpdate(() => { + this.Tasks.hooks.add('afterUpdate', () => { afterTask = true; return Promise.resolve(); }); @@ -686,32 +686,32 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; return Promise.resolve(); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; return Promise.resolve(); }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.hooks.add('beforeDestroy', () => { beforeMiniTask = true; return Promise.resolve(); }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.hooks.add('afterDestroy', () => { afterMiniTask = true; return Promise.resolve(); }); @@ -742,32 +742,32 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; return Promise.resolve(); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; return Promise.resolve(); }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.hooks.add('beforeDestroy', () => { beforeMiniTask = true; return Promise.reject(new Error('Whoops!')); }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.hooks.add('afterDestroy', () => { afterMiniTask = true; return Promise.resolve(); }); @@ -828,32 +828,32 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; return Promise.resolve(); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; return Promise.resolve(); }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.hooks.add('beforeDestroy', () => { beforeMiniTask = true; return Promise.resolve(); }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.hooks.add('afterDestroy', () => { afterMiniTask = true; return Promise.resolve(); }); @@ -888,28 +888,28 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterMiniTask = false; const CustomErrorText = 'Whoops!'; - this.Projects.beforeCreate(() => { + this.Projects.hooks.add('beforeCreate', () => { beforeProject = true; }); - this.Projects.afterCreate(() => { + this.Projects.hooks.add('afterCreate', () => { afterProject = true; }); - this.Tasks.beforeDestroy(() => { + this.Tasks.hooks.add('beforeDestroy', () => { beforeTask = true; throw new Error(CustomErrorText); }); - this.Tasks.afterDestroy(() => { + this.Tasks.hooks.add('afterDestroy', () => { afterTask = true; }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.hooks.add('beforeDestroy', () => { beforeMiniTask = true; }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.hooks.add('afterDestroy', () => { afterMiniTask = true; }); diff --git a/test/integration/hooks/bulkOperation.test.js b/test/integration/hooks/bulkOperation.test.js index c0700016db80..a8fd02d85848 100644 --- a/test/integration/hooks/bulkOperation.test.js +++ b/test/integration/hooks/bulkOperation.test.js @@ -39,9 +39,9 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.beforeBulkCreate(beforeBulk); + this.User.hooks.add('beforeBulkCreate', beforeBulk); - this.User.afterBulkCreate(afterBulk); + this.User.hooks.add('afterBulkCreate', afterBulk); return this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, @@ -55,7 +55,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('should return an error from before', function() { - this.User.beforeBulkCreate(() => { + this.User.hooks.add('beforeBulkCreate', () => { throw new Error('Whoops!'); }); @@ -66,7 +66,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('should return an error from after', function() { - this.User.afterBulkCreate(() => { + this.User.hooks.add('afterBulkCreate', () => { throw new Error('Whoops!'); }); @@ -101,22 +101,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { let beforeBulkCreate = false, afterBulkCreate = false; - this.User.beforeBulkCreate(() => { + this.User.hooks.add('beforeBulkCreate', () => { beforeBulkCreate = true; return Promise.resolve(); }); - this.User.afterBulkCreate(() => { + this.User.hooks.add('afterBulkCreate', () => { afterBulkCreate = true; return Promise.resolve(); }); - this.User.beforeCreate(user => { + this.User.hooks.add('beforeCreate', user => { user.beforeHookTest = true; return Promise.resolve(); }); - this.User.afterCreate(user => { + this.User.hooks.add('afterCreate', user => { user.username = `User${user.id}`; return Promise.resolve(); }); @@ -135,21 +135,21 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { let beforeBulkCreate = false, afterBulkCreate = false; - this.User.beforeBulkCreate(() => { + this.User.hooks.add('beforeBulkCreate', () => { beforeBulkCreate = true; return Promise.resolve(); }); - this.User.afterBulkCreate(() => { + this.User.hooks.add('afterBulkCreate', () => { afterBulkCreate = true; return Promise.resolve(); }); - this.User.beforeCreate(() => { + this.User.hooks.add('beforeCreate', () => { return Promise.reject(new Error('You shall not pass!')); }); - this.User.afterCreate(user => { + this.User.hooks.add('afterCreate', user => { user.username = `User${user.id}`; return Promise.resolve(); }); @@ -169,8 +169,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.beforeBulkUpdate(beforeBulk); - this.User.afterBulkUpdate(afterBulk); + this.User.hooks.add('beforeBulkUpdate', beforeBulk); + this.User.hooks.add('afterBulkUpdate', afterBulk); return this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, @@ -186,7 +186,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('should return an error from before', function() { - this.User.beforeBulkUpdate(() => { + this.User.hooks.add('beforeBulkUpdate', () => { throw new Error('Whoops!'); }); @@ -199,7 +199,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('should return an error from after', function() { - this.User.afterBulkUpdate(() => { + this.User.hooks.add('afterBulkUpdate', () => { throw new Error('Whoops!'); }); @@ -236,16 +236,16 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.beforeBulkUpdate(beforeBulk); + this.User.hooks.add('beforeBulkUpdate', beforeBulk); - this.User.afterBulkUpdate(afterBulk); + this.User.hooks.add('afterBulkUpdate', afterBulk); - this.User.beforeUpdate(user => { + this.User.hooks.add('beforeUpdate', user => { expect(user.changed()).to.not.be.empty; user.beforeHookTest = true; }); - this.User.afterUpdate(user => { + this.User.hooks.add('afterUpdate', user => { user.username = `User${user.id}`; }); @@ -264,7 +264,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('should run the after/before functions for each item created successfully changing some data before updating', function() { - this.User.beforeUpdate(user => { + this.User.hooks.add('beforeUpdate', user => { expect(user.changed()).to.not.be.empty; if (user.get('id') === 1) { user.set('aNumber', user.get('aNumber') + 3); @@ -286,15 +286,15 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.beforeBulkUpdate(beforeBulk); + this.User.hooks.add('beforeBulkUpdate', beforeBulk); - this.User.afterBulkUpdate(afterBulk); + this.User.hooks.add('afterBulkUpdate', afterBulk); - this.User.beforeUpdate(() => { + this.User.hooks.add('beforeUpdate', () => { throw new Error('You shall not pass!'); }); - this.User.afterUpdate(user => { + this.User.hooks.add('afterUpdate', user => { user.username = `User${user.id}`; }); @@ -316,8 +316,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.beforeBulkDestroy(beforeBulk); - this.User.afterBulkDestroy(afterBulk); + this.User.hooks.add('beforeBulkDestroy', beforeBulk); + this.User.hooks.add('afterBulkDestroy', afterBulk); return this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }).then(() => { expect(beforeBulk).to.have.been.calledOnce; @@ -328,7 +328,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('should return an error from before', function() { - this.User.beforeBulkDestroy(() => { + this.User.hooks.add('beforeBulkDestroy', () => { throw new Error('Whoops!'); }); @@ -336,7 +336,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('should return an error from after', function() { - this.User.afterBulkDestroy(() => { + this.User.hooks.add('afterBulkDestroy', () => { throw new Error('Whoops!'); }); @@ -370,22 +370,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeHook = false, afterHook = false; - this.User.beforeBulkDestroy(() => { + this.User.hooks.add('beforeBulkDestroy', () => { beforeBulk = true; return Promise.resolve(); }); - this.User.afterBulkDestroy(() => { + this.User.hooks.add('afterBulkDestroy', () => { afterBulk = true; return Promise.resolve(); }); - this.User.beforeDestroy(() => { + this.User.hooks.add('beforeDestroy', () => { beforeHook = true; return Promise.resolve(); }); - this.User.afterDestroy(() => { + this.User.hooks.add('afterDestroy', () => { afterHook = true; return Promise.resolve(); }); @@ -408,22 +408,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeHook = false, afterHook = false; - this.User.beforeBulkDestroy(() => { + this.User.hooks.add('beforeBulkDestroy', () => { beforeBulk = true; return Promise.resolve(); }); - this.User.afterBulkDestroy(() => { + this.User.hooks.add('afterBulkDestroy', () => { afterBulk = true; return Promise.resolve(); }); - this.User.beforeDestroy(() => { + this.User.hooks.add('beforeDestroy', () => { beforeHook = true; return Promise.reject(new Error('You shall not pass!')); }); - this.User.afterDestroy(() => { + this.User.hooks.add('afterDestroy', () => { afterHook = true; return Promise.resolve(); }); @@ -456,8 +456,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.ParanoidUser.beforeBulkRestore(beforeBulk); - this.ParanoidUser.afterBulkRestore(afterBulk); + this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk); + this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk); return this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }).then(() => { expect(beforeBulk).to.have.been.calledOnce; @@ -468,7 +468,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('should return an error from before', function() { - this.ParanoidUser.beforeBulkRestore(() => { + this.ParanoidUser.hooks.add('beforeBulkRestore', () => { throw new Error('Whoops!'); }); @@ -476,7 +476,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('should return an error from after', function() { - this.ParanoidUser.afterBulkRestore(() => { + this.ParanoidUser.hooks.add('afterBulkRestore', () => { throw new Error('Whoops!'); }); @@ -504,10 +504,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.beforeBulkRestore(beforeBulk); - this.ParanoidUser.afterBulkRestore(afterBulk); - this.ParanoidUser.beforeRestore(beforeHook); - this.ParanoidUser.afterRestore(afterHook); + this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk); + this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk); + this.ParanoidUser.hooks.add('beforeRestore', beforeHook); + this.ParanoidUser.hooks.add('afterRestore', afterHook); return this.ParanoidUser.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } @@ -529,14 +529,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.beforeBulkRestore(beforeBulk); - this.ParanoidUser.afterBulkRestore(afterBulk); - this.ParanoidUser.beforeRestore(() => { + this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk); + this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk); + this.ParanoidUser.hooks.add('beforeRestore', () => { beforeHook(); return Promise.reject(new Error('You shall not pass!')); }); - this.ParanoidUser.afterRestore(afterHook); + this.ParanoidUser.hooks.add('afterRestore', afterHook); return this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { return this.ParanoidUser.destroy({ where: { aNumber: 1 } }); diff --git a/test/integration/hooks/count.test.js b/test/integration/hooks/count.test.js index ff865a2a52e7..d734802c6af4 100644 --- a/test/integration/hooks/count.test.js +++ b/test/integration/hooks/count.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('hook runs', function() { let beforeHook = false; - this.User.beforeCount(() => { + this.User.hooks.add('beforeCount', () => { beforeHook = true; }); @@ -44,7 +44,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('beforeCount hook can change options', function() { - this.User.beforeCount(options => { + this.User.hooks.add('beforeCount', options => { options.where.username = 'adam'; }); @@ -54,7 +54,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('in beforeCount hook returns error', function() { - this.User.beforeCount(() => { + this.User.hooks.add('beforeCount', () => { throw new Error('Oops!'); }); diff --git a/test/integration/hooks/create.test.js b/test/integration/hooks/create.test.js index a6c96d3dc3f0..b9d43d944979 100644 --- a/test/integration/hooks/create.test.js +++ b/test/integration/hooks/create.test.js @@ -31,10 +31,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.beforeCreate(beforeHook); - this.User.afterCreate(afterHook); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); + this.User.hooks.add('beforeCreate', beforeHook); + this.User.hooks.add('afterCreate', afterHook); + this.User.hooks.add('beforeSave', beforeSave); + this.User.hooks.add('afterSave', afterSave); return this.User.create({ username: 'Toni', mood: 'happy' }).then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -52,13 +52,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterHook = sinon.spy(), afterSave = sinon.spy(); - this.User.beforeCreate(() => { + this.User.hooks.add('beforeCreate', () => { beforeHook(); throw new Error('Whoops!'); }); - this.User.afterCreate(afterHook); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); + this.User.hooks.add('afterCreate', afterHook); + this.User.hooks.add('beforeSave', beforeSave); + this.User.hooks.add('afterSave', afterSave); return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -75,13 +75,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterSave = sinon.spy(); - this.User.beforeCreate(beforeHook); - this.User.afterCreate(() => { + this.User.hooks.add('beforeCreate', beforeHook); + this.User.hooks.add('afterCreate', () => { afterHook(); throw new Error('Whoops!'); }); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); + this.User.hooks.add('beforeSave', beforeSave); + this.User.hooks.add('afterSave', afterSave); return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -102,7 +102,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { let hookCalled = 0; - A.addHook('afterCreate', () => { + A.hooks.add('afterCreate', () => { hookCalled++; return Promise.resolve(); }); @@ -126,7 +126,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('beforeValidate', function() { let hookCalled = 0; - this.User.beforeValidate(user => { + this.User.hooks.add('beforeValidate', user => { user.mood = 'happy'; hookCalled++; }); @@ -141,7 +141,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('afterValidate', function() { let hookCalled = 0; - this.User.afterValidate(user => { + this.User.hooks.add('afterValidate', user => { user.mood = 'neutral'; hookCalled++; }); @@ -156,7 +156,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('beforeCreate', function() { let hookCalled = 0; - this.User.beforeCreate(user => { + this.User.hooks.add('beforeCreate', user => { user.mood = 'happy'; hookCalled++; }); @@ -171,7 +171,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('beforeSave', function() { let hookCalled = 0; - this.User.beforeSave(user => { + this.User.hooks.add('beforeSave', user => { user.mood = 'happy'; hookCalled++; }); @@ -186,12 +186,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('beforeSave with beforeCreate', function() { let hookCalled = 0; - this.User.beforeCreate(user => { + this.User.hooks.add('beforeCreate', user => { user.mood = 'sad'; hookCalled++; }); - this.User.beforeSave(user => { + this.User.hooks.add('beforeSave', user => { user.mood = 'happy'; hookCalled++; }); diff --git a/test/integration/hooks/destroy.test.js b/test/integration/hooks/destroy.test.js index 441c08498a6d..85431bdab4dc 100644 --- a/test/integration/hooks/destroy.test.js +++ b/test/integration/hooks/destroy.test.js @@ -27,8 +27,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeDestroy(beforeHook); - this.User.afterDestroy(afterHook); + this.User.hooks.add('beforeDestroy', beforeHook); + this.User.hooks.add('afterDestroy', afterHook); return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { return user.destroy().then(() => { @@ -44,11 +44,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeDestroy(() => { + this.User.hooks.add('beforeDestroy', () => { beforeHook(); throw new Error('Whoops!'); }); - this.User.afterDestroy(afterHook); + this.User.hooks.add('afterDestroy', afterHook); return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { return expect(user.destroy()).to.be.rejected.then(() => { @@ -62,8 +62,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeDestroy(beforeHook); - this.User.afterDestroy(() => { + this.User.hooks.add('beforeDestroy', beforeHook); + this.User.hooks.add('afterDestroy', () => { afterHook(); throw new Error('Whoops!'); }); @@ -107,7 +107,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('should not throw error when a beforeDestroy hook changes a virtual column', function() { - this.ParanoidUser.beforeDestroy(instance => instance.virtualField = 2); + this.ParanoidUser.hooks.add('beforeDestroy', instance => instance.virtualField = 2); return this.ParanoidUser.sync({ force: true }) .then(() => this.ParanoidUser.create({ username: 'user1' })) diff --git a/test/integration/hooks/find.test.js b/test/integration/hooks/find.test.js index 0fb2a5f0e6b9..8685a9264e77 100644 --- a/test/integration/hooks/find.test.js +++ b/test/integration/hooks/find.test.js @@ -30,7 +30,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('allow changing attributes via beforeFind #5675', function() { - this.User.beforeFind(options => { + this.User.hooks.add('beforeFind', options => { options.attributes = { include: ['id'] }; @@ -45,19 +45,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeHook3 = false, afterHook = false; - this.User.beforeFind(() => { + this.User.hooks.add('beforeFind', () => { beforeHook = true; }); - this.User.beforeFindAfterExpandIncludeAll(() => { + this.User.hooks.add('beforeFindAfterExpandIncludeAll', () => { beforeHook2 = true; }); - this.User.beforeFindAfterOptions(() => { + this.User.hooks.add('beforeFindAfterOptions', () => { beforeHook3 = true; }); - this.User.afterFind(() => { + this.User.hooks.add('afterFind', () => { afterHook = true; }); @@ -71,7 +71,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('beforeFind hook can change options', function() { - this.User.beforeFind(options => { + this.User.hooks.add('beforeFind', options => { options.where.username = 'joe'; }); @@ -81,7 +81,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('beforeFindAfterExpandIncludeAll hook can change options', function() { - this.User.beforeFindAfterExpandIncludeAll(options => { + this.User.hooks.add('beforeFindAfterExpandIncludeAll', options => { options.where.username = 'joe'; }); @@ -91,7 +91,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('beforeFindAfterOptions hook can change options', function() { - this.User.beforeFindAfterOptions(options => { + this.User.hooks.add('beforeFindAfterOptions', options => { options.where.username = 'joe'; }); @@ -101,7 +101,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('afterFind hook can change results', function() { - this.User.afterFind(user => { + this.User.hooks.add('afterFind', user => { user.mood = 'sad'; }); @@ -113,7 +113,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('in beforeFind hook returns error', function() { - this.User.beforeFind(() => { + this.User.hooks.add('beforeFind', () => { throw new Error('Oops!'); }); @@ -123,7 +123,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('in beforeFindAfterExpandIncludeAll hook returns error', function() { - this.User.beforeFindAfterExpandIncludeAll(() => { + this.User.hooks.add('beforeFindAfterExpandIncludeAll', () => { throw new Error('Oops!'); }); @@ -133,7 +133,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('in beforeFindAfterOptions hook returns error', function() { - this.User.beforeFindAfterOptions(() => { + this.User.hooks.add('beforeFindAfterOptions', () => { throw new Error('Oops!'); }); @@ -143,7 +143,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('in afterFind hook returns error', function() { - this.User.afterFind(() => { + this.User.hooks.add('afterFind', () => { throw new Error('Oops!'); }); diff --git a/test/integration/hooks/hooks.test.js b/test/integration/hooks/hooks.test.js index 275a24ab80bc..f0cf59e58f9e 100644 --- a/test/integration/hooks/hooks.test.js +++ b/test/integration/hooks/hooks.test.js @@ -37,13 +37,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('#define', () => { before(function() { - this.sequelize.addHook('beforeDefine', (attributes, options) => { + this.sequelize.hooks.add('beforeDefine', (attributes, options) => { options.modelName = 'bar'; options.name.plural = 'barrs'; attributes.type = DataTypes.STRING; }); - this.sequelize.addHook('afterDefine', factory => { + this.sequelize.hooks.add('afterDefine', factory => { factory.options.name.singular = 'barr'; }); @@ -67,19 +67,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); after(function() { - this.sequelize.options.hooks = {}; + this.sequelize.hooks.removeAll(); this.sequelize.modelManager.removeModel(this.model); }); }); describe('#init', () => { before(function() { - Sequelize.addHook('beforeInit', (config, options) => { + Sequelize.hooks.add('beforeInit', (config, options) => { config.database = 'db2'; options.host = 'server9'; }); - Sequelize.addHook('afterInit', sequelize => { + Sequelize.hooks.add('afterInit', sequelize => { sequelize.options.protocol = 'udp'; }); @@ -99,7 +99,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); after(() => { - Sequelize.options.hooks = {}; + Sequelize.hooks.removeAll(); }); }); @@ -236,8 +236,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeSync(beforeHook); - this.User.afterSync(afterHook); + this.User.hooks.add('beforeSync', beforeHook); + this.User.hooks.add('afterSync', afterHook); return this.User.sync().then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -249,8 +249,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeSync(beforeHook); - this.User.afterSync(afterHook); + this.User.hooks.add('beforeSync', beforeHook); + this.User.hooks.add('afterSync', afterHook); return this.User.sync({ hooks: false }).then(() => { expect(beforeHook).to.not.have.been.called; @@ -265,11 +265,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeSync(() => { + this.User.hooks.add('beforeSync', () => { beforeHook(); throw new Error('Whoops!'); }); - this.User.afterSync(afterHook); + this.User.hooks.add('afterSync', afterHook); return expect(this.User.sync()).to.be.rejected.then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -281,8 +281,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeSync(beforeHook); - this.User.afterSync(() => { + this.User.hooks.add('beforeSync', beforeHook); + this.User.hooks.add('afterSync', () => { afterHook(); throw new Error('Whoops!'); }); @@ -303,10 +303,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { modelBeforeHook = sinon.spy(), modelAfterHook = sinon.spy(); - this.sequelize.beforeBulkSync(beforeHook); - this.User.beforeSync(modelBeforeHook); - this.User.afterSync(modelAfterHook); - this.sequelize.afterBulkSync(afterHook); + this.sequelize.hooks.add('beforeBulkSync', beforeHook); + this.User.hooks.add('beforeSync', modelBeforeHook); + this.User.hooks.add('afterSync', modelAfterHook); + this.sequelize.hooks.add('afterBulkSync', afterHook); return this.sequelize.sync().then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -322,10 +322,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { modelBeforeHook = sinon.spy(), modelAfterHook = sinon.spy(); - this.sequelize.beforeBulkSync(beforeHook); - this.User.beforeSync(modelBeforeHook); - this.User.afterSync(modelAfterHook); - this.sequelize.afterBulkSync(afterHook); + this.sequelize.hooks.add('beforeBulkSync', beforeHook); + this.User.hooks.add('beforeSync', modelBeforeHook); + this.User.hooks.add('afterSync', modelAfterHook); + this.sequelize.hooks.add('afterBulkSync', afterHook); return this.sequelize.sync({ hooks: false }).then(() => { expect(beforeHook).to.not.have.been.called; @@ -336,7 +336,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); afterEach(function() { - this.sequelize.options.hooks = {}; + this.sequelize.hooks.removeAll(); }); }); @@ -346,11 +346,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('should return an error from before', function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.sequelize.beforeBulkSync(() => { + this.sequelize.hooks.add('beforeBulkSync', () => { beforeHook(); throw new Error('Whoops!'); }); - this.sequelize.afterBulkSync(afterHook); + this.sequelize.hooks.add('afterBulkSync', afterHook); return expect(this.sequelize.sync()).to.be.rejected.then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -362,8 +362,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.sequelize.beforeBulkSync(beforeHook); - this.sequelize.afterBulkSync(() => { + this.sequelize.hooks.add('beforeBulkSync', beforeHook); + this.sequelize.hooks.add('afterBulkSync', () => { afterHook(); throw new Error('Whoops!'); }); @@ -375,42 +375,24 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); afterEach(function() { - this.sequelize.options.hooks = {}; + this.sequelize.hooks.removeAll(); }); }); }); describe('#removal', () => { - it('should be able to remove by name', function() { - const sasukeHook = sinon.spy(), - narutoHook = sinon.spy(); - - this.User.addHook('beforeCreate', 'sasuke', sasukeHook); - this.User.addHook('beforeCreate', 'naruto', narutoHook); - - return this.User.create({ username: 'makunouchi' }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeCreate', 'sasuke'); - return this.User.create({ username: 'sendo' }); - }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); - }); - it('should be able to remove by reference', function() { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); - this.User.addHook('beforeCreate', sasukeHook); - this.User.addHook('beforeCreate', narutoHook); + this.User.hooks.add('beforeCreate', sasukeHook); + this.User.hooks.add('beforeCreate', narutoHook); return this.User.create({ username: 'makunouchi' }).then(() => { expect(sasukeHook).to.have.been.calledOnce; expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeCreate', sasukeHook); + this.User.hooks.remove('beforeCreate', sasukeHook); return this.User.create({ username: 'sendo' }); }).then(() => { expect(sasukeHook).to.have.been.calledOnce; @@ -422,13 +404,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); - this.User.addHook('beforeSave', sasukeHook); - this.User.addHook('beforeSave', narutoHook); + this.User.hooks.add('beforeSave', sasukeHook); + this.User.hooks.add('beforeSave', narutoHook); return this.User.create({ username: 'makunouchi' }).then(user => { expect(sasukeHook).to.have.been.calledOnce; expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeSave', sasukeHook); + this.User.hooks.remove('beforeSave', sasukeHook); return user.update({ username: 'sendo' }); }).then(() => { expect(sasukeHook).to.have.been.calledOnce; diff --git a/test/integration/hooks/restore.test.js b/test/integration/hooks/restore.test.js index e309755b5ee9..f79280de748a 100644 --- a/test/integration/hooks/restore.test.js +++ b/test/integration/hooks/restore.test.js @@ -38,8 +38,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.beforeRestore(beforeHook); - this.ParanoidUser.afterRestore(afterHook); + this.ParanoidUser.hooks.add('beforeRestore', beforeHook); + this.ParanoidUser.hooks.add('afterRestore', afterHook); return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { return user.destroy().then(() => { @@ -57,11 +57,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.beforeRestore(() => { + this.ParanoidUser.hooks.add('beforeRestore', () => { beforeHook(); throw new Error('Whoops!'); }); - this.ParanoidUser.afterRestore(afterHook); + this.ParanoidUser.hooks.add('afterRestore', afterHook); return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { return user.destroy().then(() => { @@ -77,8 +77,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.beforeRestore(beforeHook); - this.ParanoidUser.afterRestore(() => { + this.ParanoidUser.hooks.add('beforeRestore', beforeHook); + this.ParanoidUser.hooks.add('afterRestore', () => { afterHook(); throw new Error('Whoops!'); }); diff --git a/test/integration/hooks/updateAttributes.test.js b/test/integration/hooks/updateAttributes.test.js index 7d7c91c813ac..2eb6237318ea 100644 --- a/test/integration/hooks/updateAttributes.test.js +++ b/test/integration/hooks/updateAttributes.test.js @@ -29,10 +29,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.beforeUpdate(beforeHook); - this.User.afterUpdate(afterHook); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); + this.User.hooks.add('beforeUpdate', beforeHook); + this.User.hooks.add('afterUpdate', afterHook); + this.User.hooks.add('beforeSave', beforeSave); + this.User.hooks.add('afterSave', afterSave); return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { return user.update({ username: 'Chong' }).then(user => { @@ -53,13 +53,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.beforeUpdate(() => { + this.User.hooks.add('beforeUpdate', () => { beforeHook(); throw new Error('Whoops!'); }); - this.User.afterUpdate(afterHook); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); + this.User.hooks.add('afterUpdate', afterHook); + this.User.hooks.add('beforeSave', beforeSave); + this.User.hooks.add('afterSave', afterSave); return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => { @@ -77,13 +77,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.beforeUpdate(beforeHook); - this.User.afterUpdate(() => { + this.User.hooks.add('beforeUpdate', beforeHook); + this.User.hooks.add('afterUpdate', () => { afterHook(); throw new Error('Whoops!'); }); - this.User.beforeSave(beforeSave); - this.User.afterSave(afterSave); + this.User.hooks.add('beforeSave', beforeSave); + this.User.hooks.add('afterSave', afterSave); return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => { @@ -99,7 +99,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('preserves changes to instance', () => { it('beforeValidate', function() { - this.User.beforeValidate(user => { + this.User.hooks.add('beforeValidate', user => { user.mood = 'happy'; }); @@ -113,7 +113,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('afterValidate', function() { - this.User.afterValidate(user => { + this.User.hooks.add('afterValidate', user => { user.mood = 'sad'; }); @@ -128,7 +128,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('beforeSave', function() { let hookCalled = 0; - this.User.beforeSave(user => { + this.User.hooks.add('beforeSave', user => { user.mood = 'happy'; hookCalled++; }); @@ -145,12 +145,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('beforeSave with beforeUpdate', function() { let hookCalled = 0; - this.User.beforeUpdate(user => { + this.User.hooks.add('beforeUpdate', user => { user.mood = 'sad'; hookCalled++; }); - this.User.beforeSave(user => { + this.User.hooks.add('beforeSave', user => { user.mood = 'happy'; hookCalled++; }); diff --git a/test/integration/hooks/upsert.test.js b/test/integration/hooks/upsert.test.js index 289a67bfd5ea..26fd79aeb596 100644 --- a/test/integration/hooks/upsert.test.js +++ b/test/integration/hooks/upsert.test.js @@ -29,8 +29,8 @@ if (Support.sequelize.dialect.supports.upserts) { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeUpsert(beforeHook); - this.User.afterUpsert(afterHook); + this.User.hooks.add('beforeUpsert', beforeHook); + this.User.hooks.add('afterUpsert', afterHook); return this.User.upsert({ username: 'Toni', mood: 'happy' }).then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -44,11 +44,11 @@ if (Support.sequelize.dialect.supports.upserts) { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeUpsert(() => { + this.User.hooks.add('beforeUpsert', () => { beforeHook(); throw new Error('Whoops!'); }); - this.User.afterUpsert(afterHook); + this.User.hooks.add('afterUpsert', afterHook); return expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { expect(beforeHook).to.have.been.calledOnce; @@ -60,8 +60,8 @@ if (Support.sequelize.dialect.supports.upserts) { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.beforeUpsert(beforeHook); - this.User.afterUpsert(() => { + this.User.hooks.add('beforeUpsert', beforeHook); + this.User.hooks.add('afterUpsert', () => { afterHook(); throw new Error('Whoops!'); }); @@ -78,7 +78,7 @@ if (Support.sequelize.dialect.supports.upserts) { let hookCalled = 0; const valuesOriginal = { mood: 'sad', username: 'leafninja' }; - this.User.beforeUpsert(values => { + this.User.hooks.add('beforeUpsert', values => { values.mood = 'happy'; hookCalled++; }); diff --git a/test/integration/hooks/validate.test.js b/test/integration/hooks/validate.test.js index 16c7c6b2cf00..6876e1a16752 100644 --- a/test/integration/hooks/validate.test.js +++ b/test/integration/hooks/validate.test.js @@ -24,12 +24,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('#validate', () => { describe('#create', () => { it('should return the user', function() { - this.User.beforeValidate(user => { + this.User.hooks.add('beforeValidate', user => { user.username = 'Bob'; user.mood = 'happy'; }); - this.User.afterValidate(user => { + this.User.hooks.add('afterValidate', user => { user.username = 'Toni'; }); @@ -42,7 +42,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('#3534, hooks modifications', () => { it('fields modified in hooks are saved', function() { - this.User.afterValidate(user => { + this.User.hooks.add('afterValidate', user => { //if username is defined and has more than 5 char user.username = user.username ? user.username.length < 5 ? null : user.username @@ -51,7 +51,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); - this.User.beforeValidate(user => { + this.User.hooks.add('beforeValidate', user => { user.mood = user.mood || 'neutral'; }); @@ -108,7 +108,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { it('should emit an error from after hook', function() { - this.User.afterValidate(user => { + this.User.hooks.add('afterValidate', user => { user.mood = 'ecstatic'; throw new Error('Whoops! Changed user.mood!'); }); @@ -119,7 +119,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('should call validationFailed hook', function() { const validationFailedHook = sinon.spy(); - this.User.validationFailed(validationFailedHook); + this.User.hooks.add('validationFailed', validationFailedHook); return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(() => { expect(validationFailedHook).to.have.been.calledOnce; @@ -129,7 +129,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('should not replace the validation error in validationFailed hook by default', function() { const validationFailedHook = sinon.stub(); - this.User.validationFailed(validationFailedHook); + this.User.hooks.add('validationFailed', validationFailedHook); return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => { expect(err.name).to.equal('SequelizeValidationError'); @@ -139,7 +139,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('should replace the validation error if validationFailed hook creates a new error', function() { const validationFailedHook = sinon.stub().throws(new Error('Whoops!')); - this.User.validationFailed(validationFailedHook); + this.User.hooks.add('validationFailed', validationFailedHook); return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => { expect(err.message).to.equal('Whoops!'); diff --git a/test/integration/instance/save.test.js b/test/integration/instance/save.test.js index 8d4cd508c4dc..48fe9488fc11 100644 --- a/test/integration/instance/save.test.js +++ b/test/integration/instance/save.test.js @@ -146,7 +146,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { email: DataTypes.STRING }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'B'); }); @@ -177,7 +177,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { email: DataTypes.STRING }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'C'); }); @@ -214,7 +214,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'B'); }); @@ -247,7 +247,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'B'); }); diff --git a/test/integration/instance/update.test.js b/test/integration/instance/update.test.js index 1aeadc4af577..847d3645f956 100644 --- a/test/integration/instance/update.test.js +++ b/test/integration/instance/update.test.js @@ -206,7 +206,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { email: DataTypes.STRING }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'B'); }); @@ -237,7 +237,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { email: DataTypes.STRING }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'C'); }); @@ -274,7 +274,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'B'); }); @@ -307,7 +307,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.beforeUpdate(instance => { + User.hooks.add('beforeUpdate', instance => { instance.set('email', 'B'); }); diff --git a/test/integration/instance/values.test.js b/test/integration/instance/values.test.js index 4407bad3ee57..fd4b379fe75a 100644 --- a/test/integration/instance/values.test.js +++ b/test/integration/instance/values.test.js @@ -489,7 +489,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { }); let changed; - User.afterUpdate(instance => { + User.hooks.add('afterUpdate', instance => { changed = instance.changed(); return; }); diff --git a/test/integration/model.test.js b/test/integration/model.test.js index cb206b71913f..ae16e9d0b4ee 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -1142,7 +1142,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should properly set data when individualHooks are true', function() { - this.User.beforeUpdate(instance => { + this.User.hooks.add('beforeUpdate', instance => { instance.set('intVal', 1); }); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 181403a40a98..ac3caa58289a 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -454,7 +454,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - User.beforeCreate(instance => { + User.hooks.add('beforeCreate', instance => { instance.set('username', instance.get('username').trim()); }); diff --git a/test/integration/support.js b/test/integration/support.js index f646600b5d53..875f34c46bfa 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -5,10 +5,10 @@ const Support = require('../support'); const runningQueries = new Set(); before(function() { - this.sequelize.addHook('beforeQuery', (options, query) => { + this.sequelize.hooks.add('beforeQuery', (options, query) => { runningQueries.add(query); }); - this.sequelize.addHook('afterQuery', (options, query) => { + this.sequelize.hooks.add('afterQuery', (options, query) => { runningQueries.delete(query); }); }); diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index e8188505724a..934f165ba9c3 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -86,7 +86,7 @@ if (current.dialect.supports.transactions) { let transaction; return expect(this.sequelize.transaction(t => { transaction = t; - transaction.afterCommit(hook); + transaction.hooks.add('afterCommit', hook); return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT }); }).then(() => { expect(hook).to.have.been.calledOnce; @@ -98,7 +98,7 @@ if (current.dialect.supports.transactions) { it('does not run hooks when a transaction is rolled back', function() { const hook = sinon.spy(); return expect(this.sequelize.transaction(transaction => { - transaction.afterCommit(hook); + transaction.hooks.add('afterCommit', hook); return Promise.reject(new Error('Rollback')); }) ).to.eventually.be.rejected.then(() => { @@ -217,7 +217,7 @@ if (current.dialect.supports.transactions) { return expect( this.sequelize.transaction().then(t => { transaction = t; - transaction.afterCommit(hook); + transaction.hooks.add('afterCommit', hook); return t.commit().then(() => { expect(hook).to.have.been.calledOnce; expect(hook).to.have.been.calledWith(t); @@ -239,7 +239,7 @@ if (current.dialect.supports.transactions) { const hook = sinon.spy(); return expect( this.sequelize.transaction().then(t => { - t.afterCommit(hook); + t.hooks.add('afterCommit', hook); return t.rollback().then(() => { expect(hook).to.not.have.been.called; }); @@ -247,69 +247,6 @@ if (current.dialect.supports.transactions) { ).to.eventually.be.fulfilled; }); - it('should throw an error if null is passed to afterCommit', function() { - const hook = null; - let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit(); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { - throw err; - }); - } - throw err; - }) - ).to.eventually.be.rejectedWith('"fn" must be a function'); - }); - - it('should throw an error if undefined is passed to afterCommit', function() { - const hook = undefined; - let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit(); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { - throw err; - }); - } - throw err; - }) - ).to.eventually.be.rejectedWith('"fn" must be a function'); - }); - - it('should throw an error if an object is passed to afterCommit', function() { - const hook = {}; - let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit(); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { - throw err; - }); - } - throw err; - }) - ).to.eventually.be.rejectedWith('"fn" must be a function'); - }); - it('does not allow commits after rollback', function() { return expect(this.sequelize.transaction().then(t => { return t.rollback().then(() => { diff --git a/test/unit/associations/belongs-to-many.test.js b/test/unit/associations/belongs-to-many.test.js index ce53626d881a..73f2e03f580e 100644 --- a/test/unit/associations/belongs-to-many.test.js +++ b/test/unit/associations/belongs-to-many.test.js @@ -708,7 +708,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { describe('beforeBelongsToManyAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -726,7 +726,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -734,7 +734,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { describe('afterBelongsToManyAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -753,7 +753,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/associations/belongs-to.test.js b/test/unit/associations/belongs-to.test.js index 7a1f7926a605..03ae834013d8 100644 --- a/test/unit/associations/belongs-to.test.js +++ b/test/unit/associations/belongs-to.test.js @@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { describe('beforeBelongsToAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.belongsTo(this.Tasks, { hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -77,7 +77,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.belongsTo(this.Tasks, { hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -85,7 +85,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { describe('afterBelongsToAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.belongsTo(this.Tasks, { hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -105,7 +105,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.belongsTo(this.Tasks, { hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js index ab5d424f49c6..f77bf9a638cc 100644 --- a/test/unit/associations/has-many.test.js +++ b/test/unit/associations/has-many.test.js @@ -217,7 +217,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { describe('beforeHasManyAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.hasMany(this.Tasks, { hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -234,7 +234,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.hasMany(this.Tasks, { hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -242,7 +242,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { describe('afterHasManyAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.hasMany(this.Tasks, { hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -261,7 +261,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.hasMany(this.Tasks, { hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/associations/has-one.test.js b/test/unit/associations/has-one.test.js index 9aa4d44a9eed..4ae092484a6f 100644 --- a/test/unit/associations/has-one.test.js +++ b/test/unit/associations/has-one.test.js @@ -70,7 +70,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { describe('beforeHasOneAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.hasOne(this.Tasks, { hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -88,7 +88,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.beforeAssociate(beforeAssociate); + this.Projects.hooks.add('beforeAssociate', beforeAssociate); this.Projects.hasOne(this.Tasks, { hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { describe('afterHasOneAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.hasOne(this.Tasks, { hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -116,7 +116,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.afterAssociate(afterAssociate); + this.Projects.hooks.add('afterAssociate', afterAssociate); this.Projects.hasOne(this.Tasks, { hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/connection-manager.test.js b/test/unit/connection-manager.test.js index a89d2d2c415a..137bf2c4759f 100644 --- a/test/unit/connection-manager.test.js +++ b/test/unit/connection-manager.test.js @@ -37,7 +37,7 @@ describe('connection manager', () => { const username = Math.random().toString(), password = Math.random().toString(); - this.sequelize.beforeConnect(config => { + this.sequelize.hooks.add('beforeConnect', config => { config.username = username; config.password = password; return config; @@ -55,7 +55,7 @@ describe('connection manager', () => { it('should call afterConnect', function() { const spy = sinon.spy(); - this.sequelize.afterConnect(spy); + this.sequelize.hooks.add('afterConnect', spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); @@ -82,7 +82,7 @@ describe('connection manager', () => { it('should call beforeDisconnect', function() { const spy = sinon.spy(); - this.sequelize.beforeDisconnect(spy); + this.sequelize.hooks.add('beforeDisconnect', spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); @@ -94,7 +94,7 @@ describe('connection manager', () => { it('should call afterDisconnect', function() { const spy = sinon.spy(); - this.sequelize.afterDisconnect(spy); + this.sequelize.hooks.add('afterDisconnect', spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); diff --git a/test/unit/hooks.test.js b/test/unit/hooks.test.js index 5e5d60ab466e..0b62aacd71f6 100644 --- a/test/unit/hooks.test.js +++ b/test/unit/hooks.test.js @@ -15,19 +15,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('does not expose non-model hooks', function() { - for (const badHook of ['beforeDefine', 'afterDefine', 'beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect', 'beforeInit', 'afterInit']) { - expect(this.Model).to.not.have.property(badHook); + for (const badHook of ['beforeDefine', 'afterDefine', 'beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect', 'beforeInit', 'afterInit', 'beforeBulkSync', 'afterBulkSync']) { + expect(() => this.Model.hooks.add(badHook, () => {})).to.throw(/is only applicable/); } }); describe('arguments', () => { it('hooks can modify passed arguments', function() { - this.Model.addHook('beforeCreate', options => { + this.Model.hooks.add('beforeCreate', options => { options.answer = 41; }); const options = {}; - return this.Model.runHooks('beforeCreate', options).then(() => { + return this.Model.hooks.run('beforeCreate', options).then(() => { expect(options.answer).to.equal(41); }); }); @@ -71,7 +71,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - describe('defined by addHook method', () => { + describe('defined by hooks.add method', () => { beforeEach(function() { this.beforeSaveHook = sinon.spy(); this.afterSaveHook = sinon.spy(); @@ -80,8 +80,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { name: Support.Sequelize.STRING }); - this.Model.addHook('beforeSave', this.beforeSaveHook); - this.Model.addHook('afterSave', this.afterSaveHook); + this.Model.hooks.add('beforeSave', this.beforeSaveHook); + this.Model.hooks.add('afterSave', this.afterSaveHook); }); it('calls beforeSave/afterSave', function() { @@ -101,8 +101,8 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { name: Support.Sequelize.STRING }); - this.Model.addHook('beforeSave', this.beforeSaveHook); - this.Model.addHook('afterSave', this.afterSaveHook); + this.Model.hooks.add('beforeSave', this.beforeSaveHook); + this.Model.hooks.add('afterSave', this.afterSaveHook); }); it('calls beforeSave/afterSave', function() { @@ -128,20 +128,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { expect(this.hook3).to.have.been.calledOnce; }); - it('using addHook', function() { - this.Model.addHook('beforeCreate', this.hook1); - this.Model.addHook('beforeCreate', this.hook2); - this.Model.addHook('beforeCreate', this.hook3); + it('using hooks.add', function() { + this.Model.hooks.add('beforeCreate', this.hook1); + this.Model.hooks.add('beforeCreate', this.hook2); + this.Model.hooks.add('beforeCreate', this.hook3); - return this.Model.runHooks('beforeCreate'); - }); - - it('using function', function() { - this.Model.beforeCreate(this.hook1); - this.Model.beforeCreate(this.hook2); - this.Model.beforeCreate(this.hook3); - - return this.Model.runHooks('beforeCreate'); + return this.Model.hooks.run('beforeCreate'); }); it('using define', function() { @@ -149,7 +141,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hooks: { beforeCreate: [this.hook1, this.hook2, this.hook3] } - }).runHooks('beforeCreate'); + }).hooks.run('beforeCreate'); }); it('using a mixture', function() { @@ -158,36 +150,36 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeCreate: this.hook1 } }); - Model.beforeCreate(this.hook2); - Model.addHook('beforeCreate', this.hook3); + Model.hooks.add('beforeCreate', this.hook2); + Model.hooks.add('beforeCreate', this.hook3); - return Model.runHooks('beforeCreate'); + return Model.hooks.run('beforeCreate'); }); }); it('stops execution when a hook throws', function() { - this.Model.beforeCreate(() => { + this.Model.hooks.add('beforeCreate', () => { this.hook1(); throw new Error('No!'); }); - this.Model.beforeCreate(this.hook2); + this.Model.hooks.add('beforeCreate', this.hook2); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejected.then(() => { + return expect(this.Model.hooks.run('beforeCreate')).to.be.rejected.then(() => { expect(this.hook1).to.have.been.calledOnce; expect(this.hook2).not.to.have.been.called; }); }); it('stops execution when a hook rejects', function() { - this.Model.beforeCreate(() => { + this.Model.hooks.add('beforeCreate', () => { this.hook1(); return Promise.reject(new Error('No!')); }); - this.Model.beforeCreate(this.hook2); + this.Model.hooks.add('beforeCreate', this.hook2); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejected.then(() => { + return expect(this.Model.hooks.run('beforeCreate')).to.be.rejected.then(() => { expect(this.hook1).to.have.been.calledOnce; expect(this.hook2).not.to.have.been.called; }); @@ -195,14 +187,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('global hooks', () => { - describe('using addHook', () => { + describe('using hooks.add', () => { it('invokes the global hook', function() { const globalHook = sinon.spy(); - current.addHook('beforeUpdate', globalHook); + current.hooks.add('beforeUpdate', globalHook); - return this.Model.runHooks('beforeUpdate').then(() => { + return this.Model.hooks.run('beforeUpdate').then(() => { expect(globalHook).to.have.been.calledOnce; }); }); @@ -212,7 +204,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { globalHookAfter = sinon.spy(), localHook = sinon.spy(); - current.addHook('beforeUpdate', globalHookBefore); + current.hooks.add('beforeUpdate', globalHookBefore); const Model = current.define('m', {}, { hooks: { @@ -220,9 +212,9 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - current.addHook('beforeUpdate', globalHookAfter); + current.hooks.add('beforeUpdate', globalHookAfter); - return Model.runHooks('beforeUpdate').then(() => { + return Model.hooks.run('beforeUpdate').then(() => { expect(globalHookBefore).to.have.been.calledOnce; expect(globalHookAfter).to.have.been.calledOnce; expect(localHook).to.have.been.calledOnce; @@ -252,7 +244,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return Model.runHooks('beforeCreate').then(() => { + return Model.hooks.run('beforeCreate').then(() => { expect(this.beforeCreate).to.have.been.calledOnce; }); }); @@ -265,7 +257,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return Model.runHooks('beforeCreate').then(() => { + return Model.hooks.run('beforeCreate').then(() => { expect(this.beforeCreate).not.to.have.been.called; expect(localHook).to.have.been.calledOnce; }); @@ -278,20 +270,16 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { const hook1 = sinon.spy(), hook2 = sinon.spy(); - this.Model.addHook('beforeCreate', 'myHook', hook1); - this.Model.beforeCreate('myHook2', hook2); + this.Model.hooks.add('beforeCreate', hook1); - return this.Model.runHooks('beforeCreate').then(() => { + return this.Model.hooks.run('beforeCreate').then(() => { expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; hook1.resetHistory(); - hook2.resetHistory(); - this.Model.removeHook('beforeCreate', 'myHook'); - this.Model.removeHook('beforeCreate', 'myHook2'); + this.Model.hooks.remove('beforeCreate', hook1); - return this.Model.runHooks('beforeCreate'); + return this.Model.hooks.run('beforeCreate'); }).then(() => { expect(hook1).not.to.have.been.called; expect(hook2).not.to.have.been.called; @@ -304,12 +292,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hook3 = sinon.spy(), hook4 = sinon.spy(); - this.Model.addHook('beforeCreate', hook1); - this.Model.addHook('beforeCreate', 'myHook', hook2); - this.Model.beforeCreate('myHook2', hook3); - this.Model.beforeCreate(hook4); + this.Model.hooks.add('beforeCreate', hook1); + this.Model.hooks.add('beforeCreate', hook2); + this.Model.hooks.add('beforeCreate', hook3); + this.Model.hooks.add('beforeCreate', hook4); - return this.Model.runHooks('beforeCreate').then(() => { + return this.Model.hooks.run('beforeCreate').then(() => { expect(hook1).to.have.been.calledOnce; expect(hook2).to.have.been.calledOnce; expect(hook3).to.have.been.calledOnce; @@ -320,9 +308,9 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hook3.resetHistory(); hook4.resetHistory(); - this.Model.removeHook('beforeCreate', 'myHook'); + this.Model.hooks.remove('beforeCreate', hook2); - return this.Model.runHooks('beforeCreate'); + return this.Model.hooks.run('beforeCreate'); }).then(() => { expect(hook1).to.have.been.calledOnce; expect(hook2).not.to.have.been.called; @@ -332,7 +320,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - describe('#addHook', () => { + describe('#hooks.add', () => { it('should add additional hook when previous exists', function() { const hook1 = sinon.spy(), hook2 = sinon.spy(); @@ -341,9 +329,9 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hooks: { beforeCreate: hook1 } }); - Model.addHook('beforeCreate', hook2); + Model.hooks.add('beforeCreate', hook2); - return Model.runHooks('beforeCreate').then(() => { + return Model.hooks.run('beforeCreate').then(() => { expect(hook1).to.have.been.calledOnce; expect(hook2).to.have.been.calledOnce; }); @@ -352,35 +340,35 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('promises', () => { it('can return a promise', function() { - this.Model.beforeBulkCreate(() => { + this.Model.hooks.add('beforeBulkCreate', () => { return Sequelize.Promise.resolve(); }); - return expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; + return expect(this.Model.hooks.run('beforeBulkCreate')).to.be.fulfilled; }); it('can return undefined', function() { - this.Model.beforeBulkCreate(() => { + this.Model.hooks.add('beforeBulkCreate', () => { // This space intentionally left blank }); - return expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; + return expect(this.Model.hooks.run('beforeBulkCreate')).to.be.fulfilled; }); it('can return an error by rejecting', function() { - this.Model.beforeCreate(() => { + this.Model.hooks.add('beforeCreate', () => { return Promise.reject(new Error('Forbidden')); }); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); + return expect(this.Model.hooks.run('beforeCreate')).to.be.rejectedWith('Forbidden'); }); it('can return an error by throwing', function() { - this.Model.beforeCreate(() => { + this.Model.hooks.add('beforeCreate', () => { throw new Error('Forbidden'); }); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); + return expect(this.Model.hooks.run('beforeCreate')).to.be.rejectedWith('Forbidden'); }); }); @@ -393,10 +381,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('runs all beforInit/afterInit hooks', function() { - Support.Sequelize.addHook('beforeInit', 'h1', this.hook1); - Support.Sequelize.addHook('beforeInit', 'h2', this.hook2); - Support.Sequelize.addHook('afterInit', 'h3', this.hook3); - Support.Sequelize.addHook('afterInit', 'h4', this.hook4); + Support.Sequelize.hooks.add('beforeInit', this.hook1); + Support.Sequelize.hooks.add('beforeInit', this.hook2); + Support.Sequelize.hooks.add('afterInit', this.hook3); + Support.Sequelize.hooks.add('afterInit', this.hook4); Support.createSequelizeInstance(); @@ -406,10 +394,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { expect(this.hook4).to.have.been.calledOnce; // cleanup hooks on Support.Sequelize - Support.Sequelize.removeHook('beforeInit', 'h1'); - Support.Sequelize.removeHook('beforeInit', 'h2'); - Support.Sequelize.removeHook('afterInit', 'h3'); - Support.Sequelize.removeHook('afterInit', 'h4'); + Support.Sequelize.hooks.remove('beforeInit', this.hook1); + Support.Sequelize.hooks.remove('beforeInit', this.hook2); + Support.Sequelize.hooks.remove('afterInit', this.hook3); + Support.Sequelize.hooks.remove('afterInit', this.hook4); Support.createSequelizeInstance(); @@ -422,10 +410,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('runs all beforDefine/afterDefine hooks', function() { const sequelize = Support.createSequelizeInstance(); - sequelize.addHook('beforeDefine', this.hook1); - sequelize.addHook('beforeDefine', this.hook2); - sequelize.addHook('afterDefine', this.hook3); - sequelize.addHook('afterDefine', this.hook4); + sequelize.hooks.add('beforeDefine', this.hook1); + sequelize.hooks.add('beforeDefine', this.hook2); + sequelize.hooks.add('afterDefine', this.hook3); + sequelize.hooks.add('afterDefine', this.hook4); sequelize.define('Test', {}); expect(this.hook1).to.have.been.calledOnce; expect(this.hook2).to.have.been.calledOnce; diff --git a/test/unit/instance-validator.test.js b/test/unit/instance-validator.test.js index 6e045aabc8a1..4cefb222e453 100644 --- a/test/unit/instance-validator.test.js +++ b/test/unit/instance-validator.test.js @@ -89,8 +89,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { it('should run beforeValidate and afterValidate hooks when _validate is successful', function() { const beforeValidate = sinon.spy(); const afterValidate = sinon.spy(); - this.User.beforeValidate(beforeValidate); - this.User.afterValidate(afterValidate); + this.User.hooks.add('beforeValidate', beforeValidate); + this.User.hooks.add('afterValidate', afterValidate); return expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.fulfilled.then(() => { expect(beforeValidate).to.have.been.calledOnce; @@ -103,8 +103,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const beforeValidate = sinon.spy(); const afterValidate = sinon.spy(); - this.User.beforeValidate(beforeValidate); - this.User.afterValidate(afterValidate); + this.User.hooks.add('beforeValidate', beforeValidate); + this.User.hooks.add('afterValidate', afterValidate); return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => { expect(beforeValidate).to.have.been.calledOnce; @@ -113,7 +113,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('should emit an error from after hook when afterValidate fails', function() { - this.User.afterValidate(() => { + this.User.hooks.add('afterValidate', () => { throw new Error('after validation error'); }); @@ -125,7 +125,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const validationFailedHook = sinon.spy(); - this.User.validationFailed(validationFailedHook); + this.User.hooks.add('validationFailed', validationFailedHook); return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => { expect(validationFailedHook).to.have.been.calledOnce; @@ -136,7 +136,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().resolves(); - this.User.validationFailed(validationFailedHook); + this.User.hooks.add('validationFailed', validationFailedHook); return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => { expect(err.name).to.equal('SequelizeValidationError'); @@ -147,7 +147,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingInstanceValidator = new InstanceValidator(new this.User()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().throws(new Error('validation failed hook error')); - this.User.validationFailed(validationFailedHook); + this.User.hooks.add('validationFailed', validationFailedHook); return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => { expect(err.message).to.equal('validation failed hook error'); diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index cc49ae9cdcd7..fa2f9f02b185 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -10,11 +10,25 @@ import Model, { ModelAttributes, ModelOptions, UpdateOptions, + SaveOptions, + UpsertOptions, + RestoreOptions, } from './model'; import { Config, Options, Sequelize, SyncOptions } from './sequelize'; +import { Association, AssociationOptions, Transaction } from '..'; export type HookReturn = Promise | void; +export interface AssociateBeforeData { + source: S; + target: T; + type: typeof Association; +} + +export interface AssociateAfterData extends AssociateBeforeData { + association: Association; +} + /** * Options for Model.init. We mostly duplicate the Hooks here, since there is no way to combine the two * interfaces. @@ -22,29 +36,51 @@ export type HookReturn = Promise | void; export interface ModelHooks { beforeValidate(instance: M, options: ValidationOptions): HookReturn; afterValidate(instance: M, options: ValidationOptions): HookReturn; + beforeCreate(attributes: M, options: CreateOptions): HookReturn; afterCreate(attributes: M, options: CreateOptions): HookReturn; + beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; + beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; + beforeSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; afterSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; + beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; + beforeBulkDestroy(options: DestroyOptions): HookReturn; afterBulkDestroy(options: DestroyOptions): HookReturn; + beforeBulkUpdate(options: UpdateOptions): HookReturn; afterBulkUpdate(options: UpdateOptions): HookReturn; + beforeFind(options: FindOptions): HookReturn; + afterFind(instancesOrInstance: M[] | M, options: FindOptions): HookReturn; + beforeCount(options: CountOptions): HookReturn; + beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; beforeFindAfterOptions(options: FindOptions): HookReturn; afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn; beforeSync(options: SyncOptions): HookReturn; afterSync(options: SyncOptions): HookReturn; + beforeBulkSync(options: SyncOptions): HookReturn; afterBulkSync(options: SyncOptions): HookReturn; + + beforeUpsert(values: object, options: UpsertOptions): HookReturn; + afterUpsert(instance: M, options: UpsertOptions): HookReturn; + + beforeAssociate(assoc: AssociateBeforeData, options: AssociationOptions): HookReturn; + afterAssociate(assoc: AssociateAfterData, options: AssociationOptions): HookReturn; + + beforeRestore(instance: M, options: RestoreOptions): HookReturn; + afterRestore(instance: M, options: RestoreOptions): HookReturn; + } export interface SequelizeHooks extends ModelHooks { @@ -61,50 +97,19 @@ export interface SequelizeHooks extends ModelHooks { /** * Virtual class for deduplication */ -export class Hooks { +export class Hooks { /** * Add a hook to the model - * - * @param name Provide a name for the hook function. It can be used to remove the hook later or to order - * hooks based on some sort of priority system in the future. - */ - public static addHook( - hookType: K, - name: string, - fn: SequelizeHooks[K] - ): C; - public static addHook( - hookType: K, - fn: SequelizeHooks[K] - ): C; - - /** - * Remove hook from the model */ - public static removeHook(hookType: K, name: string): C; + add(hookType: K, fn: H[K]): this; - /** - * Check whether the mode has any hooks of this type - */ - public static hasHook(hookType: K): boolean; - public static hasHooks(hookType: K): boolean; - - /** - * Add a hook to the model - * - * @param name Provide a name for the hook function. It can be used to remove the hook later or to order - * hooks based on some sort of priority system in the future. - */ - public addHook(hookType: K, name: string, fn: SequelizeHooks[K]): this; - public addHook(hookType: K, fn: SequelizeHooks[K]): this; /** * Remove hook from the model */ - public removeHook(hookType: K, name: string): this; + remove(hookType: K, fn: Function): this; /** * Check whether the mode has any hooks of this type */ - public hasHook(hookType: K): boolean; - public hasHooks(hookType: K): boolean; + has(hookType: K): boolean; } diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 21fbdc72978f..a0337f967e88 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -11,7 +11,7 @@ import { } from './associations/index'; import { DataType } from './data-types'; import { Deferrable } from './deferrable'; -import { HookReturn, Hooks, ModelHooks } from './hooks'; +import { HookReturn, SequelizeHooks, ModelHooks, Hooks } from './hooks'; import { ValidationOptions } from './instance-validator'; import { ModelManager } from './model-manager'; import Op = require('./operators'); @@ -1525,7 +1525,7 @@ export interface AddScopeOptions { override: boolean; } -export abstract class Model extends Hooks { +export abstract class Model { /** The name of the database table */ public static readonly tableName: string; @@ -1561,6 +1561,8 @@ export abstract class Model extends Hooks { */ public static readonly sequelize?: Sequelize; + public static readonly hooks: Hooks; + /** * Initialize a model, representing a table in the DB, with attributes and options. * @@ -2071,314 +2073,6 @@ export abstract class Model extends Hooks { */ public static unscoped(this: M): M; - /** - * A hook that is run before validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeValidate( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - public static beforeValidate( - this: { new (): M } & typeof Model, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - - /** - * A hook that is run after validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterValidate( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - public static afterValidate( - this: { new (): M } & typeof Model, - fn: (instance: M, options: ValidationOptions) => HookReturn - ): void; - - /** - * A hook that is run before creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static beforeCreate( - this: { new (): M } & typeof Model, - name: string, - fn: (attributes: M, options: CreateOptions) => HookReturn - ): void; - public static beforeCreate( - this: { new (): M } & typeof Model, - fn: (attributes: M, options: CreateOptions) => HookReturn - ): void; - - /** - * A hook that is run after creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static afterCreate( - this: { new (): M } & typeof Model, - name: string, - fn: (attributes: M, options: CreateOptions) => HookReturn - ): void; - public static afterCreate( - this: { new (): M } & typeof Model, - fn: (attributes: M, options: CreateOptions) => HookReturn - ): void; - - /** - * A hook that is run before destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeDestroy( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - public static beforeDestroy( - this: { new (): M } & typeof Model, - fn: (instance: Model, options: InstanceDestroyOptions) => HookReturn - ): void; - - /** - * A hook that is run after destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterDestroy( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - public static afterDestroy( - this: { new (): M } & typeof Model, - fn: (instance: M, options: InstanceDestroyOptions) => HookReturn - ): void; - - /** - * A hook that is run before updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeUpdate( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - public static beforeUpdate( - this: { new (): M } & typeof Model, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - - /** - * A hook that is run after updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterUpdate( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - public static afterUpdate( - this: { new (): M } & typeof Model, - fn: (instance: M, options: UpdateOptions) => HookReturn - ): void; - - /** - * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeSave( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - public static beforeSave( - this: { new (): M } & typeof Model, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - - /** - * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterSave( - this: { new (): M } & typeof Model, - name: string, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - public static afterSave( - this: { new (): M } & typeof Model, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn - ): void; - - /** - * A hook that is run before creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static beforeBulkCreate( - this: { new (): M } & typeof Model, - name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn - ): void; - public static beforeBulkCreate( - this: { new (): M } & typeof Model, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn - ): void; - - /** - * A hook that is run after creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static afterBulkCreate( - this: { new (): M } & typeof Model, - name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn - ): void; - public static afterBulkCreate( - this: { new (): M } & typeof Model, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn - ): void; - - /** - * A hook that is run before destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => HookReturn): void; - public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => HookReturn): void; - - /** - * A hook that is run after destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => HookReturn): void; - public static afterBulkDestroy(fn: (options: DestroyOptions) => HookReturn): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => HookReturn): void; - public static beforeBulkUpdate(fn: (options: UpdateOptions) => HookReturn): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => HookReturn): void; - public static afterBulkUpdate(fn: (options: UpdateOptions) => HookReturn): void; - - /** - * A hook that is run before a find (select) query - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFind(name: string, fn: (options: FindOptions) => HookReturn): void; - public static beforeFind(fn: (options: FindOptions) => HookReturn): void; - - /** - * A hook that is run before a count query - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeCount(name: string, fn: (options: CountOptions) => HookReturn): void; - public static beforeCount(fn: (options: CountOptions) => HookReturn): void; - - /** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => HookReturn): void; - public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => HookReturn): void; - - /** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => HookReturn): void; - public static beforeFindAfterOptions(fn: (options: FindOptions) => void): HookReturn; - - /** - * A hook that is run after a find (select) query - * - * @param name - * @param fn A callback function that is called with instance(s), options - */ - public static afterFind( - this: { new (): M } & typeof Model, - name: string, - fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn - ): void; - public static afterFind( - this: { new (): M } & typeof Model, - fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn - ): void; - - /** - * A hook that is run before sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run before Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterSync(fn: (options: SyncOptions) => HookReturn): void; - /** * Creates an association between this (the source) and the provided target. The foreign key is added * on the target. diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 16a99ef5904a..65880172c64e 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1,6 +1,6 @@ import * as DataTypes from './data-types'; import * as Deferrable from './deferrable'; -import { HookReturn, Hooks, SequelizeHooks } from './hooks'; +import { HookReturn, SequelizeHooks, Hooks } from './hooks'; import { ValidationOptions } from './instance-validator'; import { AndOperator, @@ -367,7 +367,7 @@ export interface QueryOptionsTransactionRequired {} * should also be installed in your project. You don't need to import it however, as * sequelize will take care of that. */ -export class Sequelize extends Hooks { +export class Sequelize { // -------------------- Utilities ------------------------------------------------------------------------ @@ -455,297 +455,6 @@ export class Sequelize extends Hooks { */ public static where: typeof where; - /** - * A hook that is run before validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public static beforeValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run after validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public static afterValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run before creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public static beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run after creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public static afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run before destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public static beforeDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run after destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public static afterDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run before updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public static beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public static afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static beforeSave(name: string, fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - public static beforeSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - - /** - * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public static afterSave(name: string, fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - public static afterSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - - /** - * A hook that is run before creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public static beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public static afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public static afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run before destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; - public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; - public static afterBulkDestroy(fn: (options: DestroyOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public static beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public static afterBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run before a find (select) query - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFind(name: string, fn: (options: FindOptions) => void): void; - public static beforeFind(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a connection is established - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeConnect(name: string, fn: (options: Config) => void): void; - public static beforeConnect(fn: (options: Config) => void): void; - - /** - * A hook that is run after a connection is established - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterConnect(name: string, fn: (connection: unknown, options: Config) => void): void; - public static afterConnect(fn: (connection: unknown, options: Config) => void): void; - - /** - * A hook that is run before a connection is released - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeDisconnect(name: string, fn: (connection: unknown) => void): void; - public static beforeDisconnect(fn: (connection: unknown) => void): void; - - /** - * A hook that is run after a connection is released - * - * @param name - * @param fn A callback function that is called with options - */ - public static afterDisconnect(name: string, fn: (connection: unknown) => void): void; - public static afterDisconnect(fn: (connection: unknown) => void): void; - - /** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; - public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param name - * @param fn A callback function that is called with options - */ - public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; - public static beforeFindAfterOptions(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run after a find (select) query - * - * @param name - * @param fn A callback function that is called with instance(s), options - */ - public static afterFind( - name: string, - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void - ): void; - public static afterFind( - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void - ): void; - - /** - * A hook that is run before a define call - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public static beforeDefine( - name: string, - fn: (attributes: ModelAttributes, options: ModelOptions) => void - ): void; - public static beforeDefine( - fn: (attributes: ModelAttributes, options: ModelOptions) => void - ): void; - - /** - * A hook that is run after a define call - * - * @param name - * @param fn A callback function that is called with factory - */ - public static afterDefine(name: string, fn: (model: typeof Model) => void): void; - public static afterDefine(fn: (model: typeof Model) => void): void; - - /** - * A hook that is run before Sequelize() call - * - * @param name - * @param fn A callback function that is called with config, options - */ - public static beforeInit(name: string, fn: (config: Config, options: Options) => void): void; - public static beforeInit(fn: (config: Config, options: Options) => void): void; - - /** - * A hook that is run after Sequelize() call - * - * @param name - * @param fn A callback function that is called with sequelize - */ - public static afterInit(name: string, fn: (sequelize: Sequelize) => void): void; - public static afterInit(fn: (sequelize: Sequelize) => void): void; - - /** - * A hook that is run before sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run before Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public static afterSync(fn: (options: SyncOptions) => HookReturn): void; - - /** * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. */ @@ -767,6 +476,9 @@ export class Sequelize extends Hooks { [key: string]: ModelCtor; }; + public readonly hooks: Hooks; + public static readonly hooks: Hooks; + /** * Instantiate sequelize with name of database, username and password * @@ -807,235 +519,6 @@ export class Sequelize extends Hooks { */ constructor(uri: string, options?: Options); - /** - * A hook that is run before validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public beforeValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public beforeValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run after validation - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public afterValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; - public afterValidate(fn: (instance: Model, options: ValidationOptions) => void): void; - - /** - * A hook that is run before creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run after creating a single instance - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; - - /** - * A hook that is run before destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public beforeDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public beforeDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run after destroying a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public afterDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - public afterDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; - - /** - * A hook that is run before updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating a single instance - * - * @param name - * @param fn A callback function that is called with instance, options - */ - public afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; - - /** - * A hook that is run before creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after creating instances in bulk - * - * @param name - * @param fn A callback function that is called with instances, options - */ - public afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; - - /** - * A hook that is run before destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; - public beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; - - /** - * A hook that is run after destroying instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; - public afterBulkDestroy(fn: (options: DestroyOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run after updating instances in bulk - * - * @param name - * @param fn A callback function that is called with options - */ - public afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public afterBulkUpdate(fn: (options: UpdateOptions) => void): void; - - /** - * A hook that is run before a find (select) query - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeFind(name: string, fn: (options: FindOptions) => void): void; - public beforeFind(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; - public beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run before a find (select) query, after all option parsing is complete - * - * @param name - * @param fn A callback function that is called with options - */ - public beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; - public beforeFindAfterOptions(fn: (options: FindOptions) => void): void; - - /** - * A hook that is run after a find (select) query - * - * @param name - * @param fn A callback function that is called with instance(s), options - */ - public afterFind( - name: string, - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void - ): void; - public afterFind(fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void): void; - - /** - * A hook that is run before a define call - * - * @param name - * @param fn A callback function that is called with attributes, options - */ - public beforeDefine(name: string, fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; - public beforeDefine(fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; - - /** - * A hook that is run after a define call - * - * @param name - * @param fn A callback function that is called with factory - */ - public afterDefine(name: string, fn: (model: typeof Model) => void): void; - public afterDefine(fn: (model: typeof Model) => void): void; - - /** - * A hook that is run before Sequelize() call - * - * @param name - * @param fn A callback function that is called with config, options - */ - public beforeInit(name: string, fn: (config: Config, options: Options) => void): void; - public beforeInit(fn: (config: Config, options: Options) => void): void; - - /** - * A hook that is run after Sequelize() call - * - * @param name - * @param fn A callback function that is called with sequelize - */ - public afterInit(name: string, fn: (sequelize: Sequelize) => void): void; - public afterInit(fn: (sequelize: Sequelize) => void): void; - - /** - * A hook that is run before sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after sequelize.sync call - * @param fn A callback function that is called with options passed to sequelize.sync - */ - public afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run before Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public beforeSync(fn: (options: SyncOptions) => HookReturn): void; - - /** - * A hook that is run after Model.sync call - * @param fn A callback function that is called with options passed to Model.sync - */ - public afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; - public afterSync(fn: (options: SyncOptions) => HookReturn): void; - /** * Returns the specified dialect. */ diff --git a/types/lib/transaction.d.ts b/types/lib/transaction.d.ts index 4b4faa09c3a5..2c74aa9ac298 100644 --- a/types/lib/transaction.d.ts +++ b/types/lib/transaction.d.ts @@ -2,6 +2,11 @@ import { Deferrable } from './deferrable'; import { Logging } from './model'; import { Promise } from './promise'; import { Sequelize } from './sequelize'; +import { Hooks } from './hooks'; + +export interface TransactionHooks { + afterCommit(): void; +} /** * The transaction object is used to identify a running transaction. It is created by calling @@ -10,6 +15,9 @@ import { Sequelize } from './sequelize'; * To run a query under a transaction, you should pass the transaction in the options object. */ export class Transaction { + + public readonly hooks: Hooks; + constructor(sequelize: Sequelize, options: TransactionOptions); /** @@ -21,11 +29,6 @@ export class Transaction { * Rollback (abort) the transaction */ public rollback(): Promise; - - /** - * Adds hook that is run after a transaction is committed - */ - public afterCommit(fn: (transaction: this) => void | Promise): void; } // tslint:disable-next-line no-namespace diff --git a/types/test/connection.ts b/types/test/connection.ts index 20d0354ddbb4..15e665e9d945 100644 --- a/types/test/connection.ts +++ b/types/test/connection.ts @@ -3,7 +3,7 @@ import { User } from 'models/User'; export const sequelize = new Sequelize('uri'); -sequelize.afterBulkSync((options: SyncOptions) => { +sequelize.hooks.add('afterBulkSync', (options: SyncOptions) => { console.log('synced'); }); diff --git a/types/test/hooks.ts b/types/test/hooks.ts index ffae3593ee8b..9c99d9b93673 100644 --- a/types/test/hooks.ts +++ b/types/test/hooks.ts @@ -5,11 +5,14 @@ import { ModelHooks } from "../lib/hooks"; * covers types/lib/sequelize.d.ts */ -Sequelize.beforeSave((t: TestModel, options: SaveOptions) => {}); -Sequelize.afterSave((t: TestModel, options: SaveOptions) => {}); -Sequelize.afterFind((t: TestModel[] | TestModel | null, options: FindOptions) => {}); -Sequelize.afterFind('namedAfterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {}); +Sequelize.hooks.add('beforeSave', (t: TestModel, options: SaveOptions) => {}); +Sequelize.hooks.add('afterSave', (t: TestModel, options: SaveOptions) => {}); +Sequelize.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {}); +Sequelize.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {}); +Sequelize.hooks.add('beforeSave', m => { + +}); /* * covers types/lib/hooks.d.ts */ @@ -33,14 +36,7 @@ const hooks: Partial = { TestModel.init({}, {sequelize, hooks }) -TestModel.addHook('beforeSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.addHook('afterSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.addHook('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { }); - -/* - * covers types/lib/model.d.ts - */ +TestModel.hooks.add('beforeSave', (t: TestModel, options: SaveOptions) => { }); +TestModel.hooks.add('afterSave', (t: TestModel, options: SaveOptions) => { }); +TestModel.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { }); -TestModel.beforeSave((t: TestModel, options: SaveOptions) => { }); -TestModel.afterSave((t: TestModel, options: SaveOptions) => { }); -TestModel.afterFind((t: TestModel | TestModel[] | null, options: FindOptions) => { }); diff --git a/types/test/models/User.ts b/types/test/models/User.ts index 83c1bb0c7fb6..91ab57558457 100644 --- a/types/test/models/User.ts +++ b/types/test/models/User.ts @@ -71,22 +71,26 @@ User.init( } ); -User.afterSync(() => { +User.hooks.add('afterSync', () => { sequelize.getQueryInterface().addIndex(User.tableName, { - fields: ['lastName'], - using: 'BTREE', - name: 'lastNameIdx', - concurrently: true, + fields: ['lastName'], + using: 'BTREE', + name: 'lastNameIdx', + concurrently: true, }) }) // Hooks -User.afterFind((users, options) => { +User.hooks.add('afterFind', (users: User[], options: FindOptions) => { console.log('found'); }); +User.hooks.add('afterBulkCreate', (users: User[]) => { + +}) + // TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly -User.addHook('beforeFind', 'test', (options: FindOptions) => { +User.hooks.add('beforeFind', (options: FindOptions) => { return undefined; }); diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts index 51866e317f00..f1f9b5f2fc36 100644 --- a/types/test/sequelize.ts +++ b/types/test/sequelize.ts @@ -21,29 +21,30 @@ const conn = sequelize.connectionManager; // hooks -sequelize.beforeCreate('test', () => { +sequelize.hooks.add('beforeCreate', () => { // noop }); sequelize - .addHook('beforeConnect', (config: Config) => { + .hooks.add('beforeConnect', (config: Config) => { // noop }) - .addHook('beforeBulkSync', () => { + .add('beforeBulkSync', () => { // noop }); -Sequelize.addHook('beforeCreate', () => { +Sequelize.hooks.add('beforeCreate', () => { // noop -}).addHook('beforeBulkCreate', () => { +}) +.add('beforeBulkCreate', () => { // noop }); -Sequelize.beforeConnect(() => { +Sequelize.hooks.add('beforeConnect', () => { }); -Sequelize.afterConnect(() => { +Sequelize.hooks.add('afterConnect', () => { }); diff --git a/types/test/transaction.ts b/types/test/transaction.ts index 8f4e7e02b997..ca2095359909 100644 --- a/types/test/transaction.ts +++ b/types/test/transaction.ts @@ -5,7 +5,7 @@ export const sequelize = new Sequelize('uri'); async function trans() { const a: number = await sequelize.transaction(async transaction => { - transaction.afterCommit(() => console.log('transaction complete')); + transaction.hooks.add('afterCommit', () => console.log('transaction complete')); User.create( { data: 123, From e02594fb5b8b18fe68a77a0a0f51620a9d96acb9 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Wed, 9 Oct 2019 15:06:42 -0700 Subject: [PATCH 017/414] ci: do not use v6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3b774b9b2c57..e08087eced3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ jobs: script: - npm run test-integration - stage: test - node_js: '6' + node_js: '8' sudo: required env: POSTGRES_VER=postgres-95 SEQ_PG_PORT=8990 DIALECT=postgres-native - stage: release From 8fe367e97f1820938f32834e52ddb07450d28173 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 14 Jan 2020 13:30:01 -0300 Subject: [PATCH 018/414] docs(manuals): extensive rewrite (#11825) --- docs/css/style.css | 4 + docs/index.md | 18 +- docs/manual-groups.json | 64 +- .../advanced-many-to-many.md | 620 +++++++++ .../association-scopes.md | 64 + .../creating-with-associations.md | 130 ++ .../eager-loading.md | 664 ++++++++++ .../polymorphic-associations.md | 427 ++++++ docs/manual/associations.md | 1175 ----------------- docs/manual/core-concepts/assocs.md | 778 +++++++++++ .../core-concepts/getters-setters-virtuals.md | 201 +++ docs/manual/core-concepts/getting-started.md | 111 ++ docs/manual/core-concepts/model-basics.md | 436 ++++++ docs/manual/core-concepts/model-instances.md | 172 +++ .../core-concepts/model-querying-basics.md | 700 ++++++++++ .../core-concepts/model-querying-finders.md | 83 ++ docs/manual/core-concepts/paranoid.md | 105 ++ .../manual/{ => core-concepts}/raw-queries.md | 151 ++- .../validations-and-constraints.md | 270 ++++ docs/manual/data-types.md | 330 ----- docs/manual/dialects.md | 96 -- docs/manual/getting-started.md | 243 ---- docs/manual/hooks.md | 403 ------ docs/manual/instances.md | 408 ------ docs/manual/migrations.md | 662 ---------- docs/manual/models-definition.md | 727 ---------- docs/manual/models-usage.md | 807 ----------- docs/manual/moved/associations.md | 16 + docs/manual/moved/data-types.md | 12 + docs/manual/moved/models-definition.md | 55 + docs/manual/moved/models-usage.md | 12 + docs/manual/moved/querying.md | 13 + docs/manual/other-topics/connection-pool.md | 17 + .../constraints-and-circularities.md | 113 ++ .../other-topics/dialect-specific-things.md | 195 +++ .../other-topics/extending-data-types.md | 113 ++ docs/manual/other-topics/hooks.md | 385 ++++++ docs/manual/other-topics/indexes.md | 47 + docs/manual/{ => other-topics}/legacy.md | 8 +- docs/manual/{ => other-topics}/legal.md | 0 docs/manual/other-topics/migrations.md | 537 ++++++++ docs/manual/other-topics/naming-strategies.md | 157 +++ .../manual/other-topics/optimistic-locking.md | 7 + docs/manual/other-topics/other-data-types.md | 192 +++ docs/manual/other-topics/query-interface.md | 152 +++ .../{ => other-topics}/read-replication.md | 10 +- docs/manual/{ => other-topics}/resources.md | 0 docs/manual/{ => other-topics}/scopes.md | 243 ++-- docs/manual/other-topics/sub-queries.md | 164 +++ docs/manual/other-topics/transactions.md | 311 +++++ docs/manual/{ => other-topics}/typescript.md | 9 +- .../{ => other-topics}/upgrade-to-v6.md | 20 +- docs/manual/{ => other-topics}/whos-using.md | 0 docs/manual/querying.md | 530 -------- docs/manual/transactions.md | 251 ---- docs/redirects.json | 4 + docs/redirects/create-redirects.js | 18 + docs/transforms/menu-groups.js | 47 +- lib/model.js | 27 +- lib/sequelize.js | 3 +- package.json | 2 +- types/lib/model.d.ts | 12 +- 62 files changed, 7604 insertions(+), 5927 deletions(-) create mode 100644 docs/manual/advanced-association-concepts/advanced-many-to-many.md create mode 100644 docs/manual/advanced-association-concepts/association-scopes.md create mode 100644 docs/manual/advanced-association-concepts/creating-with-associations.md create mode 100644 docs/manual/advanced-association-concepts/eager-loading.md create mode 100644 docs/manual/advanced-association-concepts/polymorphic-associations.md delete mode 100644 docs/manual/associations.md create mode 100644 docs/manual/core-concepts/assocs.md create mode 100644 docs/manual/core-concepts/getters-setters-virtuals.md create mode 100644 docs/manual/core-concepts/getting-started.md create mode 100644 docs/manual/core-concepts/model-basics.md create mode 100644 docs/manual/core-concepts/model-instances.md create mode 100644 docs/manual/core-concepts/model-querying-basics.md create mode 100644 docs/manual/core-concepts/model-querying-finders.md create mode 100644 docs/manual/core-concepts/paranoid.md rename docs/manual/{ => core-concepts}/raw-queries.md (54%) create mode 100644 docs/manual/core-concepts/validations-and-constraints.md delete mode 100644 docs/manual/data-types.md delete mode 100644 docs/manual/dialects.md delete mode 100644 docs/manual/getting-started.md delete mode 100644 docs/manual/hooks.md delete mode 100644 docs/manual/instances.md delete mode 100644 docs/manual/migrations.md delete mode 100644 docs/manual/models-definition.md delete mode 100644 docs/manual/models-usage.md create mode 100644 docs/manual/moved/associations.md create mode 100644 docs/manual/moved/data-types.md create mode 100644 docs/manual/moved/models-definition.md create mode 100644 docs/manual/moved/models-usage.md create mode 100644 docs/manual/moved/querying.md create mode 100644 docs/manual/other-topics/connection-pool.md create mode 100644 docs/manual/other-topics/constraints-and-circularities.md create mode 100644 docs/manual/other-topics/dialect-specific-things.md create mode 100644 docs/manual/other-topics/extending-data-types.md create mode 100644 docs/manual/other-topics/hooks.md create mode 100644 docs/manual/other-topics/indexes.md rename docs/manual/{ => other-topics}/legacy.md (92%) rename docs/manual/{ => other-topics}/legal.md (100%) create mode 100644 docs/manual/other-topics/migrations.md create mode 100644 docs/manual/other-topics/naming-strategies.md create mode 100644 docs/manual/other-topics/optimistic-locking.md create mode 100644 docs/manual/other-topics/other-data-types.md create mode 100644 docs/manual/other-topics/query-interface.md rename docs/manual/{ => other-topics}/read-replication.md (58%) rename docs/manual/{ => other-topics}/resources.md (100%) rename docs/manual/{ => other-topics}/scopes.md (51%) create mode 100644 docs/manual/other-topics/sub-queries.md create mode 100644 docs/manual/other-topics/transactions.md rename docs/manual/{ => other-topics}/typescript.md (98%) rename docs/manual/{ => other-topics}/upgrade-to-v6.md (81%) rename docs/manual/{ => other-topics}/whos-using.md (100%) delete mode 100644 docs/manual/querying.md delete mode 100644 docs/manual/transactions.md create mode 100644 docs/redirects.json create mode 100644 docs/redirects/create-redirects.js diff --git a/docs/css/style.css b/docs/css/style.css index 635ef42161b4..99187d3ab6c3 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -38,6 +38,10 @@ div.sequelize { font-size: 17px; } +.no-mouse { + pointer-events: none; +} + .api-reference-link { font-weight: bold; padding: 0 20px; diff --git a/docs/index.md b/docs/index.md index d1bc0cfe70ec..f62f464fc096 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,11 +18,11 @@ [![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/master/LICENSE) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. +Sequelize is a promise-based Node.js [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. -Sequelize follows [SEMVER](http://semver.org). Supports Node v10 and above to use ES6 features. +Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 and above. -You are currently looking at the **Tutorials and Guides** for Sequelize. You might also be interested in the [API Reference](identifiers). +You are currently looking at the **Tutorials and Guides** for Sequelize. You might also be interested in the [API Reference](identifiers.html). ## Quick example @@ -36,14 +36,14 @@ User.init({ birthday: DataTypes.DATE }, { sequelize, modelName: 'user' }); -sequelize.sync() - .then(() => User.create({ +(async () => { + await sequelize.sync(); + const jane = await User.create({ username: 'janedoe', birthday: new Date(1980, 6, 20) - })) - .then(jane => { - console.log(jane.toJSON()); }); + console.log(jane.toJSON()); +})(); ``` -To learn more about how to use Sequelize, read the tutorials available in the left menu. Begin with [Getting Started](manual/getting-started). +To learn more about how to use Sequelize, read the tutorials available in the left menu. Begin with [Getting Started](manual/getting-started.html). diff --git a/docs/manual-groups.json b/docs/manual-groups.json index cf7a3da64266..60e4151f12ed 100644 --- a/docs/manual-groups.json +++ b/docs/manual-groups.json @@ -1,26 +1,50 @@ { "Core Concepts": [ - "getting-started.md", - "dialects.md", - "data-types.md", - "models-definition.md", - "models-usage.md", - "hooks.md", - "querying.md", - "instances.md", - "associations.md", - "raw-queries.md" + "core-concepts/getting-started.md", + "core-concepts/model-basics.md", + "core-concepts/model-instances.md", + "core-concepts/model-querying-basics.md", + "core-concepts/model-querying-finders.md", + "core-concepts/getters-setters-virtuals.md", + "core-concepts/validations-and-constraints.md", + "core-concepts/raw-queries.md", + "core-concepts/assocs.md", + "core-concepts/paranoid.md" + ], + "Advanced Association Concepts": [ + "advanced-association-concepts/eager-loading.md", + "advanced-association-concepts/creating-with-associations.md", + "advanced-association-concepts/advanced-many-to-many.md", + "advanced-association-concepts/association-scopes.md", + "advanced-association-concepts/polymorphic-associations.md" ], "Other Topics": [ - "transactions.md", - "scopes.md", - "read-replication.md", - "migrations.md", - "resources.md", - "typescript.md", - "upgrade-to-v6.md", - "legacy.md", - "whos-using.md", - "legal.md" + "other-topics/dialect-specific-things.md", + "other-topics/transactions.md", + "other-topics/hooks.md", + "other-topics/query-interface.md", + "other-topics/naming-strategies.md", + "other-topics/scopes.md", + "other-topics/sub-queries.md", + "other-topics/other-data-types.md", + "other-topics/constraints-and-circularities.md", + "other-topics/extending-data-types.md", + "other-topics/indexes.md", + "other-topics/optimistic-locking.md", + "other-topics/read-replication.md", + "other-topics/connection-pool.md", + "other-topics/legacy.md", + "other-topics/migrations.md", + "other-topics/typescript.md", + "other-topics/resources.md", + "other-topics/upgrade-to-v6.md", + "other-topics/whos-using.md", + "other-topics/legal.md" + ], + "__hidden__": [ + "moved/associations.md", + "moved/data-types.md", + "moved/models-usage.md", + "moved/querying.md" ] } \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/advanced-many-to-many.md b/docs/manual/advanced-association-concepts/advanced-many-to-many.md new file mode 100644 index 000000000000..af155955f8fb --- /dev/null +++ b/docs/manual/advanced-association-concepts/advanced-many-to-many.md @@ -0,0 +1,620 @@ +# Advanced M:N Associations + +Make sure you have read the [associations guide](assocs.html) before reading this guide. + +Let's start with an example of a Many-to-Many relationship between `User` and `Profile`. + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + points: DataTypes.INTEGER +}, { timestamps: false }); +const Profile = sequelize.define('profile', { + name: DataTypes.STRING +}, { timestamps: false }); +``` + +The simplest way to define the Many-to-Many relationship is: + +```js +User.belongsToMany(Profile, { through: 'User_Profiles' }); +Profile.belongsToMany(User, { through: 'User_Profiles' }); +``` + +By passing a string to `through` above, we are asking Sequelize to automatically generate a model named `User_Profiles` as the *through table* (also known as junction table), with only two columns: `userId` and `profileId`. A composite unique key will be established on these two columns. + +We can also define ourselves a model to be used as the through table. + +```js +const User_Profile = sequelize.define('User_Profile', {}, { timestamps: false }); +User.belongsToMany(Profile, { through: User_Profile }); +Profile.belongsToMany(User, { through: User_Profile }); +``` + +The above has the exact same effect. Note that we didn't define any attributes on the `User_Profile` model. The fact that we passed it into a `belongsToMany` call tells sequelize to create the two attributes `userId` and `profileId` automatically, just like other associations also cause Sequelize to automatically add a column to one of the involved models. + +However, defining the model by ourselves has several advantages. We can, for example, define more columns on our through table: + +```js +const User_Profile = sequelize.define('User_Profile', { + selfGranted: DataTypes.BOOLEAN +}, { timestamps: false }); +User.belongsToMany(Profile, { through: User_Profile }); +Profile.belongsToMany(User, { through: User_Profile }); +``` + +With this, we can now track an extra information at the through table, namely the `selfGranted` boolean. For example, when calling the `user.addProfile()` we can pass values for the extra columns using the `through` option. + +Example: + +```js +const amidala = User.create({ username: 'p4dm3', points: 1000 }); +const queen = Profile.create({ name: 'Queen' }); +await amidala.addProfile(queen, { through: { selfGranted: false } }); +const result = await User.findOne({ + where: { username: 'p4dm3' } + include: Profile +}); +console.log(result); +``` + +Output: + +```json +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen", + "User_Profile": { + "userId": 4, + "profileId": 6, + "selfGranted": false + } + } + ] +} +``` + +You probably noticed that the `User_Profiles` table does not have an `id` field. As mentioned above, it has a composite unique key instead. The name of this composite unique key is chosen automatically by Sequelize but can be customized with the `uniqueKey` option: + +```js +User.belongsToMany(Profile, { through: User_Profiles, uniqueKey: 'my_custom_unique' }); +``` + +Another possibility, if desired, is to force the through table to have a primary key just like other standard tables. To do this, simply define the primary key in the model: + +```js +const User_Profile = sequelize.define('User_Profile', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + selfGranted: DataTypes.BOOLEAN +}, { timestamps: false }); +User.belongsToMany(Profile, { through: User_Profile }); +Profile.belongsToMany(User, { through: User_Profile }); +``` + +The above will still create two columns `userId` and `profileId`, of course, but instead of setting up a composite unique key on them, the model will use its `id` column as primary key. Everything else will still work just fine. + +## Through tables versus normal tables and the "Super Many-to-Many association" + +Now we will compare the usage of the last Many-to-Many setup shown above with the usual One-to-Many relationships, so that in the end we conclude with the concept of a *"Super Many-to-Many relationship"*. + +### Models recap (with minor rename) + +To make things easier to follow, let's rename our `User_Profile` model to `grant`. Note that everything works in the same way as before. Our models are: + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + points: DataTypes.INTEGER +}, { timestamps: false }); + +const Profile = sequelize.define('profile', { + name: DataTypes.STRING +}, { timestamps: false }); + +const Grant = sequelize.define('grant', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + selfGranted: DataTypes.BOOLEAN +}, { timestamps: false }); +``` + +We established a Many-to-Many relationship between `User` and `Profile` using the `Grant` model as the through table: + +```js +User.belongsToMany(Profile, { through: Grant }); +Profile.belongsToMany(User, { through: Grant }); +``` + +This automatically added the columns `userId` and `profileId` to the `Grant` model. + +**Note:** As shown above, we have chosen to force the `grant` model to have a single primary key (called `id`, as usual). This is necessary for the *Super Many-to-Many relationship* that will be defined soon. + +### Using One-to-Many relationships instead + +Instead of setting up the Many-to-Many relationship defined above, what if we did the following instead? + +```js +// Setup a One-to-Many relationship between User and Grant +User.hasMany(Grant); +Grant.belongsTo(User); + +// Also setup a One-to-Many relationship between Profile and Grant +Profile.hasMany(Grant); +Grant.belongsTo(Profile); +``` + +The result is essentially the same! This is because `User.hasMany(Grant)` and `Profile.hasMany(Grant)` will automatically add the `userId` and `profileId` columns to `Grant`, respectively. + +This shows that one Many-to-Many relationship isn't very different from two One-to-Many relationships. The tables in the database look the same. + +The only difference is when you try to perform an eager load with Sequelize. + +```js +// With the Many-to-Many approach, you can do: +User.findAll({ include: Profile }); +Profile.findAll({ include: User }); +// However, you can't do: +User.findAll({ include: Grant }); +Profile.findAll({ include: Grant }); +Grant.findAll({ include: User }); +Grant.findAll({ include: Profile }); + +// On the other hand, with the double One-to-Many approach, you can do: +User.findAll({ include: Grant }); +Profile.findAll({ include: Grant }); +Grant.findAll({ include: User }); +Grant.findAll({ include: Profile }); +// However, you can't do: +User.findAll({ include: Profile }); +Profile.findAll({ include: User }); +// Although you can emulate those with nested includes, as follows: +User.findAll({ + include: { + model: Grant, + include: Profile + } +}); // This emulates the `User.findAll({ include: Profile })`, however + // the resulting object structure is a bit different. The original + // structure has the form `user.profiles[].grant`, while the emulated + // structure has the form `user.grants[].profiles[]`. +``` + +### The best of both worlds: the Super Many-to-Many relationship + +We can simply combine both approaches shown above! + +```js +// The Super Many-to-Many relationship +User.belongsToMany(Profile, { through: Grant }); +Profile.belongsToMany(User, { through: Grant }); +User.hasMany(Grant); +Grant.belongsTo(User); +Profile.hasMany(Grant); +Grant.belongsTo(Profile); +``` + +This way, we can do all kinds of eager loading: + +```js +// All these work: +User.findAll({ include: Profile }); +Profile.findAll({ include: User }); +User.findAll({ include: Grant }); +Profile.findAll({ include: Grant }); +Grant.findAll({ include: User }); +Grant.findAll({ include: Profile }); +``` + +We can even perform all kinds of deeply nested includes: + +```js +User.findAll({ + include: [ + { + model: Grant, + include: [User, Profile] + }, + { + model: Profile, + include: { + model: User, + include: { + model: Grant, + include: [User, Profile] + } + } + } + ] +}); +``` + +## Aliases and custom key names + +Similarly to the other relationships, aliases can be defined for Many-to-Many relationships. + +Before proceeding, please recall [the aliasing example for `belongsTo`](assocs.html#defining-an-alias) on the [associations guide](assocs.html). Note that, in that case, defining an association impacts both the way includes are done (i.e. passing the association name) and the name Sequelize chooses for the foreign key (in that example, `leaderId` was created on the `Ship` model). + +Defining an alias for a `belongsToMany` association also impacts the way includes are performed: + +```js +Product.belongsToMany(Category, { as: 'groups', through: 'product_categories' }); +Category.belongsToMany(Product, { as: 'items', through: 'product_categories' }); + +// [...] + +await Product.findAll({ include: Category }); // This doesn't work + +await Product.findAll({ // This works, passing the alias + include: { + model: Category, + as: 'groups' + } +}); + +await Product.findAll({ include: 'groups' }); // This also works +``` + +However, defining an alias here has nothing to do with the foreign key names. The names of both foreign keys created in the through table are still constructed by Sequelize based on the name of the models being associated. This can readily be seen by inspecting the generated SQL for the through table in the example above: + +```sql +CREATE TABLE IF NOT EXISTS `product_categories` ( + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL, + `productId` INTEGER NOT NULL REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + `categoryId` INTEGER NOT NULL REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (`productId`, `categoryId`) +); +``` + +We can see that the foreign keys are `productId` and `categoryId`. To change these names, Sequelize accepts the options `foreignKey` and `otherKey` respectively (i.e., the `foreignKey` defines the key for the source model in the through relation, and `otherKey` defines it for the target model): + +```js +Product.belongsToMany(Category, { + through: 'product_categories', + foreignKey: 'objectId', // replaces `productId` + otherKey: 'typeId' // replaces `categoryId` +}); +Category.belongsToMany(Product, { + through: 'product_categories', + foreignKey: 'typeId', // replaces `categoryId` + otherKey: 'objectId' // replaces `productId` +}); +``` + +Generated SQL: + +```sql +CREATE TABLE IF NOT EXISTS `product_categories` ( + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL, + `objectId` INTEGER NOT NULL REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + `typeId` INTEGER NOT NULL REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (`objectId`, `typeId`) +); +``` + +As shown above, when you define a Many-to-Many relationship with two `belongsToMany` calls (which is the standard way), you should provide the `foreignKey` and `otherKey` options appropriately in both calls. If you pass these options in only one of the calls, the Sequelize behavior will be unreliable. + +## Self-references + +Sequelize supports self-referential Many-to-Many relationships, intuitively: + +```js +Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) +// This will create the table PersonChildren which stores the ids of the objects. +``` + +## Specifying attributes from the through table + +By default, when eager loading a many-to-many relationship, Sequelize will return data in the following structure (based on the first example in this guide): + +```json +// User.findOne({ include: Profile }) +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen", + "grant": { + "userId": 4, + "profileId": 6, + "selfGranted": false + } + } + ] +} +``` + +Notice that the outer object is an `User`, which has a field called `profiles`, which is a `Profile` array, such that each `Profile` comes with an extra field called `grant` which is a `Grant` instance. This is the default structure created by Sequelize when eager loading from a Many-to-Many relationship. + +However, if you want only some of the attributes of the through table, you can provide an array with the attributes you want in the `attributes` option. For example, if you only want the `selfGranted` attribute from the through table: + +```js +User.findOne({ + include: { + model: Profile, + through: { + attributes: ['selfGranted'] + } + } +}); +``` + +Output: + +```json +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen", + "grant": { + "selfGranted": false + } + } + ] +} +``` + +If you don't want the nested `grant` field at all, use `attributes: []`: + +```js +User.findOne({ + include: { + model: Profile, + through: { + attributes: [] + } + } +}); +``` + +Output: + +```json +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen" + } + ] +} +``` + +If you are using mixins (such as `user.getProfiles()`) instead of finder methods (such as `User.findAll()`), you have to use the `joinTableAttributes` option instead: + +```js +someUser.getProfiles({ joinTableAttributes: ['selfGranted'] }); +``` + +Output: + +```json +[ + { + "id": 6, + "name": "queen", + "grant": { + "selfGranted": false + } + } +] +``` + +## Many-to-many-to-many relationships and beyond + +Consider you are trying to model a game championship. There are players and teams. Teams play games. However, players can change teams in the middle of the championship (but not in the middle of a game). So, given one specific game, there are certain teams participating in that game, and each of these teams has a set of players (for that game). + +So we start by defining the three relevant models: + +```js +const Player = sequelize.define('Player', { username: DataTypes.STRING }); +const Team = sequelize.define('Team', { name: DataTypes.STRING }); +const Game = sequelize.define('Game', { name: DataTypes.INTEGER }); +``` + +Now, the question is: how to associate them? + +First, we note that: + +* One game has many teams associated to it (the ones that are playing that game); +* One team may have participated in many games. + +The above observations show that we need a Many-to-Many relationship between Game and Team. Let's use the Super Many-to-Many relationship as explained earlier in this guide: + +```js +// Super Many-to-Many relationship between Game and Team +const GameTeam = sequelize.define('GameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Team.belongsToMany(Game, { through: GameTeam }); +Game.belongsToMany(Team, { through: GameTeam }); +GameTeam.belongsTo(Game); +GameTeam.belongsTo(Team); +Game.hasMany(GameTeam); +Team.hasMany(GameTeam); +``` + +The part about players is trickier. We note that the set of players that form a team depends not only on the team (obviously), but also on which game is being considered. Therefore, we don't want a Many-to-Many relationship between Player and Team. We also don't want a Many-to-Many relationship between Player and Game. Instead of associating a Player to any of those models, what we need is an association between a Player and something like a *"team-game pair constraint"*, since it is the pair (team plus game) that defines which players belong there. So what we are looking for turns out to be precisely the junction model, GameTeam, itself! And, we note that, since a given *game-team pair* specifies many players, and on the other hand that the same player can participate of many *game-team pairs*, we need a Many-to-Many relationship between Player and GameTeam! + +To provide the greatest flexibility, let's use the Super Many-to-Many relationship construction here again: + +```js +// Super Many-to-Many relationship between Player and GameTeam +const PlayerGameTeam = sequelize.define('PlayerGameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Player.belongsToMany(GameTeam, { through: PlayerGameTeam }); +GameTeam.belongsToMany(Player, { through: PlayerGameTeam }); +PlayerGameTeam.belongsTo(Player); +PlayerGameTeam.belongsTo(GameTeam); +Player.hasMany(PlayerGameTeam); +GameTeam.hasMany(PlayerGameTeam); +``` + +The above associations achieve precisely what we want. Here is a full runnable example of this: + +```js +const { Sequelize, Op, Model, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:', { + define: { timestamps: false } // Just for less clutter in this example +}); +const Player = sequelize.define('Player', { username: DataTypes.STRING }); +const Team = sequelize.define('Team', { name: DataTypes.STRING }); +const Game = sequelize.define('Game', { name: DataTypes.INTEGER }); + +// We apply a Super Many-to-Many relationship between Game and Team +const GameTeam = sequelize.define('GameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Team.belongsToMany(Game, { through: GameTeam }); +Game.belongsToMany(Team, { through: GameTeam }); +GameTeam.belongsTo(Game); +GameTeam.belongsTo(Team); +Game.hasMany(GameTeam); +Team.hasMany(GameTeam); + +// We apply a Super Many-to-Many relationship between Player and GameTeam +const PlayerGameTeam = sequelize.define('PlayerGameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Player.belongsToMany(GameTeam, { through: PlayerGameTeam }); +GameTeam.belongsToMany(Player, { through: PlayerGameTeam }); +PlayerGameTeam.belongsTo(Player); +PlayerGameTeam.belongsTo(GameTeam); +Player.hasMany(PlayerGameTeam); +GameTeam.hasMany(PlayerGameTeam); + +(async () => { + + await sequelize.sync(); + await Player.bulkCreate([ + { username: 's0me0ne' }, + { username: 'empty' }, + { username: 'greenhead' }, + { username: 'not_spock' }, + { username: 'bowl_of_petunias' } + ]); + await Game.bulkCreate([ + { name: 'The Big Clash' }, + { name: 'Winter Showdown' }, + { name: 'Summer Beatdown' } + ]); + await Team.bulkCreate([ + { name: 'The Martians' }, + { name: 'The Earthlings' }, + { name: 'The Plutonians' } + ]); + + // Let's start defining which teams were in which games. This can be done + // in several ways, such as calling `.setTeams` on each game. However, for + // brevity, we will use direct `create` calls instead, referring directly + // to the IDs we want. We know that IDs are given in order starting from 1. + await GameTeam.bulkCreate([ + { GameId: 1, TeamId: 1 }, // this GameTeam will get id 1 + { GameId: 1, TeamId: 2 }, // this GameTeam will get id 2 + { GameId: 2, TeamId: 1 }, // this GameTeam will get id 3 + { GameId: 2, TeamId: 3 }, // this GameTeam will get id 4 + { GameId: 3, TeamId: 2 }, // this GameTeam will get id 5 + { GameId: 3, TeamId: 3 } // this GameTeam will get id 6 + ]); + + // Now let's specify players. + // For brevity, let's do it only for the second game (Winter Showdown). + // Let's say that that s0me0ne and greenhead played for The Martians, while + // not_spock and bowl_of_petunias played for The Plutonians: + await PlayerGameTeam.bulkCreate([ + // In 'Winter Showdown' (i.e. GameTeamIds 3 and 4): + { PlayerId: 1, GameTeamId: 3 }, // s0me0ne played for The Martians + { PlayerId: 3, GameTeamId: 3 }, // greenhead played for The Martians + { PlayerId: 4, GameTeamId: 4 }, // not_spock played for The Plutonians + { PlayerId: 5, GameTeamId: 4 } // bowl_of_petunias played for The Plutonians + ]); + + // Now we can make queries! + const game = await Game.findOne({ + where: { + name: "Winter Showdown" + }, + include: { + model: GameTeam, + include: [ + { + model: Player, + through: { attributes: [] } // Hide unwanted `PlayerGameTeam` nested object from results + }, + Team + ] + } + }); + + console.log(`Found game: "${game.name}"`); + for (let i = 0; i < game.GameTeams.length; i++) { + const team = game.GameTeams[i].Team; + const players = game.GameTeams[i].Players; + console.log(`- Team "${team.name}" played game "${game.name}" with the following players:`); + console.log(players.map(p => `--- ${p.username}`).join('\n')); + } + +})(); +``` + +Output: + +```text +Found game: "Winter Showdown" +- Team "The Martians" played game "Winter Showdown" with the following players: +--- s0me0ne +--- greenhead +- Team "The Plutonians" played game "Winter Showdown" with the following players: +--- not_spock +--- bowl_of_petunias +``` + +So this is how we can achieve a *many-to-many-to-many* relationship between three models in Sequelize, by taking advantage of the Super Many-to-Many relationship technique! + +This idea can be applied recursively for even more complex, *many-to-many-to-...-to-many* relationships (although at some point queries might become slow). \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/association-scopes.md b/docs/manual/advanced-association-concepts/association-scopes.md new file mode 100644 index 000000000000..cfd7353a546c --- /dev/null +++ b/docs/manual/advanced-association-concepts/association-scopes.md @@ -0,0 +1,64 @@ +# Association Scopes + +This section concerns association scopes, which are similar but not the same as [model scopes](scopes.html). + +Association scopes can be placed both on the associated model (the target of the association) and on the through table for Many-to-Many relationships. + +## Concept + +Similarly to how a [model scope](scopes.html) is automatically applied on the model static calls, such as `Model.scope('foo').findAll()`, an association scope is a rule (more precisely, a set of default attributes and options) that is automatically applied on instance calls from the model. Here, *instance calls* mean method calls that are called from an instance (rather than from the Model itself). Mixins are the main example of instance methods (`instance.getSomething`, `instance.setSomething`, `instance.addSomething` and `instance.createSomething`). + +Association scopes behave just like model scopes, in the sense that both cause an automatic application of things like `where` clauses to finder calls; the difference being that instead of applying to static finder calls (which is the case for model scopes), the association scopes automatically apply to instance finder calls (such as mixins). + +## Example + +A basic example of an association scope for the One-to-Many association between models `Foo` and `Bar` is shown below. + +* Setup: + + ```js + const Foo = sequelize.define('foo', { name: DataTypes.STRING }); + const Bar = sequelize.define('bar', { status: DataTypes.STRING }); + Foo.hasMany(Bar, { + scope: { + status: 'open' + }, + as: 'openBars' + }); + await sequelize.sync(); + const myFoo = await Foo.create({ name: "My Foo" }); + ``` + +* After this setup, calling `myFoo.getOpenBars()` generates the following SQL: + + ```sql + SELECT + `id`, `status`, `createdAt`, `updatedAt`, `fooId` + FROM `bars` AS `bar` + WHERE `bar`.`status` = 'open' AND `bar`.`fooId` = 1; + ``` + +With this we can see that upon calling the `.getOpenBars()` mixin, the association scope `{ status: 'open' }` was automatically applied into the `WHERE` clause of the generated SQL. + +## Achieving the same behavior with standard scopes + +We could have achieved the same behavior with standard scopes: + +```js +// Foo.hasMany(Bar, { +// scope: { +// status: 'open' +// }, +// as: 'openBars' +// }); + +Bar.addScope('open', { + where: { + status: 'open' + } +}); +Foo.hasMany(Bar); +Foo.hasMany(Bar.scope('testTitleScope'), { as: 'openBars' }); +``` + +With the above code, `myFoo.getOpenBars()` yields the same SQL shown above. \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/creating-with-associations.md b/docs/manual/advanced-association-concepts/creating-with-associations.md new file mode 100644 index 000000000000..60f5e80ea71d --- /dev/null +++ b/docs/manual/advanced-association-concepts/creating-with-associations.md @@ -0,0 +1,130 @@ +# Creating with Associations + +An instance can be created with nested association in one step, provided all elements are new. + +In contrast, performing updates and deletions involving nested objects is currently not possible. For that, you will have to perform each separate action explicitly. + +## BelongsTo / HasMany / HasOne association + +Consider the following models: + +```js +class Product extends Model {} +Product.init({ + title: Sequelize.STRING +}, { sequelize, modelName: 'product' }); +class User extends Model {} +User.init({ + firstName: Sequelize.STRING, + lastName: Sequelize.STRING +}, { sequelize, modelName: 'user' }); +class Address extends Model {} +Address.init({ + type: DataTypes.STRING, + line1: Sequelize.STRING, + line2: Sequelize.STRING, + city: Sequelize.STRING, + state: Sequelize.STRING, + zip: Sequelize.STRING, +}, { sequelize, modelName: 'address' }); + +// We save the return values of the association setup calls to use them later +Product.User = Product.belongsTo(User); +User.Addresses = User.hasMany(Address); +// Also works for `hasOne` +``` + +A new `Product`, `User`, and one or more `Address` can be created in one step in the following way: + +```js +return Product.create({ + title: 'Chair', + user: { + firstName: 'Mick', + lastName: 'Broadstone', + addresses: [{ + type: 'home', + line1: '100 Main St.', + city: 'Austin', + state: 'TX', + zip: '78704' + }] + } +}, { + include: [{ + association: Product.User, + include: [ User.Addresses ] + }] +}); +``` + +Observe the usage of the `include` option in the `Product.create` call. That is necessary for Sequelize to understand what you are trying to create along with the association. + +Note: here, our user model is called `user`, with a lowercase `u` - This means that the property in the object should also be `user`. If the name given to `sequelize.define` was `User`, the key in the object should also be `User`. Likewise for `addresses`, except it's pluralized being a `hasMany` association. + +## BelongsTo association with an alias + +The previous example can be extended to support an association alias. + +```js +const Creator = Product.belongsTo(User, { as: 'creator' }); + +return Product.create({ + title: 'Chair', + creator: { + firstName: 'Matt', + lastName: 'Hansen' + } +}, { + include: [ Creator ] +}); +``` + +## HasMany / BelongsToMany association + +Let's introduce the ability to associate a product with many tags. Setting up the models could look like: + +```js +class Tag extends Model {} +Tag.init({ + name: Sequelize.STRING +}, { sequelize, modelName: 'tag' }); + +Product.hasMany(Tag); +// Also works for `belongsToMany`. +``` + +Now we can create a product with multiple tags in the following way: + +```js +Product.create({ + id: 1, + title: 'Chair', + tags: [ + { name: 'Alpha'}, + { name: 'Beta'} + ] +}, { + include: [ Tag ] +}) +``` + +And, we can modify this example to support an alias as well: + +```js +const Categories = Product.hasMany(Tag, { as: 'categories' }); + +Product.create({ + id: 1, + title: 'Chair', + categories: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] +}, { + include: [{ + association: Categories, + as: 'categories' + }] +}) +``` \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/eager-loading.md b/docs/manual/advanced-association-concepts/eager-loading.md new file mode 100644 index 000000000000..73fbf24acad5 --- /dev/null +++ b/docs/manual/advanced-association-concepts/eager-loading.md @@ -0,0 +1,664 @@ +# Eager Loading + +As briefly mentioned in [the associations guide](assocs.html), eager Loading is the act of querying data of several models at once (one 'main' model and one or more associated models). At the SQL level, this is a query with one or more [joins](https://en.wikipedia.org/wiki/Join_(SQL)). + +When this is done, the associated models will be added by Sequelize in appropriately named, automatically created field(s) in the returned objects. + +In Sequelize, eager loading is mainly done by using the `include` option on a model finder query (such as `findOne`, `findAll`, etc). + +## Basic example + +Let's assume the following setup: + +```js +const User = sequelize.define('user', { name: DataTypes.STRING }, { timestamps: false }); +const Task = sequelize.define('task', { name: DataTypes.STRING }, { timestamps: false }); +const Tool = sequelize.define('tool', { + name: DataTypes.STRING, + size: DataTypes.STRING +}, { timestamps: false }); +User.hasMany(Task); +Task.belongsTo(User); +User.hasMany(Tool, { as: 'Instruments' }); +``` + +### Fetching a single associated element + +OK. So, first of all, let's load all tasks with their associated user: + +```js +const tasks = await Task.findAll({ include: User }); +console.log(JSON.stringify(tasks, null, 2)); +``` + +Output: + +```json +[{ + "name": "A Task", + "id": 1, + "userId": 1, + "user": { + "name": "John Doe", + "id": 1 + } +}] +``` + +Here, `tasks[0].user instanceof User` is `true`. This shows that when Sequelize fetches associated models, they are added to the output object as model instances. + +Above, the associated model was added to a new field called `user` in the fetched task. The name of this field was automatically chosen by Sequelize based on the name of the associated model, where its pluralized form is used when applicable (i.e., when the association is `hasMany` or `belongsToMany`). In other words, since `Task.belongsTo(User)`, a task is associated to one user, therefore the logical choice is the singular form (which Sequelize follows automatically). + +### Fetching all associated elements + +Now, instead of loading the user that is associated to a given task, we will do the opposite - we will find all tasks associated to a given user. + +The method call is essentially the same. The only difference is that now the extra field created in the query result uses the pluralized form (`tasks` in this case), and its value is an array of task instances (instead of a single instance, as above). + +```js +const users = await User.findAll({ include: Task }); +console.log(JSON.stringify(users, null, 2)); +``` + +Output: + +```json +[{ + "name": "John Doe", + "id": 1, + "tasks": [{ + "name": "A Task", + "id": 1, + "userId": 1 + }] +}] +``` + +Notice that the accessor (the `tasks` property in the resulting instance) is pluralized since the association is one-to-many. + +### Fetching an Aliased association + +If an association is aliased (using the `as` option), you must specify this alias when including the model. Instead of passing the model directly to the `include` option, you should instead provide an object with two options: `model` and `as`. + +Notice how the user's `Tool`s are aliased as `Instruments` above. In order to get that right you have to specify the model you want to load, as well as the alias: + +```js +const users = await User.findAll({ + include: { model: Tool, as: 'Instruments' } +}); +console.log(JSON.stringify(users, null, 2)); +``` + +Output: + +```json +[{ + "name": "John Doe", + "id": 1, + "Instruments": [{ + "name": "Scissor", + "id": 1, + "userId": 1 + }] +}] +``` + +You can also include by alias name by specifying a string that matches the association alias: + +```js +User.findAll({ include: 'Instruments' }); // Also works +User.findAll({ include: { association: 'Instruments' } }); // Also works +``` + +### Required eager loading + +When eager loading, we can force the query to return only records which have an associated model, effectively converting the query from the default `OUTER JOIN` to an `INNER JOIN`. This is done with the `required: true` option, as follows: + +```js +User.findAll({ + include: { + model: Task, + required: true + } +}); +``` + +This option also works on nested includes. + +### Eager loading filtered at the associated model level + +When eager loading, we can also filter the associated model using the `where` option, as in the following example: + +```js +User.findAll({ + include: { + model: Tool, + as: 'Instruments' + where: { + size: { + [Op.ne]: 'small' + } + } + } +}); +``` + +Generated SQL: + +```sql +SELECT + `user`.`id`, + `user`.`name`, + `Instruments`.`id` AS `Instruments.id`, + `Instruments`.`name` AS `Instruments.name`, + `Instruments`.`size` AS `Instruments.size`, + `Instruments`.`userId` AS `Instruments.userId` +FROM `users` AS `user` +INNER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` AND + `Instruments`.`size` != 'small'; +``` + +Note that the SQL query generated above will only fetch users that have at least one tool that matches the condition (of not being `small`, in this case). This is the case because, when the `where` option is used inside an `include`, Sequelize automatically sets the `required` option to `true`. This means that, instead of an `OUTER JOIN`, an `INNER JOIN` is done, returning only the parent models with at least one matching children. + +Note also that the `where` option used was converted into a condition for the `ON` clause of the `INNER JOIN`. In order to obtain a *top-level* `WHERE` clause, instead of an `ON` clause, something different must be done. This will be shown next. + +#### Referring to other columns + +If you want to apply a `WHERE` clause in an included model referring to a value from an associated model, you can simply use the `Sequelize.col` function, as show in the example below: + +```js +// Find all projects with a least one task where task.state === project.state +Project.findAll({ + include: { + model: Task, + where: { + state: Sequelize.col('project.state') + } + } +}) +``` + +### Complex where clauses at the top-level + +To obtain top-level `WHERE` clauses that involve nested columns, Sequelize provides a way to reference nested columns: the `'$nested.column$'` syntax. + +It can be used, for example, to move the where conditions from an included model from the `ON` condition to a top-level `WHERE` clause. + +```js +User.findAll({ + where: { + '$Instruments.size$': { [Op.ne]: 'small' } + }, + include: [{ + model: Tool, + as: 'Instruments' + }] +}); +``` + +Generated SQL: + +```sql +SELECT + `user`.`id`, + `user`.`name`, + `Instruments`.`id` AS `Instruments.id`, + `Instruments`.`name` AS `Instruments.name`, + `Instruments`.`size` AS `Instruments.size`, + `Instruments`.`userId` AS `Instruments.userId` +FROM `users` AS `user` +LEFT OUTER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` +WHERE `Instruments`.`size` != 'small'; +``` + +The `$nested.column$` syntax also works for columns that are nested several levels deep, such as `$some.super.deeply.nested.column$`. Therefore, you can use this to make complex filters on deeply nested columns. + +For a better understanding of all differences between the inner `where` option (used inside an `include`), with and without the `required` option, and a top-level `where` using the `$nested.column$` syntax, below we have four examples for you: + +```js +// Inner where, with default `required: true` +await User.findAll({ + include: { + model: Tool, + as: 'Instruments', + where: { + size: { [Op.ne]: 'small' } + } + } +}); + +// Inner where, `required: false` +await User.findAll({ + include: { + model: Tool, + as: 'Instruments', + where: { + size: { [Op.ne]: 'small' } + }, + required: false + } +}); + +// Top-level where, with default `required: false` +await User.findAll({ + where: { + '$Instruments.size$': { [Op.ne]: 'small' } + }, + include: { + model: Tool, + as: 'Instruments' + } +}); + +// Top-level where, `required: true` +await User.findAll({ + where: { + '$Instruments.size$': { [Op.ne]: 'small' } + }, + include: { + model: Tool, + as: 'Instruments', + required: true + } +}); +``` + +Generated SQLs, in order: + +```sql +-- Inner where, with default `required: true` +SELECT [...] FROM `users` AS `user` +INNER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` + AND `Instruments`.`size` != 'small'; + +-- Inner where, `required: false` +SELECT [...] FROM `users` AS `user` +LEFT OUTER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` + AND `Instruments`.`size` != 'small'; + +-- Top-level where, with default `required: false` +SELECT [...] FROM `users` AS `user` +LEFT OUTER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` +WHERE `Instruments`.`size` != 'small'; + +-- Top-level where, `required: true` +SELECT [...] FROM `users` AS `user` +INNER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` +WHERE `Instruments`.`size` != 'small'; +``` + +### Fetching with `RIGHT OUTER JOIN` (MySQL, MariaDB, PostgreSQL and MSSQL only) + +By default, associations are loaded using a `LEFT OUTER JOIN` - that is to say it only includes records from the parent table. You can change this behavior to a `RIGHT OUTER JOIN` by passing the `right` option, if the dialect you are using supports it. + +Currenly, SQLite does not support [right joins](https://www.sqlite.org/omitted.html). + +*Note:* `right` is only respected if `required` is false. + +```js +User.findAll({ + include: [{ + model: Task // will create a left join + }] +}); +User.findAll({ + include: [{ + model: Task, + right: true // will create a right join + }] +}); +User.findAll({ + include: [{ + model: Task, + required: true, + right: true // has no effect, will create an inner join + }] +}); +User.findAll({ + include: [{ + model: Task, + where: { name: { [Op.ne]: 'empty trash' } }, + right: true // has no effect, will create an inner join + }] +}); +User.findAll({ + include: [{ + model: Tool, + where: { name: { [Op.ne]: 'empty trash' } }, + required: false // will create a left join + }] +}); +User.findAll({ + include: [{ + model: Tool, + where: { name: { [Op.ne]: 'empty trash' } }, + required: false + right: true // will create a right join + }] +}); +``` + +## Multiple eager loading + +The `include` option can receive an array in order to fetch multiple associated models at once: + +```js +Foo.findAll({ + include: [ + { + model: Bar, + required: true + }, + { + model: Baz, + where: /* ... */ + }, + Qux // Shorthand syntax for { model: Qux } also works here + ] +}) +``` + +## Eager loading with Many-to-Many relationships + +When you perform eager loading on a model with a Belongs-to-Many relationship, Sequelize will fetch the junction table data as well, by default. For example: + +```js +const Foo = sequelize.define('Foo', { name: DataTypes.TEXT }); +const Bar = sequelize.define('Bar', { name: DataTypes.TEXT }); +Foo.belongsToMany(Bar, { through: 'Foo_Bar' }); +Bar.belongsToMany(Foo, { through: 'Foo_Bar' }); + +await sequelize.sync(); +const foo = await Foo.create({ name: 'foo' }); +const bar = await Bar.create({ name: 'bar' }); +await foo.addBar(bar); +const fetchedFoo = Foo.findOne({ include: Bar }); +console.log(JSON.stringify(fetchedFoo, null, 2)); +``` + +Output: + +```json +{ + "id": 1, + "name": "foo", + "Bars": [ + { + "id": 1, + "name": "bar", + "Foo_Bar": { + "FooId": 1, + "BarId": 1 + } + } + ] +} +``` + +Note that every bar instance eager loaded into the `"Bars"` property has an extra property called `Foo_Bar` which is the relevant Sequelize instance of the junction model. By default, Sequelize fetches all attributes from the junction table in order to build this extra property. + +However, you can specify which attributes you want fetched. This is done with the `attributes` option applied inside the `through` option of the include. For example: + +```js +Foo.findAll({ + include: [{ + model: Bar, + through: { + attributes: [/* list the wanted attributes here */] + } + }] +}); +``` + +If you don't want anything from the junction table, you can explicitly provide an empty array to the `attributes` option, and in this case nothing will be fetched and the extra property will not even be created: + +```js +Foo.findOne({ + include: { + model: Bar, + attributes: [] + } +}); +``` + +Output: + +```json +{ + "id": 1, + "name": "foo", + "Bars": [ + { + "id": 1, + "name": "bar" + } + ] +} +``` + +Whenever including a model from a Many-to-Many relationship, you can also apply a filter on the junction table. This is done with the `where` option applied inside the `through` option of the include. For example: + +```js +User.findAll({ + include: [{ + model: Project, + through: { + where: { + // Here, `completed` is a column present at the junction table + completed: true + } + } + }] +}); +``` + +Generated SQL (using SQLite): + +```sql +SELECT + `User`.`id`, + `User`.`name`, + `Projects`.`id` AS `Projects.id`, + `Projects`.`name` AS `Projects.name`, + `Projects->User_Project`.`completed` AS `Projects.User_Project.completed`, + `Projects->User_Project`.`UserId` AS `Projects.User_Project.UserId`, + `Projects->User_Project`.`ProjectId` AS `Projects.User_Project.ProjectId` +FROM `Users` AS `User` +LEFT OUTER JOIN `User_Projects` AS `Projects->User_Project` ON + `User`.`id` = `Projects->User_Project`.`UserId` +LEFT OUTER JOIN `Projects` AS `Projects` ON + `Projects`.`id` = `Projects->User_Project`.`ProjectId` AND + `Projects->User_Project`.`completed` = 1; +``` + +## Including everything + +To include all associated models, you can use the `all` and `nested` options: + +```js +// Fetch all models associated with User +User.findAll({ include: { all: true }}); + +// Fetch all models associated with User and their nested associations (recursively) +User.findAll({ include: { all: true, nested: true }}); +``` + +## Including soft deleted records + +In case you want to eager load soft deleted records you can do that by setting `include.paranoid` to `false`: + +```js +User.findAll({ + include: [{ + model: Tool, + as: 'Instruments', + where: { size: { [Op.ne]: 'small' } }, + paranoid: false + }] +}); +``` + +## Ordering eager loaded associations + +When you want to apply `ORDER` clauses to eager loaded models, you must use the top-level `order` option with augmented arrays, starting with the specification of the nested model you want to sort. + +This is better understood with examples. + +```js +Company.findAll({ + include: Division, + order: [ + // We start the order array with the model we want to sort + [Division, 'name', 'ASC'] + ] +}); +Company.findAll({ + include: Division, + order: [ + [Division, 'name', 'DESC'] + ] +}); +Company.findAll({ + // If the include uses an alias... + include: { model: Division, as: 'Div' }, + order: [ + // ...we use the same syntax from the include + // in the beginning of the order array + [{ model: Division, as: 'Div' }, 'name', 'DESC'] + ] +}); + +Company.findAll({ + // If we have includes nested in several levels... + include: { + model: Division, + include: Department + }, + order: [ + // ... we replicate the include chain of interest + // at the beginning of the order array + [Division, Department, 'name', 'DESC'] + ] +}); +``` + +In the case of many-to-many relationships, you are also able to sort by attributes in the through table. For example, assuming we have a Many-to-Many relationship between `Division` and `Department` whose junction model is `DepartmentDivision`, you can do: + +```js +Company.findAll({ + include: { + model: Division, + include: Department + }, + order: [ + [Division, DepartmentDivision, 'name', 'ASC'] + ] +}); +``` + +In all the above examples, you have noticed that the `order` option is used at the top-level. The only situation in which `order` also works inside the include option is when `separate: true` is used. In that case, the usage is as follows: + +```js +// This only works for `separate: true` (which in turn +// only works for has-many relationships). +User.findAll({ + include: { + model: Post, + separate: true, + order: [ + ['createdAt', 'DESC'] + ] + } +}); +``` + +### Complex ordering involving sub-queries + +Take a look at the [guide on sub-queries](sub-queries.html) for an example of how to use a sub-query to assist a more complex ordering. + +## Nested eager loading + +You can use nested eager loading to load all related models of a related model: + +```js +const users = await User.findAll({ + include: { + model: Tool, + as: 'Instruments', + include: { + model: Teacher, + include: [ /* etc */ ] + } + } +}); +console.log(JSON.stringify(users, null, 2)); +``` + +Output: + +```json +[{ + "name": "John Doe", + "id": 1, + "Instruments": [{ // 1:M and N:M association + "name": "Scissor", + "id": 1, + "userId": 1, + "Teacher": { // 1:1 association + "name": "Jimi Hendrix" + } + }] +}] +``` + +This will produce an outer join. However, a `where` clause on a related model will create an inner join and return only the instances that have matching sub-models. To return all parent instances, you should add `required: false`. + +```js +User.findAll({ + include: [{ + model: Tool, + as: 'Instruments', + include: [{ + model: Teacher, + where: { + school: "Woodstock Music School" + }, + required: false + }] + }] +}); +``` + +The query above will return all users, and all their instruments, but only those teachers associated with `Woodstock Music School`. + +## Using `findAndCountAll` with includes + +The `findAndCountAll` utility function supports includes. Only the includes that are marked as `required` will be considered in `count`. For example, if you want to find and count all users who have a profile: + +```js +User.findAndCountAll({ + include: [ + { model: Profile, required: true } + ], + limit: 3 +}); +``` + +Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted. Adding a `where` clause to the include automatically makes it required: + +```js +User.findAndCountAll({ + include: [ + { model: Profile, where: { active: true } } + ], + limit: 3 +}); +``` + +The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/polymorphic-associations.md b/docs/manual/advanced-association-concepts/polymorphic-associations.md new file mode 100644 index 000000000000..39ee46372626 --- /dev/null +++ b/docs/manual/advanced-association-concepts/polymorphic-associations.md @@ -0,0 +1,427 @@ +# Polymorphic Associations + +_**Note:** the usage of polymorphic associations in Sequelize, as outlined in this guide, should be done with caution. Don't just copy-paste code from here, otherwise you might easily make mistakes and introduce bugs in your code. Make sure you understand what is going on._ + +## Concept + +A **polymorphic association** consists on two (or more) associations happening with the same foreign key. + +For example, consider the models `Image`, `Video` and `Comment`. The first two represent something that a user might post. We want to allow comments to be placed in both of them. This way, we immediately think of establishing the following associations: + +* A One-to-Many association between `Image` and `Comment`: + + ```js + Image.hasMany(Comment); + Comment.belongsTo(Image); + ``` + +* A One-to-Many association between `Video` and `Comment`: + + ```js + Video.hasMany(Comment); + Comment.belongsTo(Video); + ``` + +However, the above would cause Sequelize to create two foreign keys on the `Comment` table: `ImageId` and `VideoId`. This is not ideal because this structure makes it look like a comment can be attached at the same time to one image and one video, which isn't true. Instead, what we really want here is precisely a polymorphic association, in which a `Comment` points to a single **Commentable**, an abstract polymorphic entity that represents one of `Image` or `Video`. + +Before proceeding to how to configure such an association, let's see how using it looks like: + +```js +const image = await Image.create({ url: "https://placekitten.com/408/287" }); +const comment = await image.createComment({ content: "Awesome!" }); + +console.log(comment.commentableId === image.id); // true + +// We can also retrieve which type of commentable a comment is associated to. +// The following prints the model name of the associated commentable instance. +console.log(comment.commentableType); // "Image" + +// We can use a polymorphic method to retrieve the associated commentable, without +// having to worry whether it's an Image or a Video. +const associatedCommentable = await comment.getCommentable(); + +// In this example, `associatedCommentable` is the same thing as `image`: +const isDeepEqual = require('deep-equal'); +console.log(isDeepEqual(image, commentable)); // true +``` + +## Configuring a One-to-Many polymorphic association + +To setup the polymorphic association for the example above (which is an example of One-to-Many polymorphic association), we have the following steps: + +* Define a string field called `commentableType` in the `Comment` model; +* Define the `hasMany` and `belongsTo` association between `Image`/`Video` and `Comment`: + * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; + * Specifying the appropriate [association scopes](association-scopes.html); +* To properly support lazy loading, define a new instance method on the `Comment` model called `getCommentable` which calls, under the hood, the correct mixin to fetch the appropriate commentable; +* To properly support eager loading, define an `afterFind` hook on the `Comment` model that automatically populates the `commentable` field in every instance; +* To prevent bugs/mistakes in eager loading, you can also delete the concrete fields `image` and `video` from Comment instances in the same `afterFind` hook, leaving only the abstract `commentable` field available. + +Here is an example: + +```js +// Helper function +const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`; + +class Image extends Model {} +Image.init({ + title: DataTypes.STRING, + url: DataTypes.STRING +}, { sequelize, modelName: 'image' }); + +class Video extends Model {} +Video.init({ + title: DataTypes.STRING, + text: DataTypes.STRING +}, { sequelize, modelName: 'video' }); + +class Comment extends Model { + getCommentable(options) { + if (!this.commentableType) return Promise.resolve(null); + const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`; + return this[mixinMethodName](options); + } +} +Comment.init({ + title: DataTypes.STRING, + commentableId: DataTypes.INTEGER, + commentableType: DataTypes.STRING +}, { sequelize, modelName: 'comment' }); + +Image.hasMany(Comment, { + foreignKey: 'commentableId', + constraints: false, + scope: { + commentableType: 'image' + } +}); +Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false }); + +Video.hasMany(Comment, { + foreignKey: 'commentableId', + constraints: false, + scope: { + commentableType: 'video' + } +}); +Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false }); + +Comment.addHook("afterFind", findResult => { + if (!Array.isArray(findResult)) findResult = [findResult]; + for (const instance of findResult) { + if (instance.commentableType === "image" && instance.image !== undefined) { + instance.commentable = instance.image; + } else if (instance.commentableType === "video" && instance.video !== undefined) { + instance.commentable = instance.video; + } + // To prevent mistakes: + delete instance.image; + delete instance.dataValues.image; + delete instance.video; + delete instance.dataValues.video; + } +}); +``` + +Since the `commentableId` column references several tables (two in this case), we cannot add a `REFERENCES` constraint to it. This is why the `constraints: false` option was used. + +Note that, in the code above: + +* The *Image -> Comment* association defined an association scope: `{ commentableType: 'image' }` +* The *Video -> Comment* association defined an association scope: `{ commentableType: 'video' }` + +These scopes are automatically applied when using the association functions (as explained in the [Association Scopes](association-scopes.html) guide). Some examples are below, with their generated SQL statements: + +* `image.getComments()`: + + ```sql + SELECT "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" + FROM "comments" AS "comment" + WHERE "comment"."commentableType" = 'image' AND "comment"."commentableId" = 1; + ``` + + Here we can see that `` `comment`.`commentableType` = 'image'`` was automatically added to the `WHERE` clause of the generated SQL. This is exactly the behavior we want. + +* `image.createComment({ title: 'Awesome!' })`: + + ```sql + INSERT INTO "comments" ( + "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" + ) VALUES ( + DEFAULT, 'Awesome!', 'image', 1, + '2018-04-17 05:36:40.454 +00:00', '2018-04-17 05:36:40.454 +00:00' + ) RETURNING *; + ``` + +* `image.addComment(comment)`: + + ```sql + UPDATE "comments" + SET "commentableId"=1, "commentableType"='image', "updatedAt"='2018-04-17 05:38:43.948 +00:00' + WHERE "id" IN (1) + ``` + +### Polymorphic lazy loading + +The `getCommentable` instance method on `Comment` provides an abstraction for lazy loading the associated commentable - working whether the comment belongs to an Image or a Video. + +It works by simply converting the `commentableType` string into a call to the correct mixin (either `getImage` or `getVideo`). + +Note that the `getCommentable` implementation above: + +* Returns `null` when no association is present (which is good); +* Allows you to pass an options object to `getCommentable(options)`, just like any other standard Sequelize method. This is useful to specify where-conditions or includes, for example. + +### Polymorphic eager loading + +Now, we want to perform a polymorphic eager loading of the associated commentables for one (or more) comments. We want to achieve something similar to the following idea: + +```js +const comment = await Comment.findOne({ + include: [ /* What to put here? */ ] +}); +console.log(comment.commentable); // This is our goal +``` + +The solution is to tell Sequelize to include both Images and Videos, so that our `afterFind` hook defined above will do the work, automatically adding the `commentable` field to the instance object, providing the abstraction we want. + +For example: + +```js +const comments = await Comment.findAll({ + include: [Image, Video] +}); +for (const comment of comments) { + const message = `Found comment #${comment.id} with ${comment.commentableType} commentable:`; + console.log(message, comment.commentable.toJSON()); +} +``` + +Output example: + +```text +Found comment #1 with image commentable: { id: 1, + title: 'Meow', + url: 'https://placekitten.com/408/287', + createdAt: 2019-12-26T15:04:53.047Z, + updatedAt: 2019-12-26T15:04:53.047Z } +``` + +### Caution - possibly invalid eager/lazy loading! + +Consider a comment `Foo` whose `commentableId` is 2 and `commentableType` is `image`. Consider also that `Image A` and `Video X` both happen to have an id equal to 2. Conceptually, it is clear that `Video X` is not associated to `Foo`, because even though its id is 2, the `commentableType` of `Foo` is `image`, not `video`. However, this distinction is made by Sequelize only at the level of the abstractions performed by `getCommentable` and the hook we created above. + +This means that if you call `Comment.findAll({ include: Video })` in the situation above, `Video X` will be eager loaded into `Foo`. Thankfully, our `afterFind` hook will delete it automatically, to help prevent bugs, but regardless it is important that you understand what is going on. + +The best way to prevent this kind of mistake is to **avoid using the concrete accessors and mixins directly at all costs** (such as `.image`, `.getVideo()`, `.setImage()`, etc), always preferring the abstractions we created, such as `.getCommentable()` and `.commentable`. If you really need to access eager-loaded `.image` and `.video` for some reason, make sure you wrap that in a type check such as `comment.commentableType === 'image'`. + +## Configuring a Many-to-Many polymorphic association + +In the above example, we had the models `Image` and `Video` being abstractly called *commentables*, with one *commentable* having many comments. However, one given comment would belong to a single *commentable* - this is why the whole situation is a One-to-Many polymorphic association. + +Now, to consider a Many-to-Many polymorphic association, instead of considering comments, we will consider tags. For convenience, instead of calling Image and Video as *commentables*, we will now call them *taggables*. One *taggable* may have several tags, and at the same time one tag can be placed in several *taggables*. + +The setup for this goes as follows: + +* Define the juncion model explicitly, specifying the two foreign keys as `tagId` and `taggableId` (this way it is a junction model for a Many-to-Many relationship between `Tag` and the abstract concept of *taggable*); +* Define a string field called `taggableType` in the junction model; +* Define the `belongsToMany` associations between the two models and `Tag`: + * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; + * Specifying the appropriate [association scopes](association-scopes.html); +* Define a new instance method on the `Tag` model called `getTaggables` which calls, under the hood, the correct mixin to fetch the appropriate taggables. + +Implementation: + +```js +class Tag extends Model { + getTaggables(options) { + const images = await this.getImages(options); + const videos = await this.getVideos(options); + // Concat images and videos in a single array of taggables + return images.concat(videos); + } +} +Tag.init({ + name: DataTypes.STRING +}, { sequelize, modelName: 'tag' }); + +// Here we define the junction model explicitly +class Tag_Taggable extends Model {} +Tag_Taggable.init({ + tagId: { + type: DataTypes.INTEGER, + unique: 'tt_unique_constraint' + }, + taggableId: { + type: DataTypes.INTEGER, + unique: 'tt_unique_constraint', + references: null + }, + taggableType: { + type: DataTypes.STRING, + unique: 'tt_unique_constraint' + } +}, { sequelize, modelName: 'tag_taggable' }); + +Image.belongsToMany(Tag, { + through: { + model: Tag_Taggable, + unique: false, + scope: { + taggableType: 'image' + } + }, + foreignKey: 'taggableId', + constraints: false +}); +Tag.belongsToMany(Image, { + through: { + model: Tag_Taggable, + unique: false + }, + foreignKey: 'tagId', + constraints: false +}); + +Video.belongsToMany(Tag, { + through: { + model: Tag_Taggable, + unique: false, + scope: { + taggableType: 'video' + } + }, + foreignKey: 'taggableId', + constraints: false +}); +Tag.belongsToMany(Video, { + through: { + model: Tag_Taggable, + unique: false + }, + foreignKey: 'tagId', + constraints: false +}); +``` + +The `constraints: false` option disables references constraints, as the `taggableId` column references several tables, we cannot add a `REFERENCES` constraint to it. + +Note that: + +* The *Image -> Tag* association defined an association scope: `{ taggableType: 'image' }` +* The *Video -> Tag* association defined an association scope: `{ taggableType: 'video' }` + +These scopes are automatically applied when using the association functions. Some examples are below, with their generated SQL statements: + +* `image.getTags()`: + + ```sql + SELECT + `tag`.`id`, + `tag`.`name`, + `tag`.`createdAt`, + `tag`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` + FROM `tags` AS `tag` + INNER JOIN `tag_taggables` AS `tag_taggable` ON + `tag`.`id` = `tag_taggable`.`tagId` AND + `tag_taggable`.`taggableId` = 1 AND + `tag_taggable`.`taggableType` = 'image'; + ``` + + Here we can see that `` `tag_taggable`.`taggableType` = 'image'`` was automatically added to the `WHERE` clause of the generated SQL. This is exactly the behavior we want. + +* `tag.getTaggables()`: + + ```sql + SELECT + `image`.`id`, + `image`.`url`, + `image`.`createdAt`, + `image`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` + FROM `images` AS `image` + INNER JOIN `tag_taggables` AS `tag_taggable` ON + `image`.`id` = `tag_taggable`.`taggableId` AND + `tag_taggable`.`tagId` = 1; + + SELECT + `video`.`id`, + `video`.`url`, + `video`.`createdAt`, + `video`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` + FROM `videos` AS `video` + INNER JOIN `tag_taggables` AS `tag_taggable` ON + `video`.`id` = `tag_taggable`.`taggableId` AND + `tag_taggable`.`tagId` = 1; + ``` + +Note that the above implementation of `getTaggables()` allows you to pass an options object to `getCommentable(options)`, just like any other standard Sequelize method. This is useful to specify where-conditions or includes, for example. + +### Applying scopes on the target model + +In the example above, the `scope` options (such as `scope: { taggableType: 'image' }`) were applied to the *through* model, not the *target* model, since it was used under the `through` option. + +We can also apply an association scope on the target model. We can even do both at the same time. + +To illustrate this, consider an extension of the above example between tags and taggables, where each tag has a status. This way, to get all pending tags of an image, we could establish another `belognsToMany` relationship between `Image` and `Tag`, this time applying a scope on the through model and another scope on the target model: + +```js +Image.belongsToMany(Tag, { + through: { + model: Tag_Taggable, + unique: false, + scope: { + taggableType: 'image' + } + }, + scope: { + status: 'pending' + }, + as: 'pendingTags', + foreignKey: 'taggableId', + constraints: false +}); +``` + +This way, when calling `image.getPendingTags()`, the following SQL query will be generated: + +```sql +SELECT + `tag`.`id`, + `tag`.`name`, + `tag`.`status`, + `tag`.`createdAt`, + `tag`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` +FROM `tags` AS `tag` +INNER JOIN `tag_taggables` AS `tag_taggable` ON + `tag`.`id` = `tag_taggable`.`tagId` AND + `tag_taggable`.`taggableId` = 1 AND + `tag_taggable`.`taggableType` = 'image' +WHERE ( + `tag`.`status` = 'pending' +); +``` + +We can see that both scopes were applied automatically: + +* `` `tag_taggable`.`taggableType` = 'image'`` was added automatically to the `INNER JOIN`; +* `` `tag`.`status` = 'pending'`` was added automatically to an outer where clause. \ No newline at end of file diff --git a/docs/manual/associations.md b/docs/manual/associations.md deleted file mode 100644 index 3a3c2f6062d5..000000000000 --- a/docs/manual/associations.md +++ /dev/null @@ -1,1175 +0,0 @@ -# Associations - -This section describes the various association types in sequelize. There are four types of -associations available in Sequelize - -1. BelongsTo -2. HasOne -3. HasMany -4. BelongsToMany - -## Basic Concepts - -### Source & Target - -Let's first begin with a basic concept that you will see used in most associations, **source** and **target** model. Suppose you are trying to add an association between two Models. Here we are adding a `hasOne` association between `User` and `Project`. - -```js -class User extends Model {} -User.init({ - name: Sequelize.STRING, - email: Sequelize.STRING -}, { - sequelize, - modelName: 'user' -}); - -class Project extends Model {} -Project.init({ - name: Sequelize.STRING -}, { - sequelize, - modelName: 'project' -}); - -User.hasOne(Project); -``` - -`User` model (the model that the function is being invoked on) is the __source__. `Project` model (the model being passed as an argument) is the __target__. - -### Foreign Keys - -When you create associations between your models in sequelize, foreign key references with constraints will automatically be created. The setup below: - -```js -class Task extends Model {} -Task.init({ title: Sequelize.STRING }, { sequelize, modelName: 'task' }); -class User extends Model {} -User.init({ username: Sequelize.STRING }, { sequelize, modelName: 'user' }); - -User.hasMany(Task); // Will add userId to Task model -Task.belongsTo(User); // Will also add userId to Task model -``` - -Will generate the following SQL: - -```sql -CREATE TABLE IF NOT EXISTS "users" ( - "id" SERIAL, - "username" VARCHAR(255), - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - PRIMARY KEY ("id") -); - -CREATE TABLE IF NOT EXISTS "tasks" ( - "id" SERIAL, - "title" VARCHAR(255), - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "userId" INTEGER REFERENCES "users" ("id") ON DELETE - SET - NULL ON UPDATE CASCADE, - PRIMARY KEY ("id") -); -``` - -The relation between `tasks` and `users` model injects the `userId` foreign key on `tasks` table, and marks it as a reference to the `users` table. By default `userId` will be set to `NULL` if the referenced user is deleted, and updated if the id of the `userId` updated. These options can be overridden by passing `onUpdate` and `onDelete` options to the association calls. The validation options are `RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL`. - -For 1:1 and 1:m associations the default option is `SET NULL` for deletion, and `CASCADE` for updates. For n:m, the default for both is `CASCADE`. This means, that if you delete or update a row from one side of an n:m association, all the rows in the join table referencing that row will also be deleted or updated. - -#### underscored option - -Sequelize allow setting `underscored` option for Model. When `true` this option will set the -`field` option on all attributes to the underscored version of its name. This also applies to -foreign keys generated by associations. - -Let's modify last example to use `underscored` option. - -```js -class Task extends Model {} -Task.init({ - title: Sequelize.STRING -}, { - underscored: true, - sequelize, - modelName: 'task' -}); - -class User extends Model {} -User.init({ - username: Sequelize.STRING -}, { - underscored: true, - sequelize, - modelName: 'user' -}); - -// Will add userId to Task model, but field will be set to `user_id` -// This means column name will be `user_id` -User.hasMany(Task); - -// Will also add userId to Task model, but field will be set to `user_id` -// This means column name will be `user_id` -Task.belongsTo(User); -``` - -Will generate the following SQL: - -```sql -CREATE TABLE IF NOT EXISTS "users" ( - "id" SERIAL, - "username" VARCHAR(255), - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - PRIMARY KEY ("id") -); - -CREATE TABLE IF NOT EXISTS "tasks" ( - "id" SERIAL, - "title" VARCHAR(255), - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "user_id" INTEGER REFERENCES "users" ("id") ON DELETE - SET - NULL ON UPDATE CASCADE, - PRIMARY KEY ("id") -); -``` - -With the underscored option attributes injected to model are still camel cased but `field` option is set to their underscored version. - -#### Cyclic dependencies & Disabling constraints - -Adding constraints between tables means that tables must be created in the database in a certain order, when using `sequelize.sync`. If `Task` has a reference to `User`, the `users` table must be created before the `tasks` table can be created. This can sometimes lead to circular references, where sequelize cannot find an order in which to sync. Imagine a scenario of documents and versions. A document can have multiple versions, and for convenience, a document has a reference to its current version. - -```js -class Document extends Model {} -Document.init({ - author: Sequelize.STRING -}, { sequelize, modelName: 'document' }); -class Version extends Model {} -Version.init({ - timestamp: Sequelize.DATE -}, { sequelize, modelName: 'version' }); - -Document.hasMany(Version); // This adds documentId attribute to version -Document.belongsTo(Version, { - as: 'Current', - foreignKey: 'currentVersionId' -}); // This adds currentVersionId attribute to document -``` - -However, the code above will result in the following error: `Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents`. - -In order to alleviate that, we can pass `constraints: false` to one of the associations: - -```js -Document.hasMany(Version); -Document.belongsTo(Version, { - as: 'Current', - foreignKey: 'currentVersionId', - constraints: false -}); -``` - -Which will allow us to sync the tables correctly: - -```sql -CREATE TABLE IF NOT EXISTS "documents" ( - "id" SERIAL, - "author" VARCHAR(255), - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "currentVersionId" INTEGER, - PRIMARY KEY ("id") -); - -CREATE TABLE IF NOT EXISTS "versions" ( - "id" SERIAL, - "timestamp" TIMESTAMP WITH TIME ZONE, - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE - SET - NULL ON UPDATE CASCADE, - PRIMARY KEY ("id") -); -``` - -#### Enforcing a foreign key reference without constraints - -Sometimes you may want to reference another table, without adding any constraints, or associations. In that case you can manually add the reference attributes to your schema definition, and mark the relations between them. - -```js -class Trainer extends Model {} -Trainer.init({ - firstName: Sequelize.STRING, - lastName: Sequelize.STRING -}, { sequelize, modelName: 'trainer' }); - -// Series will have a trainerId = Trainer.id foreign reference key -// after we call Trainer.hasMany(series) -class Series extends Model {} -Series.init({ - title: Sequelize.STRING, - subTitle: Sequelize.STRING, - description: Sequelize.TEXT, - // Set FK relationship (hasMany) with `Trainer` - trainerId: { - type: Sequelize.INTEGER, - references: { - model: Trainer, - key: 'id' - } - } -}, { sequelize, modelName: 'series' }); - -// Video will have seriesId = Series.id foreign reference key -// after we call Series.hasOne(Video) -class Video extends Model {} -Video.init({ - title: Sequelize.STRING, - sequence: Sequelize.INTEGER, - description: Sequelize.TEXT, - // set relationship (hasOne) with `Series` - seriesId: { - type: Sequelize.INTEGER, - references: { - model: Series, // Can be both a string representing the table name or a Sequelize model - key: 'id' - } - } -}, { sequelize, modelName: 'video' }); - -Series.hasOne(Video); -Trainer.hasMany(Series); -``` - -## One-To-One associations - -One-To-One associations are associations between exactly two models connected by a single foreign key. - -### BelongsTo - -BelongsTo associations are associations where the foreign key for the one-to-one relation exists on the **source model**. - -A simple example would be a **Player** being part of a **Team** with the foreign key on the player. - -```js -class Player extends Model {} -Player.init({/* attributes */}, { sequelize, modelName: 'player' }); -class Team extends Model {} -Team.init({/* attributes */}, { sequelize, modelName: 'team' }); - -Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team -``` - -#### Foreign keys - -By default the foreign key for a belongsTo relation will be generated from the target model name and the target primary key name. - -The default casing is `camelCase`. If the source model is configured with `underscored: true` the foreignKey will be created with field `snake_case`. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -// will add companyId to user -User.belongsTo(Company); - -class User extends Model {} -User.init({/* attributes */}, { underscored: true, sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({ - uuid: { - type: Sequelize.UUID, - primaryKey: true - } -}, { sequelize, modelName: 'company' }); - -// will add companyUuid to user with field company_uuid -User.belongsTo(Company); -``` - -In cases where `as` has been defined it will be used in place of the target model name. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class UserRole extends Model {} -UserRole.init({/* attributes */}, { sequelize, modelName: 'userRole' }); - -User.belongsTo(UserRole, {as: 'role'}); // Adds roleId to user rather than userRoleId -``` - -In all cases the default foreign key can be overwritten with the `foreignKey` option. -When the foreign key option is used, Sequelize will use it as-is: - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User -``` - -#### Target keys - -The target key is the column on the target model that the foreign key column on the source model points to. By default the target key for a belongsTo relation will be the target model's primary key. To define a custom column, use the `targetKey` option. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User -``` - -### HasOne - -HasOne associations are associations where the foreign key for the one-to-one relation exists on the **target model**. - -```js -class User extends Model {} -User.init({/* ... */}, { sequelize, modelName: 'user' }) -class Project extends Model {} -Project.init({/* ... */}, { sequelize, modelName: 'project' }) - -// One-way associations -Project.hasOne(User) - -/* - In this example hasOne will add an attribute projectId to the User model! - Furthermore, Project.prototype will gain the methods getUser and setUser according - to the first parameter passed to define. If you have underscore style - enabled, the added attribute will be project_id instead of projectId. - - The foreign key will be placed on the users table. - - You can also define the foreign key, e.g. if you already have an existing - database and want to work on it: -*/ - -Project.hasOne(User, { foreignKey: 'initiator_id' }) - -/* - Because Sequelize will use the model's name (first parameter of define) for - the accessor methods, it is also possible to pass a special option to hasOne: -*/ - -Project.hasOne(User, { as: 'Initiator' }) -// Now you will get Project.getInitiator and Project.setInitiator - -// Or let's define some self references -class Person extends Model {} -Person.init({ /* ... */}, { sequelize, modelName: 'person' }) - -Person.hasOne(Person, {as: 'Father'}) -// this will add the attribute FatherId to Person - -// also possible: -Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'}) -// this will add the attribute DadId to Person - -// In both cases you will be able to do: -Person.setFather -Person.getFather - -// If you need to join a table twice you can double join the same table -Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'}); -Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'}); - -Game.belongsTo(Team); -``` - -Even though it is called a HasOne association, for most 1:1 relations you usually want the BelongsTo association since BelongsTo will add the foreignKey on the source where hasOne will add on the target. - -#### Source keys - -The source key is the attribute on the source model that the foreign key attribute on the target model points to. By default the source key for a `hasOne` relation will be the source model's primary attribute. To use a custom attribute, use the `sourceKey` option. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -// Adds companyName attribute to User -// Use name attribute from Company as source attribute -Company.hasOne(User, {foreignKey: 'companyName', sourceKey: 'name'}); -``` - -### Difference between HasOne and BelongsTo - -In Sequelize 1:1 relationship can be set using HasOne and BelongsTo. They are suitable for different scenarios. Lets study this difference using an example. - -Suppose we have two tables to link **Player** and **Team**. Lets define their models. - -```js -class Player extends Model {} -Player.init({/* attributes */}, { sequelize, modelName: 'player' }) -class Team extends Model {} -Team.init({/* attributes */}, { sequelize, modelName: 'team' }); -``` - -When we link two models in Sequelize we can refer them as pairs of **source** and **target** models. Like this - -Having **Player** as the **source** and **Team** as the **target** - -```js -Player.belongsTo(Team); -//Or -Player.hasOne(Team); -``` - -Having **Team** as the **source** and **Player** as the **target** - -```js -Team.belongsTo(Player); -//Or -Team.hasOne(Player); -``` - -HasOne and BelongsTo insert the association key in different models from each other. HasOne inserts the association key in **target** model whereas BelongsTo inserts the association key in the **source** model. - -Here is an example demonstrating use cases of BelongsTo and HasOne. - -```js -class Player extends Model {} -Player.init({/* attributes */}, { sequelize, modelName: 'player' }) -class Coach extends Model {} -Coach.init({/* attributes */}, { sequelize, modelName: 'coach' }) -class Team extends Model {} -Team.init({/* attributes */}, { sequelize, modelName: 'team' }); -``` - -Suppose our `Player` model has information about its team as `teamId` column. Information about each Team's `Coach` is stored in the `Team` model as `coachId` column. These both scenarios requires different kind of 1:1 relation because foreign key relation is present on different models each time. - -When information about association is present in **source** model we can use `belongsTo`. In this case `Player` is suitable for `belongsTo` because it has `teamId` column. - -```js -Player.belongsTo(Team) // `teamId` will be added on Player / Source model -``` - -When information about association is present in **target** model we can use `hasOne`. In this case `Coach` is suitable for `hasOne` because `Team` model store information about its `Coach` as `coachId` field. - -```js -Coach.hasOne(Team) // `coachId` will be added on Team / Target model -``` - -## One-To-Many associations (hasMany) - -One-To-Many associations are connecting one source with multiple targets. The targets however are again connected to exactly one specific source. - -```js -class User extends Model {} -User.init({/* ... */}, { sequelize, modelName: 'user' }) -class Project extends Model {} -Project.init({/* ... */}, { sequelize, modelName: 'project' }) - -// OK. Now things get more complicated (not really visible to the user :)). -// First let's define a hasMany association -Project.hasMany(User, {as: 'Workers'}) -``` - -This will add the attribute `projectId` to User. Depending on your setting for underscored the column in the table will either be called `projectId` or `project_id`. Instances of Project will get the accessors `getWorkers` and `setWorkers`. - -Sometimes you may need to associate records on different columns, you may use `sourceKey` option: - -```js -class City extends Model {} -City.init({ countryCode: Sequelize.STRING }, { sequelize, modelName: 'city' }); -class Country extends Model {} -Country.init({ isoCode: Sequelize.STRING }, { sequelize, modelName: 'country' }); - -// Here we can connect countries and cities base on country code -Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'}); -City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'}); -``` - -So far we dealt with a one-way association. But we want more! Let's define it the other way around by creating a many to many association in the next section. - -## Belongs-To-Many associations - -Belongs-To-Many associations are used to connect sources with multiple targets. Furthermore the targets can also have connections to multiple sources. - -```js -Project.belongsToMany(User, {through: 'UserProject'}); -User.belongsToMany(Project, {through: 'UserProject'}); -``` - -This will create a new model called UserProject with the equivalent foreign keys `projectId` and `userId`. Whether the attributes are camelcase or not depends on the two models joined by the table (in this case User and Project). - -Defining `through` is **required**. Sequelize would previously attempt to autogenerate names but that would not always lead to the most logical setups. - -This will add methods `getUsers`, `setUsers`, `addUser`,`addUsers` to `Project`, and `getProjects`, `setProjects`, `addProject`, and `addProjects` to `User`. - -Sometimes you may want to rename your models when using them in associations. Let's define users as workers and projects as tasks by using the alias (`as`) option. We will also manually define the foreign keys to use: - -```js -User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' }) -Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' }) -``` - -`foreignKey` will allow you to set **source model** key in the **through** relation. -`otherKey` will allow you to set **target model** key in the **through** relation. - -```js -User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'}) -``` - -Of course you can also define self references with belongsToMany: - -```js -Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) -// This will create the table PersonChildren which stores the ids of the objects. - -``` - -#### Source and target keys - -If you want to create a belongs to many relationship that does not use the default primary key some setup work is required. -You must set the `sourceKey` (optionally `targetKey`) appropriately for the two ends of the belongs to many. Further you must also ensure you have appropriate indexes created on your relationships. For example: - -```js -const User = this.sequelize.define('User', { - id: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'user_id' - }, - userSecondId: { - type: DataTypes.UUID, - allowNull: false, - defaultValue: DataTypes.UUIDV4, - field: 'user_second_id' - } -}, { - tableName: 'tbl_user', - indexes: [ - { - unique: true, - fields: ['user_second_id'] - } - ] -}); - -const Group = this.sequelize.define('Group', { - id: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'group_id' - }, - groupSecondId: { - type: DataTypes.UUID, - allowNull: false, - defaultValue: DataTypes.UUIDV4, - field: 'group_second_id' - } -}, { - tableName: 'tbl_group', - indexes: [ - { - unique: true, - fields: ['group_second_id'] - } - ] -}); - -User.belongsToMany(Group, { - through: 'usergroups', - sourceKey: 'userSecondId' -}); -Group.belongsToMany(User, { - through: 'usergroups', - sourceKey: 'groupSecondId' -}); -``` - -If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one: - -```js -class User extends Model {} -User.init({}, { sequelize, modelName: 'user' }) -class Project extends Model {} -Project.init({}, { sequelize, modelName: 'project' }) -class UserProjects extends Model {} -UserProjects.init({ - status: DataTypes.STRING -}, { sequelize, modelName: 'userProjects' }) - -User.belongsToMany(Project, { through: UserProjects }) -Project.belongsToMany(User, { through: UserProjects }) -``` - -To add a new project to a user and set its status, you pass extra `options.through` to the setter, which contains the attributes for the join table - -```js -user.addProject(project, { through: { status: 'started' }}) -``` - -By default the code above will add projectId and userId to the UserProjects table, and _remove any previously defined primary key attribute_ - the table will be uniquely identified by the combination of the keys of the two tables, and there is no reason to have other PK columns. To enforce a primary key on the `UserProjects` model you can add it manually. - -```js -class UserProjects extends Model {} -UserProjects.init({ - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true - }, - status: DataTypes.STRING -}, { sequelize, modelName: 'userProjects' }) -``` - -With Belongs-To-Many you can query based on **through** relation and select specific attributes. For example using `findAll` with **through** - -```js -User.findAll({ - include: [{ - model: Project, - through: { - attributes: ['createdAt', 'startedAt', 'finishedAt'], - where: {completed: true} - } - }] -}); -``` - -Belongs-To-Many creates a unique key when primary key is not present on through model. This unique key name can be overridden using **uniqueKey** option. - -```js -Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' }) -``` - -## Naming strategy - -By default sequelize will use the model name (the name passed to `sequelize.define`) to figure out the name of the model when used in associations. For example, a model named `user` will add the functions `get/set/add User` to instances of the associated model, and a property named `.user` in eager loading, while a model named `User` will add the same functions, but a property named `.User` (notice the upper case U) in eager loading. - -As we've already seen, you can alias models in associations using `as`. In single associations (has one and belongs to), the alias should be singular, while for many associations (has many) it should be plural. Sequelize then uses the [inflection][0] library to convert the alias to its singular form. However, this might not always work for irregular or non-english words. In this case, you can provide both the plural and the singular form of the alias: - -```js -User.belongsToMany(Project, { as: { singular: 'task', plural: 'tasks' }}) -// Notice that inflection has no problem singularizing tasks, this is just for illustrative purposes. -``` - -If you know that a model will always use the same alias in associations, you can provide it when creating the model - -```js -class Project extends Model {} -Project.init(attributes, { - name: { - singular: 'task', - plural: 'tasks', - }, - sequelize, - modelName: 'project' -}) - -User.belongsToMany(Project); -``` - -This will add the functions `add/set/get Tasks` to user instances. - -Remember, that using `as` to change the name of the association will also change the name of the foreign key. When using `as`, it is safest to also specify the foreign key. - -```js -Invoice.belongsTo(Subscription) -Subscription.hasMany(Invoice) -``` - -Without `as`, this adds `subscriptionId` as expected. However, if you were to say `Invoice.belongsTo(Subscription, { as: 'TheSubscription' })`, you will have both `subscriptionId` and `theSubscriptionId`, because sequelize is not smart enough to figure that the calls are two sides of the same relation. 'foreignKey' fixes this problem; - -```js -Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' }) -Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' }) -``` - -## Associating objects - -Because Sequelize is doing a lot of magic, you have to call `Sequelize.sync` after setting the associations! Doing so will allow you the following: - -```js -Project.hasMany(Task) -Task.belongsTo(Project) - -Project.create()... -Task.create()... -Task.create()... - -// save them... and then: -project.setTasks([task1, task2]).then(() => { - // saved! -}) - -// ok, now they are saved... how do I get them later on? -project.getTasks().then(associatedTasks => { - // associatedTasks is an array of tasks -}) - -// You can also pass filters to the getter method. -// They are equal to the options you can pass to a usual finder method. -project.getTasks({ where: 'id > 10' }).then(tasks => { - // tasks with an id greater than 10 :) -}) - -// You can also only retrieve certain fields of a associated object. -project.getTasks({attributes: ['title']}).then(tasks => { - // retrieve tasks with the attributes "title" and "id" -}) -``` - -To remove created associations you can just call the set method without a specific id: - -```js -// remove the association with task1 -project.setTasks([task2]).then(associatedTasks => { - // you will get task2 only -}) - -// remove 'em all -project.setTasks([]).then(associatedTasks => { - // you will get an empty array -}) - -// or remove 'em more directly -project.removeTask(task1).then(() => { - // it's gone -}) - -// and add 'em again -project.addTask(task1).then(() => { - // it's back again -}) -``` - -You can of course also do it vice versa: - -```js -// project is associated with task1 and task2 -task2.setProject(null).then(() => { - // and it's gone -}) -``` - -For hasOne/belongsTo it's basically the same: - -```js -Task.hasOne(User, {as: "Author"}) -Task.setAuthor(anAuthor) -``` - -Adding associations to a relation with a custom join table can be done in two ways (continuing with the associations defined in the previous chapter): - -```js -// Either by adding a property with the name of the join table model to the object, before creating the association -project.UserProjects = { - status: 'active' -} -u.addProject(project) - -// Or by providing a second options.through argument when adding the association, containing the data that should go in the join table -u.addProject(project, { through: { status: 'active' }}) - - -// When associating multiple objects, you can combine the two options above. In this case the second argument -// will be treated as a defaults object, that will be used if no data is provided -project1.UserProjects = { - status: 'inactive' -} - -u.setProjects([project1, project2], { through: { status: 'active' }}) -// The code above will record inactive for project one, and active for project two in the join table -``` - -When getting data on an association that has a custom join table, the data from the join table will be returned as a DAO instance: - -```js -u.getProjects().then(projects => { - const project = projects[0] - - if (project.UserProjects.status === 'active') { - // .. do magic - - // since this is a real DAO instance, you can save it directly after you are done doing magic - return project.UserProjects.save() - } -}) -``` - -If you only need some of the attributes from the join table, you can provide an array with the attributes you want: - -```js -// This will select only name from the Projects table, and only status from the UserProjects table -user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']}) -``` - -## Check associations - -You can also check if an object is already associated with another one (N:M only). Here is how you'd do it: - -```js -// check if an object is one of associated ones: -Project.create({ /* */ }).then(project => { - return User.create({ /* */ }).then(user => { - return project.hasUser(user).then(result => { - // result would be false - return project.addUser(user).then(() => { - return project.hasUser(user).then(result => { - // result would be true - }) - }) - }) - }) -}) - -// check if all associated objects are as expected: -// let's assume we have already a project and two users -project.setUsers([user1, user2]).then(() => { - return project.hasUsers([user1]); -}).then(result => { - // result would be true - return project.hasUsers([user1, user2]); -}).then(result => { - // result would be true -}) -``` - -## Advanced Concepts - -### Scopes - -This section concerns association scopes. For a definition of association scopes vs. scopes on associated models, see [Scopes](scopes.html). - -Association scopes allow you to place a scope (a set of default attributes for `get` and `create`) on the association. Scopes can be placed both on the associated model (the target of the association), and on the through table for n:m relations. - -#### 1:n - -Assume we have models Comment, Post, and Image. A comment can be associated to either an image or a post via `commentableId` and `commentable` - we say that Post and Image are `Commentable` - -```js -class Post extends Model {} -Post.init({ - title: Sequelize.STRING, - text: Sequelize.STRING -}, { sequelize, modelName: 'post' }); - -class Image extends Model {} -Image.init({ - title: Sequelize.STRING, - link: Sequelize.STRING -}, { sequelize, modelName: 'image' }); - -class Comment extends Model { - getItem(options) { - return this[ - 'get' + - this.get('commentable') - [0] - .toUpperCase() + - this.get('commentable').substr(1) - ](options); - } -} - -Comment.init({ - title: Sequelize.STRING, - commentable: Sequelize.STRING, - commentableId: Sequelize.INTEGER -}, { sequelize, modelName: 'comment' }); - -Post.hasMany(Comment, { - foreignKey: 'commentableId', - constraints: false, - scope: { - commentable: 'post' - } -}); - -Comment.belongsTo(Post, { - foreignKey: 'commentableId', - constraints: false, - as: 'post' -}); - -Image.hasMany(Comment, { - foreignKey: 'commentableId', - constraints: false, - scope: { - commentable: 'image' - } -}); - -Comment.belongsTo(Image, { - foreignKey: 'commentableId', - constraints: false, - as: 'image' -}); -``` - -`constraints: false` disables references constraints, as `commentableId` column references several tables, we cannot add a `REFERENCES` constraint to it. - -Note that the Image -> Comment and Post -> Comment relations define a scope, `commentable: 'image'` and `commentable: 'post'` respectively. This scope is automatically applied when using the association functions: - -```js -image.getComments() -// SELECT "id", "title", "commentable", "commentableId", "createdAt", "updatedAt" FROM "comments" AS -// "comment" WHERE "comment"."commentable" = 'image' AND "comment"."commentableId" = 1; - -image.createComment({ - title: 'Awesome!' -}) -// INSERT INTO "comments" ("id","title","commentable","commentableId","createdAt","updatedAt") VALUES -// (DEFAULT,'Awesome!','image',1,'2018-04-17 05:36:40.454 +00:00','2018-04-17 05:36:40.454 +00:00') -// RETURNING *; - -image.addComment(comment); -// UPDATE "comments" SET "commentableId"=1,"commentable"='image',"updatedAt"='2018-04-17 05:38:43.948 -// +00:00' WHERE "id" IN (1) -``` - -The `getItem` utility function on `Comment` completes the picture - it simply converts the `commentable` string into a call to either `getImage` or `getPost`, providing an abstraction over whether a comment belongs to a post or an image. You can pass a normal options object as a parameter to `getItem(options)` to specify any where conditions or includes. - -#### n:m - -Continuing with the idea of a polymorphic model, consider a tag table - an item can have multiple tags, and a tag can be related to several items. - -For brevity, the example only shows a Post model, but in reality Tag would be related to several other models. - -```js -class ItemTag extends Model {} -ItemTag.init({ - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true - }, - tagId: { - type: Sequelize.INTEGER, - unique: 'item_tag_taggable' - }, - taggable: { - type: Sequelize.STRING, - unique: 'item_tag_taggable' - }, - taggableId: { - type: Sequelize.INTEGER, - unique: 'item_tag_taggable', - references: null - } -}, { sequelize, modelName: 'item_tag' }); - -class Tag extends Model {} -Tag.init({ - name: Sequelize.STRING, - status: Sequelize.STRING -}, { sequelize, modelName: 'tag' }); - -Post.belongsToMany(Tag, { - through: { - model: ItemTag, - unique: false, - scope: { - taggable: 'post' - } - }, - foreignKey: 'taggableId', - constraints: false -}); - -Tag.belongsToMany(Post, { - through: { - model: ItemTag, - unique: false - }, - foreignKey: 'tagId', - constraints: false -}); -``` - -Notice that the scoped column (`taggable`) is now on the through model (`ItemTag`). - -We could also define a more restrictive association, for example, to get all pending tags for a post by applying a scope of both the through model (`ItemTag`) and the target model (`Tag`): - -```js -Post.belongsToMany(Tag, { - through: { - model: ItemTag, - unique: false, - scope: { - taggable: 'post' - } - }, - scope: { - status: 'pending' - }, - as: 'pendingTags', - foreignKey: 'taggableId', - constraints: false -}); - -post.getPendingTags(); -``` - -```sql -SELECT - "tag"."id", - "tag"."name", - "tag"."status", - "tag"."createdAt", - "tag"."updatedAt", - "item_tag"."id" AS "item_tag.id", - "item_tag"."tagId" AS "item_tag.tagId", - "item_tag"."taggable" AS "item_tag.taggable", - "item_tag"."taggableId" AS "item_tag.taggableId", - "item_tag"."createdAt" AS "item_tag.createdAt", - "item_tag"."updatedAt" AS "item_tag.updatedAt" -FROM - "tags" AS "tag" - INNER JOIN "item_tags" AS "item_tag" ON "tag"."id" = "item_tag"."tagId" - AND "item_tag"."taggableId" = 1 - AND "item_tag"."taggable" = 'post' -WHERE - ("tag"."status" = 'pending'); -``` - -`constraints: false` disables references constraints on the `taggableId` column. Because the column is polymorphic, we cannot say that it `REFERENCES` a specific table. - -### Creating with associations - -An instance can be created with nested association in one step, provided all elements are new. - -#### BelongsTo / HasMany / HasOne association - -Consider the following models: - -```js -class Product extends Model {} -Product.init({ - title: Sequelize.STRING -}, { sequelize, modelName: 'product' }); -class User extends Model {} -User.init({ - firstName: Sequelize.STRING, - lastName: Sequelize.STRING -}, { sequelize, modelName: 'user' }); -class Address extends Model {} -Address.init({ - type: Sequelize.STRING, - line1: Sequelize.STRING, - line2: Sequelize.STRING, - city: Sequelize.STRING, - state: Sequelize.STRING, - zip: Sequelize.STRING, -}, { sequelize, modelName: 'address' }); - -Product.User = Product.belongsTo(User); -User.Addresses = User.hasMany(Address); -// Also works for `hasOne` -``` - -A new `Product`, `User`, and one or more `Address` can be created in one step in the following way: - -```js -return Product.create({ - title: 'Chair', - user: { - firstName: 'Mick', - lastName: 'Broadstone', - addresses: [{ - type: 'home', - line1: '100 Main St.', - city: 'Austin', - state: 'TX', - zip: '78704' - }] - } -}, { - include: [{ - association: Product.User, - include: [ User.Addresses ] - }] -}); -``` - -Here, our user model is called `user`, with a lowercase u - This means that the property in the object should also be `user`. If the name given to `sequelize.define` was `User`, the key in the object should also be `User`. Likewise for `addresses`, except it's pluralized being a `hasMany` association. - -#### BelongsTo association with an alias - -The previous example can be extended to support an association alias. - -```js -const Creator = Product.belongsTo(User, { as: 'creator' }); - -return Product.create({ - title: 'Chair', - creator: { - firstName: 'Matt', - lastName: 'Hansen' - } -}, { - include: [ Creator ] -}); -``` - -#### HasMany / BelongsToMany association - -Let's introduce the ability to associate a product with many tags. Setting up the models could look like: - -```js -class Tag extends Model {} -Tag.init({ - name: Sequelize.STRING -}, { sequelize, modelName: 'tag' }); - -Product.hasMany(Tag); -// Also works for `belongsToMany`. -``` - -Now we can create a product with multiple tags in the following way: - -```js -Product.create({ - id: 1, - title: 'Chair', - tags: [ - { name: 'Alpha'}, - { name: 'Beta'} - ] -}, { - include: [ Tag ] -}) -``` - -And, we can modify this example to support an alias as well: - -```js -const Categories = Product.hasMany(Tag, { as: 'categories' }); - -Product.create({ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] -}, { - include: [{ - association: Categories, - as: 'categories' - }] -}) -``` - -*** - -[0]: https://www.npmjs.org/package/inflection diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md new file mode 100644 index 000000000000..098bccd5e7c2 --- /dev/null +++ b/docs/manual/core-concepts/assocs.md @@ -0,0 +1,778 @@ +# Associations + +Sequelize supports the standard associations: [One-To-One](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29), [One-To-Many](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) and [Many-To-Many](https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29). + +To do this, Sequelize provides **four** types of associations that should be combined to create them: + +* The `HasOne` association +* The `BelongsTo` association +* The `HasMany` association +* The `BelongsToMany` association + +The guide will start explaining how to define these four types of associations, and then will follow up to explain how to combine those to define the three standard association types ([One-To-One](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29), [One-To-Many](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) and [Many-To-Many](https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29)). + +## Defining the Sequelize associations + +The four association types are defined in a very similar way. Let's say we have two models, `A` and `B`. Telling Sequelize that you want an association between the two needs just a function call: + +```js +const A = sequelize.define('A', /* ... */); +const B = sequelize.define('B', /* ... */); + +A.hasOne(B); // A HasOne B +A.belongsTo(B); // A BelongsTo B +A.hasMany(B); // A HasMany B +A.belongsToMany(B, { through: 'C' }); // A BelongsToMany B through the junction table C +``` + +They all accept an options object as a second parameter (optional for the first three, mandatory for `belongsToMany` containing at least the `through` property): + +```js +A.hasOne(B, { /* options */ }); +A.belongsTo(B, { /* options */ }); +A.hasMany(B, { /* options */ }); +A.belongsToMany(B, { through: 'C', /* options */ }); +``` + +The order in which the association is defined is relevant. In other words, the order matters, for the four cases. In all examples above, `A` is called the **source** model and `B` is called the **target** model. This terminology is important. + +The `A.hasOne(B)` association means that a One-To-One relationship exists between `A` and `B`, with the foreign key being defined in the target model (`B`). + +The `A.belongsTo(B)` association means that a One-To-One relationship exists between `A` and `B`, with the foreign key being defined in the source model (`A`). + +The `A.hasMany(B)` association means that a One-To-Many relationship exists between `A` and `B`, with the foreign key being defined in the target model (`B`). + +These three calls will cause Sequelize to automatically add foreign keys to the appropriate models (unless they are already present). + +The `A.belongsToMany(B, { through: 'C' })` association means that a Many-To-Many relationship exists between `A` and `B`, using table `C` as [junction table](https://en.wikipedia.org/wiki/Associative_entity), which will have the foreign keys (`aId` and `bId`, for example). Sequelize will automatically create this model `C` (unless it already exists) and define the appropriate foreign keys on it. + +*Note: In the examples above for `belongsToMany`, a string (`'C'`) was passed to the through option. In this case, Sequelize automatically generates a model with this name. However, you can also pass a model directly, if you have already defined it.* + +These are the main ideas involved in each type of association. However, these relationships are often used in pairs, in order to enable better usage with Sequelize. This will be seen later on. + +## Creating the standard relationships + +As mentioned, usually the Sequelize associations are defined in pairs. In summary: + +* To create a **One-To-One** relationship, the `hasOne` and `belongsTo` associations are used together; +* To create a **One-To-Many** relationship, the `hasMany` and `belongsTo` associations are used together; +* To create a **Many-To-Many** relationship, two `belongsToMany` calls are used together. + * Note: there is also a *Super Many-To-Many* relationship, which uses six associations at once, and will be discussed in the [Advanced Many-to-Many relationships guide](advanced-many-to-many.html). + +This will all be seen in detail next. The advantages of using these pairs instead of one single association will be discussed in the end of this chapter. + +## One-To-One relationships + +### Philosophy + +Before digging into the aspects of using Sequelize, it is useful to take a step back to consider what happens with a One-To-One relationship. + +Let's say we have two models, `Foo` and `Bar`. We want to establish a One-To-One relationship between Foo and Bar. We know that in a relational database, this will be done by establishing a foreign key in one of the tables. So in this case, a very relevant question is: in which table do we want this foreign key to be? In other words, do we want `Foo` to have a `barId` column, or should `Bar` have a `fooId` column instead? + +In principle, both options are a valid way to establish a One-To-One relationship between Foo and Bar. However, when we say something like *"there is a One-To-One relationship between Foo and Bar"*, it is unclear whether or not the relationship is *mandatory* or optional. In other words, can a Foo exist without a Bar? Can a Bar exist without a Foo? The answers to these questions helps figuring out where we want the foreign key column to be. + +### Goal + +For the rest of this example, let's assume that we have two models, `Foo` and `Bar`. We want to setup a One-To-One relationship between them such that `Foo` gets a `barId` column. + +### Implementation + +The main setup to achieve the goal is as follows: + +```js +Foo.hasOne(Bar); +Bar.belongsTo(Foo); +``` + +Since no option was passed, Sequelize will infer what to do from the names of the models. In this case, Sequelize knows that a `barId` column must be added to `Foo`. + +This way, calling `Bar.sync()` after the above will yield the following SQL (on PostgreSQL, for example): + +```sql +CREATE TABLE IF NOT EXISTS "foos" ( + /* ... */ +); +CREATE TABLE IF NOT EXISTS "bars" ( + /* ... */ + "fooId" INTEGER REFERENCES "foos" ("id") ON DELETE SET NULL ON UPDATE CASCADE + /* ... */ +); +``` + +### Options + +Various options can be passed as a second parameter of the association call. + +#### `onDelete` and `onUpdate` + +For example, to configure the `ON DELETE` and `ON UPDATE` behaviors, you can do: + +```js +Foo.hasOne(Bar, { + onDelete: 'RESTRICT', + onUpdate: 'RESTRICT' +}); +Bar.belongsTo(Foo); +``` + +The possible choices are `RESTRICT`, `CASCADE`, `NO ACTION`, `SET DEFAULT` and `SET NULL`. + +The defaults for the One-To-One associations is `SET NULL` for `ON DELETE` and `CASCADE` for `ON UPDATE`. + +#### Customizing the foreign key + +Both the `hasOne` and `belongsTo` calls shown above will infer that the foreign key to be created should be called `fooId`. To use a different name, such as `myFooId`: + +```js +// Option 1 +Foo.hasOne(Bar, { + foreignKey: 'myFooId' +}); +Bar.belongsTo(Foo); + +// Option 2 +Foo.hasOne(Bar, { + foreignKey: { + name: 'myFooId' + } +}); +Bar.belongsTo(Foo); + +// Option 3 +Foo.hasOne(Bar); +Bar.belongsTo(Foo, { + foreignKey: 'myFooId' +}); + +// Option 4 +Foo.hasOne(Bar); +Bar.belongsTo(Foo, { + foreignKey: { + name: 'myFooId' + } +}); +``` + +As shown above, the `foreignKey` option accepts a string or an object. When receiving an object, this object will be used as the definition for the column just like it would do in a standard `sequelize.define` call. Therefore, specifying options such as `type`, `allowNull`, `defaultValue`, etc, just work. + +For example, to use `UUID` as the foreign key data type instead of the default (`INTEGER`), you can simply do: + +```js +const { DataTypes } = require("Sequelize"); + +Foo.hasOne(Bar, { + foreignKey: { + // name: 'myFooId' + type: DataTypes.UUID + } +}); +Bar.belongsTo(Foo); +``` + +#### Mandatory versus optional associations + +By default, the association is considered optional. In other words, in our example, the `fooId` is allowed to be null, meaning that one Bar can exist without a Foo. Changing this is just a matter of specifying `allowNull: false` in the foreign key options: + +```js +Foo.hasOne(Bar, { + foreignKey: { + allowNull: false + } +}); +// "fooId" INTEGER NOT NULL REFERENCES "foos" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT +``` + +## One-To-Many relationships + +### Philosophy + +One-To-Many associations are connecting one source with multiple targets, while all these targets are connected only with this single source. + +This means that, unlike the One-To-One association, in which we had to choose where the foreign key would be placed, there is only one option in One-To-Many associations. For example, if one Foo has many Bars (and this way each Bar belongs to one Foo), then the only sensible implementation is to have a `fooId` column in the `Bar` table. The opposite is impossible, since one Foo has many Bars. + +### Goal + +In this example, we have the models `Team` and `Player`. We want to tell Sequelize that there is a One-To-Many relationship between them, meaning that one Team has many Players, while each Playes belongs to a single Team. + +### Implementation + +The main way to do this is as follows: + +```js +Team.hasMany(Player); +Player.belongsTo(Team); +``` + +Again, as mentioned, the main way to do it used a pair of Sequelize associations (`hasMany` and `belongsTo`). + +For example, in PostgreSQL, the above setup will yield the following SQL upon `sync()`: + +```sql +CREATE TABLE IF NOT EXISTS "Teams" ( + /* ... */ +); +CREATE TABLE IF NOT EXISTS "Players" ( + /* ... */ + "TeamId" INTEGER REFERENCES "Teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + /* ... */ +); +``` + +### Options + +The options to be applied in this case are the same from the One-To-One case. For example, to change the name of the foreign key and make sure that the relationship is mandatory, we can do: + +```js +Team.hasMany(Player, { + foreignKey: 'clubId' +}); +Player.belongsTo(Team); +``` + +Like One-To-One relationships, `ON DELETE` defaults to `SET NULL`a nd `ON UPDATE` defaults to `CASCADE`. + +## Many-To-Many relationships + +### Philosophy + +Many-To-Many associations connect one source with multiple targets, while all these targets can in turn be connected to other sources beyond the first. + +This cannot be represented by adding one foreign key to one of the tables, like the other relationships did. Instead, the concept of a [Junction Model](https://en.wikipedia.org/wiki/Associative_entity) is used. This will be an extra model (and extra table in the database) which will have two foreign key columns and will keep track of the associations. The junction table is also sometimes called *join table* or *through table*. + +### Goal + +For this example, we will consider the models `Movie` and `Actor`. One actor may have participated in many movies, and one movie had many actors involved with its production. The junction table that will keep track of the associations will be called `ActorMovies`, which will contain the foreign keys `movieId` and `actorId`. + +### Implementation + +The main way to do this in Sequelize is as follows: + +```js +const Movie = sequelize.define('Movie', { name: DataTypes.STRING }); +const Actor = sequelize.define('Actor', { name: DataTypes.STRING }); +Movie.belongsToMany(Actor, { through: 'ActorMovies' }); +Actor.belongsToMany(Movie, { through: 'ActorMovies' }); +``` + +Since a string was given in the `through` option of the `belongsToMany` call, Sequelize will automatically create the `ActorMovies` model which will act as the junction model. For example, in PostgreSQL: + +```sql +CREATE TABLE IF NOT EXISTS "ActorMovies" ( + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "MovieId" INTEGER REFERENCES "Movies" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + "ActorId" INTEGER REFERENCES "Actors" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("MovieId","ActorId") +); +``` + +Instead of a string, passing a model directly is also supported, and in that case the given model will be used as the junction model (and no model will be created automatically). For example: + +```js +const Movie = sequelize.define('Movie', { name: DataTypes.STRING }); +const Actor = sequelize.define('Actor', { name: DataTypes.STRING }); +const ActorMovies = sequelize.define('ActorMovies', { + MovieId: { + type: DataTypes.INTEGER, + references: { + model: Movie, // 'Movies' would also work + key: 'id' + } + }, + ActorId: { + type: DataTypes.INTEGER, + references: { + model: Actor, // 'Actors' would also work + key: 'id' + } + } +}); +Movie.belongsToMany(Actor, { through: 'ActorMovies' }); +Actor.belongsToMany(Movie, { through: 'ActorMovies' }); +``` + +The above yields the following SQL in PostgreSQL, which is equivalent to the one shown above: + +```sql +CREATE TABLE IF NOT EXISTS "ActorMovies" ( + "MovieId" INTEGER NOT NULL REFERENCES "Movies" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + "ActorId" INTEGER NOT NULL REFERENCES "Actors" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + UNIQUE ("MovieId", "ActorId"), -- Note: Sequelize generated this UNIQUE constraint but + PRIMARY KEY ("MovieId","ActorId") -- it is irrelevant since it's also a PRIMARY KEY +); +``` + +### Options + +Unlike One-To-One and One-To-Many relationships, the defaults for both `ON UPDATE` and `ON DELETE` are `CASCADE` for Many-To-Many relationships. + +## Basics of queries involving associations + +With the basics of defining associations covered, we can look at queries involving associations. The most common queries on this matter are the *read* queries (i.e. SELECTs). Later on, other types of queries will be shown. + +In order to study this, we will consider an example in which we have Ships and Captains, and a one-to-one relationship between them. We will allow null on foreign keys (the default), meaning that a Ship can exist without a Captain and vice-versa. + +```js +// This is the setup of our models for the examples below +const Ship = sequelize.define('ship', { + name: DataTypes.TEXT, + crewCapacity: DataTypes.INTEGER, + amountOfSails: DataTypes.INTEGER +}, { timestamps: false }); +const Captain = sequelize.define('captain', { + name: DataTypes.TEXT, + skillLevel: { + type: DataTypes.INTEGER, + validate: { min: 1, max: 10 } + } +}, { timestamps: false }); +Captain.hasOne(Ship); +Ship.belongsTo(Captain); +``` + +### Fetching associations - Eager Loading vs Lazy Loading + +The concepts of Eager Loading and Lazy Loading are fundamental to understand how fetching associations work in Sequelize. Lazy Loading refers to the technique of fetching the associated data only when you really want it; Eager Loading, on the other hand, refers to the technique of fetching everything at once, since the beginning, with a larger query. + +#### Lazy Loading example + +```js +const awesomeCaptain = await Captain.findOne({ + where: { + name: "Jack Sparrow" + } +}); +// Do stuff with the fetched captain +console.log('Name:', awesomeCaptain.name); +console.log('Skill Level:', awesomeCaptain.skillLevel); +// Now we want information about his ship! +const hisShip = await awesomeCaptain.getShip(); +// Do stuff with the ship +console.log('Ship Name:', hisShip.name); +console.log('Amount of Sails:', hisShip.amountOfSails); +``` + +Observe that in the example above, we made two queries, only fetching the associated ship when we wanted to use it. This can be especially useful if we may or may not need the ship, perhaps we want to fetch it conditionally, only in a few cases; this way we can save time and memory by only fetching it when necessary. + +Note: the `getShip()` instance method used above is one of the methods Sequelize automatically adds to `Captain` instances. There are others. You will learn more about them later in this guide. + +#### Eager Loading Example + +```js +const awesomeCaptain = await Captain.findOne({ + where: { + name: "Jack Sparrow" + }, + include: Ship +}); +// Now the ship comes with it +console.log('Name:', awesomeCaptain.name); +console.log('Skill Level:', awesomeCaptain.skillLevel); +console.log('Ship Name:', awesomeCaptain.ship.name); +console.log('Amount of Sails:', awesomeCaptain.ship.amountOfSails); +``` + +As shown above, Eager Loading is performed in Sequelize by using the `include` option. Observe that here only one query was performed to the database (which brings the associated data along with the instance). + +This was just a quick introduction to Eager Loading in Sequelize. There is a lot more to it, which you can learn at [the dedicated guide on Eager Loading](eager-loading.html). + +### Creating, updating and deleting + +The above showed the basics on queries for fetching data involving associations. For creating, updating and deleting, you can either: + +* Use the standard model queries directly: + + ```js + // Example: creating an associated model using the standard methods + Bar.create({ + name: 'My Bar', + fooId: 5 + }); + // This creates a Bar belonging to the Foo of ID 5 (since fooId is + // a regular column, after all). Nothing very clever going on here. + ``` + +* Or use the *[special methods/mixins](#special-methods-mixins-added-to-instances)* available for associated models, which are explained later on this page. + +**Note:** The [`save()` instance method](../class/lib/model.js~Model.html#instance-method-save) is not aware of associations. In other words, if you change a value from a *child* object that was eager loaded along a *parent* object, calling `save()` on the parent will completely ignore the change that happened on the child. + +## Association Aliases & Custom Foreign Keys + +In all the above examples, Sequelize automatically defined the foreign key names. For example, in the Ship and Captain example, Sequelize automatically defined a `captainId` field on the Ship model. However, it is easy to specify a custom foreign key. + +Let's consider the models Ship and Captain in a simplified form, just to focus on the current topic, as shown below (less fields): + +```js +const Ship = sequelize.define('ship', { name: DataTypes.TEXT }, { timestamps: false }); +const Captain = sequelize.define('captain', { name: DataTypes.TEXT }, { timestamps: false }); +``` + +There are three ways to specify a different name for the foreign key: + +* By providing the foreign key name directly +* By defining an Alias +* By doing both things + +### Recap: the default setup + +By using simply `Ship.belongsTo(Captain)`, sequelize will generate the foreign key name automatically: + +```js +Ship.belongsTo(Captain); // This creates the `captainId` foreign key in Ship. + +// Eager Loading is done by passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); +// Or by providing the associated model name: +console.log((await Ship.findAll({ include: 'captain' })).toJSON()); + +// Also, instances obtain a `getCaptain()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getCaptain()).toJSON()); +``` + +### Providing the foreign key name directly + +The foreign key name can be provided directly with an option in the association definition, as follows: + +```js +Ship.belongsTo(Captain, { foreignKey: 'bossId' }); // This creates the `bossId` foreign key in Ship. + +// Eager Loading is done by passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); +// Or by providing the associated model name: +console.log((await Ship.findAll({ include: 'Captain' })).toJSON()); + +// Also, instances obtain a `getCaptain()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getCaptain()).toJSON()); +``` + +### Defining an Alias + +Defining an Alias is more powerful than simply specifying a custom name for the foreign key. This is better understood with an example: + + + +```js +Ship.belongsTo(Captain, { as: 'leader' }); // This creates the `leaderId` foreign key in Ship. + +// Eager Loading no longer works by passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); // Throws an error +// Instead, you have to pass the alias: +console.log((await Ship.findAll({ include: 'leader' })).toJSON()); +// Or you can pass an object specifying the model and alias: +console.log((await Ship.findAll({ + include: { + model: Captain, + as: 'leader' + } +})).toJSON()); + +// Also, instances obtain a `getLeader()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getLeader()).toJSON()); +``` + +Aliases are especially useful when you need to define two different associations between the same models. For example, if we have the models `Mail` and `Person`, we may want to associate them twice, to represent the `sender` and `receiver` of the Mail. In this case we must use an alias for each association, since otherwise a call like `mail.getPerson()` would be ambiguous. With the `sender` and `receiver` aliases, we would have the two methods available and working: `mail.getSender()` and `mail.getReceiver()`, both of them returning a `Promise`. + +When defining an alias for a `hasOne` or `belongsTo` association, you should use the singular form of a word (such as `leader`, in the example above). On the other hand, when defining an alias for `hasMany` and `belongsToMany`, you should use the plural form. Defining aliases for Many-to-Many relationships (with `belongsToMany`) is covered in the [Advanced Many-to-Many Associations guide](advanced-many-to-many.html). + +### Doing both things + +We can define and alias and also directly define the foreign key: + +```js +Ship.belongsTo(Captain, { as: 'leader', foreignKey: 'bossId' }); // This creates the `bossId` foreign key in Ship. + +// Since an alias was defined, eager Loading doesn't work by simply passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); // Throws an error +// Instead, you have to pass the alias: +console.log((await Ship.findAll({ include: 'leader' })).toJSON()); +// Or you can pass an object specifying the model and alias: +console.log((await Ship.findAll({ + include: { + model: Captain, + as: 'leader' + } +})).toJSON()); + +// Also, instances obtain a `getLeader()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getLeader()).toJSON()); +``` + +## Special methods/mixins added to instances + +When an association is defined between two models, the instances of those models gain special methods to interact with their associated counterparts. + +For example, if we have two models, `Foo` and `Bar`, and they are associated, their instances will have the following methods/mixins available, depending on the association type: + +### `Foo.hasOne(Bar)` + +* `fooInstance.getBar()` +* `fooInstance.setBar()` +* `fooInstance.createBar()` + +Example: + +```js +const foo = await Foo.create({ name: 'the-foo' }); +const bar1 = await Bar.create({ name: 'some-bar' }); +const bar2 = await Bar.create({ name: 'another-bar' }); +console.log(await foo.getBar()); // null +await foo.setBar(bar1); +console.log((await foo.getBar()).name); // 'some-bar' +await foo.createBar({ name: 'yet-another-bar' }); +const newlyAssociatedBar = await foo.getBar(); +console.log(newlyAssociatedBar.name); // 'yet-another-bar' +await foo.setBar(null); // Un-associate +console.log(await foo.getBar()); // null +``` + +### `Foo.belongsTo(Bar)` + +The same ones from `Foo.hasOne(Bar)`: + +* `fooInstance.getBar()` +* `fooInstance.setBar()` +* `fooInstance.createBar()` + +### `Foo.hasMany(Bar)` + +* `fooInstance.getBars()` +* `fooInstance.countBars()` +* `fooInstance.hasBar()` +* `fooInstance.hasBars()` +* `fooInstance.setBars()` +* `fooInstance.addBar()` +* `fooInstance.addBars()` +* `fooInstance.removeBar()` +* `fooInstance.removeBars()` +* `fooInstance.createBar()` + +Example: + +```js +const foo = await Foo.create({ name: 'the-foo' }); +const bar1 = await Bar.create({ name: 'some-bar' }); +const bar2 = await Bar.create({ name: 'another-bar' }); +console.log(await foo.getBars()); // [] +console.log(await foo.countBars()); // 0 +console.log(await foo.hasBar(bar1)); // false +await foo.addBars([bar1, bar2]); +console.log(await foo.countBars()); // 2 +await foo.addBar(bar1); +console.log(await foo.countBars()); // 2 +console.log(await foo.hasBar(bar1)); // true +await foo.removeBar(bar2); +console.log(await foo.countBars()); // 1 +await foo.createBar({ name: 'yet-another-bar' }); +console.log(await foo.countBars()); // 2 +await foo.setBars([]); // Un-associate all previously associated bars +console.log(await foo.countBars()); // 0 +``` + +The getter method accepts options just like the usual finder methods (such as `findAll`): + +```js +const easyTasks = await project.getTasks({ + where: { + difficulty: { + [Op.lte]: 5 + } + } +}); +const taskTitles = (await project.getTasks({ + attributes: ['title'], + raw: true +})).map(task => task.title); +``` + +### `Foo.belongsToMany(Bar, { through: Baz })` + +The same ones from `Foo.hasMany(Bar)`: + +* `fooInstance.getBars()` +* `fooInstance.countBars()` +* `fooInstance.hasBar()` +* `fooInstance.hasBars()` +* `fooInstance.setBars()` +* `fooInstance.addBar()` +* `fooInstance.addBars()` +* `fooInstance.removeBar()` +* `fooInstance.removeBars()` +* `fooInstance.createBar()` + +### Note: Method names + +As shown in the examples above, the names Sequelize gives to these special methods are formed by a prefix (e.g. `get`, `add`, `set`) concatenated with the model name (with the first letter in uppercase). When necessary, the plural is used, such as in `fooInstance.setBars()`. Again, irregular plurals are also handled automatically by Sequelize. For example, `Person` becomes `People` and `Hypothesis` becomes `Hypotheses`. + +If an alias was defined, it will be used instead of the model name to form the method names. For example: + +```js +Task.hasOne(User, { as: 'Author' }); +``` + +* `taskInstance.getAuthor()` +* `taskInstance.setAuthor()` +* `taskInstance.createAuthor()` + +## Why associations are defined in pairs? + +As mentioned earlier and shown in most examples above, usually associations in Sequelize are defined in pairs: + +* To create a **One-To-One** relationship, the `hasOne` and `belongsTo` associations are used together; +* To create a **One-To-Many** relationship, the `hasMany` and `belongsTo` associations are used together; +* To create a **Many-To-Many** relationship, two `belongsToMany` calls are used together. + +When a Sequelize association is defined between two models, only the *source* model *knows about it*. So, for example, when using `Foo.hasOne(Bar)` (so `Foo` is the source model and `Bar` is the target model), only `Foo` knows about the existence of this association. This is why in this case, as shown above, `Foo` instances gain the methods `getBar()`, `setBar()` and `createBar()`, while on the other hand `Bar` instances get nothing. + +Similarly, for `Foo.hasOne(Bar)`, since `Foo` knows about the relationship, we can perform eager loading as in `Foo.findOne({ include: Bar })`, but we can't do `Bar.findOne({ include: Foo })`. + +Therefore, to bring full power to Sequelize usage, we usually setup the relationship in pairs, so that both models get to *know about it*. + +Practical demonstration: + +* If we do not define the pair of associations, calling for example just `Foo.hasOne(Bar)`: + + ```js + // This works... + await Foo.findOne({ include: Bar }); + + // But this throws an error: + await Bar.findOne({ include: Foo }); + // SequelizeEagerLoadingError: foo is not associated to bar! + ``` + +* If we define the pair as recommended, i.e., both `Foo.hasOne(Bar)` and `Bar.belongsTo(Foo)`: + + ```js + // This works! + await Foo.findOne({ include: Bar }); + + // This also works! + await Bar.findOne({ include: Foo }); + ``` + +## Multiple associations involving the same models + +In Sequelize, it is possible to define multiple associations between the same models. You just have to define different aliases for them: + +```js +Team.hasOne(Game, { as: 'HomeTeam', foreignKey: 'homeTeamId' }); +Team.hasOne(Game, { as: 'AwayTeam', foreignKey: 'awayTeamId' }); +Game.belongsTo(Team); +``` + +## Creating associations referencing a field which is not the primary key + +In all the examples above, the associations were defined by referencing the primary keys of the involved models (in our case, their IDs). However, Sequelize allows you to define an association that uses another field, instead of the primary key field, to establish the association. + +This other field must have a unique constraint on it (otherwise, it wouldn't make sense). + +### For `belongsTo` relationships + +First, recall that the `A.belongsTo(B)` association places the foreign key in the *source model* (i.e., in `A`). + +Let's again use the example of Ships and Captains. Additionally, we will assume that Captain names are unique: + +```js +const Ship = sequelize.define('ship', { name: DataTypes.TEXT }, { timestamps: false }); +const Captain = sequelize.define('captain', { + name: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +``` + +This way, instead of keeping the `captainId` on our Ships, we could keep a `captainName` instead and use it as our association tracker. In other words, instead of referencing the `id` from the target model (Captain), our relationship will reference another column on the target model: the `name` column. To specify this, we have to define a *target key*. We will also have to specify a name for the foreign key itself: + +```js +Ship.belongsTo(Captain, { targetKey: 'name', foreignKey: 'captainName' }); +// This creates a foreign key called `captainName` in the source model (Ship) +// which references the `name` field from the target model (Captain). +``` + +Now we can do things like: + +```js +await Captain.create({ name: "Jack Sparrow" }); +const ship = await Ship.create({ name: "Black Pearl", captainName: "Jack Sparrow" }); +console.log((await ship.getCaptain()).name); // "Jack Sparrow" +``` + +### For `hasOne` and `hasMany` relationships + +The exact same idea can be applied to the `hasOne` and `hasMany` associations, but instead of providing a `targetKey`, we provide a `sourceKey` when defining the association. This is because unlike `belongsTo`, the `hasOne` and `hasMany` associations keep the foreign key on the target model: + +```js +const Foo = sequelize.define('foo', { + name: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +const Bar = sequelize.define('bar', { + title: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +const Baz = sequelize.define('baz', { summary: DataTypes.TEXT }, { timestamps: false }); +Foo.hasOne(Bar, { sourceKey: 'name', foreignKey: 'fooName' }); +Bar.hasMany(Baz, { sourceKey: 'title', foreignKey: 'barTitle' }); +// [...] +await Bar.setFoo("Foo's Name Here"); +await Baz.addBar("Bar's Title Here"); +``` + +### For `belongsToMany` relationships + +The same idea can also be applied to `belongsToMany` relationships. However, unlike the other situations, in which we have only one foreign key involved, the `belongsToMany` relationship involves two foreign keys which are kept on an extra table (the junction table). + +Consider the following setup: + +```js +const Foo = sequelize.define('foo', { + name: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +const Bar = sequelize.define('bar', { + title: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +``` + +There are four cases to consider: + +* We might want a many-to-many relationship using the default primary keys for both `Foo` and `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar' }); +// This creates a junction table `foo_bar` with fields `fooId` and `barId` +``` + +* We might want a many-to-many relationship using the default primary key for `Foo` but a different field for `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar', targetKey: 'title' }); +// This creates a junction table `foo_bar` with fields `fooId` and `barTitle` +``` + +* We might want a many-to-many relationship using the a different field for `Foo` and the default primary key for `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar', sourceKey: 'name' }); +// This creates a junction table `foo_bar` with fields `fooName` and `barId` +``` + +* We might want a many-to-many relationship using different fields for both `Foo` and `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar', sourceKey: 'name', targetKey: 'title' }); +// This creates a junction table `foo_bar` with fields `fooName` and `barTitle` +``` + +### Notes + +Don't forget that the field referenced in the association must have a unique constraint placed on it. Otherwise, an error will be thrown (and sometimes with a mysterious error message - such as `SequelizeDatabaseError: SQLITE_ERROR: foreign key mismatch - "ships" referencing "captains"` for SQLite). + +The trick to deciding between `sourceKey` and `targetKey` is just to remember where each relationship places its foreign key. As mentioned in the beginning of this guide: + +* `A.belongsTo(B)` keeps the foreign key in the source model (`A`), therefore the referenced key is in the target model, hence the usage of `targetKey`. + +* `A.hasOne(B)` and `A.hasMany(B)` keep the foreign key in the target model (`B`), therefore the referenced key is in the source model, hence the usage of `sourceKey`. + +* `A.belongsToMany(B)` involves an extra table (the junction table), therefore both `sourceKey` and `targetKey` are usable, with `sourceKey` corresponding to some field in `A` (the source) and `targetKey` corresponding to some field in `B` (the target). \ No newline at end of file diff --git a/docs/manual/core-concepts/getters-setters-virtuals.md b/docs/manual/core-concepts/getters-setters-virtuals.md new file mode 100644 index 000000000000..b171a02e156b --- /dev/null +++ b/docs/manual/core-concepts/getters-setters-virtuals.md @@ -0,0 +1,201 @@ +# Getters, Setters & Virtuals + +Sequelize allows you to define custom getters and setters for the attributes of your models. + +Sequelize also allows you to specify the so-called *virtual attributes*, which are attributes on the Sequelize Model that doesn't really exist in the underlying SQL table, but instead are populated automatically by Sequelize. They are very useful for simplifying code, for example. + +## Getters + +A getter is a `get()` function defined for one column in the model definition: + +```js +const User = sequelize.define('user', { + // Let's say we wanted to see every username in uppercase, even + // though they are not necessarily uppercase in the database itself + username: { + type: DataTypes.STRING, + get() { + const rawValue = this.getDataValue(username); + return rawValue ? rawValue.toUpperCase() : null; + } + } +}); +``` + +This getter, just like a standard JavaScript getter, is called automatically when the field value is read: + +```js +const user = User.build({ username: 'SuperUser123' }); +console.log(user.username); // 'SUPERUSER123' +console.log(user.getDataValue(username)); // 'SuperUser123' +``` + +Note that, although `SUPERUSER123` was logged above, the value truly stored in the database is still `SuperUser123`. We used `this.getDataValue(username)` to obtain this value, and converted it to uppercase. + +Had we tried to use `this.username` in the getter instead, we would have gotten an infinite loop! This is why Sequelize provides the `getDataValue` method. + +## Setters + +A setter is a `set()` function defined for one column in the model definition. It receives the value being set: + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + password: { + type: DataTypes.STRING, + set(value) { + // Storing passwords in plaintext in the database is terrible. + // Hashing the value with an appropriate cryptographic hash function is better. + this.setDataValue('password', hash(value)); + } + } +}); +``` + +```js +const user = User.build({ username: 'someone', password: 'NotSo§tr0ngP4$SW0RD!' }); +console.log(user.password); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' +console.log(user.getDataValue(password)); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' +``` + +Observe that Sequelize called the setter automatically, before even sending data to the database. The only data the database ever saw was the already hashed value. + +If we wanted to involve another field from our model instance in the computation, that is possible and very easy! + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + password: { + type: DataTypes.STRING, + set(value) { + // Storing passwords in plaintext in the database is terrible. + // Hashing the value with an appropriate cryptographic hash function is better. + // Using the username as a salt is better. + this.setDataValue('password', hash(this.username + value)); + } + } +}); +``` + +**Note:** The above examples involving password handling, although much better than simply storing the password in plaintext, are far from perfect security. Handling passwords properly is hard, everything here is just for the sake of an example to show Sequelize functionality. We suggest involving a cybersecurity expert and/or reading [OWASP](https://www.owasp.org/) documents and/or visiting the [InfoSec StackExchange](https://security.stackexchange.com/). + +## Combining getters and setters + +Getters and setters can be both defined in the same field. + +For the sake of an example, let's say we are modeling a `Post`, whose `content` is a text of unlimited length. To improve memory usage, let's say we want to store a gzipped version of the content. + +*Note: modern databases should do some compression automatically in these cases. Please note that this is just for the sake of an example.* + +```js +const { gzipSync, gunzipSync } = require('zlib'); + +const Post = sequelize.define('post', { + content: { + type: DataTypes.TEXT, + get() { + const storedValue = this.getDataValue('content'); + const gzippedBuffer = Buffer.from(storedValue, 'base64'); + const unzippedBuffer = gunzipSync(gzippedBuffer); + return unzippedBuffer.toString(); + }, + set(value) { + const gzippedBuffer = gzipSync(value); + this.setDataValue('content', gzippedBuffer.toString('base64')); + } + } +}); +``` + +With the above setup, whenever we try to interact with the `content` field of our `Post` model, Sequelize will automatically handle the custom getter and setter. For example: + +```js +const post = await Post.create({ content: 'Hello everyone!' }); + +console.log(post.content); // 'Hello everyone!' +// Everything is happening under the hood, so we can even forget that the +// content is actually being stored as a gzipped base64 string! + +// However, if we are really curious, we can get the 'raw' data... +console.log(post.getDataValue('content')); +// Output: 'H4sIAAAAAAAACvNIzcnJV0gtSy2qzM9LVQQAUuk9jQ8AAAA=' +``` + +## Virtual fields + +Virtual fields are fields that Sequelize populates under the hood, but in reality they don't even exist in the database. + +For example, let's say we have the `firstName` and `lastName` attributes for a User. + +*Again, this is [only for the sake of an example](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/).* + +It would be nice to have a simple way to obtain the *full name* directly! We can combine the idea of `getters` with the special data type Sequelize provides for this kind of situation: `DataTypes.VIRTUAL`: + +```js +const { DataTypes } = require("sequelize"); + +const User = sequelize.define('user', { + firstName: DataTypes.TEXT, + lastName: DataTypes.TEXT, + fullName: { + type: DataTypes.VIRTUAL, + get() { + return `${this.firstName} ${this.lastName}`; + }, + set(value) { + throw new Error('Do not try to set the `fullName` value!'); + } + } +}); +``` + +The `VIRTUAL` field does not cause a column in the table to exist. In other words, the model above will not have a `fullName` column. However, it will appear to have it! + +```js +const user = await User.create({ firstName: 'John', lastName: 'Doe' }); +console.log(user.fullName); // 'John Doe' +``` + +## `getterMethods` and `setterMethods` + +Sequelize also provides the `getterMethods` and `setterMethods` options in the model definition to specify things that look like, but aren't exactly the same as, virtual attributes. This usage is discouraged and likely to be deprecated in the future (in favor of using virtual attributes directly). + +Example: + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:'); + +const User = sequelize.define('user', { + firstName: DataTypes.STRING, + lastName: DataTypes.STRING +}, { + getterMethods: { + fullName() { + return this.firstName + ' ' + this.lastName; + } + }, + setterMethods: { + fullName(value) { + // Note: this is just for demonstration. + // See: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/ + const names = value.split(' '); + const firstName = names[0]; + const lastName = names.slice(1).join(' '); + this.setDataValue('firstName', firstName); + this.setDataValue('lastName', lastName); + } + } +}); + +(async () => { + await sequelize.sync(); + let user = await User.create({ firstName: 'John', lastName: 'Doe' }); + console.log(user.fullName); // 'John Doe' + user.fullName = 'Someone Else'; + await user.save(); + user = await User.findOne(); + console.log(user.firstName); // 'Someone' + console.log(user.lastName); // 'Else' +})(); +``` \ No newline at end of file diff --git a/docs/manual/core-concepts/getting-started.md b/docs/manual/core-concepts/getting-started.md new file mode 100644 index 000000000000..eaff1c48a0d3 --- /dev/null +++ b/docs/manual/core-concepts/getting-started.md @@ -0,0 +1,111 @@ +# Getting Started + +In this tutorial you will learn to make a simple setup of Sequelize. + +## Installing + +Sequelize is available via [npm](https://www.npmjs.com/package/sequelize) (or [yarn](https://yarnpkg.com/package/sequelize)). + +```sh +npm install --save sequelize +``` + +You'll also have to manually install the driver for your database of choice: + +```sh +# One of the following: +$ npm install --save pg pg-hstore # Postgres +$ npm install --save mysql2 +$ npm install --save mariadb +$ npm install --save sqlite3 +$ npm install --save tedious # Microsoft SQL Server +``` + +## Connecting to a database + +To connect to the database, you must create a Sequelize instance. This can be done by either passing the connection parameters separately to the Sequelize constructor or by passing a single connection URI: + +```js +const { Sequelize } = require('sequelize'); + +// Option 1: Passing a connection URI +const sequelize = new Sequelize('sqlite::memory:') // Example for sqlite +const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Example for postgres + +// Option 2: Passing parameters separately (sqlite) +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: 'path/to/database.sqlite' +}); + +// Option 2: Passing parameters separately (other dialects) +const sequelize = new Sequelize('database', 'username', 'password', { + host: 'localhost', + dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ +}); +``` + +The Sequelize constructor accepts a lot of options. They are documented in the [API Reference](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). + +### Testing the connection + +You can use the `.authenticate()` function to test if the connection is OK: + +```js +try { + await sequelize.authenticate(); + console.log('Connection has been established successfully.'); +} catch (error) { + console.error('Unable to connect to the database:', error); +} +``` + +### Closing the connection + +Sequelize will keep the connection open by default, and use the same connection for all queries. If you need to close the connection, call `sequelize.close()` (which is asynchronous and returns a Promise). + +## Terminology convention + +Observe that, in the examples above, `Sequelize` refers to the library itself while `sequelize` refers to an instance of Sequelize, which represents a connection to one database. This is the recommended convention and it will be followed throughout the documentation. + +## Tip for reading the docs + +You are encouraged to run code examples locally while reading the Sequelize docs. This will help you learn faster. The easiest way to do this is using the SQLite dialect: + +```js +const { Sequelize, Op, Model, DataTypes } = require("sequelize"); +const sequelize = new Sequelize("sqlite::memory:"); + +// Code here! It works! +``` + +To experiment with the other dialects, which are harder to setup locally, you can use the [Sequelize SSCCE](https://github.com/papb/sequelize-sscce) GitHub repository, which allows you to run code on all supported dialects directly from GitHub, for free, without any setup! + +## New databases versus existing databases + +If you are starting a project from scratch, and your database does not exist yet, Sequelize can be used since the beginning in order to automate the creation of every table in your database. + +Also, if you want to use Sequelize to connect to a database that is already filled with tables and data, that works as well! Sequelize has got you covered in both cases. + +## Logging + +By default, Sequelize will log to console every SQL query it performs. The `options.logging` option can be used to customize this behavior, by defining the function that gets executed every time Sequelize would log something. The default value is `console.log` and when using that only the first log parameter of log function call is displayed. For example, for query logging the first parameter is the raw query and the second (hidden by default) is the Sequelize object. + +Common useful values for `options.logging`: + +```js +const sequelize = new Sequelize('sqlite::memory:', { + // Choose one of the logging options + logging: console.log, // Default, displays the first parameter of the log function call + logging: (...msg) => console.log(msg), // Displays all log function call parameters + logging: false, // Disables logging + logging: msg => logger.debug(msg), // Use custom logger (e.g. Winston or Bunyan), displays the first parameter + logging: logger.debug.bind(logger) // Alternative way to use custom logger, displays all messages +}); +``` + +## Bluebird Promises and async/await + +Most of the methods provided by Sequelize are asynchronous and therefore return Promises. They are all [Bluebird](http://bluebirdjs.com) Promises, so you can use the rich Bluebird API (for example, using `finally`, `tap`, `tapCatch`, `map`, `mapSeries`, etc) out of the box. You can access the Bluebird constructor used internally by Sequelize with `Sequelize.Promise`, if you want to set any Bluebird specific options. + +Of course, using `async` and `await` works normally as well. \ No newline at end of file diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md new file mode 100644 index 000000000000..7cf8ce161a14 --- /dev/null +++ b/docs/manual/core-concepts/model-basics.md @@ -0,0 +1,436 @@ +# Model Basics + +In this tutorial you will learn what models are in Sequelize and how to use them. + +## Concept + +Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends [Model](../class/lib/model.js~Model.html). + +The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types). + +A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as `User`) while tables have pluralized names (such as `Users`), although this is fully configurable. + +## Model Definition + +Models can be defined in two equivalent ways in Sequelize: + +* Calling [`sequelize.define(modelName, attributes, options)`](../class/lib/sequelize.js~Sequelize.html#instance-method-define) +* Extending [Model](../class/lib/model.js~Model.html) and calling [`init(attributes, options)`](../class/lib/model.js~Model.html#static-method-init) + +After a model is defined, it is available within `sequelize.models` by its model name. + +To learn with an example, we will consider that we want to create a model to represent users, which have a `firstName` and a `lastName`. We want our model to be called `User`, and the table it represents is called `Users` in the database. + +Both ways to define this model are shown below. After being defined, we can access our model with `sequelize.models.User`. + +### Using [`sequelize.define`](../class/lib/sequelize.js~Sequelize.html#instance-method-define): + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:'); + +const User = sequelize.define('User', { + // Model attributes are defined here + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING + // allowNull defaults to true + } +}, { + // Other model options go here +}); + +// `sequelize.define` also returns the model +console.log(User === sequelize.models.User); // true +``` + +### Extending [Model](../class/lib/model.js~Model.html) + +```js +const { Sequelize, DataTypes, Model } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory'); + +class User extends Model {} + +User.init({ + // Model attributes are defined here + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING + // allowNull defaults to true + } +}, { + // Other model options go here + sequelize, // We need to pass the connection instance + modelName: 'User' // We need to choose the model name +}); + +// the defined model is the class itself +console.log(User === sequelize.models.User); // true +``` + +Internally, `sequelize.define` calls `Model.init`, so both approaches are essentially equivalent. + +## Table name inference + +Observe that, in both methods above, the table name (`Users`) was never explicitly defined. However, the model name was given (`User`). + +By default, when the table name is not given, Sequelize automatically pluralizes the model name and uses that as the table name. This pluralization is done under the hood by a library called [inflection](https://www.npmjs.com/package/inflection), so that irregular plurals (such as `person -> people`) are computed correctly. + +Of course, this behavior is easily configurable. + +### Enforcing the table name to be equal to the model name + +You can stop the auto-pluralization performed by Sequelize using the `freezeTableName: true` option. This way, Sequelize will infer the table name to be equal to the model name, without any modifications: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + freezeTableName: true +}); +``` + +The example above will create a model named `User` pointing to a table also named `User`. + +This behavior can also be defined globally for the sequelize instance, when it is created: + +```js +const sequelize = new Sequelize('sqlite::memory:', { + define: { + freezeTableName: true + } +}); +``` + +This way, all tables will use the same name as the model name. + +### Providing the table name directly + +You can simply tell Sequelize the name of the table directly as well: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + tableName: 'Employees' +}); +``` + +## Model synchronization + +When you define a model, you're telling Sequelize a few things about its table in the database. However, what if the table actually doesn't even exist in the database? What if it exists, but it has different columns, less columns, or any other difference? + +This is where model synchronization comes in. A model can be synchronized with the database by calling [`model.sync(options)`](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-sync), an asynchronous function (that returns a Promise). With this call, Sequelize will automatically perform an SQL query to the database. Note that this changes only the table in the database, not the model in the JavaScript side. + +* `User.sync()` - This creates the table if it doesn't exist (and does nothing if it already exists) +* `User.sync({ force: true })` - This creates the table, dropping it first if it already existed +* `User.sync({ alter: true })` - This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model. + +Example: + +```js +await User.sync({ force: true }); +console.log("The table for the User model was just (re)created!"); +``` + +### Synchronizing all models at once + +You can use [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync) to automatically synchronize all models. Example: + +```js +await sequelize.sync({ force: true }); +console.log("All models were synchronized successfully."); +``` + +### Dropping tables + +To drop the table related to a model: + +```js +await User.drop(); +console.log("User table dropped!"); +``` + +To drop all tables: + +```js +await sequelize.drop(); +console.log("All tables dropped!"); +``` + +### Database safety check + +As shown above, the `sync` and `drop` operations are destructive. Sequelize acceps a `match` option as an additional safety check, which receives a RegExp: + +```js +// This will run .sync() only if database name ends with '_test' +sequelize.sync({ force: true, match: /_test$/ }); +``` + +### Synchronization in production + +As shown above, `sync({ force: true })` and `sync({ alter: true })` can be destructive operations. Therefore, they are not recommended for production-level software. Instead, synchronization should be done with the advanced concept of [Migrations](migrations.html), with the help of the [Sequelize CLI](https://github.com/sequelize/cli). + +## Timestamps + +By default, Sequelize automatically adds the fields `createdAt` and `updatedAt` to every model, using the data type `DataTypes.DATE`. Those fields are automatically managed as well - whenever you use Sequelize to create or update something, those fields will be set correctly. The `createdAt` field will contain the timestamp representing the moment of creation, and the `updatedAt` will contain the timestamp of the latest update. + +**Note:** This is done in the Sequelize level (i.e. not done with *SQL triggers*). This means that direct SQL queries (for example queries performed without Sequelize by any other means) will not cause these fields to be updated automatically. + +This behavior can be disabled for a model with the `timestamps: false` option: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + timestamps: false +}); +``` + +It is also possible to enable only one of `createdAt`/`updatedAt`, and to provide a custom name for these columns: + +```js +class Foo extends Model {} +Foo.init({ /* attributes */ }, { + sequelize, + + // don't forget to enable timestamps! + timestamps: true, + + // I don't want createdAt + createdAt: false, + + // I want updatedAt to actually be called updateTimestamp + updatedAt: 'updateTimestamp' +}); +``` + +## Column declaration shorthand syntax + +If the only thing being specified about a column is its data type, the syntax can be shortened: + +```js +// This: +sequelize.define('User', { + name: { + type: DataTypes.STRING + } +}); + +// Can be simplified to: +sequelize.define('User', { name: DataTypes.STRING }); +``` + +## Default Values + +By default, Sequelize assumes that the default value of a column is `NULL`. This behavior can be changed by passing a specific `defaultValue` to the column definition: + +```js +sequelize.define('User', { + name: { + type: DataTypes.STRING, + defaultValue: "John Doe" + } +}); +``` + +Some special values, such as `Sequelize.NOW`, are also accepted: + +```js +sequelize.define('Foo', { + bar: { + type: DataTypes.DATETIME, + defaultValue: Sequelize.NOW + // This way, the current date/time will be used to populate this column (at the moment of insertion) + } +}); +``` + +## Data Types + +Every column you define in your model must have a data type. Sequelize provides [a lot of built-in data types](https://github.com/sequelize/sequelize/blob/master/lib/data-types.js). To access a built-in data type, you must import `DataTypes`: + +```js +const { DataTypes } = require("sequelize"); // Import the built-in data types +``` + +### Strings + +```js +DataTypes.STRING // VARCHAR(255) +DataTypes.STRING(1234) // VARCHAR(1234) +DataTypes.STRING.BINARY // VARCHAR BINARY +DataTypes.TEXT // TEXT +DataTypes.TEXT('tiny') // TINYTEXT +DataTypes.CITEXT // CITEXT PostgreSQL and SQLite only. +``` + +### Boolean + +```js +DataTypes.BOOLEAN // TINYINT(1) +``` + +### Numbers + +```js +DataTypes.INTEGER // INTEGER +DataTypes.BIGINT // BIGINT +DataTypes.BIGINT(11) // BIGINT(11) + +DataTypes.FLOAT // FLOAT +DataTypes.FLOAT(11) // FLOAT(11) +DataTypes.FLOAT(11, 10) // FLOAT(11,10) + +DataTypes.REAL // REAL PostgreSQL only. +DataTypes.REAL(11) // REAL(11) PostgreSQL only. +DataTypes.REAL(11, 12) // REAL(11,12) PostgreSQL only. + +DataTypes.DOUBLE // DOUBLE +DataTypes.DOUBLE(11) // DOUBLE(11) +DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) + +DataTypes.DECIMAL // DECIMAL +DataTypes.DECIMAL(10, 2) // DECIMAL(10,2) +``` + +#### Unsigned & Zerofill integers - MySQL/MariaDB only + +In MySQL and MariaDB, the data types `INTEGER`, `BIGINT`, `FLOAT` and `DOUBLE` can be set as unsigned or zerofill (or both), as follows: + +```js +DataTypes.INTEGER.UNSIGNED +DataTypes.INTEGER.ZEROFILL +DataTypes.INTEGER.UNSIGNED.ZEROFILL +// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER +// Same for BIGINT, FLOAT and DOUBLE +``` + +### Dates + +```js +DataTypes.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres +DataTypes.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision +DataTypes.DATEONLY // DATE without time +``` + +### UUIDs + +For UUIDs, use `DataTypes.UUID`. It becomes the `UUID` data type for PostgreSQL and SQLite, and `CHAR(36)` for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use `Sequelize.UUIDV1` or `Sequelize.UUIDV4` as the default value: + +```js +{ + type: DataTypes.UUID, + defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1 +} +``` + +### Others + +There are other data types, covered in a [separate guide](other-data-types.html). + +## Column Options + +When defining a column, apart from specifying the `type` of the column, and the `allowNull` and `defaultValue` options mentioned above, there are a lot more options that can be used. Some examples are below. + +```js +const { Model, DataTypes, Deferrable } = require("sequelize"); + +class Foo extends Model {} +Foo.init({ + // instantiating will automatically set the flag to true if not set + flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, + + // default values for dates => current time + myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + + // setting allowNull to false will add NOT NULL to the column, which means an error will be + // thrown from the DB when the query is executed if the column is null. If you want to check that a value + // is not null before querying the DB, look at the validations section below. + title: { type: DataTypes.STRING, allowNull: false }, + + // Creating two objects with the same value will throw an error. The unique property can be either a + // boolean, or a string. If you provide the same string for multiple columns, they will form a + // composite unique key. + uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' }, + uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' }, + + // The unique property is simply a shorthand to create a unique constraint. + someUnique: { type: DataTypes.STRING, unique: true }, + + // Go on reading for further information about primary keys + identifier: { type: DataTypes.STRING, primaryKey: true }, + + // autoIncrement can be used to create auto_incrementing integer columns + incrementMe: { type: DataTypes.INTEGER, autoIncrement: true }, + + // You can specify a custom column name via the 'field' attribute: + fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' }, + + // It is possible to create foreign keys: + bar_id: { + type: DataTypes.INTEGER, + + references: { + // This is a reference to another model + model: Bar, + + // This is the column name of the referenced model + key: 'id', + + // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type. + deferrable: Deferrable.INITIALLY_IMMEDIATE + // Options: + // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints + // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction + // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction + } + }, + + // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL + commentMe: { + type: DataTypes.INTEGER, + comment: 'This is a column name that has a comment' + } +}, { + sequelize, + modelName: 'foo', + + // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options: + indexes: [{ unique: true, fields: ['someUnique'] }] +}); +``` + +## Taking advantage of Models being classes + +The Sequelize models are [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). You can very easily add custom instance or class level methods. + +```js +class User extends Model { + static classLevelMethod() { + return 'foo'; + } + instanceLevelMethod() { + return 'bar'; + } + getFullname() { + return [this.firstname, this.lastname].join(' '); + } +} +User.init({ + firstname: Sequelize.TEXT, + lastname: Sequelize.TEXT +}, { sequelize }); + +console.log(User.classLevelMethod()); // 'foo' +const user = User.build({ firstname: 'Jane', lastname: 'Doe' }); +console.log(user.instanceLevelMethod()); // 'bar' +console.log(user.getFullname()); // 'Jane Doe' +``` \ No newline at end of file diff --git a/docs/manual/core-concepts/model-instances.md b/docs/manual/core-concepts/model-instances.md new file mode 100644 index 000000000000..29c3ce942de4 --- /dev/null +++ b/docs/manual/core-concepts/model-instances.md @@ -0,0 +1,172 @@ +# Model Instances + +As you already know, a model is an [ES6 class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). An instance of the class represents one object from that model (which maps to one row of the table in the database). This way, model instances are [DAOs](https://en.wikipedia.org/wiki/Data_access_object). + +For this guide, the following setup will be assumed: + +```js +const { Sequelize, Model, DataTypes } = require("sequelize"); +const sequelize = new Sequelize("sqlite::memory:"); + +const User = sequelize.define("user", { + name: DataTypes.TEXT, + favoriteColor: { + type: DataTypes.TEXT, + defaultValue: 'green' + }, + age: DataTypes.INTEGER, + cash: DataTypes.INTEGER +}); + +(async () => { + await sequelize.sync({ force: true }); + // Code here +})(); +``` + +## Creating an instance + +Although a model is a class, you should not create instances by using the `new` operator directly. Instead, the [`build`](../class/lib/model.js~Model.html#static-method-build) method should be used: + +```js +const jane = User.build({ name: "Jane" }); +console.log(jane instanceof User); // true +console.log(jane.name); // "Jane" +``` + +However, the code above does not communicate with the database at all (note that it is not even asynchronous)! This is because the [`build`](../class/lib/model.js~Model.html#static-method-build) method only creates an object that *represents* data that *can* be mapped to a database. In order to really save (i.e. persist) this instance in the database, the [`save`](../class/lib/model.js~Model.html#instance-method-save) method should be used: + +```js +await jane.save(); +console.log('Jane was saved to the database!'); +``` + +Note, from the usage of `await` in the snippet above, that `save` is an asynchronous method. In fact, almost every Sequelize method is asynchronous; `build` is one of the very few exceptions. + +### A very useful shortcut: the `create` method + +Sequelize provides the [`create`](../class/lib/model.js~Model.html#static-method-create) method, which combines the `build` and `save` methods shown above into a single method: + +```js +const jane = await User.create({ name: "Jane" }); +// Jane exists in the database now! +console.log(jane instanceof User); // true +console.log(jane.name); // "Jane" +``` + +## Note: logging instances + +Trying to log a model instance directly to `console.log` will produce a lot of clutter, since Sequelize instances have a lot of things attached to them. Instead, you can use the `.toJSON()` method (which, by the way, automatically guarantees the instances to be `JSON.stringify`-ed well). + +```js +const jane = await User.create({ name: "Jane" }); +// console.log(jane); // Don't do this +console.log(jane.toJSON()); // This is good! +console.log(JSON.stringify(jane, null, 4)); // This is also good! +``` + +## Default values + +Built instances will automatically get default values: + +```js +const jane = User.build({ name: "Jane" }); +console.log(jane.favoriteColor); // "green" +``` + +## Updating an instance + +If you change the value of some field of an instance, calling `save` again will update it accordingly: + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +jane.name = "Ada"; +// the name is still "Jane" in the database +await jane.save(); +// Now the name was updated to "Ada" in the database! +``` + +## Deleting an instance + +You can delete an instance by calling [`destroy`](../class/lib/model.js~Model.html#instance-method-destroy): + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +await jane.destroy(); +// Now this entry was removed from the database +``` + +## Reloading an instance + +You can reload an instance from the database by calling [`reload`](../class/lib/model.js~Model.html#instance-method-reload): + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +jane.name = "Ada"; +// the name is still "Jane" in the database +await jane.reload(); +console.log(jane.name); // "Jane" +``` + +The reload call generates a `SELECT` query to get the up-to-date data from the database. + +## Saving only some fields + +It is possible to define which attributes should be saved when calling `save`, by passing an array of column names. + +This is useful when you set attributes based on a previously defined object, for example, when you get the values of an object via a form of a web app. Furthermore, this is used internally in the `update` implementation. This is how it looks like: + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +console.log(jane.favoriteColor); // "green" +jane.name = "Jane II"; +jane.favoriteColor = "blue"; +await jane.save({ fields: ['name'] }); +console.log(jane.name); // "Jane II" +console.log(jane.favoriteColor); // "blue" +// The above printed blue because the local object has it set to blue, but +// in the database it is still "green": +await jane.reload(); +console.log(jane.name); // "Jane II" +console.log(jane.favoriteColor); // "green" +``` + +## Change-awareness of save + +The `save` method is optimized internally to only update fields that really changed. This means that if you don't change anything and call `save`, Sequelize will know that the save is superfluous and do nothing, i.e., no query will be generated (it will still return a Promise, but it will resolve immediately). + +Also, if only a few attributes have changed when you call `save`, only those fields will be sent in the `UPDATE` query, to improve performance. + +## Incrementing and decrementing integer values + +In order to increment/decrement values of an instance without running into concurrency issues, Sequelize provides the [`increment`](../class/lib/model.js~Model.html#instance-method-increment) and [`decrement`](../class/lib/model.js~Model.html#instance-method-decrement) instance methods. + +```js +const jane = await User.create({ name: "Jane", age: 100 }); +const incrementResult = await user.increment('age', { by: 2 }); +// Note: to increment by 1 you can omit the `by` option and just do `user.increment('age')` + +// In PostgreSQL, `incrementResult` will be the updated user, unless the option +// `{ returning: false }` was set (and then it will be undefined). + +// In other dialects, `incrementResult` will be undefined. If you need the updated instance, you will have to call `user.reload()`. +``` + +You can also increment multiple fields at once: + +```js +const jane = await User.create({ name: "Jane", age: 100, cash: 5000 }); +await jane.increment({ + 'age': 2, + 'cash': 100 +}); + +// If the values are incremented by the same amount, you can use this other syntax as well: +await jane.increment(['age', 'cash'], { by: 2 }); +``` + +Decrementing works in the exact same way. \ No newline at end of file diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md new file mode 100644 index 000000000000..684066a99c0b --- /dev/null +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -0,0 +1,700 @@ +# Model Querying - Basics + +Sequelize provides various methods to assist querying your database for data. + +*Important notice: to perform production-ready queries with Sequelize, make sure you have read the [Transactions guide](transactions.html) as well. Transactions are important to ensure data integrity and to provide other benefits.* + +This guide will show how to make the standard [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) queries. + +## Simple INSERT queries + +First, a simple example: + +```js +// Create a new user +const jane = await User.create({ firstName: "Jane", lastName: "Doe" }); +console.log("Jane's auto-generated ID:", jane.id); +``` + +The [`Model.create()`](../class/lib/model.js~Model.html#static-method-create) method is a shorthand for building an unsaved instance with [`Model.build()`](../class/lib/model.js~Model.html#static-method-build) and saving the instance with [`instance.save()`](../class/lib/model.js~Model.html#instance-method-save). + +It is also possible to define which attributes can be set in the `create` method. This can be especially useful if you create database entries based on a form which can be filled by a user. Using that would for example allow you to restrict the `User` model to set only an username and an address but not an admin flag: + +```js +const user = await User.create({ + username: 'alice123', + isAdmin: true +}, { fields: ['username'] }); +// let's assume the default of isAdmin is false +console.log(user.username); // 'alice123' +console.log(user.isAdmin); // false +``` + +## Simple SELECT queries + +You can read the whole table from the database with the [`findAll`](../class/lib/model.js~Model.html#static-method-findAll) method: + +```js +// Find all users +const users = await User.findAll(); +console.log(users.every(user => user instanceof User)); // true +console.log("All users:", JSON.stringify(users, null, 2)); +``` + +```sql +SELECT * FROM ... +``` + +## Specifying attributes for SELECT queries + +To select only some attributes, you can use the `attributes` option: + +```js +Model.findAll({ + attributes: ['foo', 'bar'] +}); +``` + +```sql +SELECT foo, bar FROM ... +``` + +Attributes can be renamed using a nested array: + +```js +Model.findAll({ + attributes: ['foo', ['bar', 'baz'], 'qux'] +}); +``` + +```sql +SELECT foo, bar AS baz, qux FROM ... +``` + +You can use [`sequelize.fn`](../class/lib/sequelize.js~Sequelize.html#static-method-fn) to do aggregations: + +```js +Model.findAll({ + attributes: [ + 'foo', + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'] + 'bar' + ] +}); +``` + +```sql +SELECT foo, COUNT(hats) AS n_hats, bar FROM ... +``` + +When using aggregation function, you must give it an alias to be able to access it from the model. In the example above you can get the number of hats with `instance.n_hats`. + +Sometimes it may be tiresome to list all the attributes of the model if you only want to add an aggregation: + +```js +// This is a tiresome way of getting the number of hats (along with every column) +Model.findAll({ + attributes: [ + 'id', 'foo', 'bar', 'baz', 'qux', 'hats', // We had to list all attributes... + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'] // To add the aggregation... + ] +}); + +// This is shorter, and less error prone because it still works if you add / remove attributes from your model later +Model.findAll({ + attributes: { + include: [ + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'] + ] + } +}); +``` + +```sql +SELECT id, foo, bar, baz, qux, hats, COUNT(hats) AS n_hats FROM ... +``` + +Similarly, it's also possible to remove a selected few attributes: + +```js +Model.findAll({ + attributes: { exclude: ['baz'] } +}); +``` + +```sql +-- Assuming all columns are 'id', 'foo', 'bar', 'baz' and 'qux' +SELECT id, foo, bar, qux FROM ... +``` + +## Applying WHERE clauses + +The `where` option is used to filter the query. There are lots of operators to use for the `where` clause, available as Symbols from [`Op`](../variable/index.html#static-variable-Op). + +### The basics + +```js +Post.findAll({ + where: { + authorId: 2 + } +}); +// SELECT * FROM post WHERE authorId = 2 +``` + +Observe that no operator (from `Op`) was explicitly passed, so Sequelize assumed an equality comparison by default. The above code is equivalent to: + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + authorId: { + [Op.eq]: 2 + } + } +}); +// SELECT * FROM post WHERE authorId = 2 +``` + +Multiple checks can be passed: + +```js +Post.findAll({ + where: { + authorId: 12 + status: 'active' + } +}); +// SELECT * FROM post WHERE authorId = 12 AND status = 'active'; +``` + +Just like Sequelize inferred the `Op.eq` operator in the first example, here Sequelize inferred that the caller wanted an `AND` for the two checks. The code above is equivalent to: + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + [Op.and]: [ + { authorId: 12 }, + { status: 'active' } + ] + } +}); +// SELECT * FROM post WHERE authorId = 12 AND status = 'active'; +``` + +An `OR` can be easily performed in a similar way: + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + [Op.or]: [ + { authorId: 12 }, + { authorId: 13 } + ] + } +}); +// SELECT * FROM post WHERE authorId = 12 OR authorId = 13; +``` + +Since the above was an `OR` involving the same field, Sequelize allows you to use a slightly different structure which is more readable and generates the same behavior: + +```js +const { Op } = require("sequelize"); +Post.destroy({ + where: { + authorId: { + [Op.or]: [12, 13] + } + } +}); +// DELETE FROM post WHERE authorId = 12 OR authorId = 13; +``` + +### Operators + +Sequelize provides several operators. + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + [Op.and]: [{ a: 5 }, { b: 6 }], // (a = 5) AND (b = 6) + [Op.or]: [{ a: 5 }, { b: 6 }], // (a = 5) OR (b = 6) + someAttribute: { + // Basics + [Op.eq]: 3, // = 3 + [Op.ne]: 20, // != 20 + [Op.is]: null, // IS NULL + [Op.not]: true, // IS NOT TRUE + [Op.or]: [5, 6], // (someAttribute = 5) OR (someAttribute = 6) + + // Using dialect specific column identifiers (PG in the following example): + [Op.col]: 'user.organization_id', // = "user"."organization_id" + + // Number comparisons + [Op.gt]: 6, // > 6 + [Op.gte]: 6, // >= 6 + [Op.lt]: 10, // < 10 + [Op.lte]: 10, // <= 10 + [Op.between]: [6, 10], // BETWEEN 6 AND 10 + [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 + + // Other operators + + [Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1) + + [Op.in]: [1, 2], // IN [1, 2] + [Op.notIn]: [1, 2], // NOT IN [1, 2] + + [Op.like]: '%hat', // LIKE '%hat' + [Op.notLike]: '%hat', // NOT LIKE '%hat' + [Op.startsWith]: 'hat', // LIKE 'hat%' + [Op.endsWith]: 'hat', // LIKE '%hat' + [Op.substring]: 'hat', // LIKE '%hat%' + [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) + [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) + [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) + [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) + [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) + [Op.notIRegexp]: '^[h|a|t]', // !~* '^[h|a|t]' (PG only) + + [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + + // In Postgres, Op.like/Op.iLike/Op.notLike can be combined to Op.any: + [Op.like]: { [Op.any]: ['cat', 'hat'] } // LIKE ANY ARRAY['cat', 'hat'] + + // There are more postgres-only range operators, see below + } + } +}); +``` + +#### Shorthand syntax for `Op.in` + +Passing an array directly to the `where` option will implicitly use the `IN` operator: + +```js +Post.findAll({ + where: { + id: [1,2,3] // Same as using `id: { [Op.in]: [1,2,3] }` + } +}); +// SELECT ... FROM "posts" AS "post" WHERE "post"."id" IN (1, 2, 3); +``` + +### Logical combinations with operators + +The operators `Op.and`, `Op.or` and `Op.not` can be used to create arbitrarily complex nested logical comparisons. + +#### Examples with `Op.and` and `Op.or` + +```js +const { Op } = require("sequelize"); + +Foo.findAll({ + where: { + rank: { + [Op.or]: { + [Op.lt]: 1000, + [Op.eq]: null + } + }, + // rank < 1000 OR rank IS NULL + + { + createdAt: { + [Op.lt]: new Date(), + [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000) + } + }, + // createdAt < [timestamp] AND createdAt > [timestamp] + + { + [Op.or]: [ + { + title: { + [Op.like]: 'Boat%' + } + }, + { + description: { + [Op.like]: '%boat%' + } + } + ] + } + // title LIKE 'Boat%' OR description LIKE '%boat%' + } +}); +``` + +#### Examples with `Op.not` + +```js +Project.findAll({ + where: { + name: 'Some Project', + [Op.not]: [ + { id: [1,2,3] }, + { + description: { + [Op.like]: 'Hello%' + } + } + ] + } +}); +``` + +The above will generate: + +```sql +SELECT * +FROM `Projects` +WHERE ( + `Projects`.`name` = 'a project' + AND NOT ( + `Projects`.`id` IN (1,2,3) + OR + `Projects`.`description` LIKE 'Hello%' + ) +) +``` + +### Advanced queries with functions (not just columns) + +What if you wanted to obtain something like `WHERE char_length("content") = 7`? + +```js +Post.findAll({ + where: sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7) +}); +// SELECT ... FROM "posts" AS "post" WHERE char_length("content") = 7 +``` + +Note the usage of the [`sequelize.fn`](../class/lib/sequelize.js~Sequelize.html#static-method-fn) and [`sequelize.col`](../class/lib/sequelize.js~Sequelize.html#static-method-col) methods, which should be used to specify an SQL function call and a table column, respectively. These methods should be used instead of passing a plain string (such as `char_length(content)`) because Sequelize needs to treat this situation differently (for example, using other symbol escaping approaches). + +What if you need something even more complex? + +```js +Post.findAll({ + where: { + [Op.or]: [ + sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7), + { + content: { + [Op.like]: 'Hello%' + } + }, + { + [Op.and]: [ + { status: 'draft' }, + sequelize.where(sequelize.fn('char_length', sequelize.col('content')), { + [Op.gt]: 10 + }) + ] + } + ] + } +}); +``` + +The above generates the following SQL: + +```sql +SELECT + ... +FROM "posts" AS "post" +WHERE ( + char_length("content") = 7 + OR + "post"."content" LIKE 'Hello%' + OR ( + "post"."status" = 'draft' + AND + char_length("content") > 10 + ) +) +``` + +### Postgres-only Range Operators + +Range types can be queried with all supported operators. + +Keep in mind, the provided range value can [define the bound inclusion/exclusion](data-types.html#range-types) as well. + +```js +[Op.contains]: 2, // @> '2'::integer (PG range contains element operator) +[Op.contains]: [1, 2], // @> [1, 2) (PG range contains range operator) +[Op.contained]: [1, 2], // <@ [1, 2) (PG range is contained by operator) +[Op.overlap]: [1, 2], // && [1, 2) (PG range overlap (have points in common) operator) +[Op.adjacent]: [1, 2], // -|- [1, 2) (PG range is adjacent to operator) +[Op.strictLeft]: [1, 2], // << [1, 2) (PG range strictly left of operator) +[Op.strictRight]: [1, 2], // >> [1, 2) (PG range strictly right of operator) +[Op.noExtendRight]: [1, 2], // &< [1, 2) (PG range does not extend to the right of operator) +[Op.noExtendLeft]: [1, 2], // &> [1, 2) (PG range does not extend to the left of operator) +``` + +### Deprecated: Operator Aliases + +In Sequelize v4, it was possible to specify strings to refer to operators, instead of using Symbols. This is now deprecated and heavily discouraged, and will probably be removed in the next major version. If you really need it, you can pass the `operatorAliases` option in the Sequelize constructor. + +For example: + +```js +const { Sequelize, Op } = require("sequelize"); +const sequelize = new Sequelize('sqlite::memory:', { + operatorsAliases: { + $gt: Op.gt + } +}); + +// Now we can use `$gt` instead of `[Op.gt]` in where clauses: +Foo.findAll({ + where: { + $gt: 6 // Works like using [Op.gt] + } +}); +``` + +## Simple UPDATE queries + +Update queries also accept the `where` option, just like the read queries shown above. + +```js +// Change everyone without a last name to "Doe" +await User.update({ lastName: "Doe" }, { + where: { + lastName: null + } +}); +``` + +## Simple DELETE queries + +Delete queries also accept the `where` option, just like the read queries shown above. + +```js +// Delete everyone named "Jane" +await User.destroy({ + where: { + firstName: "Jane" + } +}); +``` + +To destroy everything the `TRUNCATE` SQL can be used: + +```js +// Truncate the table +await User.destroy({ + truncate: true +}); +``` + +## Creating in bulk + +Sequelize provides the `Model.bulkCreate` method to allow creating multiple records at once, with only one query. + +The usage of `Model.bulkCreate` is very similar to `Model.create`, by receiving an array of objects instead of a single object. + +```js +const captains = await Captain.bulkCreate([ + { name: 'Jack Sparrow' }, + { name: 'Davy Jones' } +]); +console.log(captains.length); // 2 +console.log(captains[0] instanceof Captain); // true +console.log(captains[0].name); // 'Jack Sparrow' +console.log(captains[0].id); // 1 // (or another auto-generated value) +``` + +However, by default, `bulkCreate` does not run validations on each object that is going to be created (which `create` does). To make `bulkCreate` run these validations as well, you must pass the `validate: true` option. This will decrease performance. Usage example: + +```js +const Foo = sequelize.define('foo', { + bar: { + type: DataTypes.TEXT, + validate: { + len: [4, 6] + } + } +}); + +// This will not throw an error, both instances will be created +await Foo.bulkCreate([ + { name: 'abc123' }, + { name: 'name too long' } +]); + +// This will throw an error, nothing will be created +await Foo.bulkCreate([ + { name: 'abc123' }, + { name: 'name too long' } +], { validate: true }); +``` + +If you are accepting values directly from the user, it might be beneficial to limit the columns that you want to actually insert. To support this, `bulkCreate()` accepts a `fields` option, an array defining which fields must be considered (the rest will be ignored). + +```js +await User.bulkCreate([ + { username: 'foo' }, + { username: 'bar', admin: true } +], { fields: ['username'] }); +// Neither foo nor bar are admins. +``` + +## Ordering and Grouping + +Sequelize provides the `order` and `group` options to work with `ORDER BY` and `GROUP BY`. + +### Ordering + +The `order` option takes an array of items to order the query by or a sequelize method. These *items* are themselves arrays in the form `[column, direction]`. The column will be escaped correctly and the direction will be checked in a whitelist of valid directions (such as `ASC`, `DESC`, `NULLS FIRST`, etc). + +```js +Subtask.findAll({ + order: [ + // Will escape title and validate DESC against a list of valid direction parameters + ['title', 'DESC'], + + // Will order by max(age) + sequelize.fn('max', sequelize.col('age')), + + // Will order by max(age) DESC + [sequelize.fn('max', sequelize.col('age')), 'DESC'], + + // Will order by otherfunction(`col1`, 12, 'lalala') DESC + [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], + + // Will order an associated model's createdAt using the model name as the association's name. + [Task, 'createdAt', 'DESC'], + + // Will order through an associated model's createdAt using the model names as the associations' names. + [Task, Project, 'createdAt', 'DESC'], + + // Will order by an associated model's createdAt using the name of the association. + ['Task', 'createdAt', 'DESC'], + + // Will order by a nested associated model's createdAt using the names of the associations. + ['Task', 'Project', 'createdAt', 'DESC'], + + // Will order by an associated model's createdAt using an association object. (preferred method) + [Subtask.associations.Task, 'createdAt', 'DESC'], + + // Will order by a nested associated model's createdAt using association objects. (preferred method) + [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'], + + // Will order by an associated model's createdAt using a simple association object. + [{model: Task, as: 'Task'}, 'createdAt', 'DESC'], + + // Will order by a nested associated model's createdAt simple association objects. + [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC'] + ], + + // Will order by max age descending + order: sequelize.literal('max(age) DESC'), + + // Will order by max age ascending assuming ascending is the default order when direction is omitted + order: sequelize.fn('max', sequelize.col('age')), + + // Will order by age ascending assuming ascending is the default order when direction is omitted + order: sequelize.col('age'), + + // Will order randomly based on the dialect (instead of fn('RAND') or fn('RANDOM')) + order: sequelize.random() +}); + +Foo.findOne({ + order: [ + // will return `name` + ['name'], + // will return `username` DESC + ['username', 'DESC'], + // will return max(`age`) + sequelize.fn('max', sequelize.col('age')), + // will return max(`age`) DESC + [sequelize.fn('max', sequelize.col('age')), 'DESC'], + // will return otherfunction(`col1`, 12, 'lalala') DESC + [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], + // will return otherfunction(awesomefunction(`col`)) DESC, This nesting is potentially infinite! + [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'] + ] +}); +``` + +To recap, the elements of the order array can be the following: + +* A string (which will be automatically quoted) +* An array, whose first element will be quoted, second will be appended verbatim +* An object with a `raw` field: + * The content of `raw` will be added verbatim without quoting + * Everything else is ignored, and if raw is not set, the query will fail +* A call to `Sequelize.fn` (which will generate a function call in SQL) +* A call to `Sequelize.col` (which will quoute the column name) + +### Grouping + +The syntax for grouping and ordering are equal, except that grouping does not accept a direction as last argument of the array (there is no `ASC`, `DESC`, `NULLS FIRST`, etc). + +You can also pass a string directly to `group`, which will be included directly (verbatim) into the generated SQL. Use with caution and don't use with user generated content. + +```js +Project.findAll({ group: 'name' }); +// yields 'GROUP BY name' +``` + +## Limits and Pagination + +The `limit` and `offset` options allow you to work with limiting / pagination: + +```js +// Fetch 10 instances/rows +Project.findAll({ limit: 10 }); + +// Skip 8 instances/rows +Project.findAll({ offset: 8 }); + +// Skip 5 instances and fetch the 5 after that +Project.findAll({ offset: 5, limit: 5 }); +``` + +Usually these are used alongside the `order` option. + +## Utility methods + +Sequelize also provides a few utility methods. + +### `count` + +The `count` method simply counts the occurrences of elements in the database. + +```js +console.log(`There are ${await Project.count()} projects`); + +const amount = await Project.count({ + where: { + id: { + [Op.gt]: 25 + } + } +}); +console.log(`There are ${amount} projects with an id greater than 25`); +``` + +### `max`, `min` and `sum` + +Sequelize also provides the `max`, `min` and `sum` convenience methods. + +Let's assume we have three users, whose ages are 10, 5, and 40. + +```js +await User.max('age'); // 40 +await User.max('age', { where: { age: { [Op.lt]: 20 } } }); // 10 +await User.min('age'); // 5 +await User.min('age', { where: { age: { [Op.gt]: 5 } } }); // 10 +await User.sum('age'); // 55 +await User.sum('age', { where: { age: { [Op.gt]: 5 } } }); // 50 +``` diff --git a/docs/manual/core-concepts/model-querying-finders.md b/docs/manual/core-concepts/model-querying-finders.md new file mode 100644 index 000000000000..c5644a9dca57 --- /dev/null +++ b/docs/manual/core-concepts/model-querying-finders.md @@ -0,0 +1,83 @@ +# Model Querying - Finders + +Finder methods are the ones that generate `SELECT` queries. + +By default, the results of all finder methods are instances of the model class (as opposed to being just plain JavaScript objects). This means that after the database returns the results, Sequelize automatically wraps everything in proper instance objects. In a few cases, when there are too many results, this wrapping can be inefficient. To disable this wrapping and receive a plain response instead, pass `{ raw: true }` as an option to the finder method. + +## `findAll` + +The `findAll` method is already known from the previous tutorial. It generates a standard `SELECT` query which will retrieve all entries from the table (unless restricted by something like a `where` clause, for example). + +## `findByPk` + +The `findByPk` method obtains only a single entry from the table, using the provided primary key. + +```js +const project = await Project.findByPk(123); +if (project === null) { + console.log('Not found!'); +} else { + console.log(project instanceof Project); // true + // Its primary key is 123 +} +``` + +## `findOne` + +The `findOne` method obtains the first entry it finds (that fulfills the optional query options, if provided). + +```js +const project = await Project.findOne({ where: { title: 'My Title' } }); +if (project === null) { + console.log('Not found!'); +} else { + console.log(project instanceof Project); // true + console.log(project.title); // 'My Title' +} +``` + +## `findOrCreate` + +The method `findOrCreate` will create an entry in the table unless it can find one fulfilling the query options. In both cases, it will return an instance (either the found instance or the created instance) and a boolean indicating whether that instance was created or already existed. + +The `where` option is considered for finding the entry, and the `defaults` option is used to define what must be created in case nothing was found. If the `defaults` do not contain values for every column, Sequelize will take the values given to `where` (if present). + +Let's assume we have an empty database with a `User` model which has a `username` and a `job`. + +```js +const [user, created] = await User.findOrCreate({ + where: { username: 'sdepold' }, + defaults: { + job: 'Technical Lead JavaScript' + } +}); +console.log(user.username); // 'sdepold' +console.log(user.job); // This may or may not be 'Technical Lead JavaScript' +console.log(created); // The boolean indicating whether this instance was just created +if (created) { + console.log(user.job); // This will certainly be 'Technical Lead JavaScript' +} +``` + +## `findAndCountAll` + +The `findAndCountAll` method is a convenience method that combines `findAll` and `count`. This is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query. + +The `findAndCountAll` method returns an object with two properties: + +* `count` - an integer - the total number records matching the query +* `rows` - an array of objects - the obtained records + +```js +const { count, rows } = await Project.findAndCountAll({ + where: { + title: { + [Op.like]: 'foo%' + } + }, + offset: 10, + limit: 2 +}); +console.log(count); +console.log(rows); +``` \ No newline at end of file diff --git a/docs/manual/core-concepts/paranoid.md b/docs/manual/core-concepts/paranoid.md new file mode 100644 index 000000000000..dd580d0578a9 --- /dev/null +++ b/docs/manual/core-concepts/paranoid.md @@ -0,0 +1,105 @@ +# Paranoid + +Sequelize supports the concept of *paranoid* tables. A *paranoid* table is one that, when told to delete a record, it will not truly delete it. Instead, a special column called `deletedAt` will have its value set to the timestamp of that deletion request. + +This means that paranoid tables perform a *soft-deletion* of records, instead of a *hard-deletion*. + +## Defining a model as paranoid + +To make a model paranoid, you must pass the `paranoid: true` option to the model definition. Paranoid requires timestamps to work (i.e. it won't work if you also pass `timestamps: false`). + +You can also change the default column name (which is `deletedAt`) to something else. + +```js +class Post extends Model {} +Post.init({ /* attributes here */ }, { + sequelize, + paranoid: true, + + // If you want to give a custom name to the deletedAt column + deletedAt: 'destroyTime' +}); +``` + +## Deleting + +When you call the `destroy` method, a soft-deletion will happen: + +```js +await Post.destroy({ + where: { + id: 1 + } +}); +// UPDATE "posts" SET "deletedAt"=[timestamp] WHERE "deletedAt" IS NULL AND "id" = 1 +``` + +If you really want a hard-deletion and your model is paranoid, you can force it using the `force: true` option: + +```js +await Post.destroy({ + where: { + id: 1 + }, + force: true +}); +// DELETE FROM "posts" WHERE "id" = 1 +``` + +The above examples used the static `destroy` method as an example (`Post.destroy`), but everything works in the same way with the instance method: + +```js +const post = await Post.create({ title: 'test' }); +console.log(post instanceof Post); // true +await post.destroy(); // Would just set the `deletedAt` flag +await post.destroy({ force: true }); // Would really delete the record +``` + +## Restoring + +To restore soft-deleted records, you can use the `restore` method, which comes both in the static version as well as in the instance version: + +```js +// Example showing the instance `restore` method +// We create a post, soft-delete it and then restore it back +const post = await Post.create({ title: 'test' }); +console.log(post instanceof Post); // true +await post.destroy(); +console.log('soft-deleted!'); +await post.restore(); +console.log('restored!'); + +// Example showing the static `restore` method. +// Restoring every soft-deleted post with more than 100 likes +await Post.restore({ + where: { + likes: { + [Op.gt]: 100 + } + } +}); +``` + +## Behavior with other queries + +Every query performed by Sequelize will automatically ignore soft-deleted records (except raw queries, of course). + +This means that, for example, the `findAll` method will not see the soft-deleted records, fetching only the ones that were not deleted. + +Even if you simply call `findByPk` providing the primary key of a soft-deleted record, the result will be `null` as if that record didn't exist. + +If you really want to let the query see the soft-deleted records, you can pass the `paranoid: false` option to the query method. For example: + +```js +await Post.findByPk(123); // This will return `null` if the record of id 123 is soft-deleted +await Post.findByPk(123, { paranoid: false }); // This will retrieve the record + +await Post.findAll({ + where: { foo: 'bar' } +}); // This will not retrieve soft-deleted records + +await Post.findAll({ + where: { foo: 'bar' }, + paranoid: false +}); // This will also retrieve soft-deleted records +``` \ No newline at end of file diff --git a/docs/manual/raw-queries.md b/docs/manual/core-concepts/raw-queries.md similarity index 54% rename from docs/manual/raw-queries.md rename to docs/manual/core-concepts/raw-queries.md index 3bf8e450c307..31cedd8d0b7f 100644 --- a/docs/manual/raw-queries.md +++ b/docs/manual/core-concepts/raw-queries.md @@ -1,44 +1,40 @@ -# Raw queries +# Raw Queries -As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can use the function `sequelize.query`. +As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can use the [`sequelize.query`](../class/lib/sequelize.js~Sequelize.html#instance-method-query) method. -By default the function will return two arguments - a results array, and an object containing metadata (affected rows etc.). Note that since this is a raw query, the metadata (property names etc.) is dialect specific. Some dialects return the metadata "within" the results object (as properties on an array). However, two arguments will always be returned, but for MSSQL and MySQL it will be two references to the same object. +By default the function will return two arguments - a results array, and an object containing metadata (such as amount of affected rows, etc). Note that since this is a raw query, the metadata are dialect specific. Some dialects return the metadata "within" the results object (as properties on an array). However, two arguments will always be returned, but for MSSQL and MySQL it will be two references to the same object. ```js -sequelize.query("UPDATE users SET y = 42 WHERE x = 12").then(([results, metadata]) => { - // Results will be an empty array and metadata will contain the number of affected rows. -}) +const [results, metadata] = await sequelize.query("UPDATE users SET y = 42 WHERE x = 12"); +// Results will be an empty array and metadata will contain the number of affected rows. ``` In cases where you don't need to access the metadata you can pass in a query type to tell sequelize how to format the results. For example, for a simple select query you could do: ```js -sequelize.query("SELECT * FROM `users`", { type: sequelize.QueryTypes.SELECT}) - .then(users => { - // We don't need spread here, since only the results will be returned for select queries - }) +const { QueryTypes } = require('sequelize'); +const users = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes.SELECT }); +// We didn't need to destructure the result here - the results were returned directly ``` -Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/lib/query-types.js) +Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/lib/query-types.js). A second option is the model. If you pass a model the returned data will be instances of that model. ```js // Callee is the model definition. This allows you to easily map a query to a predefined model -sequelize - .query('SELECT * FROM projects', { - model: Projects, - mapToModel: true // pass true here if you have any mapped fields - }) - .then(projects => { - // Each record will now be an instance of Project - }) +const projects = await sequelize.query('SELECT * FROM projects', { + model: Projects, + mapToModel: true // pass true here if you have any mapped fields +}); +// Each element of `projects` is now an instance of Project ``` -See more options in the [query API reference](../class/lib/sequelize.js~Sequelize.html#instance-method-query). Some examples below: +See more options in the [query API reference](../class/lib/sequelize.js~Sequelize.html#instance-method-query). Some examples: ```js -sequelize.query('SELECT 1', { +const { QueryTypes } = require('sequelize'); +await sequelize.query('SELECT 1', { // A function (or false) for logging your queries // Will get called for every SQL query that gets sent // to the server. @@ -52,17 +48,13 @@ sequelize.query('SELECT 1', { raw: false, // The type of query you are executing. The query type affects how results are formatted before they are passed back. - type: Sequelize.QueryTypes.SELECT -}) + type: QueryTypes.SELECT +}); // Note the second argument being null! // Even if we declared a callee here, the raw: true would // supersede and return a raw object. -sequelize - .query('SELECT * FROM projects', { raw: true }) - .then(projects => { - console.log(projects) - }) +console.log(await sequelize.query('SELECT * FROM projects', { raw: true })); ``` ## "Dotted" attributes @@ -70,19 +62,20 @@ sequelize If an attribute name of the table contains dots, the resulting objects will be nested. This is due to the usage of [dottie.js](https://github.com/mickhansen/dottie.js/) under the hood. See below: ```js -sequelize.query('select 1 as `foo.bar.baz`').then(rows => { - console.log(JSON.stringify(rows)) -}) +const rows = await sequelize.query('select 1 as `foo.bar.baz`'); +console.log(JSON.stringify(rows, null, 2)); ``` ```json -[{ - "foo": { - "bar": { - "baz": 1 +[ + { + "foo": { + "bar": { + "baz": 1 + } } } -}] +] ``` ## Replacements @@ -93,37 +86,51 @@ Replacements in a query can be done in two different ways, either using named pa * If an object is passed, `:key` will be replaced with the keys from that object. If the object contains keys not found in the query or vice versa, an exception will be thrown. ```js -sequelize.query('SELECT * FROM projects WHERE status = ?', - { replacements: ['active'], type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) - -sequelize.query('SELECT * FROM projects WHERE status = :status ', - { replacements: { status: 'active' }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT * FROM projects WHERE status = ?', + { + replacements: ['active'], + type: QueryTypes.SELECT + } +); + +await sequelize.query( + 'SELECT * FROM projects WHERE status = :status', + { + replacements: { status: 'active' }, + type: QueryTypes.SELECT + } +); ``` Array replacements will automatically be handled, the following query searches for projects where the status matches an array of values. ```js -sequelize.query('SELECT * FROM projects WHERE status IN(:status) ', - { replacements: { status: ['active', 'inactive'] }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT * FROM projects WHERE status IN(:status)', + { + replacements: { status: ['active', 'inactive'] }, + type: QueryTypes.SELECT + } +); ``` -To use the wildcard operator %, append it to your replacement. The following query matches users with names that start with 'ben'. +To use the wildcard operator `%`, append it to your replacement. The following query matches users with names that start with 'ben'. ```js -sequelize.query('SELECT * FROM users WHERE name LIKE :search_name ', - { replacements: { search_name: 'ben%' }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT * FROM users WHERE name LIKE :search_name', + { + replacements: { search_name: 'ben%' }, + type: QueryTypes.SELECT + } +); ``` ## Bind Parameter @@ -139,15 +146,21 @@ The array or object must contain all bound values or Sequelize will throw an exc The database may add further restrictions to this. Bind parameters cannot be SQL keywords, nor table or column names. They are also ignored in quoted text or data. In PostgreSQL it may also be needed to typecast them, if the type cannot be inferred from the context `$1::varchar`. ```js -sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1', - { bind: ['active'], type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) - -sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status', - { bind: { status: 'active' }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1', + { + bind: ['active'], + type: QueryTypes.SELECT + } +); + +await sequelize.query( + 'SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status', + { + bind: { status: 'active' }, + type: QueryTypes.SELECT + } +); ``` diff --git a/docs/manual/core-concepts/validations-and-constraints.md b/docs/manual/core-concepts/validations-and-constraints.md new file mode 100644 index 000000000000..cdc4139c9640 --- /dev/null +++ b/docs/manual/core-concepts/validations-and-constraints.md @@ -0,0 +1,270 @@ +# Validations & Constraints + +In this tutorial you will learn how to setup validations and constraints for your models in Sequelize. + +For this tutorial, the following setup will be assumed: + +```js +const { Sequelize, Op, Model, DataTypes } = require("sequelize"); +const sequelize = new Sequelize("sqlite::memory:"); + +const User = sequelize.define("user", { + username: { + type: DataTypes.TEXT, + allowNull: false, + unique: true + }, + hashedPassword: { + type: DataTypes.STRING(64), + is: /^[0-9a-f]{64}$/i + } +}); + +(async () => { + await sequelize.sync({ force: true }); + // Code here +})(); +``` + +## Difference between Validations and Constraints + +Validations are checks performed in the Sequelize level, in pure JavaScript. They can be arbitrarily complex if you provide a custom validator function, or can be one of the built-in validators offered by Sequelize. If a validation fails, no SQL query will be sent to the database at all. + +On the other hand, constraints are rules defined at SQL level. The most basic example of constraint is an Unique Constraint. If a constraint check fails, an error will be thrown by the database and Sequelize will forward this error to JavaScript (in this example, throwing a `SequelizeUniqueConstraintError`). Note that in this case, the SQL query was performed, unlike the case for validations. + +## Unique Constraint + +Our code example above defines a unique constraint on the `username` field: + +```js +/* ... */ { + username: { + type: DataTypes.TEXT, + allowNull: false, + unique: true + }, +} /* ... */ +``` + +When this model is synchronized (by calling `sequelize.sync` for example), the `username` field will be created in the table as `` `name` TEXT UNIQUE``, and an attempt to insert an username that already exists there will throw a `SequelizeUniqueConstraintError`. + +## Allowing/disallowing null values + +By default, `null` is an allowed value for every column of a model. This can be disabled setting the `allowNull: false` option for a column, as it was done in the `username` field from our code example: + +```js +/* ... */ { + username: { + type: DataTypes.TEXT, + allowNull: false, + unique: true + }, +} /* ... */ +``` + +Without `allowNull: false`, the call `User.create({})` would work. + +### Note about `allowNull` implementation + +The `allowNull` check is the only check in Sequelize that is a mix of a *validation* and a *constraint* in the senses described at the beginning of this tutorial. This is because: + +* If an attempt is made to set `null` to a field that does not allow null, a `ValidationError` will be thrown *without any SQL query being performed*. +* In addition, after `sequelize.sync`, the column that has `allowNull: false` will be defined with a `NOT NULL` SQL constraint. This way, direct SQL queries that attempt to set the value to `null` will also fail. + +## Validators + +Model validators allow you to specify format/content/inheritance validations for each attribute of the model. Validations are automatically run on `create`, `update` and `save`. You can also call `validate()` to manually validate an instance. + +### Per-attribute validations + +You can define your custom validators or use several built-in validators, implemented by [validator.js (10.11.0)](https://github.com/chriso/validator.js), as shown below. + +```js +sequelize.define('foo', { + bar: { + type: DataTypes.STRING, + validate: { + is: /^[a-z]+$/i, // matches this RegExp + is: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string + not: /^[a-z]+$/i, // does not match this RegExp + not: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string + isEmail: true, // checks for email format (foo@bar.com) + isUrl: true, // checks for url format (http://foo.com) + isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format + isIPv4: true, // checks for IPv4 (129.89.23.1) + isIPv6: true, // checks for IPv6 format + isAlpha: true, // will only allow letters + isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail + isNumeric: true, // will only allow numbers + isInt: true, // checks for valid integers + isFloat: true, // checks for valid floating point numbers + isDecimal: true, // checks for any numbers + isLowercase: true, // checks for lowercase + isUppercase: true, // checks for uppercase + notNull: true, // won't allow null + isNull: true, // only allows null + notEmpty: true, // don't allow empty strings + equals: 'specific value', // only allow a specific value + contains: 'foo', // force specific substrings + notIn: [['foo', 'bar']], // check the value is not one of these + isIn: [['foo', 'bar']], // check the value is one of these + notContains: 'bar', // don't allow specific substrings + len: [2,10], // only allow values with length between 2 and 10 + isUUID: 4, // only allow uuids + isDate: true, // only allow date strings + isAfter: "2011-11-05", // only allow date strings after a specific date + isBefore: "2011-11-05", // only allow date strings before a specific date + max: 23, // only allow values <= 23 + min: 23, // only allow values >= 23 + isCreditCard: true, // check for valid credit card numbers + + // Examples of custom validators: + isEven(value) { + if (parseInt(value) % 2 !== 0) { + throw new Error('Only even values are allowed!'); + } + } + isGreaterThanOtherField(value) { + if (parseInt(value) <= parseInt(this.otherField)) { + throw new Error('Bar must be greater than otherField.'); + } + } + } + } +}); +``` + +Note that where multiple arguments need to be passed to the built-in validation functions, the arguments to be passed must be in an array. But if a single array argument is to be passed, for instance an array of acceptable strings for `isIn`, this will be interpreted as multiple string arguments instead of one array argument. To work around this pass a single-length array of arguments, such as `[['foo', 'bar']]` as shown above. + +To use a custom error message instead of that provided by [validator.js](https://github.com/chriso/validator.js), use an object instead of the plain value or array of arguments, for example a validator which needs no argument can be given a custom message with + +```js +isInt: { + msg: "Must be an integer number of pennies" +} +``` + +or if arguments need to also be passed add an `args` property: + +```js +isIn: { + args: [['en', 'zh']], + msg: "Must be English or Chinese" +} +``` + +When using custom validator functions the error message will be whatever message the thrown `Error` object holds. + +See [the validator.js project](https://github.com/chriso/validator.js) for more details on the built in validation methods. + +**Hint:** You can also define a custom function for the logging part. Just pass a function. The first parameter will be the string that is logged. + +### `allowNull` interaction with other validators + +If a particular field of a model is set to not allow null (with `allowNull: false`) and that value has been set to `null`, all validators will be skipped and a `ValidationError` will be thrown. + +On the other hand, if it is set to allow null (with `allowNull: true`) and that value has been set to `null`, only the built-in validators will be skipped, while the custom validators will still run. + +This means you can, for instance, have a string field which validates its length to be between 5 and 10 characters, but which also allows `null` (since the length validator will be skipped automatically when the value is `null`): + +```js +class User extends Model {} +User.init({ + username: { + type: DataTypes.STRING, + allowNull: true, + validate: { + len: [5, 10] + } + } +}, { sequelize }); +``` + +You also can conditionally allow `null` values, with a custom validator, since it won't be skipped: + +```js +class User extends Model {} +User.init({ + age: Sequelize.INTEGER, + name: { + type: DataTypes.STRING, + allowNull: true, + validate: { + customValidator(value) { + if (value === null && this.age !== 10) { + throw new Error("name can't be null unless age is 10"); + } + }) + } + } +}, { sequelize }); +``` + +You can customize `allowNull` error message by setting the `notNull` validator: + +```js +class User extends Model {} +User.init({ + name: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: 'Please enter your name' + } + } + } +}, { sequelize }); +``` + +### Model-wide validations + +Validations can also be defined to check the model after the field-specific validators. Using this you could, for example, ensure either neither of `latitude` and `longitude` are set or both, and fail if one but not the other is set. + +Model validator methods are called with the model object's context and are deemed to fail if they throw an error, otherwise pass. This is just the same as with custom field-specific validators. + +Any error messages collected are put in the validation result object alongside the field validation errors, with keys named after the failed validation method's key in the `validate` option object. Even though there can only be one error message for each model validation method at any one time, it is presented as a single string error in an array, to maximize consistency with the field errors. + +An example: + +```js +class Place extends Model {} +Place.init({ + name: Sequelize.STRING, + address: Sequelize.STRING, + latitude: { + type: DataTypes.INTEGER, + validate: { + min: -90, + max: 90 + } + }, + longitude: { + type: DataTypes.INTEGER, + validate: { + min: -180, + max: 180 + } + }, +}, { + sequelize, + validate: { + bothCoordsOrNone() { + if ((this.latitude === null) !== (this.longitude === null)) { + throw new Error('Either both latitude and longitude, or neither!'); + } + } + } +}) +``` + +In this simple case an object fails validation if either latitude or longitude is given, but not both. If we try to build one with an out-of-range latitude and no longitude, `somePlace.validate()` might return: + +```js +{ + 'latitude': ['Invalid number: latitude'], + 'bothCoordsOrNone': ['Either both latitude and longitude, or neither!'] +} +``` + +Such validation could have also been done with a custom validator defined on a single attribute (such as the `latitude` attribute, by checking `(value === null) !== (this.longitude === null)`), but the model-wide validation approach is cleaner. diff --git a/docs/manual/data-types.md b/docs/manual/data-types.md deleted file mode 100644 index 0ce73b7d14da..000000000000 --- a/docs/manual/data-types.md +++ /dev/null @@ -1,330 +0,0 @@ -# Datatypes - -Below are some of the datatypes supported by sequelize. For a full and updated list, see [DataTypes](/master/variable/index.html#static-variable-DataTypes). - -```js -Sequelize.STRING // VARCHAR(255) -Sequelize.STRING(1234) // VARCHAR(1234) -Sequelize.STRING.BINARY // VARCHAR BINARY -Sequelize.TEXT // TEXT -Sequelize.TEXT('tiny') // TINYTEXT -Sequelize.CITEXT // CITEXT PostgreSQL and SQLite only. - -Sequelize.INTEGER // INTEGER -Sequelize.BIGINT // BIGINT -Sequelize.BIGINT(11) // BIGINT(11) - -Sequelize.FLOAT // FLOAT -Sequelize.FLOAT(11) // FLOAT(11) -Sequelize.FLOAT(11, 10) // FLOAT(11,10) - -Sequelize.REAL // REAL PostgreSQL only. -Sequelize.REAL(11) // REAL(11) PostgreSQL only. -Sequelize.REAL(11, 12) // REAL(11,12) PostgreSQL only. - -Sequelize.DOUBLE // DOUBLE -Sequelize.DOUBLE(11) // DOUBLE(11) -Sequelize.DOUBLE(11, 10) // DOUBLE(11,10) - -Sequelize.DECIMAL // DECIMAL -Sequelize.DECIMAL(10, 2) // DECIMAL(10,2) - -Sequelize.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres -Sequelize.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision -Sequelize.DATEONLY // DATE without time. -Sequelize.BOOLEAN // TINYINT(1) - -Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2' -Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only. -Sequelize.ARRAY(Sequelize.ENUM) // Defines an array of ENUM. PostgreSQL only. - -Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only. -Sequelize.JSONB // JSONB column. PostgreSQL only. - -Sequelize.BLOB // BLOB (bytea for PostgreSQL) -Sequelize.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL. Other options are medium and long) - -Sequelize.UUID // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically) - -Sequelize.CIDR // CIDR datatype for PostgreSQL -Sequelize.INET // INET datatype for PostgreSQL -Sequelize.MACADDR // MACADDR datatype for PostgreSQL - -Sequelize.RANGE(Sequelize.INTEGER) // Defines int4range range. PostgreSQL only. -Sequelize.RANGE(Sequelize.BIGINT) // Defined int8range range. PostgreSQL only. -Sequelize.RANGE(Sequelize.DATE) // Defines tstzrange range. PostgreSQL only. -Sequelize.RANGE(Sequelize.DATEONLY) // Defines daterange range. PostgreSQL only. -Sequelize.RANGE(Sequelize.DECIMAL) // Defines numrange range. PostgreSQL only. - -Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only. - -Sequelize.GEOMETRY // Spatial column. PostgreSQL (with PostGIS) or MySQL only. -Sequelize.GEOMETRY('POINT') // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only. -Sequelize.GEOMETRY('POINT', 4326) // Spatial column with geometry type and SRID. PostgreSQL (with PostGIS) or MySQL only. -``` - -The BLOB datatype allows you to insert data both as strings and as buffers. When you do a find or findAll on a model which has a BLOB column, that data will always be returned as a buffer. - -If you are working with the PostgreSQL TIMESTAMP WITHOUT TIME ZONE and you need to parse it to a different timezone, please use the pg library's own parser: - -```js -require('pg').types.setTypeParser(1114, stringValue => { - return new Date(stringValue + '+0000'); - // e.g., UTC offset. Use any offset that you would like. -}); -``` - -In addition to the type mentioned above, integer, bigint, float and double also support unsigned and zerofill properties, which can be combined in any order: -Be aware that this does not apply for PostgreSQL! - -```js -Sequelize.INTEGER.UNSIGNED // INTEGER UNSIGNED -Sequelize.INTEGER(11).UNSIGNED // INTEGER(11) UNSIGNED -Sequelize.INTEGER(11).ZEROFILL // INTEGER(11) ZEROFILL -Sequelize.INTEGER(11).ZEROFILL.UNSIGNED // INTEGER(11) UNSIGNED ZEROFILL -Sequelize.INTEGER(11).UNSIGNED.ZEROFILL // INTEGER(11) UNSIGNED ZEROFILL -``` - -_The examples above only show integer, but the same can be done with bigint and float_ - -Usage in object notation: - -```js -// for enums: -class MyModel extends Model {} -MyModel.init({ - states: { - type: Sequelize.ENUM, - values: ['active', 'pending', 'deleted'] - } -}, { sequelize }) -``` - -### Array(ENUM) - -Its only supported with PostgreSQL. - -Array(Enum) type require special treatment. Whenever Sequelize will talk to database it has to typecast Array values with ENUM name. - -So this enum name must follow this pattern `enum__`. If you are using `sync` then correct name will automatically be generated. - -### Range types - -Since range types have extra information for their bound inclusion/exclusion it's not -very straightforward to just use a tuple to represent them in javascript. - -When supplying ranges as values you can choose from the following APIs: - -```js -// defaults to '["2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' -// inclusive lower bound, exclusive upper bound -Timeline.create({ range: [new Date(Date.UTC(2016, 0, 1)), new Date(Date.UTC(2016, 1, 1))] }); - -// control inclusion -const range = [ - { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, - { value: new Date(Date.UTC(2016, 1, 1)), inclusive: true }, -]; -// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]' - -// composite form -const range = [ - { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, - new Date(Date.UTC(2016, 1, 1)), -]; -// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' - -Timeline.create({ range }); -``` - -However, please note that whenever you get back a value that is range you will -receive: - -```js -// stored value: ("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"] -range // [{ value: Date, inclusive: false }, { value: Date, inclusive: true }] -``` - -You will need to call reload after updating an instance with a range type or use `returning: true` option. - -#### Special Cases - -```js -// empty range: -Timeline.create({ range: [] }); // range = 'empty' - -// Unbounded range: -Timeline.create({ range: [null, null] }); // range = '[,)' -// range = '[,"2016-01-01 00:00:00+00:00")' -Timeline.create({ range: [null, new Date(Date.UTC(2016, 0, 1))] }); - -// Infinite range: -// range = '[-infinity,"2016-01-01 00:00:00+00:00")' -Timeline.create({ range: [-Infinity, new Date(Date.UTC(2016, 0, 1))] }); -``` - -## Extending datatypes - -Most likely the type you are trying to implement is already included in [DataTypes](data-types.html). If a new datatype is not included, this manual will show how to write it yourself. - -Sequelize doesn't create new datatypes in the database. This tutorial explains how to make Sequelize recognize new datatypes and assumes that those new datatypes are already created in the database. - -To extend Sequelize datatypes, do it before any instance is created. This example creates a dummy `NEWTYPE` that replicates the built-in datatype `Sequelize.INTEGER(11).ZEROFILL.UNSIGNED`. - -```js -// myproject/lib/sequelize.js - -const Sequelize = require('Sequelize'); -const sequelizeConfig = require('../config/sequelize') -const sequelizeAdditions = require('./sequelize-additions') - -// Function that adds new datatypes -sequelizeAdditions(Sequelize) - -// In this exmaple a Sequelize instance is created and exported -const sequelize = new Sequelize(sequelizeConfig) - -module.exports = sequelize -``` - -```js -// myproject/lib/sequelize-additions.js - -module.exports = function sequelizeAdditions(Sequelize) { - - DataTypes = Sequelize.DataTypes - - /* - * Create new types - */ - class NEWTYPE extends DataTypes.ABSTRACT { - // Mandatory, complete definition of the new type in the database - toSql() { - return 'INTEGER(11) UNSIGNED ZEROFILL' - } - - // Optional, validator function - validate(value, options) { - return (typeof value === 'number') && (! Number.isNaN(value)) - } - - // Optional, sanitizer - _sanitize(value) { - // Force all numbers to be positive - if (value < 0) { - value = 0 - } - - return Math.round(value) - } - - // Optional, value stringifier before sending to database - _stringify(value) { - return value.toString() - } - - // Optional, parser for values received from the database - static parse(value) { - return Number.parseInt(value) - } - } - - DataTypes.NEWTYPE = NEWTYPE; - - // Mandatory, set key - DataTypes.NEWTYPE.prototype.key = DataTypes.NEWTYPE.key = 'NEWTYPE' - - // Optional, disable escaping after stringifier. Not recommended. - // Warning: disables Sequelize protection against SQL injections - // DataTypes.NEWTYPE.escape = false - - // For convenience - // `classToInvokable` allows you to use the datatype without `new` - Sequelize.NEWTYPE = Sequelize.Utils.classToInvokable(DataTypes.NEWTYPE) - -} -``` - -After creating this new datatype, you need to map this datatype in each database dialect and make some adjustments. - -## PostgreSQL - -Let's say the name of the new datatype is `pg_new_type` in the postgres database. That name has to be mapped to `DataTypes.NEWTYPE`. Additionally, it is required to create a child postgres-specific datatype. - -```js -// myproject/lib/sequelize-additions.js - -module.exports = function sequelizeAdditions(Sequelize) { - - DataTypes = Sequelize.DataTypes - - /* - * Create new types - */ - - ... - - /* - * Map new types - */ - - // Mandatory, map postgres datatype name - DataTypes.NEWTYPE.types.postgres = ['pg_new_type'] - - // Mandatory, create a postgres-specific child datatype with its own parse - // method. The parser will be dynamically mapped to the OID of pg_new_type. - PgTypes = DataTypes.postgres - - PgTypes.NEWTYPE = function NEWTYPE() { - if (!(this instanceof PgTypes.NEWTYPE)) return new PgTypes.NEWTYPE(); - DataTypes.NEWTYPE.apply(this, arguments); - } - inherits(PgTypes.NEWTYPE, DataTypes.NEWTYPE); - - // Mandatory, create, override or reassign a postgres-specific parser - //PgTypes.NEWTYPE.parse = value => value; - PgTypes.NEWTYPE.parse = DataTypes.NEWTYPE.parse; - - // Optional, add or override methods of the postgres-specific datatype - // like toSql, escape, validate, _stringify, _sanitize... - -} -``` - -### Ranges - -After a new range type has been [defined in postgres](https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-DEFINING), it is trivial to add it to Sequelize. - -In this example the name of the postgres range type is `newtype_range` and the name of the underlying postgres datatype is `pg_new_type`. The key of `subtypes` and `castTypes` is the key of the Sequelize datatype `DataTypes.NEWTYPE.key`, in lower case. - -```js -// myproject/lib/sequelize-additions.js - -module.exports = function sequelizeAdditions(Sequelize) { - - DataTypes = Sequelize.DataTypes - - /* - * Create new types - */ - - ... - - /* - * Map new types - */ - - ... - - /* - * Add suport for ranges - */ - - // Add postgresql range, newtype comes from DataType.NEWTYPE.key in lower case - DataTypes.RANGE.types.postgres.subtypes.newtype = 'newtype_range'; - DataTypes.RANGE.types.postgres.castTypes.newtype = 'pg_new_type'; - -} -``` - -The new range can be used in model definitions as `Sequelize.RANGE(Sequelize.NEWTYPE)` or `DataTypes.RANGE(DataTypes.NEWTYPE)`. diff --git a/docs/manual/dialects.md b/docs/manual/dialects.md deleted file mode 100644 index ed1032d4c909..000000000000 --- a/docs/manual/dialects.md +++ /dev/null @@ -1,96 +0,0 @@ -# Dialects - -Sequelize is independent from specific dialects. This means that you'll have to install the respective connector library to your project yourself. - -## MySQL - -In order to get Sequelize working nicely together with MySQL, you'll need to install`mysql2@^1.5.2`or higher. Once that's done you can use it like this: - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'mysql' -}) -``` - -**Note:** You can pass options directly to dialect library by setting the -`dialectOptions` parameter. - -## MariaDB - -Library for MariaDB is `mariadb`. - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'mariadb', - dialectOptions: {connectTimeout: 1000} // mariadb connector option -}) -``` - -or using connection String: - -```js -const sequelize = new Sequelize('mariadb://user:password@example.com:9821/database') -``` - -## SQLite - -For SQLite compatibility you'll need`sqlite3@^4.0.0`. Configure Sequelize like this: - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - // sqlite! now! - dialect: 'sqlite', - - // the storage engine for sqlite - // - default ':memory:' - storage: 'path/to/database.sqlite' -}) -``` - -Or you can use a connection string as well with a path: - -```js -const sequelize = new Sequelize('sqlite:/home/abs/path/dbname.db') -const sequelize = new Sequelize('sqlite:relativePath/dbname.db') -``` - -## PostgreSQL - -For PostgreSQL, two libraries are needed, `pg@^7.0.0` and `pg-hstore`. You'll just need to define the dialect: - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - // gimme postgres, please! - dialect: 'postgres' -}) -``` - -To connect over a unix domain socket, specify the path to the socket directory -in the `host` option. - -The socket path must start with `/`. - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - // gimme postgres, please! - dialect: 'postgres', - host: '/path/to/socket_directory' -}) -``` - -## MSSQL - -The library for MSSQL is`tedious@^6.0.0` You'll just need to define the dialect. -Please note: `tedious@^6.0.0` requires you to nest MSSQL specific options inside an additional `options`-object inside the `dialectOptions`-object. - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'mssql', - dialectOptions: { - options: { - useUTC: false, - dateFirst: 1, - } - } -}) -``` diff --git a/docs/manual/getting-started.md b/docs/manual/getting-started.md deleted file mode 100644 index 39ec39610b08..000000000000 --- a/docs/manual/getting-started.md +++ /dev/null @@ -1,243 +0,0 @@ -# Getting started - -In this tutorial you will learn to make a simple setup of Sequelize to learn the basics. - -## Installing - -Sequelize is available via [npm](https://www.npmjs.com/package/sequelize) (or [yarn](https://yarnpkg.com/package/sequelize)). - -```sh -npm install --save sequelize -``` - -You'll also have to manually install the driver for your database of choice: - -```sh -# One of the following: -$ npm install --save pg pg-hstore # Postgres -$ npm install --save mysql2 -$ npm install --save mariadb -$ npm install --save sqlite3 -$ npm install --save tedious # Microsoft SQL Server -``` - -## Setting up a connection - -To connect to the database, you must create a Sequelize instance. This can be done by either passing the connection parameters separately to the Sequelize constructor or by passing a single connection URI: - -```js -const Sequelize = require('sequelize'); - -// Option 1: Passing parameters separately -const sequelize = new Sequelize('database', 'username', 'password', { - host: 'localhost', - dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ -}); - -// Option 2: Passing a connection URI -const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname'); -``` - -The Sequelize constructor takes a whole slew of options that are documented in the [API Reference for the Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). - -### Note: setting up SQLite - -If you're using SQLite, you should use the following instead: - -```js -const sequelize = new Sequelize({ - dialect: 'sqlite', - storage: 'path/to/database.sqlite' -}); -``` - -### Note: connection pool (production) - -If you're connecting to the database from a single process, you should create only one Sequelize instance. Sequelize will set up a connection pool on initialization. This connection pool can be configured through the constructor's `options` parameter (using `options.pool`), as is shown in the following example: - -```js -const sequelize = new Sequelize(/* ... */, { - // ... - pool: { - max: 5, - min: 0, - acquire: 30000, - idle: 10000 - } -}); -``` - -Learn more in the [API Reference for the Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). If you're connecting to the database from multiple processes, you'll have to create one instance per process, but each instance should have a maximum connection pool size of such that the total maximum size is respected. For example, if you want a max connection pool size of 90 and you have three processes, the Sequelize instance of each process should have a max connection pool size of 30. - -### Note: setting up logging - -Using `options.logging` can be used to define the function that gets executed every time Sequelize would log something. The default value is `console.log` and when using that only the first log parameter of log function call is displayed. For example, for query logging the first parameter is the raw query and the second (hidden by default) is the Sequelize object. - -Common useful values for `options.logging`: - -```js -const sequelize = new Sequelize(/* ... */, { - // Choose one of the logging options - logging: console.log, // Default, displays the first parameter of the log function call - logging: (...msg) => console.log(msg), // Displays all log function call parameters - logging: false, // Disables logging - logging: msg => logger.debug(msg), // Use custom logger (e.g. Winston or Bunyan), displays the first parameter - logging: logger.debug.bind(logger) // Alternative way to use custom logger, displays all messages -}); -``` - -### Testing the connection - -You can use the `.authenticate()` function to test if the connection is OK: - -```js -sequelize - .authenticate() - .then(() => { - console.log('Connection has been established successfully.'); - }) - .catch(err => { - console.error('Unable to connect to the database:', err); - }); -``` - -### Closing the connection - -Sequelize will keep the connection open by default, and use the same connection for all queries. If you need to close the connection, call `sequelize.close()` (which is asynchronous and returns a Promise). - -## Modeling a table - -A model is a class that extends `Sequelize.Model`. Models can be defined in two equivalent ways. The first, with `Sequelize.Model.init(attributes, options)`: - -```js -const Model = Sequelize.Model; -class User extends Model {} -User.init({ - // attributes - firstName: { - type: Sequelize.STRING, - allowNull: false - }, - lastName: { - type: Sequelize.STRING - // allowNull defaults to true - } -}, { - sequelize, - modelName: 'user' - // options -}); -``` - -Alternatively, using `sequelize.define`: - -```js -const User = sequelize.define('user', { - // attributes - firstName: { - type: Sequelize.STRING, - allowNull: false - }, - lastName: { - type: Sequelize.STRING - // allowNull defaults to true - } -}, { - // options -}); -``` - -Internally, `sequelize.define` calls `Model.init`. - -The above code tells Sequelize to expect a table named `users` in the database with the fields `firstName` and `lastName`. The table name is automatically pluralized by default (a library called [inflection](https://www.npmjs.com/package/inflection) is used under the hood to do this). This behavior can be stopped for a specific model by using the `freezeTableName: true` option, or for all models by using the `define` option from the [Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). - -Sequelize also defines by default the fields `id` (primary key), `createdAt` and `updatedAt` to every model. This behavior can also be changed, of course (check the API Reference to learn more about the available options). - -### Changing the default model options - -The Sequelize constructor takes a `define` option which will change the default options for all defined models. - -```js -const sequelize = new Sequelize(connectionURI, { - define: { - // The `timestamps` field specify whether or not the `createdAt` and `updatedAt` fields will be created. - // This was true by default, but now is false by default - timestamps: false - } -}); - -// Here `timestamps` will be false, so the `createdAt` and `updatedAt` fields will not be created. -class Foo extends Model {} -Foo.init({ /* ... */ }, { sequelize }); - -// Here `timestamps` is directly set to true, so the `createdAt` and `updatedAt` fields will be created. -class Bar extends Model {} -Bar.init({ /* ... */ }, { sequelize, timestamps: true }); -``` - -You can read more about creating models in the [Model.init API Reference](../class/lib/model.js~Model.html#static-method-init), or in the [sequelize.define API reference](../class/lib/sequelize.js~Sequelize.html#instance-method-define). - -## Synchronizing the model with the database - -If you want Sequelize to automatically create the table (or modify it as needed) according to your model definition, you can use the `sync` method, as follows: - -```js -// Note: using `force: true` will drop the table if it already exists -User.sync({ force: true }).then(() => { - // Now the `users` table in the database corresponds to the model definition - return User.create({ - firstName: 'John', - lastName: 'Hancock' - }); -}); -``` - -### Synchronizing all models at once - -Instead of calling `sync()` for every model, you can call `sequelize.sync()` which will automatically sync all models. - -### Note for production - -In production, you might want to consider using Migrations instead of calling `sync()` in your code. Learn more in the [Migrations](migrations.html) guide. - -## Querying - -A few simple queries are shown below: - -```js -// Find all users -User.findAll().then(users => { - console.log("All users:", JSON.stringify(users, null, 4)); -}); - -// Create a new user -User.create({ firstName: "Jane", lastName: "Doe" }).then(jane => { - console.log("Jane's auto-generated ID:", jane.id); -}); - -// Delete everyone named "Jane" -User.destroy({ - where: { - firstName: "Jane" - } -}).then(() => { - console.log("Done"); -}); - -// Change everyone without a last name to "Doe" -User.update({ lastName: "Doe" }, { - where: { - lastName: null - } -}).then(() => { - console.log("Done"); -}); -``` - -Sequelize has a lot of options for querying. You will learn more about those in the next tutorials. It is also possible to make raw SQL queries, if you really need them. - -## Promises and async/await - -As shown above by the extensive usage of `.then` calls, Sequelize uses Promises extensively. This means that, if your Node version supports it, you can use ES2017 `async/await` syntax for all asynchronous calls made with Sequelize. - -Also, all Sequelize promises are in fact [Bluebird](http://bluebirdjs.com) promises, so you have the rich Bluebird API to use as well (for example, using `finally`, `tap`, `tapCatch`, `map`, `mapSeries`, etc). You can access the Bluebird constructor used internally by Sequelize with `Sequelize.Promise`, if you want to set any Bluebird specific options. diff --git a/docs/manual/hooks.md b/docs/manual/hooks.md deleted file mode 100644 index 3d26089b07d4..000000000000 --- a/docs/manual/hooks.md +++ /dev/null @@ -1,403 +0,0 @@ -# Hooks - -Hooks (also known as lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a `beforeUpdate` hook. - -**Note:** _You can't use hooks with instances. Hooks are used with models._ - -For a full list of hooks, see [Hooks file](https://github.com/sequelize/sequelize/blob/master/lib/hooks.js#L7). - -## Order of Operations - -```text -(1) - beforeBulkCreate(instances, options) - beforeBulkDestroy(options) - beforeBulkUpdate(options) -(2) - beforeValidate(instance, options) -(-) - validate -(3) - afterValidate(instance, options) - - or - - validationFailed(instance, options, error) -(4) - beforeCreate(instance, options) - beforeDestroy(instance, options) - beforeUpdate(instance, options) - beforeSave(instance, options) - beforeUpsert(values, options) -(-) - create - destroy - update -(5) - afterCreate(instance, options) - afterDestroy(instance, options) - afterUpdate(instance, options) - afterSave(instance, options) - afterUpsert(created, options) -(6) - afterBulkCreate(instances, options) - afterBulkDestroy(options) - afterBulkUpdate(options) -``` - -## Declaring Hooks - -Arguments to hooks are passed by reference. This means, that you can change the values, and this will be reflected in the insert / update statement. A hook may contain async actions - in this case the hook function should return a promise. - -There are currently three ways to programmatically add hooks: - -```js -// Method 1 via the .init() method -class User extends Model {} -User.init({ - username: DataTypes.STRING, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } -}, { - hooks: { - beforeValidate: (user, options) => { - user.mood = 'happy'; - }, - afterValidate: (user, options) => { - user.username = 'Toni'; - } - }, - sequelize -}); - -// Method 2 via the .addHook() method -User.addHook('beforeValidate', (user, options) => { - user.mood = 'happy'; -}); - -User.addHook('afterValidate', 'someCustomName', (user, options) => { - return Promise.reject(new Error("I'm afraid I can't let you do that!")); -}); - -// Method 3 via the direct method -User.beforeCreate((user, options) => { - return hashPassword(user.password).then(hashedPw => { - user.password = hashedPw; - }); -}); - -User.afterValidate('myHookAfter', (user, options) => { - user.username = 'Toni'; -}); -``` - -## Removing hooks - -Only a hook with name param can be removed. - -```js -class Book extends Model {} -Book.init({ - title: DataTypes.STRING -}, { sequelize }); - -Book.addHook('afterCreate', 'notifyUsers', (book, options) => { - // ... -}); - -Book.removeHook('afterCreate', 'notifyUsers'); -``` - -You can have many hooks with same name. Calling `.removeHook()` will remove all of them. - -## Global / universal hooks - -Global hooks are hooks which are run for all models. They can define behaviours that you want for all your models, and are especially useful for plugins. They can be defined in two ways, which have slightly different semantics: - -### Default Hooks (Sequelize.options.define) - -```js -const sequelize = new Sequelize(..., { - define: { - hooks: { - beforeCreate: () => { - // Do stuff - } - } - } -}); -``` - -This adds a default hook to all models, which is run if the model does not define its own `beforeCreate` hook: - -```js -class User extends Model {} -User.init({}, { sequelize }); -class Project extends Model {} -Project.init({}, { - hooks: { - beforeCreate: () => { - // Do other stuff - } - }, - sequelize -}); - -User.create() // Runs the global hook -Project.create() // Runs its own hook (because the global hook is overwritten) -``` - -### Permanent Hooks (Sequelize.addHook) - -```js -sequelize.addHook('beforeCreate', () => { - // Do stuff -}); -``` - -This hook is always run before create, regardless of whether the model specifies its own `beforeCreate` hook. Local hooks are always run before global hooks: - -```js -class User extends Model {} -User.init({}, { sequelize }); -class Project extends Model {} -Project.init({}, { - hooks: { - beforeCreate: () => { - // Do other stuff - } - }, - sequelize -}); - -User.create() // Runs the global hook -Project.create() // Runs its own hook, followed by the global hook -``` - -Permanent hooks may also be defined in `Sequelize.options`: - -```js -new Sequelize(..., { - hooks: { - beforeCreate: () => { - // do stuff - } - } -}); -``` - -### Connection Hooks - -Sequelize provides four hooks that are executed immediately before and after a database connection is obtained or released: - -```text -beforeConnect(config) -afterConnect(connection, config) -beforeDisconnect(connection) -afterDisconnect(connection) -``` - -These hooks can be useful if you need to asynchronously obtain database credentials, or need to directly access the low-level database connection after it has been created. - -For example, we can asynchronously obtain a database password from a rotating token store, and mutate Sequelize's configuration object with the new credentials: - -```js -sequelize.beforeConnect((config) => { - return getAuthToken() - .then((token) => { - config.password = token; - }); - }); -``` - -These hooks may _only_ be declared as a permanent global hook, as the connection pool is shared by all models. - -## Instance hooks - -The following hooks will emit whenever you're editing a single object - -```text -beforeValidate -afterValidate or validationFailed -beforeCreate / beforeUpdate / beforeSave / beforeDestroy -afterCreate / afterUpdate / afterSave / afterDestroy -``` - -```js -// ...define ... -User.beforeCreate(user => { - if (user.accessLevel > 10 && user.username !== "Boss") { - throw new Error("You can't grant this user an access level above 10!") - } -}) -``` - -This example will return an error: - -```js -User.create({username: 'Not a Boss', accessLevel: 20}).catch(err => { - console.log(err); // You can't grant this user an access level above 10! -}); -``` - -The following example would return successful: - -```js -User.create({username: 'Boss', accessLevel: 20}).then(user => { - console.log(user); // user object with username as Boss and accessLevel of 20 -}); -``` - -### Model hooks - -Sometimes you'll be editing more than one record at a time by utilizing the `bulkCreate, update, destroy` methods on the model. The following will emit whenever you're using one of those methods: - -```text -beforeBulkCreate(instances, options) -beforeBulkUpdate(options) -beforeBulkDestroy(options) -afterBulkCreate(instances, options) -afterBulkUpdate(options) -afterBulkDestroy(options) -``` - -If you want to emit hooks for each individual record, along with the bulk hooks you can pass `individualHooks: true` to the call. - -**WARNING**: if you use individual hooks, *all instances that are updated or destroyed will get loaded into memory* before your hooks are called. The number of instances Sequelize can handle with individual hooks is limited by available memory. - -```js -Model.destroy({ where: {accessLevel: 0}, individualHooks: true}); -// Will select all records that are about to be deleted and emit before- + after- Destroy on each instance - -Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: true}); -// Will select all records that are about to be updated and emit before- + after- Update on each instance -``` - -The `options` argument of hook method would be the second argument provided to the corresponding method or its -cloned and extended version. - -```js -Model.beforeBulkCreate((records, {fields}) => { - // records = the first argument sent to .bulkCreate - // fields = one of the second argument fields sent to .bulkCreate -}) - -Model.bulkCreate([ - {username: 'Toni'}, // part of records argument - {username: 'Tobi'} // part of records argument - ], {fields: ['username']} // options parameter -) - -Model.beforeBulkUpdate(({attributes, where}) => { - // where - in one of the fields of the clone of second argument sent to .update - // attributes - is one of the fields that the clone of second argument of .update would be extended with -}) - -Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/) - -Model.beforeBulkDestroy(({where, individualHooks}) => { - // individualHooks - default of overridden value of extended clone of second argument sent to Model.destroy - // where - in one of the fields of the clone of second argument sent to Model.destroy -}) - -Model.destroy({ where: {username: 'Tom'}} /*where argument*/) -``` - -If you use `Model.bulkCreate(...)` with the `updateOnDuplicate` option, changes made in the hook to fields that aren't given in the `updateOnDuplicate` array will not be persisted to the database. However it is possible to change the updateOnDuplicate option inside the hook if this is what you want. - -```js -// Bulk updating existing users with updateOnDuplicate option -Users.bulkCreate([ - { id: 1, isMember: true }, - { id: 2, isMember: false } -], { - updateOnDuplicate: ['isMember'] -}); - -User.beforeBulkCreate((users, options) => { - for (const user of users) { - if (user.isMember) { - user.memberSince = new Date(); - } - } - - // Add memberSince to updateOnDuplicate otherwise the memberSince date wont be - // saved to the database - options.updateOnDuplicate.push('memberSince'); -}); -``` - -## Associations - -For the most part hooks will work the same for instances when being associated except a few things - -1. When using add/set functions the beforeUpdate/afterUpdate hooks will run. -2. When using add functions for belongsToMany relationships that will add record to pivot table, beforeBulkCreate/afterBulkCreate hooks in intermediate model will run. -3. The only way to call beforeDestroy/afterDestroy hooks are on associations with `onDelete: 'cascade'` and the option `hooks: true`. For instance: - -```js -class Projects extends Model {} -Projects.init({ - title: DataTypes.STRING -}, { sequelize }); - -class Tasks extends Model {} -Tasks.init({ - title: DataTypes.STRING -}, { sequelize }); - -Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true }); -Tasks.belongsTo(Projects); -``` - -This code will run beforeDestroy/afterDestroy on the Tasks table. Sequelize, by default, will try to optimize your queries as much as possible. When calling cascade on delete, Sequelize will simply execute a - -```sql -DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey -``` - -However, adding `hooks: true` explicitly tells Sequelize that optimization is not of your concern and will perform a `SELECT` on the associated objects and destroy each instance one by one in order to be able to call the hooks with the right parameters. - -If your association is of type `n:m`, you may be interested in firing hooks on the through model when using the `remove` call. Internally, sequelize is using `Model.destroy` resulting in calling the `bulkDestroy` instead of the `before/afterDestroy` hooks on each through instance. - -This can be simply solved by passing `{individualHooks: true}` to the `remove` call, resulting on each hook to be called on each removed through instance object. - -## A Note About Transactions - -Note that many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction _is_ specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet: - -```js -// Here we use the promise-style of async hooks rather than -// the callback. -User.addHook('afterCreate', (user, options) => { - // 'transaction' will be available in options.transaction - - // This operation will be part of the same transaction as the - // original User.create call. - return User.update({ - mood: 'sad' - }, { - where: { - id: user.id - }, - transaction: options.transaction - }); -}); - - -sequelize.transaction(transaction => { - User.create({ - username: 'someguy', - mood: 'happy', - transaction - }); -}); -``` - -If we had not included the transaction option in our call to `User.update` in the preceding code, no change would have occurred, since our newly created user does not exist in the database until the pending transaction has been committed. - -### Internal Transactions - -It is very important to recognize that sequelize may make use of transactions internally for certain operations such as `Model.findOrCreate`. If your hook functions execute read or write operations that rely on the object's presence in the database, or modify the object's stored values like the example in the preceding section, you should always specify `{ transaction: options.transaction }`. - -If the hook has been called in the process of a transacted operation, this makes sure that your dependent read/write is a part of that same transaction. If the hook is not transacted, you have simply specified `{ transaction: null }` and can expect the default behaviour. diff --git a/docs/manual/instances.md b/docs/manual/instances.md deleted file mode 100644 index d1a442c99d9e..000000000000 --- a/docs/manual/instances.md +++ /dev/null @@ -1,408 +0,0 @@ -# Instances - -## Building a non-persistent instance - -In order to create instances of defined classes just do as follows. You might recognize the syntax if you coded Ruby in the past. Using the `build`-method will return an unsaved object, which you explicitly have to save. - -```js -const project = Project.build({ - title: 'my awesome project', - description: 'woot woot. this will make me a rich man' -}) - -const task = Task.build({ - title: 'specify the project idea', - description: 'bla', - deadline: new Date() -}) -``` - -Built instances will automatically get default values when they were defined: - -```js -// first define the model -class Task extends Model {} -Task.init({ - title: Sequelize.STRING, - rating: { type: Sequelize.TINYINT, defaultValue: 3 } -}, { sequelize, modelName: 'task' }); - -// now instantiate an object -const task = Task.build({title: 'very important task'}) - -task.title // ==> 'very important task' -task.rating // ==> 3 -``` - -To get it stored in the database, use the `save`-method and catch the events ... if needed: - -```js -project.save().then(() => { - // my nice callback stuff -}) - -task.save().catch(error => { - // mhhh, wth! -}) - -// you can also build, save and access the object with chaining: -Task - .build({ title: 'foo', description: 'bar', deadline: new Date() }) - .save() - .then(anotherTask => { - // you can now access the currently saved task with the variable anotherTask... nice! - }) - .catch(error => { - // Ooops, do some error-handling - }) -``` - -## Creating persistent instances - -While an instance created with `.build()` requires an explicit `.save()` call to be stored in the database, `.create()` omits that requirement altogether and automatically stores your instance's data once called. - -```js -Task.create({ title: 'foo', description: 'bar', deadline: new Date() }).then(task => { - // you can now access the newly created task via the variable task -}) -``` - -It is also possible to define which attributes can be set via the create method. This can be especially very handy if you create database entries based on a form which can be filled by a user. Using that would for example allow you to restrict the `User` model to set only a username and an address but not an admin flag: - -```js -User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] }).then(user => { - // let's assume the default of isAdmin is false: - console.log(user.get({ - plain: true - })) // => { username: 'barfooz', isAdmin: false } -}) -``` - -## Updating / Saving / Persisting an instance - -Now lets change some values and save changes to the database... There are two ways to do that: - -```js -// way 1 -task.title = 'a very different title now' -task.save().then(() => {}) - -// way 2 -task.update({ - title: 'a very different title now' -}).then(() => {}) -``` - -It's also possible to define which attributes should be saved when calling `save`, by passing an array of column names. This is useful when you set attributes based on a previously defined object. E.g. if you get the values of an object via a form of a web app. Furthermore this is used internally for `update`. This is how it looks like: - -```js -task.title = 'foooo' -task.description = 'baaaaaar' -task.save({fields: ['title']}).then(() => { - // title will now be 'foooo' but description is the very same as before -}) - -// The equivalent call using update looks like this: -task.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']}).then(() => { - // title will now be 'foooo' but description is the very same as before -}) -``` - -When you call `save` without changing any attribute, this method will execute nothing; - -## Destroying / Deleting persistent instances - -Once you created an object and got a reference to it, you can delete it from the database. The relevant method is `destroy`: - -```js -Task.create({ title: 'a task' }).then(task => { - // now you see me... - return task.destroy(); -}).then(() => { - // now i'm gone :) -}) -``` - -If the `paranoid` options is true, the object will not be deleted, instead the `deletedAt` column will be set to the current timestamp. To force the deletion, you can pass `force: true` to the destroy call: - -```js -task.destroy({ force: true }) -``` - -After an object is soft deleted in `paranoid` mode, you will not be able to create a new instance with the same primary key -until you have force-deleted the old instance. - -## Restoring soft-deleted instances - -If you have soft-deleted an instance of a model with `paranoid: true`, and would like to undo the deletion, use the `restore` method: - -```js -Task.create({ title: 'a task' }).then(task => { - // now you see me... - return task.destroy(); -}).then((task) => { -  // now i'm gone, but wait... - return task.restore(); -}) -``` - -## Working in bulk (creating, updating and destroying multiple rows at once) - -In addition to updating a single instance, you can also create, update, and delete multiple instances at once. The functions you are looking for are called - -* `Model.bulkCreate` -* `Model.update` -* `Model.destroy` - -Since you are working with multiple models, the callbacks will not return DAO instances. BulkCreate will return an array of model instances/DAOs, they will however, unlike `create`, not have the resulting values of autoIncrement attributes.`update` and `destroy` will return the number of affected rows. - -First lets look at bulkCreate - -```js -User.bulkCreate([ - { username: 'barfooz', isAdmin: true }, - { username: 'foo', isAdmin: true }, - { username: 'bar', isAdmin: false } -]).then(() => { // Notice: There are no arguments here, as of right now you'll have to... - return User.findAll(); -}).then(users => { - console.log(users) // ... in order to get the array of user objects -}) -``` - -Insert several rows and return all columns (Postgres only): - -```js -User.bulkCreate([ - { username: 'barfooz', isAdmin: true }, - { username: 'foo', isAdmin: true }, - { username: 'bar', isAdmin: false } -], { returning: true }) // will return all columns for each row inserted -.then((result) => { - console.log(result); -}); -``` - -Insert several rows and return specific columns (Postgres only): - -```js -User.bulkCreate([ - { username: 'barfooz', isAdmin: true }, - { username: 'foo', isAdmin: true }, - { username: 'bar', isAdmin: false } -], { returning: ['username'] }) // will return only the specified columns for each row inserted -.then((result) => { - console.log(result); -}); -``` - -To update several rows at once: - -```js -Task.bulkCreate([ - {subject: 'programming', status: 'executing'}, - {subject: 'reading', status: 'executing'}, - {subject: 'programming', status: 'finished'} -]).then(() => { - return Task.update( - { status: 'inactive' }, /* set attributes' value */ - { where: { subject: 'programming' }} /* where criteria */ - ); -}).then(([affectedCount, affectedRows]) => { - // Notice that affectedRows will only be defined in dialects which support returning: true - - // affectedCount will be 2 - return Task.findAll(); -}).then(tasks => { - console.log(tasks) // the 'programming' tasks will both have a status of 'inactive' -}) -``` - -And delete them: - -```js -Task.bulkCreate([ - {subject: 'programming', status: 'executing'}, - {subject: 'reading', status: 'executing'}, - {subject: 'programming', status: 'finished'} -]).then(() => { - return Task.destroy({ - where: { - subject: 'programming' - }, - truncate: true /* this will ignore where and truncate the table instead */ - }); -}).then(affectedRows => { - // affectedRows will be 2 - return Task.findAll(); -}).then(tasks => { - console.log(tasks) // no programming, just reading :( -}) -``` - -If you are accepting values directly from the user, it might be beneficial to limit the columns that you want to actually insert.`bulkCreate()`accepts an options object as the second parameter. The object can have a `fields` parameter, (an array) to let it know which fields you want to build explicitly - -```js -User.bulkCreate([ - { username: 'foo' }, - { username: 'bar', admin: true} -], { fields: ['username'] }).then(() => { - // nope bar, you can't be admin! -}) -``` - -`bulkCreate` was originally made to be a mainstream/fast way of inserting records, however, sometimes you want the luxury of being able to insert multiple rows at once without sacrificing model validations even when you explicitly tell Sequelize which columns to sift through. You can do by adding a `validate: true` property to the options object. - -```js -class Tasks extends Model {} -Tasks.init({ - name: { - type: Sequelize.STRING, - validate: { - notNull: { args: true, msg: 'name cannot be null' } - } - }, - code: { - type: Sequelize.STRING, - validate: { - len: [3, 10] - } - } -}, { sequelize, modelName: 'tasks' }) - -Tasks.bulkCreate([ - {name: 'foo', code: '123'}, - {code: '1234'}, - {name: 'bar', code: '1'} -], { validate: true }).catch(errors => { - /* console.log(errors) would look like: - [ - { record: - ... - name: 'SequelizeBulkRecordError', - message: 'Validation error', - errors: - { name: 'SequelizeValidationError', - message: 'Validation error', - errors: [Object] } }, - { record: - ... - name: 'SequelizeBulkRecordError', - message: 'Validation error', - errors: - { name: 'SequelizeValidationError', - message: 'Validation error', - errors: [Object] } } - ] - */ -}) -``` - -## Values of an instance - -If you log an instance you will notice, that there is a lot of additional stuff. In order to hide such stuff and reduce it to the very interesting information, you can use the`get`-attribute. Calling it with the option `plain` = true will only return the values of an instance. - -```js -Person.create({ - name: 'Rambow', - firstname: 'John' -}).then(john => { - console.log(john.get({ - plain: true - })) -}) - -// result: - -// { name: 'Rambow', -// firstname: 'John', -// id: 1, -// createdAt: Tue, 01 May 2012 19:12:16 GMT, -// updatedAt: Tue, 01 May 2012 19:12:16 GMT -// } -``` - -**Hint:**You can also transform an instance into JSON by using `JSON.stringify(instance)`. This will basically return the very same as `values`. - -## Reloading instances - -If you need to get your instance in sync, you can use the method`reload`. It will fetch the current data from the database and overwrite the attributes of the model on which the method has been called on. - -```js -Person.findOne({ where: { name: 'john' } }).then(person => { - person.name = 'jane' - console.log(person.name) // 'jane' - - person.reload().then(() => { - console.log(person.name) // 'john' - }) -}) -``` - -## Incrementing - -In order to increment values of an instance without running into concurrency issues, you may use `increment`. - -First of all you can define a field and the value you want to add to it. - -```js -User.findByPk(1).then(user => { - return user.increment('my-integer-field', {by: 2}) -}).then(user => { - // Postgres will return the updated user by default (unless disabled by setting { returning: false }) - // In other dialects, you'll want to call user.reload() to get the updated instance... -}) -``` - -Second, you can define multiple fields and the value you want to add to them. - -```js -User.findByPk(1).then(user => { - return user.increment([ 'my-integer-field', 'my-very-other-field' ], {by: 2}) -}).then(/* ... */) -``` - -Third, you can define an object containing fields and its increment values. - -```js -User.findByPk(1).then(user => { - return user.increment({ - 'my-integer-field': 2, - 'my-very-other-field': 3 - }) -}).then(/* ... */) -``` - -## Decrementing - -In order to decrement values of an instance without running into concurrency issues, you may use `decrement`. - -First of all you can define a field and the value you want to add to it. - -```js -User.findByPk(1).then(user => { - return user.decrement('my-integer-field', {by: 2}) -}).then(user => { - // Postgres will return the updated user by default (unless disabled by setting { returning: false }) - // In other dialects, you'll want to call user.reload() to get the updated instance... -}) -``` - -Second, you can define multiple fields and the value you want to add to them. - -```js -User.findByPk(1).then(user => { - return user.decrement([ 'my-integer-field', 'my-very-other-field' ], {by: 2}) -}).then(/* ... */) -``` - -Third, you can define an object containing fields and its decrement values. - -```js -User.findByPk(1).then(user => { - return user.decrement({ - 'my-integer-field': 2, - 'my-very-other-field': 3 - }) -}).then(/* ... */) -``` diff --git a/docs/manual/migrations.md b/docs/manual/migrations.md deleted file mode 100644 index be4f0447fe07..000000000000 --- a/docs/manual/migrations.md +++ /dev/null @@ -1,662 +0,0 @@ -# Migrations - -Just like you use Git / SVN to manage changes in your source code, you can use migrations to keep track of changes to the database. With migrations you can transfer your existing database into another state and vice versa: Those state transitions are saved in migration files, which describe how to get to the new state and how to revert the changes in order to get back to the old state. - -You will need [Sequelize CLI][0]. The CLI ships support for migrations and project bootstrapping. - -## The CLI - -### Installing CLI - -Let's start with installing CLI, you can find instructions [here][0]. Most preferred way is installing locally like this - -```bash -$ npm install --save sequelize-cli -``` - -### Bootstrapping - -To create an empty project you will need to execute `init` command - -```bash -$ npx sequelize-cli init -``` - -This will create following folders - -- `config`, contains config file, which tells CLI how to connect with database -- `models`, contains all models for your project -- `migrations`, contains all migration files -- `seeders`, contains all seed files - -#### Configuration - -Before continuing further we will need to tell CLI how to connect to database. To do that let's open default config file `config/config.json`. It looks something like this - -```json -{ - "development": { - "username": "root", - "password": null, - "database": "database_development", - "host": "127.0.0.1", - "dialect": "mysql" - }, - "test": { - "username": "root", - "password": null, - "database": "database_test", - "host": "127.0.0.1", - "dialect": "mysql" - }, - "production": { - "username": "root", - "password": null, - "database": "database_production", - "host": "127.0.0.1", - "dialect": "mysql" - } -} -``` - -Now edit this file and set correct database credentials and dialect. The keys of the objects(ex. "development") are used on `model/index.js` for matching `process.env.NODE_ENV` (When undefined, "development" is a default value.). - -**Note:** _If your database doesn't exists yet, you can just call `db:create` command. With proper access it will create that database for you._ - -### Creating first Model (and Migration) - -Once you have properly configured CLI config file you are ready to create your first migration. It's as simple as executing a simple command. - -We will use `model:generate` command. This command requires two options - -- `name`, Name of the model -- `attributes`, List of model attributes - -Let's create a model named `User`. - -```bash -$ npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string -``` - -This will do following - -- Create a model file `user` in `models` folder -- Create a migration file with name like `XXXXXXXXXXXXXX-create-user.js` in `migrations` folder - -**Note:** _Sequelize will only use Model files, it's the table representation. On the other hand, the migration file is a change in that model or more specifically that table, used by CLI. Treat migrations like a commit or a log for some change in database._ - -### Running Migrations - -Until this step, we haven't inserted anything into the database. We have just created required model and migration files for our first model `User`. Now to actually create that table in database you need to run `db:migrate` command. - -```bash -$ npx sequelize-cli db:migrate -``` - -This command will execute these steps: - -- Will ensure a table called `SequelizeMeta` in database. This table is used to record which migrations have run on the current database -- Start looking for any migration files which haven't run yet. This is possible by checking `SequelizeMeta` table. In this case it will run `XXXXXXXXXXXXXX-create-user.js` migration, which we created in last step. -- Creates a table called `Users` with all columns as specified in its migration file. - -### Undoing Migrations - -Now our table has been created and saved in database. With migration you can revert to old state by just running a command. - -You can use `db:migrate:undo`, this command will revert most recent migration. - -```bash -$ npx sequelize-cli db:migrate:undo -``` - -You can revert back to initial state by undoing all migrations with `db:migrate:undo:all` command. You can also revert back to a specific migration by passing its name in `--to` option. - -```bash -$ npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js -``` - -### Creating First Seed - -Suppose we want to insert some data into a few tables by default. If we follow up on previous example we can consider creating a demo user for `User` table. - -To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database table with sample data or test data. - -Let's create a seed file which will add a demo user to our `User` table. - -```bash -$ npx sequelize-cli seed:generate --name demo-user -``` - -This command will create a seed file in `seeders` folder. File name will look something like `XXXXXXXXXXXXXX-demo-user.js`. It follows the same `up / down` semantics as the migration files. - -Now we should edit this file to insert demo user to `User` table. - -```js -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.bulkInsert('Users', [{ - firstName: 'John', - lastName: 'Doe', - email: 'demo@demo.com', - createdAt: new Date(), - updatedAt: new Date() - }], {}); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.bulkDelete('Users', null, {}); - } -}; - -``` - -### Running Seeds - -In last step you have create a seed file. It's still not committed to database. To do that we need to run a simple command. - -```bash -$ npx sequelize-cli db:seed:all -``` - -This will execute that seed file and you will have a demo user inserted into `User` table. - -**Note:** _Seeders execution is not stored anywhere unlike migrations, which use the `SequelizeMeta` table. If you wish to override this please read `Storage` section_ - -### Undoing Seeds - -Seeders can be undone if they are using any storage. There are two commands available for that: - -If you wish to undo most recent seed - -```bash -$ npx sequelize-cli db:seed:undo -``` - -If you wish to undo a specific seed - -```bash -$ npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data -``` - -If you wish to undo all seeds - -```bash -$ npx sequelize-cli db:seed:undo:all -``` - -## Advance Topics - -### Migration Skeleton - -The following skeleton shows a typical migration file. - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - // logic for transforming into the new state - }, - - down: (queryInterface, Sequelize) => { - // logic for reverting the changes - } -} -``` - -We can generate this file using `migration:generate`. This will create `xxx-migration-skeleton.js` in your migration folder. - -```bash -$ npx sequelize-cli migration:generate --name migration-skeleton -``` - -The passed `queryInterface` object can be used to modify the database. The `Sequelize` object stores the available data types such as `STRING` or `INTEGER`. Function `up` or `down` should return a `Promise`. Let's look at an example: - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('Person', { - name: Sequelize.STRING, - isBetaMember: { - type: Sequelize.BOOLEAN, - defaultValue: false, - allowNull: false - } - }); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Person'); - } -} -``` - -The following is an example of a migration that performs two changes in the database, using a transaction to ensure that all instructions are successfully executed or rolled back in case of failure: - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.addColumn('Person', 'petName', { - type: Sequelize.STRING - }, { transaction: t }), - queryInterface.addColumn('Person', 'favoriteColor', { - type: Sequelize.STRING, - }, { transaction: t }) - ]) - }) - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.removeColumn('Person', 'petName', { transaction: t }), - queryInterface.removeColumn('Person', 'favoriteColor', { transaction: t }) - ]) - }) - } -}; -``` - -The next example is of a migration that has a foreign key. You can use references to specify a foreign key: - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('Person', { - name: Sequelize.STRING, - isBetaMember: { - type: Sequelize.BOOLEAN, - defaultValue: false, - allowNull: false - }, - userId: { - type: Sequelize.INTEGER, - references: { - model: { - tableName: 'users', - schema: 'schema' - }, - key: 'id' - }, - allowNull: false - }, - }); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Person'); - } -} - -``` - -The next example is of a migration that uses async/await where you create an unique index on a new column: - -```js -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.addColumn( - 'Person', - 'petName', - { - type: Sequelize.STRING, - }, - { transaction } - ); - await queryInterface.addIndex( - 'Person', - 'petName', - { - fields: 'petName', - unique: true, - transaction, - } - ); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.removeColumn('Person', 'petName', { transaction }); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; -``` - -### The `.sequelizerc` File - -This is a special configuration file. It lets you specify following options that you would usually pass as arguments to CLI: - -- `env`: The environment to run the command in -- `config`: The path to the config file -- `options-path`: The path to a JSON file with additional options -- `migrations-path`: The path to the migrations folder -- `seeders-path`: The path to the seeders folder -- `models-path`: The path to the models folder -- `url`: The database connection string to use. Alternative to using --config files -- `debug`: When available show various debug information - -Some scenarios where you can use it. - -- You want to override default path to `migrations`, `models`, `seeders` or `config` folder. -- You want to rename `config.json` to something else like `database.json` - -And a whole lot more. Let's see how you can use this file for custom configuration. - -For starters, let's create an empty file in the root directory of your project. - -```bash -$ touch .sequelizerc -``` - -Now let's work with an example config. - -```js -const path = require('path'); - -module.exports = { - 'config': path.resolve('config', 'database.json'), - 'models-path': path.resolve('db', 'models'), - 'seeders-path': path.resolve('db', 'seeders'), - 'migrations-path': path.resolve('db', 'migrations') -} -``` - -With this config you are telling CLI to - -- Use `config/database.json` file for config settings -- Use `db/models` as models folder -- Use `db/seeders` as seeders folder -- Use `db/migrations` as migrations folder - -### Dynamic Configuration - -Configuration file is by default a JSON file called `config.json`. But sometimes you want to execute some code or access environment variables which is not possible in JSON files. - -Sequelize CLI can read from both `JSON` and `JS` files. This can be setup with `.sequelizerc` file. Let see how - -First you need to create a `.sequelizerc` file in the root folder of your project. This file should override config path to a `JS` file. Like this - -```js -const path = require('path'); - -module.exports = { - 'config': path.resolve('config', 'config.js') -} -``` - -Now Sequelize CLI will load `config/config.js` for getting configuration options. Since this is a JS file you can have any code executed and export final dynamic configuration file. - -An example of `config/config.js` file - -```js -const fs = require('fs'); - -module.exports = { - development: { - username: 'database_dev', - password: 'database_dev', - database: 'database_dev', - host: '127.0.0.1', - dialect: 'mysql' - }, - test: { - username: 'database_test', - password: null, - database: 'database_test', - host: '127.0.0.1', - dialect: 'mysql' - }, - production: { - username: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - host: process.env.DB_HOSTNAME, - dialect: 'mysql', - dialectOptions: { - ssl: { - ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') - } - } - } -}; -``` - -### Using Babel - -Now you know how to use `.sequelizerc` file. Now let's see how to use this file to use babel with `sequelize-cli` setup. This will allow you to write migrations and seeders with ES6/ES7 syntax. - -First install `babel-register` - -```bash -$ npm i --save-dev babel-register -``` - -Now let's create `.sequelizerc` file, it can include any configuration you may want to change for `sequelize-cli` but in addition to that we want it to register babel for our codebase. Something like this - -```bash -$ touch .sequelizerc # Create rc file -``` - -Now include `babel-register` setup in this file - -```js -require("babel-register"); - -const path = require('path'); - -module.exports = { - 'config': path.resolve('config', 'config.json'), - 'models-path': path.resolve('models'), - 'seeders-path': path.resolve('seeders'), - 'migrations-path': path.resolve('migrations') -} -``` - -Now CLI will be able to run ES6/ES7 code from migrations/seeders etc. Please keep in mind this depends upon your configuration of `.babelrc`. Please read more about that at [babeljs.io](https://babeljs.io). - -### Using Environment Variables - -With CLI you can directly access the environment variables inside the `config/config.js`. You can use `.sequelizerc` to tell CLI to use `config/config.js` for configuration. This is explained in last section. - -Then you can just expose file with proper environment variables. - -```js -module.exports = { - development: { - username: 'database_dev', - password: 'database_dev', - database: 'database_dev', - host: '127.0.0.1', - dialect: 'mysql' - }, - test: { - username: process.env.CI_DB_USERNAME, - password: process.env.CI_DB_PASSWORD, - database: process.env.CI_DB_NAME, - host: '127.0.0.1', - dialect: 'mysql' - }, - production: { - username: process.env.PROD_DB_USERNAME, - password: process.env.PROD_DB_PASSWORD, - database: process.env.PROD_DB_NAME, - host: process.env.PROD_DB_HOSTNAME, - dialect: 'mysql' - } -}; -``` - -### Specifying Dialect Options - -Sometime you want to specify a dialectOption, if it's a general config you can just add it in `config/config.json`. Sometime you want to execute some code to get dialectOptions, you should use dynamic config file for those cases. - -```json -{ - "production": { - "dialect":"mysql", - "dialectOptions": { - "bigNumberStrings": true - } - } -} -``` - -### Production Usages - -Some tips around using CLI and migration setup in production environment. - -1) Use environment variables for config settings. This is better achieved with dynamic configuration. A sample production safe configuration may look like. - -```js -const fs = require('fs'); - -module.exports = { - development: { - username: 'database_dev', - password: 'database_dev', - database: 'database_dev', - host: '127.0.0.1', - dialect: 'mysql' - }, - test: { - username: 'database_test', - password: null, - database: 'database_test', - host: '127.0.0.1', - dialect: 'mysql' - }, - production: { - username: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - host: process.env.DB_HOSTNAME, - dialect: 'mysql', - dialectOptions: { - ssl: { - ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') - } - } - } -}; -``` - -Our goal is to use environment variables for various database secrets and not accidentally check them in to source control. - -### Storage - -There are three types of storage that you can use: `sequelize`, `json`, and `none`. - -- `sequelize` : stores migrations and seeds in a table on the sequelize database -- `json` : stores migrations and seeds on a json file -- `none` : does not store any migration/seed - -#### Migration Storage - -By default the CLI will create a table in your database called `SequelizeMeta` containing an entry -for each executed migration. To change this behavior, there are three options you can add to the -configuration file. Using `migrationStorage`, you can choose the type of storage to be used for -migrations. If you choose `json`, you can specify the path of the file using `migrationStoragePath` -or the CLI will write to the file `sequelize-meta.json`. If you want to keep the information in the -database, using `sequelize`, but want to use a different table, you can change the table name using -`migrationStorageTableName`. Also you can define a different schema for the `SequelizeMeta` table by -providing the `migrationStorageTableSchema` property. - -```json -{ - "development": { - "username": "root", - "password": null, - "database": "database_development", - "host": "127.0.0.1", - "dialect": "mysql", - - // Use a different storage type. Default: sequelize - "migrationStorage": "json", - - // Use a different file name. Default: sequelize-meta.json - "migrationStoragePath": "sequelizeMeta.json", - - // Use a different table name. Default: SequelizeMeta - "migrationStorageTableName": "sequelize_meta", - - // Use a different schema for the SequelizeMeta table - "migrationStorageTableSchema": "custom_schema" - } -} -``` - -**Note:** _The `none` storage is not recommended as a migration storage. If you decide to use it, be -aware of the implications of having no record of what migrations did or didn't run._ - -#### Seed Storage - -By default the CLI will not save any seed that is executed. If you choose to change this behavior (!), -you can use `seederStorage` in the configuration file to change the storage type. If you choose `json`, -you can specify the path of the file using `seederStoragePath` or the CLI will write to the file -`sequelize-data.json`. If you want to keep the information in the database, using `sequelize`, you can -specify the table name using `seederStorageTableName`, or it will default to `SequelizeData`. - -```json -{ - "development": { - "username": "root", - "password": null, - "database": "database_development", - "host": "127.0.0.1", - "dialect": "mysql", - // Use a different storage. Default: none - "seederStorage": "json", - // Use a different file name. Default: sequelize-data.json - "seederStoragePath": "sequelizeData.json", - // Use a different table name. Default: SequelizeData - "seederStorageTableName": "sequelize_data" - } -} -``` - -### Configuration Connection String - -As an alternative to the `--config` option with configuration files defining your database, you can -use the `--url` option to pass in a connection string. For example: - -```bash -$ npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name' -``` - -### Passing Dialect Specific Options - -```json -{ - "production": { - "dialect":"postgres", - "dialectOptions": { - // dialect options like SSL etc here - } - } -} -``` - -### Programmatic use - -Sequelize has a [sister library][1] for programmatically handling execution and logging of migration tasks. - -## Query Interface - -Using `queryInterface` object described before you can change database schema. To see full list of public methods it supports check [QueryInterface API][2] - -[0]: https://github.com/sequelize/cli -[1]: https://github.com/sequelize/umzug -[2]: ../class/lib/query-interface.js~QueryInterface.html diff --git a/docs/manual/models-definition.md b/docs/manual/models-definition.md deleted file mode 100644 index 30bb5d62a650..000000000000 --- a/docs/manual/models-definition.md +++ /dev/null @@ -1,727 +0,0 @@ -# Model definition - -To define mappings between a model and a table, use the `define` method. Each column must have a datatype, see more about [datatypes][1]. - -```js -class Project extends Model {} -Project.init({ - title: Sequelize.STRING, - description: Sequelize.TEXT -}, { sequelize, modelName: 'project' }); - -class Task extends Model {} -Task.init({ - title: Sequelize.STRING, - description: Sequelize.TEXT, - deadline: Sequelize.DATE -}, { sequelize, modelName: 'task' }) -``` - -Apart from [datatypes][1], there are plenty of options that you can set on each column. - -```js -class Foo extends Model {} -Foo.init({ - // instantiating will automatically set the flag to true if not set - flag: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true }, - - // default values for dates => current time - myDate: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }, - - // setting allowNull to false will add NOT NULL to the column, which means an error will be - // thrown from the DB when the query is executed if the column is null. If you want to check that a value - // is not null before querying the DB, look at the validations section below. - title: { type: Sequelize.STRING, allowNull: false }, - - // Creating two objects with the same value will throw an error. The unique property can be either a - // boolean, or a string. If you provide the same string for multiple columns, they will form a - // composite unique key. - uniqueOne: { type: Sequelize.STRING, unique: 'compositeIndex' }, - uniqueTwo: { type: Sequelize.INTEGER, unique: 'compositeIndex' }, - - // The unique property is simply a shorthand to create a unique constraint. - someUnique: { type: Sequelize.STRING, unique: true }, - - // It's exactly the same as creating the index in the model's options. - { someUnique: { type: Sequelize.STRING } }, - { indexes: [ { unique: true, fields: [ 'someUnique' ] } ] }, - - // Go on reading for further information about primary keys - identifier: { type: Sequelize.STRING, primaryKey: true }, - - // autoIncrement can be used to create auto_incrementing integer columns - incrementMe: { type: Sequelize.INTEGER, autoIncrement: true }, - - // You can specify a custom column name via the 'field' attribute: - fieldWithUnderscores: { type: Sequelize.STRING, field: 'field_with_underscores' }, - - // It is possible to create foreign keys: - bar_id: { - type: Sequelize.INTEGER, - - references: { - // This is a reference to another model - model: Bar, - - // This is the column name of the referenced model - key: 'id', - - // This declares when to check the foreign key constraint. PostgreSQL only. - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE - } - }, - - // It is possible to add comments on columns for MySQL, PostgreSQL and MSSQL only - commentMe: { - type: Sequelize.INTEGER, - - comment: 'This is a column name that has a comment' - } -}, { - sequelize, - modelName: 'foo' -}); -``` - -The comment option can also be used on a table, see [model configuration][0]. - -## Timestamps - -By default, Sequelize will add the attributes `createdAt` and `updatedAt` to your model so you will be able to know when the database entry went into the db and when it was updated last. - -Note that if you are using Sequelize migrations you will need to add the `createdAt` and `updatedAt` fields to your migration definition: - -```js -module.exports = { - up(queryInterface, Sequelize) { - return queryInterface.createTable('my-table', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - - // Timestamps - createdAt: Sequelize.DATE, - updatedAt: Sequelize.DATE, - }) - }, - down(queryInterface, Sequelize) { - return queryInterface.dropTable('my-table'); - }, -} - -``` - -If you do not want timestamps on your models, only want some timestamps, or you are working with an existing database where the columns are named something else, jump straight on to [configuration][0] to see how to do that. - -## Deferrable - -When you specify a foreign key column it is optionally possible to declare the deferrable -type in PostgreSQL. The following options are available: - -```js -// Defer all foreign key constraint check to the end of a transaction -Sequelize.Deferrable.INITIALLY_DEFERRED - -// Immediately check the foreign key constraints -Sequelize.Deferrable.INITIALLY_IMMEDIATE - -// Don't defer the checks at all -Sequelize.Deferrable.NOT -``` - -The last option is the default in PostgreSQL and won't allow you to dynamically change -the rule in a transaction. See [the transaction section](transactions.html#options) for further information. - -## Getters & setters - -It is possible to define 'object-property' getters and setter functions on your models, these can be used both for 'protecting' properties that map to database fields and for defining 'pseudo' properties. - -Getters and Setters can be defined in 2 ways (you can mix and match these 2 approaches): - -* as part of a single property definition -* as part of a model options - -**N.B:** If a getter or setter is defined in both places then the function found in the relevant property definition will always take precedence. - -### Defining as part of a property - -```js -class Employee extends Model {} -Employee.init({ - name: { - type: Sequelize.STRING, - allowNull: false, - get() { - const title = this.getDataValue('title'); - // 'this' allows you to access attributes of the instance - return this.getDataValue('name') + ' (' + title + ')'; - }, - }, - title: { - type: Sequelize.STRING, - allowNull: false, - set(val) { - this.setDataValue('title', val.toUpperCase()); - } - } -}, { sequelize, modelName: 'employee' }); - -Employee - .create({ name: 'John Doe', title: 'senior engineer' }) - .then(employee => { - console.log(employee.get('name')); // John Doe (SENIOR ENGINEER) - console.log(employee.get('title')); // SENIOR ENGINEER - }) -``` - -### Defining as part of the model options - -Below is an example of defining the getters and setters in the model options. - -The `fullName` getter, is an example of how you can define pseudo properties on your models - attributes which are not actually part of your database schema. In fact, pseudo properties can be defined in two ways: using model getters, or by using a column with the [`VIRTUAL` datatype](/variable/index.html#static-variable-DataTypes). Virtual datatypes can have validations, while getters for virtual attributes cannot. - -Note that the `this.firstname` and `this.lastname` references in the `fullName` getter function will trigger a call to the respective getter functions. If you do not want that then use the `getDataValue()` method to access the raw value (see below). - -```js -class Foo extends Model { - get fullName() { - return this.firstname + ' ' + this.lastname; - } - - set fullName(value) { - const names = value.split(' '); - this.setDataValue('firstname', names.slice(0, -1).join(' ')); - this.setDataValue('lastname', names.slice(-1).join(' ')); - } -} -Foo.init({ - firstname: Sequelize.STRING, - lastname: Sequelize.STRING -}, { - sequelize, - modelName: 'foo' -}); - -// Or with `sequelize.define` -sequelize.define('Foo', { - firstname: Sequelize.STRING, - lastname: Sequelize.STRING -}, { - getterMethods: { - fullName() { - return this.firstname + ' ' + this.lastname; - } - }, - - setterMethods: { - fullName(value) { - const names = value.split(' '); - - this.setDataValue('firstname', names.slice(0, -1).join(' ')); - this.setDataValue('lastname', names.slice(-1).join(' ')); - } - } -}); -``` - -### Helper functions for use inside getter and setter definitions - -* retrieving an underlying property value - always use `this.getDataValue()` - -```js -/* a getter for 'title' property */ -get() { - return this.getDataValue('title') -} -``` - -* setting an underlying property value - always use `this.setDataValue()` - -```js -/* a setter for 'title' property */ -set(title) { - this.setDataValue('title', title.toString().toLowerCase()); -} -``` - -**N.B:** It is important to stick to using the `setDataValue()` and `getDataValue()` functions (as opposed to accessing the underlying "data values" property directly) - doing so protects your custom getters and setters from changes in the underlying model implementations. - -## Validations - -Model validations allow you to specify format/content/inheritance validations for each attribute of the model. - -Validations are automatically run on `create`, `update` and `save`. You can also call `validate()` to manually validate an instance. - -### Per-attribute validations - -You can define your custom validators or use several built-in validators, implemented by [validator.js][3], as shown below. - -```js -class ValidateMe extends Model {} -ValidateMe.init({ - bar: { - type: Sequelize.STRING, - validate: { - is: ["^[a-z]+$",'i'], // will only allow letters - is: /^[a-z]+$/i, // same as the previous example using real RegExp - not: ["[a-z]",'i'], // will not allow letters - isEmail: true, // checks for email format (foo@bar.com) - isUrl: true, // checks for url format (http://foo.com) - isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format - isIPv4: true, // checks for IPv4 (129.89.23.1) - isIPv6: true, // checks for IPv6 format - isAlpha: true, // will only allow letters - isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail - isNumeric: true, // will only allow numbers - isInt: true, // checks for valid integers - isFloat: true, // checks for valid floating point numbers - isDecimal: true, // checks for any numbers - isLowercase: true, // checks for lowercase - isUppercase: true, // checks for uppercase - notNull: true, // won't allow null - isNull: true, // only allows null - notEmpty: true, // don't allow empty strings - equals: 'specific value', // only allow a specific value - contains: 'foo', // force specific substrings - notIn: [['foo', 'bar']], // check the value is not one of these - isIn: [['foo', 'bar']], // check the value is one of these - notContains: 'bar', // don't allow specific substrings - len: [2,10], // only allow values with length between 2 and 10 - isUUID: 4, // only allow uuids - isDate: true, // only allow date strings - isAfter: "2011-11-05", // only allow date strings after a specific date - isBefore: "2011-11-05", // only allow date strings before a specific date - max: 23, // only allow values <= 23 - min: 23, // only allow values >= 23 - isCreditCard: true, // check for valid credit card numbers - - // Examples of custom validators: - isEven(value) { - if (parseInt(value) % 2 !== 0) { - throw new Error('Only even values are allowed!'); - } - } - isGreaterThanOtherField(value) { - if (parseInt(value) <= parseInt(this.otherField)) { - throw new Error('Bar must be greater than otherField.'); - } - } - } - } -}, { sequelize }); -``` - -Note that where multiple arguments need to be passed to the built-in validation functions, the arguments to be passed must be in an array. But if a single array argument is to be passed, for instance an array of acceptable strings for `isIn`, this will be interpreted as multiple string arguments instead of one array argument. To work around this pass a single-length array of arguments, such as `[['one', 'two']]` as shown above. - -To use a custom error message instead of that provided by [validator.js][3], use an object instead of the plain value or array of arguments, for example a validator which needs no argument can be given a custom message with - -```js -isInt: { - msg: "Must be an integer number of pennies" -} -``` - -or if arguments need to also be passed add an `args` property: - -```js -isIn: { - args: [['en', 'zh']], - msg: "Must be English or Chinese" -} -``` - -When using custom validator functions the error message will be whatever message the thrown `Error` object holds. - -See [the validator.js project][3] for more details on the built in validation methods. - -**Hint:** You can also define a custom function for the logging part. Just pass a function. The first parameter will be the string that is logged. - -### Per-attribute validators and `allowNull` - -If a particular field of a model is set to not allow null (with `allowNull: false`) and that value has been set to `null`, all validators will be skipped and a `ValidationError` will be thrown. - -On the other hand, if it is set to allow null (with `allowNull: true`) and that value has been set to `null`, only the built-in validators will be skipped, while the custom validators will still run. - -This means you can, for instance, have a string field which validates its length to be between 5 and 10 characters, but which also allows `null` (since the length validator will be skipped automatically when the value is `null`): - -```js -class User extends Model {} -User.init({ - username: { - type: Sequelize.STRING, - allowNull: true, - validate: { - len: [5, 10] - } - } -}, { sequelize }); -``` - -You also can conditionally allow `null` values, with a custom validator, since it won't be skipped: - -```js -class User extends Model {} -User.init({ - age: Sequelize.INTEGER, - name: { - type: Sequelize.STRING, - allowNull: true, - validate: { - customValidator(value) { - if (value === null && this.age !== 10) { - throw new Error("name can't be null unless age is 10"); - } - }) - } - } -}, { sequelize }); -``` - -You can customize `allowNull` error message by setting the `notNull` validator: - -```js -class User extends Model {} -User.init({ - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - notNull: { - msg: 'Please enter your name' - } - } - } -}, { sequelize }); -``` - -### Model-wide validations - -Validations can also be defined to check the model after the field-specific validators. Using this you could, for example, ensure either neither of `latitude` and `longitude` are set or both, and fail if one but not the other is set. - -Model validator methods are called with the model object's context and are deemed to fail if they throw an error, otherwise pass. This is just the same as with custom field-specific validators. - -Any error messages collected are put in the validation result object alongside the field validation errors, with keys named after the failed validation method's key in the `validate` option object. Even though there can only be one error message for each model validation method at any one time, it is presented as a single string error in an array, to maximize consistency with the field errors. - -An example: - -```js -class Pub extends Model {} -Pub.init({ - name: { type: Sequelize.STRING }, - address: { type: Sequelize.STRING }, - latitude: { - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: null, - validate: { min: -90, max: 90 } - }, - longitude: { - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: null, - validate: { min: -180, max: 180 } - }, -}, { - validate: { - bothCoordsOrNone() { - if ((this.latitude === null) !== (this.longitude === null)) { - throw new Error('Require either both latitude and longitude or neither') - } - } - }, - sequelize, -}) -``` - -In this simple case an object fails validation if either latitude or longitude is given, but not both. If we try to build one with an out-of-range latitude and no longitude, `raging_bullock_arms.validate()` might return - -```js -{ - 'latitude': ['Invalid number: latitude'], - 'bothCoordsOrNone': ['Require either both latitude and longitude or neither'] -} -``` - -Such validation could have also been done with a custom validator defined on a single attribute (such as the `latitude` attribute, by checking `(value === null) !== (this.longitude === null)`), but the model-wide validation approach is cleaner. - -## Configuration - -You can also influence the way Sequelize handles your column names: - -```js -class Bar extends Model {} -Bar.init({ /* bla */ }, { - // The name of the model. The model will be stored in `sequelize.models` under this name. - // This defaults to class name i.e. Bar in this case. This will control name of auto-generated - // foreignKey and association naming - modelName: 'bar', - - // don't add the timestamp attributes (updatedAt, createdAt) - timestamps: false, - - // don't delete database entries but set the newly added attribute deletedAt - // to the current date (when deletion was done). paranoid will only work if - // timestamps are enabled - paranoid: true, - - // Will automatically set field option for all attributes to snake cased name. - // Does not override attribute with field option already defined - underscored: true, - - // disable the modification of table names; By default, sequelize will automatically - // transform all passed model names (first parameter of define) into plural. - // if you don't want that, set the following - freezeTableName: true, - - // define the table's name - tableName: 'my_very_custom_table_name', - - // Enable optimistic locking. When enabled, sequelize will add a version count attribute - // to the model and throw an OptimisticLockingError error when stale instances are saved. - // Set to true or a string with the attribute name you want to use to enable. - version: true, - - // Sequelize instance - sequelize, -}) -``` - -If you want sequelize to handle timestamps, but only want some of them, or want your timestamps to be called something else, you can override each column individually: - -```js -class Foo extends Model {} -Foo.init({ /* bla */ }, { - // don't forget to enable timestamps! - timestamps: true, - - // I don't want createdAt - createdAt: false, - - // I want updatedAt to actually be called updateTimestamp - updatedAt: 'updateTimestamp', - - // And deletedAt to be called destroyTime (remember to enable paranoid for this to work) - deletedAt: 'destroyTime', - paranoid: true, - - sequelize, -}) -``` - -You can also change the database engine, e.g. to MyISAM. InnoDB is the default. - -```js -class Person extends Model {} -Person.init({ /* attributes */ }, { - engine: 'MYISAM', - sequelize -}) - -// or globally -const sequelize = new Sequelize(db, user, pw, { - define: { engine: 'MYISAM' } -}) -``` - -Finally you can specify a comment for the table in MySQL and PG - -```js -class Person extends Model {} -Person.init({ /* attributes */ }, { - comment: "I'm a table comment!", - sequelize -}) -``` - -## Import - -You can also store your model definitions in a single file using the `import` method. The returned object is exactly the same as defined in the imported file's function. Since `v1:5.0` of Sequelize the import is cached, so you won't run into troubles when calling the import of a file twice or more often. - -```js -// in your server file - e.g. app.js -const Project = sequelize.import(__dirname + "/path/to/models/project") - -// The model definition is done in /path/to/models/project.js -// As you might notice, the DataTypes are the very same as explained above -module.exports = (sequelize, DataTypes) => { - class Project extends sequelize.Model { } - Project.init({ - name: DataTypes.STRING, - description: DataTypes.TEXT - }, { sequelize }); - return Project; -} -``` - -The `import` method can also accept a callback as an argument. - -```js -sequelize.import('project', (sequelize, DataTypes) => { - class Project extends sequelize.Model {} - Project.init({ - name: DataTypes.STRING, - description: DataTypes.TEXT - }, { sequelize }) - return Project; -}) -``` - -This extra capability is useful when, for example, `Error: Cannot find module` is thrown even though `/path/to/models/project` seems to be correct. Some frameworks, such as Meteor, overload `require`, and spit out "surprise" results like : - -```text -Error: Cannot find module '/home/you/meteorApp/.meteor/local/build/programs/server/app/path/to/models/project.js' -``` - -This is solved by passing in Meteor's version of `require`. So, while this probably fails ... - -```js -const AuthorModel = db.import('./path/to/models/project'); -``` - -... this should succeed ... - -```js -const AuthorModel = db.import('project', require('./path/to/models/project')); -``` - -## Optimistic Locking - -Sequelize has built-in support for optimistic locking through a model instance version count. -Optimistic locking is disabled by default and can be enabled by setting the `version` property to true in a specific model definition or global model configuration. See [model configuration][0] for more details. - -Optimistic locking allows concurrent access to model records for edits and prevents conflicts from overwriting data. It does this by checking whether another process has made changes to a record since it was read and throws an OptimisticLockError when a conflict is detected. - -## Database synchronization - -When starting a new project you won't have a database structure and using Sequelize you won't need to. Just specify your model structures and let the library do the rest. Currently supported is the creation and deletion of tables: - -```js -// Create the tables: -Project.sync() -Task.sync() - -// Force the creation! -Project.sync({force: true}) // this will drop the table first and re-create it afterwards - -// drop the tables: -Project.drop() -Task.drop() - -// event handling: -Project.[sync|drop]().then(() => { - // ok ... everything is nice! -}).catch(error => { - // oooh, did you enter wrong database credentials? -}) -``` - -Because synchronizing and dropping all of your tables might be a lot of lines to write, you can also let Sequelize do the work for you: - -```js -// Sync all models that aren't already in the database -sequelize.sync() - -// Force sync all models -sequelize.sync({force: true}) - -// Drop all tables -sequelize.drop() - -// emit handling: -sequelize.[sync|drop]().then(() => { - // woot woot -}).catch(error => { - // whooops -}) -``` - -Because `.sync({ force: true })` is destructive operation, you can use `match` option as an additional safety check. -`match` option tells sequelize to match a regex against the database name before syncing - a safety check for cases -where `force: true` is used in tests but not live code. - -```js -// This will run .sync() only if database name ends with '_test' -sequelize.sync({ force: true, match: /_test$/ }); -``` - -## Expansion of models - -Sequelize Models are ES6 classes. You can very easily add custom instance or class level methods. - -```js -class User extends Model { - // Adding a class level method - static classLevelMethod() { - return 'foo'; - } - - // Adding an instance level method - instanceLevelMethod() { - return 'bar'; - } -} -User.init({ firstname: Sequelize.STRING }, { sequelize }); -``` - -Of course you can also access the instance's data and generate virtual getters: - -```js -class User extends Model { - getFullname() { - return [this.firstname, this.lastname].join(' '); - } -} -User.init({ firstname: Sequelize.STRING, lastname: Sequelize.STRING }, { sequelize }); - -// Example: -User.build({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar' -``` - -### Indexes - -Sequelize supports adding indexes to the model definition which will be created during `Model.sync()` or `sequelize.sync`. - -```js -class User extends Model {} -User.init({}, { - indexes: [ - // Create a unique index on email - { - unique: true, - fields: ['email'] - }, - - // Creates a gin index on data with the jsonb_path_ops operator - { - fields: ['data'], - using: 'gin', - operator: 'jsonb_path_ops' - }, - - // By default index name will be [table]_[fields] - // Creates a multi column partial index - { - name: 'public_by_author', - fields: ['author', 'status'], - where: { - status: 'public' - } - }, - - // A BTREE index with an ordered field - { - name: 'title_index', - using: 'BTREE', - fields: ['author', {attribute: 'title', collate: 'en_US', order: 'DESC', length: 5}] - } - ], - sequelize -}); -``` - -[0]: models-definition.html#configuration -[1]: data-types.html -[3]: https://github.com/chriso/validator.js -[5]: /docs/final/misc#asynchronicity diff --git a/docs/manual/models-usage.md b/docs/manual/models-usage.md deleted file mode 100644 index ca13e281cd0e..000000000000 --- a/docs/manual/models-usage.md +++ /dev/null @@ -1,807 +0,0 @@ -# Model usage - -## Data retrieval / Finders - -Finder methods are intended to query data from the database. They do *not* return plain objects but instead return model instances. Because finder methods return model instances you can call any model instance member on the result as described in the documentation for [*instances*](instances.html). - -In this document we'll explore what finder methods can do: - -### `find` - Search for one specific element in the database - -```js -// search for known ids -Project.findByPk(123).then(project => { - // project will be an instance of Project and stores the content of the table entry - // with id 123. if such an entry is not defined you will get null -}) - -// search for attributes -Project.findOne({ where: {title: 'aProject'} }).then(project => { - // project will be the first entry of the Projects table with the title 'aProject' || null -}) - - -Project.findOne({ - where: {title: 'aProject'}, - attributes: ['id', ['name', 'title']] -}).then(project => { - // project will be the first entry of the Projects table with the title 'aProject' || null - // project.get('title') will contain the name of the project -}) -``` - -### `findOrCreate` - Search for a specific element or create it if not available - -The method `findOrCreate` can be used to check if a certain element already exists in the database. If that is the case the method will result in a respective instance. If the element does not yet exist, it will be created. - -Let's assume we have an empty database with a `User` model which has a `username` and a `job`. - -`where` option will be appended to `defaults` for create case. - -```js -User - .findOrCreate({where: {username: 'sdepold'}, defaults: {job: 'Technical Lead JavaScript'}}) - .then(([user, created]) => { - console.log(user.get({ - plain: true - })) - console.log(created) - - /* - findOrCreate returns an array containing the object that was found or created and a boolean that - will be true if a new object was created and false if not, like so: - - [ { - username: 'sdepold', - job: 'Technical Lead JavaScript', - id: 1, - createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), - updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) - }, - true ] - - In the example above, the array spread on line 3 divides the array into its 2 parts and passes them - as arguments to the callback function defined beginning at line 39, which treats them as "user" and - "created" in this case. (So "user" will be the object from index 0 of the returned array and - "created" will equal "true".) - */ - }) -``` - -The code created a new instance. So when we already have an instance ... - -```js -User.create({ username: 'fnord', job: 'omnomnom' }) - .then(() => User.findOrCreate({where: {username: 'fnord'}, defaults: {job: 'something else'}})) - .then(([user, created]) => { - console.log(user.get({ - plain: true - })) - console.log(created) - - /* - In this example, findOrCreate returns an array like this: - [ { - username: 'fnord', - job: 'omnomnom', - id: 2, - createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), - updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) - }, - false - ] - The array returned by findOrCreate gets spread into its 2 parts by the array spread on line 3, and - the parts will be passed as 2 arguments to the callback function beginning on line 69, which will - then treat them as "user" and "created" in this case. (So "user" will be the object from index 0 - of the returned array and "created" will equal "false".) - */ - }) -``` - -... the existing entry will not be changed. See the `job` of the second user, and the fact that created was false. - -### `findAndCountAll` - Search for multiple elements in the database, returns both data and total count - -This is a convenience method that combines`findAll` and `count` (see below) this is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query: - -The success handler will always receive an object with two properties: - -* `count` - an integer, total number records matching the where clause and other filters due to associations -* `rows` - an array of objects, the records matching the where clause and other filters due to associations, within the limit and offset range - -```js -Project - .findAndCountAll({ - where: { - title: { - [Op.like]: 'foo%' - } - }, - offset: 10, - limit: 2 - }) - .then(result => { - console.log(result.count); - console.log(result.rows); - }); -``` - -It support includes. Only the includes that are marked as `required` will be added to the count part: - -Suppose you want to find all users who have a profile attached: - -```js -User.findAndCountAll({ - include: [ - { model: Profile, required: true } - ], - limit: 3 -}); -``` - -Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted. Adding a `where` clause to the include automatically makes it required: - -```js -User.findAndCountAll({ - include: [ - { model: Profile, where: { active: true }} - ], - limit: 3 -}); -``` - -The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. - -The options object that you pass to `findAndCountAll` is the same as for `findAll` (described below). - -### `findAll` - Search for multiple elements in the database - -```js -// find multiple entries -Project.findAll().then(projects => { - // projects will be an array of all Project instances -}) - -// search for specific attributes - hash usage -Project.findAll({ where: { name: 'A Project' } }).then(projects => { - // projects will be an array of Project instances with the specified name -}) - -// search within a specific range -Project.findAll({ where: { id: [1,2,3] } }).then(projects => { - // projects will be an array of Projects having the id 1, 2 or 3 - // this is actually doing an IN query -}) - -Project.findAll({ - where: { - id: { - [Op.and]: {a: 5}, // AND (a = 5) - [Op.or]: [{a: 5}, {a: 6}], // (a = 5 OR a = 6) - [Op.gt]: 6, // id > 6 - [Op.gte]: 6, // id >= 6 - [Op.lt]: 10, // id < 10 - [Op.lte]: 10, // id <= 10 - [Op.ne]: 20, // id != 20 - [Op.between]: [6, 10], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only) - }, - status: { - [Op.not]: false // status NOT FALSE - } - } -}) -``` - -### Complex filtering / OR / NOT queries - -It's possible to do complex where queries with multiple levels of nested AND, OR and NOT conditions. In order to do that you can use `or`, `and` or `not` `Operators`: - -```js -Project.findOne({ - where: { - name: 'a project', - [Op.or]: [ - { id: [1,2,3] }, - { id: { [Op.gt]: 10 } } - ] - } -}) - -Project.findOne({ - where: { - name: 'a project', - id: { - [Op.or]: [ - [1,2,3], - { [Op.gt]: 10 } - ] - } - } -}) -``` - -Both pieces of code will generate the following: - -```sql -SELECT * -FROM `Projects` -WHERE ( - `Projects`.`name` = 'a project' - AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10) -) -LIMIT 1; -``` - -`not` example: - -```js -Project.findOne({ - where: { - name: 'a project', - [Op.not]: [ - { id: [1,2,3] }, - { array: { [Op.contains]: [3,4,5] } } - ] - } -}); -``` - -Will generate: - -```sql -SELECT * -FROM `Projects` -WHERE ( - `Projects`.`name` = 'a project' - AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[]) -) -LIMIT 1; -``` - -### Manipulating the dataset with limit, offset, order and group - -To get more relevant data, you can use limit, offset, order and grouping: - -```js -// limit the results of the query -Project.findAll({ limit: 10 }) - -// step over the first 10 elements -Project.findAll({ offset: 10 }) - -// step over the first 10 elements, and take 2 -Project.findAll({ offset: 10, limit: 2 }) -``` - -The syntax for grouping and ordering are equal, so below it is only explained with a single example for group, and the rest for order. Everything you see below can also be done for group - -```js -Project.findAll({order: [['title', 'DESC']]}) -// yields ORDER BY title DESC - -Project.findAll({group: 'name'}) -// yields GROUP BY name -``` - -Notice how in the two examples above, the string provided is inserted verbatim into the query, i.e. column names are not escaped. When you provide a string to order/group, this will always be the case. If you want to escape column names, you should provide an array of arguments, even though you only want to order/group by a single column - -```js -something.findOne({ - order: [ - // will return `name` - ['name'], - // will return `username` DESC - ['username', 'DESC'], - // will return max(`age`) - sequelize.fn('max', sequelize.col('age')), - // will return max(`age`) DESC - [sequelize.fn('max', sequelize.col('age')), 'DESC'], - // will return otherfunction(`col1`, 12, 'lalala') DESC - [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], - // will return otherfunction(awesomefunction(`col`)) DESC, This nesting is potentially infinite! - [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'] - ] -}) -``` - -To recap, the elements of the order/group array can be the following: - -* String - will be quoted -* Array - first element will be quoted, second will be appended verbatim -* Object - - * Raw will be added verbatim without quoting - * Everything else is ignored, and if raw is not set, the query will fail -* Sequelize.fn and Sequelize.col returns functions and quoted column names - -### Raw queries - -Sometimes you might be expecting a massive dataset that you just want to display, without manipulation. For each row you select, Sequelize creates an instance with functions for update, delete, get associations etc. If you have thousands of rows, this might take some time. If you only need the raw data and don't want to update anything, you can do like this to get the raw data. - -```js -// Are you expecting a massive dataset from the DB, -// and don't want to spend the time building DAOs for each entry? -// You can pass an extra query option to get the raw data instead: -Project.findAll({ where: { ... }, raw: true }) -``` - -### `count` - Count the occurrences of elements in the database - -There is also a method for counting database objects: - -```js -Project.count().then(c => { - console.log("There are " + c + " projects!") -}) - -Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => { - console.log("There are " + c + " projects with an id greater than 25.") -}) -``` - -### `max` - Get the greatest value of a specific attribute within a specific table - -And here is a method for getting the max value of an attribute - -```js -/* - Let's assume 3 person objects with an attribute age. - The first one is 10 years old, - the second one is 5 years old, - the third one is 40 years old. -*/ -Project.max('age').then(max => { - // this will return 40 -}) - -Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => { - // will be 10 -}) -``` - -### `min` - Get the least value of a specific attribute within a specific table - -And here is a method for getting the min value of an attribute: - -```js -/* - Let's assume 3 person objects with an attribute age. - The first one is 10 years old, - the second one is 5 years old, - the third one is 40 years old. -*/ -Project.min('age').then(min => { - // this will return 5 -}) - -Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => { - // will be 10 -}) -``` - -### `sum` - Sum the value of specific attributes - -In order to calculate the sum over a specific column of a table, you can -use the `sum` method. - -```js -/* - Let's assume 3 person objects with an attribute age. - The first one is 10 years old, - the second one is 5 years old, - the third one is 40 years old. -*/ -Project.sum('age').then(sum => { - // this will return 55 -}) - -Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => { - // will be 50 -}) -``` - -## Eager loading - -When you are retrieving data from the database there is a fair chance that you also want to get associations with the same query - this is called eager loading. The basic idea behind that, is the use of the attribute `include` when you are calling `find` or `findAll`. Lets assume the following setup: - -```js -class User extends Model {} -User.init({ name: Sequelize.STRING }, { sequelize, modelName: 'user' }) -class Task extends Model {} -Task.init({ name: Sequelize.STRING }, { sequelize, modelName: 'task' }) -class Tool extends Model {} -Tool.init({ name: Sequelize.STRING }, { sequelize, modelName: 'tool' }) - -Task.belongsTo(User) -User.hasMany(Task) -User.hasMany(Tool, { as: 'Instruments' }) - -sequelize.sync().then(() => { - // this is where we continue ... -}) -``` - -OK. So, first of all, let's load all tasks with their associated user. - -```js -Task.findAll({ include: [ User ] }).then(tasks => { - console.log(JSON.stringify(tasks)) - - /* - [{ - "name": "A Task", - "id": 1, - "createdAt": "2013-03-20T20:31:40.000Z", - "updatedAt": "2013-03-20T20:31:40.000Z", - "userId": 1, - "user": { - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z" - } - }] - */ -}) -``` - -Notice that the accessor (the `User` property in the resulting instance) is singular because the association is one-to-something. - -Next thing: Loading of data with many-to-something associations! - -```js -User.findAll({ include: [ Task ] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "tasks": [{ - "name": "A Task", - "id": 1, - "createdAt": "2013-03-20T20:31:40.000Z", - "updatedAt": "2013-03-20T20:31:40.000Z", - "userId": 1 - }] - }] - */ -}) -``` - -Notice that the accessor (the `Tasks` property in the resulting instance) is plural because the association is many-to-something. - -If an association is aliased (using the `as` option), you must specify this alias when including the model. Notice how the user's `Tool`s are aliased as `Instruments` above. In order to get that right you have to specify the model you want to load, as well as the alias: - -```js -User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }] - */ -}) -``` - -You can also include by alias name by specifying a string that matches the association alias: - -```js -User.findAll({ include: ['Instruments'] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }] - */ -}) - -User.findAll({ include: [{ association: 'Instruments' }] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }] - */ -}) -``` - -When eager loading we can also filter the associated model using `where`. This will return all `User`s in which the `where` clause of `Tool` model matches rows. - -```js -User.findAll({ - include: [{ - model: Tool, - as: 'Instruments', - where: { name: { [Op.like]: '%ooth%' } } - }] -}).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - - [{ - "name": "John Smith", - "id": 2, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - */ - }) -``` - -When an eager loaded model is filtered using `include.where` then `include.required` is implicitly set to -`true`. This means that an inner join is done returning parent models with any matching children. - -### Top level where with eagerly loaded models - -To move the where conditions from an included model from the `ON` condition to the top level `WHERE` you can use the `'$nested.column$'` syntax: - -```js -User.findAll({ - where: { - '$Instruments.name$': { [Op.iLike]: '%ooth%' } - }, - include: [{ - model: Tool, - as: 'Instruments' - }] -}).then(users => { - console.log(JSON.stringify(users)); - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - - [{ - "name": "John Smith", - "id": 2, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - */ -``` - -### Including everything - -To include all attributes, you can pass a single object with `all: true`: - -```js -User.findAll({ include: [{ all: true }]}); -``` - -### Including soft deleted records - -In case you want to eager load soft deleted records you can do that by setting `include.paranoid` to `false` - -```js -User.findAll({ - include: [{ - model: Tool, - where: { name: { [Op.like]: '%ooth%' } }, - paranoid: false // query and loads the soft deleted records - }] -}); -``` - -### Ordering Eager Loaded Associations - -In the case of a one-to-many relationship. - -```js -Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] }); -Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] }); -Company.findAll({ - include: [ { model: Division, as: 'Div' } ], - order: [ [ { model: Division, as: 'Div' }, 'name' ] ] -}); -Company.findAll({ - include: [ { model: Division, as: 'Div' } ], - order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ] -}); -Company.findAll({ - include: [ { model: Division, include: [ Department ] } ], - order: [ [ Division, Department, 'name' ] ] -}); -``` - -In the case of many-to-many joins, you are also able to sort by attributes in the through table. - -```js -Company.findAll({ - include: [ { model: Division, include: [ Department ] } ], - order: [ [ Division, DepartmentDivision, 'name' ] ] -}); -``` - -### Nested eager loading - -You can use nested eager loading to load all related models of a related model: - -```js -User.findAll({ - include: [ - {model: Tool, as: 'Instruments', include: [ - {model: Teacher, include: [ /* etc */]} - ]} - ] -}).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ // 1:M and N:M association - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1, - "Teacher": { // 1:1 association - "name": "Jimi Hendrix" - } - }] - }] - */ -}) -``` - -This will produce an outer join. However, a `where` clause on a related model will create an inner join and return only the instances that have matching sub-models. To return all parent instances, you should add `required: false`. - -```js -User.findAll({ - include: [{ - model: Tool, - as: 'Instruments', - include: [{ - model: Teacher, - where: { - school: "Woodstock Music School" - }, - required: false - }] - }] -}).then(users => { - /* ... */ -}) -``` - -The query above will return all users, and all their instruments, but only those teachers associated with `Woodstock Music School`. - -Include all also supports nested loading: - -```js -User.findAll({ include: [{ all: true, nested: true }]}); -``` - -### Use right join for association - -By default, associations are loaded using a left join, that is to say it only includes records from the parent table. You can change this behavior to a right join by passing the `right` property, if the dialect you are using supports it. Currenly, `sqlite` *does not* support [right joins](https://www.sqlite.org/omitted.html). - -*Note:* `right` is only respected if `required` is false. - -```js -User.findAll({ - include: [{ - model: Tool // will create a left join - }] -}); - -User.findAll({ - include: [{ - model: Tool, - right: true // will create a right join - }] -}); - -User.findAll({ - include: [{ - model: Tool, - required: true, - right: true // has no effect, will create an inner join - }] -}); - -User.findAll({ - include: [{ - model: Tool, - where: { name: { [Op.like]: '%ooth%' } }, - right: true // has no effect, will create an inner join - }] -}); - -User.findAll({ - include: [{ - model: Tool, - where: { name: { [Op.like]: '%ooth%' } }, - required: false - right: true // because we set `required` to false, this will create a right join - }] -}); -``` diff --git a/docs/manual/moved/associations.md b/docs/manual/moved/associations.md new file mode 100644 index 000000000000..cf001aac731f --- /dev/null +++ b/docs/manual/moved/associations.md @@ -0,0 +1,16 @@ +# \[MOVED\] Associations + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Associations](assocs.html) +* **Advanced Association Concepts** + * [Eager Loading](eager-loading.html) + * [Creating with Associations](creating-with-associations.html) + * [Advanced M:N Associations](advanced-many-to-many.html) + * [Polymorphism & Scopes](polymorphism-and-scopes.html) +* **Other Topics** + * [Naming Strategies](naming-strategies.html) + * [Constraints & Circularities](constraints-and-circularities.html) \ No newline at end of file diff --git a/docs/manual/moved/data-types.md b/docs/manual/moved/data-types.md new file mode 100644 index 000000000000..4ba48b9798d9 --- /dev/null +++ b/docs/manual/moved/data-types.md @@ -0,0 +1,12 @@ +# \[MOVED\] Data Types + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Model Basics: Data Types](model-basics.html#data-types) +* **Other Topics** + * [Other Data Types](other-data-types.html) + * [Extending Data Types](extending-data-types.html) + * [Dialect-Specific Things](dialect-specific-things.html) \ No newline at end of file diff --git a/docs/manual/moved/models-definition.md b/docs/manual/moved/models-definition.md new file mode 100644 index 000000000000..177e8a28dcc1 --- /dev/null +++ b/docs/manual/moved/models-definition.md @@ -0,0 +1,55 @@ +# \[MOVED\] Models Definition + +The contents of this page were moved to [Model Basics](model-basics.html). + +The only exception is the guide on `sequelize.import`, which is deprecated and was removed from the docs. However, if you really need it, it was kept here. + +---- + +## Deprecated: `sequelize.import` + +> _**Note:** You should not use `sequelize.import`. Please just use `require` instead._ +> +> _This documentation has been kept just in case you really need to maintain old code that uses it._ + +You can store your model definitions in a single file using the `sequelize.import` method. The returned object is exactly the same as defined in the imported file's function. The import is cached, just like `require`, so you won't run into trouble if importing a file more than once. + +```js +// in your server file - e.g. app.js +const Project = sequelize.import(__dirname + "/path/to/models/project"); + +// The model definition is done in /path/to/models/project.js +module.exports = (sequelize, DataTypes) => { + return sequelize.define('project', { + name: DataTypes.STRING, + description: DataTypes.TEXT + }); +}; +``` + +The `import` method can also accept a callback as an argument. + +```js +sequelize.import('project', (sequelize, DataTypes) => { + return sequelize.define('project', { + name: DataTypes.STRING, + description: DataTypes.TEXT + }); +}); +``` + +This extra capability is useful when, for example, `Error: Cannot find module` is thrown even though `/path/to/models/project` seems to be correct. Some frameworks, such as Meteor, overload `require`, and might raise an error such as: + +```text +Error: Cannot find module '/home/you/meteorApp/.meteor/local/build/programs/server/app/path/to/models/project.js' +``` + +This can be worked around by passing in Meteor's version of `require`: + +```js +// If this fails... +const AuthorModel = db.import('./path/to/models/project'); + +// Try this instead! +const AuthorModel = db.import('project', require('./path/to/models/project')); +``` \ No newline at end of file diff --git a/docs/manual/moved/models-usage.md b/docs/manual/moved/models-usage.md new file mode 100644 index 000000000000..020eeacab726 --- /dev/null +++ b/docs/manual/moved/models-usage.md @@ -0,0 +1,12 @@ +# \[MOVED\] Models Usage + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Model Querying - Basics](model-querying-basics.html) + * [Model Querying - Finders](model-querying-finders.html) + * [Raw Queries](raw-queries.html) +* **Advanced Association Concepts** + * [Eager Loading](eager-loading.html) \ No newline at end of file diff --git a/docs/manual/moved/querying.md b/docs/manual/moved/querying.md new file mode 100644 index 000000000000..94b8d8ae9c99 --- /dev/null +++ b/docs/manual/moved/querying.md @@ -0,0 +1,13 @@ +# \[MOVED\] Querying + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Model Querying - Basics](model-querying-basics.html) + * [Model Querying - Finders](model-querying-finders.html) + * [Raw Queries](raw-queries.html) + * [Associations](assocs.html) +* **Other Topics** + * [Dialect-Specific Things](dialect-specific-things.html) \ No newline at end of file diff --git a/docs/manual/other-topics/connection-pool.md b/docs/manual/other-topics/connection-pool.md new file mode 100644 index 000000000000..d8501ef5ef3c --- /dev/null +++ b/docs/manual/other-topics/connection-pool.md @@ -0,0 +1,17 @@ +# Connection Pool + +If you're connecting to the database from a single process, you should create only one Sequelize instance. Sequelize will set up a connection pool on initialization. This connection pool can be configured through the constructor's `options` parameter (using `options.pool`), as is shown in the following example: + +```js +const sequelize = new Sequelize(/* ... */, { + // ... + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } +}); +``` + +Learn more in the [API Reference for the Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). If you're connecting to the database from multiple processes, you'll have to create one instance per process, but each instance should have a maximum connection pool size of such that the total maximum size is respected. For example, if you want a max connection pool size of 90 and you have three processes, the Sequelize instance of each process should have a max connection pool size of 30. diff --git a/docs/manual/other-topics/constraints-and-circularities.md b/docs/manual/other-topics/constraints-and-circularities.md new file mode 100644 index 000000000000..c48708a0168f --- /dev/null +++ b/docs/manual/other-topics/constraints-and-circularities.md @@ -0,0 +1,113 @@ +# Constraints & Circularities + +Adding constraints between tables means that tables must be created in the database in a certain order, when using `sequelize.sync`. If `Task` has a reference to `User`, the `User` table must be created before the `Task` table can be created. This can sometimes lead to circular references, where Sequelize cannot find an order in which to sync. Imagine a scenario of documents and versions. A document can have multiple versions, and for convenience, a document has a reference to its current version. + +```js +const { Sequelize, Model, DataTypes } = require("sequelize"); + +class Document extends Model {} +Document.init({ + author: DataTypes.STRING +}, { sequelize, modelName: 'document' }); + +class Version extends Model {} +Version.init({ + timestamp: DataTypes.DATE +}, { sequelize, modelName: 'version' }); + +Document.hasMany(Version); // This adds documentId attribute to version +Document.belongsTo(Version, { + as: 'Current', + foreignKey: 'currentVersionId' +}); // This adds currentVersionId attribute to document +``` + +However, unfortunately the code above will result in the following error: + +```text +Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents +``` + +In order to alleviate that, we can pass `constraints: false` to one of the associations: + +```js +Document.hasMany(Version); +Document.belongsTo(Version, { + as: 'Current', + foreignKey: 'currentVersionId', + constraints: false +}); +``` + +Which will allow us to sync the tables correctly: + +```sql +CREATE TABLE IF NOT EXISTS "documents" ( + "id" SERIAL, + "author" VARCHAR(255), + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "currentVersionId" INTEGER, + PRIMARY KEY ("id") +); + +CREATE TABLE IF NOT EXISTS "versions" ( + "id" SERIAL, + "timestamp" TIMESTAMP WITH TIME ZONE, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE + SET + NULL ON UPDATE CASCADE, + PRIMARY KEY ("id") +); +``` + +## Enforcing a foreign key reference without constraints + +Sometimes you may want to reference another table, without adding any constraints, or associations. In that case you can manually add the reference attributes to your schema definition, and mark the relations between them. + +```js +class Trainer extends Model {} +Trainer.init({ + firstName: Sequelize.STRING, + lastName: Sequelize.STRING +}, { sequelize, modelName: 'trainer' }); + +// Series will have a trainerId = Trainer.id foreign reference key +// after we call Trainer.hasMany(series) +class Series extends Model {} +Series.init({ + title: Sequelize.STRING, + subTitle: Sequelize.STRING, + description: Sequelize.TEXT, + // Set FK relationship (hasMany) with `Trainer` + trainerId: { + type: DataTypes.INTEGER, + references: { + model: Trainer, + key: 'id' + } + } +}, { sequelize, modelName: 'series' }); + +// Video will have seriesId = Series.id foreign reference key +// after we call Series.hasOne(Video) +class Video extends Model {} +Video.init({ + title: Sequelize.STRING, + sequence: Sequelize.INTEGER, + description: Sequelize.TEXT, + // set relationship (hasOne) with `Series` + seriesId: { + type: DataTypes.INTEGER, + references: { + model: Series, // Can be both a string representing the table name or a Sequelize model + key: 'id' + } + } +}, { sequelize, modelName: 'video' }); + +Series.hasOne(Video); +Trainer.hasMany(Series); +``` \ No newline at end of file diff --git a/docs/manual/other-topics/dialect-specific-things.md b/docs/manual/other-topics/dialect-specific-things.md new file mode 100644 index 000000000000..324525f8efc6 --- /dev/null +++ b/docs/manual/other-topics/dialect-specific-things.md @@ -0,0 +1,195 @@ +# Dialect-Specific Things + +## Underlying Connector Libraries + +### MySQL + +The underlying connector library used by Sequelize for MySQL is the [mysql2](https://www.npmjs.com/package/mysql2) npm package (version 1.5.2 or higher). + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'mysql', + dialectOptions: { + // Your mysql2 options here + } +}) +``` + +### MariaDB + +The underlying connector library used by Sequelize for MariaDB is the [mariadb](https://www.npmjs.com/package/mariadb) npm package. + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'mariadb', + dialectOptions: { + // Your mariadb options here + // connectTimeout: 1000 + } +}); +``` + +### SQLite + +The underlying connector library used by Sequelize for SQLite is the [sqlite3](https://www.npmjs.com/package/sqlite3) npm package (version 4.0.0 or above). + +You specify the storage file in the Sequelize constructor with the `storage` option (use `:memory:` for an in-memory SQLite instance). + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'sqlite', + storage: 'path/to/database.sqlite' // or ':memory:' + dialectOptions: { + // Your sqlite3 options here + } +}); +``` + +### PostgreSQL + +The underlying connector library used by Sequelize for PostgreSQL is the [pg](https://www.npmjs.com/package/pg) npm package (version 7.0.0 or above). The module [pg-hstore](https://www.npmjs.com/package/pg-hstore) is also necessary. + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'postgres', + dialectOptions: { + // Your pg options here + } +}); +``` + +To connect over a unix domain socket, specify the path to the socket directory in the `host` option. The socket path must start with `/`. + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'postgres', + host: '/path/to/socket_directory' +}); +``` + +### MSSQL + +The underlying connector library used by Sequelize for MSSQL is the [tedious](https://www.npmjs.com/package/tedious) npm package (version 6.0.0 or above). + +You can provide custom options to it using `dialectOptions.options` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'postgres', + dialectOptions: { + // Observe the need for this nested `options` field for MSSQL + options: { + // Your tedious options here + useUTC: false, + dateFirst: 1 + } + } +}); +``` + +## Data type: TIMESTAMP WITHOUT TIME ZONE - PostgreSQL only + +If you are working with the PostgreSQL `TIMESTAMP WITHOUT TIME ZONE` and you need to parse it to a different timezone, please use the pg library's own parser: + +```js +require('pg').types.setTypeParser(1114, stringValue => { + return new Date(stringValue + '+0000'); + // e.g., UTC offset. Use any offset that you would like. +}); +``` + +## Data type: ARRAY(ENUM) - PostgreSQL only + +Array(Enum) type requireS special treatment. Whenever Sequelize will talk to the database, it has to typecast array values with ENUM name. + +So this enum name must follow this pattern `enum__`. If you are using `sync` then correct name will automatically be generated. + +## Table Hints - MSSQL only + +The `tableHint` option can be used to define a table hint. The hint must be a value from `TableHints` and should only be used when absolutely necessary. Only a single table hint is currently supported per query. + +Table hints override the default behavior of MSSQL query optimizer by specifing certain options. They only affect the table or view referenced in that clause. + +```js +const { TableHints } = require('sequelize'); +Project.findAll({ + // adding the table hint NOLOCK + tableHint: TableHints.NOLOCK + // this will generate the SQL 'WITH (NOLOCK)' +}) +``` + +## Index Hints - MySQL/MariaDB only + +The `indexHints` option can be used to define index hints. The hint type must be a value from `IndexHints` and the values should reference existing indexes. + +Index hints [override the default behavior of the MySQL query optimizer](https://dev.mysql.com/doc/refman/5.7/en/index-hints.html). + +```js +const { IndexHints } = require("sequelize"); +Project.findAll({ + indexHints: [ + { type: IndexHints.USE, values: ['index_project_on_name'] } + ], + where: { + id: { + [Op.gt]: 623 + }, + name: { + [Op.like]: 'Foo %' + } + } +}); +``` + +The above will generate a MySQL query that looks like this: + +```sql +SELECT * FROM Project USE INDEX (index_project_on_name) WHERE name LIKE 'FOO %' AND id > 623; +``` + +`Sequelize.IndexHints` includes `USE`, `FORCE`, and `IGNORE`. + +See [Issue #9421](https://github.com/sequelize/sequelize/issues/9421) for the original API proposal. + +## Engines - MySQL/MariaDB only + +The default engine for a model is InnoDB. + +You can change the engine for a model with the `engine` option (e.g., to MyISAM): + +```js +const Person = sequelize.define('person', { /* attributes */ }, { + engine: 'MYISAM' +}); +``` + +Like every option for the definition of a model, this setting can also be changed globally with the `define` option of the Sequelize constructor: + +```js +const sequelize = new Sequelize(db, user, pw, { + define: { engine: 'MYISAM' } +}) +``` + +## Table comments - MySQL/MariaDB/PostgreSQL only + +You can specify a comment for a table when defining the model: + +```js +class Person extends Model {} +Person.init({ /* attributes */ }, { + comment: "I'm a table comment!", + sequelize +}) +``` + +The comment will be set when calling `sync()`. \ No newline at end of file diff --git a/docs/manual/other-topics/extending-data-types.md b/docs/manual/other-topics/extending-data-types.md new file mode 100644 index 000000000000..2e0938916faf --- /dev/null +++ b/docs/manual/other-topics/extending-data-types.md @@ -0,0 +1,113 @@ +# Extending Data Types + +Most likely the type you are trying to implement is already included in [DataTypes](data-types.html). If a new datatype is not included, this manual will show how to write it yourself. + +Sequelize doesn't create new datatypes in the database. This tutorial explains how to make Sequelize recognize new datatypes and assumes that those new datatypes are already created in the database. + +To extend Sequelize datatypes, do it before any Sequelize instance is created. + +## Example + +In this example, we will create a type called `SOMETYPE` that replicates the built-in datatype `DataTypes.INTEGER(11).ZEROFILL.UNSIGNED`. + +```js +const { Sequelize, DataTypes, Utils } = require('Sequelize'); +createTheNewDataType(); +const sequelize = new Sequelize('sqlite::memory:'); + +function createTheNewDataType() { + + class SOMETYPE extends DataTypes.ABSTRACT { + // Mandatory: complete definition of the new type in the database + toSql() { + return 'INTEGER(11) UNSIGNED ZEROFILL' + } + + // Optional: validator function + validate(value, options) { + return (typeof value === 'number') && (!Number.isNaN(value)); + } + + // Optional: sanitizer + _sanitize(value) { + // Force all numbers to be positive + return value < 0 ? 0 : Math.round(value); + } + + // Optional: value stringifier before sending to database + _stringify(value) { + return value.toString(); + } + + // Optional: parser for values received from the database + static parse(value) { + return Number.parseInt(value); + } + } + + // Mandatory: set the type key + SOMETYPE.prototype.key = SOMETYPE.key = 'SOMETYPE'; + + // Mandatory: add the new type to DataTypes. Optionally wrap it on `Utils.classToInvokable` to + // be able to use this datatype directly without having to call `new` on it. + DataTypes.SOMETYPE = Utils.classToInvokable(SOMETYPE); + + // Optional: disable escaping after stringifier. Do this at your own risk, since this opens opportunity for SQL injections. + // DataTypes.SOMETYPE.escape = false; + +} +``` + +After creating this new datatype, you need to map this datatype in each database dialect and make some adjustments. + +## PostgreSQL + +Let's say the name of the new datatype is `pg_new_type` in the postgres database. That name has to be mapped to `DataTypes.SOMETYPE`. Additionally, it is required to create a child postgres-specific datatype. + +```js +function createTheNewDataType() { + // [...] + + const PgTypes = DataTypes.postgres; + + // Mandatory: map postgres datatype name + DataTypes.SOMETYPE.types.postgres = ['pg_new_type']; + + // Mandatory: create a postgres-specific child datatype with its own parse + // method. The parser will be dynamically mapped to the OID of pg_new_type. + PgTypes.SOMETYPE = function SOMETYPE() { + if (!(this instanceof PgTypes.SOMETYPE)) { + return new PgTypes.SOMETYPE(); + } + DataTypes.SOMETYPE.apply(this, arguments); + } + const util = require('util'); // Built-in Node package + util.inherits(PgTypes.SOMETYPE, DataTypes.SOMETYPE); + + // Mandatory: create, override or reassign a postgres-specific parser + // PgTypes.SOMETYPE.parse = value => value; + PgTypes.SOMETYPE.parse = DataTypes.SOMETYPE.parse || x => x; + + // Optional: add or override methods of the postgres-specific datatype + // like toSql, escape, validate, _stringify, _sanitize... + +} +``` + +### Ranges + +After a new range type has been [defined in postgres](https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-DEFINING), it is trivial to add it to Sequelize. + +In this example the name of the postgres range type is `SOMETYPE_range` and the name of the underlying postgres datatype is `pg_new_type`. The key of `subtypes` and `castTypes` is the key of the Sequelize datatype `DataTypes.SOMETYPE.key`, in lower case. + +```js +function createTheNewDataType() { + // [...] + + // Add postgresql range, SOMETYPE comes from DataType.SOMETYPE.key in lower case + DataTypes.RANGE.types.postgres.subtypes.SOMETYPE = 'SOMETYPE_range'; + DataTypes.RANGE.types.postgres.castTypes.SOMETYPE = 'pg_new_type'; +} +``` + +The new range can be used in model definitions as `DataTypes.RANGE(DataTypes.SOMETYPE)` or `DataTypes.RANGE(DataTypes.SOMETYPE)`. diff --git a/docs/manual/other-topics/hooks.md b/docs/manual/other-topics/hooks.md new file mode 100644 index 000000000000..9dd422232df0 --- /dev/null +++ b/docs/manual/other-topics/hooks.md @@ -0,0 +1,385 @@ +# Hooks + +Hooks (also known as lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a `beforeUpdate` hook. + +**Note:** _You can't use hooks with instances. Hooks are used with models._ + +## Available hooks + +Sequelize provides a lot of hooks. The full list can be found in directly in the [source code - lib/hooks.js](https://github.com/sequelize/sequelize/blob/master/lib/hooks.js#L7). + +## Hooks firing order + +The diagram below shows the firing order for the most common hooks. + +_**Note:** this list is not exhaustive._ + +```text +(1) + beforeBulkCreate(instances, options) + beforeBulkDestroy(options) + beforeBulkUpdate(options) +(2) + beforeValidate(instance, options) + +[... validation happens ...] + +(3) + afterValidate(instance, options) + validationFailed(instance, options, error) +(4) + beforeCreate(instance, options) + beforeDestroy(instance, options) + beforeUpdate(instance, options) + beforeSave(instance, options) + beforeUpsert(values, options) + +[... creation/update/destruction happens ...] + +(5) + afterCreate(instance, options) + afterDestroy(instance, options) + afterUpdate(instance, options) + afterSave(instance, options) + afterUpsert(created, options) +(6) + afterBulkCreate(instances, options) + afterBulkDestroy(options) + afterBulkUpdate(options) +``` + +## Declaring Hooks + +Arguments to hooks are passed by reference. This means, that you can change the values, and this will be reflected in the insert / update statement. A hook may contain async actions - in this case the hook function should return a promise. + +There are currently three ways to programmatically add hooks: + +```js +// Method 1 via the .init() method +class User extends Model {} +User.init({ + username: DataTypes.STRING, + mood: { + type: DataTypes.ENUM, + values: ['happy', 'sad', 'neutral'] + } +}, { + hooks: { + beforeValidate: (user, options) => { + user.mood = 'happy'; + }, + afterValidate: (user, options) => { + user.username = 'Toni'; + } + }, + sequelize +}); + +// Method 2 via the .addHook() method +User.addHook('beforeValidate', (user, options) => { + user.mood = 'happy'; +}); + +User.addHook('afterValidate', 'someCustomName', (user, options) => { + return Promise.reject(new Error("I'm afraid I can't let you do that!")); +}); + +// Method 3 via the direct method +User.beforeCreate(async (user, options) => { + const hashedPassword = await hashPassword(user.password); + user.password = hashedPassword; +}); + +User.afterValidate('myHookAfter', (user, options) => { + user.username = 'Toni'; +}); +``` + +## Removing hooks + +Only a hook with name param can be removed. + +```js +class Book extends Model {} +Book.init({ + title: DataTypes.STRING +}, { sequelize }); + +Book.addHook('afterCreate', 'notifyUsers', (book, options) => { + // ... +}); + +Book.removeHook('afterCreate', 'notifyUsers'); +``` + +You can have many hooks with same name. Calling `.removeHook()` will remove all of them. + +## Global / universal hooks + +Global hooks are hooks which are run for all models. They can define behaviours that you want for all your models, and are especially useful for plugins. They can be defined in two ways, which have slightly different semantics: + +### Default Hooks (on Sequelize constructor options) + +```js +const sequelize = new Sequelize(..., { + define: { + hooks: { + beforeCreate() { + // Do stuff + } + } + } +}); +``` + +This adds a default hook to all models, which is run if the model does not define its own `beforeCreate` hook: + +```js +const User = sequelize.define('User', {}); +const Project = sequelize.define('Project', {}, { + hooks: { + beforeCreate() { + // Do other stuff + } + } +}); + +await User.create({}); // Runs the global hook +await Project.create({}); // Runs its own hook (because the global hook is overwritten) +``` + +### Permanent Hooks (with `sequelize.addHook`) + +```js +sequelize.addHook('beforeCreate', () => { + // Do stuff +}); +``` + +This hook is always run, whether or not the model specifies its own `beforeCreate` hook. Local hooks are always run before global hooks: + +```js +const User = sequelize.define('User', {}); +const Project = sequelize.define('Project', {}, { + hooks: { + beforeCreate() { + // Do other stuff + } + } +}); + +await User.create({}); // Runs the global hook +await Project.create({}); // Runs its own hook, followed by the global hook +``` + +Permanent hooks may also be defined in the options passed to the Sequelize constructor: + +```js +new Sequelize(..., { + hooks: { + beforeCreate() { + // do stuff + } + } +}); +``` + +Note that the above is not the same as the *Default Hooks* mentioned above. That one uses the `define` option of the constructor. This one does not. + +### Connection Hooks + +Sequelize provides four hooks that are executed immediately before and after a database connection is obtained or released: + +* `sequelize.beforeConnect(callback)` + * The callback has the form `async (config) => /* ... */` +* `sequelize.afterConnect(callback)` + * The callback has the form `async (connection, config) => /* ... */` +* `sequelize.beforeDisconnect(callback)` + * The callback has the form `async (connection) => /* ... */` +* `sequelize.afterDisconnect(callback)` + * The callback has the form `async (connection) => /* ... */` + +These hooks can be useful if you need to asynchronously obtain database credentials, or need to directly access the low-level database connection after it has been created. + +For example, we can asynchronously obtain a database password from a rotating token store, and mutate Sequelize's configuration object with the new credentials: + +```js +sequelize.beforeConnect(async (config) => { + config.password = await getAuthToken(); +}); +``` + +These hooks may *only* be declared as a permanent global hook, as the connection pool is shared by all models. + +## Instance hooks + +The following hooks will emit whenever you're editing a single object: + +* `beforeValidate` +* `afterValidate` / `validationFailed` +* `beforeCreate` / `beforeUpdate` / `beforeSave` / `beforeDestroy` +* `afterCreate` / `afterUpdate` / `afterSave` / `afterDestroy` + +```js +User.beforeCreate(user => { + if (user.accessLevel > 10 && user.username !== "Boss") { + throw new Error("You can't grant this user an access level above 10!"); + } +}); +``` + +The following example will throw an error: + +```js +try { + await User.create({ username: 'Not a Boss', accessLevel: 20 }); +} catch (error) { + console.log(error); // You can't grant this user an access level above 10! +}; +``` + +The following example will be successful: + +```js +const user = await User.create({ username: 'Boss', accessLevel: 20 }); +console.log(user); // user object with username 'Boss' and accessLevel of 20 +``` + +### Model hooks + +Sometimes you'll be editing more than one record at a time by using methods like `bulkCreate`, `update` and `destroy`. The following hooks will emit whenever you're using one of those methods: + +* `YourModel.beforeBulkCreate(callback)` + * The callback has the form `(instances, options) => /* ... */` +* `YourModel.beforeBulkUpdate(callback)` + * The callback has the form `(options) => /* ... */` +* `YourModel.beforeBulkDestroy(callback)` + * The callback has the form `(options) => /* ... */` +* `YourModel.afterBulkCreate(callback)` + * The callback has the form `(instances, options) => /* ... */` +* `YourModel.afterBulkUpdate(callback)` + * The callback has the form `(options) => /* ... */` +* `YourModel.afterBulkDestroy(callback)` + * The callback has the form `(options) => /* ... */` + +Note: methods like `bulkCreate` do not emit individual hooks by default - only the bulk hooks. However, if you want individual hooks to be emitted as well, you can pass the `{ individualHooks: true }` option to the query call. However, this can drastically impact performance, depending on the number of records involved (since, among other things, all instances will be loaded into memory). Examples: + +```js +await Model.destroy({ + where: { accessLevel: 0 }, + individualHooks: true +}); +// This will select all records that are about to be deleted and emit `beforeDestroy` and `afterDestroy` on each instance. + +await Model.update({ username: 'Tony' }, { + where: { accessLevel: 0 }, + individualHooks: true +}); +// This will select all records that are about to be updated and emit `beforeUpdate` and `afterUpdate` on each instance. +``` + +If you use `Model.bulkCreate(...)` with the `updateOnDuplicate` option, changes made in the hook to fields that aren't given in the `updateOnDuplicate` array will not be persisted to the database. However it is possible to change the `updateOnDuplicate` option inside the hook if this is what you want. + +```js +User.beforeBulkCreate((users, options) => { + for (const user of users) { + if (user.isMember) { + user.memberSince = new Date(); + } + } + + // Add `memberSince` to updateOnDuplicate otherwise it won't be persisted + if (options.updateOnDuplicate && !options.updateOnDuplicate.includes('memberSince')) { + options.updateOnDuplicate.push('memberSince'); + } +}); + +// Bulk updating existing users with updateOnDuplicate option +await Users.bulkCreate([ + { id: 1, isMember: true }, + { id: 2, isMember: false } +], { + updateOnDuplicate: ['isMember'] +}); +``` + +## Associations + +For the most part hooks will work the same for instances when being associated. + +### One-to-One and One-to-Many associations + +* When using `add`/`set` mixin methods the `beforeUpdate` and `afterUpdate` hooks will run. + +* The `beforeDestroy` and `afterDestroy` hooks will only be called on associations that have `onDelete: 'CASCADE'` and `hooks: true`. For example: + +```js +class Projects extends Model {} +Projects.init({ + title: DataTypes.STRING +}, { sequelize }); + +class Tasks extends Model {} +Tasks.init({ + title: DataTypes.STRING +}, { sequelize }); + +Projects.hasMany(Tasks, { onDelete: 'CASCADE', hooks: true }); +Tasks.belongsTo(Projects); +``` + +This code will run `beforeDestroy` and `afterDestroy` hooks on the Tasks model. + +Sequelize, by default, will try to optimize your queries as much as possible. When calling cascade on delete, Sequelize will simply execute: + +```sql +DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey +``` + +However, adding `hooks: true` explicitly tells Sequelize that optimization is not of your concern. Then, Sequelize will first perform a `SELECT` on the associated objects and destroy each instance, one by one, in order to be able to properly call the hooks (with the right parameters). + +### Many-to-Many associations + +* When using `add` mixin methods for `belongsToMany` relationships (that will add one or more records to the junction table) the `beforeBulkCreate` and `afterBulkCreate` hooks in the junction model will run. + * If `{ individualHooks: true }` was passed to the call, then each individual hook will also run. + +* When using `remove` mixin methods for `belongsToMany` relationships (that will remove one or more records to the junction table) the `beforeBulkDestroy` and `afterBulkDestroy` hooks in the junction model will run. + * If `{ individualHooks: true }` was passed to the call, then each individual hook will also run. + +If your association is Many-to-Many, you may be interested in firing hooks on the through model when using the `remove` call. Internally, sequelize is using `Model.destroy` resulting in calling the `bulkDestroy` instead of the `before/afterDestroy` hooks on each through instance. + +## Hooks and Transactions + +Many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction *is* specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet: + +```js +User.addHook('afterCreate', async (user, options) => { + // We can use `options.transaction` to perform some other call + // using the same transaction of the call that triggered this hook + await User.update({ mood: 'sad' }, { + where: { + id: user.id + }, + transaction: options.transaction + }); +}); + +await sequelize.transaction(async t => { + await User.create({ + username: 'someguy', + mood: 'happy', + transaction: t + }); +}); +``` + +If we had not included the transaction option in our call to `User.update` in the preceding code, no change would have occurred, since our newly created user does not exist in the database until the pending transaction has been committed. + +### Internal Transactions + +It is very important to recognize that sequelize may make use of transactions internally for certain operations such as `Model.findOrCreate`. If your hook functions execute read or write operations that rely on the object's presence in the database, or modify the object's stored values like the example in the preceding section, you should always specify `{ transaction: options.transaction }`: + +* If a transaction was used, then `{ transaction: options.transaction }` will ensure it is used again; +* Otherwise, `{ transaction: options.transaction }` will be equivalent to `{ transaction: undefined }`, which won't use a transaction (which is ok). + +This way your hooks will always behave correctly. \ No newline at end of file diff --git a/docs/manual/other-topics/indexes.md b/docs/manual/other-topics/indexes.md new file mode 100644 index 000000000000..123ec878457e --- /dev/null +++ b/docs/manual/other-topics/indexes.md @@ -0,0 +1,47 @@ +# Indexes + +Sequelize supports adding indexes to the model definition which will be created on [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync). + +```js +const User = sequelize.define('User', { /* attributes */ }, { + indexes: [ + // Create a unique index on email + { + unique: true, + fields: ['email'] + }, + + // Creates a gin index on data with the jsonb_path_ops operator + { + fields: ['data'], + using: 'gin', + operator: 'jsonb_path_ops' + }, + + // By default index name will be [table]_[fields] + // Creates a multi column partial index + { + name: 'public_by_author', + fields: ['author', 'status'], + where: { + status: 'public' + } + }, + + // A BTREE index with an ordered field + { + name: 'title_index', + using: 'BTREE', + fields: [ + 'author', + { + attribute: 'title', + collate: 'en_US', + order: 'DESC', + length: 5 + } + ] + } + ] +}); +``` \ No newline at end of file diff --git a/docs/manual/legacy.md b/docs/manual/other-topics/legacy.md similarity index 92% rename from docs/manual/legacy.md rename to docs/manual/other-topics/legacy.md index 499f15394ff2..249f5a9638de 100644 --- a/docs/manual/legacy.md +++ b/docs/manual/other-topics/legacy.md @@ -1,4 +1,4 @@ -# Working with legacy tables +# Working with Legacy Tables While out of the box Sequelize will seem a bit opinionated it's easy to work legacy tables and forward proof your application by defining (otherwise generated) table and field names. @@ -21,7 +21,7 @@ User.init({ class MyModel extends Model {} MyModel.init({ userId: { - type: Sequelize.INTEGER, + type: DataTypes.INTEGER, field: 'user_id' } }, { sequelize }); @@ -37,7 +37,7 @@ To define your own primary key: class Collection extends Model {} Collection.init({ uid: { - type: Sequelize.INTEGER, + type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true // Automatically gets converted to SERIAL for postgres } @@ -46,7 +46,7 @@ Collection.init({ class Collection extends Model {} Collection.init({ uuid: { - type: Sequelize.UUID, + type: DataTypes.UUID, primaryKey: true } }, { sequelize }); diff --git a/docs/manual/legal.md b/docs/manual/other-topics/legal.md similarity index 100% rename from docs/manual/legal.md rename to docs/manual/other-topics/legal.md diff --git a/docs/manual/other-topics/migrations.md b/docs/manual/other-topics/migrations.md new file mode 100644 index 000000000000..73d350788210 --- /dev/null +++ b/docs/manual/other-topics/migrations.md @@ -0,0 +1,537 @@ +# Migrations + +Just like you use [version control](https://en.wikipedia.org/wiki/Version_control) systems such as [Git](https://en.wikipedia.org/wiki/Git) to manage changes in your source code, you can use **migrations** to keep track of changes to the database. With migrations you can transfer your existing database into another state and vice versa: Those state transitions are saved in migration files, which describe how to get to the new state and how to revert the changes in order to get back to the old state. + +You will need the [Sequelize Command-Line Interface (CLI)](https://github.com/sequelize/cli). The CLI ships support for migrations and project bootstrapping. + +A Migration in Sequelize is javascript file which exports two functions, `up` and `down`, that dictate how to perform the migration and undo it. You define those functions manually, but you don't call them manually; they will be called automatically by the CLI. In these functions, you should simply perform whatever queries you need, with the help of `sequelize.query` and whichever other methods Sequelize provides to you. There is no extra magic beyond that. + +## Installing the CLI + +To install the Sequelize CLI: + +```text +npm install --save-dev sequelize-cli +``` + +For details see the [CLI GitHub repository](https://github.com/sequelize/cli). + +## Project bootstrapping + +To create an empty project you will need to execute `init` command + +```text +npx sequelize-cli init +``` + +This will create following folders + +- `config`, contains config file, which tells CLI how to connect with database +- `models`, contains all models for your project +- `migrations`, contains all migration files +- `seeders`, contains all seed files + +### Configuration + +Before continuing further we will need to tell the CLI how to connect to the database. To do that let's open default config file `config/config.json`. It looks something like this: + +```json +{ + "development": { + "username": "root", + "password": null, + "database": "database_development", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "test": { + "username": "root", + "password": null, + "database": "database_test", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "production": { + "username": "root", + "password": null, + "database": "database_production", + "host": "127.0.0.1", + "dialect": "mysql" + } +} +``` + +Note that the Sequelize CLI assumes mysql by default. If you're using another dialect, you need to change the content of the `"dialect"` option. + +Now edit this file and set correct database credentials and dialect. The keys of the objects (e.g. "development") are used on `model/index.js` for matching `process.env.NODE_ENV` (When undefined, "development" is a default value). + +Sequelize will use the default connection port for each dialect (for example, for postgres, it is port 5432). If you need to specify a different port, use the `"port"` field (it is not present by default in `config/config.js` but you can simply add it). + +**Note:** _If your database doesn't exist yet, you can just call `db:create` command. With proper access it will create that database for you._ + +## Creating the first Model (and Migration) + +Once you have properly configured CLI config file you are ready to create your first migration. It's as simple as executing a simple command. + +We will use `model:generate` command. This command requires two options: + +- `name`: the name of the model; +- `attributes`: the list of model attributes. + +Let's create a model named `User`. + +```text +npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string +``` + +This will: + +- Create a model file `user` in `models` folder; +- Create a migration file with name like `XXXXXXXXXXXXXX-create-user.js` in `migrations` folder. + +**Note:** _Sequelize will only use Model files, it's the table representation. On the other hand, the migration file is a change in that model or more specifically that table, used by CLI. Treat migrations like a commit or a log for some change in database._ + +## Running Migrations + +Until this step, we haven't inserted anything into the database. We have just created required model and migration files for our first model `User`. Now to actually create that table in database you need to run `db:migrate` command. + +```text +npx sequelize-cli db:migrate +``` + +This command will execute these steps: + +- Will ensure a table called `SequelizeMeta` in database. This table is used to record which migrations have run on the current database +- Start looking for any migration files which haven't run yet. This is possible by checking `SequelizeMeta` table. In this case it will run `XXXXXXXXXXXXXX-create-user.js` migration, which we created in last step. +- Creates a table called `Users` with all columns as specified in its migration file. + +## Undoing Migrations + +Now our table has been created and saved in database. With migration you can revert to old state by just running a command. + +You can use `db:migrate:undo`, this command will revert most recent migration. + +```text +npx sequelize-cli db:migrate:undo +``` + +You can revert back to initial state by undoing all migrations with `db:migrate:undo:all` command. You can also revert back to a specific migration by passing its name in `--to` option. + +```text +npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js +``` + +### Creating the first Seed + +Suppose we want to insert some data into a few tables by default. If we follow up on previous example we can consider creating a demo user for `User` table. + +To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database table with sample data or test data. + +Let's create a seed file which will add a demo user to our `User` table. + +```text +npx sequelize-cli seed:generate --name demo-user +``` + +This command will create a seed file in `seeders` folder. File name will look something like `XXXXXXXXXXXXXX-demo-user.js`. It follows the same `up / down` semantics as the migration files. + +Now we should edit this file to insert demo user to `User` table. + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.bulkInsert('Users', [{ + firstName: 'John', + lastName: 'Doe', + email: 'example@example.com', + createdAt: new Date(), + updatedAt: new Date() + }]); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.bulkDelete('Users', null, {}); + } +}; +``` + +## Running Seeds + +In last step you have create a seed file. It's still not committed to database. To do that we need to run a simple command. + +```text +npx sequelize-cli db:seed:all +``` + +This will execute that seed file and you will have a demo user inserted into `User` table. + +**Note:** _Seeder execution history is not stored anywhere, unlike migrations, which use the `SequelizeMeta` table. If you wish to change this behavior, please read the `Storage` section._ + +## Undoing Seeds + +Seeders can be undone if they are using any storage. There are two commands available for that: + +If you wish to undo the most recent seed: + +```text +npx sequelize-cli db:seed:undo +``` + +If you wish to undo a specific seed: + +```text +npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data +``` + +If you wish to undo all seeds: + +```text +npx sequelize-cli db:seed:undo:all +``` + +## Migration Skeleton + +The following skeleton shows a typical migration file. + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + // logic for transforming into the new state + }, + down: (queryInterface, Sequelize) => { + // logic for reverting the changes + } +} +``` + +We can generate this file using `migration:generate`. This will create `xxx-migration-skeleton.js` in your migration folder. + +```text +npx sequelize-cli migration:generate --name migration-skeleton +``` + +The passed `queryInterface` object can be used to modify the database. The `Sequelize` object stores the available data types such as `STRING` or `INTEGER`. Function `up` or `down` should return a `Promise`. Let's look at an example: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Person', { + name: Sequelize.DataTypes.STRING, + isBetaMember: { + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Person'); + } +}; +``` + +The following is an example of a migration that performs two changes in the database, using an automatically-managed transaction to ensure that all instructions are successfully executed or rolled back in case of failure: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction(t => { + return Promise.all([ + queryInterface.addColumn('Person', 'petName', { + type: Sequelize.DataTypes.STRING + }, { transaction: t }), + queryInterface.addColumn('Person', 'favoriteColor', { + type: Sequelize.DataTypes.STRING, + }, { transaction: t }) + ]); + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction(t => { + return Promise.all([ + queryInterface.removeColumn('Person', 'petName', { transaction: t }), + queryInterface.removeColumn('Person', 'favoriteColor', { transaction: t }) + ]); + }); + } +}; +``` + +The next example is of a migration that has a foreign key. You can use references to specify a foreign key: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Person', { + name: Sequelize.DataTypes.STRING, + isBetaMember: { + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + }, + userId: { + type: Sequelize.DataTypes.INTEGER, + references: { + model: { + tableName: 'users', + schema: 'schema' + }, + key: 'id' + }, + allowNull: false + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Person'); + } +} +``` + +The next example is of a migration that uses async/await where you create an unique index on a new column, with a manually-managed transaction: + +```js +module.exports = { + async up(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'Person', + 'petName', + { + type: Sequelize.DataTypes.STRING, + }, + { transaction } + ); + await queryInterface.addIndex( + 'Person', + 'petName', + { + fields: 'petName', + unique: true, + transaction, + } + ); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + async down(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('Person', 'petName', { transaction }); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + } +}; +``` + +### The `.sequelizerc` file + +This is a special configuration file. It lets you specify the following options that you would usually pass as arguments to CLI: + +- `env`: The environment to run the command in +- `config`: The path to the config file +- `options-path`: The path to a JSON file with additional options +- `migrations-path`: The path to the migrations folder +- `seeders-path`: The path to the seeders folder +- `models-path`: The path to the models folder +- `url`: The database connection string to use. Alternative to using --config files +- `debug`: When available show various debug information + +Some scenarios where you can use it: + +- You want to override default path to `migrations`, `models`, `seeders` or `config` folder. +- You want to rename `config.json` to something else like `database.json` + +And a whole lot more. Let's see how you can use this file for custom configuration. + +To begin, let's create the `.sequelizerc` file in the root directory of your project, with the following content: + +```js +// .sequelizerc + +const path = require('path'); + +module.exports = { + 'config': path.resolve('config', 'database.json'), + 'models-path': path.resolve('db', 'models'), + 'seeders-path': path.resolve('db', 'seeders'), + 'migrations-path': path.resolve('db', 'migrations') +}; +``` + +With this config you are telling the CLI to: + +- Use `config/database.json` file for config settings; +- Use `db/models` as models folder; +- Use `db/seeders` as seeders folder; +- Use `db/migrations` as migrations folder. + +### Dynamic configuration + +The configuration file is by default a JSON file called `config.json`. But sometimes you need a dynamic configuration, for example to access environment variables or execute some other code to determine the configuration. + +Thankfully, the Sequelize CLI can read from both `.json` and `.js` files. This can be setup with `.sequelizerc` file. You just have to provide the path to your `.js` file as the `config` option of your exported object: + +```js +const path = require('path'); + +module.exports = { + 'config': path.resolve('config', 'config.js') +} +``` + +Now the Sequelize CLI will load `config/config.js` for getting configuration options. + +An example of `config/config.js` file: + +```js +const fs = require('fs'); + +module.exports = { + development: { + username: 'database_dev', + password: 'database_dev', + database: 'database_dev', + host: '127.0.0.1', + port: 3306, + dialect: 'mysql', + dialectOptions: { + bigNumberStrings: true + } + }, + test: { + username: process.env.CI_DB_USERNAME, + password: process.env.CI_DB_PASSWORD, + database: process.env.CI_DB_NAME, + host: '127.0.0.1', + port: 3306, + dialect: 'mysql', + dialectOptions: { + bigNumberStrings: true + } + }, + production: { + username: process.env.PROD_DB_USERNAME, + password: process.env.PROD_DB_PASSWORD, + database: process.env.PROD_DB_NAME, + host: process.env.PROD_DB_HOSTNAME, + port: process.env.PROD_DB_PORT, + dialect: 'mysql', + dialectOptions: { + bigNumberStrings: true, + ssl: { + ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') + } + } + } +}; +``` + +The example above also shows how to add custom dialect options to the configuration. + +### Using Babel + +To enable more modern constructions in your migrations and seeders, you can simply install `babel-register` and require it at the beginning of `.sequelizerc`: + +```text +npm i --save-dev babel-register +``` + +```js +// .sequelizerc + +require("babel-register"); + +const path = require('path'); + +module.exports = { + 'config': path.resolve('config', 'config.json'), + 'models-path': path.resolve('models'), + 'seeders-path': path.resolve('seeders'), + 'migrations-path': path.resolve('migrations') +} +``` + +Of course, the outcome will depend upon your babel configuration (such as in a `.babelrc` file). Learn more at [babeljs.io](https://babeljs.io). + +### Security tip + +Use environment variables for config settings. This is because secrets such as passwords should never be part of the source code (and especially not committed to version control). + +### Storage + +There are three types of storage that you can use: `sequelize`, `json`, and `none`. + +- `sequelize` : stores migrations and seeds in a table on the sequelize database +- `json` : stores migrations and seeds on a json file +- `none` : does not store any migration/seed + +#### Migration Storage + +By default the CLI will create a table in your database called `SequelizeMeta` containing an entry for each executed migration. To change this behavior, there are three options you can add to the configuration file. Using `migrationStorage`, you can choose the type of storage to be used for migrations. If you choose `json`, you can specify the path of the file using `migrationStoragePath` or the CLI will write to the file `sequelize-meta.json`. If you want to keep the information in the database, using `sequelize`, but want to use a different table, you can change the table name using `migrationStorageTableName`. Also you can define a different schema for the `SequelizeMeta` table by providing the `migrationStorageTableSchema` property. + +```json +{ + "development": { + "username": "root", + "password": null, + "database": "database_development", + "host": "127.0.0.1", + "dialect": "mysql", + + // Use a different storage type. Default: sequelize + "migrationStorage": "json", + + // Use a different file name. Default: sequelize-meta.json + "migrationStoragePath": "sequelizeMeta.json", + + // Use a different table name. Default: SequelizeMeta + "migrationStorageTableName": "sequelize_meta", + + // Use a different schema for the SequelizeMeta table + "migrationStorageTableSchema": "custom_schema" + } +} +``` + +**Note:** _The `none` storage is not recommended as a migration storage. If you decide to use it, be aware of the implications of having no record of what migrations did or didn't run._ + +#### Seed Storage + +By default the CLI will not save any seed that is executed. If you choose to change this behavior (!), you can use `seederStorage` in the configuration file to change the storage type. If you choose `json`, you can specify the path of the file using `seederStoragePath` or the CLI will write to the file `sequelize-data.json`. If you want to keep the information in the database, using `sequelize`, you can specify the table name using `seederStorageTableName`, or it will default to `SequelizeData`. + +```json +{ + "development": { + "username": "root", + "password": null, + "database": "database_development", + "host": "127.0.0.1", + "dialect": "mysql", + // Use a different storage. Default: none + "seederStorage": "json", + // Use a different file name. Default: sequelize-data.json + "seederStoragePath": "sequelizeData.json", + // Use a different table name. Default: SequelizeData + "seederStorageTableName": "sequelize_data" + } +} +``` + +### Configuration Connection String + +As an alternative to the `--config` option with configuration files defining your database, you can use the `--url` option to pass in a connection string. For example: + +```text +npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name' +``` + +### Programmatic usage + +Sequelize has a sister library called [umzug](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks. \ No newline at end of file diff --git a/docs/manual/other-topics/naming-strategies.md b/docs/manual/other-topics/naming-strategies.md new file mode 100644 index 000000000000..f97edd004fcd --- /dev/null +++ b/docs/manual/other-topics/naming-strategies.md @@ -0,0 +1,157 @@ +# Naming Strategies + +## The `underscored` option + +Sequelize provides the `underscored` option for a model. When `true`, this option will set the `field` option on all attributes to the [snake_case](https://en.wikipedia.org/wiki/Snake_case) version of its name. This also applies to foreign keys automatically generated by associations and other automatically generated fields. Example: + +```js +const User = sequelize.define('task', { username: Sequelize.STRING }, { + underscored: true +}); +const Task = sequelize.define('task', { title: Sequelize.STRING }, { + underscored: true +}); +User.hasMany(Task); +Task.belongsTo(User); +``` + +Above we have the models User and Task, both using the `underscored` option. We also have a One-to-Many relationship between them. Also, recall that since `timestamps` is true by default, we should expect the `createdAt` and `updatedAt` fields to be automatically created as well. + +Without the `underscored` option, Sequelize would automatically define: + +* A `createdAt` attribute for each model, pointing to a column named `createdAt` in each table +* An `updatedAt` attribute for each model, pointing to a column named `updatedAt` in each table +* A `userId` attribute in the `Task` model, pointing to a column named `userId` in the task table + +With the `underscored` option enabled, Sequelize will instead define: + +* A `createdAt` attribute for each model, pointing to a column named `created_at` in each table +* An `updatedAt` attribute for each model, pointing to a column named `updated_at` in each table +* A `userId` attribute in the `Task` model, pointing to a column named `user_id` in the task table + +Note that in both cases the fields are still [camelCase](https://en.wikipedia.org/wiki/Camel_case) in the JavaScript side; this option only changes how these fields are mapped to the database itself. The `field` option of every attribute is set to their snake_case version, but the attribute itself remains camelCase. + +This way, calling `sync()` on the above code will generate the following: + +```sql +CREATE TABLE IF NOT EXISTS "users" ( + "id" SERIAL, + "username" VARCHAR(255), + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY ("id") +); +CREATE TABLE IF NOT EXISTS "tasks" ( + "id" SERIAL, + "title" VARCHAR(255), + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "user_id" INTEGER REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + PRIMARY KEY ("id") +); +``` + +## Singular vs. Plural + +At a first glance, it can be confusing whether the singular form or plural form of a name shall be used around in Sequelize. This section aims at clarifying that a bit. + +Recall that Sequelize uses a library called [inflection](https://www.npmjs.com/package/inflection) under the hood, so that irregular plurals (such as `person -> people`) are computed correctly. However, if you're working in another language, you may want to define the singular and plural forms of names directly; sequelize allows you to do this with some options. + +### When defining models + +Models should be defined with the singular form of a word. Example: + +```js +sequelize.define('foo', { name: DataTypes.STRING }); +``` + +Above, the model name is `foo` (singular), and the respective table name is `foos`, since Sequelize automatically gets the plural for the table name. + +### When defining a reference key in a model + +```js +sequelize.define('foo', { + name: DataTypes.STRING, + barId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: "bars", + key: "id" + }, + onDelete: "CASCADE" + }, +}); +``` + +In the above example we are manually defining a key that references another model. It's not usual to do this, but if you have to, you should use the table name there. This is because the reference is created upon the referencced table name. In the example above, the plural form was used (`bars`), assuming that the `bar` model was created with the default settings (making its underlying table automatically pluralized). + +### When retrieving data from eager loading + +When you perform an `include` in a query, the included data will be added to an extra field in the returned objects, according to the following rules: + +* When including something from a single association (`hasOne` or `belongsTo`) - the field name will be the singular version of the model name; +* When including something from a multiple association (`hasMany` or `belongsToMany`) - the field name will be the plural form of the model. + +In short, the name of the field will take the most logical form in each situation. + +Examples: + +```js +// Assuming Foo.hasMany(Bar) +const foo = Foo.findOne({ include: Bar }); +// foo.bars will be an array +// foo.bar will not exist since it doens't make sense + +// Assuming Foo.hasOne(Bar) +const foo = Foo.findOne({ include: Bar }); +// foo.bar will be an object (possibly null if there is no associated model) +// foo.bars will not exist since it doens't make sense + +// And so on. +``` + +### Overriding singulars and plurals when defining aliases + +When defining an alias for an association, instead of using simply `{ as: 'myAlias' }`, you can pass an object to specify the singular and plural forms: + +```js +Project.belongsToMany(User, { + as: { + singular: 'líder', + plural: 'líderes' + } +}); +``` + +If you know that a model will always use the same alias in associations, you can provide the singular and plural forms directly to the model itself: + +```js +const User = sequelize.define('user', { /* ... */ }, { + name: { + singular: 'líder', + plural: 'líderes', + } +}); +Project.belongsToMany(User); +``` + +The mixins added to the user instances will use the correct forms. For example, instead of `project.addUser()`, Sequelize will provide `project.getLíder()`. Also, instead of `project.setUsers()`, Sequelize will provide `project.setLíderes()`. + +Note: recall that using `as` to change the name of the association will also change the name of the foreign key. Therefore it is recommended to also specify the foreign key(s) involved directly in this case. + +```js +// Example of possible mistake +Invoice.belongsTo(Subscription, { as: 'TheSubscription' }); +Subscription.hasMany(Invoice); +``` + +The first call above will establish a foreign key called `theSubscriptionId` on `Invoice`. However, the second call will also establish a foreign key on `Invoice` (since as we know, `hasMany` calls places foreign keys in the target model) - however, it will be named `subscriptionId`. This way you will have both `subscriptionId` and `theSubscriptionId` columns. + +The best approach is to choose a name for the foreign key and place it explicitly in both calls. For example, if `subscription_id` was chosen: + +```js +// Fixed example +Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' }); +Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' }); +``` \ No newline at end of file diff --git a/docs/manual/other-topics/optimistic-locking.md b/docs/manual/other-topics/optimistic-locking.md new file mode 100644 index 000000000000..56b76e5e398f --- /dev/null +++ b/docs/manual/other-topics/optimistic-locking.md @@ -0,0 +1,7 @@ +## Optimistic Locking + +Sequelize has built-in support for optimistic locking through a model instance version count. + +Optimistic locking is disabled by default and can be enabled by setting the `version` property to true in a specific model definition or global model configuration. See [model configuration](models-definition.html#configuration) for more details. + +Optimistic locking allows concurrent access to model records for edits and prevents conflicts from overwriting data. It does this by checking whether another process has made changes to a record since it was read and throws an OptimisticLockError when a conflict is detected. \ No newline at end of file diff --git a/docs/manual/other-topics/other-data-types.md b/docs/manual/other-topics/other-data-types.md new file mode 100644 index 000000000000..fa0561385520 --- /dev/null +++ b/docs/manual/other-topics/other-data-types.md @@ -0,0 +1,192 @@ +# Other Data Types + +Apart from the most common data types mentioned in the Model Basics guide, Sequelize provides several other data types. + +## Ranges (PostgreSQL only) + +```js +DataTypes.RANGE(DataTypes.INTEGER) // int4range +DataTypes.RANGE(DataTypes.BIGINT) // int8range +DataTypes.RANGE(DataTypes.DATE) // tstzrange +DataTypes.RANGE(DataTypes.DATEONLY) // daterange +DataTypes.RANGE(DataTypes.DECIMAL) // numrange +``` + +Since range types have extra information for their bound inclusion/exclusion it's not very straightforward to just use a tuple to represent them in javascript. + +When supplying ranges as values you can choose from the following APIs: + +```js +// defaults to inclusive lower bound, exclusive upper bound +const range = [ + new Date(Date.UTC(2016, 0, 1)), + new Date(Date.UTC(2016, 1, 1)) +]; +// '["2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' + +// control inclusion +const range = [ + { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, + { value: new Date(Date.UTC(2016, 1, 1)), inclusive: true }, +]; +// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]' + +// composite form +const range = [ + { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, + new Date(Date.UTC(2016, 1, 1)), +]; +// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' + +const Timeline = sequelize.define('Timeline', { + range: DataTypes.RANGE(DataTypes.DATE) +}); + +await Timeline.create({ range }); +``` + +However, retrieved range values always come in the form of an array of objects. For example, if the stored value is `("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]`, after a finder query you will get: + +```js +[ + { value: Date, inclusive: false }, + { value: Date, inclusive: true } +] +``` + +You will need to call `reload()` after updating an instance with a range type or use the `returning: true` option. + +### Special Cases + +```js +// empty range: +Timeline.create({ range: [] }); // range = 'empty' + +// Unbounded range: +Timeline.create({ range: [null, null] }); // range = '[,)' +// range = '[,"2016-01-01 00:00:00+00:00")' +Timeline.create({ range: [null, new Date(Date.UTC(2016, 0, 1))] }); + +// Infinite range: +// range = '[-infinity,"2016-01-01 00:00:00+00:00")' +Timeline.create({ range: [-Infinity, new Date(Date.UTC(2016, 0, 1))] }); +``` + +## BLOBs + +```js +DataTypes.BLOB // BLOB (bytea for PostgreSQL) +DataTypes.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL) +DataTypes.BLOB('medium') // MEDIUMBLOB (bytea for PostgreSQL) +DataTypes.BLOB('long') // LONGBLOB (bytea for PostgreSQL) +``` + +The blob datatype allows you to insert data both as strings and as buffers. However, when a blob is retrieved from database with Sequelize, it will always be retrieved as a buffer. + +## ENUMs + +The ENUM is a data type that accepts only a few values, specified as a list. + +```js +DataTypes.ENUM('foo', 'bar') // An ENUM with allowed values 'foo' and 'bar' +``` + +ENUMs can also be specified with the `values` field of the column definition, as follows: + +```js +sequelize.define('foo', { + states: { + type: DataTypes.ENUM, + values: ['active', 'pending', 'deleted'] + } +}); +``` + +## JSON (SQLite, MySQL, MariaDB and PostgreSQL only) + +The `DataTypes.JSON` data type is only supported for SQLite, MySQL, MariaDB and PostgreSQL. However, there is a minimum support for MSSQL (see below). + +### Note for PostgreSQL + +The JSON data type in PostgreSQL stores the value as plain text, as opposed to binary representation. If you simply want to store and retrieve a JSON representation, using JSON will take less disk space and less time to build from its input representation. However, if you want to do any operations on the JSON value, you should prefer the JSONB data type described below. + +### JSONB (PostgreSQL only) + +PostgreSQL also supports a JSONB data type: `DataTypes.JSONB`. It can be queried in three different ways: + +```js +// Nested object +await Foo.findOne({ + where: { + meta: { + video: { + url: { + [Op.ne]: null + } + } + } + } +}); + +// Nested key +await Foo.findOne({ + where: { + "meta.audio.length": { + [Op.gt]: 20 + } + } +}); + +// Containment +await Foo.findOne({ + where: { + meta: { + [Op.contains]: { + site: { + url: 'http://google.com' + } + } + } + } +}); +``` + +### MSSQL + +MSSQL does not have a JSON data type, however it does provide some support for JSON stored as strings through certain functions since SQL Server 2016. Using these functions, you will be able to query the JSON stored in the string, but any returned values will need to be parsed seperately. + +```js +// ISJSON - to test if a string contains valid JSON +await User.findAll({ + where: sequelize.where(sequelize.fn('ISJSON', sequelize.col('userDetails')), 1) +}) + +// JSON_VALUE - extract a scalar value from a JSON string +await User.findAll({ + attributes: [[ sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), 'address line 1']] +}) + +// JSON_VALUE - query a scalar value from a JSON string +await User.findAll({ + where: sequelize.where(sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), '14, Foo Street') +}) + +// JSON_QUERY - extract an object or array +await User.findAll({ + attributes: [[ sequelize.fn('JSON_QUERY', sequelize.col('userDetails'), '$.address'), 'full address']] +}) +``` + +## Others + +```js +DataTypes.ARRAY(/* DataTypes.SOMETHING */) // Defines an array of DataTypes.SOMETHING. PostgreSQL only. + +DataTypes.CIDR // CIDR PostgreSQL only +DataTypes.INET // INET PostgreSQL only +DataTypes.MACADDR // MACADDR PostgreSQL only + +DataTypes.GEOMETRY // Spatial column. PostgreSQL (with PostGIS) or MySQL only. +DataTypes.GEOMETRY('POINT') // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only. +DataTypes.GEOMETRY('POINT', 4326) // Spatial column with geometry type and SRID. PostgreSQL (with PostGIS) or MySQL only. +``` \ No newline at end of file diff --git a/docs/manual/other-topics/query-interface.md b/docs/manual/other-topics/query-interface.md new file mode 100644 index 000000000000..be7f8b2e96c0 --- /dev/null +++ b/docs/manual/other-topics/query-interface.md @@ -0,0 +1,152 @@ +# Query Interface + +An instance of Sequelize uses something called **Query Interface** to communicate to the database in a dialect-agnostic way. Most of the methods you've learned in this manual are implemented with the help of several methods from the query interface. + +The methods from the query interface are therefore lower-level methods; you should use them only if you do not find another way to do it with higher-level APIs from Sequelize. They are, of course, still higher-level than running raw queries directly (i.e., writing SQL by hand). + +This guide shows a few examples, but for the full list of what it can do, and for detailed usage of each method, check the [QueryInterface API](../class/lib/query-interface.js~QueryInterface.html). + +## Obtaining the query interface + +From now on, we will call `queryInterface` the singleton instance of the [QueryInterface](../class/lib/query-interface.js~QueryInterface.html) class, which is available on your Sequelize instance: + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize(/* ... */); +const queryInterface = sequelize.getQueryInterface(); +``` + +## Creating a table + +```js +queryInterface.createTable('Person', { + name: DataTypes.STRING, + isBetaMember: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + } +}); +``` + +Generated SQL (using SQLite): + +```SQL +CREATE TABLE IF NOT EXISTS `Person` ( + `name` VARCHAR(255), + `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0 +); +``` + +**Note:** Consider defining a Model instead and calling `YourModel.sync()` instead, which is a higher-level approach. + +## Adding a column to a table + +```js +queryInterface.addColumn('Person', 'petName', { type: DataTypes.STRING }); +``` + +Generated SQL (using SQLite): + +```sql +ALTER TABLE `Person` ADD `petName` VARCHAR(255); +``` + +## Changing the datatype of a column + +```js +queryInterface.changeColumn('Person', 'foo', { + type: DataTypes.FLOAT, + defaultValue: 3.14, + allowNull: false +}); +``` + +Generated SQL (using MySQL): + +```sql +ALTER TABLE `Person` CHANGE `foo` `foo` FLOAT NOT NULL DEFAULT 3.14; +``` + +## Removing a column + +```js +queryInterface.removeColumn('Person', 'petName', { /* query options */ }); +``` + +Generated SQL (using PostgreSQL): + +```SQL +ALTER TABLE "public"."Person" DROP COLUMN "petName"; +``` + +## Changing and removing columns in SQLite + +SQLite does not support directly altering and removing columns. However, Sequelize will try to work around this by recreating the whole table with the help of a backup table, inspired by [these instructions](https://www.sqlite.org/lang_altertable.html#otheralter). + +For example: + +```js +// Assuming we have a table in SQLite created as follows: +queryInterface.createTable('Person', { + name: DataTypes.STRING, + isBetaMember: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + }, + petName: DataTypes.STRING, + foo: DataTypes.INTEGER +}); + +// And we change a column: +queryInterface.changeColumn('Person', 'foo', { + type: DataTypes.FLOAT, + defaultValue: 3.14, + allowNull: false +}); +``` + +The following SQL calls are generated for SQLite: + +```sql +PRAGMA TABLE_INFO(`Person`); + +CREATE TABLE IF NOT EXISTS `Person_backup` ( + `name` VARCHAR(255), + `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0, + `foo` FLOAT NOT NULL DEFAULT '3.14', + `petName` VARCHAR(255) +); + +INSERT INTO `Person_backup` + SELECT + `name`, + `isBetaMember`, + `foo`, + `petName` + FROM `Person`; + +DROP TABLE `Person`; + +CREATE TABLE IF NOT EXISTS `Person` ( + `name` VARCHAR(255), + `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0, + `foo` FLOAT NOT NULL DEFAULT '3.14', + `petName` VARCHAR(255) +); + +INSERT INTO `Person` + SELECT + `name`, + `isBetaMember`, + `foo`, + `petName` + FROM `Person_backup`; + +DROP TABLE `Person_backup`; +``` + +## Other + +As mentioned in the beginning of this guide, there is a lot more to the Query Interface available in Sequelize! Check the [QueryInterface API](../class/lib/query-interface.js~QueryInterface.html) for a full list of what can be done. \ No newline at end of file diff --git a/docs/manual/read-replication.md b/docs/manual/other-topics/read-replication.md similarity index 58% rename from docs/manual/read-replication.md rename to docs/manual/other-topics/read-replication.md index 10c7166e3bc5..2fe8fdd90b71 100644 --- a/docs/manual/read-replication.md +++ b/docs/manual/other-topics/read-replication.md @@ -1,6 +1,6 @@ -# Read replication +# Read Replication -Sequelize supports read replication, i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the write master, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). +Sequelize supports [read replication](https://en.wikipedia.org/wiki/Replication_%28computing%29#Database_replication), i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the write master, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). ```js const sequelize = new Sequelize('database', null, null, { @@ -8,10 +8,10 @@ const sequelize = new Sequelize('database', null, null, { port: 3306 replication: { read: [ - { host: '8.8.8.8', username: 'read-username', password: 'some-password' }, - { host: '9.9.9.9', username: 'another-username', password: null } + { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, + { host: '9.9.9.9', username: 'read-2-username', password: process.env.READ_DB_2_PW } ], - write: { host: '1.1.1.1', username: 'write-username', password: 'any-password' } + write: { host: '1.1.1.1', username: 'write-username', password: process.env.WRITE_DB_PW } }, pool: { // If you want to override the options used for the read/write pool you can do so here max: 20, diff --git a/docs/manual/resources.md b/docs/manual/other-topics/resources.md similarity index 100% rename from docs/manual/resources.md rename to docs/manual/other-topics/resources.md diff --git a/docs/manual/scopes.md b/docs/manual/other-topics/scopes.md similarity index 51% rename from docs/manual/scopes.md rename to docs/manual/other-topics/scopes.md index 3a21d68a9734..73eb380364af 100644 --- a/docs/manual/scopes.md +++ b/docs/manual/other-topics/scopes.md @@ -1,6 +1,8 @@ # Scopes -Scoping allows you to define commonly used queries that you can easily use later. Scopes can include all the same attributes as regular finders, `where`, `include`, `limit` etc. +Scopes are used to help you reuse code. You can define commonly used queries, specifying options such as `where`, `include`, `limit`, etc. + +This guide concerns model scopes. You might also be interested in the [guide for association scopes](association-scopes.html), which are similar but not the same thing. ## Definition @@ -24,17 +26,17 @@ Project.init({ }, activeUsers: { include: [ - { model: User, where: { active: true }} + { model: User, where: { active: true } } ] }, - random () { + random() { return { where: { someNumber: Math.random() } } }, - accessLevel (value) { + accessLevel(value) { return { where: { accessLevel: { @@ -49,7 +51,7 @@ Project.init({ }); ``` -You can also add scopes after a model has been defined by calling `addScope`. This is especially useful for scopes with includes, where the model in the include might not be defined at the time the other model is being defined. +You can also add scopes after a model has been defined by calling [`YourModel.addScope`](../class/lib/model.js~Model.html#static-method-addScope). This is especially useful for scopes with includes, where the model in the include might not be defined at the time the other model is being defined. The default scope is always applied. This means, that with the model definition above, `Project.findAll()` will create the following query: @@ -60,22 +62,22 @@ SELECT * FROM projects WHERE active = true The default scope can be removed by calling `.unscoped()`, `.scope(null)`, or by invoking another scope: ```js -Project.scope('deleted').findAll(); // Removes the default scope +await Project.scope('deleted').findAll(); // Removes the default scope ``` ```sql SELECT * FROM projects WHERE deleted = true ``` -It is also possible to include scoped models in a scope definition. This allows you to avoid duplicating `include`, `attributes` or `where` definitions. -Using the above example, and invoking the `active` scope on the included User model (rather than specifying the condition directly in that include object): +It is also possible to include scoped models in a scope definition. This allows you to avoid duplicating `include`, `attributes` or `where` definitions. Using the above example, and invoking the `active` scope on the included User model (rather than specifying the condition directly in that include object): ```js -activeUsers: { +// The `activeUsers` scope defined in the example above could also have been defined this way: +Project.addScope('activeUsers', { include: [ - { model: User.scope('active')} + { model: User.scope('active') } ] -} +}); ``` ## Usage @@ -84,12 +86,14 @@ Scopes are applied by calling `.scope` on the model definition, passing the name ```js const DeletedProjects = Project.scope('deleted'); +await DeletedProjects.findAll(); -DeletedProjects.findAll(); -// some time passes - -// let's look for deleted projects again! -DeletedProjects.findAll(); +// The above is equivalent to: +await Project.findAll({ + where: { + deleted: true + } +}); ``` Scopes apply to `.find`, `.findAll`, `.count`, `.update`, `.increment` and `.destroy`. @@ -97,9 +101,11 @@ Scopes apply to `.find`, `.findAll`, `.count`, `.update`, `.increment` and `.des Scopes which are functions can be invoked in two ways. If the scope does not take any arguments it can be invoked as normally. If the scope takes arguments, pass an object: ```js -Project.scope('random', { method: ['accessLevel', 19]}).findAll(); +await Project.scope('random', { method: ['accessLevel', 19] }).findAll(); ``` +Generated SQL: + ```sql SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19 ``` @@ -110,10 +116,12 @@ Several scopes can be applied simultaneously by passing an array of scopes to `. ```js // These two are equivalent -Project.scope('deleted', 'activeUsers').findAll(); -Project.scope(['deleted', 'activeUsers']).findAll(); +await Project.scope('deleted', 'activeUsers').findAll(); +await Project.scope(['deleted', 'activeUsers']).findAll(); ``` +Generated SQL: + ```sql SELECT * FROM projects INNER JOIN users ON projects.userId = users.id @@ -124,9 +132,11 @@ AND users.active = true If you want to apply another scope alongside the default scope, pass the key `defaultScope` to `.scope`: ```js -Project.scope('defaultScope', 'deleted').findAll(); +await Project.scope('defaultScope', 'deleted').findAll(); ``` +Generated SQL: + ```sql SELECT * FROM projects WHERE active = true AND deleted = true ``` @@ -134,28 +144,26 @@ SELECT * FROM projects WHERE active = true AND deleted = true When invoking several scopes, keys from subsequent scopes will overwrite previous ones (similarly to [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)), except for `where` and `include`, which will be merged. Consider two scopes: ```js -{ - scope1: { - where: { - firstName: 'bob', - age: { - [Op.gt]: 20 - } - }, - limit: 2 +YourMode.addScope('scope1', { + where: { + firstName: 'bob', + age: { + [Op.gt]: 20 + } }, - scope2: { - where: { - age: { - [Op.gt]: 30 - } - }, - limit: 10 - } -} + limit: 2 +}); +YourMode.addScope('scope2', { + where: { + age: { + [Op.gt]: 30 + } + }, + limit: 10 +}); ``` -Calling `.scope('scope1', 'scope2')` will yield the following query +Using `.scope('scope1', 'scope2')` will yield the following WHERE clause: ```sql WHERE firstName = 'bob' AND age > 30 LIMIT 10 @@ -175,6 +183,8 @@ Project.scope('deleted').findAll({ }) ``` +Generated where clause: + ```sql WHERE deleted = true AND firstName = 'john' ``` @@ -185,17 +195,13 @@ Here the `deleted` scope is merged with the finder. If we were to pass `where: { Includes are merged recursively based on the models being included. This is a very powerful merge, added on v5, and is better understood with an example. -Consider four models: Foo, Bar, Baz and Qux, with has-many associations as follows: +Consider the models `Foo`, `Bar`, `Baz` and `Qux`, with One-to-Many associations as follows: ```js -class Foo extends Model {} -class Bar extends Model {} -class Baz extends Model {} -class Qux extends Model {} -Foo.init({ name: Sequelize.STRING }, { sequelize }); -Bar.init({ name: Sequelize.STRING }, { sequelize }); -Baz.init({ name: Sequelize.STRING }, { sequelize }); -Qux.init({ name: Sequelize.STRING }, { sequelize }); +const Foo = sequelize.define('Foo', { name: Sequelize.STRING }); +const Bar = sequelize.define('Bar', { name: Sequelize.STRING }); +const Baz = sequelize.define('Baz', { name: Sequelize.STRING }); +const Qux = sequelize.define('Qux', { name: Sequelize.STRING }); Foo.hasMany(Bar, { foreignKey: 'fooId' }); Bar.hasMany(Baz, { foreignKey: 'barId' }); Baz.hasMany(Qux, { foreignKey: 'bazId' }); @@ -204,126 +210,75 @@ Baz.hasMany(Qux, { foreignKey: 'bazId' }); Now, consider the following four scopes defined on Foo: ```js -{ - includeEverything: { - include: { - model: this.Bar, - include: [{ - model: this.Baz, - include: this.Qux - }] - } - }, - limitedBars: { +Foo.addScope('includeEverything', { + include: { + model: Bar, include: [{ - model: this.Bar, - limit: 2 + model: Baz, + include: Qux }] - }, - limitedBazs: { + } +}); + +Foo.addScope('limitedBars', { + include: [{ + model: Bar, + limit: 2 + }] +}); + +Foo.addScope('limitedBazs', { + include: [{ + model: Bar, include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - limit: 2 - }] + model: Baz, + limit: 2 }] - }, - excludeBazName: { + }] +}); + +Foo.addScope('excludeBazName', { + include: [{ + model: Bar, include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - attributes: { - exclude: ['name'] - } - }] + model: Baz, + attributes: { + exclude: ['name'] + } }] - } -} + }] +}); ``` These four scopes can be deeply merged easily, for example by calling `Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll()`, which would be entirely equivalent to calling the following: ```js -Foo.findAll({ +await Foo.findAll({ include: { - model: this.Bar, + model: Bar, limit: 2, include: [{ - model: this.Baz, + model: Baz, limit: 2, attributes: { exclude: ['name'] }, - include: this.Qux + include: Qux }] } }); + +// The above is equivalent to: +await Foo.scope([ + 'includeEverything', + 'limitedBars', + 'limitedBazs', + 'excludeBazName' +]).findAll(); ``` Observe how the four scopes were merged into one. The includes of scopes are merged based on the model being included. If one scope includes model A and another includes model B, the merged result will include both models A and B. On the other hand, if both scopes include the same model A, but with different options (such as nested includes or other attributes), those will be merged recursively, as shown above. The merge illustrated above works in the exact same way regardless of the order applied to the scopes. The order would only make a difference if a certain option was set by two different scopes - which is not the case of the above example, since each scope does a different thing. -This merge strategy also works in the exact same way with options passed to `.findAll`, `.findOne` and the like. - -## Associations - -Sequelize has two different but related scope concepts in relation to associations. The difference is subtle but important: - -* **Association scopes** Allow you to specify default attributes when getting and setting associations - useful when implementing polymorphic associations. This scope is only invoked on the association between the two models, when using the `get`, `set`, `add` and `create` associated model functions -* **Scopes on associated models** Allows you to apply default and other scopes when fetching associations, and allows you to pass a scoped model when creating associations. These scopes both apply to regular finds on the model and to find through the association. - -As an example, consider the models Post and Comment. Comment is associated to several other models (Image, Video etc.) and the association between Comment and other models is polymorphic, which means that Comment stores a `commentable` column, in addition to the foreign key `commentable_id`. - -The polymorphic association can be implemented with an _association scope_ : - -```js -this.Post.hasMany(this.Comment, { - foreignKey: 'commentable_id', - scope: { - commentable: 'post' - } -}); -``` - -When calling `post.getComments()`, this will automatically add `WHERE commentable = 'post'`. Similarly, when adding new comments to a post, `commentable` will automagically be set to `'post'`. The association scope is meant to live in the background without the programmer having to worry about it - it cannot be disabled. For a more complete polymorphic example, see [Association scopes](associations.html#scopes) - -Consider then, that Post has a default scope which only shows active posts: `where: { active: true }`. This scope lives on the associated model (Post), and not on the association like the `commentable` scope did. Just like the default scope is applied when calling `Post.findAll()`, it is also applied when calling `User.getPosts()` - this will only return the active posts for that user. - -To disable the default scope, pass `scope: null` to the getter: `User.getPosts({ scope: null })`. Similarly, if you want to apply other scopes, pass an array like you would to `.scope`: - -```js -User.getPosts({ scope: ['scope1', 'scope2']}); -``` - -If you want to create a shortcut method to a scope on an associated model, you can pass the scoped model to the association. Consider a shortcut to get all deleted posts for a user: - -```js -class Post extends Model {} -Post.init(attributes, { - defaultScope: { - where: { - active: true - } - }, - scopes: { - deleted: { - where: { - deleted: true - } - } - }, - sequelize, -}); - -User.hasMany(Post); // regular getPosts association -User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' }); - -``` - -```js -User.getPosts(); // WHERE active = true -User.getDeletedPosts(); // WHERE deleted = true -``` +This merge strategy also works in the exact same way with options passed to `.findAll`, `.findOne` and the like. \ No newline at end of file diff --git a/docs/manual/other-topics/sub-queries.md b/docs/manual/other-topics/sub-queries.md new file mode 100644 index 000000000000..213e4ec3a1ab --- /dev/null +++ b/docs/manual/other-topics/sub-queries.md @@ -0,0 +1,164 @@ +# Sub Queries + +Consider you have two models, `Post` and `Reaction`, with a One-to-Many relationship set up, so that one post has many reactions: + +```js +const Post = sequelize.define('post', { + content: DataTypes.STRING +}, { timestamps: false }); + +const Reaction = sequelize.define('reaction', { + type: DataTypes.STRING +}, { timestamps: false }); + +Post.hasMany(Reaction); +Reaction.belongsTo(Post); +``` + +*Note: we have disabled timestamps just to have shorter queries for the next examples.* + +Let's fill our tables with some data: + +```js +async function makePostWithReactions(content, reactionTypes) { + const post = await Post.create({ content }); + await Reaction.bulkCreate( + reactionTypes.map(type => ({ type, postId: post.id })) + ); + return post; +} + +await makePostWithReactions('Hello World', [ + 'Like', 'Angry', 'Laugh', 'Like', 'Like', 'Angry', 'Sad', 'Like' +]); +await makePostWithReactions('My Second Post', [ + 'Laugh', 'Laugh', 'Like', 'Laugh' +]); +``` + +Now, we are ready for examples of the power of subqueries. + +Let's say we wanted to compute via SQL a `laughReactionsCount` for each post. We can achieve that with a sub-query, such as the following: + +```sql +SELECT + *, + ( + SELECT COUNT(*) + FROM reactions AS reaction + WHERE + reaction.postId = post.id + AND + reaction.type = "Laugh" + ) AS laughReactionsCount +FROM posts AS post +``` + +If we run the above raw SQL query through Sequelize, we get: + +```json +[ + { + "id": 1, + "content": "Hello World", + "laughReactionsCount": 1 + }, + { + "id": 2, + "content": "My Second Post", + "laughReactionsCount": 3 + } +] +``` + +So how can we achieve that with more help from Sequelize, without having to write the whole raw query by hand? + +The answer: by combining the `attributes` option of the finder methods (such as `findAll`) with the `sequelize.literal` utility function, that allows you to directly insert arbitrary content into the query without any automatic escaping. + +This means that Sequelize will help you with the main, larger query, but you will still have to write that sub-query by yourself: + +```js +Post.findAll({ + attributes: { + include: [ + [ + // Note the wrapping parentheses in the call below! + sequelize.literal(`( + SELECT COUNT(*) + FROM reactions AS reaction + WHERE + reaction.postId = post.id + AND + reaction.type = "Laugh" + )`), + 'laughReactionsCount' + ] + ] + } +}); +``` + +*Important Note: Since `sequelize.literal` inserts arbitrary content without escaping to the query, it deserves very special attention since it may be a source of (major) security vulnerabilities. It should not be used on user-generated content.* However, here, we are using `sequelize.literal` with a fixed string, carefully written by us (the coders). This is ok, since we know what we are doing. + +The above gives the following output: + +```json +[ + { + "id": 1, + "content": "Hello World", + "laughReactionsCount": 1 + }, + { + "id": 2, + "content": "My Second Post", + "laughReactionsCount": 3 + } +] +``` + +Success! + +## Using sub-queries for complex ordering + +This idea can be used to enable complex ordering, such as ordering posts by the number of laugh reactions they have: + +```js +Post.findAll({ + attributes: { + include: [ + [ + sequelize.literal(`( + SELECT COUNT(*) + FROM reactions AS reaction + WHERE + reaction.postId = post.id + AND + reaction.type = "Laugh" + )`), + 'laughReactionsCount' + ] + ] + }, + order: [ + [sequelize.literal('laughReactionsCount'), 'DESC'] + ] +}); +``` + +Result: + +```json +[ + { + "id": 2, + "content": "My Second Post", + "laughReactionsCount": 3 + }, + { + "id": 1, + "content": "Hello World", + "laughReactionsCount": 1 + } +] +``` \ No newline at end of file diff --git a/docs/manual/other-topics/transactions.md b/docs/manual/other-topics/transactions.md new file mode 100644 index 000000000000..8a862fef0c11 --- /dev/null +++ b/docs/manual/other-topics/transactions.md @@ -0,0 +1,311 @@ +# Transactions + +Sequelize does not use [transactions](https://en.wikipedia.org/wiki/Database_transaction) by default. However, for production-ready usage of Sequelize, you should definitely configure Sequelize to use transactions. + +Sequelize supports two ways of using transactions: + +1. **Unmanaged transactions:** Committing and rolling back the transaction should be done manually by the user (by calling the appropriate Sequelize methods). + +2. **Managed transactions**: Sequelize will automatically rollback the transaction if any error is thrown, or commit the transaction otherwise. Also, if CLS (Continuation Local Storage) is enabled, all queries within the transaction callback will automatically receive the transaction object. + +## Unmanaged transactions + +Let's start with an example: + +```js +// First, we start a transaction and save it into a variable +const t = await sequelize.transaction(); + +try { + + // Then, we do some calls passing this transaction as an option: + + const user = await User.create({ + firstName: 'Bart', + lastName: 'Simpson' + }, { transaction: t }); + + await user.addSibling({ + firstName: 'Lisa', + lastName: 'Simpson' + }, { transaction: t }); + + // If the execution reaches this line, no errors were thrown. + // We commit the transaction. + await t.commit(); + +} catch (error) { + + // If the execution reaches this line, an error was thrown. + // We rollback the transaction. + await t.rollback(); + +} +``` + +As shown above, the *unmanaged transaction* approach requires that you commit and rollback the transaction manually, when necessary. + +## Managed transactions + +Managed transactions handle committing or rolling back the transaction automatically. You start a managed transaction by passing a callback to `sequelize.transaction`. This callback can be `async` (and usually is). + +The following will happen in this case: + +* Sequelize will automatically start a transaction and obtain a transaction object `t` +* Then, Sequelize will execute the callback you provided, passing `t` into it +* If your callback throws, Sequelize will automatically rollback the transaction +* If your callback succeeds, Sequelize will automatically commit the transaction +* Only then the `sequelize.transaction` call will settle: + * Either resolving with the resolution of your callback + * Or, if your callback throws, rejecting with the thrown error + +Example code: + +```js +try { + + const result = await sequelize.transaction(async (t) => { + + const user = await User.create({ + firstName: 'Abraham', + lastName: 'Lincoln' + }, { transaction: t }); + + await user.setShooter({ + firstName: 'John', + lastName: 'Boothe' + }, { transaction: t }); + + return user; + + }); + + // If the execution reaches this line, the transaction has been committed successfully + // `result` is whatever was returned from the transaction callback (the `user`, in this case) + +} catch (error) { + + // If the execution reaches this line, an error occurred. + // The transaction has already been rolled back automatically by Sequelize! + +} +``` + +Note that `t.commit()` and `t.rollback()` were not called directly (which is correct). + +### Throw errors to rollback + +When using the managed transaction you should *never* commit or rollback the transaction manually. If all queries are successful (in the sense of not throwing any error), but you still want to rollback the transaction, you should throw an error yourself: + +```js +await sequelize.transaction(t => { + const user = await User.create({ + firstName: 'Abraham', + lastName: 'Lincoln' + }, { transaction: t }); + + // Woops, the query was successful but we still want to roll back! + // We throw an error manually, so that Sequelize handles everything automatically. + throw new Error(); +}); +``` + +### Automatically pass transactions to all queries + +In the examples above, the transaction is still manually passed, by passing `{ transaction: t }` as the second argument. To automatically pass the transaction to all queries you must install the [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) (CLS) module and instantiate a namespace in your own code: + +```js +const cls = require('cls-hooked'); +const namespace = cls.createNamespace('my-very-own-namespace'); +``` + +To enable CLS you must tell sequelize which namespace to use by using a static method of the sequelize constructor: + +```js +const Sequelize = require('sequelize'); +Sequelize.useCLS(namespace); + +new Sequelize(....); +``` + +Notice, that the `useCLS()` method is on the *constructor*, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances. + +CLS works like a thread-local storage for callbacks. What this means in practice is that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the `transaction` property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time: + +```js +sequelize.transaction((t1) => { + namespace.get('transaction') === t1; // true +}); + +sequelize.transaction((t2) => { + namespace.get('transaction') === t2; // true +}); +``` + +In most case you won't need to access `namespace.get('transaction')` directly, since all queries will automatically look for a transaction on the namespace: + +```js +sequelize.transaction((t1) => { + // With CLS enabled, the user will be created inside the transaction + return User.create({ name: 'Alice' }); +}); +``` + +## Concurrent/Partial transactions + +You can have concurrent transactions within a sequence of queries or have some of them excluded from any transactions. Use the `transaction` option to control which transaction a query belongs to: + +**Note:** *SQLite does not support more than one transaction at the same time.* + +### With CLS enabled + +```js +sequelize.transaction((t1) => { + return sequelize.transaction((t2) => { + // With CLS enabled, queries here will by default use t2. + // Pass in the `transaction` option to define/alter the transaction they belong to. + return Promise.all([ + User.create({ name: 'Bob' }, { transaction: null }), + User.create({ name: 'Mallory' }, { transaction: t1 }), + User.create({ name: 'John' }) // this would default to t2 + ]); + }); +}); +``` + +## Passing options + +The `sequelize.transaction` method accepts options. + +For unmanaged transactions, just use `sequelize.transaction(options)`. + +For managed transactions, use `sequelize.transaction(options, callback)`. + +## Isolation levels + +The possible isolations levels to use when starting a transaction: + +```js +const { Transaction } = require('sequelize'); + +// The following are valid isolation levels: +Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" +Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" +Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" +Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE" +``` + +By default, sequelize uses the isolation level of the database. If you want to use a different isolation level, pass in the desired level as the first argument: + +```js +const { Transaction } = require('sequelize'); + +await sequelize.transaction({ + isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE +}, async (t) => { + // Your code +}); +``` + +You can also overwrite the `isolationLevel` setting globally with an option in the Sequelize constructor: + +```js +const { Sequelize, Transaction } = require('sequelize'); + +const sequelize = new Sequelize('sqlite::memory:', { + isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE +}); +``` + +**Note for MSSQL:** _The `SET ISOLATION LEVEL` queries are not logged since the specified `isolationLevel` is passed directly to `tedious`._ + +## Usage with other sequelize methods + +The `transaction` option goes with most other options, which are usually the first argument of a method. + +For methods that take values, like `.create`, `.update()`, etc. `transaction` should be passed to the option in the second argument. + +If unsure, refer to the API documentation for the method you are using to be sure of the signature. + +Examples: + +```js +await User.create({ name: 'Foo Bar' }, { transaction: t }); + +await User.findAll({ + where: { + name: 'Foo Bar' + }, + transaction: t +}); +``` + +## The `afterCommit` hook + +A `transaction` object allows tracking if and when it is committed. + +An `afterCommit` hook can be added to both managed and unmanaged transaction objects: + +```js +// Managed transaction: +await sequelize.transaction(async (t) => { + t.afterCommit(() => { + // Your logic + }); +}); + +// Unmanaged transaction: +const t = await sequelize.transaction(); +t.afterCommit(() => { + // Your logic +}); +await t.commit(); +``` + +The callback passed to `afterCommit` can be `async`. In this case: + +* For a managed transaction: the `sequelize.transaction` call will wait for it before settling; +* For an unmanaged transaction: the `t.commit` call will wait for it before settling. + +Notes: + +* The `afterCommit` hook is not raised if the transaction is rolled back; +* The `afterCommit` hook does not modify the return value of the transaction (unlike most hooks) + +You can use the `afterCommit` hook in conjunction with model hooks to know when a instance is saved and available outside of a transaction + +```js +User.afterSave((instance, options) => { + if (options.transaction) { + // Save done within a transaction, wait until transaction is committed to + // notify listeners the instance has been saved + options.transaction.afterCommit(() => /* Notify */) + return; + } + // Save done outside a transaction, safe for callers to fetch the updated model + // Notify +}); +``` + +## Locks + +Queries within a `transaction` can be performed with locks: + +```js +return User.findAll({ + limit: 1, + lock: true, + transaction: t1 +}); +``` + +Queries within a transaction can skip locked rows: + +```js +return User.findAll({ + limit: 1, + lock: true, + skipLocked: true, + transaction: t2 +}); +``` diff --git a/docs/manual/typescript.md b/docs/manual/other-topics/typescript.md similarity index 98% rename from docs/manual/typescript.md rename to docs/manual/other-topics/typescript.md index 11daa0ba157b..c478b9ac95d3 100644 --- a/docs/manual/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -173,13 +173,10 @@ const MyDefineModel = sequelize.define('MyDefineModel', { } }); -function stuffTwo() { - MyDefineModel.findByPk(1, { +async function stuffTwo() { + const myModel = await MyDefineModel.findByPk(1, { rejectOnEmpty: true, - }) - .then(myModel => { - console.log(myModel.id); }); + console.log(myModel.id); } - ``` diff --git a/docs/manual/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md similarity index 81% rename from docs/manual/upgrade-to-v6.md rename to docs/manual/other-topics/upgrade-to-v6.md index 1958aa3a209c..563f8fc6ccdf 100644 --- a/docs/manual/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -1,12 +1,12 @@ # Upgrade to v6 -Sequelize v6 is the next major release after v5 +Sequelize v6 is the next major release after v5. Below is a list of breaking changes to help you upgrade. ## Breaking Changes ### Support for Node 10 and up -Sequelize v6 will only support Node 10 and up [#10821](https://github.com/sequelize/sequelize/issues/10821) +Sequelize v6 will only support Node 10 and up [#10821](https://github.com/sequelize/sequelize/issues/10821). ### CLS @@ -20,28 +20,28 @@ You should now use [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) packag Sequelize.useCLS(namespace); ``` -Bluebird [now supports](https://github.com/petkaantonov/bluebird/issues/1403) `async_hooks`. This configuration will automatically be enabled when invoking `Sequelize.useCLS`. Thus all promises should maintain CLS context without `cls-bluebird` patching. +[Bluebird now supports `async_hooks`](https://github.com/petkaantonov/bluebird/issues/1403). This configuration will automatically be enabled when invoking `Sequelize.useCLS`. This way, using [`cls-bluebird`](https://www.npmjs.com/package/cls-bluebird) is no longer necessary. ### Model -**`options.returning`** +#### `options.returning` -Option `returning: true` will no longer return attributes that are not defined in the model. Old behavior can be restored by using `returning: ['*']` +Option `returning: true` will no longer return attributes that are not defined in the model. Old behavior can be achieved by using `returning: ['*']` instead. -**`Model.changed()`** +#### `Model.changed()` -This method now tests for equality with `_.isEqual` and is now deep aware. Modifying nested value for JSON object won't mark them as changed, because it is still the same object. +This method now tests for equality with [`_.isEqual`](https://lodash.com/docs/4.17.15#isEqual) and is now deep aware for JSON objects. Modifying a nested value for a JSON object won't mark it as changed (since it is still the same object). ```js const instance = await MyModel.findOne(); - instance.myJsonField.a = 1; - console.log(instance.changed()); // logs `false` + instance.myJsonField.someProperty = 12345; // Changed from something else to 12345 + console.log(instance.changed()); // false await instance.save(); // this will not save anything instance.changed('myJsonField', true); - console.log(instance.changed()); // logs `["myJsonField"]` + console.log(instance.changed()); // ['myJsonField'] await instance.save(); // will save ``` diff --git a/docs/manual/whos-using.md b/docs/manual/other-topics/whos-using.md similarity index 100% rename from docs/manual/whos-using.md rename to docs/manual/other-topics/whos-using.md diff --git a/docs/manual/querying.md b/docs/manual/querying.md deleted file mode 100644 index 3a5069ee83e4..000000000000 --- a/docs/manual/querying.md +++ /dev/null @@ -1,530 +0,0 @@ -# Querying - -## Attributes - -To select only some attributes, you can use the `attributes` option. Most often, you pass an array: - -```js -Model.findAll({ - attributes: ['foo', 'bar'] -}); -``` - -```sql -SELECT foo, bar ... -``` - -Attributes can be renamed using a nested array: - -```js -Model.findAll({ - attributes: ['foo', ['bar', 'baz']] -}); -``` - -```sql -SELECT foo, bar AS baz ... -``` - -You can use `sequelize.fn` to do aggregations: - -```js -Model.findAll({ - attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] -}); -``` - -```sql -SELECT COUNT(hats) AS no_hats ... -``` - -When using aggregation function, you must give it an alias to be able to access it from the model. In the example above you can get the number of hats with `instance.get('no_hats')`. - -Sometimes it may be tiresome to list all the attributes of the model if you only want to add an aggregation: - -```js -// This is a tiresome way of getting the number of hats... -Model.findAll({ - attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] -}); - -// This is shorter, and less error prone because it still works if you add / remove attributes -Model.findAll({ - attributes: { include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] } -}); -``` - -```sql -SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ... -``` - -Similarly, it's also possible to remove a selected few attributes: - -```js -Model.findAll({ - attributes: { exclude: ['baz'] } -}); -``` - -```sql -SELECT id, foo, bar, quz ... -``` - -## Where - -Whether you are querying with findAll/find or doing bulk updates/destroys you can pass a `where` object to filter the query. - -`where` generally takes an object from attribute:value pairs, where value can be primitives for equality matches or keyed objects for other operators. - -It's also possible to generate complex AND/OR conditions by nesting sets of `or` and `and` `Operators`. - -### Basics - -```js -const Op = Sequelize.Op; - -Post.findAll({ - where: { - authorId: 2 - } -}); -// SELECT * FROM post WHERE authorId = 2 - -Post.findAll({ - where: { - authorId: 12, - status: 'active' - } -}); -// SELECT * FROM post WHERE authorId = 12 AND status = 'active'; - -Post.findAll({ - where: { - [Op.or]: [{authorId: 12}, {authorId: 13}] - } -}); -// SELECT * FROM post WHERE authorId = 12 OR authorId = 13; - -Post.findAll({ - where: { - authorId: { - [Op.or]: [12, 13] - } - } -}); -// SELECT * FROM post WHERE authorId = 12 OR authorId = 13; - -Post.destroy({ - where: { - status: 'inactive' - } -}); -// DELETE FROM post WHERE status = 'inactive'; - -Post.update({ - updatedAt: null, -}, { - where: { - deletedAt: { - [Op.ne]: null - } - } -}); -// UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL; - -Post.findAll({ - where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6) -}); -// SELECT * FROM post WHERE char_length(status) = 6; -``` - -### Operators - -Sequelize exposes symbol operators that can be used for to create more complex comparisons - - -```js -const Op = Sequelize.Op - -[Op.and]: [{a: 5}, {b: 6}] // (a = 5) AND (b = 6) -[Op.or]: [{a: 5}, {a: 6}] // (a = 5 OR a = 6) -[Op.gt]: 6, // > 6 -[Op.gte]: 6, // >= 6 -[Op.lt]: 10, // < 10 -[Op.lte]: 10, // <= 10 -[Op.ne]: 20, // != 20 -[Op.eq]: 3, // = 3 -[Op.is]: null // IS NULL -[Op.not]: true, // IS NOT TRUE -[Op.between]: [6, 10], // BETWEEN 6 AND 10 -[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 -[Op.in]: [1, 2], // IN [1, 2] -[Op.notIn]: [1, 2], // NOT IN [1, 2] -[Op.like]: '%hat', // LIKE '%hat' -[Op.notLike]: '%hat' // NOT LIKE '%hat' -[Op.iLike]: '%hat' // ILIKE '%hat' (case insensitive) (PG only) -[Op.notILike]: '%hat' // NOT ILIKE '%hat' (PG only) -[Op.startsWith]: 'hat' // LIKE 'hat%' -[Op.endsWith]: 'hat' // LIKE '%hat' -[Op.substring]: 'hat' // LIKE '%hat%' -[Op.regexp]: '^[h|a|t]' // REGEXP/~ '^[h|a|t]' (MySQL/PG only) -[Op.notRegexp]: '^[h|a|t]' // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) -[Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (PG only) -[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) -[Op.like]: { [Op.any]: ['cat', 'hat']} - // LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike -[Op.overlap]: [1, 2] // && [1, 2] (PG array overlap operator) -[Op.contains]: [1, 2] // @> [1, 2] (PG array contains operator) -[Op.contained]: [1, 2] // <@ [1, 2] (PG array contained by operator) -[Op.any]: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only) - -[Op.col]: 'user.organization_id' // = "user"."organization_id", with dialect specific column identifiers, PG in this example -[Op.gt]: { [Op.all]: literal('SELECT 1') } - // > ALL (SELECT 1) -``` - -#### Range Operators - -Range types can be queried with all supported operators. - -Keep in mind, the provided range value can -[define the bound inclusion/exclusion](data-types.html#range-types) -as well. - -```js -// All the above equality and inequality operators plus the following: - -[Op.contains]: 2 // @> '2'::integer (PG range contains element operator) -[Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator) -[Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator) -[Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator) -[Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator) -[Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator) -[Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator) -[Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator) -[Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator) -``` - -#### Combinations - -```js -const Op = Sequelize.Op; - -{ - rank: { - [Op.or]: { - [Op.lt]: 1000, - [Op.eq]: null - } - } -} -// rank < 1000 OR rank IS NULL - -{ - createdAt: { - [Op.lt]: new Date(), - [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000) - } -} -// createdAt < [timestamp] AND createdAt > [timestamp] - -{ - [Op.or]: [ - { - title: { - [Op.like]: 'Boat%' - } - }, - { - description: { - [Op.like]: '%boat%' - } - } - ] -} -// title LIKE 'Boat%' OR description LIKE '%boat%' -``` - -#### Operators Aliases - -Sequelize allows setting specific strings as aliases for operators. With v5 this will give you deprecation warning. - -```js -const Op = Sequelize.Op; -const operatorsAliases = { - $gt: Op.gt -} -const connection = new Sequelize(db, user, pass, { operatorsAliases }) - -[Op.gt]: 6 // > 6 -$gt: 6 // same as using Op.gt (> 6) -``` - -#### Operators security - -By default Sequelize will use Symbol operators. Using Sequelize without any aliases improves security. Not having any string aliases will make it extremely unlikely that operators could be injected but you should always properly validate and sanitize user input. - -Some frameworks automatically parse user input into js objects and if you fail to sanitize your input it might be possible to inject an Object with string operators to Sequelize. - -For better security it is highly advised to use symbol operators from `Sequelize.Op` like `Op.and` / `Op.or` in your code and not depend on any string based operators like `$and` / `$or` at all. You can limit alias your application will need by setting `operatorsAliases` option, remember to sanitize user input especially when you are directly passing them to Sequelize methods. - -```js -const Op = Sequelize.Op; - -//use sequelize without any operators aliases -const connection = new Sequelize(db, user, pass, { operatorsAliases: false }); - -//use sequelize with only alias for $and => Op.and -const connection2 = new Sequelize(db, user, pass, { operatorsAliases: { $and: Op.and } }); -``` - -Sequelize will warn you if you're using the default aliases and not limiting them -if you want to keep using all default aliases (excluding legacy ones) without the warning you can pass the following operatorsAliases option - - -```js -const Op = Sequelize.Op; -const operatorsAliases = { - $eq: Op.eq, - $ne: Op.ne, - $gte: Op.gte, - $gt: Op.gt, - $lte: Op.lte, - $lt: Op.lt, - $not: Op.not, - $in: Op.in, - $notIn: Op.notIn, - $is: Op.is, - $like: Op.like, - $notLike: Op.notLike, - $iLike: Op.iLike, - $notILike: Op.notILike, - $regexp: Op.regexp, - $notRegexp: Op.notRegexp, - $iRegexp: Op.iRegexp, - $notIRegexp: Op.notIRegexp, - $between: Op.between, - $notBetween: Op.notBetween, - $overlap: Op.overlap, - $contains: Op.contains, - $contained: Op.contained, - $adjacent: Op.adjacent, - $strictLeft: Op.strictLeft, - $strictRight: Op.strictRight, - $noExtendRight: Op.noExtendRight, - $noExtendLeft: Op.noExtendLeft, - $and: Op.and, - $or: Op.or, - $any: Op.any, - $all: Op.all, - $values: Op.values, - $col: Op.col -}; - -const connection = new Sequelize(db, user, pass, { operatorsAliases }); -``` - -### JSON - -The JSON data type is supported by the PostgreSQL, SQLite, MySQL and MariaDB dialects only. - -#### PostgreSQL - -The JSON data type in PostgreSQL stores the value as plain text, as opposed to binary representation. If you simply want to store and retrieve a JSON representation, using JSON will take less disk space and less time to build from its input representation. However, if you want to do any operations on the JSON value, you should prefer the JSONB data type described below. - -#### MSSQL - -MSSQL does not have a JSON data type, however it does provide support for JSON stored as strings through certain functions since SQL Server 2016. Using these functions, you will be able to query the JSON stored in the string, but any returned values will need to be parsed seperately. - -```js -// ISJSON - to test if a string contains valid JSON -User.findAll({ - where: sequelize.where(sequelize.fn('ISJSON', sequelize.col('userDetails')), 1) -}) - -// JSON_VALUE - extract a scalar value from a JSON string -User.findAll({ - attributes: [[ sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), 'address line 1']] -}) - -// JSON_VALUE - query a scalar value from a JSON string -User.findAll({ - where: sequelize.where(sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), '14, Foo Street') -}) - -// JSON_QUERY - extract an object or array -User.findAll({ - attributes: [[ sequelize.fn('JSON_QUERY', sequelize.col('userDetails'), '$.address'), 'full address']] -}) -``` - -### JSONB - -JSONB can be queried in three different ways. - -#### Nested object - -```js -{ - meta: { - video: { - url: { - [Op.ne]: null - } - } - } -} -``` - -#### Nested key - -```js -{ - "meta.audio.length": { - [Op.gt]: 20 - } -} -``` - -#### Containment - -```js -{ - "meta": { - [Op.contains]: { - site: { - url: 'http://google.com' - } - } - } -} -``` - -### Relations / Associations - -```js -// Find all projects with a least one task where task.state === project.state -Project.findAll({ - include: [{ - model: Task, - where: { state: Sequelize.col('project.state') } - }] -}) -``` - -## Pagination / Limiting - -```js -// Fetch 10 instances/rows -Project.findAll({ limit: 10 }) - -// Skip 8 instances/rows -Project.findAll({ offset: 8 }) - -// Skip 5 instances and fetch the 5 after that -Project.findAll({ offset: 5, limit: 5 }) -``` - -## Ordering - -`order` takes an array of items to order the query by or a sequelize method. Generally you will want to use a tuple/array of either attribute, direction or just direction to ensure proper escaping. - -```js -Subtask.findAll({ - order: [ - // Will escape title and validate DESC against a list of valid direction parameters - ['title', 'DESC'], - - // Will order by max(age) - sequelize.fn('max', sequelize.col('age')), - - // Will order by max(age) DESC - [sequelize.fn('max', sequelize.col('age')), 'DESC'], - - // Will order by otherfunction(`col1`, 12, 'lalala') DESC - [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], - - // Will order an associated model's created_at using the model name as the association's name. - [Task, 'createdAt', 'DESC'], - - // Will order through an associated model's created_at using the model names as the associations' names. - [Task, Project, 'createdAt', 'DESC'], - - // Will order by an associated model's created_at using the name of the association. - ['Task', 'createdAt', 'DESC'], - - // Will order by a nested associated model's created_at using the names of the associations. - ['Task', 'Project', 'createdAt', 'DESC'], - - // Will order by an associated model's created_at using an association object. (preferred method) - [Subtask.associations.Task, 'createdAt', 'DESC'], - - // Will order by a nested associated model's created_at using association objects. (preferred method) - [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'], - - // Will order by an associated model's created_at using a simple association object. - [{model: Task, as: 'Task'}, 'createdAt', 'DESC'], - - // Will order by a nested associated model's created_at simple association objects. - [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC'] - ] - - // Will order by max age descending - order: sequelize.literal('max(age) DESC') - - // Will order by max age ascending assuming ascending is the default order when direction is omitted - order: sequelize.fn('max', sequelize.col('age')) - - // Will order by age ascending assuming ascending is the default order when direction is omitted - order: sequelize.col('age') - - // Will order randomly based on the dialect (instead of fn('RAND') or fn('RANDOM')) - order: sequelize.random() -}) -``` - -## Table Hint - -`tableHint` can be used to optionally pass a table hint when using mssql. The hint must be a value from `Sequelize.TableHints` and should only be used when absolutely necessary. Only a single table hint is currently supported per query. - -Table hints override the default behavior of mssql query optimizer by specifing certain options. They only affect the table or view referenced in that clause. - -```js -const TableHints = Sequelize.TableHints; - -Project.findAll({ - // adding the table hint NOLOCK - tableHint: TableHints.NOLOCK - // this will generate the SQL 'WITH (NOLOCK)' -}) -``` - -## Index Hints - -`indexHints` can be used to optionally pass index hints when using mysql. The hint type must be a value from `Sequelize.IndexHints` and the values should reference existing indexes. - -Index hints [override the default behavior of the mysql query optimizer](https://dev.mysql.com/doc/refman/5.7/en/index-hints.html). - -```js -Project.findAll({ - indexHints: [ - { type: IndexHints.USE, values: ['index_project_on_name'] } - ], - where: { - id: { - [Op.gt]: 623 - }, - name: { - [Op.like]: 'Foo %' - } - } -}) -``` - -Will generate a mysql query that looks like this: - -```sql -SELECT * FROM Project USE INDEX (index_project_on_name) WHERE name LIKE 'FOO %' AND id > 623; -``` - -`Sequelize.IndexHints` includes `USE`, `FORCE`, and `IGNORE`. - -See [Issue #9421](https://github.com/sequelize/sequelize/issues/9421) for the original API proposal. diff --git a/docs/manual/transactions.md b/docs/manual/transactions.md deleted file mode 100644 index ef22be529060..000000000000 --- a/docs/manual/transactions.md +++ /dev/null @@ -1,251 +0,0 @@ -# Transactions - -Sequelize supports two ways of using transactions: - -1. **Managed**, One which will automatically commit or rollback the transaction based on the result of a promise chain and, (if CLS enabled) pass the transaction to all calls within the callback -2. **Unmanaged**, One which leaves committing, rolling back and passing the transaction to the user - -The key difference is that the managed transaction uses a callback that expects a promise to be returned to it while the unmanaged transaction returns a promise. - -## Managed transaction (auto-callback) - -Managed transactions handle committing or rolling back the transaction automatically. You start a managed transaction by passing a callback to `sequelize.transaction`. - -Notice how the callback passed to `transaction` returns a promise chain, and does not explicitly call `t.commit()` nor `t.rollback()`. If all promises in the returned chain are resolved successfully the transaction is committed. If one or several of the promises are rejected, the transaction is rolled back. - -```js -return sequelize.transaction(t => { - // chain all your queries here. make sure you return them. - return User.create({ - firstName: 'Abraham', - lastName: 'Lincoln' - }, {transaction: t}).then(user => { - return user.setShooter({ - firstName: 'John', - lastName: 'Boothe' - }, {transaction: t}); - }); - -}).then(result => { - // Transaction has been committed - // result is whatever the result of the promise chain returned to the transaction callback -}).catch(err => { - // Transaction has been rolled back - // err is whatever rejected the promise chain returned to the transaction callback -}); -``` - -### Throw errors to rollback - -When using the managed transaction you should _never_ commit or rollback the transaction manually. If all queries are successful, but you still want to rollback the transaction (for example because of a validation failure) you should throw an error to break and reject the chain: - -```js -return sequelize.transaction(t => { - return User.create({ - firstName: 'Abraham', - lastName: 'Lincoln' - }, {transaction: t}).then(user => { - // Woops, the query was successful but we still want to roll back! - throw new Error(); - }); -}); -``` - -### Automatically pass transactions to all queries - -In the examples above, the transaction is still manually passed, by passing `{ transaction: t }` as the second argument. To automatically pass the transaction to all queries you must install the [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) (CLS) module and instantiate a namespace in your own code: - -```js -const cls = require('cls-hooked'); -const namespace = cls.createNamespace('my-very-own-namespace'); -``` - -To enable CLS you must tell sequelize which namespace to use by using a static method of the sequelize constructor: - -```js -const Sequelize = require('sequelize'); -Sequelize.useCLS(namespace); - -new Sequelize(....); -``` - -Notice, that the `useCLS()` method is on the *constructor*, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances. - -CLS works like a thread-local storage for callbacks. What this means in practice is that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the `transaction` property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time: - -```js -sequelize.transaction((t1) => { - namespace.get('transaction') === t1; // true -}); - -sequelize.transaction((t2) => { - namespace.get('transaction') === t2; // true -}); -``` - -In most case you won't need to access `namespace.get('transaction')` directly, since all queries will automatically look for a transaction on the namespace: - -```js -sequelize.transaction((t1) => { - // With CLS enabled, the user will be created inside the transaction - return User.create({ name: 'Alice' }); -}); -``` - -## Concurrent/Partial transactions - -You can have concurrent transactions within a sequence of queries or have some of them excluded from any transactions. Use the `{transaction: }` option to control which transaction a query belong to: - -**Warning:** _SQLite does not support more than one transaction at the same time._ - -### With CLS enabled - -```js -sequelize.transaction((t1) => { - return sequelize.transaction((t2) => { - // With CLS enable, queries here will by default use t2 - // Pass in the `transaction` option to define/alter the transaction they belong to. - return Promise.all([ - User.create({ name: 'Bob' }, { transaction: null }), - User.create({ name: 'Mallory' }, { transaction: t1 }), - User.create({ name: 'John' }) // this would default to t2 - ]); - }); -}); -``` - -## Isolation levels - -The possible isolations levels to use when starting a transaction: - -```js -Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" -Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" -Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" -Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE" -``` - -By default, sequelize uses the isolation level of the database. If you want to use a different isolation level, pass in the desired level as the first argument: - -```js -return sequelize.transaction({ - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE - }, (t) => { - - // your transactions - - }); -``` - -The `isolationLevel` can either be set globally when initializing the Sequelize instance or -locally for every transaction: - -```js -// globally -new Sequelize('db', 'user', 'pw', { - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE -}); - -// locally -sequelize.transaction({ - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE -}); -``` - -**Note:** _The SET ISOLATION LEVEL queries are not logged in case of MSSQL as the specified isolationLevel is passed directly to tedious_ - -## Unmanaged transaction (then-callback) - -Unmanaged transactions force you to manually rollback or commit the transaction. If you don't do that, the transaction will hang until it times out. To start an unmanaged transaction, call `sequelize.transaction()` without a callback (you can still pass an options object) and call `then` on the returned promise. Notice that `commit()` and `rollback()` returns a promise. - -```js -return sequelize.transaction().then(t => { - return User.create({ - firstName: 'Bart', - lastName: 'Simpson' - }, {transaction: t}).then(user => { - return user.addSibling({ - firstName: 'Lisa', - lastName: 'Simpson' - }, {transaction: t}); - }).then(() => { - return t.commit(); - }).catch((err) => { - return t.rollback(); - }); -}); -``` - -## Usage with other sequelize methods - -The `transaction` option goes with most other options, which are usually the first argument of a method. -For methods that take values, like `.create`, `.update()`, etc. `transaction` should be passed to the option in the second argument. -If unsure, refer to the API documentation for the method you are using to be sure of the signature. - -## After commit hook - -A `transaction` object allows tracking if and when it is committed. - -An `afterCommit` hook can be added to both managed and unmanaged transaction objects: - -```js -sequelize.transaction(t => { - t.afterCommit((transaction) => { - // Your logic - }); -}); - -sequelize.transaction().then(t => { - t.afterCommit((transaction) => { - // Your logic - }); - - return t.commit(); -}) -``` - -The function passed to `afterCommit` can optionally return a promise that will resolve before the promise chain -that created the transaction resolves - -`afterCommit` hooks are _not_ raised if a transaction is rolled back - -`afterCommit` hooks do _not_ modify the return value of the transaction, unlike standard hooks - -You can use the `afterCommit` hook in conjunction with model hooks to know when a instance is saved and available outside -of a transaction - -```js -model.afterSave((instance, options) => { - if (options.transaction) { - // Save done within a transaction, wait until transaction is committed to - // notify listeners the instance has been saved - options.transaction.afterCommit(() => /* Notify */) - return; - } - // Save done outside a transaction, safe for callers to fetch the updated model - // Notify -}) -``` - -## Locks - -Queries within a `transaction` can be performed with locks - -```js -return User.findAll({ - limit: 1, - lock: true, - transaction: t1 -}) -``` - -Queries within a transaction can skip locked rows - -```js -return User.findAll({ - limit: 1, - lock: true, - skipLocked: true, - transaction: t2 -}) -``` diff --git a/docs/redirects.json b/docs/redirects.json new file mode 100644 index 000000000000..6701da309294 --- /dev/null +++ b/docs/redirects.json @@ -0,0 +1,4 @@ +{ + "manual/dialects.html": "dialect-specific-things.html", + "manual/instances.html": "model-instances.html" +} \ No newline at end of file diff --git a/docs/redirects/create-redirects.js b/docs/redirects/create-redirects.js new file mode 100644 index 000000000000..5e0e0c68c791 --- /dev/null +++ b/docs/redirects/create-redirects.js @@ -0,0 +1,18 @@ +'use strict'; + +const jetpack = require('fs-jetpack'); +const redirectMap = require('./../redirects.json'); + +function makeBoilerplate(url) { + return ` + + + Redirecting... + + + `; +} + +for (const source of Object.keys(redirectMap)) { + jetpack.write(`esdoc/${source}`, makeBoilerplate(redirectMap[source])); +} \ No newline at end of file diff --git a/docs/transforms/menu-groups.js b/docs/transforms/menu-groups.js index d28627528c8c..3d3667b57c65 100644 --- a/docs/transforms/menu-groups.js +++ b/docs/transforms/menu-groups.js @@ -3,10 +3,24 @@ const _ = require('lodash'); const manualGroups = require('./../manual-groups.json'); -module.exports = function transform($) { - const listItems = $('nav div.manual-toc-root div[data-ice=manual]'); +function extractFileNameFromPath(path) { + if (/\.\w+$/.test(path)) { + return /([^/]*)\.\w+$/.exec(path)[1]; + } + return /[^/]*$/.exec(path)[0]; +} - $(listItems.get(0)).before(` +const hiddenManualNames = manualGroups.__hidden__.map(extractFileNameFromPath); + +function isLinkToHiddenManual(link) { + const linkTargetName = extractFileNameFromPath(link); + return hiddenManualNames.includes(linkTargetName); +} + +module.exports = function transform($, filePath) { + const sidebarManualDivs = $('nav div.manual-toc-root div[data-ice=manual]'); + + $(sidebarManualDivs.get(0)).before(` @@ -14,11 +28,28 @@ module.exports = function transform($) { let count = 0; _.each(manualGroups, (manuals, groupName) => { - $(listItems.get(count)).before(` -
- ${groupName} -
- `); + if (groupName !== '__hidden__') { + const groupTitleElement = $(`
${groupName}
`); + $(sidebarManualDivs.get(count)).before(groupTitleElement); + } count += manuals.length; }); + + // Remove links to hidden manuals + sidebarManualDivs.each(/* @this */ function() { + const link = $(this).find('li.indent-h1').data('link'); + if (isLinkToHiddenManual(link)) { + $(this).remove(); + } + }); + + // Remove previews for hidden manuals in index.html + if (filePath.endsWith('index.html') && $('div.manual-cards').length > 0) { + $('div.manual-card-wrap').each(/* @this */ function() { + const link = $(this).find('a').attr('href'); + if (isLinkToHiddenManual(link)) { + $(this).remove(); + } + }); + } }; \ No newline at end of file diff --git a/lib/model.js b/lib/model.js index 99bca1b2b0cd..ec5d16b20ea1 100644 --- a/lib/model.js +++ b/lib/model.js @@ -834,10 +834,6 @@ class Model { * The table columns are defined by the hash that is given as the first argument. * Each attribute of the hash represents a column. * - * For more about Validations - * - * More examples, Model Definition - * * @example * Project.init({ * columnA: { @@ -860,9 +856,13 @@ class Model { * sequelize.models.modelName // The model will now be available in models under the class name * * @see - * {@link DataTypes} + * Model Basics guide + * + * @see + * Hooks guide + * * @see - * {@link Hooks} + * Validations & Constraints guide * * @param {Object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: * @param {string|DataTypes|Object} attributes.column The description of a database column @@ -1667,7 +1667,7 @@ class Model { * @param {number} [options.limit] Limit for result * @param {number} [options.offset] Offset for result * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string|Object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. See [transaction.LOCK for an example](transaction#lock) + * @param {string|Object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. * @param {boolean} [options.skipLocked] Skip locked rows. Only supported in Postgres. * @param {boolean} [options.raw] Return raw result. See sequelize.query for more information. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -3826,11 +3826,14 @@ class Model { } /** - * Validate this instance, and if the validation passes, persist it to the database. It will only save changed fields, and do nothing if no fields have changed. - * - * On success, the callback will be called with this instance. On validation error, the callback will be called with an instance of `Sequelize.ValidationError`. - * This error will have a property for each of the fields for which validation failed, with the error message for that field. - * + * Validates this instance, and if the validation passes, persists it to the database. + * + * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. + * * @param {Object} [options] save options * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. diff --git a/lib/sequelize.js b/lib/sequelize.js index de50901d6fec..ebd92dbd0932 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -392,9 +392,8 @@ class Sequelize { * * @see * {@link Model.init} for a more comprehensive specification of the `options` and `attributes` objects. - * @see Model definition Manual related to model definition * @see - * {@link DataTypes} For a list of possible data types + * Model Basics guide * * @returns {Model} Newly defined model * diff --git a/package.json b/package.json index 87e21d0ed52d..7e459e3888c5 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "test-docker": "npm run test-docker-unit && npm run test-docker-integration", "test-docker-unit": "npm run test-unit", "test-docker-integration": "env-cmd $npm_package_options_env_cmd npm run test-integration", - "docs": "esdoc -c docs/esdoc-config.js && node docs/run-docs-transforms.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER", + "docs": "rimraf esdoc && esdoc -c docs/esdoc-config.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER && node docs/run-docs-transforms.js && node docs/redirects/create-redirects.js", "teaser": "node scripts/teaser", "test-unit": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.js\"", "test-unit-mariadb": "cross-env DIALECT=mariadb npm run test-unit", diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index cb54bb495229..7a623aa5f682 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2620,11 +2620,13 @@ export abstract class Model extends Hooks { public previous(key: K): this[K]; /** - * Validate this instance, and if the validation passes, persist it to the database. - * - * On success, the callback will be called with this instance. On validation error, the callback will be - * called with an instance of `Sequelize.ValidationError`. This error will have a property for each of the - * fields for which validation failed, with the error message for that field. + * Validates this instance, and if the validation passes, persists it to the database. + * + * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. */ public save(options?: SaveOptions): Promise; From 1c49827f3d5c75471170e9c95795d5d183e25595 Mon Sep 17 00:00:00 2001 From: spinlud Date: Tue, 14 Jan 2020 18:00:02 +0100 Subject: [PATCH 019/414] refactor(types): remove duplicated 'mariadb' dialect type (#11821) --- types/lib/sequelize.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 9bce31458efe..4098ff763f00 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -170,7 +170,7 @@ export interface Config { }; } -export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql' | 'mariadb'; +export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql'; export interface RetryOptions { match?: (RegExp | string | Function)[]; From e1e82ed310a0b7717155519e900d9df5a7130212 Mon Sep 17 00:00:00 2001 From: Dan Rumney Date: Thu, 16 Jan 2020 00:25:29 -0600 Subject: [PATCH 020/414] fix(typings): correct overloaded method order (#11727) --- types/lib/model.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 7a623aa5f682..eca21c33dd91 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1792,26 +1792,26 @@ export abstract class Model extends Hooks { * Search for a single instance by its primary key. This applies LIMIT 1, so the listener will * always be called with a single instance. */ - public static findByPk( - this: { new (): M } & typeof Model, - identifier?: Identifier, - options?: Omit - ): Promise; public static findByPk( this: { new (): M } & typeof Model, identifier: Identifier, options: Omit ): Promise; + public static findByPk( + this: { new (): M } & typeof Model, + identifier?: Identifier, + options?: Omit + ): Promise; /** * Search for a single instance. This applies LIMIT 1, so the listener will always be called with a single * instance. */ + public static findOne(this: { new (): M } & typeof Model, options: NonNullFindOptions): Promise; public static findOne( this: { new (): M } & typeof Model, options?: FindOptions ): Promise; - public static findOne(this: { new (): M } & typeof Model, options: NonNullFindOptions): Promise; /** * Run an aggregation method on the specified field From 4bd2be8f12d99782d956f07c0da08bed74928803 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Fri, 17 Jan 2020 03:30:06 -0300 Subject: [PATCH 021/414] docs(raw-queries): remove outdated info (#11833) --- docs/manual/core-concepts/raw-queries.md | 40 ++++++++++++++++++------ 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/manual/core-concepts/raw-queries.md b/docs/manual/core-concepts/raw-queries.md index 31cedd8d0b7f..eb41d2637b6b 100644 --- a/docs/manual/core-concepts/raw-queries.md +++ b/docs/manual/core-concepts/raw-queries.md @@ -57,17 +57,38 @@ await sequelize.query('SELECT 1', { console.log(await sequelize.query('SELECT * FROM projects', { raw: true })); ``` -## "Dotted" attributes +## "Dotted" attributes and the `nest` option -If an attribute name of the table contains dots, the resulting objects will be nested. This is due to the usage of [dottie.js](https://github.com/mickhansen/dottie.js/) under the hood. See below: +If an attribute name of the table contains dots, the resulting objects can become nested objects by setting the `nest: true` option. This is achieved with [dottie.js](https://github.com/mickhansen/dottie.js/) under the hood. See below: -```js -const rows = await sequelize.query('select 1 as `foo.bar.baz`'); -console.log(JSON.stringify(rows, null, 2)); -``` +* Without `nest: true`: + + ```js + const { QueryTypes } = require('sequelize'); + const records = await sequelize.query('select 1 as `foo.bar.baz`', { + type: QueryTypes.SELECT + }); + console.log(JSON.stringify(records[0], null, 2)); + ``` -```json -[ + ```json + { + "foo.bar.baz": 1 + } + ``` + +* With `nest: true`: + + ```js + const { QueryTypes } = require('sequelize'); + const records = await sequelize.query('select 1 as `foo.bar.baz`', { + nest: true, + type: QueryTypes.SELECT + }); + console.log(JSON.stringify(records[0], null, 2)); + ``` + + ```json { "foo": { "bar": { @@ -75,8 +96,7 @@ console.log(JSON.stringify(rows, null, 2)); } } } -] -``` + ``` ## Replacements From eb9d232ff2bcb3d330bb40a32a65f55a5dc4945b Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 17 Jan 2020 11:32:52 -0500 Subject: [PATCH 022/414] docs(dialect-specific): add MSSQL domain auth example (#11799) --- .../other-topics/dialect-specific-things.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/manual/other-topics/dialect-specific-things.md b/docs/manual/other-topics/dialect-specific-things.md index 324525f8efc6..5950bcf1fe01 100644 --- a/docs/manual/other-topics/dialect-specific-things.md +++ b/docs/manual/other-topics/dialect-specific-things.md @@ -95,6 +95,29 @@ const sequelize = new Sequelize('database', 'username', 'password', { }); ``` +#### MSSQL Domain Account + +In order to connect with a domain account, use the following format. + +```js +const sequelize = new Sequelize('database', null, null, { + dialect: 'mssql', + dialectOptions: { + authentication: { + type: 'ntlm', + options: { + domain: 'yourDomain', + userName: 'username', + password: 'password' + } + }, + options: { + instanceName: 'SQLEXPRESS' + } + } +}) +``` + ## Data type: TIMESTAMP WITHOUT TIME ZONE - PostgreSQL only If you are working with the PostgreSQL `TIMESTAMP WITHOUT TIME ZONE` and you need to parse it to a different timezone, please use the pg library's own parser: From 04d0f9e0c6c99f0ffc6cd1f9444e5516f03b51ff Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 18 Jan 2020 01:39:28 -0300 Subject: [PATCH 023/414] docs(manuals): fix missing models-definition page (#11838) --- docs/manual-groups.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/manual-groups.json b/docs/manual-groups.json index 60e4151f12ed..91bf48cc2753 100644 --- a/docs/manual-groups.json +++ b/docs/manual-groups.json @@ -44,7 +44,8 @@ "__hidden__": [ "moved/associations.md", "moved/data-types.md", + "moved/models-definition.md", "moved/models-usage.md", "moved/querying.md" ] -} \ No newline at end of file +} From 0098de0c5596a79c8bd08c3351b712dad953cf8e Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 18 Jan 2020 11:23:36 +0530 Subject: [PATCH 024/414] build: update dependencies and docs (#11842) --- README.md | 4 +- package-lock.json | 1856 ++++++++++++++++++++++++++------------------- package.json | 24 +- 3 files changed, 1098 insertions(+), 786 deletions(-) diff --git a/README.md b/README.md index dcdee4b0fe68..e463e9f59632 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,12 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) [![Travis Build Status](https://badgen.net/travis/sequelize/sequelize?icon=travis)](https://travis-ci.org/sequelize/sequelize) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/9l1ypgwsp5ij46m3/branch/master?svg=true)](https://ci.appveyor.com/project/sushantdhiman/sequelize/branch/master) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) +[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize/master?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. Sequelize follows [SEMVER](http://semver.org). +Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. Sequelize follows [Semantic Versioning](http://semver.org). New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.org/master). You might also be interested in the [API Reference](https://sequelize.org/master/identifiers). diff --git a/package-lock.json b/package-lock.json index 76d98402b3dd..2e6fb392b484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,21 +5,52 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz", + "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.3", + "@babel/helpers": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "@babel/generator": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", - "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", + "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", "dev": true, "requires": { - "@babel/types": "^7.7.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -34,38 +65,49 @@ } }, "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.7.4" + "@babel/types": "^7.8.3" + } + }, + "@babel/helpers": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.3.tgz", + "integrity": "sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -82,9 +124,9 @@ } }, "@babel/parser": { - "version": "7.7.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.5.tgz", - "integrity": "sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", + "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", "dev": true }, "@babel/runtime": { @@ -105,28 +147,28 @@ } }, "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", + "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.3", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -141,9 +183,9 @@ } }, "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -160,213 +202,171 @@ } }, "@commitlint/cli": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-8.2.0.tgz", - "integrity": "sha512-8fJ5pmytc38yw2QWbTTJmXLfSiWPwMkHH4govo9zJ/+ERPBF2jvlxD/dQvk24ezcizjKc6LFka2edYC4OQ+Dgw==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-8.3.5.tgz", + "integrity": "sha512-6+L0vbw55UEdht71pgWOE55SRgb+8OHcEwGDB234VlIBFGK9P2QOBU7MHiYJ5cjdjCQ0rReNrGjOHmJ99jwf0w==", "dev": true, "requires": { - "@commitlint/format": "^8.2.0", - "@commitlint/lint": "^8.2.0", - "@commitlint/load": "^8.2.0", - "@commitlint/read": "^8.2.0", + "@commitlint/format": "^8.3.4", + "@commitlint/lint": "^8.3.5", + "@commitlint/load": "^8.3.5", + "@commitlint/read": "^8.3.4", "babel-polyfill": "6.26.0", "chalk": "2.4.2", "get-stdin": "7.0.0", - "lodash": "4.17.14", + "lodash": "4.17.15", "meow": "5.0.0", "resolve-from": "5.0.0", "resolve-global": "1.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", - "dev": true - } } }, "@commitlint/config-angular": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-angular/-/config-angular-8.2.0.tgz", - "integrity": "sha512-N1MDHoYwTlWtQwXbDFAvu3pMS0encb0QR29LfEqvR1+2DV2SrdAfCEIX9Wi3DgEo2Ra5uKDDHhYbMuQWV5Jvyg==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/config-angular/-/config-angular-8.3.4.tgz", + "integrity": "sha512-mFg1Yj2xFDBJJyltGP3RLqZOk89HuNK1ttOcRA9lsTjTVVu4MrNv5sQ1LkW645xn4Vy9zgxlB0CrR4LXEg5QpQ==", "dev": true, "requires": { - "@commitlint/config-angular-type-enum": "^8.2.0" + "@commitlint/config-angular-type-enum": "^8.3.4" } }, "@commitlint/config-angular-type-enum": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-angular-type-enum/-/config-angular-type-enum-8.2.0.tgz", - "integrity": "sha512-XjIwB7/sw3MRL3Y8jqmf1FQrwoH/RU6l/UXuSjDYRjFLUqosVPcb7bGjLd22Mpbc0szIpEBys3VPthNY8akTUw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/config-angular-type-enum/-/config-angular-type-enum-8.3.4.tgz", + "integrity": "sha512-V8DJ9G3vd8/g55euhwqNPv9gycqbNGfJsswpga7RP0tetzcekkXeE9fXnFBJklqzXrjwUmP8Of5eEsGmbt39IQ==", "dev": true }, "@commitlint/ensure": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-8.2.0.tgz", - "integrity": "sha512-XZZih/kcRrqK7lEORbSYCfqQw6byfsFbLygRGVdJMlCPGu9E2MjpwCtoj5z7y/lKfUB3MJaBhzn2muJqS1gC6A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-8.3.4.tgz", + "integrity": "sha512-8NW77VxviLhD16O3EUd02lApMFnrHexq10YS4F4NftNoErKbKaJ0YYedktk2boKrtNRf/gQHY/Qf65edPx4ipw==", "dev": true, "requires": { - "lodash": "4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", - "dev": true - } + "lodash": "4.17.15" } }, "@commitlint/execute-rule": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.2.0.tgz", - "integrity": "sha512-9MBRthHaulbWTa8ReG2Oii2qc117NuvzhZdnkuKuYLhker7sUXGFcVhLanuWUKGyfyI2o9zVr/NHsNbCCsTzAA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.3.4.tgz", + "integrity": "sha512-f4HigYjeIBn9f7OuNv5zh2y5vWaAhNFrfeul8CRJDy82l3Y+09lxOTGxfF3uMXKrZq4LmuK6qvvRCZ8mUrVvzQ==", "dev": true }, "@commitlint/format": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-8.2.0.tgz", - "integrity": "sha512-sA77agkDEMsEMrlGhrLtAg8vRexkOofEEv/CZX+4xlANyAz2kNwJvMg33lcL65CBhqKEnRRJRxfZ1ZqcujdKcQ==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-8.3.4.tgz", + "integrity": "sha512-809wlQ/ND6CLZON+w2Rb3YM2TLNDfU2xyyqpZeqzf2reJNpySMSUAeaO/fNDJSOKIsOsR3bI01rGu6hv28k+Nw==", "dev": true, "requires": { "chalk": "^2.0.1" } }, "@commitlint/is-ignored": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-8.2.0.tgz", - "integrity": "sha512-ADaGnKfbfV6KD1pETp0Qf7XAyc75xTy3WJlbvPbwZ4oPdBMsXF0oXEEGMis6qABfU2IXan5/KAJgAFX3vdd0jA==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-8.3.5.tgz", + "integrity": "sha512-Zo+8a6gJLFDTqyNRx53wQi/XTiz8mncvmWf/4oRG+6WRcBfjSSHY7KPVj5Y6UaLy2EgZ0WQ2Tt6RdTDeQiQplA==", "dev": true, "requires": { - "@types/semver": "^6.0.1", - "semver": "6.2.0" + "semver": "6.3.0" }, "dependencies": { "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@commitlint/lint": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-8.2.0.tgz", - "integrity": "sha512-ch9JN8aR37ufdjoWv50jLfvFz9rWMgLW5HEkMGLsM/51gjekmQYS5NJg8S2+6F5+jmralAO7VkUMI6FukXKX0A==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-8.3.5.tgz", + "integrity": "sha512-02AkI0a6PU6rzqUvuDkSi6rDQ2hUgkq9GpmdJqfai5bDbxx2939mK4ZO+7apbIh4H6Pae7EpYi7ffxuJgm+3hQ==", "dev": true, "requires": { - "@commitlint/is-ignored": "^8.2.0", - "@commitlint/parse": "^8.2.0", - "@commitlint/rules": "^8.2.0", + "@commitlint/is-ignored": "^8.3.5", + "@commitlint/parse": "^8.3.4", + "@commitlint/rules": "^8.3.4", "babel-runtime": "^6.23.0", - "lodash": "4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", - "dev": true - } + "lodash": "4.17.15" } }, "@commitlint/load": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.2.0.tgz", - "integrity": "sha512-EV6PfAY/p83QynNd1llHxJiNxKmp43g8+7dZbyfHFbsGOdokrCnoelAVZ+WGgktXwLN/uXyfkcIAxwac015UYw==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.3.5.tgz", + "integrity": "sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw==", "dev": true, "requires": { - "@commitlint/execute-rule": "^8.2.0", - "@commitlint/resolve-extends": "^8.2.0", + "@commitlint/execute-rule": "^8.3.4", + "@commitlint/resolve-extends": "^8.3.5", "babel-runtime": "^6.23.0", "chalk": "2.4.2", "cosmiconfig": "^5.2.0", - "lodash": "4.17.14", + "lodash": "4.17.15", "resolve-from": "^5.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", - "dev": true - } } }, "@commitlint/message": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-8.2.0.tgz", - "integrity": "sha512-LNsSwDLIFgE3nb/Sb1PIluYNy4Q8igdf4tpJCdv5JJDf7CZCZt3ZTglj0YutZZorpRRuHJsVIB2+dI4bVH3bFw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-8.3.4.tgz", + "integrity": "sha512-nEj5tknoOKXqBsaQtCtgPcsAaf5VCg3+fWhss4Vmtq40633xLq0irkdDdMEsYIx8rGR0XPBTukqzln9kAWCkcA==", "dev": true }, "@commitlint/parse": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-8.2.0.tgz", - "integrity": "sha512-vzouqroTXG6QXApkrps0gbeSYW6w5drpUk7QAeZIcaCSPsQXDM8eqqt98ZzlzLJHo5oPNXPX1AAVSTrssvHemA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-8.3.4.tgz", + "integrity": "sha512-b3uQvpUQWC20EBfKSfMRnyx5Wc4Cn778bVeVOFErF/cXQK725L1bYFvPnEjQO/GT8yGVzq2wtLaoEqjm1NJ/Bw==", "dev": true, "requires": { "conventional-changelog-angular": "^1.3.3", - "conventional-commits-parser": "^2.1.0", + "conventional-commits-parser": "^3.0.0", "lodash": "^4.17.11" } }, "@commitlint/read": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-8.2.0.tgz", - "integrity": "sha512-1tBai1VuSQmsOTsvJr3Fi/GZqX3zdxRqYe/yN4i3cLA5S2Y4QGJ5I3l6nGZlKgm/sSelTCVKHltrfWU8s5H7SA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-8.3.4.tgz", + "integrity": "sha512-FKv1kHPrvcAG5j+OSbd41IWexsbLhfIXpxVC/YwQZO+FR0EHmygxQNYs66r+GnhD1EfYJYM4WQIqd5bJRx6OIw==", "dev": true, "requires": { - "@commitlint/top-level": "^8.2.0", + "@commitlint/top-level": "^8.3.4", "@marionebl/sander": "^0.6.0", "babel-runtime": "^6.23.0", - "git-raw-commits": "^1.3.0" + "git-raw-commits": "^2.0.0" } }, "@commitlint/resolve-extends": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.2.0.tgz", - "integrity": "sha512-cwi0HUsDcD502HBP8huXfTkVuWmeo1Fiz3GKxNwMBBsJV4+bKa7QrtxbNpXhVuarX7QjWfNTvmW6KmFS7YK9uw==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.3.5.tgz", + "integrity": "sha512-nHhFAK29qiXNe6oH6uG5wqBnCR+BQnxlBW/q5fjtxIaQALgfoNLHwLS9exzbIRFqwJckpR6yMCfgMbmbAOtklQ==", "dev": true, "requires": { - "@types/node": "^12.0.2", "import-fresh": "^3.0.0", - "lodash": "4.17.14", + "lodash": "4.17.15", "resolve-from": "^5.0.0", "resolve-global": "^1.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.14", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", - "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==", - "dev": true - } } }, "@commitlint/rules": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-8.2.0.tgz", - "integrity": "sha512-FlqSBBP2Gxt5Ibw+bxdYpzqYR6HI8NIBpaTBhAjSEAduQtdWFMOhF0zsgkwH7lHN7opaLcnY2fXxAhbzTmJQQA==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-8.3.4.tgz", + "integrity": "sha512-xuC9dlqD5xgAoDFgnbs578cJySvwOSkMLQyZADb1xD5n7BNcUJfP8WjT9W1Aw8K3Wf8+Ym/ysr9FZHXInLeaRg==", "dev": true, "requires": { - "@commitlint/ensure": "^8.2.0", - "@commitlint/message": "^8.2.0", - "@commitlint/to-lines": "^8.2.0", + "@commitlint/ensure": "^8.3.4", + "@commitlint/message": "^8.3.4", + "@commitlint/to-lines": "^8.3.4", "babel-runtime": "^6.23.0" } }, "@commitlint/to-lines": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-8.2.0.tgz", - "integrity": "sha512-LXTYG3sMenlN5qwyTZ6czOULVcx46uMy+MEVqpvCgptqr/MZcV/C2J+S2o1DGwj1gOEFMpqrZaE3/1R2Q+N8ng==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-8.3.4.tgz", + "integrity": "sha512-5AvcdwRsMIVq0lrzXTwpbbG5fKRTWcHkhn/hCXJJ9pm1JidsnidS1y0RGkb3O50TEHGewhXwNoavxW9VToscUA==", "dev": true }, "@commitlint/top-level": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-8.2.0.tgz", - "integrity": "sha512-Yaw4KmYNy31/HhRUuZ+fupFcDalnfpdu4JGBgGAqS9aBHdMSSWdWqtAaDaxdtWjTZeN3O0sA2gOhXwvKwiDwvw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-8.3.4.tgz", + "integrity": "sha512-nOaeLBbAqSZNpKgEtO6NAxmui1G8ZvLG+0wb4rvv6mWhPDzK1GNZkCd8FUZPahCoJ1iHDoatw7F8BbJLg4nDjg==", "dev": true, "requires": { "find-up": "^4.0.0" @@ -392,9 +392,78 @@ } }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.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" + } + }, + "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 + }, + "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 + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", + "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "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-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -423,6 +492,12 @@ } } }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@marionebl/sander": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@marionebl/sander/-/sander-0.6.1.tgz", @@ -533,9 +608,9 @@ } }, "@octokit/rest": { - "version": "16.35.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.35.0.tgz", - "integrity": "sha512-9ShFqYWo0CLoGYhA1FdtdykJuMzS/9H6vSbbQWDX4pWr4p9v+15MsH/wpd/3fIU+tSxylaNO48+PIHqOkBRx3w==", + "version": "16.37.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.37.0.tgz", + "integrity": "sha512-qLPK9FOCK4iVpn6ghknNuv/gDDxXQG6+JBQvoCwWjQESyis9uemakjzN36nvvp8SCny7JuzHI2RV8ChbV5mYdQ==", "dev": true, "requires": { "@octokit/request": "^5.2.0", @@ -571,9 +646,9 @@ } }, "@semantic-release/commit-analyzer": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-6.3.3.tgz", - "integrity": "sha512-Pyv1ZL2u5AIOY4YbxFCAB5J1PEh5yON8ylbfiPiriDGGW6Uu1U3Y8lysMtWu+FUD5x7tSnyIzhqx0+fxPxqbgw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-7.0.0.tgz", + "integrity": "sha512-t5wMGByv+SknjP2m3rhWN4vmXoQ16g5VFY8iC4/tcbLPvzxK+35xsTIeUsrVFZv3ymdgAQKIr5J3lKjhF/VZZQ==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -581,7 +656,8 @@ "conventional-commits-parser": "^3.0.7", "debug": "^4.0.0", "import-from": "^3.0.0", - "lodash": "^4.17.4" + "lodash": "^4.17.4", + "micromatch": "^3.1.10" }, "dependencies": { "conventional-changelog-angular": { @@ -593,30 +669,6 @@ "compare-func": "^1.3.1", "q": "^1.5.1" } - }, - "conventional-commits-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz", - "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==", - "dev": true, - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.1", - "lodash": "^4.17.15", - "meow": "^5.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0", - "trim-off-newlines": "^1.0.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } } } }, @@ -627,9 +679,9 @@ "dev": true }, "@semantic-release/github": { - "version": "5.5.5", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.5.5.tgz", - "integrity": "sha512-Wo9OIULMRydbq+HpFh9yiLvra1XyEULPro9Tp4T5MQJ0WZyAQ3YQm74IdT8Pe/UmVDq2nfpT1oHrWkwOc4loHg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-6.0.1.tgz", + "integrity": "sha512-4/xMKFe7svbv5ltvBxoqPY8fBSPyllVtnf2RMHaddeRKC8C/7FqakwRDmui7jgC3alVrVsRtz/jdTdZjB4J28Q==", "dev": true, "requires": { "@octokit/rest": "^16.27.0", @@ -640,9 +692,9 @@ "dir-glob": "^3.0.0", "fs-extra": "^8.0.0", "globby": "^10.0.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^3.0.0", - "issue-parser": "^5.0.0", + "http-proxy-agent": "^3.0.0", + "https-proxy-agent": "^4.0.0", + "issue-parser": "^6.0.0", "lodash": "^4.17.4", "mime": "^2.4.3", "p-filter": "^2.0.0", @@ -668,9 +720,9 @@ } }, "globby": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", - "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, "requires": { "@types/glob": "^7.1.1", @@ -698,14 +750,14 @@ } }, "@semantic-release/npm": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-5.3.4.tgz", - "integrity": "sha512-XjITNRA/oOpJ7BfHk/WaOHs1WniYBszTde/bwADjjk1Luacpxg87jbDQVVt/oA3Zlx+MelxACRIEuRiPC5gu8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-6.0.0.tgz", + "integrity": "sha512-aqODzbtWpVHO/keinbBMnZEaN/TkdwQvyDWcT0oNbKFpZwLjNjn+QVItoLekF62FLlXXziu2y6V4wnl9FDnujA==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", - "execa": "^3.2.0", + "execa": "^4.0.0", "fs-extra": "^8.0.0", "lodash": "^4.17.15", "nerf-dart": "^1.0.0", @@ -714,6 +766,7 @@ "rc": "^1.2.8", "read-pkg": "^5.0.0", "registry-auth-token": "^4.0.0", + "semver": "^6.3.0", "tempy": "^0.3.0" }, "dependencies": { @@ -729,9 +782,9 @@ } }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", + "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -741,7 +794,6 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } @@ -773,20 +825,14 @@ "dev": true }, "npm-run-path": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" } }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -817,6 +863,12 @@ "type-fest": "^0.6.0" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -877,21 +929,6 @@ "q": "^1.5.1" } }, - "conventional-commits-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz", - "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==", - "dev": true, - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.1", - "lodash": "^4.17.15", - "meow": "^5.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0", - "trim-off-newlines": "^1.0.0" - } - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -921,9 +958,9 @@ } }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -992,15 +1029,6 @@ "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } } } }, @@ -1104,12 +1132,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "@types/semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-1OzrNb4RuAzIT7wHSsgZRlMBlNsJl+do6UblR7JMW4oB7bbR+uBEYtUh7gEc/jM84GGilh68lSOokyM/zNUlBA==", - "dev": true - }, "@types/validator": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", @@ -1188,13 +1210,10 @@ } }, "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true }, "aggregate-error": { "version": "3.0.1", @@ -1283,12 +1302,12 @@ } }, "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { - "default-require-extensions": "^2.0.0" + "default-require-extensions": "^3.0.0" } }, "aproba": { @@ -1815,15 +1834,15 @@ } }, "caching-transform": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", - "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { - "hasha": "^3.0.0", - "make-dir": "^2.0.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.4.2" + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" } }, "caller-callsite": { @@ -2305,6 +2324,12 @@ "through2": "^3.0.0" }, "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "through2": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", @@ -2327,35 +2352,27 @@ } }, "conventional-commits-parser": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", - "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz", + "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==", "dev": true, "requires": { "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", - "lodash": "^4.2.1", - "meow": "^4.0.0", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^5.0.0", "split2": "^2.0.0", - "through2": "^2.0.0", + "through2": "^3.0.0", "trim-off-newlines": "^1.0.0" }, "dependencies": { - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" + "readable-stream": "2 || 3" } } } @@ -2417,27 +2434,6 @@ } } }, - "cp-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", - "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", - "dev": true, - "requires": { - "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": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, "cross-env": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", @@ -2620,12 +2616,20 @@ "dev": true }, "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { - "strip-bom": "^3.0.0" + "strip-bom": "^4.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + } } }, "define-properties": { @@ -2895,12 +2899,12 @@ "dev": true }, "env-ci": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-4.5.1.tgz", - "integrity": "sha512-Xtmr+ordf8POu3NcNzx3eOa2zHyfD4h3fPHX5fLklkWa86ck35n1c9oZmyUnVPUl9zHnpZWdWtCUBPSWEagjCQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.1.tgz", + "integrity": "sha512-xXgohoOAFFF1Y3EdsSKP7olyH/DLS6ZD3aglV6mDFAXBqBXLJSsZLrOZdYfDs5mOmgNaP3YYynObzwF3QkC24g==", "dev": true, "requires": { - "execa": "^3.2.0", + "execa": "^4.0.0", "java-properties": "^1.0.0" }, "dependencies": { @@ -2916,9 +2920,9 @@ } }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", + "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -2928,7 +2932,6 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } @@ -2949,20 +2952,14 @@ "dev": true }, "npm-run-path": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" } }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3048,21 +3045,6 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3421,9 +3403,9 @@ "dev": true }, "eslint": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.7.2.tgz", - "integrity": "sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -3480,6 +3462,12 @@ "type-fest": "^0.8.1" } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -3503,9 +3491,9 @@ } }, "eslint-plugin-mocha": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz", - "integrity": "sha512-3uwlJVLijjEmBeNyH60nzqgA1gacUWLUmcKV8PIGNvj1kwP/CTgAWQHn2ayyJVwziX+KETkr9opNwT1qD/RZ5A==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz", + "integrity": "sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA==", "dev": true, "requires": { "ramda": "^0.26.1" @@ -3892,51 +3880,111 @@ } }, "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "find-versions": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", - "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", - "dev": true, - "requires": { - "semver-regex": "^2.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", + "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", "dev": true, "requires": { - "is-buffer": "~2.0.3" + "commondir": "^1.0.1", + "make-dir": "^3.0.0", + "pkg-dir": "^4.1.0" }, "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - } - } - }, + "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-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.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" + } + }, + "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 + }, + "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 + }, + "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" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "requires": { + "semver-regex": "^2.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + } + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -3988,40 +4036,55 @@ "dev": true }, "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" }, "dependencies": { "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "shebang-regex": "^3.0.0" } }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -4061,6 +4124,12 @@ "readable-stream": "^2.0.0" } }, + "fromentries": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", + "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", + "dev": true + }, "fs-extra": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", @@ -4177,6 +4246,12 @@ "is-property": "^1.0.2" } }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4251,33 +4326,25 @@ } }, "git-raw-commits": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz", - "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.3.tgz", + "integrity": "sha512-SoSsFL5lnixVzctGEi2uykjA7B5I0AhO9x6kdzvGRHbxsa6JSEgrgy1esRKsfOKE1cgyOJ/KDR2Trxu157sb8w==", "dev": true, "requires": { "dargs": "^4.0.1", "lodash.template": "^4.0.2", - "meow": "^4.0.0", + "meow": "^5.0.0", "split2": "^2.0.0", - "through2": "^2.0.0" + "through2": "^3.0.0" }, "dependencies": { - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" + "readable-stream": "2 || 3" } } } @@ -4399,9 +4466,9 @@ "dev": true }, "handlebars": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", - "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.2.tgz", + "integrity": "sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -4503,12 +4570,21 @@ } }, "hasha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", - "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.1.0.tgz", + "integrity": "sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA==", "dev": true, "requires": { - "is-stream": "^1.0.1" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + } } }, "he": { @@ -4529,6 +4605,12 @@ "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, + "html-escaper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", + "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", + "dev": true + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -4557,30 +4639,13 @@ } }, "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-3.0.0.tgz", + "integrity": "sha512-uGuJaBWQWDQCJI5ip0d/VTYZW0nRrlLWXA4A7P1jrsa+f77rW2yXz315oBt6zGCF6l8C2tlMxY7ffULCj+5FhA==", "dev": true, "requires": { - "agent-base": "4", - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "agent-base": "5", + "debug": "4" } }, "http-signature": { @@ -4595,24 +4660,13 @@ } }, "https-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "agent-base": "5", + "debug": "4" } }, "human-signals": { @@ -4878,9 +4932,9 @@ "dev": true }, "inquirer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", - "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz", + "integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -4892,7 +4946,7 @@ "lodash": "^4.17.15", "mute-stream": "0.0.8", "run-async": "^2.2.0", - "rxjs": "^6.4.0", + "rxjs": "^6.5.3", "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" @@ -5276,9 +5330,9 @@ "dev": true }, "issue-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-5.0.0.tgz", - "integrity": "sha512-q/16W7EPHRL0FKVz9NU++TUsoygXGj6JOi88oulyAcQG+IEZ0T6teVdE+VLbe19OfL/tbV8Wi3Dfo0HedeHW0Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", "dev": true, "requires": { "lodash.capitalize": "^4.2.1", @@ -5289,67 +5343,155 @@ } }, "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", "dev": true }, "istanbul-lib-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { - "append-transform": "^1.0.0" + "append-transform": "^2.0.0" } }, "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.0.tgz", + "integrity": "sha512-Nm4wVHdo7ZXSG30KjZ2Wl5SU/Bw7bDx1PdaiIFzEStdjs0H12mOTncn1GVYuqQSaZxpg87VGBRsVRPGD2cD1AQ==", "dev": true, "requires": { - "@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" + "@babel/core": "^7.7.5", + "@babel/parser": "^7.7.5", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", + "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" }, "dependencies": { @@ -5362,12 +5504,13 @@ } }, "istanbul-reports": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", - "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A==", "dev": true, "requires": { - "handlebars": "^4.1.2" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, "java-properties": { @@ -5377,9 +5520,9 @@ "dev": true }, "js-combinatorics": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/js-combinatorics/-/js-combinatorics-0.5.4.tgz", - "integrity": "sha512-PCqUIKGqv/Kjao1G4GE/Yni6QkCP2nWW3KnxL+8IGWPlP18vQpT8ufGMf4XUAAY8JHEryUCJbf51zG8329ntMg==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/js-combinatorics/-/js-combinatorics-0.5.5.tgz", + "integrity": "sha512-WglFY9EQvwndNhuJLxxyjnC16649lfZly/G3M3zgQMwcWlJDJ0Jn9niPWeYjnLXwWOEycYVxR2Tk98WLeFkrcw==", "dev": true }, "js-tokens": { @@ -5479,6 +5622,21 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonc-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.0.tgz", + "integrity": "sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==", + "dev": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -6100,25 +6258,18 @@ "dev": true }, "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "semver": "^6.0.0" }, "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -6145,19 +6296,25 @@ } }, "mariadb": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.1.4.tgz", - "integrity": "sha512-CMLbIKZCzxero94luo25IKpboEuUCvA2g2+NMUBdRYpBCDmnsiQ8kwdgqvuVQ13a4/ZKr87BC1nbe4b3UZXXmQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.1.5.tgz", + "integrity": "sha512-uRHtjc0bg+Rt91LvIaZNo8tLcYs9YPiiW9Mke/BavFzg0MnctJBDkqZW9X59NyrRGDd7Oz9UshTWgmUpfDZc0Q==", "dev": true, "requires": { "@types/geojson": "^7946.0.7", - "@types/node": "^12.12.11", + "@types/node": "^13.1.4", "denque": "^1.4.1", "iconv-lite": "^0.5.0", "long": "^4.0.0", "moment-timezone": "^0.5.27" }, "dependencies": { + "@types/node": { + "version": "13.1.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.8.tgz", + "integrity": "sha512-6XzyyNM9EKQW4HKuzbo/CkOIjn/evtCmsU+MUM1xDfJ+3/rNjBttM1NgN7AOQvN6tP1Sl1D1PIKMreTArnxM9A==", + "dev": true + }, "iconv-lite": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz", @@ -6170,41 +6327,52 @@ } }, "markdown-it": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-9.0.1.tgz", - "integrity": "sha512-XC9dMBHg28Xi7y5dPuLjM61upIGPJG8AiHNHYqIaXER2KNnn7eKnM5/sF0ImNnyoV224Ogn9b1Pck8VH4k0bxw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", + "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", "dev": true, "requires": { "argparse": "^1.0.7", - "entities": "~1.1.1", + "entities": "~2.0.0", "linkify-it": "^2.0.0", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" + }, + "dependencies": { + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + } } }, "markdownlint": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.16.0.tgz", - "integrity": "sha512-Zo+iPezP3eM6lLhKepkUw+X98H44lipIdx4d6faaugfB0+7VuDB3R0hXmx7z9F1N3/ypn46oOFgAD9iF++Ie6A==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.18.0.tgz", + "integrity": "sha512-nQAfK9Pbq0ZRoMC/abNGterEnV3kL8MZmi0WHhw8WJKoIbsm3cXGufGsxzCRvjW15cxe74KWcxRSKqwplS26Bw==", "dev": true, "requires": { - "markdown-it": "9.0.1" + "markdown-it": "10.0.0" } }, "markdownlint-cli": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.18.0.tgz", - "integrity": "sha512-mQ2zvjMLoy0P2kb9Y03SqC24WPH4fTRN0/CyCorB122c4Chg9vWJKgUKBz3KR7swpzqmlI0SYq/7Blbqe4kb2g==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.21.0.tgz", + "integrity": "sha512-gvnczz3W3Wgex851/cIQ/2y8GNhY+EVK8Ael8kRd8hoSQ0ps9xjhtwPwMyJPoiYbAoPxG6vSBFISiysaAbCEZg==", "dev": true, "requires": { "commander": "~2.9.0", "deep-extend": "~0.5.1", "get-stdin": "~5.0.1", "glob": "~7.1.2", - "js-yaml": "^3.13.1", + "ignore": "~5.1.4", + "js-yaml": "~3.13.1", + "jsonc-parser": "~2.2.0", "lodash.differencewith": "~4.5.0", "lodash.flatten": "~4.4.0", - "markdownlint": "~0.16.0", + "markdownlint": "~0.18.0", + "markdownlint-rule-helpers": "~0.6.0", "minimatch": "~3.0.4", "rc": "~1.2.7" }, @@ -6223,9 +6391,21 @@ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", "dev": true + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true } } }, + "markdownlint-rule-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.6.0.tgz", + "integrity": "sha512-LiZVAbg9/cqkBHtLNNqHV3xuy4Y2L/KuGU6+ZXqCT9NnCdEkIoxeI5/96t+ExquBY0iHy2CVWxPH16nG1RKQVQ==", + "dev": true + }, "marked": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", @@ -6286,23 +6466,6 @@ "yargs-parser": "^10.0.0" } }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -6736,12 +6899,6 @@ "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", "dev": true }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -6820,6 +6977,15 @@ } } }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, "nopt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", @@ -6875,9 +7041,9 @@ } }, "npm": { - "version": "6.13.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.4.tgz", - "integrity": "sha512-vTcUL4SCg3AzwInWTbqg1OIaOXlzKSS8Mb8kc5avwrJpcvevDA5J9BhYSuei+fNs3pwOp4lzA5x2FVDXACvoXA==", + "version": "6.13.6", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.6.tgz", + "integrity": "sha512-NomC08kv7HIl1FOyLOe9Hp89kYsOsvx52huVIJ7i8hFW8Xp65lDwe/8wTIrh9q9SaQhA8hTrfXPh3BEL3TmMpw==", "dev": true, "requires": { "JSONStream": "^1.3.5", @@ -6965,7 +7131,7 @@ "once": "~1.4.0", "opener": "^1.5.1", "osenv": "^0.1.5", - "pacote": "^9.5.11", + "pacote": "^9.5.12", "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", @@ -9247,7 +9413,7 @@ } }, "pacote": { - "version": "9.5.11", + "version": "9.5.12", "bundled": true, "dev": true, "requires": { @@ -10454,79 +10620,133 @@ "optional": true }, "nyc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", - "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", - "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", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.0.tgz", + "integrity": "sha512-qcLBlNCKMDVuKb7d1fpxjPR8sHeMVX0CHarXAVzrVWoFrigCkYR8xcrjfXSPi5HXM7EU78L6ywO7w1c5rZNCNg==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.0", "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", + "make-dir": "^3.0.0", + "node-preload": "^0.2.0", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", "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" + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "uuid": "^3.3.3", + "yargs": "^15.0.2" }, "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "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": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.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==", + "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": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.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" + } + }, + "p-map": { "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==", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "aggregate-error": "^3.0.0" } }, "p-try": { @@ -10535,16 +10755,64 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "resolve-from": { + "path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", + "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^16.1.0" + } + }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", + "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -10745,6 +11013,12 @@ "os-tmpdir": "^1.0.0" } }, + "p-each-series": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", + "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", + "dev": true + }, "p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -10821,13 +11095,13 @@ "dev": true }, "package-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", - "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "requires": { "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", + "hasha": "^5.0.0", "lodash.flattendeep": "^4.4.0", "release-zalgo": "^1.0.0" } @@ -11019,9 +11293,9 @@ } }, "picomatch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", - "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", "dev": true }, "pify": { @@ -11163,6 +11437,15 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -11354,9 +11637,9 @@ "dev": true }, "registry-auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.0.0.tgz", - "integrity": "sha512-lpQkHxd9UL6tb3k/aHAVfnVtn+Bcs9ob5InuFLLEDqSqeq+AljB8GZW9xY0x7F+xYwEcjKe07nyoxzEYz6yvkw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.0.tgz", + "integrity": "sha512-7uxS951DeOBOwsv8deX+l7HcjY2VZxaOgHtM6RKzg3HhpE+bJ0O7VbuMJLosC1T5WSFpHm0DuFIbqUl43jHpsA==", "dev": true, "requires": { "rc": "^1.2.8", @@ -11626,21 +11909,21 @@ "dev": true }, "semantic-release": { - "version": "15.13.31", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.13.31.tgz", - "integrity": "sha512-mrtYkH4p0FvXIRFCsr2r5il/A+Uj7oeeq+dgyojAbr4Tzywv9AlCYHeE3A8U3eE4bMJPiBV4YnQRsk1QS8yDDw==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-16.0.2.tgz", + "integrity": "sha512-KQmPGJvhB3qn49pFGnAuSm8txOV6nLWrUg/jQG+1CYfg7rrroVKqpZ9mmA6+PjfoFCroQ0Na7Ee5DuzHiR6e/A==", "dev": true, "requires": { - "@semantic-release/commit-analyzer": "^6.1.0", + "@semantic-release/commit-analyzer": "^7.0.0", "@semantic-release/error": "^2.2.0", - "@semantic-release/github": "^5.1.0", - "@semantic-release/npm": "^5.0.5", + "@semantic-release/github": "^6.0.0", + "@semantic-release/npm": "^6.0.0", "@semantic-release/release-notes-generator": "^7.1.2", "aggregate-error": "^3.0.0", "cosmiconfig": "^6.0.0", "debug": "^4.0.0", - "env-ci": "^4.0.0", - "execa": "^3.2.0", + "env-ci": "^5.0.0", + "execa": "^4.0.0", "figures": "^3.0.0", "find-versions": "^3.0.0", "get-stream": "^5.0.0", @@ -11648,13 +11931,15 @@ "hook-std": "^2.0.0", "hosted-git-info": "^3.0.0", "lodash": "^4.17.15", - "marked": "^0.7.0", + "marked": "^0.8.0", "marked-terminal": "^3.2.0", - "p-locate": "^4.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", "p-reduce": "^2.0.0", "read-pkg-up": "^7.0.0", "resolve-from": "^5.0.0", - "semver": "^6.0.0", + "semver": "^7.1.1", + "semver-diff": "^3.1.1", "signale": "^1.2.1", "yargs": "^15.0.1" }, @@ -11666,15 +11951,24 @@ "dev": true }, "ansi-styles": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", - "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -11732,9 +12026,9 @@ } }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", + "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -11744,11 +12038,19 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -11777,6 +12079,12 @@ "lru-cache": "^5.1.1" } }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -11793,30 +12101,34 @@ } }, "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", + "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==", "dev": true }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, "npm-run-path": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz", - "integrity": "sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" } }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -11922,6 +12234,15 @@ "ansi-regex": "^5.0.0" } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11943,9 +12264,9 @@ } }, "yargs": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.0.2.tgz", - "integrity": "sha512-GH/X/hYt+x5hOat4LMnCqMd8r5Cv78heOMIJn1hr7QPPBqfeC6p89Y78+WB9yGDvfpCvgasfmWLzNzEioOUD9Q==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", + "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -11974,9 +12295,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.1.tgz", + "integrity": "sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A==" }, "semver-compare": { "version": "1.0.0", @@ -11984,6 +12305,23 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "semver-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", @@ -11997,9 +12335,9 @@ "dev": true }, "sequelize-pool": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", - "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-3.0.0.tgz", + "integrity": "sha512-BSA6gmba/cINu+cM2l/cKaTCgtrn6PBElk9dGq8U46vEPkaMTl7GDYlYfsRXztxtlAPCk62gdJ9GHj7H4gcXMg==" }, "set-blocking": { "version": "2.0.0", @@ -12288,17 +12626,37 @@ "dev": true }, "spawn-wrap": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", - "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", "signal-exit": "^3.0.2", - "which": "^1.3.0" + "which": "^2.0.1" + }, + "dependencies": { + "rimraf": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", + "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "spdx-correct": { @@ -12699,9 +13057,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", + "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -12743,70 +13101,14 @@ } }, "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.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.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", - "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 - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - } - } + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" } }, "text-extensions": { @@ -13018,6 +13320,15 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", @@ -13031,9 +13342,9 @@ "dev": true }, "uglify-js": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", - "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.5.tgz", + "integrity": "sha512-GFZ3EXRptKGvb/C1Sq6nO1iI7AGcjyqmIyOw0DrD0675e+NNbGO72xmMM2iEBdFbxaTLo70NbjM/Wy54uZIlsg==", "dev": true, "optional": true, "requires": { @@ -13182,9 +13493,9 @@ "dev": true }, "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { "version": "2.1.0", @@ -13440,14 +13751,15 @@ } }, "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, "xml-name-validator": { @@ -13458,9 +13770,9 @@ "optional": true }, "xmldom": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.2.1.tgz", + "integrity": "sha512-kXXiYvmblIgEemGeB75y97FyaZavx6SQhGppLw5TKWAD2Wd0KAly0g23eVLh17YcpxZpnFym1Qk/eaRjy1APPg==", "dev": true }, "xpath.js": { diff --git a/package.json b/package.json index 7e459e3888c5..9d9c134bdab2 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,16 @@ "moment": "^2.24.0", "moment-timezone": "^0.5.21", "retry-as-promised": "^3.2.0", - "semver": "^6.3.0", - "sequelize-pool": "^2.3.0", + "semver": "^7.1.0", + "sequelize-pool": "^3.0.0", "toposort-class": "^1.0.1", - "uuid": "^3.3.3", + "uuid": "^3.4.0", "validator": "^10.11.0", "wkx": "^0.4.8" }, "devDependencies": { - "@commitlint/cli": "^8.2.0", - "@commitlint/config-angular": "^8.2.0", + "@commitlint/cli": "^8.3.5", + "@commitlint/config-angular": "^8.3.4", "@types/bluebird": "^3.5.26", "@types/node": "^12.7.8", "@types/validator": "^10.11.0", @@ -61,24 +61,24 @@ "esdoc": "^1.1.0", "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", - "eslint": "^6.4.0", + "eslint": "^6.8.0", "eslint-plugin-jsdoc": "^4.1.1", - "eslint-plugin-mocha": "^5.2.1", + "eslint-plugin-mocha": "^6.2.2", "fs-jetpack": "^2.2.2", "husky": "^1.3.1", - "js-combinatorics": "^0.5.4", + "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", "lint-staged": "^8.1.5", - "mariadb": "^2.1.1", - "markdownlint-cli": "^0.18.0", + "mariadb": "^2.1.5", + "markdownlint-cli": "^0.21.0", "mocha": "^6.1.4", "mysql2": "^1.6.5", - "nyc": "^14.1.1", + "nyc": "^15.0.0", "pg": "^7.8.1", "pg-hstore": "^2.x", "pg-types": "^2.0.0", "rimraf": "^2.6.3", - "semantic-release": "^15.13.16", + "semantic-release": "^16.0.2", "sinon": "^7.5.0", "sinon-chai": "^3.3.0", "sqlite3": "^4.0.6", From fbd19d986170ad1edeeba123a0730854824aa515 Mon Sep 17 00:00:00 2001 From: Andrew Heuermann Date: Sat, 18 Jan 2020 04:20:19 -0600 Subject: [PATCH 025/414] feat(postgres): idle_in_transaction_session_timeout connection option (#11775) --- lib/dialects/postgres/connection-manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 6e000b4603f2..8181595e63a2 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -114,7 +114,9 @@ class ConnectionManager extends AbstractConnectionManager { // this feature has been added in pg module v6.0.0, check pg/CHANGELOG.md 'keepAlive', // Times out queries after a set time in milliseconds. Added in pg v7.3 - 'statement_timeout' + 'statement_timeout', + // Terminate any session with an open transaction that has been idle for longer than the specified duration in milliseconds. Added in pg v7.17.0 only supported in postgres >= 10 + 'idle_in_transaction_session_timeout' ])); } From 365d7c35eb9badf42d0e45513338194fb1c549be Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sun, 19 Jan 2020 04:25:24 -0300 Subject: [PATCH 026/414] fix(typings): queryInterface.addIndex (#11844) --- types/lib/query-interface.d.ts | 3 ++- types/test/query-interface.ts | 9 +++++++++ types/type-helpers/set-required.d.ts | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 types/type-helpers/set-required.d.ts diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 7f22da42e187..a1ea23154090 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -4,6 +4,7 @@ import { Promise } from './promise'; import QueryTypes = require('./query-types'); import { Sequelize, RetryOptions } from './sequelize'; import { Transaction } from './transaction'; +import { SetRequired } from './../type-helpers/set-required'; type BindOrReplacements = { [key: string]: unknown } | unknown[]; type FieldMap = { [key: string]: string }; @@ -403,7 +404,7 @@ export class QueryInterface { ): Promise; public addIndex( tableName: string, - options: QueryInterfaceIndexOptions & { fields: string[] }, + options: SetRequired, rawTablename?: string ): Promise; diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index 6a20cc592333..ad95eee09a4b 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -145,6 +145,15 @@ queryInterface.addIndex('Person', ['firstname', 'lastname'], { type: 'UNIQUE', }); +queryInterface.addIndex('Foo', { + name: 'foo_a', + fields: [ + { name: 'foo_b', order: 'DESC' }, + 'foo_c', + { name: 'foo_d', order: 'ASC', collate: 'foobar', length: 42 } + ], +}); + queryInterface.removeIndex('Person', 'SuperDuperIndex'); // or diff --git a/types/type-helpers/set-required.d.ts b/types/type-helpers/set-required.d.ts new file mode 100644 index 000000000000..db9109189b8a --- /dev/null +++ b/types/type-helpers/set-required.d.ts @@ -0,0 +1,16 @@ +/** + * Full credits to sindresorhus/type-fest + * + * https://github.com/sindresorhus/type-fest/blob/v0.8.1/source/set-required.d.ts + * + * Thank you! + */ +export type SetRequired = + // Pick just the keys that are not required from the base type. + Pick> & + // Pick the keys that should be required from the base type and make them required. + Required> extends + // If `InferredType` extends the previous, then for each key, use the inferred type key. + infer InferredType + ? {[KeyType in keyof InferredType]: InferredType[KeyType]} + : never; \ No newline at end of file From 1f42c79d58a6fafef4882ff93a2bc20eaefc488e Mon Sep 17 00:00:00 2001 From: Oliver Emery Date: Tue, 21 Jan 2020 02:09:33 +0000 Subject: [PATCH 027/414] fix(types): fix BelongsToManyGetAssociationsMixinOptions (#11818) --- types/lib/associations/belongs-to-many.d.ts | 5 +++++ types/test/model.ts | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index e7af47d59da6..54524e6d5562 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -2,6 +2,7 @@ import { BulkCreateOptions, CreateOptions, Filterable, + FindAttributeOptions, FindOptions, InstanceDestroyOptions, InstanceUpdateOptions, @@ -99,6 +100,10 @@ export class BelongsToMany ext * @see BelongsToManyGetAssociationsMixin */ export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions { + /** + * A list of the attributes from the join table that you want to select. + */ + joinTableAttributes?: FindAttributeOptions /** * Apply a scope on the related model, or remove its default scope by passing false. */ diff --git a/types/test/model.ts b/types/test/model.ts index 21e8e49cad33..7edc6064389b 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -1,4 +1,4 @@ -import { Association, DataTypes, HasOne, Model, Sequelize } from 'sequelize'; +import { Association, BelongsToManyGetAssociationsMixin, DataTypes, HasOne, Model, Sequelize } from 'sequelize'; class MyModel extends Model { public num!: number; @@ -103,3 +103,15 @@ UserModel.findCreateFind({ */ class TestModel extends Model {}; TestModel.primaryKeyAttributes; + +/** + * Test for joinTableAttributes on BelongsToManyGetAssociationsMixin + */ +class SomeModel extends Model { + public getOthers!: BelongsToManyGetAssociationsMixin +} + +const someInstance = new SomeModel() +someInstance.getOthers({ + joinTableAttributes: { include: [ 'id' ] } +}) From e1fef557c62e3a4b132c83ce182041e4712bebf5 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 21 Jan 2020 03:23:05 -0300 Subject: [PATCH 028/414] docs(optimistic-locking): fix missing manual (#11850) --- docs/manual/other-topics/optimistic-locking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/other-topics/optimistic-locking.md b/docs/manual/other-topics/optimistic-locking.md index 56b76e5e398f..7db529e43319 100644 --- a/docs/manual/other-topics/optimistic-locking.md +++ b/docs/manual/other-topics/optimistic-locking.md @@ -1,4 +1,4 @@ -## Optimistic Locking +# Optimistic Locking Sequelize has built-in support for optimistic locking through a model instance version count. From 3288225bf1ccf3d2ee19e305ce9d31c2402b933b Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 21 Jan 2020 03:23:32 -0300 Subject: [PATCH 029/414] refactor(docs): catch some mistakes (#11851) --- docs/esdoc-config.js | 18 ++++++++---------- docs/manual-utils.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 docs/manual-utils.js diff --git a/docs/esdoc-config.js b/docs/esdoc-config.js index 10d40c65aa90..fc2adcc9c089 100644 --- a/docs/esdoc-config.js +++ b/docs/esdoc-config.js @@ -1,15 +1,8 @@ 'use strict'; -const _ = require('lodash'); +const { getDeclaredManuals, checkManuals } = require('./manual-utils'); -const manualGroups = require('./manual-groups.json'); - -const manual = { - index: './docs/index.md', - globalIndex: true, - asset: './docs/images', - files: _.flatten(_.values(manualGroups)).map(file => `./docs/manual/${file}`) -}; +checkManuals(); module.exports = { source: './lib', @@ -45,7 +38,12 @@ module.exports = { repository: 'https://github.com/sequelize/sequelize', site: 'https://sequelize.org/master/' }, - manual + manual: { + index: './docs/index.md', + globalIndex: true, + asset: './docs/images', + files: getDeclaredManuals() + } } } ] diff --git a/docs/manual-utils.js b/docs/manual-utils.js new file mode 100644 index 000000000000..04109eda59a7 --- /dev/null +++ b/docs/manual-utils.js @@ -0,0 +1,37 @@ +'use strict'; + +const _ = require('lodash'); +const jetpack = require('fs-jetpack'); +const { normalize } = require('path'); +const assert = require('assert'); + +function getDeclaredManuals() { + const declaredManualGroups = require('./manual-groups.json'); + return _.flatten(_.values(declaredManualGroups)).map(file => { + return normalize(`./docs/manual/${file}`); + }); +} + +function getAllManuals() { + return jetpack.find('./docs/manual/', { matching: '*.md' }).map(m => { + return normalize(`./${m}`); + }); +} + +function checkManuals() { + // First we check that declared manuals and all manuals are the same + const declared = getDeclaredManuals().sort(); + const all = getAllManuals().sort(); + assert.deepStrictEqual(declared, all); + + // Then we check that every manual begins with a single `#`. This is + // important for ESDoc to render the left menu correctly. + for (const manualRelativePath of all) { + assert( + /^#[^#]/.test(jetpack.read(manualRelativePath)), + `Manual '${manualRelativePath}' must begin with a single '#'` + ); + } +} + +module.exports = { getDeclaredManuals, getAllManuals, checkManuals }; \ No newline at end of file From cb22281889ba134fd6c17ad184e6424ddfdba027 Mon Sep 17 00:00:00 2001 From: Muhammed Kalkan Date: Tue, 21 Jan 2020 09:24:03 +0300 Subject: [PATCH 030/414] fix: properly select SRID if present (#11763) --- lib/dialects/mariadb/data-types.js | 12 ++ lib/dialects/mysql/data-types.js | 2 +- lib/dialects/postgres/data-types.js | 4 +- .../dialects/postgres/data-types.test.js | 1 + test/integration/model/geography.test.js | 192 +++++++++++++++--- test/integration/model/geometry.test.js | 77 ++++++- test/tmp/.gitkeep | 1 + 7 files changed, 253 insertions(+), 36 deletions(-) diff --git a/lib/dialects/mariadb/data-types.js b/lib/dialects/mariadb/data-types.js index 5979fa2841a9..69f6df175e02 100644 --- a/lib/dialects/mariadb/data-types.js +++ b/lib/dialects/mariadb/data-types.js @@ -1,5 +1,6 @@ 'use strict'; +const wkx = require('wkx'); const _ = require('lodash'); const moment = require('moment-timezone'); @@ -92,6 +93,17 @@ module.exports = BaseTypes => { this.sqlType = this.type; } } + static parse(value) { + value = value.buffer(); + // Empty buffer, MySQL doesn't support POINT EMPTY + // check, https://dev.mysql.com/worklog/task/?id=2381 + if (!value || value.length === 0) { + return null; + } + // For some reason, discard the first 4 bytes + value = value.slice(4); + return wkx.Geometry.parse(value).toGeoJSON({ shortCrs: true }); + } toSql() { return this.sqlType; } diff --git a/lib/dialects/mysql/data-types.js b/lib/dialects/mysql/data-types.js index 98e8aa0a97a8..c0605cb2b777 100644 --- a/lib/dialects/mysql/data-types.js +++ b/lib/dialects/mysql/data-types.js @@ -109,7 +109,7 @@ module.exports = BaseTypes => { } // For some reason, discard the first 4 bytes value = value.slice(4); - return wkx.Geometry.parse(value).toGeoJSON(); + return wkx.Geometry.parse(value).toGeoJSON({ shortCrs: true }); } toSql() { return this.sqlType; diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js index 68ead0fa1397..6a8b016f7ed6 100644 --- a/lib/dialects/postgres/data-types.js +++ b/lib/dialects/postgres/data-types.js @@ -306,7 +306,7 @@ module.exports = BaseTypes => { } static parse(value) { const b = Buffer.from(value, 'hex'); - return wkx.Geometry.parse(b).toGeoJSON(); + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); } _stringify(value, options) { return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; @@ -333,7 +333,7 @@ module.exports = BaseTypes => { } static parse(value) { const b = Buffer.from(value, 'hex'); - return wkx.Geometry.parse(b).toGeoJSON(); + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); } _stringify(value, options) { return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; diff --git a/test/integration/dialects/postgres/data-types.test.js b/test/integration/dialects/postgres/data-types.test.js index c4978b9ccb5b..7407abc2de21 100644 --- a/test/integration/dialects/postgres/data-types.test.js +++ b/test/integration/dialects/postgres/data-types.test.js @@ -6,6 +6,7 @@ const Support = require('../../support'); const dialect = Support.getTestDialect(); const DataTypes = require('../../../../lib/data-types'); + if (dialect === 'postgres') { describe('[POSTGRES Specific] Data Types', () => { describe('DATE/DATEONLY Validate and Stringify', () => { diff --git a/test/integration/model/geography.test.js b/test/integration/model/geography.test.js index 52eebe1c96c7..ede7ff497276 100644 --- a/test/integration/model/geography.test.js +++ b/test/integration/model/geography.test.js @@ -23,7 +23,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Pub = this.sequelize.define('Pub', { location: { field: 'coordinates', type: DataTypes.GEOGRAPHY } }), - point = { type: 'Point', coordinates: [39.807222, -76.984722] }; + point = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; return Pub.sync({ force: true }).then(() => { return Pub.create({ location: point }); @@ -35,7 +43,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should create a geography object', function() { const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; + const point = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; return User.create({ username: 'username', geography: point }).then(newUser => { expect(newUser).not.to.be.null; @@ -45,8 +61,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should update a geography object', function() { const User = this.User; - const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, - point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; + const point1 = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + point2 = { + type: 'Point', coordinates: [49.807222, -86.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: point1 }; return User.create(props).then(() => { @@ -71,7 +103,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should create a geography object', function() { const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; + const point = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; return User.create({ username: 'username', geography: point }).then(newUser => { expect(newUser).not.to.be.null; @@ -81,8 +121,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should update a geography object', function() { const User = this.User; - const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, - point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; + const point1 = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + point2 = { + type: 'Point', coordinates: [49.807222, -86.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: point1 }; return User.create(props).then(() => { @@ -107,7 +163,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should create a geography object', function() { const User = this.User; - const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]] }; + const point = { + type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; return User.create({ username: 'username', geography: point }).then(newUser => { expect(newUser).not.to.be.null; @@ -117,8 +181,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should update a geography object', function() { const User = this.User; - const point1 = { type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]] }, - point2 = { type: 'LineString', coordinates: [[101.0, 0.0], [102.0, 1.0]] }; + const point1 = { + type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + point2 = { + type: 'LineString', coordinates: [[101.0, 0.0], [102.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: point1 }; return User.create(props).then(() => { @@ -143,10 +223,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should create a geography object', function() { const User = this.User; - const point = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const point = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; return User.create({ username: 'username', geography: point }).then(newUser => { expect(newUser).not.to.be.null; @@ -156,13 +244,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should update a geography object', function() { const User = this.User; - const polygon1 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] - ] }, - polygon2 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const polygon1 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + polygon2 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: polygon1 }; return User.create(props).then(() => { @@ -188,10 +292,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should create a geography object', function() { const User = this.User; - const point = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const point = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; return User.create({ username: 'username', geography: point }).then(newUser => { expect(newUser).not.to.be.null; @@ -201,13 +313,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should update a geography object', function() { const User = this.User; - const polygon1 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] - ] }, - polygon2 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const polygon1 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + polygon2 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: polygon1 }; return User.create(props).then(() => { diff --git a/test/integration/model/geometry.test.js b/test/integration/model/geometry.test.js index 0925939f37ed..c521f27b6f13 100644 --- a/test/integration/model/geometry.test.js +++ b/test/integration/model/geometry.test.js @@ -59,6 +59,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.geometry).to.be.deep.eql(point2); }); }); + + it('works with crs field', function() { + const Pub = this.sequelize.define('Pub', { + location: { field: 'coordinates', type: DataTypes.GEOMETRY } + }), + point = { type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + return Pub.sync({ force: true }).then(() => { + return Pub.create({ location: point }); + }).then(pub => { + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); + }); + }); }); describe('GEOMETRY(POINT)', () => { @@ -95,6 +116,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.geometry).to.be.deep.eql(point2); }); }); + + it('works with crs field', function() { + const User = this.User; + const point = { type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + return User.create({ username: 'username', geometry: point }).then(newUser => { + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); + }); + }); }); describe('GEOMETRY(LINESTRING)', () => { @@ -131,6 +169,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.geometry).to.be.deep.eql(point2); }); }); + + it('works with crs field', function() { + const User = this.User; + const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + return User.create({ username: 'username', geometry: point }).then(newUser => { + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); + }); + }); + }); describe('GEOMETRY(POLYGON)', () => { @@ -156,6 +212,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); + it('works with crs field', function() { + const User = this.User; + const point = { type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0]]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + return User.create({ username: 'username', geometry: point }).then(newUser => { + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); + }); + }); + it('should update a geometry object', function() { const User = this.User; const polygon1 = { type: 'Polygon', coordinates: [ @@ -202,7 +277,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { // MySQL 5.7, those guys finally fixed this if (dialect === 'mysql' && semver.gte(this.sequelize.options.databaseVersion, '5.7.0')) { return; - } + } return this.Model.create({ location: { diff --git a/test/tmp/.gitkeep b/test/tmp/.gitkeep index e69de29bb2d1..8b137891791f 100644 --- a/test/tmp/.gitkeep +++ b/test/tmp/.gitkeep @@ -0,0 +1 @@ + From 51b020775f014aa3b01bf205e9bb4443316e510f Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 21 Jan 2020 03:36:21 -0300 Subject: [PATCH 031/414] fix(increment): broken queries (#11852) --- lib/dialects/abstract/query-generator.js | 38 ++++++----- lib/model.js | 68 +++++++++++-------- lib/query-interface.js | 8 +-- test/integration/model/increment.test.js | 37 +++++++++- .../dialects/mariadb/query-generator.test.js | 10 +-- .../dialects/mssql/query-generator.test.js | 14 ++-- .../dialects/mysql/query-generator.test.js | 10 +-- .../dialects/postgres/query-generator.test.js | 14 ++-- .../dialects/sqlite/query-generator.test.js | 10 +-- 9 files changed, 131 insertions(+), 78 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 5de520d06b74..2b03e5af400c 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -407,20 +407,21 @@ class QueryGenerator { /** * Returns an update query using arithmetic operator * - * @param {string} operator String with the arithmetic operator (e.g. '+' or '-') - * @param {string} tableName Name of the table - * @param {Object} attrValueHash A hash with attribute-value-pairs - * @param {Object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {string} operator String with the arithmetic operator (e.g. '+' or '-') + * @param {string} tableName Name of the table + * @param {Object} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {Object} incrementAmountsByField A plain-object with attribute-value-pairs + * @param {Object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs * @param {Object} options - * @param {Object} attributes + * + * @private */ - arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes) { + arithmeticQuery(operator, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = options || {}; _.defaults(options, { returning: true }); - attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull); + extraAttributesToBeUpdated = Utils.removeNullValuesFromHash(extraAttributesToBeUpdated, this.options.omitNull); - const values = []; let outputFragment = ''; let returningFragment = ''; @@ -431,18 +432,21 @@ class QueryGenerator { returningFragment = returnValues.returningFragment; } - for (const key in attrValueHash) { - const value = attrValueHash[key]; - values.push(`${this.quoteIdentifier(key)}=${this.quoteIdentifier(key)}${operator} ${this.escape(value)}`); + const updateSetSqlFragments = []; + for (const field in incrementAmountsByField) { + const incrementAmount = incrementAmountsByField[field]; + const quotedField = this.quoteIdentifier(field); + const escapedAmount = this.escape(incrementAmount); + updateSetSqlFragments.push(`${quotedField}=${quotedField}${operator} ${escapedAmount}`); } - - attributes = attributes || {}; - for (const key in attributes) { - const value = attributes[key]; - values.push(`${this.quoteIdentifier(key)}=${this.escape(value)}`); + for (const field in extraAttributesToBeUpdated) { + const newValue = extraAttributesToBeUpdated[field]; + const quotedField = this.quoteIdentifier(field); + const escapedValue = this.escape(newValue); + updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); } - return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where)}${returningFragment}`.trim(); + return `UPDATE ${this.quoteTable(tableName)} SET ${updateSetSqlFragments.join(',')}${outputFragment} ${this.whereQuery(where)}${returningFragment}`.trim(); } /* diff --git a/lib/model.js b/lib/model.js index ec5d16b20ea1..5f92888e0567 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3334,55 +3334,69 @@ class Model { */ static increment(fields, options) { options = options || {}; + if (typeof fields === 'string') fields = [fields]; + if (Array.isArray(fields)) { + fields = fields.map(f => { + if (this.rawAttributes[f] && this.rawAttributes[f].field && this.rawAttributes[f].field !== f) { + return this.rawAttributes[f].field; + } + return f; + }); + } this._injectScope(options); this._optionsMustContainWhere(options); - const updatedAtAttr = this._timestampAttributes.updatedAt; - const versionAttr = this._versionAttribute; - const updatedAtAttribute = this.rawAttributes[updatedAtAttr]; options = Utils.defaults({}, options, { by: 1, - attributes: {}, where: {}, increment: true }); + const isSubtraction = !options.increment; Utils.mapOptionFieldNames(options, this); const where = Object.assign({}, options.where); - let values = {}; - if (typeof fields === 'string') { - values[fields] = options.by; - } else if (Array.isArray(fields)) { - fields.forEach(field => { - values[field] = options.by; - }); - } else { // Assume fields is key-value pairs - values = fields; + // A plain object whose keys are the fields to be incremented and whose values are + // the amounts to be incremented by. + let incrementAmountsByField = {}; + if (Array.isArray(fields)) { + incrementAmountsByField = {}; + for (const field of fields) { + incrementAmountsByField[field] = options.by; + } + } else { + // If the `fields` argument is not an array, then we assume it already has the + // form necessary to be placed directly in the `incrementAmountsByField` variable. + incrementAmountsByField = fields; } - if (!options.silent && updatedAtAttr && !values[updatedAtAttr]) { - options.attributes[updatedAtAttribute.field || updatedAtAttr] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect); - } - if (versionAttr) { - values[versionAttr] = options.increment ? 1 : -1; + // If optimistic locking is enabled, we can take advantage that this is an + // increment/decrement operation and send it here as well. We put `-1` for + // decrementing because it will be subtracted, getting `-(-1)` which is `+1` + if (this._versionAttribute) { + incrementAmountsByField[this._versionAttribute] = isSubtraction ? -1 : 1; } - for (const attr of Object.keys(values)) { - // Field name mapping - if (this.rawAttributes[attr] && this.rawAttributes[attr].field && this.rawAttributes[attr].field !== attr) { - values[this.rawAttributes[attr].field] = values[attr]; - delete values[attr]; - } + const extraAttributesToBeUpdated = {}; + + const updatedAtAttr = this._timestampAttributes.updatedAt; + if (!options.silent && updatedAtAttr && !incrementAmountsByField[updatedAtAttr]) { + const attrName = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; + extraAttributesToBeUpdated[attrName] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect); } + const tableName = this.getTableName(options); let promise; - if (!options.increment) { - promise = this.QueryInterface.decrement(this, this.getTableName(options), values, where, options); + if (isSubtraction) { + promise = this.QueryInterface.decrement( + this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options + ); } else { - promise = this.QueryInterface.increment(this, this.getTableName(options), values, where, options); + promise = this.QueryInterface.increment( + this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options + ); } return promise.then(affectedRows => { diff --git a/lib/query-interface.js b/lib/query-interface.js index f61b34f2832f..712d848c73d9 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -1133,10 +1133,10 @@ class QueryInterface { ); } - increment(model, tableName, values, identifier, options) { + increment(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = Utils.cloneDeep(options); - const sql = this.QueryGenerator.arithmeticQuery('+', tableName, values, identifier, options, options.attributes); + const sql = this.QueryGenerator.arithmeticQuery('+', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); options.type = QueryTypes.UPDATE; options.model = model; @@ -1144,10 +1144,10 @@ class QueryInterface { return this.sequelize.query(sql, options); } - decrement(model, tableName, values, identifier, options) { + decrement(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = Utils.cloneDeep(options); - const sql = this.QueryGenerator.arithmeticQuery('-', tableName, values, identifier, options, options.attributes); + const sql = this.QueryGenerator.arithmeticQuery('-', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); options.type = QueryTypes.UPDATE; options.model = model; diff --git a/test/integration/model/increment.test.js b/test/integration/model/increment.test.js index e2cbd10c24da..1f986bb4085e 100644 --- a/test/integration/model/increment.test.js +++ b/test/integration/model/increment.test.js @@ -53,7 +53,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe(method, () => { before(function() { this.assert = (increment, decrement) => { - return method === 'increment' ? increment : decrement; + return method === 'increment' ? increment : decrement; }; }); @@ -231,6 +231,41 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(notJeff.aNumber).to.equal(this.assert(3, 3)); }); }); + + it('should not care for attributes in the instance scope', function() { + this.User.addScope('test', { + attributes: ['foo', 'bar'] + }); + return this.User.scope('test').create({ id: 5, aNumber: 5 }) + .then(createdUser => createdUser[method]('aNumber', { by: 2 })) + .then(() => this.User.findByPk(5)) + .then(user => { + expect(user.aNumber).to.equal(this.assert(7, 3)); + }); + }); + it('should not care for exclude-attributes in the instance scope', function() { + this.User.addScope('test', { + attributes: { exclude: ['foo', 'bar'] } + }); + return this.User.scope('test').create({ id: 5, aNumber: 5 }) + .then(createdUser => createdUser[method]('aNumber', { by: 2 })) + .then(() => this.User.findByPk(5)) + .then(user => { + expect(user.aNumber).to.equal(this.assert(7, 3)); + }); + }); + it('should not care for include-attributes in the instance scope', function() { + this.User.addScope('test', { + attributes: { include: ['foo', 'bar'] } + }); + return this.User.scope('test').create({ id: 5, aNumber: 5 }) + .then(createdUser => createdUser[method]('aNumber', { by: 2 })) + .then(() => this.User.findByPk(5)) + .then(user => { + expect(user.aNumber).to.equal(this.assert(7, 3)); + }); + }); + }); }); }); diff --git a/test/unit/dialects/mariadb/query-generator.test.js b/test/unit/dialects/mariadb/query-generator.test.js index f2c0c2c62151..4c1cb34a9db6 100644 --- a/test/unit/dialects/mariadb/query-generator.test.js +++ b/test/unit/dialects/mariadb/query-generator.test.js @@ -82,27 +82,27 @@ if (dialect === 'mariadb') { arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\' WHERE `bar` = \'biz\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' } ], diff --git a/test/unit/dialects/mssql/query-generator.test.js b/test/unit/dialects/mssql/query-generator.test.js index bcb2f277e4d1..88e8a085b4fa 100644 --- a/test/unit/dialects/mssql/query-generator.test.js +++ b/test/unit/dialects/mssql/query-generator.test.js @@ -260,37 +260,37 @@ if (current.dialect.name === 'mssql') { [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' OUTPUT INSERTED.*' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' OUTPUT INSERTED.* WHERE [bar] = N\'biz\'' }, { title: 'Should use the plus operator without returning clause', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' OUTPUT INSERTED.*' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]- -1 OUTPUT INSERTED.*' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' OUTPUT INSERTED.* WHERE [bar] = N\'biz\'' }, { title: 'Should use the minus operator without returning clause', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\'' } ].forEach(test => { diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 51d3d8119851..7a8faaa05bf5 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -39,27 +39,27 @@ if (dialect === 'mysql') { arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\' WHERE `bar` = \'biz\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' } ], diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js index 17327f445137..d82fe2d6500c 100644 --- a/test/unit/dialects/postgres/query-generator.test.js +++ b/test/unit/dialects/postgres/query-generator.test.js @@ -55,37 +55,37 @@ if (dialect.startsWith('postgres')) { arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' WHERE "bar" = \'biz\' RETURNING *' }, { title: 'Should use the plus operator without returning clause', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' WHERE "bar" = \'biz\' RETURNING *' }, { title: 'Should use the minus operator without returning clause', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\'' } ], diff --git a/test/unit/dialects/sqlite/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js index 3ccfd481ae8a..022937efd8a2 100644 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ b/test/unit/dialects/sqlite/query-generator.test.js @@ -23,27 +23,27 @@ if (dialect === 'sqlite') { arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\' WHERE `bar` = \'biz\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' } ], From 91153938d563cd9687b431b6bb95cf4a32c13cb6 Mon Sep 17 00:00:00 2001 From: Sushant Date: Tue, 21 Jan 2020 12:14:35 +0530 Subject: [PATCH 032/414] docs: update broken link to beta changelog --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e463e9f59632..a29c6ae195c5 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.or [![npm version](https://badgen.net/npm/v/sequelize/next)](https://www.npmjs.com/package/sequelize) -`v6-beta` is now available. You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/upgrade-to-v6.md). +`v6-beta` is now available. You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/other-topics/upgrade-to-v6.md). ## Installation @@ -54,5 +54,5 @@ If you have security issues to report, please refer to our [Responsible Disclosu - [Plugins](https://sequelize.org/master/manual/resources.html) ### Translations -- [English](https://sequelize.org) (OFFICIAL) +- [English](https://sequelize.org/master) (OFFICIAL) - [中文文档](https://github.com/demopark/sequelize-docs-Zh-CN) (UNOFFICIAL) From ba9a2f65fde3dfbfe559902fca691119b8a0a2cf Mon Sep 17 00:00:00 2001 From: Eike Lurz Date: Tue, 21 Jan 2020 23:41:39 +0100 Subject: [PATCH 033/414] fix(types): adds 'hooks' to CreateOptions (#11736) --- types/lib/associations/base.d.ts | 14 +-- types/lib/instance-validator.d.ts | 9 +- types/lib/model.d.ts | 183 ++++++++++++++---------------- types/lib/sequelize.d.ts | 15 +-- 4 files changed, 95 insertions(+), 126 deletions(-) diff --git a/types/lib/associations/base.d.ts b/types/lib/associations/base.d.ts index 75603eba713f..186005992fed 100644 --- a/types/lib/associations/base.d.ts +++ b/types/lib/associations/base.d.ts @@ -1,4 +1,4 @@ -import { ColumnOptions, Model, ModelCtor } from '../model'; +import { ColumnOptions, Model, ModelCtor, Hookable } from '../model'; export abstract class Association { public associationType: string; @@ -42,17 +42,7 @@ export interface ForeignKeyOptions extends ColumnOptions { /** * Options provided when associating models */ -export interface AssociationOptions { - /** - * Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. - * For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks - * for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking - * any hooks. - * - * @default false - */ - hooks?: boolean; - +export interface AssociationOptions extends Hookable { /** * The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If * you create multiple associations between the same tables, you should provide an alias to be able to diff --git a/types/lib/instance-validator.d.ts b/types/lib/instance-validator.d.ts index 0d441995cce2..c2f3469d81ac 100644 --- a/types/lib/instance-validator.d.ts +++ b/types/lib/instance-validator.d.ts @@ -1,4 +1,6 @@ -export interface ValidationOptions { +import { Hookable } from "./model"; + +export interface ValidationOptions extends Hookable { /** * An array of strings. All properties that are in this array will not be validated */ @@ -7,9 +9,4 @@ export interface ValidationOptions { * An array of strings. Only the properties that are in this array will be validated */ fields?: string[]; - /** - * Run before and after validate hooks. - * @default true. - */ - hooks?: boolean; } diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index eca21c33dd91..138843dbebf5 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -154,7 +154,7 @@ export interface WhereOperators { [Op.ne]?: string | number | Literal | WhereOperators; /** Example: `[Op.not]: true,` becomes `IS NOT TRUE` */ - [Op.not]?: boolean | string | number | Literal | WhereOperators; + [Op.not]?: boolean | string | number | Literal | WhereOperators; /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ [Op.between]?: [number, number]; @@ -479,13 +479,13 @@ export type ProjectionAlias = [string | Literal | Fn, string]; export type FindAttributeOptions = | (string | ProjectionAlias)[] | { - exclude: string[]; - include?: (string | ProjectionAlias)[]; - } + exclude: string[]; + include?: (string | ProjectionAlias)[]; + } | { - exclude?: string[]; - include: (string | ProjectionAlias)[]; - }; + exclude?: string[]; + include: (string | ProjectionAlias)[]; + }; export interface IndexHint { type: IndexHints; @@ -545,9 +545,9 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para * locks with joins. See [transaction.LOCK for an example](transaction#lock) */ lock?: - | LOCK - | { level: LOCK; of: typeof Model } - | boolean; + | LOCK + | { level: LOCK; of: typeof Model } + | boolean; /** * Skip locked rows. Only supported in Postgres. */ @@ -615,7 +615,7 @@ export interface CountWithOptions extends CountOptions { group: GroupOption; } -export interface FindAndCountOptions extends CountOptions, FindOptions {} +export interface FindAndCountOptions extends CountOptions, FindOptions { } /** * Options for Model.build method @@ -651,7 +651,7 @@ export interface Silent { /** * Options for Model.create method */ -export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable { +export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable, Hookable { /** * If set, only columns matching those in fields will be saved */ @@ -668,6 +668,17 @@ export interface CreateOptions extends BuildOptions, Logging, Silent, Transactio * @default true */ validate?: boolean; + +} + +export interface Hookable { + + /** + * If `false` the applicable hooks will not be called. + * The default value depends on the context. + */ + hooks?: boolean + } /** @@ -688,17 +699,12 @@ export interface FindOrCreateOptions extends Logging, Transactionable { /** * Options for Model.upsert method */ -export interface UpsertOptions extends Logging, Transactionable, SearchPathable { +export interface UpsertOptions extends Logging, Transactionable, SearchPathable, Hookable { /** * The fields to insert / update. Defaults to all fields */ fields?: string[]; - /** - * Run before / after bulk create hooks? - */ - hooks?: boolean; - /** * Return the affected rows (only for postgres) */ @@ -713,7 +719,7 @@ export interface UpsertOptions extends Logging, Transactionable, SearchPathable /** * Options for Model.bulkCreate method */ -export interface BulkCreateOptions extends Logging, Transactionable { +export interface BulkCreateOptions extends Logging, Transactionable, Hookable { /** * Fields to insert (defaults to all fields) */ @@ -725,11 +731,6 @@ export interface BulkCreateOptions extends Logging, Transactionable { */ validate?: boolean; - /** - * Run before / after bulk create hooks? - */ - hooks?: boolean; - /** * Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if * options.hooks is true. @@ -763,7 +764,7 @@ export interface BulkCreateOptions extends Logging, Transactionable { /** * The options passed to Model.destroy in addition to truncate */ -export interface TruncateOptions extends Logging, Transactionable, Filterable { +export interface TruncateOptions extends Logging, Transactionable, Filterable, Hookable { /** * Only used in conjuction with TRUNCATE. Truncates all tables that have foreign-key references to the * named table, or to any tables added to the group due to CASCADE. @@ -772,11 +773,6 @@ export interface TruncateOptions extends Logging, Transactionable, Filterable { */ cascade?: boolean; - /** - * Run before / after bulk destroy hooks? - */ - hooks?: boolean; - /** * If set to true, destroy will SELECT all records matching the where parameter and will execute before / * after destroy hooks on each row @@ -814,11 +810,7 @@ export interface DestroyOptions extends TruncateOptions { /** * Options for Model.restore */ -export interface RestoreOptions extends Logging, Transactionable, Filterable { - /** - * Run before / after bulk restore hooks? - */ - hooks?: boolean; +export interface RestoreOptions extends Logging, Transactionable, Filterable, Hookable { /** * If set to true, restore will find all records within the where parameter and will execute before / after @@ -835,7 +827,7 @@ export interface RestoreOptions extends Logging, Transactionable, Filterable { /** * Options used for Model.update */ -export interface UpdateOptions extends Logging, Transactionable, Paranoid { +export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hookable { /** * Options to describe the scope of the search. */ @@ -854,13 +846,6 @@ export interface UpdateOptions extends Logging, Transactionable, Paranoid { */ validate?: boolean; - /** - * Run before / after bulk update hooks? - * - * @default true - */ - hooks?: boolean; - /** * Whether or not to update the side effects of any virtual setters. * @@ -913,7 +898,7 @@ export interface AggregateOptions extends QueryOpt /** * Options used for Instance.increment method */ -export interface IncrementDecrementOptions extends Logging, Transactionable, Silent, SearchPathable, Filterable {} +export interface IncrementDecrementOptions extends Logging, Transactionable, Silent, SearchPathable, Filterable { } /** * Options used for Instance.increment method @@ -930,7 +915,7 @@ export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptio /** * Options used for Instance.restore method */ -export interface InstanceRestoreOptions extends Logging, Transactionable {} +export interface InstanceRestoreOptions extends Logging, Transactionable { } /** * Options used for Instance.destroy method @@ -945,7 +930,7 @@ export interface InstanceDestroyOptions extends Logging, Transactionable { /** * Options used for Instance.update method */ -export interface InstanceUpdateOptions extends SaveOptions, SetOptions, Filterable {} +export interface InstanceUpdateOptions extends SaveOptions, SetOptions, Filterable { } /** * Options used for Instance.set method @@ -1512,7 +1497,7 @@ export interface ModelOptions { /** * Options passed to [[Model.init]] */ -export interface InitOptions extends ModelOptions { +export interface InitOptions extends ModelOptions { /** * The sequelize connection. Required ATM. */ @@ -1639,10 +1624,10 @@ export abstract class Model extends Hooks { * @param options */ public static schema( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, schema: string, options?: SchemaOptions - ): { new (): M } & typeof Model; + ): { new(): M } & typeof Model; /** * Get the tablename of the model, taking schema into account. The method will return The name as a string @@ -1708,7 +1693,7 @@ export abstract class Model extends Hooks { * @return Model A reference to the model, with the scope(s) applied. Calling scope again on the returned * model will clear the previous scope. */ - public static scope( + public static scope( this: M, options?: string | ScopeOptions | (string | ScopeOptions)[] | WhereAttributeHash ): M; @@ -1786,7 +1771,7 @@ export abstract class Model extends Hooks { * * @see {Sequelize#query} */ - public static findAll(this: { new (): M } & typeof Model, options?: FindOptions): Promise; + public static findAll(this: { new(): M } & typeof Model, options?: FindOptions): Promise; /** * Search for a single instance by its primary key. This applies LIMIT 1, so the listener will @@ -1809,7 +1794,7 @@ export abstract class Model extends Hooks { */ public static findOne(this: { new (): M } & typeof Model, options: NonNullFindOptions): Promise; public static findOne( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, options?: FindOptions ): Promise; @@ -1823,7 +1808,7 @@ export abstract class Model extends Hooks { * which case the complete data result is returned. */ public static aggregate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, field: keyof M, aggregateFunction: string, options?: AggregateOptions @@ -1877,7 +1862,7 @@ export abstract class Model extends Hooks { * profiles will be counted */ public static findAndCountAll( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, options?: FindAndCountOptions ): Promise<{ rows: M[]; count: number }>; @@ -1885,7 +1870,7 @@ export abstract class Model extends Hooks { * Find the maximum value of field */ public static max( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, field: keyof M, options?: AggregateOptions ): Promise; @@ -1894,7 +1879,7 @@ export abstract class Model extends Hooks { * Find the minimum value of field */ public static min( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, field: keyof M, options?: AggregateOptions ): Promise; @@ -1903,7 +1888,7 @@ export abstract class Model extends Hooks { * Find the sum of field */ public static sum( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, field: keyof M, options?: AggregateOptions ): Promise; @@ -1912,7 +1897,7 @@ export abstract class Model extends Hooks { * Builds a new model instance. Values is an object of key value pairs, must be defined but can be empty. */ public static build( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, record?: object, options?: BuildOptions ): M; @@ -1921,7 +1906,7 @@ export abstract class Model extends Hooks { * Undocumented bulkBuild */ public static bulkBuild( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, records: object[], options?: BuildOptions ): M[]; @@ -1930,7 +1915,7 @@ export abstract class Model extends Hooks { * Builds a new model instance and calls save on it. */ public static create( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, values?: object, options?: CreateOptions ): Promise; @@ -1941,7 +1926,7 @@ export abstract class Model extends Hooks { * The successfull result of the promise will be (instance, initialized) - Make sure to use `.then(([...]))` */ public static findOrBuild( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, options: FindOrCreateOptions ): Promise<[M, boolean]>; @@ -1957,7 +1942,7 @@ export abstract class Model extends Hooks { * will be created instead, and any unique constraint violation will be handled internally. */ public static findOrCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, options: FindOrCreateOptions ): Promise<[M, boolean]>; @@ -1966,7 +1951,7 @@ export abstract class Model extends Hooks { * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again */ public static findCreateFind( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, options: FindOrCreateOptions ): Promise<[M, boolean]>; @@ -1990,16 +1975,16 @@ export abstract class Model extends Hooks { * whether the row was inserted or not. */ public static upsert( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, values: object, options?: UpsertOptions & { returning?: false | undefined } ): Promise; - public static upsert ( - this: { new (): M } & typeof Model, + public static upsert( + this: { new(): M } & typeof Model, values: object, options?: UpsertOptions & { returning: true } - ): Promise<[ M, boolean ]>; + ): Promise<[M, boolean]>; /** * Create and insert multiple instances in bulk. @@ -2013,7 +1998,7 @@ export abstract class Model extends Hooks { * @param records List of objects (key/value pairs) to create instances from */ public static bulkCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, records: object[], options?: BulkCreateOptions ): Promise; @@ -2041,7 +2026,7 @@ export abstract class Model extends Hooks { * affected rows (only supported in postgres with `options.returning` true.) */ public static update( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, values: object, options: UpdateOptions ): Promise<[number, M[]]>; @@ -2050,7 +2035,7 @@ export abstract class Model extends Hooks { * Increments a single field. */ public static increment( - this: { new (): M }, + this: { new(): M }, field: K, options: IncrementDecrementOptionsWithBy ): Promise; @@ -2059,7 +2044,7 @@ export abstract class Model extends Hooks { * Increments multiple fields by the same value. */ public static increment( - this: { new (): M }, + this: { new(): M }, fields: K[], options: IncrementDecrementOptionsWithBy ): Promise; @@ -2068,7 +2053,7 @@ export abstract class Model extends Hooks { * Increments multiple fields by different values. */ public static increment( - this: { new (): M }, + this: { new(): M }, fields: { [key in K]?: number }, options: IncrementDecrementOptions ): Promise; @@ -2091,12 +2076,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeValidate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; public static beforeValidate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; @@ -2107,12 +2092,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterValidate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; public static afterValidate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; @@ -2123,12 +2108,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with attributes, options */ public static beforeCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (attributes: M, options: CreateOptions) => HookReturn ): void; public static beforeCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (attributes: M, options: CreateOptions) => HookReturn ): void; @@ -2139,12 +2124,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with attributes, options */ public static afterCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (attributes: M, options: CreateOptions) => HookReturn ): void; public static afterCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (attributes: M, options: CreateOptions) => HookReturn ): void; @@ -2155,12 +2140,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeDestroy( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; public static beforeDestroy( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; @@ -2171,12 +2156,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterDestroy( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; public static afterDestroy( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; @@ -2187,12 +2172,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeUpdate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: UpdateOptions) => HookReturn ): void; public static beforeUpdate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: UpdateOptions) => HookReturn ): void; @@ -2203,12 +2188,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterUpdate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: UpdateOptions) => HookReturn ): void; public static afterUpdate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: UpdateOptions) => HookReturn ): void; @@ -2219,12 +2204,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeSave( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; public static beforeSave( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; @@ -2235,12 +2220,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterSave( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; public static afterSave( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; @@ -2251,12 +2236,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instances, options */ public static beforeBulkCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; public static beforeBulkCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; @@ -2267,12 +2252,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instances, options */ public static afterBulkCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; public static afterBulkCreate( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; @@ -2355,12 +2340,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance(s), options */ public static afterFind( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, name: string, fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn ): void; public static afterFind( - this: { new (): M } & typeof Model, + this: { new(): M } & typeof Model, fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn ): void; @@ -2743,6 +2728,6 @@ export abstract class Model extends Hooks { export type ModelType = typeof Model; -export type ModelCtor = { new (): M } & ModelType; +export type ModelCtor = { new(): M } & ModelType; export default Model; diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 4098ff763f00..b9ea929f7296 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -20,6 +20,7 @@ import { WhereAttributeHash, WhereOperators, ModelCtor, + Hookable, } from './model'; import { ModelManager } from './model-manager'; import * as Op from './operators'; @@ -46,7 +47,7 @@ export interface SyncAlterOptions { /** * Sync Options */ -export interface SyncOptions extends Logging { +export interface SyncOptions extends Logging, Hookable { /** * If force is true, each DAO will do DROP TABLE IF EXISTS ..., before it tries to create its own table */ @@ -74,13 +75,9 @@ export interface SyncOptions extends Logging { */ searchPath?: string; - /** - * If hooks is true then beforeSync, afterSync, beforeBulkSync, afterBulkSync hooks will be called - */ - hooks?: boolean; } -export interface DefaultSetOptions {} +export interface DefaultSetOptions { } /** * Connection Pool options @@ -170,7 +167,7 @@ export interface Config { }; } -export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql'; +export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql'; export interface RetryOptions { match?: (RegExp | string | Function)[]; @@ -380,7 +377,7 @@ export interface Options extends Logging { retry?: RetryOptions; } -export interface QueryOptionsTransactionRequired {} +export interface QueryOptionsTransactionRequired { } /** * This is the main class, the entry point to sequelize. To use it, you just need to @@ -1080,7 +1077,7 @@ export class Sequelize extends Hooks { * Returns the database name. */ - public getDatabaseName() : string; + public getDatabaseName(): string; /** * Returns an instance of QueryInterface. From 12700ca0e1492e8ca4570e9f2bb06d6737fa3bdf Mon Sep 17 00:00:00 2001 From: Andrew Heuermann Date: Tue, 21 Jan 2020 23:19:24 -0600 Subject: [PATCH 034/414] fix(associations): gets on many-to-many with non-primary target key (#11778) --- lib/associations/belongs-to-many.js | 3 + .../associations/belongs-to-many.test.js | 79 +++++++++++++++++++ .../unit/associations/belongs-to-many.test.js | 41 +++++++++- 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 9fcbe758b627..54dbaf8b1b68 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -355,6 +355,7 @@ class BelongsToMany extends Association { }); this.oneFromSource = new HasOne(this.source, this.through.model, { foreignKey: this.foreignKey, + sourceKey: this.sourceKey, as: this.through.model.name }); @@ -366,6 +367,7 @@ class BelongsToMany extends Association { }); this.oneFromTarget = new HasOne(this.target, this.through.model, { foreignKey: this.otherKey, + sourceKey: this.targetKey, as: this.through.model.name }); @@ -376,6 +378,7 @@ class BelongsToMany extends Association { this.paired.oneFromTarget = new HasOne(this.paired.target, this.paired.through.model, { foreignKey: this.paired.otherKey, + sourceKey: this.paired.targetKey, as: this.paired.through.model.name }); } diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 84449e021577..bd3dcf3c6264 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -731,6 +731,85 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); }); + it('supports non primary key attributes for joins for getting associations (sourceKey/targetKey)', function() { + const User = this.sequelize.define('User', { + userId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: DataTypes.UUIDV4 + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + field: 'user_second_id' + } + }, { + tableName: 'tbl_user', + indexes: [ + { + unique: true, + fields: ['user_second_id'] + } + ] + }); + + const Group = this.sequelize.define('Group', { + groupId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: DataTypes.UUIDV4 + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + field: 'group_second_id' + } + }, { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'] + } + ] + }); + + User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId', targetKey: 'groupSecondId' }); + Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); + + return this.sequelize.sync({ force: true }).then(() => { + return Promise.join( + User.create(), + User.create(), + Group.create(), + Group.create() + ).then(([user1, user2, group1, group2]) => { + return Promise.join(user1.addGroup(group1), user2.addGroup(group2)) + .then(() => { + return Promise.join( + user1.getGroups(), + user2.getGroups(), + group1.getUsers(), + group2.getUsers() + ); + }).then(([groups1, groups2, users1, users2]) => { + expect(groups1.length).to.be.equal(1); + expect(groups1[0].id).to.be.equal(group1.id); + expect(groups2.length).to.be.equal(1); + expect(groups2[0].id).to.be.equal(group2.id); + expect(users1.length).to.be.equal(1); + expect(users1[0].id).to.be.equal(user1.id); + expect(users2.length).to.be.equal(1); + expect(users2[0].id).to.be.equal(user2.id); + }); + }); + }); + }); + it('supports non primary key attributes for joins (custom foreignKey)', function() { const User = this.sequelize.define('User', { id: { diff --git a/test/unit/associations/belongs-to-many.test.js b/test/unit/associations/belongs-to-many.test.js index 672187f800f1..1b621a6c4268 100644 --- a/test/unit/associations/belongs-to-many.test.js +++ b/test/unit/associations/belongs-to-many.test.js @@ -332,7 +332,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'productId', 'tagId']); }); - it('should setup hasOne relations to source and target from join model with defined foreign/other keys', function() { + it('should setup hasMany relations to source and target from join model with defined foreign/other keys', function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -406,6 +406,45 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'productId', 'tagId']); }); + it('should setup hasOne relations to source and target from join model with defined source keys', function() { + const Product = this.sequelize.define('Product', { + title: DataTypes.STRING, + productSecondaryId: DataTypes.STRING + }), + Tag = this.sequelize.define('Tag', { + name: DataTypes.STRING, + tagSecondaryId: DataTypes.STRING + }), + ProductTag = this.sequelize.define('ProductTag', { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true + }, + priority: DataTypes.INTEGER + }, { + timestamps: false + }); + + Product.Tags = Product.belongsToMany(Tag, { through: ProductTag, sourceKey: 'productSecondaryId' }); + Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, sourceKey: 'tagSecondaryId' }); + + expect(Product.Tags.oneFromSource).to.be.an.instanceOf(HasOne); + expect(Product.Tags.oneFromTarget).to.be.an.instanceOf(HasOne); + + expect(Tag.Products.oneFromSource).to.be.an.instanceOf(HasOne); + expect(Tag.Products.oneFromTarget).to.be.an.instanceOf(HasOne); + + expect(Tag.Products.oneFromSource.sourceKey).to.equal(Tag.Products.sourceKey); + expect(Tag.Products.oneFromTarget.sourceKey).to.equal(Tag.Products.targetKey); + + expect(Product.Tags.oneFromSource.sourceKey).to.equal(Product.Tags.sourceKey); + expect(Product.Tags.oneFromTarget.sourceKey).to.equal(Product.Tags.targetKey); + + expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); + expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'ProductProductSecondaryId', 'TagTagSecondaryId']); + }); + it('should setup belongsTo relations to source and target from join model with only foreign keys defined', function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING From cfc9685bca66ad9528a854bf28c0198005e100da Mon Sep 17 00:00:00 2001 From: Andrew Vereshchak Date: Wed, 22 Jan 2020 21:18:17 +0200 Subject: [PATCH 035/414] feat(sqlite): automatic path provision for 'options.storage' (#11853) --- lib/dialects/sqlite/connection-manager.js | 17 ++++++++--- package-lock.json | 25 ++++------------ package.json | 2 +- test/integration/configuration.test.js | 27 ++++++++++++++--- .../sqlite/connection-manager.test.js | 30 ++++++++++++------- 5 files changed, 63 insertions(+), 38 deletions(-) diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 5c7f11679c9d..9ecbd73105b7 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -1,5 +1,7 @@ 'use strict'; +const path = require('path'); +const jetpack = require('fs-jetpack'); const AbstractConnectionManager = require('../abstract/connection-manager'); const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); @@ -44,19 +46,26 @@ class ConnectionManager extends AbstractConnectionManager { getConnection(options) { options = options || {}; options.uuid = options.uuid || 'default'; - options.inMemory = (this.sequelize.options.storage || this.sequelize.options.host || ':memory:') === ':memory:' ? 1 : 0; + options.storage = this.sequelize.options.storage || this.sequelize.options.host || ':memory:'; + options.inMemory = options.storage === ':memory:' ? 1 : 0; const dialectOptions = this.sequelize.options.dialectOptions; - options.readWriteMode = dialectOptions && dialectOptions.mode; + const defaultReadWriteMode = this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE; + + options.readWriteMode = dialectOptions && dialectOptions.mode || defaultReadWriteMode; if (this.connections[options.inMemory || options.uuid]) { return Promise.resolve(this.connections[options.inMemory || options.uuid]); } + if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) { + jetpack.dir(path.dirname(options.storage)); // automatic path provision for `options.storage` + } + return new Promise((resolve, reject) => { this.connections[options.inMemory || options.uuid] = new this.lib.Database( - this.sequelize.options.storage || this.sequelize.options.host || ':memory:', - options.readWriteMode || this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE, // default mode + options.storage, + options.readWriteMode, err => { if (err) return reject(new sequelizeErrors.ConnectionError(err)); debug(`connection acquired ${options.uuid}`); diff --git a/package-lock.json b/package-lock.json index 2e6fb392b484..d319417cc844 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1635,8 +1635,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -1751,7 +1750,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2287,8 +2285,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", @@ -4145,7 +4142,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-2.2.3.tgz", "integrity": "sha512-MldfoKMz2NwpvP3UFfVXLp4NCncy9yxGamgBK6hofFaisnWoGvgkAyTtKwcq++leztgZuM4ywrZEaUtiyVfWgA==", - "dev": true, "requires": { "minimatch": "^3.0.2", "rimraf": "^2.6.3" @@ -4173,8 +4169,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", @@ -4353,7 +4348,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4913,7 +4907,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -4922,8 +4915,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -6530,7 +6522,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10926,7 +10917,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -11161,8 +11151,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -11846,7 +11835,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -13738,8 +13726,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index 9d9c134bdab2..23201711e2b1 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "bluebird": "^3.7.1", "debug": "^4.1.1", "dottie": "^2.0.0", + "fs-jetpack": "^2.2.3", "inflection": "1.12.0", "lodash": "^4.17.15", "moment": "^2.24.0", @@ -64,7 +65,6 @@ "eslint": "^6.8.0", "eslint-plugin-jsdoc": "^4.1.1", "eslint-plugin-mocha": "^6.2.2", - "fs-jetpack": "^2.2.2", "husky": "^1.3.1", "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js index d43289aaf6a7..2c63ac341ddf 100644 --- a/test/integration/configuration.test.js +++ b/test/integration/configuration.test.js @@ -17,12 +17,31 @@ if (dialect === 'sqlite') { describe(Support.getTestDialectTeaser('Configuration'), () => { describe('Connections problems should fail with a nice message', () => { it('when we don\'t have the correct server details', () => { - const seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, { storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect }); + const options = { + logging: false, + host: '0.0.0.1', + port: config[dialect].port, + dialect + }; + + const constructorArgs = [ + config[dialect].database, + config[dialect].username, + config[dialect].password, + options + ]; + + let willBeRejectedWithArgs = [[Sequelize.HostNotReachableError, Sequelize.InvalidConnectionError]]; + if (dialect === 'sqlite') { + options.storage = '/path/to/no/where/land'; + options.dialectOptions = { mode: sqlite3.OPEN_READONLY }; // SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors. - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file'); + willBeRejectedWithArgs = [Sequelize.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file']; } - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith([Sequelize.HostNotReachableError, Sequelize.InvalidConnectionError]); + + const seq = new Sequelize(...constructorArgs); + return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(...willBeRejectedWithArgs); }); it('when we don\'t have the correct login information', () => { @@ -88,7 +107,7 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { ); }) .then(() => { - // By default, sqlite creates a connection that's READWRITE | CREATE + // By default, sqlite creates a connection that's READWRITE | CREATE const sequelize = new Sequelize('sqlite://foo', { storage: p }); diff --git a/test/integration/dialects/sqlite/connection-manager.test.js b/test/integration/dialects/sqlite/connection-manager.test.js index fe62034d7930..8a2ac9a04070 100644 --- a/test/integration/dialects/sqlite/connection-manager.test.js +++ b/test/integration/dialects/sqlite/connection-manager.test.js @@ -1,24 +1,25 @@ 'use strict'; const chai = require('chai'); -const fs = require('fs'); -const path = require('path'); +const jetpack = require('fs-jetpack').cwd(__dirname); const expect = chai.expect; const Support = require('../../support'); const dialect = Support.getTestDialect(); const DataTypes = require('../../../../lib/data-types'); const fileName = `${Math.random()}_test.sqlite`; +const folderName = `${Math.random()}_test_folder`; if (dialect === 'sqlite') { describe('[SQLITE Specific] Connection Manager', () => { after(() => { - fs.unlinkSync(path.join(__dirname, fileName)); + jetpack.remove(fileName); + jetpack.remove(folderName); }); it('close connection and remove journal and wal files', function() { const sequelize = Support.createSequelizeInstance({ - storage: path.join(__dirname, fileName) + storage: jetpack.path(fileName) }); const User = sequelize.define('User', { username: DataTypes.STRING }); @@ -32,19 +33,28 @@ if (dialect === 'sqlite') { }); }) .then(() => { - expect(fs.existsSync(path.join(__dirname, fileName))).to.be.true; - expect(fs.existsSync(path.join(__dirname, `${fileName}-shm`)), 'shm file should exists').to.be.true; - expect(fs.existsSync(path.join(__dirname, `${fileName}-wal`)), 'wal file should exists').to.be.true; + expect(jetpack.exists(fileName)).to.be.equal('file'); + expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); + expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); return sequelize.close(); }) .then(() => { - expect(fs.existsSync(path.join(__dirname, fileName))).to.be.true; - expect(fs.existsSync(path.join(__dirname, `${fileName}-shm`)), 'shm file exists').to.be.false; - expect(fs.existsSync(path.join(__dirname, `${fileName}-wal`)), 'wal file exists').to.be.false; + expect(jetpack.exists(fileName)).to.be.equal('file'); + expect(jetpack.exists(`${fileName}-shm`), 'shm file exists').to.be.false; + expect(jetpack.exists(`${fileName}-wal`), 'wal file exists').to.be.false; return this.sequelize.query('PRAGMA journal_mode = DELETE'); }); }); + + it('automatic path provision for `options.storage`', () => { + const p = jetpack.path(folderName, fileName); + return Support.createSequelizeInstance({ storage: p }) + .define('User', { username: DataTypes.STRING }) + .sync({ force: true }).then(() => { + expect(jetpack.exists(p)).to.be.equal('file'); + }); + }); }); } From 6837a1741f69452efb5827277373f238af4d3230 Mon Sep 17 00:00:00 2001 From: Andrew Vereshchak Date: Thu, 23 Jan 2020 17:55:27 +0200 Subject: [PATCH 036/414] fix(sqlite): moving 'fs-jetpack' back to devDependencies (#11861) using recursive 'fs.mkdirSync()' instead of 'jetpack.dir()' --- lib/dialects/sqlite/connection-manager.js | 5 ++-- package-lock.json | 25 ++++++++++++++----- package.json | 2 +- .../sqlite/connection-manager.test.js | 10 ++++---- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 9ecbd73105b7..47fde4f10a35 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -1,7 +1,7 @@ 'use strict'; +const fs = require('fs'); const path = require('path'); -const jetpack = require('fs-jetpack'); const AbstractConnectionManager = require('../abstract/connection-manager'); const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); @@ -59,7 +59,8 @@ class ConnectionManager extends AbstractConnectionManager { } if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) { - jetpack.dir(path.dirname(options.storage)); // automatic path provision for `options.storage` + // automatic path provision for `options.storage` + fs.mkdirSync(path.dirname(options.storage), { recursive: true }); } return new Promise((resolve, reject) => { diff --git a/package-lock.json b/package-lock.json index d319417cc844..2e6fb392b484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1635,7 +1635,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -1750,6 +1751,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2285,7 +2287,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -4142,6 +4145,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-2.2.3.tgz", "integrity": "sha512-MldfoKMz2NwpvP3UFfVXLp4NCncy9yxGamgBK6hofFaisnWoGvgkAyTtKwcq++leztgZuM4ywrZEaUtiyVfWgA==", + "dev": true, "requires": { "minimatch": "^3.0.2", "rimraf": "^2.6.3" @@ -4169,7 +4173,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "function-bind": { "version": "1.1.1", @@ -4348,6 +4353,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4907,6 +4913,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -4915,7 +4922,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "1.3.5", @@ -6522,6 +6530,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10917,6 +10926,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -11151,7 +11161,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", @@ -11835,6 +11846,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, "requires": { "glob": "^7.1.3" } @@ -13726,7 +13738,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index 23201711e2b1..97f26ed18316 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "bluebird": "^3.7.1", "debug": "^4.1.1", "dottie": "^2.0.0", - "fs-jetpack": "^2.2.3", "inflection": "1.12.0", "lodash": "^4.17.15", "moment": "^2.24.0", @@ -65,6 +64,7 @@ "eslint": "^6.8.0", "eslint-plugin-jsdoc": "^4.1.1", "eslint-plugin-mocha": "^6.2.2", + "fs-jetpack": "^2.2.3", "husky": "^1.3.1", "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", diff --git a/test/integration/dialects/sqlite/connection-manager.test.js b/test/integration/dialects/sqlite/connection-manager.test.js index 8a2ac9a04070..5c525abbd32b 100644 --- a/test/integration/dialects/sqlite/connection-manager.test.js +++ b/test/integration/dialects/sqlite/connection-manager.test.js @@ -8,13 +8,14 @@ const dialect = Support.getTestDialect(); const DataTypes = require('../../../../lib/data-types'); const fileName = `${Math.random()}_test.sqlite`; -const folderName = `${Math.random()}_test_folder`; +const directoryName = `${Math.random()}_test_directory`; +const nestedFileName = jetpack.path(directoryName, 'subdirectory', 'test.sqlite'); if (dialect === 'sqlite') { describe('[SQLITE Specific] Connection Manager', () => { after(() => { jetpack.remove(fileName); - jetpack.remove(folderName); + jetpack.remove(directoryName); }); it('close connection and remove journal and wal files', function() { @@ -49,11 +50,10 @@ if (dialect === 'sqlite') { }); it('automatic path provision for `options.storage`', () => { - const p = jetpack.path(folderName, fileName); - return Support.createSequelizeInstance({ storage: p }) + return Support.createSequelizeInstance({ storage: nestedFileName }) .define('User', { username: DataTypes.STRING }) .sync({ force: true }).then(() => { - expect(jetpack.exists(p)).to.be.equal('file'); + expect(jetpack.exists(nestedFileName)).to.be.equal('file'); }); }); }); From 9d3be832838b3db2326cdbac60d670118b4112ec Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Fri, 24 Jan 2020 02:54:19 -0300 Subject: [PATCH 037/414] refactor(*): use async/await (#11864) --- .../associations/belongs-to-many.test.js | 41 +- .../associations/belongs-to.test.js | 28 +- test/integration/cls.test.js | 51 +- test/integration/model.test.js | 2520 +++++++---------- 4 files changed, 1113 insertions(+), 1527 deletions(-) diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index bd3dcf3c6264..f1059b31b1f8 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -1437,19 +1437,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); }); - it('should count all associations', function() { - return expect(this.user.countTasks({})).to.eventually.equal(2); + it('should count all associations', async function() { + expect(await this.user.countTasks({})).to.equal(2); }); - it('should count filtered associations', function() { - return expect(this.user.countTasks({ - where: { - active: true - } - })).to.eventually.equal(1); + it('should count filtered associations', async function() { + expect(await this.user.countTasks({ where: { active: true } })).to.equal(1); }); - it('should count scoped associations', function() { + it('should count scoped associations', async function() { this.User.belongsToMany(this.Task, { as: 'activeTasks', through: this.UserTask, @@ -1458,12 +1454,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { } }); - return expect(this.user.countActiveTasks({})).to.eventually.equal(1); + expect(await this.user.countActiveTasks({})).to.equal(1); }); - it('should count scoped through associations', function() { - const user = this.user; - + it('should count scoped through associations', async function() { this.User.belongsToMany(this.Task, { as: 'startedTasks', through: { @@ -1474,20 +1468,13 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { } }); - return Promise.join( - this.Task.create().then(task => { - return user.addTask(task, { - through: { started: true } - }); - }), - this.Task.create().then(task => { - return user.addTask(task, { - through: { started: true } - }); - }) - ).then(() => { - return expect(user.countStartedTasks({})).to.eventually.equal(2); - }); + for (let i = 0; i < 2; i++) { + await this.user.addTask(await this.Task.create(), { + through: { started: true } + }); + } + + expect(await this.user.countStartedTasks({})).to.equal(2); }); }); diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index 4537576f52ff..6b12840acc34 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -381,27 +381,19 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { }); }); - it('supports setting same association twice', function() { - const Home = this.sequelize.define('home', {}), - User = this.sequelize.define('user'); + it('supports setting same association twice', async function() { + const Home = this.sequelize.define('home', {}); + const User = this.sequelize.define('user'); Home.belongsTo(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Home.create(), - User.create() - ]); - }).then(([home, user]) => { - ctx.home = home; - ctx.user = user; - return home.setUser(user); - }).then(() => { - return ctx.home.setUser(ctx.user); - }).then(() => { - return expect(ctx.home.getUser()).to.eventually.have.property('id', ctx.user.get('id')); - }); + await this.sequelize.sync({ force: true }); + const [home, user] = await Promise.all([ + Home.create(), + User.create() + ]); + await home.setUser(user); + expect(await home.getUser()).to.have.property('id', user.id); }); }); diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js index f0be74cbf8f2..83bae78e1fd2 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -19,46 +19,37 @@ if (current.dialect.supports.transactions) { delete Sequelize._cls; }); - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - this.ns = cls.getNamespace('sequelize'); - this.User = this.sequelize.define('user', { - name: Sequelize.STRING - }); - return this.sequelize.sync({ force: true }); + beforeEach(async function() { + this.sequelize = await Support.prepareTransactionTest(this.sequelize); + this.ns = cls.getNamespace('sequelize'); + this.User = this.sequelize.define('user', { + name: Sequelize.STRING }); + await this.sequelize.sync({ force: true }); }); describe('context', () => { it('does not use continuation storage on manually managed transactions', function() { - return Sequelize._clsRun(() => { - return this.sequelize.transaction().then(transaction => { - expect(this.ns.get('transaction')).not.to.be.ok; - return transaction.rollback(); - }); + return Sequelize._clsRun(async () => { + const transaction = await this.sequelize.transaction(); + expect(this.ns.get('transaction')).not.to.be.ok; + await transaction.rollback(); }); }); - it('supports several concurrent transactions', function() { + it('supports several concurrent transactions', async function() { let t1id, t2id; - return Promise.join( - this.sequelize.transaction(() => { + await Promise.all([ + this.sequelize.transaction(async () => { t1id = this.ns.get('transaction').id; - - return Promise.resolve(); }), - this.sequelize.transaction(() => { + this.sequelize.transaction(async () => { t2id = this.ns.get('transaction').id; - - return Promise.resolve(); - }), - () => { - expect(t1id).to.be.ok; - expect(t2id).to.be.ok; - expect(t1id).not.to.equal(t2id); - } - ); + }) + ]); + expect(t1id).to.be.ok; + expect(t2id).to.be.ok; + expect(t1id).not.to.equal(t2id); }); it('supports nested promise chains', function() { @@ -145,8 +136,8 @@ if (current.dialect.supports.transactions) { it('automagically uses the transaction in all calls with async/await', function() { return this.sequelize.transaction(async () => { await this.User.create({ name: 'bob' }); - await expect(this.User.findAll({ transaction: null })).to.eventually.have.length(0); - await expect(this.User.findAll({})).to.eventually.have.length(1); + expect(await this.User.findAll({ transaction: null })).to.have.length(0); + expect(await this.User.findAll({})).to.have.length(1); }); }); }); diff --git a/test/integration/model.test.js b/test/integration/model.test.js index f38f54f3b67f..bf575819c3dc 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -23,7 +23,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { aBool: DataTypes.BOOLEAN }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('constructor', () => { @@ -57,7 +57,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(factorySize).to.equal(factorySize2); }); - it('allows us to predefine the ID column with our own specs', function() { + it('allows us to predefine the ID column with our own specs', async function() { const User = this.sequelize.define('UserCol', { id: { type: Sequelize.STRING, @@ -66,9 +66,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return expect(User.create({ id: 'My own ID!' })).to.eventually.have.property('id', 'My own ID!'); - }); + await User.sync({ force: true }); + expect(await User.create({ id: 'My own ID!' })).to.have.property('id', 'My own ID!'); }); it('throws an error if 2 autoIncrements are passed', function() { @@ -104,7 +103,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }).to.throw(Error, 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field'); }); - it('should allow me to set a default value for createdAt and updatedAt', function() { + it('should allow me to set a default value for createdAt and updatedAt', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER, createdAt: { @@ -117,26 +116,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, { timestamps: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create({ aNumber: 5 }).then(user => { - return UserTable.bulkCreate([ - { aNumber: 10 }, - { aNumber: 12 } - ]).then(() => { - return UserTable.findAll({ where: { aNumber: { [Op.gte]: 10 } } }).then(users => { - expect(moment(user.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); - expect(moment(user.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); - users.forEach(u => { - expect(moment(u.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); - expect(moment(u.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); - }); - }); - }); - }); - }); + await UserTable.sync({ force: true }); + const user = await UserTable.create({ aNumber: 5 }); + await UserTable.bulkCreate([{ aNumber: 10 }, { aNumber: 12 }]); + const users = await UserTable.findAll({ where: { aNumber: { [Op.gte]: 10 } } }); + expect(moment(user.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); + expect(moment(user.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); + for (const u of users) { + expect(moment(u.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); + expect(moment(u.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); + } }); - it('should allow me to set a function as default value', function() { + it('should allow me to set a function as default value', async function() { const defaultFunction = sinon.stub().returns(5); const UserTable = this.sequelize.define('UserCol', { aNumber: { @@ -145,18 +137,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, { timestamps: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create().then(user => { - return UserTable.create().then(user2 => { - expect(user.aNumber).to.equal(5); - expect(user2.aNumber).to.equal(5); - expect(defaultFunction.callCount).to.equal(2); - }); - }); - }); + await UserTable.sync({ force: true }); + const user = await UserTable.create(); + const user2 = await UserTable.create(); + expect(user.aNumber).to.equal(5); + expect(user2.aNumber).to.equal(5); + expect(defaultFunction.callCount).to.equal(2); }); - it('should allow me to override updatedAt, createdAt, and deletedAt fields', function() { + it('should allow me to override updatedAt, createdAt, and deletedAt fields', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER }, { @@ -167,20 +156,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create({ aNumber: 4 }).then(user => { - expect(user.updatedOn).to.exist; - expect(user.dateCreated).to.exist; - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAtThisTime).to.exist; - }); - }); - }); - }); + await UserTable.sync({ force: true }); + const user = await UserTable.create({ aNumber: 4 }); + expect(user.updatedOn).to.exist; + expect(user.dateCreated).to.exist; + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAtThisTime).to.exist; }); - it('should allow me to disable some of the timestamp fields', function() { + it('should allow me to disable some of the timestamp fields', async function() { const UpdatingUser = this.sequelize.define('UpdatingUser', { name: DataTypes.STRING }, { @@ -191,27 +176,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return UpdatingUser.sync({ force: true }).then(() => { - return UpdatingUser.create({ - name: 'heyo' - }).then(user => { - expect(user.createdAt).not.to.exist; - expect(user.false).not.to.exist; // because, you know we might accidentally add a field named 'false' - - user.name = 'heho'; - return user.save().then(user => { - expect(user.updatedAt).not.to.exist; - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAtThisTime).to.exist; - }); - }); - }); - }); - }); + await UpdatingUser.sync({ force: true }); + let user = await UpdatingUser.create({ name: 'heyo' }); + expect(user.createdAt).not.to.exist; + expect(user.false).not.to.exist; // because, you know we might accidentally add a field named 'false' + user.name = 'heho'; + user = await user.save(); + expect(user.updatedAt).not.to.exist; + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAtThisTime).to.exist; }); - it('returns proper defaultValues after save when setter is set', function() { + it('returns proper defaultValues after save when setter is set', async function() { const titleSetter = sinon.spy(), Task = this.sequelize.define('TaskBuild', { title: { @@ -225,16 +202,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Task.sync({ force: true }).then(() => { - return Task.build().save().then(record => { - expect(record.title).to.be.a('string'); - expect(record.title).to.equal(''); - expect(titleSetter.notCalled).to.be.ok; // The setter method should not be invoked for default values - }); - }); + await Task.sync({ force: true }); + const record = await Task.build().save(); + expect(record.title).to.be.a('string'); + expect(record.title).to.equal(''); + expect(titleSetter.notCalled).to.be.ok; // The setter method should not be invoked for default values }); - it('should work with both paranoid and underscored being true', function() { + it('should work with both paranoid and underscored being true', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER }, { @@ -242,16 +217,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { underscored: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create({ aNumber: 30 }).then(() => { - return UserTable.count().then(c => { - expect(c).to.equal(1); - }); - }); - }); + await UserTable.sync({ force: true }); + await UserTable.create({ aNumber: 30 }); + expect(await UserTable.count()).to.equal(1); }); - it('allows multiple column unique keys to be defined', function() { + it('allows multiple column unique keys to be defined', async function() { const User = this.sequelize.define('UserWithUniqueUsername', { username: { type: Sequelize.STRING, unique: 'user_and_email' }, email: { type: Sequelize.STRING, unique: 'user_and_email' }, @@ -259,7 +230,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { bCol: { type: Sequelize.STRING, unique: 'a_and_b' } }); - return User.sync({ force: true, logging: _.after(2, _.once(sql => { + await User.sync({ force: true, logging: _.after(2, _.once(sql => { if (dialect === 'mssql') { expect(sql).to.match(/CONSTRAINT\s*([`"[]?user_and_email[`"\]]?)?\s*UNIQUE\s*\([`"[]?username[`"\]]?, [`"[]?email[`"\]]?\)/); expect(sql).to.match(/CONSTRAINT\s*([`"[]?a_and_b[`"\]]?)?\s*UNIQUE\s*\([`"[]?aCol[`"\]]?, [`"[]?bCol[`"\]]?\)/); @@ -270,61 +241,59 @@ describe(Support.getTestDialectTeaser('Model'), () => { })) }); }); - it('allows unique on column with field aliases', function() { + it('allows unique on column with field aliases', async function() { const User = this.sequelize.define('UserWithUniqueFieldAlias', { userName: { type: Sequelize.STRING, unique: 'user_name_unique', field: 'user_name' } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.queryInterface.showIndex(User.tableName).then(indexes => { - let idxUnique; - if (dialect === 'sqlite') { - expect(indexes).to.have.length(1); - idxUnique = indexes[0]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: undefined }]); - } else if (dialect === 'mysql') { - expect(indexes).to.have.length(2); - idxUnique = indexes[1]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: 'ASC' }]); - expect(idxUnique.type).to.equal('BTREE'); - } else if (dialect === 'postgres') { - expect(indexes).to.have.length(2); - idxUnique = indexes[1]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, order: undefined, length: undefined }]); - } else if (dialect === 'mssql') { - expect(indexes).to.have.length(2); - idxUnique = indexes[1]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, length: undefined, order: 'ASC' }]); - } - }); - }); + await User.sync({ force: true }); + const indexes = await this.sequelize.queryInterface.showIndex(User.tableName); + let idxUnique; + if (dialect === 'sqlite') { + expect(indexes).to.have.length(1); + idxUnique = indexes[0]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: undefined }]); + } else if (dialect === 'mysql') { + expect(indexes).to.have.length(2); + idxUnique = indexes[1]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: 'ASC' }]); + expect(idxUnique.type).to.equal('BTREE'); + } else if (dialect === 'postgres') { + expect(indexes).to.have.length(2); + idxUnique = indexes[1]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, order: undefined, length: undefined }]); + } else if (dialect === 'mssql') { + expect(indexes).to.have.length(2); + idxUnique = indexes[1]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, length: undefined, order: 'ASC' }]); + } }); - it('allows us to customize the error message for unique constraint', function() { + it('allows us to customize the error message for unique constraint', async function() { const User = this.sequelize.define('UserWithUniqueUsername', { username: { type: Sequelize.STRING, unique: { name: 'user_and_email', msg: 'User and email must be unique' } }, email: { type: Sequelize.STRING, unique: 'user_and_email' } }); - return User.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ - User.create({ username: 'tobi', email: 'tobi@tobi.me' }), - User.create({ username: 'tobi', email: 'tobi@tobi.me' })]); - }).catch(Sequelize.UniqueConstraintError, err => { + await User.sync({ force: true }); + await Sequelize.Promise.all([ + User.create({ username: 'tobi', email: 'tobi@tobi.me' }), + User.create({ username: 'tobi', email: 'tobi@tobi.me' }) + ]).catch(Sequelize.UniqueConstraintError, err => { expect(err.message).to.equal('User and email must be unique'); }); }); // If you use migrations to create unique indexes that have explicit names and/or contain fields // that have underscore in their name. Then sequelize must use the index name to map the custom message to the error thrown from db. - it('allows us to map the customized error message with unique constraint name', function() { + it('allows us to map the customized error message with unique constraint name', async function() { // Fake migration style index creation with explicit index definition let User = this.sequelize.define('UserWithUniqueUsername', { user_id: { type: Sequelize.INTEGER }, @@ -340,26 +309,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { }] }); - return User.sync({ force: true }).then(() => { - // Redefine the model to use the index in database and override error message - User = this.sequelize.define('UserWithUniqueUsername', { - user_id: { type: Sequelize.INTEGER, unique: { name: 'user_and_email_index', msg: 'User and email must be unique' } }, - email: { type: Sequelize.STRING, unique: 'user_and_email_index' } - }); - return Sequelize.Promise.all([ - User.create({ user_id: 1, email: 'tobi@tobi.me' }), - User.create({ user_id: 1, email: 'tobi@tobi.me' })]); - }).catch(Sequelize.UniqueConstraintError, err => { + await User.sync({ force: true }); + + // Redefine the model to use the index in database and override error message + User = this.sequelize.define('UserWithUniqueUsername', { + user_id: { type: Sequelize.INTEGER, unique: { name: 'user_and_email_index', msg: 'User and email must be unique' } }, + email: { type: Sequelize.STRING, unique: 'user_and_email_index' } + }); + + await Sequelize.Promise.all([ + User.create({ user_id: 1, email: 'tobi@tobi.me' }), + User.create({ user_id: 1, email: 'tobi@tobi.me' }) + ]).catch(Sequelize.UniqueConstraintError, err => { expect(err.message).to.equal('User and email must be unique'); }); }); - it('should allow the user to specify indexes in options', function() { + it('should allow the user to specify indexes in options', async function() { const indices = [{ name: 'a_b_uniq', unique: true, method: 'BTREE', - fields: ['fieldB', { attribute: 'fieldA', collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', order: 'DESC', length: 5 }] + fields: [ + 'fieldB', + { + attribute: 'fieldA', + collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', + order: 'DESC', + length: 5 + } + ] }]; if (dialect !== 'mssql') { @@ -385,90 +364,85 @@ describe(Support.getTestDialectTeaser('Model'), () => { engine: 'MyISAM' }); - return this.sequelize.sync().then(() => { - return this.sequelize.sync(); // The second call should not try to create the indices again - }).then(() => { - return this.sequelize.queryInterface.showIndex(Model.tableName); - }).then(args => { - let primary, idx1, idx2, idx3; + await this.sequelize.sync(); + await this.sequelize.sync(); // The second call should not try to create the indices again + const args = await this.sequelize.queryInterface.showIndex(Model.tableName); + let primary, idx1, idx2, idx3; - if (dialect === 'sqlite') { - // PRAGMA index_info does not return the primary index - idx1 = args[0]; - idx2 = args[1]; - - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: undefined }, - { attribute: 'fieldA', length: undefined, order: undefined } - ]); - - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined } - ]); - } else if (dialect === 'mssql') { - idx1 = args[0]; + if (dialect === 'sqlite') { + // PRAGMA index_info does not return the primary index + idx1 = args[0]; + idx2 = args[1]; - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: 'ASC', collate: undefined }, - { attribute: 'fieldA', length: undefined, order: 'DESC', collate: undefined } - ]); - } else if (dialect === 'postgres') { - // Postgres returns indexes in alphabetical order - primary = args[2]; - idx1 = args[0]; - idx2 = args[1]; - idx3 = args[2]; - - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: undefined, collate: undefined }, - { attribute: 'fieldA', length: undefined, order: 'DESC', collate: 'en_US' } - ]); - - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined, collate: undefined } - ]); - - expect(idx3.fields).to.deep.equal([ - { attribute: 'fieldD', length: undefined, order: undefined, collate: undefined } - ]); - } else { - // And finally mysql returns the primary first, and then the rest in the order they were defined - primary = args[0]; - idx1 = args[1]; - idx2 = args[2]; + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: undefined }, + { attribute: 'fieldA', length: undefined, order: undefined } + ]); - expect(primary.primary).to.be.ok; + expect(idx2.fields).to.deep.equal([ + { attribute: 'fieldC', length: undefined, order: undefined } + ]); + } else if (dialect === 'mssql') { + idx1 = args[0]; - expect(idx1.type).to.equal('BTREE'); - expect(idx2.type).to.equal('FULLTEXT'); + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: 'ASC', collate: undefined }, + { attribute: 'fieldA', length: undefined, order: 'DESC', collate: undefined } + ]); + } else if (dialect === 'postgres') { + // Postgres returns indexes in alphabetical order + primary = args[2]; + idx1 = args[0]; + idx2 = args[1]; + idx3 = args[2]; + + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: undefined, collate: undefined }, + { attribute: 'fieldA', length: undefined, order: 'DESC', collate: 'en_US' } + ]); - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: 'ASC' }, - { attribute: 'fieldA', length: 5, order: 'ASC' } - ]); + expect(idx2.fields).to.deep.equal([ + { attribute: 'fieldC', length: undefined, order: undefined, collate: undefined } + ]); - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined } - ]); - } + expect(idx3.fields).to.deep.equal([ + { attribute: 'fieldD', length: undefined, order: undefined, collate: undefined } + ]); + } else { + // And finally mysql returns the primary first, and then the rest in the order they were defined + primary = args[0]; + idx1 = args[1]; + idx2 = args[2]; - expect(idx1.name).to.equal('a_b_uniq'); - expect(idx1.unique).to.be.ok; + expect(primary.primary).to.be.ok; - if (dialect !== 'mssql') { - expect(idx2.name).to.equal('models_field_c'); - expect(idx2.unique).not.to.be.ok; - } - }); + expect(idx1.type).to.equal('BTREE'); + expect(idx2.type).to.equal('FULLTEXT'); + + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: 'ASC' }, + { attribute: 'fieldA', length: 5, order: 'ASC' } + ]); + + expect(idx2.fields).to.deep.equal([ + { attribute: 'fieldC', length: undefined, order: undefined } + ]); + } + + expect(idx1.name).to.equal('a_b_uniq'); + expect(idx1.unique).to.be.ok; + + if (dialect !== 'mssql') { + expect(idx2.name).to.equal('models_field_c'); + expect(idx2.unique).not.to.be.ok; + } }); }); describe('build', () => { - it("doesn't create database entries", function() { + it("doesn't create database entries", async function() { this.User.build({ username: 'John Wayne' }); - return this.User.findAll().then(users => { - expect(users).to.have.length(0); - }); + expect(await this.User.findAll()).to.have.length(0); }); it('fills the objects with default values', function() { @@ -671,149 +645,115 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('findOne', () => { if (current.dialect.supports.transactions) { - it('supports the transaction option in the first parameter', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findOne({ where: { username: 'foo' }, transaction: t }).then(user => { - expect(user).to.not.be.null; - return t.rollback(); - }); - }); - }); - }); + it('supports the transaction option in the first parameter', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { + username: Sequelize.STRING, + foo: Sequelize.STRING }); + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const user = await User.findOne({ where: { username: 'foo' }, transaction: t }); + expect(user).to.not.be.null; + await t.rollback(); }); } - it('should not fail if model is paranoid and where is an empty array', function() { + it('should not fail if model is paranoid and where is an empty array', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }) - .then(() => { - return User.create({ username: 'A fancy name' }); - }) - .then(() => { - return User.findOne({ where: [] }); - }) - .then(u => { - expect(u.username).to.equal('A fancy name'); - }); + await User.sync({ force: true }); + await User.create({ username: 'A fancy name' }); + expect((await User.findOne({ where: [] })).username).to.equal('A fancy name'); }); - // https://github.com/sequelize/sequelize/issues/8406 - it('should work if model is paranoid and only operator in where clause is a Symbol', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true } ); - - return User.sync({ force: true }) - .then(() => { - return User.create({ username: 'foo' }); - }) - .then(() => { - return User.findOne({ - where: { - [Op.or]: [ - { username: 'bar' }, - { username: 'baz' } - ] - } - }); - }) - .then(user => { - expect(user).to.not.be.ok; - }); + it('should work if model is paranoid and only operator in where clause is a Symbol (#8406)', async function() { + const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); + + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + expect(await User.findOne({ + where: { + [Op.or]: [ + { username: 'bar' }, + { username: 'baz' } + ] + } + })).to.not.be.ok; }); }); describe('findOrBuild', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findOrBuild({ - where: { username: 'foo' } - }).then(([user1]) => { - return User.findOrBuild({ - where: { username: 'foo' }, - transaction: t - }).then(([user2]) => { - return User.findOrBuild({ - where: { username: 'foo' }, - defaults: { foo: 'asd' }, - transaction: t - }).then(([user3]) => { - expect(user1.isNewRecord).to.be.true; - expect(user2.isNewRecord).to.be.false; - expect(user3.isNewRecord).to.be.false; - return t.commit(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const [user1] = await User.findOrBuild({ + where: { username: 'foo' } + }); + const [user2] = await User.findOrBuild({ + where: { username: 'foo' }, + transaction: t + }); + const [user3] = await User.findOrBuild({ + where: { username: 'foo' }, + defaults: { foo: 'asd' }, + transaction: t + }); + expect(user1.isNewRecord).to.be.true; + expect(user2.isNewRecord).to.be.false; + expect(user3.isNewRecord).to.be.false; + await t.commit(); }); } describe('returns an instance if it already exists', () => { - it('with a single find field', function() { - return this.User.create({ username: 'Username' }).then(user => { - return this.User.findOrBuild({ - where: { username: user.username } - }).then(([_user, initialized]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(initialized).to.be.false; - }); + it('with a single find field', async function() { + const user = await this.User.create({ username: 'Username' }); + const [_user, initialized] = await this.User.findOrBuild({ + where: { username: user.username } }); + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(initialized).to.be.false; }); - it('with multiple find fields', function() { - return this.User.create({ username: 'Username', data: 'data' }).then(user => { - return this.User.findOrBuild({ where: { + it('with multiple find fields', async function() { + const user = await this.User.create({ username: 'Username', data: 'data' }); + const [_user, initialized] = await this.User.findOrBuild({ + where: { username: user.username, data: user.data - } }).then(([_user, initialized]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(_user.data).to.equal('data'); - expect(initialized).to.be.false; - }); + } }); + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(_user.data).to.equal('data'); + expect(initialized).to.be.false; }); - it('builds a new instance with default value.', function() { - const data = { - username: 'Username' - }, - default_values = { - data: 'ThisIsData' - }; - - return this.User.findOrBuild({ - where: data, - defaults: default_values - }).then(([user, initialized]) => { - expect(user.id).to.be.null; - expect(user.username).to.equal('Username'); - expect(user.data).to.equal('ThisIsData'); - expect(initialized).to.be.true; - expect(user.isNewRecord).to.be.true; + it('builds a new instance with default value.', async function() { + const [user, initialized] = await this.User.findOrBuild({ + where: { username: 'Username' }, + defaults: { data: 'ThisIsData' } }); + expect(user.id).to.be.null; + expect(user.username).to.equal('Username'); + expect(user.data).to.equal('ThisIsData'); + expect(initialized).to.be.true; + expect(user.isNewRecord).to.be.true; }); }); }); describe('save', () => { - it('should mapping the correct fields when saving instance. see #10589', function() { + it('should map the correct fields when saving instance (#10589)', async function() { const User = this.sequelize.define('User', { id3: { field: 'id', @@ -832,35 +772,30 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - // Setup - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id3: 94, id: 87, id2: 943 }); - }) - // Test - .then(() => User.findByPk(94)) - .then(user => user.set('id2', 8877)) - .then(user => user.save({ id2: 8877 })) - // Validate - .then(() => User.findByPk(94)) - .then(user => expect(user.id2).to.equal(8877)); + await this.sequelize.sync({ force: true }); + await User.create({ id3: 94, id: 87, id2: 943 }); + const user = await User.findByPk(94); + await user.set('id2', 8877); + await user.save({ id2: 8877 }); + expect((await User.findByPk(94)).id2).to.equal(8877); }); }); describe('update', () => { - it('throws an error if no where clause is given', function() { + it('throws an error if no where clause is given', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return User.update(); - }).then(() => { + await this.sequelize.sync({ force: true }); + try { + await User.update(); throw new Error('Update should throw an error if no where clause is given.'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('Missing where attribute in the options parameter'); - }); + } }); - it('should mapping the correct fields when updating instance. see #10589', function() { + it('should map the correct fields when updating instance (#10589)', async function() { const User = this.sequelize.define('User', { id3: { field: 'id', @@ -879,45 +814,35 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - // Setup - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id3: 94, id: 87, id2: 943 }); - }) - // Test - .then(() => User.findByPk(94)) - .then(user => { - return user.update({ id2: 8877 }); - }) - // Validate - .then(() => User.findByPk(94)) - .then(user => expect(user.id2).to.equal(8877)); + await this.sequelize.sync({ force: true }); + await User.create({ id3: 94, id: 87, id2: 943 }); + const user = await User.findByPk(94); + await user.update({ id2: 8877 }); + expect((await User.findByPk(94)).id2).to.equal(8877); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(() => { - return sequelize.transaction().then(t => { - return User.update({ username: 'bar' }, { where: { username: 'foo' }, transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].username).to.equal('foo'); - expect(users2[0].username).to.equal('bar'); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + + const t = await sequelize.transaction(); + await User.update({ username: 'bar' }, { + where: { username: 'foo' }, + transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].username).to.equal('foo'); + expect(users2[0].username).to.equal('bar'); + await t.rollback(); }); } - it('updates the attributes that we select only without updating createdAt', function() { + it('updates the attributes that we select only without updating createdAt', async function() { const User = this.sequelize.define('User1', { username: Sequelize.STRING, secretValue: Sequelize.STRING @@ -926,27 +851,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ username: 'Peter', secretValue: '42' }).then(user => { - return user.update({ secretValue: '43' }, { - fields: ['secretValue'], - logging(sql) { - test = true; - if (dialect === 'mssql') { - expect(sql).to.not.contain('createdAt'); - } else { - expect(sql).to.match(/UPDATE\s+[`"]+User1s[`"]+\s+SET\s+[`"]+secretValue[`"]=(\$1|\?),[`"]+updatedAt[`"]+=(\$2|\?)\s+WHERE [`"]+id[`"]+\s=\s(\$3|\?)/); - } - }, - returning: ['*'] - }); - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + const user = await User.create({ username: 'Peter', secretValue: '42' }); + await user.update({ secretValue: '43' }, { + fields: ['secretValue'], + logging(sql) { + test = true; + if (dialect === 'mssql') { + expect(sql).to.not.contain('createdAt'); + } else { + expect(sql).to.match(/UPDATE\s+[`"]+User1s[`"]+\s+SET\s+[`"]+secretValue[`"]=(\$1|\?),[`"]+updatedAt[`"]+=(\$2|\?)\s+WHERE [`"]+id[`"]+\s=\s(\$3|\?)/); + } + }, + returning: ['*'] }); + expect(test).to.be.true; }); - it('allows sql logging of updated statements', function() { + it('allows sql logging of updated statements', async function() { const User = this.sequelize.define('User', { name: Sequelize.STRING, bio: Sequelize.TEXT @@ -954,125 +876,114 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ name: 'meg', bio: 'none' }).then(u => { - expect(u).to.exist; - return u.update({ name: 'brian' }, { - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('UPDATE'); - } - }); - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + const u = await User.create({ name: 'meg', bio: 'none' }); + expect(u).to.exist; + await u.update({ name: 'brian' }, { + logging(sql) { + test = true; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('UPDATE'); + } }); + expect(test).to.be.true; }); - it('updates only values that match filter', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('updates only values that match filter', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(3); + { username: 'Bob', secretValue: '43' } + ]; - users.forEach(user => { - if (user.secretValue === '42') { - expect(user.username).to.equal('Bill'); - } else { - expect(user.username).to.equal('Bob'); - } - }); + await this.User.bulkCreate(data); + await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users).to.have.lengthOf(3); - }); - }); - }); + for (const user of users) { + if (user.secretValue === '42') { + expect(user.username).to.equal('Bill'); + } else { + expect(user.username).to.equal('Bob'); + } + } }); - it('throws an error if where has a key with undefined value', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('throws an error if where has a key with undefined value', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42', username: undefined } }).then(() => { - throw new Error('Update should throw an error if where has a key with undefined value'); - }, err => { - expect(err).to.be.an.instanceof(Error); - expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + try { + await this.User.update({ username: 'Bill' }, { + where: { + secretValue: '42', + username: undefined + } }); - }); + throw new Error('Update should throw an error if where has a key with undefined value'); + } catch (err) { + expect(err).to.be.an.instanceof(Error); + expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); + } }); - it('updates only values that match the allowed fields', function() { + it('updates only values that match the allowed fields', async function() { const data = [{ username: 'Peter', secretValue: '42' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill', secretValue: '43' }, { where: { secretValue: '42' }, fields: ['username'] }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(1); - - const user = users[0]; - expect(user.username).to.equal('Bill'); - expect(user.secretValue).to.equal('42'); - }); - }); - }); + await this.User.bulkCreate(data); + await this.User.update({ username: 'Bill', secretValue: '43' }, { where: { secretValue: '42' }, fields: ['username'] }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users).to.have.lengthOf(1); + expect(users[0].username).to.equal('Bill'); + expect(users[0].secretValue).to.equal('42'); }); - it('updates with casting', function() { - return this.User.create({ - username: 'John' - }).then(() => { - return this.User.update({ username: this.sequelize.cast('1', dialect === 'mssql' ? 'nvarchar' : 'char') }, { where: { username: 'John' } }).then(() => { - return this.User.findAll().then(users => { - expect(users[0].username).to.equal('1'); - }); - }); + it('updates with casting', async function() { + await this.User.create({ username: 'John' }); + await this.User.update({ + username: this.sequelize.cast('1', dialect === 'mssql' ? 'nvarchar' : 'char') + }, { + where: { username: 'John' } }); + expect((await this.User.findOne()).username).to.equal('1'); }); - it('updates with function and column value', function() { - return this.User.create({ - username: 'John' - }).then(() => { - return this.User.update({ username: this.sequelize.fn('upper', this.sequelize.col('username')) }, { where: { username: 'John' } }).then(() => { - return this.User.findAll().then(users => { - expect(users[0].username).to.equal('JOHN'); - }); - }); + it('updates with function and column value', async function() { + await this.User.create({ username: 'John' }); + await this.User.update({ + username: this.sequelize.fn('upper', this.sequelize.col('username')) + }, { + where: { username: 'John' } }); + expect((await this.User.findOne()).username).to.equal('JOHN'); }); - it('does not update virtual attributes', function() { + it('does not update virtual attributes', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING, virtual: Sequelize.VIRTUAL }); - return User.create({ - username: 'jan' - }).then(() => { - return User.update({ - username: 'kurt', - virtual: 'test' - }, { - where: { - username: 'jan' - } - }); - }).then(() => { - return User.findAll(); - }).then(([user]) => { - expect(user.username).to.equal('kurt'); + await User.create({ username: 'jan' }); + await User.update({ + username: 'kurt', + virtual: 'test' + }, { + where: { + username: 'jan' + } }); + const user = await User.findOne(); + expect(user.username).to.equal('kurt'); + expect(user.virtual).to.not.equal('test'); }); - it('doesn\'t update attributes that are altered by virtual setters when option is enabled', function() { + it('doesn\'t update attributes that are altered by virtual setters when option is enabled', async function() { const User = this.sequelize.define('UserWithVirtualSetters', { username: Sequelize.STRING, illness_name: Sequelize.STRING, @@ -1086,29 +997,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'Jan', - illness_name: 'Headache', - illness_pain: 5 - }); - }).then(() => { - return User.update({ - illness: { pain: 10, name: 'Backache' } - }, { - where: { - username: 'Jan' - }, - sideEffects: false - }); - }).then(() => { - return User.findAll(); - }).then(([user]) => { - expect(user.illness_pain).to.be.equal(5); + await User.sync({ force: true }); + await User.create({ + username: 'Jan', + illness_name: 'Headache', + illness_pain: 5 + }); + await User.update({ + illness: { pain: 10, name: 'Backache' } + }, { + where: { + username: 'Jan' + }, + sideEffects: false }); + expect((await User.findOne()).illness_pain).to.be.equal(5); }); - it('updates attributes that are altered by virtual setters', function() { + it('updates attributes that are altered by virtual setters', async function() { const User = this.sequelize.define('UserWithVirtualSetters', { username: Sequelize.STRING, illness_name: Sequelize.STRING, @@ -1122,334 +1028,277 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'Jan', - illness_name: 'Headache', - illness_pain: 5 - }); - }).then(() => { - return User.update({ - illness: { pain: 10, name: 'Backache' } - }, { - where: { - username: 'Jan' - } - }); - }).then(() => { - return User.findAll(); - }).then(([user]) => { - expect(user.illness_pain).to.be.equal(10); + await User.sync({ force: true }); + await User.create({ + username: 'Jan', + illness_name: 'Headache', + illness_pain: 5 + }); + await User.update({ + illness: { pain: 10, name: 'Backache' } + }, { + where: { + username: 'Jan' + } }); + expect((await User.findOne()).illness_pain).to.be.equal(10); }); - it('should properly set data when individualHooks are true', function() { + it('should properly set data when individualHooks are true', async function() { this.User.beforeUpdate(instance => { instance.set('intVal', 1); }); - return this.User.create({ username: 'Peter' }).then(user => { - return this.User.update({ data: 'test' }, { where: { id: user.id }, individualHooks: true }).then(() => { - return this.User.findByPk(user.id).then(userUpdated => { - expect(userUpdated.intVal).to.be.equal(1); - }); - }); + const user = await this.User.create({ username: 'Peter' }); + await this.User.update({ data: 'test' }, { + where: { id: user.id }, + individualHooks: true }); + expect((await this.User.findByPk(user.id)).intVal).to.be.equal(1); }); - it('sets updatedAt to the current timestamp', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('sets updatedAt to the current timestamp', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; + { username: 'Bob', secretValue: '43' } + ]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }); - }).then(users => { - this.updatedAt = users[0].updatedAt; + await this.User.bulkCreate(data); + let users = await this.User.findAll({ order: ['id'] }); + this.updatedAt = users[0].updatedAt; - expect(this.updatedAt).to.be.ok; - expect(this.updatedAt).to.equalTime(users[2].updatedAt); // All users should have the same updatedAt + expect(this.updatedAt).to.be.ok; + expect(this.updatedAt).to.equalTime(users[2].updatedAt); // All users should have the same updatedAt - // Pass the time so we can actually see a change - this.clock.tick(1000); - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); - }).then(() => { - return this.User.findAll({ order: ['id'] }); - }).then(users => { - expect(users[0].username).to.equal('Bill'); - expect(users[1].username).to.equal('Bill'); - expect(users[2].username).to.equal('Bob'); + // Pass the time so we can actually see a change + this.clock.tick(1000); + await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); - expect(users[0].updatedAt).to.be.afterTime(this.updatedAt); - expect(users[2].updatedAt).to.equalTime(this.updatedAt); - }); + users = await this.User.findAll({ order: ['id'] }); + expect(users[0].username).to.equal('Bill'); + expect(users[1].username).to.equal('Bill'); + expect(users[2].username).to.equal('Bob'); + + expect(users[0].updatedAt).to.be.afterTime(this.updatedAt); + expect(users[2].updatedAt).to.equalTime(this.updatedAt); }); - it('returns the number of affected rows', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('returns the number of affected rows', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; + { username: 'Bob', secretValue: '43' } + ]; - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }).then(([affectedRows]) => { - expect(affectedRows).to.equal(2); - }).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '44' } }).then(([affectedRows]) => { - expect(affectedRows).to.equal(0); - }); - }); - }); + await this.User.bulkCreate(data); + let [affectedRows] = await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); + expect(affectedRows).to.equal(2); + [affectedRows] = await this.User.update({ username: 'Bill' }, { where: { secretValue: '44' } }); + expect(affectedRows).to.equal(0); }); - it('does not update soft deleted records when model is paranoid', function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING }, { paranoid: true }); + it('does not update soft deleted records when model is paranoid', async function() { + const ParanoidUser = this.sequelize.define('ParanoidUser', { + username: DataTypes.STRING + }, { paranoid: true }); - return this.sequelize.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate([ - { username: 'user1' }, - { username: 'user2' } - ]); - }).then(() => { - return ParanoidUser.destroy({ - where: { - username: 'user1' - } - }); - }).then(() => { - return ParanoidUser.update({ username: 'foo' }, { - where: {} - }); - }).then(() => { - return ParanoidUser.findAll({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(users => { - expect(users).to.have.lengthOf(1, 'should not update soft-deleted record'); + await this.sequelize.sync({ force: true }); + await ParanoidUser.bulkCreate([ + { username: 'user1' }, + { username: 'user2' } + ]); + await ParanoidUser.destroy({ + where: { username: 'user1' } }); + await ParanoidUser.update({ username: 'foo' }, { where: {} }); + const users = await ParanoidUser.findAll({ + paranoid: false, + where: { + username: 'foo' + } + }); + expect(users).to.have.lengthOf(1, 'should not update soft-deleted record'); }); - it('updates soft deleted records when paranoid is overridden', function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING }, { paranoid: true }); + it('updates soft deleted records when paranoid is overridden', async function() { + const ParanoidUser = this.sequelize.define('ParanoidUser', { + username: DataTypes.STRING + }, { paranoid: true }); - return this.sequelize.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate([ - { username: 'user1' }, - { username: 'user2' } - ]); - }).then(() => { - return ParanoidUser.destroy({ - where: { - username: 'user1' - } - }); - }).then(() => { - return ParanoidUser.update({ username: 'foo' }, { - where: {}, - paranoid: false - }); - }).then(() => { - return ParanoidUser.findAll({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(users => { - expect(users).to.have.lengthOf(2); + await this.sequelize.sync({ force: true }); + await ParanoidUser.bulkCreate([ + { username: 'user1' }, + { username: 'user2' } + ]); + await ParanoidUser.destroy({ where: { username: 'user1' } }); + await ParanoidUser.update({ username: 'foo' }, { + where: {}, + paranoid: false + }); + const users = await ParanoidUser.findAll({ + paranoid: false, + where: { + username: 'foo' + } }); + expect(users).to.have.lengthOf(2); }); - it('calls update hook for soft deleted objects', function() { + it('calls update hook for soft deleted objects', async function() { const hookSpy = sinon.spy(); const User = this.sequelize.define('User', { username: DataTypes.STRING }, { paranoid: true, hooks: { beforeUpdate: hookSpy } } ); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'user1' } - ]); - }).then(() => { - return User.destroy({ - where: { - username: 'user1' - } - }); - }).then(() => { - return User.update( - { username: 'updUser1' }, - { paranoid: false, where: { username: 'user1' }, individualHooks: true }); - }).then(() => { - return User.findOne({ where: { username: 'updUser1' }, paranoid: false }); - }).then( user => { - expect(user).to.not.be.null; - expect(user.username).to.eq('updUser1'); - expect(hookSpy).to.have.been.called; + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }]); + await User.destroy({ + where: { + username: 'user1' + } + }); + await User.update({ username: 'updUser1' }, { + paranoid: false, + where: { username: 'user1' }, + individualHooks: true }); + const user = await User.findOne({ where: { username: 'updUser1' }, paranoid: false }); + expect(user).to.not.be.null; + expect(user.username).to.eq('updUser1'); + expect(hookSpy).to.have.been.called; }); if (dialect === 'postgres') { - it('returns the affected rows if `options.returning` is true', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('returns the affected rows if `options.returning` is true', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' }, returning: true }).then(([count, rows]) => { - expect(count).to.equal(2); - expect(rows).to.have.length(2); - }).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '44' }, returning: true }).then(([count, rows]) => { - expect(count).to.equal(0); - expect(rows).to.have.length(0); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + let [count, rows] = await this.User.update({ username: 'Bill' }, { + where: { secretValue: '42' }, + returning: true }); + expect(count).to.equal(2); + expect(rows).to.have.length(2); + [count, rows] = await this.User.update({ username: 'Bill' }, { + where: { secretValue: '44' }, + returning: true + }); + expect(count).to.equal(0); + expect(rows).to.have.length(0); }); } if (dialect === 'mysql') { - it('supports limit clause', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('supports limit clause', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Peter', secretValue: '42' }, - { username: 'Peter', secretValue: '42' }]; + { username: 'Peter', secretValue: '42' } + ]; - return this.User.bulkCreate(data).then(() => { - return this.User.update({ secretValue: '43' }, { where: { username: 'Peter' }, limit: 1 }).then(([affectedRows]) => { - expect(affectedRows).to.equal(1); - }); + await this.User.bulkCreate(data); + const [affectedRows] = await this.User.update({ secretValue: '43' }, { + where: { username: 'Peter' }, + limit: 1 }); + expect(affectedRows).to.equal(1); }); } }); describe('destroy', () => { - it('convenient method `truncate` should clear the table', function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - data = [ - { username: 'user1' }, - { username: 'user2' } - ]; + it('`truncate` method should clear the table', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + await User.truncate(); + expect(await User.findAll()).to.have.lengthOf(0); + }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate(data); - }).then(() => { - return User.truncate(); - }).then(() => { - return expect(User.findAll()).to.eventually.have.length(0); - }); - }); - - it('truncate should clear the table', function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - data = [ - { username: 'user1' }, - { username: 'user2' } - ]; - - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate(data); - }).then(() => { - return User.destroy({ truncate: true }); - }).then(() => { - return expect(User.findAll()).to.eventually.have.length(0); - }); + it('`truncate` option should clear the table', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + await User.destroy({ truncate: true }); + expect(await User.findAll()).to.have.lengthOf(0); }); - it('throws an error if no where clause is given', function() { + it('throws an error if no where clause is given', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - - return this.sequelize.sync({ force: true }).then(() => { - return User.destroy(); - }).then(() => { + await this.sequelize.sync({ force: true }); + try { + await User.destroy(); throw new Error('Destroy should throw an error if no where clause is given.'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('Missing where or truncate attribute in the options parameter of model.destroy.'); - }); + } }); - it('deletes all instances when given an empty where object', function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - data = [ - { username: 'user1' }, - { username: 'user2' } - ]; - - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate(data); - }).then(() => { - return User.destroy({ where: {} }); - }).then(affectedRows => { - expect(affectedRows).to.equal(2); - return User.findAll(); - }).then(users => { - expect(users).to.have.length(0); - }); + it('deletes all instances when given an empty where object', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + const affectedRows = await User.destroy({ where: {} }); + expect(affectedRows).to.equal(2); + expect(await User.findAll()).to.have.lengthOf(0); }); - it('throws an error if where has a key with undefined value', function() { + it('throws an error if where has a key with undefined value', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return User.destroy({ where: { username: undefined } }); - }).then(() => { + await this.sequelize.sync({ force: true }); + try { + await User.destroy({ where: { username: undefined } }); throw new Error('Destroy should throw an error if where has a key with undefined value'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); - }); + } }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(() => { - return sequelize.transaction().then(t => { - return User.destroy({ - where: {}, - transaction: t - }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(1); - expect(count2).to.equal(0); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await User.destroy({ + where: {}, + transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(1); + expect(count2).to.equal(0); + await t.rollback(); }); } - it('deletes values that match filter', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('deletes values that match filter', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.destroy({ where: { secretValue: '42' } }) - .then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Bob'); - }); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + await this.User.destroy({ where: { secretValue: '42' } }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Bob'); }); - it('works without a primary key', function() { + it('works without a primary key', async function() { const Log = this.sequelize.define('Log', { client_id: DataTypes.INTEGER, content: DataTypes.TEXT, @@ -1457,26 +1306,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); Log.removeAttribute('id'); - return Log.sync({ force: true }).then(() => { - return Log.create({ - client_id: 13, - content: 'Error!', - timestamp: new Date() - }); - }).then(() => { - return Log.destroy({ - where: { - client_id: 13 - } - }); - }).then(() => { - return Log.findAll().then(logs => { - expect(logs.length).to.equal(0); - }); + await Log.sync({ force: true }); + await Log.create({ + client_id: 13, + content: 'Error!', + timestamp: new Date() + }); + await Log.destroy({ + where: { + client_id: 13 + } }); + expect(await Log.findAll()).to.have.lengthOf(0); }); - it('supports .field', function() { + it('supports .field', async function() { const UserProject = this.sequelize.define('UserProject', { userId: { type: DataTypes.INTEGER, @@ -1484,152 +1328,113 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return UserProject.sync({ force: true }).then(() => { - return UserProject.create({ - userId: 10 - }); - }).then(() => { - return UserProject.destroy({ - where: { - userId: 10 - } - }); - }).then(() => { - return UserProject.findAll(); - }).then(userProjects => { - expect(userProjects.length).to.equal(0); - }); + await UserProject.sync({ force: true }); + await UserProject.create({ userId: 10 }); + await UserProject.destroy({ where: { userId: 10 } }); + expect(await UserProject.findAll()).to.have.lengthOf(0); }); - it('sets deletedAt to the current timestamp if paranoid is true', function() { - const qi = this.sequelize.queryInterface.QueryGenerator.quoteIdentifier.bind(this.sequelize.queryInterface.QueryGenerator), - ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Sequelize.STRING, - secretValue: Sequelize.STRING, - data: Sequelize.STRING, - intVal: { type: Sequelize.INTEGER, defaultValue: 1 } - }, { - paranoid: true - }), - data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; + it('sets deletedAt to the current timestamp if paranoid is true', async function() { + const ParanoidUser = this.sequelize.define('ParanoidUser', { + username: Sequelize.STRING, + secretValue: Sequelize.STRING, + data: Sequelize.STRING, + intVal: { type: Sequelize.INTEGER, defaultValue: 1 } + }, { paranoid: true }); + const data = [ + { username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '42' }, + { username: 'Bob', secretValue: '43' } + ]; - const ctx = {}; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate(data); - }).then(() => { - // since we save in UTC, let's format to UTC time - ctx.date = moment().utc().format('YYYY-MM-DD h:mm'); - return ParanoidUser.destroy({ where: { secretValue: '42' } }); - }).then(() => { - return ParanoidUser.findAll({ order: ['id'] }); - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Bob'); + await ParanoidUser.sync({ force: true }); + await ParanoidUser.bulkCreate(data); - return this.sequelize.query(`SELECT * FROM ${qi('ParanoidUsers')} WHERE ${qi('deletedAt')} IS NOT NULL ORDER BY ${qi('id')}`); - }).then(([users]) => { - expect(users[0].username).to.equal('Peter'); - expect(users[1].username).to.equal('Paul'); + // since we save in UTC, let's format to UTC time + const date = moment().utc().format('YYYY-MM-DD h:mm'); + await ParanoidUser.destroy({ where: { secretValue: '42' } }); - expect(moment(new Date(users[0].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(ctx.date); - expect(moment(new Date(users[1].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(ctx.date); - }); + let users = await ParanoidUser.findAll({ order: ['id'] }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Bob'); + + const queryGenerator = this.sequelize.queryInterface.QueryGenerator; + const qi = queryGenerator.quoteIdentifier.bind(queryGenerator); + const query = `SELECT * FROM ${qi('ParanoidUsers')} WHERE ${qi('deletedAt')} IS NOT NULL ORDER BY ${qi('id')}`; + [users] = await this.sequelize.query(query); + + expect(users[0].username).to.equal('Peter'); + expect(users[1].username).to.equal('Paul'); + + const formatDate = val => moment(new Date(val)).utc().format('YYYY-MM-DD h:mm'); + + expect(formatDate(users[0].deletedAt)).to.equal(date); + expect(formatDate(users[1].deletedAt)).to.equal(date); }); - it('does not set deletedAt for previously destroyed instances if paranoid is true', function() { + it('does not set deletedAt for previously destroyed instances if paranoid is true', async function() { const User = this.sequelize.define('UserCol', { secretValue: Sequelize.STRING, username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'Toni', secretValue: '42' }, - { username: 'Tobi', secretValue: '42' }, - { username: 'Max', secretValue: '42' } - ]).then(() => { - return User.findByPk(1).then(user => { - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - const deletedAt = user.deletedAt; - - return User.destroy({ where: { secretValue: '42' } }).then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAt).to.eql(deletedAt); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Toni', secretValue: '42' }, + { username: 'Tobi', secretValue: '42' }, + { username: 'Max', secretValue: '42' } + ]); + const user = await User.findByPk(1); + await user.destroy(); + await user.reload({ paranoid: false }); + const deletedAt = user.deletedAt; + await User.destroy({ where: { secretValue: '42' } }); + await user.reload({ paranoid: false }); + expect(user.deletedAt).to.eql(deletedAt); }); describe("can't find records marked as deleted with paranoid being true", () => { - it('with the DAOFactory', function() { + it('with the DAOFactory', async function() { const User = this.sequelize.define('UserCol', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'Toni' }, - { username: 'Tobi' }, - { username: 'Max' } - ]).then(() => { - return User.findByPk(1).then(user => { - return user.destroy().then(() => { - return User.findByPk(1).then(user => { - expect(user).to.be.null; - return User.count().then(cnt => { - expect(cnt).to.equal(2); - return User.findAll().then(users => { - expect(users).to.have.length(2); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Toni' }, + { username: 'Tobi' }, + { username: 'Max' } + ]); + const user = await User.findByPk(1); + await user.destroy(); + expect(await User.findByPk(1)).to.be.null; + expect(await User.count()).to.equal(2); + expect(await User.findAll()).to.have.length(2); }); }); describe('can find paranoid records if paranoid is marked as false in query', () => { - it('with the DAOFactory', function() { + it('with the DAOFactory', async function() { const User = this.sequelize.define('UserCol', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }) - .then(() => { - return User.bulkCreate([ - { username: 'Toni' }, - { username: 'Tobi' }, - { username: 'Max' } - ]); - }) - .then(() => { return User.findByPk(1); }) - .then(user => { return user.destroy(); }) - .then(() => { return User.findOne({ where: 1, paranoid: false }); }) - .then(user => { - expect(user).to.exist; - return User.findByPk(1); - }) - .then(user => { - expect(user).to.be.null; - return Promise.all([User.count(), User.count({ paranoid: false })]); - }) - .then(([cnt, cntWithDeleted]) => { - expect(cnt).to.equal(2); - expect(cntWithDeleted).to.equal(3); - }); + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Toni' }, + { username: 'Tobi' }, + { username: 'Max' } + ]); + const user = await User.findByPk(1); + await user.destroy(); + expect(await User.findOne({ where: 1, paranoid: false })).to.exist; + expect(await User.findByPk(1)).to.be.null; + expect(await User.count()).to.equal(2); + expect(await User.count({ paranoid: false })).to.equal(3); }); }); - it('should include deleted associated records if include has paranoid marked as false', function() { + it('should include deleted associated records if include has paranoid marked as false', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); @@ -1641,190 +1446,153 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasMany(Pet); Pet.belongsTo(User); - let user; - return User.sync({ force: true }) - .then(() => { return Pet.sync({ force: true }); }) - .then(() => { return User.create({ username: 'Joe' }); }) - .then(_user => { - user = _user; - return Pet.bulkCreate([ - { name: 'Fido', UserId: user.id }, - { name: 'Fifi', UserId: user.id } - ]); - }) - .then(() => { return Pet.findByPk(1); }) - .then(pet => { return pet.destroy(); }) - .then(() => { - return Promise.all([ - User.findOne({ where: { id: user.id }, include: Pet }), - User.findOne({ - where: { id: user.id }, - include: [{ model: Pet, paranoid: false }] - }) - ]); - }) - .then(([user, userWithDeletedPets]) => { - expect(user).to.exist; - expect(user.Pets).to.have.length(1); - expect(userWithDeletedPets).to.exist; - expect(userWithDeletedPets.Pets).to.have.length(2); - }); - }); - - it('should delete a paranoid record if I set force to true', function() { + await User.sync({ force: true }); + await Pet.sync({ force: true }); + const userId = (await User.create({ username: 'Joe' })).id; + await Pet.bulkCreate([ + { name: 'Fido', UserId: userId }, + { name: 'Fifi', UserId: userId } + ]); + const pet = await Pet.findByPk(1); + await pet.destroy(); + const user = await User.findOne({ + where: { id: userId }, + include: Pet + }); + const userWithDeletedPets = await User.findOne({ + where: { id: userId }, + include: { model: Pet, paranoid: false } + }); + expect(user).to.exist; + expect(user.Pets).to.have.length(1); + expect(userWithDeletedPets).to.exist; + expect(userWithDeletedPets.Pets).to.have.length(2); + }); + + it('should delete a paranoid record if I set force to true', async function() { const User = this.sequelize.define('paranoiduser', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'Bob' }, - { username: 'Tobi' }, - { username: 'Max' }, - { username: 'Tony' } - ]); - }).then(() => { - return User.findOne({ where: { username: 'Bob' } }); - }).then(user => { - return user.destroy({ force: true }); - }).then(() => { - return expect(User.findOne({ where: { username: 'Bob' } })).to.eventually.be.null; - }).then(() => { - return User.findOne({ where: { username: 'Tobi' } }); - }).then(tobi => { - return tobi.destroy(); - }).then(() => { - return this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', { plain: true }); - }).then(result => { - expect(result.username).to.equal('Tobi'); - return User.destroy({ where: { username: 'Tony' } }); - }).then(() => { - return this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', { plain: true }); - }).then(result => { - expect(result.username).to.equal('Tony'); - return User.destroy({ where: { username: ['Tony', 'Max'] }, force: true }); - }).then(() => { - return this.sequelize.query('SELECT * FROM paranoidusers', { raw: true }); - }).then(([users]) => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('Tobi'); - }); - }); - - it('returns the number of affected rows', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Bob' }, + { username: 'Tobi' }, + { username: 'Max' }, + { username: 'Tony' } + ]); + const user = await User.findOne({ where: { username: 'Bob' } }); + await user.destroy({ force: true }); + expect(await User.findOne({ where: { username: 'Bob' } })).to.be.null; + const tobi = await User.findOne({ where: { username: 'Tobi' } }); + await tobi.destroy(); + let result = await this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', { plain: true }); + expect(result.username).to.equal('Tobi'); + await User.destroy({ where: { username: 'Tony' } }); + result = await this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', { plain: true }); + expect(result.username).to.equal('Tony'); + await User.destroy({ where: { username: ['Tony', 'Max'] }, force: true }); + const [users] = await this.sequelize.query('SELECT * FROM paranoidusers', { raw: true }); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('Tobi'); + }); + + it('returns the number of affected rows', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.destroy({ where: { secretValue: '42' } }).then(affectedRows => { - expect(affectedRows).to.equal(2); - }); - }).then(() => { - return this.User.destroy({ where: { secretValue: '44' } }).then(affectedRows => { - expect(affectedRows).to.equal(0); - }); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; - it('supports table schema/prefix', function() { - const data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }], - prefixUser = this.User.schema('prefix'); - - const run = function() { - return prefixUser.sync({ force: true }).then(() => { - return prefixUser.bulkCreate(data).then(() => { - return prefixUser.destroy({ where: { secretValue: '42' } }).then(() => { - return prefixUser.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Bob'); - }); - }); - }); - }); - }; - return Support.dropTestSchemas(this.sequelize) - .then(() => this.sequelize.queryInterface.createSchema('prefix')) - .then(() => run.call(this)) - .then(() => this.sequelize.queryInterface.dropSchema('prefix')); + await this.User.bulkCreate(data); + let affectedRows = await this.User.destroy({ where: { secretValue: '42' } }); + expect(affectedRows).to.equal(2); + affectedRows = await this.User.destroy({ where: { secretValue: '44' } }); + expect(affectedRows).to.equal(0); }); - it('should work if model is paranoid and only operator in where clause is a Symbol', function() { + it('supports table schema/prefix', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '42' }, + { username: 'Bob', secretValue: '43' } + ]; + const prefixUser = this.User.schema('prefix'); + + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.queryInterface.createSchema('prefix'); + await prefixUser.sync({ force: true }); + await prefixUser.bulkCreate(data); + await prefixUser.destroy({ where: { secretValue: '42' } }); + const users = await prefixUser.findAll({ order: ['id'] }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Bob'); + await this.sequelize.queryInterface.dropSchema('prefix'); + }); + + it('should work if model is paranoid and only operator in where clause is a Symbol', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING - }, { - paranoid: true - }); + }, { paranoid: true }); - return User.sync({ force: true }) - .then(() => User.create({ username: 'foo' })) - .then(() => User.create({ username: 'bar' })) - .then(() => { - return User.destroy({ - where: { - [Op.or]: [ - { username: 'bar' }, - { username: 'baz' } - ] - } - }); - }) - .then(() => User.findAll()) - .then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('foo'); - }); + await User.sync({ force: true }); + await User.bulkCreate([{ username: 'foo' }, { username: 'bar' }]); + await User.destroy({ + where: { + [Op.or]: [ + { username: 'bar' }, + { username: 'baz' } + ] + } + }); + const users = await User.findAll(); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('foo'); }); }); describe('restore', () => { - it('returns an error if the model is not paranoid', function() { - return this.User.create({ username: 'Peter', secretValue: '42' }) - .then(() => { - expect(() => {this.User.restore({ where: { secretValue: '42' } });}).to.throw(Error, 'Model is not paranoid'); - }); + it('synchronously throws an error if the model is not paranoid', async function() { + expect(() => { + this.User.restore({ where: { secretValue: '42' } }); + throw new Error('Did not throw synchronously'); + }).to.throw(Error, 'Model is not paranoid'); }); - it('restores a previously deleted model', function() { + it('restores a previously deleted model', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Sequelize.STRING, - secretValue: Sequelize.STRING, - data: Sequelize.STRING, - intVal: { type: Sequelize.INTEGER, defaultValue: 1 } - }, { - paranoid: true - }), - data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '43' }, - { username: 'Bob', secretValue: '44' }]; - - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate(data); - }).then(() => { - return ParanoidUser.destroy({ where: { secretValue: '42' } }); - }).then(() => { - return ParanoidUser.restore({ where: { secretValue: '42' } }); - }).then(() => { - return ParanoidUser.findOne({ where: { secretValue: '42' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.username).to.equal('Peter'); + username: Sequelize.STRING, + secretValue: Sequelize.STRING, + data: Sequelize.STRING, + intVal: { type: Sequelize.INTEGER, defaultValue: 1 } + }, { + paranoid: true }); + const data = [ + { username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '43' }, + { username: 'Bob', secretValue: '44' } + ]; + + await ParanoidUser.sync({ force: true }); + await ParanoidUser.bulkCreate(data); + await ParanoidUser.destroy({ where: { secretValue: '42' } }); + await ParanoidUser.restore({ where: { secretValue: '42' } }); + const user = await ParanoidUser.findOne({ where: { secretValue: '42' } }); + expect(user).to.be.ok; + expect(user.username).to.equal('Peter'); }); }); describe('equals', () => { - it('correctly determines equality of objects', function() { - return this.User.create({ username: 'hallo', data: 'welt' }).then(u => { - expect(u.equals(u)).to.be.ok; - }); + it('correctly determines equality of objects', async function() { + const user = await this.User.create({ username: 'hallo', data: 'welt' }); + expect(user.equals(user)).to.be.ok; }); // sqlite can't handle multiple primary keys if (dialect !== 'sqlite') { - it('correctly determines equality with multiple primary keys', function() { + it('correctly determines equality with multiple primary keys', async function() { const userKeys = this.sequelize.define('userkeys', { foo: { type: Sequelize.STRING, primaryKey: true }, bar: { type: Sequelize.STRING, primaryKey: true }, @@ -1832,19 +1600,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { bio: Sequelize.TEXT }); - return userKeys.sync({ force: true }).then(() => { - return userKeys.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }).then(u => { - expect(u.equals(u)).to.be.ok; - }); - }); + await userKeys.sync({ force: true }); + const user = await userKeys.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }); + expect(user.equals(user)).to.be.ok; }); } }); - describe('equalsOneOf', () => { - // sqlite can't handle multiple primary keys - if (dialect !== 'sqlite') { - beforeEach(function() { + // sqlite can't handle multiple primary keys + if (dialect !== 'sqlite') { + describe('equalsOneOf', () => { + beforeEach(async function() { this.userKey = this.sequelize.define('userKeys', { foo: { type: Sequelize.STRING, primaryKey: true }, bar: { type: Sequelize.STRING, primaryKey: true }, @@ -1852,81 +1618,67 @@ describe(Support.getTestDialectTeaser('Model'), () => { bio: Sequelize.TEXT }); - return this.userKey.sync({ force: true }); + await this.userKey.sync({ force: true }); }); - it('determines equality if one is matching', function() { - return this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }).then(u => { - expect(u.equalsOneOf([u, { a: 1 }])).to.be.ok; - }); + it('determines equality if one is matching', async function() { + const u = await this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }); + expect(u.equalsOneOf([u, { a: 1 }])).to.be.ok; }); - it("doesn't determine equality if none is matching", function() { - return this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }).then(u => { - expect(u.equalsOneOf([{ b: 2 }, { a: 1 }])).to.not.be.ok; - }); + it("doesn't determine equality if none is matching", async function() { + const u = await this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }); + expect(u.equalsOneOf([{ b: 2 }, { a: 1 }])).to.not.be.ok; }); - } - }); + }); + } describe('count', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(0); - expect(count2).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(0); + expect(count2).to.equal(1); + await t.rollback(); }); } - it('counts all created objects', function() { - return this.User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]).then(() => { - return this.User.count().then(count => { - expect(count).to.equal(2); - }); - }); + it('counts all created objects', async function() { + await this.User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + expect(await this.User.count()).to.equal(2); }); - it('returns multiple rows when using group', function() { - return this.User.bulkCreate([ + it('returns multiple rows when using group', async function() { + await this.User.bulkCreate([ { username: 'user1', data: 'A' }, { username: 'user2', data: 'A' }, { username: 'user3', data: 'B' } - ]).then(() => { - return this.User.count({ - attributes: ['data'], - group: ['data'] - }).then(count => { - expect(count.length).to.equal(2); - }); + ]); + const count = await this.User.count({ + attributes: ['data'], + group: ['data'] }); + expect(count).to.have.lengthOf(2); }); - describe('aggregate', () => { - if (dialect === 'mssql') { - return; - } - it('allows grouping by aliased attribute', function() { - return this.User.aggregate('id', 'count', { - attributes: [['id', 'id2']], - group: ['id2'], - logging: true + if (dialect !== 'mssql') { + describe('aggregate', () => { + it('allows grouping by aliased attribute', async function() { + await this.User.aggregate('id', 'count', { + attributes: [['id', 'id2']], + group: ['id2'], + logging: true + }); }); }); - }); + } describe('options sent to aggregate', () => { let options, aggregateSpy; @@ -1940,267 +1692,156 @@ describe(Support.getTestDialectTeaser('Model'), () => { afterEach(() => { expect(aggregateSpy).to.have.been.calledWith( sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('where', { username: 'user1' }))); + sinon.match.object.and(sinon.match.has('where', { username: 'user1' })) + ); aggregateSpy.restore(); }); - it('modifies option "limit" by setting it to null', function() { + it('modifies option "limit" by setting it to null', async function() { options.limit = 5; - return this.User.count(options).then(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('limit', null))); - }); + await this.User.count(options); + expect(aggregateSpy).to.have.been.calledWith( + sinon.match.any, sinon.match.any, + sinon.match.object.and(sinon.match.has('limit', null)) + ); }); - it('modifies option "offset" by setting it to null', function() { + it('modifies option "offset" by setting it to null', async function() { options.offset = 10; - return this.User.count(options).then(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('offset', null))); - }); + await this.User.count(options); + expect(aggregateSpy).to.have.been.calledWith( + sinon.match.any, sinon.match.any, + sinon.match.object.and(sinon.match.has('offset', null)) + ); }); - it('modifies option "order" by setting it to null', function() { + it('modifies option "order" by setting it to null', async function() { options.order = 'username'; - return this.User.count(options).then(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('order', null))); - }); + await this.User.count(options); + expect(aggregateSpy).to.have.been.calledWith( + sinon.match.any, sinon.match.any, + sinon.match.object.and(sinon.match.has('order', null)) + ); }); }); - it('allows sql logging', function() { + it('allows sql logging', async function() { let test = false; - return this.User.count({ + await this.User.count({ logging(sql) { test = true; expect(sql).to.exist; expect(sql.toUpperCase()).to.include('SELECT'); } - }).then(() => { - expect(test).to.be.true; }); + expect(test).to.be.true; }); - it('filters object', function() { - return this.User.create({ username: 'user1' }).then(() => { - return this.User.create({ username: 'foo' }).then(() => { - return this.User.count({ where: { username: { [Op.like]: '%us%' } } }).then(count => { - expect(count).to.equal(1); - }); - }); - }); + it('filters object', async function() { + await this.User.create({ username: 'user1' }); + await this.User.create({ username: 'foo' }); + const count = await this.User.count({ where: { username: { [Op.like]: '%us%' } } }); + expect(count).to.equal(1); }); - it('supports distinct option', function() { + it('supports distinct option', async function() { const Post = this.sequelize.define('Post', {}); const PostComment = this.sequelize.define('PostComment', {}); Post.hasMany(PostComment); - return Post.sync({ force: true }) - .then(() => PostComment.sync({ force: true })) - .then(() => Post.create({})) - .then(post => PostComment.bulkCreate([{ PostId: post.id }, { PostId: post.id }])) - .then(() => Promise.join( - Post.count({ distinct: false, include: [{ model: PostComment, required: false }] }), - Post.count({ distinct: true, include: [{ model: PostComment, required: false }] }), - (count1, count2) => { - expect(count1).to.equal(2); - expect(count2).to.equal(1); - }) - ); + await Post.sync({ force: true }); + await PostComment.sync({ force: true }); + const post = await Post.create({}); + await PostComment.bulkCreate([{ PostId: post.id }, { PostId: post.id }]); + const count1 = await Post.count({ distinct: false, include: { model: PostComment, required: false } }); + const count2 = await Post.count({ distinct: true, include: { model: PostComment, required: false } }); + expect(count1).to.equal(2); + expect(count2).to.equal(1); }); }); - describe('min', () => { - beforeEach(function() { - this.UserWithAge = this.sequelize.define('UserWithAge', { - age: Sequelize.INTEGER - }); - - this.UserWithDec = this.sequelize.define('UserWithDec', { - value: Sequelize.DECIMAL(10, 3) - }); - - return this.UserWithAge.sync({ force: true }).then(() => { - return this.UserWithDec.sync({ force: true }); - }); - }); - - if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { age: Sequelize.INTEGER }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }).then(() => { - return User.min('age').then(min1 => { - return User.min('age', { transaction: t }).then(min2 => { - expect(min1).to.be.not.ok; - expect(min2).to.equal(2); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - } - - it('should return the min value', function() { - return this.UserWithAge.bulkCreate([{ age: 3 }, { age: 2 }]).then(() => { - return this.UserWithAge.min('age').then(min => { - expect(min).to.equal(2); - }); - }); - }); - - it('allows sql logging', function() { - let test = false; - return this.UserWithAge.min('age', { - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }).then(() => { - expect(test).to.be.true; - }); - }); - - it('should allow decimals in min', function() { - return this.UserWithDec.bulkCreate([{ value: 5.5 }, { value: 3.5 }]).then(() => { - return this.UserWithDec.min('value').then(min => { - expect(min).to.equal(3.5); - }); - }); - }); - - it('should allow strings in min', function() { - return this.User.bulkCreate([{ username: 'bbb' }, { username: 'yyy' }]).then(() => { - return this.User.min('username').then(min => { - expect(min).to.equal('bbb'); + for (const methodName of ['min', 'max']) { + describe(methodName, () => { + beforeEach(async function() { + this.UserWithAge = this.sequelize.define('UserWithAge', { + age: Sequelize.INTEGER, + order: Sequelize.INTEGER }); - }); - }); - it('should allow dates in min', function() { - return this.User.bulkCreate([{ theDate: new Date(2000, 1, 1) }, { theDate: new Date(1990, 1, 1) }]).then(() => { - return this.User.min('theDate').then(min => { - expect(min).to.be.a('Date'); - expect(new Date(1990, 1, 1)).to.equalDate(min); + this.UserWithDec = this.sequelize.define('UserWithDec', { + value: Sequelize.DECIMAL(10, 3) }); - }); - }); - }); - - describe('max', () => { - beforeEach(function() { - this.UserWithAge = this.sequelize.define('UserWithAge', { - age: Sequelize.INTEGER, - order: Sequelize.INTEGER - }); - - this.UserWithDec = this.sequelize.define('UserWithDec', { - value: Sequelize.DECIMAL(10, 3) - }); - return this.UserWithAge.sync({ force: true }).then(() => { - return this.UserWithDec.sync({ force: true }); + await this.UserWithAge.sync({ force: true }); + await this.UserWithDec.sync({ force: true }); }); - }); - if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { + if (current.dialect.supports.transactions) { + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); const User = sequelize.define('User', { age: Sequelize.INTEGER }); - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }).then(() => { - return User.max('age').then(min1 => { - return User.max('age', { transaction: t }).then(min2 => { - expect(min1).to.be.not.ok; - expect(min2).to.equal(5); - return t.rollback(); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }); + const val1 = await User[methodName]('age'); + const val2 = await User[methodName]('age', { transaction: t }); + expect(val1).to.be.not.ok; + expect(val2).to.equal(methodName === 'min' ? 2 : 5); + await t.rollback(); }); - }); - } + } - it('should return the max value for a field named the same as an SQL reserved keyword', function() { - return this.UserWithAge.bulkCreate([{ age: 2, order: 3 }, { age: 3, order: 5 }]).then(() => { - return this.UserWithAge.max('order').then(max => { - expect(max).to.equal(5); - }); + it('returns the correct value', async function() { + await this.UserWithAge.bulkCreate([{ age: 3 }, { age: 2 }]); + expect(await this.UserWithAge[methodName]('age')).to.equal(methodName === 'min' ? 2 : 3); }); - }); - it('should return the max value', function() { - return this.UserWithAge.bulkCreate([{ age: 2 }, { age: 3 }]).then(() => { - return this.UserWithAge.max('age').then(max => { - expect(max).to.equal(3); + it('allows sql logging', async function() { + let test = false; + await this.UserWithAge[methodName]('age', { + logging(sql) { + test = true; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('SELECT'); + } }); + expect(test).to.be.true; }); - }); - it('should allow decimals in max', function() { - return this.UserWithDec.bulkCreate([{ value: 3.5 }, { value: 5.5 }]).then(() => { - return this.UserWithDec.max('value').then(max => { - expect(max).to.equal(5.5); - }); + it('should allow decimals', async function() { + await this.UserWithDec.bulkCreate([{ value: 5.5 }, { value: 3.5 }]); + expect(await this.UserWithDec[methodName]('value')).to.equal(methodName === 'min' ? 3.5 : 5.5); }); - }); - it('should allow dates in max', function() { - return this.User.bulkCreate([ - { theDate: new Date(2013, 11, 31) }, - { theDate: new Date(2000, 1, 1) } - ]).then(() => { - return this.User.max('theDate').then(max => { - expect(max).to.be.a('Date'); - expect(max).to.equalDate(new Date(2013, 11, 31)); - }); + it('should allow strings', async function() { + await this.User.bulkCreate([{ username: 'bbb' }, { username: 'yyy' }]); + expect(await this.User[methodName]('username')).to.equal(methodName === 'min' ? 'bbb' : 'yyy'); }); - }); - it('should allow strings in max', function() { - return this.User.bulkCreate([{ username: 'aaa' }, { username: 'zzz' }]).then(() => { - return this.User.max('username').then(max => { - expect(max).to.equal('zzz'); - }); + it('should allow dates', async function() { + const date1 = new Date(2000, 1, 1); + const date2 = new Date(1990, 1, 1); + await this.User.bulkCreate([{ theDate: date1 }, { theDate: date2 }]); + expect(await this.User[methodName]('theDate')).to.equalDate(methodName === 'min' ? date2 : date1); }); - }); - it('allows sql logging', function() { - let logged = false; - return this.UserWithAge.max('age', { - logging(sql) { - expect(sql).to.exist; - logged = true; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }).then(() => { - expect(logged).to.true; + it('should work with fields named as an SQL reserved keyword', async function() { + await this.UserWithAge.bulkCreate([ + { age: 2, order: 3 }, + { age: 3, order: 5 } + ]); + expect(await this.UserWithAge[methodName]('order')).to.equal(methodName === 'min' ? 3 : 5); }); }); - }); + } describe('sum', () => { - beforeEach(function() { + beforeEach(async function() { this.UserWithAge = this.sequelize.define('UserWithAge', { age: Sequelize.INTEGER, order: Sequelize.INTEGER, @@ -2223,74 +1864,61 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Promise.join( + await Promise.all([ this.UserWithAge.sync({ force: true }), this.UserWithDec.sync({ force: true }), this.UserWithFields.sync({ force: true }) - ); + ]); }); - it('should return the sum of the values for a field named the same as an SQL reserved keyword', function() { - return this.UserWithAge.bulkCreate([{ age: 2, order: 3 }, { age: 3, order: 5 }]).then(() => { - return this.UserWithAge.sum('order').then(sum => { - expect(sum).to.equal(8); - }); - }); + it('should work in the simplest case', async function() { + await this.UserWithAge.bulkCreate([{ age: 2 }, { age: 3 }]); + expect(await this.UserWithAge.sum('age')).to.equal(5); }); - it('should return the sum of a field in various records', function() { - return this.UserWithAge.bulkCreate([{ age: 2 }, { age: 3 }]).then(() => { - return this.UserWithAge.sum('age').then(sum => { - expect(sum).to.equal(5); - }); - }); + it('should work with fields named as an SQL reserved keyword', async function() { + await this.UserWithAge.bulkCreate([{ age: 2, order: 3 }, { age: 3, order: 5 }]); + expect(await this.UserWithAge.sum('order')).to.equal(8); }); - it('should allow decimals in sum', function() { - return this.UserWithDec.bulkCreate([{ value: 3.5 }, { value: 5.25 }]).then(() => { - return this.UserWithDec.sum('value').then(sum => { - expect(sum).to.equal(8.75); - }); - }); + it('should allow decimals in sum', async function() { + await this.UserWithDec.bulkCreate([{ value: 3.5 }, { value: 5.25 }]); + expect(await this.UserWithDec.sum('value')).to.equal(8.75); }); - it('should accept a where clause', function() { - const options = { where: { 'gender': 'male' } }; - - return this.UserWithAge.bulkCreate([{ age: 2, gender: 'male' }, { age: 3, gender: 'female' }]).then(() => { - return this.UserWithAge.sum('age', options).then(sum => { - expect(sum).to.equal(2); - }); - }); + it('should accept a where clause', async function() { + const options = { where: { gender: 'male' } }; + await this.UserWithAge.bulkCreate([ + { age: 2, gender: 'male' }, + { age: 3, gender: 'female' } + ]); + expect(await this.UserWithAge.sum('age', options)).to.equal(2); }); - it('should accept a where clause with custom fields', function() { - return this.UserWithFields.bulkCreate([ + it('should accept a where clause with custom fields', async function() { + const options = { where: { gender: 'male' } }; + await this.UserWithFields.bulkCreate([ { age: 2, gender: 'male' }, { age: 3, gender: 'female' } - ]).then(() => { - return expect(this.UserWithFields.sum('age', { - where: { 'gender': 'male' } - })).to.eventually.equal(2); - }); + ]); + expect(await this.UserWithFields.sum('age', options)).to.equal(2); }); - it('allows sql logging', function() { - let logged = false; - return this.UserWithAge.sum('age', { + it('allows sql logging', async function() { + let test = false; + await this.UserWithAge.sum('age', { logging(sql) { + test = true; expect(sql).to.exist; - logged = true; expect(sql.toUpperCase()).to.include('SELECT'); } - }).then(() => { - expect(logged).to.true; }); + expect(test).to.true; }); }); describe('schematic support', () => { - beforeEach(function() { + beforeEach(async function() { this.UserPublic = this.sequelize.define('UserPublic', { age: Sequelize.INTEGER }); @@ -2299,117 +1927,107 @@ describe(Support.getTestDialectTeaser('Model'), () => { age: Sequelize.INTEGER }); - return Support.dropTestSchemas(this.sequelize) - .then(() => this.sequelize.createSchema('schema_test')) - .then(() => this.sequelize.createSchema('special')) - .then(() => this.UserSpecial.schema('special').sync({ force: true })) - .then(UserSpecialSync => { - this.UserSpecialSync = UserSpecialSync; - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('schema_test'); + await this.sequelize.createSchema('special'); + this.UserSpecialSync = await this.UserSpecial.schema('special').sync({ force: true }); }); - afterEach(function() { - return this.sequelize.dropSchema('schema_test') + afterEach(async function() { + await this.sequelize.dropSchema('schema_test') .finally(() => this.sequelize.dropSchema('special')) .finally(() => this.sequelize.dropSchema('prefix')); }); - it('should be able to drop with schemas', function() { - return this.UserSpecial.drop(); + it('should be able to drop with schemas', async function() { + await this.UserSpecial.drop(); }); - it('should be able to list schemas', function() { - return this.sequelize.showAllSchemas().then(schemas => { - expect(schemas).to.be.instanceof(Array); - - // sqlite & MySQL doesn't actually create schemas unless Model.sync() is called - // Postgres supports schemas natively - switch (dialect) { - case 'mssql': - case 'postgres': - expect(schemas).to.have.length(2); - break; - case 'mariadb': - expect(schemas).to.have.length(3); - break; - default : - expect(schemas).to.have.length(1); - break; - } - }); + it('should be able to list schemas', async function() { + const schemas = await this.sequelize.showAllSchemas(); + expect(schemas).to.be.instanceof(Array); + const expectedLengths = { + mssql: 2, + postgres: 2, + mariadb: 3, + mysql: 1, + sqlite: 1 + }; + expect(schemas).to.have.length(expectedLengths[dialect]); }); - if (dialect === 'mysql' || dialect === 'sqlite') { - it('should take schemaDelimiter into account if applicable', function() { + if (['mysql', 'sqlite'].includes(dialect)) { + it('should take schemaDelimiter into account if applicable', async function() { let test = 0; - const UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', { age: Sequelize.INTEGER }, { schema: 'hello', schemaDelimiter: '_' }); - const UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', { age: Sequelize.INTEGER }); - return UserSpecialUnderscore.sync({ force: true }).then(User => { - return UserSpecialDblUnderscore.schema('hello', '__').sync({ force: true }).then(DblUser => { - return DblUser.create({ age: 3 }, { - logging(sql) { - expect(sql).to.exist; - test++; - expect(sql).to.include('INSERT INTO `hello__UserSpecialDblUnderscores`'); - } - }).then(() => { - return User.create({ age: 3 }, { - logging(sql) { - expect(sql).to.exist; - test++; - expect(sql).to.include('INSERT INTO `hello_UserSpecialUnderscores`'); - } - }); - }); - }).then(() => { - expect(test).to.equal(2); - }); + const UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', { + age: Sequelize.INTEGER + }, { schema: 'hello', schemaDelimiter: '_' }); + const UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', { + age: Sequelize.INTEGER + }); + const User = await UserSpecialUnderscore.sync({ force: true }); + const DblUser = await UserSpecialDblUnderscore.schema('hello', '__').sync({ force: true }); + await DblUser.create({ age: 3 }, { + logging(sql) { + test++; + expect(sql).to.exist; + expect(sql).to.include('INSERT INTO `hello__UserSpecialDblUnderscores`'); + } + }); + await User.create({ age: 3 }, { + logging(sql) { + test++; + expect(sql).to.exist; + expect(sql).to.include('INSERT INTO `hello_UserSpecialUnderscores`'); + } }); + expect(test).to.equal(2); }); } - it('should describeTable using the default schema settings', function() { + it('should describeTable using the default schema settings', async function() { const UserPublic = this.sequelize.define('Public', { username: Sequelize.STRING }); - let count = 0; - return UserPublic.sync({ force: true }).then(() => { - return UserPublic.schema('special').sync({ force: true }).then(() => { - return this.sequelize.queryInterface.describeTable('Publics', { - logging(sql) { - if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql' || dialect === 'mariadb') { - expect(sql).to.not.contain('special'); - count++; - } - } - }).then(table => { - if (dialect === 'postgres') { - expect(table.id.defaultValue).to.not.contain('special'); - count++; - } - return this.sequelize.queryInterface.describeTable('Publics', { - schema: 'special', - logging(sql) { - if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql' || dialect === 'mariadb') { - expect(sql).to.contain('special'); - count++; - } - } - }).then(table => { - if (dialect === 'postgres') { - expect(table.id.defaultValue).to.contain('special'); - count++; - } - }); - }).then(() => { - expect(count).to.equal(2); - }); - }); + let test = 0; + + await UserPublic.sync({ force: true }); + await UserPublic.schema('special').sync({ force: true }); + + let table = await this.sequelize.queryInterface.describeTable('Publics', { + logging(sql) { + if (['sqlite', 'mysql', 'mssql', 'mariadb'].includes(dialect)) { + test++; + expect(sql).to.not.contain('special'); + } + } }); + + if (dialect === 'postgres') { + test++; + expect(table.id.defaultValue).to.not.contain('special'); + } + + table = await this.sequelize.queryInterface.describeTable('Publics', { + schema: 'special', + logging(sql) { + if (['sqlite', 'mysql', 'mssql', 'mariadb'].includes(dialect)) { + test++; + expect(sql).to.contain('special'); + } + } + }); + + if (dialect === 'postgres') { + test++; + expect(table.id.defaultValue).to.contain('special'); + } + + expect(test).to.equal(2); }); - it('should be able to reference a table with a schema set', function() { + it('should be able to reference a table with a schema set', async function() { const UserPub = this.sequelize.define('UserPub', { username: Sequelize.STRING }, { schema: 'prefix' }); @@ -2418,35 +2036,33 @@ describe(Support.getTestDialectTeaser('Model'), () => { name: Sequelize.STRING }, { schema: 'prefix' }); - UserPub.hasMany(ItemPub, { - foreignKeyConstraint: true - }); + UserPub.hasMany(ItemPub, { foreignKeyConstraint: true }); - const run = function() { - return UserPub.sync({ force: true }).then(() => { - return ItemPub.sync({ force: true, logging: _.after(2, _.once(sql => { - if (dialect === 'postgres') { - expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/); - } else if (dialect === 'mssql') { - expect(sql).to.match(/REFERENCES\s+\[prefix\]\.\[UserPubs\] \(\[id\]\)/); - } else if (dialect === 'mariadb') { - expect(sql).to.match(/REFERENCES\s+`prefix`\.`UserPubs` \(`id`\)/); - } else { - expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/); - } + if (['postgres', 'mssql', 'mariadb'].includes(dialect)) { + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.queryInterface.createSchema('prefix'); + } - })) }); - }); - }; + let test = false; - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.queryInterface.createSchema('prefix').then(() => { - return run.call(this); - }); - }); - } - return run.call(this); + await UserPub.sync({ force: true }); + await ItemPub.sync({ + force: true, + logging: _.after(2, _.once(sql => { + test = true; + if (dialect === 'postgres') { + expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/); + } else if (dialect === 'mssql') { + expect(sql).to.match(/REFERENCES\s+\[prefix\]\.\[UserPubs\] \(\[id\]\)/); + } else if (dialect === 'mariadb') { + expect(sql).to.match(/REFERENCES\s+`prefix`\.`UserPubs` \(`id`\)/); + } else { + expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/); + } + })) + }); + + expect(test).to.be.true; }); it('should be able to create and update records under any valid schematic', function() { From 22e6549bf8a0bace13e4ef678a840430d0663387 Mon Sep 17 00:00:00 2001 From: Alan Koger Date: Fri, 24 Jan 2020 22:49:42 -0500 Subject: [PATCH 038/414] docs(assocs): fix typo (#11869) --- docs/manual/core-concepts/assocs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md index 098bccd5e7c2..b2e1d8dc85ad 100644 --- a/docs/manual/core-concepts/assocs.md +++ b/docs/manual/core-concepts/assocs.md @@ -192,7 +192,7 @@ This means that, unlike the One-To-One association, in which we had to choose wh ### Goal -In this example, we have the models `Team` and `Player`. We want to tell Sequelize that there is a One-To-Many relationship between them, meaning that one Team has many Players, while each Playes belongs to a single Team. +In this example, we have the models `Team` and `Player`. We want to tell Sequelize that there is a One-To-Many relationship between them, meaning that one Team has many Players, while each Player belongs to a single Team. ### Implementation From c41bcf4b4d68fad4303b83a057b17f0bd170b318 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 25 Jan 2020 04:09:47 -0300 Subject: [PATCH 039/414] build(eslint-plugin-jsdoc): update to v20.4.0 (#11866) --- .eslintrc.json | 7 + lib/associations/base.js | 1 + lib/associations/belongs-to-many.js | 30 +-- lib/associations/belongs-to.js | 8 +- lib/associations/has-many.js | 22 +- lib/associations/has-one.js | 8 +- lib/associations/helpers.js | 6 +- lib/data-types.js | 2 +- lib/dialects/abstract/connection-manager.js | 4 +- lib/dialects/abstract/query-generator.js | 36 +-- .../abstract/query-generator/helpers/quote.js | 2 +- .../abstract/query-generator/transaction.js | 2 +- lib/dialects/abstract/query.js | 10 +- lib/dialects/mariadb/connection-manager.js | 2 +- lib/dialects/mariadb/data-types.js | 1 + lib/dialects/mssql/data-types.js | 3 +- lib/dialects/mssql/query-generator.js | 2 +- lib/dialects/mssql/query-interface.js | 2 +- lib/dialects/mysql/connection-manager.js | 2 +- lib/dialects/mysql/data-types.js | 1 + lib/dialects/mysql/query-generator.js | 4 +- lib/dialects/mysql/query-interface.js | 4 +- lib/dialects/postgres/data-types.js | 3 +- lib/dialects/postgres/query-interface.js | 4 +- lib/dialects/postgres/query.js | 2 +- lib/dialects/sqlite/data-types.js | 2 +- lib/dialects/sqlite/query-interface.js | 14 +- lib/dialects/sqlite/query.js | 2 +- lib/errors/bulk-record-error.js | 2 +- lib/errors/connection-error.js | 1 + lib/errors/database-error.js | 2 + lib/errors/optimistic-lock-error.js | 2 + lib/errors/validation-error.js | 8 +- lib/hooks.js | 42 +++- lib/instance-validator.js | 11 +- lib/model-manager.js | 2 +- lib/model.js | 213 +++++++++--------- lib/query-interface.js | 102 ++++----- lib/sequelize.js | 90 ++++---- lib/transaction.js | 4 +- lib/utils.js | 31 +-- package-lock.json | 49 +++- package.json | 2 +- 43 files changed, 422 insertions(+), 325 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d52480a73d90..38854c60ba32 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -97,6 +97,13 @@ "no-self-compare": "error", "no-case-declarations": "off" }, + "settings": { + "jsdoc": { + "tagNamePreference": { + "augments": "extends" + } + } + }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "script" diff --git a/lib/associations/base.js b/lib/associations/base.js index a440e3ef84f1..fe769bd65eaf 100644 --- a/lib/associations/base.js +++ b/lib/associations/base.js @@ -98,6 +98,7 @@ class Association { /** * The type of the association. One of `HasMany`, `BelongsTo`, `HasOne`, `BelongsToMany` + * * @type {string} */ this.associationType = ''; diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 54dbaf8b1b68..eafd8bed3c70 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -407,8 +407,8 @@ class BelongsToMany extends Association { * {@link Model} for a full explanation of options * * @param {Model} instance instance - * @param {Object} [options] find options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @@ -476,8 +476,8 @@ class BelongsToMany extends Association { * Count everything currently associated with this, using an optional where clause. * * @param {Model} instance instance - * @param {Object} [options] find options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * * @returns {Promise} @@ -501,7 +501,7 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance source instance to check for an association with * @param {Model|Model[]|string[]|string|number[]|number} [instances] Can be an array of instances or their primary keys - * @param {Object} [options] Options passed to getAssociations + * @param {object} [options] Options passed to getAssociations * * @returns {Promise} */ @@ -546,9 +546,9 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance source instance to associate new instances with * @param {Model|Model[]|string[]|string|number[]|number} [newAssociatedObjects] A single instance or primary key, or a mixed array of persisted instances or primary keys - * @param {Object} [options] Options passed to `through.findAll`, `bulkCreate`, `update` and `destroy` - * @param {Object} [options.validate] Run validation for the join model - * @param {Object} [options.through] Additional attributes for the join table. + * @param {object} [options] Options passed to `through.findAll`, `bulkCreate`, `update` and `destroy` + * @param {object} [options.validate] Run validation for the join model + * @param {object} [options.through] Additional attributes for the join table. * * @returns {Promise} */ @@ -654,9 +654,9 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance source instance to associate new instances with * @param {Model|Model[]|string[]|string|number[]|number} [newInstances] A single instance or primary key, or a mixed array of persisted instances or primary keys - * @param {Object} [options] Options passed to `through.findAll`, `bulkCreate` and `update` - * @param {Object} [options.validate] Run validation for the join model. - * @param {Object} [options.through] Additional attributes for the join table. + * @param {object} [options] Options passed to `through.findAll`, `bulkCreate` and `update` + * @param {object} [options.validate] Run validation for the join model. + * @param {object} [options.through] Additional attributes for the join table. * * @returns {Promise} */ @@ -750,7 +750,7 @@ class BelongsToMany extends Association { * * @param {Model} sourceInstance instance to un associate instances with * @param {Model|Model[]|string|string[]|number|number[]} [oldAssociatedObjects] Can be an Instance or its primary key, or a mixed array of instances and primary keys - * @param {Object} [options] Options passed to `through.destroy` + * @param {object} [options] Options passed to `through.destroy` * * @returns {Promise} */ @@ -773,9 +773,9 @@ class BelongsToMany extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance source instance - * @param {Object} [values] values for target model - * @param {Object} [options] Options passed to create and add - * @param {Object} [options.through] Additional attributes for the join table + * @param {object} [values] values for target model + * @param {object} [options] Options passed to create and add + * @param {object} [options.through] Additional attributes for the join table * * @returns {Promise} */ diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index eafeedb45684..a48813b40974 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -114,7 +114,7 @@ class BelongsTo extends Association { * Get the associated instance. * * @param {Model|Array} instances source instances - * @param {Object} [options] find options + * @param {object} [options] find options * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false. * @param {string} [options.schema] Apply a schema on the related model * @@ -186,7 +186,7 @@ class BelongsTo extends Association { * * @param {Model} sourceInstance the source instance * @param {?|string|number} [associatedInstance] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. - * @param {Object} [options={}] options passed to `this.save` + * @param {object} [options={}] options passed to `this.save` * @param {boolean} [options.save=true] Skip saving this after setting the foreign key if false. * * @returns {Promise} @@ -216,8 +216,8 @@ class BelongsTo extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance the source instance - * @param {Object} [values={}] values to create associated model instance with - * @param {Object} [options={}] Options passed to `target.create` and setAssociation. + * @param {object} [values={}] values to create associated model instance with + * @param {object} [options={}] Options passed to `target.create` and setAssociation. * * @see * {@link Model#create} for a full explanation of options diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 9350de9eda1f..d49e881b4406 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -158,8 +158,8 @@ class HasMany extends Association { * Get everything currently associated with this, using an optional where clause. * * @param {Model|Array} instances source instances - * @param {Object} [options] find options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @@ -243,8 +243,8 @@ class HasMany extends Association { * Count everything currently associated with this, using an optional where clause. * * @param {Model} instance the source instance - * @param {Object} [options] find & count options - * @param {Object} [options.where] An optional where clause to limit the associated models + * @param {object} [options] find & count options + * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * * @returns {Promise} @@ -272,7 +272,7 @@ class HasMany extends Association { * * @param {Model} sourceInstance the source instance * @param {Model|Model[]|string[]|string|number[]|number} [targetInstances] Can be an array of instances or their primary keys - * @param {Object} [options] Options passed to getAssociations + * @param {object} [options] Options passed to getAssociations * * @returns {Promise} */ @@ -313,8 +313,8 @@ class HasMany extends Association { * * @param {Model} sourceInstance source instance to associate new instances with * @param {Model|Model[]|string[]|string|number[]|number} [targetInstances] An array of persisted instances or primary key of instances to associate with this. Pass `null` or `undefined` to remove all associations. - * @param {Object} [options] Options passed to `target.findAll` and `update`. - * @param {Object} [options.validate] Run validation for the join model + * @param {object} [options] Options passed to `target.findAll` and `update`. + * @param {object} [options.validate] Run validation for the join model * * @returns {Promise} */ @@ -388,7 +388,7 @@ class HasMany extends Association { * * @param {Model} sourceInstance the source instance * @param {Model|Model[]|string[]|string|number[]|number} [targetInstances] A single instance or primary key, or a mixed array of persisted instances or primary keys - * @param {Object} [options] Options passed to `target.update`. + * @param {object} [options] Options passed to `target.update`. * * @returns {Promise} */ @@ -416,7 +416,7 @@ class HasMany extends Association { * * @param {Model} sourceInstance instance to un associate instances with * @param {Model|Model[]|string|string[]|number|number[]} [targetInstances] Can be an Instance or its primary key, or a mixed array of instances and primary keys - * @param {Object} [options] Options passed to `target.update` + * @param {object} [options] Options passed to `target.update` * * @returns {Promise} */ @@ -441,8 +441,8 @@ class HasMany extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance source instance - * @param {Object} [values] values for target model instance - * @param {Object} [options] Options passed to `target.create` + * @param {object} [values] values for target model instance + * @param {object} [options] Options passed to `target.create` * * @returns {Promise} */ diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index b955fb5a39ff..3c8f79d13b98 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -113,7 +113,7 @@ class HasOne extends Association { * Get the associated instance. * * @param {Model|Array} instances source instances - * @param {Object} [options] find options + * @param {object} [options] find options * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @@ -186,7 +186,7 @@ class HasOne extends Association { * * @param {Model} sourceInstance the source instance * @param {?|string|number} [associatedInstance] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association. - * @param {Object} [options] Options passed to getAssociation and `target.save` + * @param {object} [options] Options passed to getAssociation and `target.save` * * @returns {Promise} */ @@ -235,8 +235,8 @@ class HasOne extends Association { * Create a new instance of the associated model and associate it with this. * * @param {Model} sourceInstance the source instance - * @param {Object} [values={}] values to create associated model instance with - * @param {Object} [options] Options passed to `target.create` and setAssociation. + * @param {object} [values={}] values to create associated model instance with + * @param {object} [options] Options passed to `target.create` and setAssociation. * * @see * {@link Model#create} for a full explanation of options diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js index 45c68915fdd7..e9a6a2501fb3 100644 --- a/lib/associations/helpers.js +++ b/lib/associations/helpers.js @@ -46,10 +46,10 @@ exports.addForeignKeyConstraints = addForeignKeyConstraints; * * @private * - * @param {Object} association instance - * @param {Object} obj Model prototype + * @param {object} association instance + * @param {object} obj Model prototype * @param {Array} methods Method names to inject - * @param {Object} aliases Mapping between model and association method names + * @param {object} aliases Mapping between model and association method names * */ function mixinMethods(association, obj, methods, aliases) { diff --git a/lib/data-types.js b/lib/data-types.js index 39dbccef7ab6..e2223ffda3be 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -157,7 +157,7 @@ class CITEXT extends ABSTRACT { */ class NUMBER extends ABSTRACT { /** - * @param {Object} options type options + * @param {object} options type options * @param {string|number} [options.length] length of type, like `INT(4)` * @param {boolean} [options.zerofill] Is zero filled? * @param {boolean} [options.unsigned] Is unsigned? diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index b37d412e0a2c..ef98e2ab5a6a 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -61,7 +61,7 @@ class ConnectionManager { * @param {string} moduleName Name of dialect module to lookup * * @private - * @returns {Object} + * @returns {object} */ _loadDialectModule(moduleName) { try { @@ -230,7 +230,7 @@ class ConnectionManager { * Get connection from pool. It sets database version if it's not already set. * Call pool.acquire to get a connection * - * @param {Object} [options] Pool options + * @param {object} [options] Pool options * @param {string} [options.type] Set which replica to use. Available options are `read` and `write` * @param {boolean} [options.useMaster=false] Force master or write replica to get connection from * diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 2b03e5af400c..690be7c456aa 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -91,9 +91,9 @@ class QueryGenerator { * Returns an insert into command * * @param {string} table - * @param {Object} valueHash attribute value pairs - * @param {Object} modelAttributes - * @param {Object} [options] + * @param {object} valueHash attribute value pairs + * @param {object} modelAttributes + * @param {object} [options] * * @private */ @@ -243,9 +243,9 @@ class QueryGenerator { * Returns an insert into command for multiple values. * * @param {string} tableName - * @param {Object} fieldValueHashes - * @param {Object} options - * @param {Object} fieldMappedAttributes + * @param {object} fieldValueHashes + * @param {object} options + * @param {object} fieldMappedAttributes * * @private */ @@ -317,10 +317,10 @@ class QueryGenerator { * Returns an update query * * @param {string} tableName - * @param {Object} attrValueHash - * @param {Object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer - * @param {Object} options - * @param {Object} attributes + * @param {object} attrValueHash + * @param {object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {object} options + * @param {object} attributes * * @private */ @@ -409,10 +409,10 @@ class QueryGenerator { * * @param {string} operator String with the arithmetic operator (e.g. '+' or '-') * @param {string} tableName Name of the table - * @param {Object} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer - * @param {Object} incrementAmountsByField A plain-object with attribute-value-pairs - * @param {Object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs - * @param {Object} options + * @param {object} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {object} incrementAmountsByField A plain-object with attribute-value-pairs + * @param {object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs + * @param {object} options * * @private */ @@ -882,7 +882,7 @@ class QueryGenerator { /** * Quote table name with optional alias and schema attribution * - * @param {string|Object} param table string or object + * @param {string|object} param table string or object * @param {string|boolean} alias alias name * * @returns {string} @@ -1693,8 +1693,8 @@ class QueryGenerator { /** * Returns the SQL fragments to handle returning the attributes from an insert/update query. * - * @param {Object} modelAttributes An object with the model attributes. - * @param {Object} options An object with options. + * @param {object} modelAttributes An object with the model attributes. + * @param {object} options An object with options. * * @private */ @@ -2040,7 +2040,7 @@ class QueryGenerator { /** * Returns an SQL fragment for adding result constraints. * - * @param {Object} options An object with selectQuery options. + * @param {object} options An object with selectQuery options. * @returns {string} The generated sql query. * @private */ diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js index 0c1561d2ab7a..19a1d983b5e5 100644 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ b/lib/dialects/abstract/query-generator/helpers/quote.js @@ -25,7 +25,7 @@ const postgresReservedWords = 'all,analyse,analyze,and,any,array,as,asc,asymmetr * * @param {string} dialect Dialect name * @param {string} identifier Identifier to quote - * @param {Object} [options] + * @param {object} [options] * @param {boolean} [options.force=false] * @param {boolean} [options.quoteIdentifiers=true] * diff --git a/lib/dialects/abstract/query-generator/transaction.js b/lib/dialects/abstract/query-generator/transaction.js index 3d1b0267ec40..06aeae5e810c 100644 --- a/lib/dialects/abstract/query-generator/transaction.js +++ b/lib/dialects/abstract/query-generator/transaction.js @@ -7,7 +7,7 @@ const TransactionQueries = { * Returns a query that sets the transaction isolation level. * * @param {string} value The isolation level. - * @param {Object} options An object with options. + * @param {object} options An object with options. * @returns {string} The generated sql query. * @private */ diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 6cd6b8b0289d..14a9be92fc55 100755 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -38,10 +38,10 @@ class AbstractQuery { * skipValueReplace: bool, do not replace (but do unescape $$). Check correct syntax and if all values are available * * @param {string} sql - * @param {Object|Array} values + * @param {object|Array} values * @param {string} dialect * @param {Function} [replacementFunc] - * @param {Object} [options] + * @param {object} [options] * @private */ static formatBindParameters(sql, values, dialect, replacementFunc, options) { @@ -324,7 +324,7 @@ class AbstractQuery { /** * @param {string} sql * @param {Function} debugContext - * @param {Array|Object} parameters + * @param {Array|object} parameters * @protected * @returns {Function} A function to call after the query was completed. */ @@ -397,8 +397,8 @@ class AbstractQuery { * ] * * @param {Array} rows - * @param {Object} includeOptions - * @param {Object} options + * @param {object} includeOptions + * @param {object} options * @private */ static _groupJoinData(rows, includeOptions, options) { diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index f6533cde90d3..c18d952ca3ae 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -49,7 +49,7 @@ class ConnectionManager extends AbstractConnectionManager { * Set the pool handlers on connection.error * Also set proper timezone once connection is connected. * - * @param {Object} config + * @param {object} config * @returns {Promise} * @private */ diff --git a/lib/dialects/mariadb/data-types.js b/lib/dialects/mariadb/data-types.js index 69f6df175e02..da2710689b3f 100644 --- a/lib/dialects/mariadb/data-types.js +++ b/lib/dialects/mariadb/data-types.js @@ -9,6 +9,7 @@ module.exports = BaseTypes => { /** * types: [buffer_type, ...] + * * @see documentation : https://mariadb.com/kb/en/library/resultset/#field-types * @see connector implementation : https://github.com/MariaDB/mariadb-connector-nodejs/blob/master/lib/const/field-type.js */ diff --git a/lib/dialects/mssql/data-types.js b/lib/dialects/mssql/data-types.js index 48adfe9ec68a..9194051a072f 100644 --- a/lib/dialects/mssql/data-types.js +++ b/lib/dialects/mssql/data-types.js @@ -8,7 +8,7 @@ module.exports = BaseTypes => { /** * Removes unsupported MSSQL options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. * - * @param {Object} dataType The base integer data type. + * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { @@ -23,6 +23,7 @@ module.exports = BaseTypes => { /** * types: [hex, ...] + * * @see hex here https://github.com/tediousjs/tedious/blob/master/src/data-type.js */ diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 33c7b9bc2914..290788987914 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -701,7 +701,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { /** * Generates an SQL query that returns all foreign keys details of a table. * - * @param {string|Object} table + * @param {string|object} table * @param {string} catalogName database name * @returns {string} */ diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index 631cd19caeb3..35eb1404d871 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -15,7 +15,7 @@ @param {QueryInterface} qi @param {string} tableName The name of the table. @param {string} attributeName The name of the attribute that we want to remove. - @param {Object} options + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @private diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 8a1414c31b59..c595790eb78b 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -49,7 +49,7 @@ class ConnectionManager extends AbstractConnectionManager { * Set the pool handlers on connection.error * Also set proper timezone once connection is connected. * - * @param {Object} config + * @param {object} config * @returns {Promise} * @private */ diff --git a/lib/dialects/mysql/data-types.js b/lib/dialects/mysql/data-types.js index c0605cb2b777..35cae2f82cb1 100644 --- a/lib/dialects/mysql/data-types.js +++ b/lib/dialects/mysql/data-types.js @@ -8,6 +8,7 @@ module.exports = BaseTypes => { /** * types: [buffer_type, ...] + * * @see buffer_type here https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-codes.html * @see hex here https://github.com/sidorares/node-mysql2/blob/master/lib/constants/types.js */ diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index f6f23f1a0752..5aff4fc931cd 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -488,7 +488,7 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { /** * Generates an SQL query that returns all foreign keys of a table. * - * @param {Object} table The table. + * @param {object} table The table. * @param {string} schemaName The name of the schema. * @returns {string} The generated sql query. * @private @@ -501,7 +501,7 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { /** * Generates an SQL query that returns the foreign key constraint of a given column. * - * @param {Object} table The table. + * @param {object} table The table. * @param {string} columnName The name of the column. * @returns {string} The generated sql query. * @private diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 48e4aba3bfdd..2ac373a49ec5 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -17,7 +17,7 @@ const sequelizeErrors = require('../../errors'); @param {QueryInterface} qi @param {string} tableName The name of the table. @param {string} columnName The name of the attribute that we want to remove. - @param {Object} options + @param {object} options @private */ @@ -52,7 +52,7 @@ function removeColumn(qi, tableName, columnName, options) { * @param {QueryInterface} qi * @param {string} tableName * @param {string} constraintName - * @param {Object} options + * @param {object} options * * @private */ diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js index 6a8b016f7ed6..70411f7a95c8 100644 --- a/lib/dialects/postgres/data-types.js +++ b/lib/dialects/postgres/data-types.js @@ -9,7 +9,7 @@ module.exports = BaseTypes => { /** * Removes unsupported Postgres options, i.e., LENGTH, UNSIGNED and ZEROFILL, for the integer data types. * - * @param {Object} dataType The base integer data type. + * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { @@ -28,6 +28,7 @@ module.exports = BaseTypes => { * oids: [oid], * array_oids: [oid] * } + * * @see oid here https://github.com/lib/pq/blob/master/oid/types.go */ diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 0375e5ca7bdf..cd8a582f401b 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -19,8 +19,8 @@ const _ = require('lodash'); * * @param {QueryInterface} qi * @param {string} tableName Name of table to create - * @param {Object} attributes Object representing a list of normalized table attributes - * @param {Object} [options] + * @param {object} attributes Object representing a list of normalized table attributes + * @param {object} [options] * @param {Model} [model] * * @returns {Promise} diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index df7c6e573c8c..dec13a879b43 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -15,7 +15,7 @@ class Query extends AbstractQuery { * Rewrite query with parameters. * * @param {string} sql - * @param {Array|Object} values + * @param {Array|object} values * @param {string} dialect * @private */ diff --git a/lib/dialects/sqlite/data-types.js b/lib/dialects/sqlite/data-types.js index 765d5efa4191..30ef4d654023 100644 --- a/lib/dialects/sqlite/data-types.js +++ b/lib/dialects/sqlite/data-types.js @@ -6,7 +6,7 @@ module.exports = BaseTypes => { /** * Removes unsupported SQLite options, i.e., UNSIGNED and ZEROFILL, for the integer data types. * - * @param {Object} dataType The base integer data type. + * @param {object} dataType The base integer data type. * @private */ function removeUnsupportedIntegerOptions(dataType) { diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 150fa6373fd8..da9a7fdc9b81 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -21,7 +21,7 @@ const QueryTypes = require('../../query-types'); @param {QueryInterface} qi @param {string} tableName The name of the table. @param {string} attributeName The name of the attribute that we want to remove. - @param {Object} options + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @since 1.6.0 @@ -48,8 +48,8 @@ exports.removeColumn = removeColumn; @param {QueryInterface} qi @param {string} tableName The name of the table. - @param {Object} attributes An object with the attribute's name as key and its options as value object. - @param {Object} options + @param {object} attributes An object with the attribute's name as key and its options as value object. + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @since 1.6.0 @@ -79,7 +79,7 @@ exports.changeColumn = changeColumn; @param {string} tableName The name of the table. @param {string} attrNameBefore The name of the attribute before it was renamed. @param {string} attrNameAfter The name of the attribute after it was renamed. - @param {Object} options + @param {object} options @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries @since 1.6.0 @@ -104,7 +104,7 @@ exports.renameColumn = renameColumn; * @param {QueryInterface} qi * @param {string} tableName * @param {string} constraintName - * @param {Object} options + * @param {object} options * * @private */ @@ -153,7 +153,7 @@ exports.removeConstraint = removeConstraint; /** * @param {QueryInterface} qi * @param {string} tableName - * @param {Object} options + * @param {object} options * * @private */ @@ -184,7 +184,7 @@ exports.addConstraint = addConstraint; /** * @param {QueryInterface} qi * @param {string} tableName - * @param {Object} options Query Options + * @param {object} options Query Options * * @private * @returns {Promise} diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 83e26c73cb32..60f5c68e2046 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -21,7 +21,7 @@ class Query extends AbstractQuery { * rewrite query with parameters. * * @param {string} sql - * @param {Array|Object} values + * @param {Array|object} values * @param {string} dialect * @private */ diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js index 51d1494f1bfa..cda33985cfa4 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -7,7 +7,7 @@ const BaseError = require('./base-error'); * Used with Promise.AggregateError * * @param {Error} error Error for a given record/instance - * @param {Object} record DAO instance that error belongs to + * @param {object} record DAO instance that error belongs to */ class BulkRecordError extends BaseError { constructor(error, record) { diff --git a/lib/errors/connection-error.js b/lib/errors/connection-error.js index 2d34ce856d59..0536dbfe4926 100644 --- a/lib/errors/connection-error.js +++ b/lib/errors/connection-error.js @@ -11,6 +11,7 @@ class ConnectionError extends BaseError { this.name = 'SequelizeConnectionError'; /** * The connection specific error which triggered this one + * * @type {Error} */ this.parent = parent; diff --git a/lib/errors/database-error.js b/lib/errors/database-error.js index 7fcb67a5e166..a0623a020e26 100644 --- a/lib/errors/database-error.js +++ b/lib/errors/database-error.js @@ -19,11 +19,13 @@ class DatabaseError extends BaseError { this.original = parent; /** * The SQL that triggered the error + * * @type {string} */ this.sql = parent.sql; /** * The parameters for the sql that triggered the error + * * @type {Array} */ this.parameters = parent.parameters; diff --git a/lib/errors/optimistic-lock-error.js b/lib/errors/optimistic-lock-error.js index a745028bc203..103ed7ea2626 100644 --- a/lib/errors/optimistic-lock-error.js +++ b/lib/errors/optimistic-lock-error.js @@ -13,11 +13,13 @@ class OptimisticLockError extends BaseError { this.name = 'SequelizeOptimisticLockError'; /** * The name of the model on which the update was attempted + * * @type {string} */ this.modelName = options.modelName; /** * The values of the attempted update + * * @type {object} */ this.values = options.values; diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 40c4fa5386e6..0cddef5d51af 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -62,8 +62,8 @@ class ValidationErrorItem { * @param {string} type The type/origin of the validation error * @param {string} path The field that triggered the validation error * @param {string} value The value that generated the error - * @param {Object} [inst] the DAO instance that caused the validation error - * @param {Object} [validatorKey] a validation "key", used for identification + * @param {object} [inst] the DAO instance that caused the validation error + * @param {object} [validatorKey] a validation "key", used for identification * @param {string} [fnName] property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable * @param {string} [fnArgs] parameters used with the BUILT-IN validator function, if applicable */ @@ -180,7 +180,7 @@ class ValidationErrorItem { /** * An enum that defines valid ValidationErrorItem `origin` values * - * @type {Object} + * @type {object} * @property CORE {string} specifies errors that originate from the sequelize "core" * @property DB {string} specifies validation errors that originate from the storage engine * @property FUNCTION {string} specifies validation errors that originate from validator functions (both built-in and custom) defined for a given attribute @@ -196,7 +196,7 @@ ValidationErrorItem.Origins = { * that maps current `type` strings (as given to ValidationErrorItem.constructor()) to * our new `origin` values. * - * @type {Object} + * @type {object} */ ValidationErrorItem.TypeStringMap = { 'notnull violation': 'CORE', diff --git a/lib/hooks.js b/lib/hooks.js index 2599bf44f4cd..890a63bfd6e6 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -75,7 +75,7 @@ const Hooks = { /** * Process user supplied hooks definition * - * @param {Object} hooks hooks definition + * @param {object} hooks hooks definition * * @private * @memberof Sequelize @@ -229,6 +229,7 @@ exports.applyTo = applyTo; /** * A hook that is run before validation + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @name beforeValidate @@ -237,6 +238,7 @@ exports.applyTo = applyTo; /** * A hook that is run after validation + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @name afterValidate @@ -245,6 +247,7 @@ exports.applyTo = applyTo; /** * A hook that is run when validation fails + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options, error. Error is the * SequelizeValidationError. If the callback throws an error, it will replace the original validation error. @@ -254,6 +257,7 @@ exports.applyTo = applyTo; /** * A hook that is run before creating a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with attributes, options * @name beforeCreate @@ -262,6 +266,7 @@ exports.applyTo = applyTo; /** * A hook that is run after creating a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with attributes, options * @name afterCreate @@ -270,6 +275,7 @@ exports.applyTo = applyTo; /** * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` + * * @param {string} name * @param {Function} fn A callback function that is called with attributes, options * @name beforeSave @@ -278,6 +284,7 @@ exports.applyTo = applyTo; /** * A hook that is run before upserting + * * @param {string} name * @param {Function} fn A callback function that is called with attributes, options * @name beforeUpsert @@ -286,6 +293,7 @@ exports.applyTo = applyTo; /** * A hook that is run after upserting + * * @param {string} name * @param {Function} fn A callback function that is called with the result of upsert(), options * @name afterUpsert @@ -294,6 +302,7 @@ exports.applyTo = applyTo; /** * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` + * * @param {string} name * @param {Function} fn A callback function that is called with attributes, options * @name afterSave @@ -302,6 +311,7 @@ exports.applyTo = applyTo; /** * A hook that is run before destroying a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @@ -311,6 +321,7 @@ exports.applyTo = applyTo; /** * A hook that is run after destroying a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @@ -320,6 +331,7 @@ exports.applyTo = applyTo; /** * A hook that is run before restoring a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @@ -329,6 +341,7 @@ exports.applyTo = applyTo; /** * A hook that is run after restoring a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @@ -338,6 +351,7 @@ exports.applyTo = applyTo; /** * A hook that is run before updating a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @name beforeUpdate @@ -346,6 +360,7 @@ exports.applyTo = applyTo; /** * A hook that is run after updating a single instance + * * @param {string} name * @param {Function} fn A callback function that is called with instance, options * @name afterUpdate @@ -354,6 +369,7 @@ exports.applyTo = applyTo; /** * A hook that is run before creating instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with instances, options * @name beforeBulkCreate @@ -362,6 +378,7 @@ exports.applyTo = applyTo; /** * A hook that is run after creating instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with instances, options * @name afterBulkCreate @@ -370,6 +387,7 @@ exports.applyTo = applyTo; /** * A hook that is run before destroying instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with options * @@ -379,6 +397,7 @@ exports.applyTo = applyTo; /** * A hook that is run after destroying instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with options * @@ -388,6 +407,7 @@ exports.applyTo = applyTo; /** * A hook that is run before restoring instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with options * @@ -397,6 +417,7 @@ exports.applyTo = applyTo; /** * A hook that is run after restoring instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with options * @@ -406,6 +427,7 @@ exports.applyTo = applyTo; /** * A hook that is run before updating instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with options * @name beforeBulkUpdate @@ -414,6 +436,7 @@ exports.applyTo = applyTo; /** * A hook that is run after updating instances in bulk + * * @param {string} name * @param {Function} fn A callback function that is called with options * @name afterBulkUpdate @@ -422,6 +445,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a find (select) query + * * @param {string} name * @param {Function} fn A callback function that is called with options * @name beforeFind @@ -430,6 +454,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded + * * @param {string} name * @param {Function} fn A callback function that is called with options * @name beforeFindAfterExpandIncludeAll @@ -438,6 +463,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a find (select) query, after all option parsing is complete + * * @param {string} name * @param {Function} fn A callback function that is called with options * @name beforeFindAfterOptions @@ -446,6 +472,7 @@ exports.applyTo = applyTo; /** * A hook that is run after a find (select) query + * * @param {string} name * @param {Function} fn A callback function that is called with instance(s), options * @name afterFind @@ -454,6 +481,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a count query + * * @param {string} name * @param {Function} fn A callback function that is called with options * @name beforeCount @@ -462,6 +490,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a define call + * * @param {string} name * @param {Function} fn A callback function that is called with attributes, options * @name beforeDefine @@ -470,6 +499,7 @@ exports.applyTo = applyTo; /** * A hook that is run after a define call + * * @param {string} name * @param {Function} fn A callback function that is called with factory * @name afterDefine @@ -478,6 +508,7 @@ exports.applyTo = applyTo; /** * A hook that is run before Sequelize() call + * * @param {string} name * @param {Function} fn A callback function that is called with config, options * @name beforeInit @@ -486,6 +517,7 @@ exports.applyTo = applyTo; /** * A hook that is run after Sequelize() call + * * @param {string} name * @param {Function} fn A callback function that is called with sequelize * @name afterInit @@ -494,6 +526,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a connection is created + * * @param {string} name * @param {Function} fn A callback function that is called with config passed to connection * @name beforeConnect @@ -502,6 +535,7 @@ exports.applyTo = applyTo; /** * A hook that is run after a connection is created + * * @param {string} name * @param {Function} fn A callback function that is called with the connection object and the config passed to connection * @name afterConnect @@ -510,6 +544,7 @@ exports.applyTo = applyTo; /** * A hook that is run before a connection is disconnected + * * @param {string} name * @param {Function} fn A callback function that is called with the connection object * @name beforeDisconnect @@ -518,6 +553,7 @@ exports.applyTo = applyTo; /** * A hook that is run after a connection is disconnected + * * @param {string} name * @param {Function} fn A callback function that is called with the connection object * @name afterDisconnect @@ -526,6 +562,7 @@ exports.applyTo = applyTo; /** * A hook that is run before Model.sync call + * * @param {string} name * @param {Function} fn A callback function that is called with options passed to Model.sync * @name beforeSync @@ -534,6 +571,7 @@ exports.applyTo = applyTo; /** * A hook that is run after Model.sync call + * * @param {string} name * @param {Function} fn A callback function that is called with options passed to Model.sync * @name afterSync @@ -542,6 +580,7 @@ exports.applyTo = applyTo; /** * A hook that is run before sequelize.sync call + * * @param {string} name * @param {Function} fn A callback function that is called with options passed to sequelize.sync * @name beforeBulkSync @@ -550,6 +589,7 @@ exports.applyTo = applyTo; /** * A hook that is run after sequelize.sync call + * * @param {string} name * @param {Function} fn A callback function that is called with options passed to sequelize.sync * @name afterBulkSync diff --git a/lib/instance-validator.js b/lib/instance-validator.js index ad14acc2421f..b8b3517cd011 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -12,7 +12,7 @@ const validator = require('./utils/validator-extras').validator; * Instance Validator. * * @param {Instance} modelInstance The model instance. - * @param {Object} options A dictionary with options. + * @param {object} options A dictionary with options. * * @private */ @@ -34,6 +34,7 @@ class InstanceValidator { /** * Exposes a reference to validator.js. This allows you to add custom validations using `validator.extend` + * * @name validator * @private */ @@ -280,7 +281,7 @@ class InstanceValidator { * @param {string} validatorType One of known to Sequelize validators. * @param {string} field The field that is being validated * - * @returns {Object} An object with specific keys to invoke the validator. + * @returns {object} An object with specific keys to invoke the validator. */ _invokeBuiltinValidator(value, test, validatorType, field) { return Promise.try(() => { @@ -329,7 +330,7 @@ class InstanceValidator { /** * Will validate a single field against its schema definition (isnull). * - * @param {Object} rawAttribute As defined in the Schema. + * @param {object} rawAttribute As defined in the Schema. * @param {string} field The field name. * @param {*} value anything. * @@ -421,7 +422,9 @@ class InstanceValidator { } } /** - * @define {string} The error key for arguments as passed by custom validators + * The error key for arguments as passed by custom validators + * + * @type {string} * @private */ InstanceValidator.RAW_KEY_NAME = 'original'; diff --git a/lib/model-manager.js b/lib/model-manager.js index 383735e5c19c..3f36edcf1e3e 100644 --- a/lib/model-manager.js +++ b/lib/model-manager.js @@ -39,7 +39,7 @@ class ModelManager { * Will take foreign key constraints into account so that dependencies are visited before dependents. * * @param {Function} iterator method to execute on each model - * @param {Object} [options] iterator options + * @param {object} [options] iterator options * @private */ forEachModel(iterator, options) { diff --git a/lib/model.js b/lib/model.js index 5f92888e0567..4ea330adc1ee 100644 --- a/lib/model.js +++ b/lib/model.js @@ -77,8 +77,8 @@ class Model { /** * Builds a new model instance. * - * @param {Object} [values={}] an object of key value pairs - * @param {Object} [options] instance construction options + * @param {object} [values={}] an object of key value pairs + * @param {object} [options] instance construction options * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. * @param {boolean} [options.isNewRecord=true] Is this a new record * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` @@ -110,6 +110,7 @@ class Model { /** * Returns true if this instance has not yet been persisted to the database + * * @property isNewRecord * @returns {boolean} */ @@ -864,8 +865,8 @@ class Model { * @see * Validations & Constraints guide * - * @param {Object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: - * @param {string|DataTypes|Object} attributes.column The description of a database column + * @param {object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: + * @param {string|DataTypes|object} attributes.column The description of a database column * @param {string|DataTypes} attributes.column.type A string or a data type * @param {boolean} [attributes.column.allowNull=true] If false, the column will have a NOT NULL constraint, and a not null validation will be run before an instance is saved. * @param {any} [attributes.column.defaultValue=null] A literal default value, a JavaScript function, or an SQL function (see `sequelize.fn`) @@ -882,28 +883,28 @@ class Model { * @param {string} [attributes.column.onDelete] What should happen when the referenced key is deleted. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or NO ACTION * @param {Function} [attributes.column.get] Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying values. * @param {Function} [attributes.column.set] Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the underlying values. - * @param {Object} [attributes.column.validate] An object of validations to execute for this column every time the model is saved. Can be either the name of a validation provided by validator.js, a validation function provided by extending validator.js (see the `DAOValidator` property for more details), or a custom validation function. Custom validation functions are called with the value of the field and the instance itself as the `this` binding, and can possibly take a second callback argument, to signal that they are asynchronous. If the validator is sync, it should throw in the case of a failed validation; if it is async, the callback should be called with the error text. - * @param {Object} options These options are merged with the default define options provided to the Sequelize constructor - * @param {Object} options.sequelize Define the sequelize instance to attach to the new Model. Throw error if none is provided. + * @param {object} [attributes.column.validate] An object of validations to execute for this column every time the model is saved. Can be either the name of a validation provided by validator.js, a validation function provided by extending validator.js (see the `DAOValidator` property for more details), or a custom validation function. Custom validation functions are called with the value of the field and the instance itself as the `this` binding, and can possibly take a second callback argument, to signal that they are asynchronous. If the validator is sync, it should throw in the case of a failed validation; if it is async, the callback should be called with the error text. + * @param {object} options These options are merged with the default define options provided to the Sequelize constructor + * @param {object} options.sequelize Define the sequelize instance to attach to the new Model. Throw error if none is provided. * @param {string} [options.modelName] Set name of the model. By default its same as Class name. - * @param {Object} [options.defaultScope={}] Define the default search scope to use for this model. Scopes have the same form as the options passed to find / findAll - * @param {Object} [options.scopes] More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about how scopes are defined, and what you can do with them + * @param {object} [options.defaultScope={}] Define the default search scope to use for this model. Scopes have the same form as the options passed to find / findAll + * @param {object} [options.scopes] More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about how scopes are defined, and what you can do with them * @param {boolean} [options.omitNull] Don't persist null values. This means that all columns with null values will not be saved * @param {boolean} [options.timestamps=true] Adds createdAt and updatedAt timestamps to the model. * @param {boolean} [options.paranoid=false] Calling `destroy` will not delete the model, but instead set a `deletedAt` timestamp if this is true. Needs `timestamps=true` to work * @param {boolean} [options.underscored=false] Add underscored field to all attributes, this covers user defined attributes, timestamps and foreign keys. Will not affect attributes with explicitly set `field` option * @param {boolean} [options.freezeTableName=false] If freezeTableName is true, sequelize will not try to alter the model name to get the table name. Otherwise, the model name will be pluralized - * @param {Object} [options.name] An object with two attributes, `singular` and `plural`, which are used when this model is associated to others. + * @param {object} [options.name] An object with two attributes, `singular` and `plural`, which are used when this model is associated to others. * @param {string} [options.name.singular=Utils.singularize(modelName)] Singular name for model * @param {string} [options.name.plural=Utils.pluralize(modelName)] Plural name for model - * @param {Array} [options.indexes] indexes definitions + * @param {Array} [options.indexes] indexes definitions * @param {string} [options.indexes[].name] The name of the index. Defaults to model name + _ + fields concatenated * @param {string} [options.indexes[].type] Index type. Only used by mysql. One of `UNIQUE`, `FULLTEXT` and `SPATIAL` * @param {string} [options.indexes[].using] The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and postgres, and postgres additionally supports GIST and GIN. * @param {string} [options.indexes[].operator] Specify index operator. * @param {boolean} [options.indexes[].unique=false] Should the index by unique? Can also be triggered by setting type to `UNIQUE` * @param {boolean} [options.indexes[].concurrently=false] PostgresSQL will build the index without taking any write locks. Postgres only - * @param {Array} [options.indexes[].fields] An array of the fields to index. Each field can either be a string containing the name of the field, a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `attribute` (field name), `length` (create a prefix index of length chars), `order` (the direction the column should be sorted in), `collate` (the collation (sort order) for the column) + * @param {Array} [options.indexes[].fields] An array of the fields to index. Each field can either be a string containing the name of the field, a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `attribute` (field name), `length` (create a prefix index of length chars), `order` (the direction the column should be sorted in), `collate` (the collation (sort order) for the column) * @param {string|boolean} [options.createdAt] Override the name of the createdAt attribute if a string is provided, or disable it if false. Timestamps must be true. Underscored field will be set with underscored setting. * @param {string|boolean} [options.updatedAt] Override the name of the updatedAt attribute if a string is provided, or disable it if false. Timestamps must be true. Underscored field will be set with underscored setting. * @param {string|boolean} [options.deletedAt] Override the name of the deletedAt attribute if a string is provided, or disable it if false. Timestamps must be true. Underscored field will be set with underscored setting. @@ -914,8 +915,8 @@ class Model { * @param {string} [options.comment] Specify comment for model's table * @param {string} [options.collate] Specify collation for model's table * @param {string} [options.initialAutoIncrement] Set the initial AUTO_INCREMENT value for the table in MySQL. - * @param {Object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, beforeSave, afterDestroy, afterUpdate, afterBulkCreate, afterSave, afterBulkDestroy and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions. - * @param {Object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error. + * @param {object} [options.hooks] An object of hook function that are called before and after certain lifecycle events. The possible hooks are: beforeValidate, afterValidate, validationFailed, beforeBulkCreate, beforeBulkDestroy, beforeBulkUpdate, beforeCreate, beforeDestroy, beforeUpdate, afterCreate, beforeSave, afterDestroy, afterUpdate, afterBulkCreate, afterSave, afterBulkDestroy and afterBulkUpdate. See Hooks for more information about hook functions and their signatures. Each property can either be a function, or an array of functions. + * @param {object} [options.validate] An object of model wide validations. Validations have access to all model values via `this`. If the validator function takes an argument, it is assumed to be async, and is called with a callback that accepts an optional error. * * @returns {Model} */ @@ -1264,7 +1265,7 @@ class Model { /** * Sync this Model to the DB, that is create the table. * - * @param {Object} [options] sync options + * @param {object} [options] sync options * * @see * {@link Sequelize#sync} for options @@ -1379,7 +1380,7 @@ class Model { /** * Drop the table represented by this Model * - * @param {Object} [options] drop options + * @param {object} [options] drop options * @param {boolean} [options.cascade=false] Also drop all objects depending on this table, such as views. Only works in postgres * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). @@ -1405,7 +1406,7 @@ class Model { * for the model. * * @param {string} schema The name of the schema - * @param {Object} [options] schema options + * @param {object} [options] schema options * @param {string} [options.schemaDelimiter='.'] The character(s) that separates the schema name from the table name * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). @@ -1437,7 +1438,7 @@ class Model { * Get the table name of the model, taking schema into account. The method will return The name as a string if the model has no schema, * or an object with `tableName`, `schema` and `delimiter` properties. * - * @returns {string|Object} + * @returns {string|object} */ static getTableName() { return this.QueryGenerator.addSchema(this); @@ -1458,8 +1459,8 @@ class Model { * By default this will throw an error if a scope with that name already exists. Pass `override: true` in the options object to silence this error. * * @param {string} name The name of the scope. Use `defaultScope` to override the default scope - * @param {Object|Function} scope scope or options - * @param {Object} [options] scope options + * @param {object|Function} scope scope or options + * @param {object} [options] scope options * @param {boolean} [options.override=false] override old scope if already defined */ static addScope(name, scope, options) { @@ -1519,7 +1520,7 @@ class Model { * Model.scope({ method: ['complexFunction', 'dan@sequelize.com', 42]}).findAll() * // WHERE email like 'dan@sequelize.com%' AND access_level >= 42 * - * @param {?Array|Object|string} [option] The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes and scope functions with no arguments, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default. + * @param {?Array|object|string} [option] The scope(s) to apply. Scopes can either be passed as consecutive arguments, or as an array of arguments. To apply simple scopes and scope functions with no arguments, pass them as strings. For scope function, pass an object, with a `method` property. The value can either be a string, if the method does not take any arguments, or an array, where the first element is the name of the method, and consecutive elements are arguments to that method. Pass null to remove all scopes, including the default. * * @returns {Model} A reference to the model, with the scope(s) applied. Calling scope again on the returned model will clear the previous scope. */ @@ -1640,39 +1641,39 @@ class Model { * * The promise is resolved with an array of Model instances if the query succeeds._ * - * @param {Object} [options] A hash of options to describe the scope of the search - * @param {Object} [options.where] A hash of attributes to describe your search. See above for examples. - * @param {Array|Object} [options.attributes] A list of the attributes that you want to select, or an object with `include` and `exclude` keys. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance + * @param {object} [options] A hash of options to describe the scope of the search + * @param {object} [options.where] A hash of attributes to describe your search. See above for examples. + * @param {Array|object} [options.attributes] A list of the attributes that you want to select, or an object with `include` and `exclude` keys. To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB (or some kind of expression such as `Sequelize.literal`, `Sequelize.fn` and so on), and the second is the name you want the attribute to have in the returned instance * @param {Array} [options.attributes.include] Select all the attributes of the model, plus some additional ones. Useful for aggregations, e.g. `{ attributes: { include: [[sequelize.fn('COUNT', sequelize.col('id')), 'total']] }` * @param {Array} [options.attributes.exclude] Select all the attributes of the model, except some few. Useful for security purposes e.g. `{ attributes: { exclude: ['password'] } }` * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be returned. If false, both deleted and non-deleted records will be returned. Only applies if `options.paranoid` is true for the model. - * @param {Array} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}` or `{ include: ['Alias']}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). + * @param {Array} [options.include] A list of associations to eagerly load using a left join. Supported is either `{ include: [ Model1, Model2, ...]}` or `{ include: [{ model: Model1, as: 'Alias' }]}` or `{ include: ['Alias']}`. If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in the as attribute when eager loading Y). * @param {Model} [options.include[].model] The model you want to eagerly load * @param {string} [options.include[].as] The alias of the relation, in case the model you want to eagerly load is aliased. For `hasOne` / `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural * @param {Association} [options.include[].association] The association you want to eagerly load. (This can be used instead of providing a model/as pair) - * @param {Object} [options.include[].where] Where clauses to apply to the child models. Note that this converts the eager load to an inner join, unless you explicitly set `required: false` + * @param {object} [options.include[].where] Where clauses to apply to the child models. Note that this converts the eager load to an inner join, unless you explicitly set `required: false` * @param {boolean} [options.include[].or=false] Whether to bind the ON and WHERE clause together by OR instead of AND. - * @param {Object} [options.include[].on] Supply your own ON condition for the join. + * @param {object} [options.include[].on] Supply your own ON condition for the join. * @param {Array} [options.include[].attributes] A list of attributes to select from the child model * @param {boolean} [options.include[].required] If true, converts to an inner join, which means that the parent model will only be loaded if it has any matching children. True if `include.where` is set, false otherwise. * @param {boolean} [options.include[].right] If true, converts to a right join if dialect support it. Ignored if `include.required` is true. * @param {boolean} [options.include[].separate] If true, runs a separate query to fetch the associated instances, only supported for hasMany associations * @param {number} [options.include[].limit] Limit the joined rows, only supported with include.separate=true * @param {string} [options.include[].through.as] The alias for the join model, in case you want to give it a different name than the default one. - * @param {Object} [options.include[].through.where] Filter on the join model for belongsToMany relations + * @param {object} [options.include[].through.where] Filter on the join model for belongsToMany relations * @param {Array} [options.include[].through.attributes] A list of attributes to select from the join model for belongsToMany relations - * @param {Array} [options.include[].include] Load further nested related models + * @param {Array} [options.include[].include] Load further nested related models * @param {boolean} [options.include[].duplicating] Mark the include as duplicating, will prevent a subquery from being used. * @param {Array|Sequelize.fn|Sequelize.col|Sequelize.literal} [options.order] Specifies an ordering. Using an array, you can provide several columns / functions to order by. Each element can be further wrapped in a two-element array. The first element is the column / function to order by, the second is the direction. For example: `order: [['name', 'DESC']]`. In this way the column will be escaped, but the direction will not. * @param {number} [options.limit] Limit for result * @param {number} [options.offset] Offset for result * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string|Object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. + * @param {string|object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. * @param {boolean} [options.skipLocked] Skip locked rows. Only supported in Postgres. * @param {boolean} [options.raw] Return raw result. See sequelize.query for more information. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Object} [options.having] Having options + * @param {object} [options.having] Having options * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean|Error} [options.rejectOnEmpty=false] Throws an error when no records found * @@ -1862,7 +1863,7 @@ class Model { * Search for a single instance by its primary key._ * * @param {number|string|Buffer} param The value of the desired instance's primary key. - * @param {Object} [options] find options + * @param {object} [options] find options * @param {Transaction} [options.transaction] Transaction to run query under * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @@ -1896,7 +1897,7 @@ class Model { * * __Alias__: _find_ * - * @param {Object} [options] A hash of options to describe the scope of the search + * @param {object} [options] A hash of options to describe the scope of the search * @param {Transaction} [options.transaction] Transaction to run query under * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @@ -1934,8 +1935,8 @@ class Model { * * @param {string} attribute The attribute to aggregate over. Can be a field name or * * @param {string} aggregateFunction The function to use for aggregation, e.g. sum, max etc. - * @param {Object} [options] Query options. See sequelize.query for full options - * @param {Object} [options.where] A hash of search attributes. + * @param {object} [options] Query options. See sequelize.query for full options + * @param {object} [options.where] A hash of search attributes. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). * @param {DataTypes|string} [options.dataType] The type of the result. If `field` is a field in this Model, the default will be the type of that field, otherwise defaults to float. @@ -1943,7 +1944,7 @@ class Model { * @param {Transaction} [options.transaction] Transaction to run query under * @param {boolean} [options.plain] When `true`, the first returned value of `aggregateFunction` is cast to `dataType` and returned. If additional attributes are specified, along with `group` clauses, set `plain` to `false` to return all values of all returned rows. Defaults to `true` * - * @returns {Promise} Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in which case the complete data result is returned. + * @returns {Promise} Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in which case the complete data result is returned. */ static aggregate(attribute, aggregateFunction, options) { options = Utils.cloneDeep(options); @@ -2006,9 +2007,9 @@ class Model { * * If you provide an `include` option, the number of matching associations will be counted instead. * - * @param {Object} [options] options - * @param {Object} [options.where] A hash of search attributes. - * @param {Object} [options.include] Include options. See `find` for details + * @param {object} [options] options + * @param {object} [options.where] A hash of search attributes. + * @param {object} [options.include] Include options. See `find` for details * @param {boolean} [options.paranoid=true] Set `true` to count only non-deleted records. Can be used on models with `paranoid` enabled * @param {boolean} [options.distinct] Apply COUNT(DISTINCT(col)) on primary key or on options.col. * @param {string} [options.col] Column on which COUNT() should be applied @@ -2076,7 +2077,7 @@ class Model { * * # Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted * - * @param {Object} [options] See findAll options + * @param {object} [options] See findAll options * * @see * {@link Model.findAll} for a specification of find and query options @@ -2110,7 +2111,7 @@ class Model { * Find the maximum value of field * * @param {string} field attribute / field name - * @param {Object} [options] See aggregate + * @param {object} [options] See aggregate * * @see * {@link Model.aggregate} for options @@ -2125,7 +2126,7 @@ class Model { * Find the minimum value of field * * @param {string} field attribute / field name - * @param {Object} [options] See aggregate + * @param {object} [options] See aggregate * * @see * {@link Model.aggregate} for options @@ -2140,7 +2141,7 @@ class Model { * Find the sum of field * * @param {string} field attribute / field name - * @param {Object} [options] See aggregate + * @param {object} [options] See aggregate * * @see * {@link Model.aggregate} for options @@ -2154,8 +2155,8 @@ class Model { /** * Builds a new model instance. * - * @param {Object|Array} values An object of key value pairs or an array of such. If an array, the function will return an array of instances. - * @param {Object} [options] Instance build options + * @param {object|Array} values An object of key value pairs or an array of such. If an array, the function will return an array of instances. + * @param {object} [options] Instance build options * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. * @param {boolean} [options.isNewRecord=true] Is this new record * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` @@ -2198,8 +2199,8 @@ class Model { * @see * {@link Model.save} * - * @param {Object} values Hash of data values to create new record with - * @param {Object} [options] Build and query options + * @param {object} values Hash of data values to create new record with + * @param {object} [options] Build and query options * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. * @param {boolean} [options.isNewRecord=true] Is this new record * @param {Array} [options.include] An array of include options - Used to build prefetched/included model instances. See `set` @@ -2232,10 +2233,10 @@ class Model { * Find a row that matches the query, or build (but don't save) the row if none is found. * The successful result of the promise will be (instance, built) * - * @param {Object} options find options - * @param {Object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. - * @param {Object} [options.defaults] Default values to use if building a new instance - * @param {Object} [options.transaction] Transaction to run query under + * @param {object} options find options + * @param {object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. + * @param {object} [options.defaults] Default values to use if building a new instance + * @param {object} [options.transaction] Transaction to run query under * * @returns {Promise} */ @@ -2276,9 +2277,9 @@ class Model { * @see * {@link Model.findAll} for a full specification of find and options * - * @param {Object} options find and create options - * @param {Object} options.where where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. - * @param {Object} [options.defaults] Default values to use if creating a new instance + * @param {object} options find and create options + * @param {object} options.where where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. + * @param {object} [options.defaults] Default values to use if creating a new instance * @param {Transaction} [options.transaction] Transaction to run query under * * @returns {Promise} @@ -2387,9 +2388,9 @@ class Model { * @see * {@link Model.findAll} for a full specification of find and options * - * @param {Object} options find options - * @param {Object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. - * @param {Object} [options.defaults] Default values to use if creating a new instance + * @param {object} options find options + * @param {object} options.where A hash of search attributes. If `where` is a plain object it will be appended with defaults to build a new instance. + * @param {object} [options.defaults] Default values to use if creating a new instance * * @returns {Promise} */ @@ -2426,8 +2427,8 @@ class Model { * * MSSQL - Implemented as a single query using `MERGE` and `WHEN (NOT) MATCHED THEN` * **Note** that SQLite returns undefined for created, no matter if the row was created or updated. This is because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know whether the row was inserted or not. * - * @param {Object} values hash of values to upsert - * @param {Object} [options] upsert options + * @param {object} values hash of values to upsert + * @param {object} [options] upsert options * @param {boolean} [options.validate=true] Run validations before the row is inserted * @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all changed fields * @param {boolean} [options.hooks=true] Run before / after upsert hooks? @@ -2519,7 +2520,7 @@ class Model { * If validation fails, the promise is rejected with an array-like [AggregateError](http://bluebirdjs.com/docs/api/aggregateerror.html) * * @param {Array} records List of objects (key/value pairs) to create instances from - * @param {Object} [options] Bulk create options + * @param {object} [options] Bulk create options * @param {Array} [options.fields] Fields to insert (defaults to all fields) * @param {boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation * @param {boolean} [options.hooks=true] Run before / after bulk create hooks? @@ -2851,7 +2852,7 @@ class Model { /** * Truncate all instances of the model. This is a convenient method for Model.destroy({ truncate: true }). * - * @param {Object} [options] The options passed to Model.destroy in addition to truncate + * @param {object} [options] The options passed to Model.destroy in addition to truncate * @param {boolean|Function} [options.cascade = false] Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. * @param {boolean} [options.restartIdentity=false] Automatically restart sequences owned by columns of the truncated table. * @param {Transaction} [options.transaction] Transaction to run query under @@ -2873,8 +2874,8 @@ class Model { /** * Delete multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. * - * @param {Object} options destroy options - * @param {Object} [options.where] Filter the destroy + * @param {object} options destroy options + * @param {object} [options.where] Filter the destroy * @param {boolean} [options.hooks=true] Run before / after bulk destroy hooks? * @param {boolean} [options.individualHooks=false] If set to true, destroy will SELECT all records matching the where parameter and will execute before / after destroy hooks on each row * @param {number} [options.limit] How many rows to delete @@ -2964,8 +2965,8 @@ class Model { /** * Restore multiple instances if `paranoid` is enabled. * - * @param {Object} options restore options - * @param {Object} [options.where] Filter the restore + * @param {object} options restore options + * @param {object} [options.where] Filter the restore * @param {boolean} [options.hooks=true] Run before / after bulk restore hooks? * @param {boolean} [options.individualHooks=false] If set to true, restore will find all records within the where parameter and will execute before / after bulkRestore hooks on each row * @param {number} [options.limit] How many rows to undelete (only for mysql) @@ -3030,9 +3031,9 @@ class Model { /** * Update multiple instances that match the where options. * - * @param {Object} values hash of values to update - * @param {Object} options update options - * @param {Object} options.where Options to describe the scope of the search. + * @param {object} values hash of values to update + * @param {object} options update options + * @param {object} options.where Options to describe the scope of the search. * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be updated. If false, both deleted and non-deleted records will be updated. Only applies if `options.paranoid` is true for the model. * @param {Array} [options.fields] Fields to update (defaults to all fields) * @param {boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation @@ -3254,7 +3255,7 @@ class Model { * Run a describe query on the table. * * @param {string} [schema] schema name to search table in - * @param {Object} [options] query options + * @param {object} [options] query options * * @returns {Promise} hash of attributes and their types */ @@ -3321,9 +3322,9 @@ class Model { * @see * {@link Model#reload} * - * @param {string|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {Object} options increment options - * @param {Object} options.where conditions hash + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} options increment options + * @param {object} options.where conditions hash * @param {number} [options.by=1] The number to increment by * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -3422,8 +3423,8 @@ class Model { * // `by` is ignored, since each column has its own value * Model.decrement({ answer: 42, tries: -1}, { by: 2, where: { foo: 'bar' } }); * - * @param {string|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {Object} options decrement options, similar to increment + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} options decrement options, similar to increment * * @see * {@link Model.increment} @@ -3452,7 +3453,7 @@ class Model { * * @param {boolean} [checkVersion=false] include version attribute in where hash * - * @returns {Object} + * @returns {object} */ where(checkVersion) { const where = this.constructor.primaryKeyAttributes.reduce((result, attribute) => { @@ -3508,11 +3509,11 @@ class Model { * If key is given and a field or virtual getter is present for the key it will call that getter - else it will return the value for key. * * @param {string} [key] key to get value of - * @param {Object} [options] get options + * @param {object} [options] get options * @param {boolean} [options.plain=false] If set to true, included instances will be returned as plain objects * @param {boolean} [options.raw=false] If set to true, field and virtual setters will be ignored * - * @returns {Object|any} + * @returns {object|any} */ get(key, options) { if (options === undefined && typeof key === 'object') { @@ -3597,9 +3598,9 @@ class Model { * @see * {@link Model.findAll} for more information about includes * - * @param {string|Object} key key to set, it can be string or object. When string it will set that key, for object it will loop over all object properties nd set them. + * @param {string|object} key key to set, it can be string or object. When string it will set that key, for object it will loop over all object properties nd set them. * @param {any} value value to set - * @param {Object} [options] set options + * @param {object} [options] set options * @param {boolean} [options.raw=false] If set to true, field and virtual setters will be ignored * @param {boolean} [options.reset=false] Clear all previously set data values * @@ -3848,7 +3849,7 @@ class Model { * * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. * - * @param {Object} [options] save options + * @param {object} [options] save options * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {boolean} [options.validate=true] If false, validations won't be run. @@ -4129,7 +4130,7 @@ class Model { * @see * {@link Model.findAll} * - * @param {Object} [options] Options that are passed on to `Model.find` + * @param {object} [options] Options that are passed on to `Model.find` * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * * @returns {Promise} @@ -4165,7 +4166,7 @@ class Model { * * The promise fulfills if and only if validation successful; otherwise it rejects an Error instance containing { field name : [error msgs] } entries. * - * @param {Object} [options] Options that are passed to the validator + * @param {object} [options] Options that are passed to the validator * @param {Array} [options.skip] An array of strings. All properties that are in this array will not be validated * @param {Array} [options.fields] An array of strings. Only the properties that are in this array will be validated * @param {boolean} [options.hooks=true] Run before and after validate hooks @@ -4185,8 +4186,8 @@ class Model { * @see * {@link Model#save} * - * @param {Object} values See `set` - * @param {Object} options See `save` + * @param {object} values See `set` + * @param {object} options See `save` * * @returns {Promise} */ @@ -4219,7 +4220,7 @@ class Model { /** * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will either be completely deleted, or have its deletedAt timestamp set to the current time. * - * @param {Object} [options={}] destroy options + * @param {object} [options={}] destroy options * @param {boolean} [options.force=false] If set to true, paranoid models will actually be deleted * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {Transaction} [options.transaction] Transaction to run query under @@ -4288,7 +4289,7 @@ class Model { /** * Restore the row corresponding to this instance. Only available for paranoid models. * - * @param {Object} [options={}] restore options + * @param {object} [options={}] restore options * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {Transaction} [options.transaction] Transaction to run query under * @@ -4341,8 +4342,8 @@ class Model { * @see * {@link Model#reload} * - * @param {string|Array|Object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {Object} [options] options + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} [options] options * @param {number} [options.by=1] The number to increment by * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -4381,8 +4382,8 @@ class Model { * * @see * {@link Model#reload} - * @param {string|Array|Object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given - * @param {Object} [options] decrement options + * @param {string|Array|object} fields If a string is provided, that column is decremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is decremented by the value given + * @param {object} [options] decrement options * @param {number} [options.by=1] The number to decrement by * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -4442,7 +4443,7 @@ class Model { * @see * {@link Model#get} * - * @returns {Object} + * @returns {object} */ toJSON() { return _.cloneDeep( @@ -4457,12 +4458,12 @@ class Model { * The foreign key is added on the target. * * @param {Model} target Target model - * @param {Object} [options] hasMany association options + * @param {object} [options] hasMany association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks - * @param {string|Object} [options.as] The alias of this model. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key in the target table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source + * @param {string|object} [options.as] The alias of this model. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target + * @param {string|object} [options.foreignKey] The name of the foreign key in the target table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source * @param {string} [options.sourceKey] The name of the field to use as the key for the association in the source table. Defaults to the primary key of the source table - * @param {Object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) + * @param {object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) * @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise * @param {string} [options.onUpdate='CASCADE'] Set `ON UPDATE` * @param {boolean} [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key. @@ -4478,16 +4479,16 @@ class Model { * Create an N:M association with a join table. Defining `through` is required. * * @param {Model} target Target model - * @param {Object} options belongsToMany association options + * @param {object} options belongsToMany association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks - * @param {Model|string|Object} options.through The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it. + * @param {Model|string|object} options.through The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it. * @param {Model} [options.through.model] The model used to join both sides of the N:M association. - * @param {Object} [options.through.scope] A key/value set that will be used for association create and find defaults on the through model. (Remember to add the attributes to the through model) + * @param {object} [options.through.scope] A key/value set that will be used for association create and find defaults on the through model. (Remember to add the attributes to the through model) * @param {boolean} [options.through.unique=true] If true a unique key will be generated from the foreign keys used (might want to turn this off and create specific unique keys when using scopes) - * @param {string|Object} [options.as] The alias of this association. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key in the join table (representing the source model) or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source - * @param {string|Object} [options.otherKey] The name of the foreign key in the join table (representing the target model) or an object representing the type definition for the other column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target - * @param {Object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) + * @param {string|object} [options.as] The alias of this association. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with `plural` and `singular` keys. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the pluralized name of target + * @param {string|object} [options.foreignKey] The name of the foreign key in the join table (representing the source model) or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source + * @param {string|object} [options.otherKey] The name of the foreign key in the join table (representing the target model) or an object representing the type definition for the other column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target + * @param {object} [options.scope] A key/value set that will be used for association create and find defaults on the target. (sqlite not supported for N:M) * @param {boolean} [options.timestamps=sequelize.options.timestamps] Should the join model have timestamps * @param {string} [options.onDelete='SET NULL|CASCADE'] Cascade if this is a n:m, and set null if it is a 1:m * @param {string} [options.onUpdate='CASCADE'] Sets `ON UPDATE` @@ -4513,10 +4514,10 @@ class Model { * Creates an association between this (the source) and the provided target. The foreign key is added on the target. * * @param {Model} target Target model - * @param {Object} [options] hasOne association options + * @param {object} [options] hasOne association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the singularized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key attribute in the target model or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source + * @param {string|object} [options.foreignKey] The name of the foreign key attribute in the target model or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of source + primary key of source * @param {string} [options.sourceKey] The name of the attribute to use as the key for the association in the source table. Defaults to the primary key of the source table * @param {string} [options.onDelete='SET NULL|CASCADE'] SET NULL if foreignKey allows nulls, CASCADE if otherwise * @param {string} [options.onUpdate='CASCADE'] Sets 'ON UPDATE' @@ -4534,10 +4535,10 @@ class Model { * Creates an association between this (the source) and the provided target. The foreign key is added on the source. * * @param {Model} target The target model - * @param {Object} [options] belongsTo association options + * @param {object} [options] belongsTo association options * @param {boolean} [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks * @param {string} [options.as] The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the association, you should provide the same alias when eager loading and when getting associated models. Defaults to the singularized name of target - * @param {string|Object} [options.foreignKey] The name of the foreign key attribute in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target + * @param {string|object} [options.foreignKey] The name of the foreign key attribute in the source table or an object representing the type definition for the foreign column (see `Sequelize.define` for syntax). When using an object, you can add a `name` property to set the name of the column. Defaults to the name of target + primary key of target * @param {string} [options.targetKey] The name of the attribute to use as the key for the association in the target table. Defaults to the primary key of the target table * @param {string} [options.onDelete='SET NULL|NO ACTION'] SET NULL if foreignKey allows nulls, NO ACTION if otherwise * @param {string} [options.onUpdate='CASCADE'] Sets 'ON UPDATE' diff --git a/lib/query-interface.js b/lib/query-interface.js index 712d848c73d9..1797fbd9f068 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -28,7 +28,7 @@ class QueryInterface { * Create a database * * @param {string} database Database name to create - * @param {Object} [options] Query options + * @param {object} [options] Query options * @param {string} [options.charset] Database default character set, MYSQL only * @param {string} [options.collate] Database default collation * @param {string} [options.encoding] Database default character set, PostgreSQL only @@ -47,7 +47,7 @@ class QueryInterface { * Drop a database * * @param {string} database Database name to drop - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -61,7 +61,7 @@ class QueryInterface { * Create a schema * * @param {string} schema Schema name to create - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -75,7 +75,7 @@ class QueryInterface { * Drop a schema * * @param {string} schema Schema name to drop - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -88,7 +88,7 @@ class QueryInterface { /** * Drop all schemas * - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -104,7 +104,7 @@ class QueryInterface { /** * Show all schemas * - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -124,7 +124,7 @@ class QueryInterface { /** * Return database version * - * @param {Object} [options] Query options + * @param {object} [options] Query options * @param {QueryType} [options.type] Query type * * @returns {Promise} @@ -184,8 +184,8 @@ class QueryInterface { * ``` * * @param {string} tableName Name of table to create - * @param {Object} attributes Object representing a list of table attributes to create - * @param {Object} [options] create table and query options + * @param {object} attributes Object representing a list of table attributes to create + * @param {object} [options] create table and query options * @param {Model} [model] model class * * @returns {Promise} @@ -240,7 +240,7 @@ class QueryInterface { * Drop a table from database * * @param {string} tableName Table name to drop - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} */ @@ -282,7 +282,7 @@ class QueryInterface { /** * Drop all tables from database * - * @param {Object} [options] query options + * @param {object} [options] query options * @param {Array} [options.skip] List of table to skip * * @returns {Promise} @@ -335,7 +335,7 @@ class QueryInterface { * Drop specified enum from database (Postgres only) * * @param {string} [enumName] Enum name to drop - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} * @private @@ -356,7 +356,7 @@ class QueryInterface { /** * Drop all enums from database (Postgres only) * - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} * @private @@ -378,7 +378,7 @@ class QueryInterface { * List all enums (Postgres only) * * @param {string} [tableName] Table whose enum to list - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} * @private @@ -394,7 +394,7 @@ class QueryInterface { * * @param {string} before Current name of table * @param {string} after New name from table - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -407,7 +407,7 @@ class QueryInterface { /** * Get all tables in current database * - * @param {Object} [options] Query options + * @param {object} [options] Query options * @param {boolean} [options.raw=true] Run query in raw mode * @param {QueryType} [options.type=QueryType.SHOWTABLE] query type * @@ -445,9 +445,9 @@ class QueryInterface { * ``` * * @param {string} tableName table name - * @param {Object} [options] Query options + * @param {object} [options] Query options * - * @returns {Promise} + * @returns {Promise} */ describeTable(tableName, options) { let schema = null; @@ -499,8 +499,8 @@ class QueryInterface { * * @param {string} table Table to add column to * @param {string} key Column name - * @param {Object} attribute Attribute definition - * @param {Object} [options] Query options + * @param {object} attribute Attribute definition + * @param {object} [options] Query options * * @returns {Promise} */ @@ -519,7 +519,7 @@ class QueryInterface { * * @param {string} tableName Table to remove column from * @param {string} attributeName Column name to remove - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -546,8 +546,8 @@ class QueryInterface { * * @param {string} tableName Table name to change from * @param {string} attributeName Column name - * @param {Object} dataTypeOrOptions Attribute definition for new column - * @param {Object} [options] Query options + * @param {object} dataTypeOrOptions Attribute definition for new column + * @param {object} [options] Query options * * @returns {Promise} */ @@ -582,7 +582,7 @@ class QueryInterface { * @param {string} tableName Table name whose column to rename * @param {string} attrNameBefore Current column name * @param {string} attrNameAfter New column name - * @param {Object} [options] Query option + * @param {object} [options] Query option * * @returns {Promise} */ @@ -625,9 +625,9 @@ class QueryInterface { /** * Add an index to a column * - * @param {string|Object} tableName Table name to add index on, can be a object with schema + * @param {string|object} tableName Table name to add index on, can be a object with schema * @param {Array} [attributes] Use options.fields instead, List of attributes to add index on - * @param {Object} options indexes options + * @param {object} options indexes options * @param {Array} options.fields List of attributes to add index on * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created * @param {boolean} [options.unique] Create a unique index @@ -635,7 +635,7 @@ class QueryInterface { * @param {string} [options.operator] Index operator * @param {string} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL * @param {string} [options.name] Name of the index. Default is
__ - * @param {Object} [options.where] Where condition on index, for partial indexes + * @param {object} [options.where] Where condition on index, for partial indexes * @param {string} [rawTablename] table name, this is just for backward compatibiity * * @returns {Promise} @@ -663,7 +663,7 @@ class QueryInterface { * Show indexes on a table * * @param {string} tableName table name - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} * @private @@ -678,7 +678,7 @@ class QueryInterface { * Returns all foreign key constraints of a table * * @param {string[]} tableNames table names - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -719,7 +719,7 @@ class QueryInterface { * Remind: constraint informations won't return if it's sqlite. * * @param {string} tableName table name - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -755,7 +755,7 @@ class QueryInterface { * * @param {string} tableName Table name to drop index from * @param {string} indexNameOrAttributes Index name - * @param {Object} [options] Query options + * @param {object} [options] Query options * * @returns {Promise} */ @@ -815,12 +815,12 @@ class QueryInterface { * * @param {string} tableName Table name where you want to add a constraint * @param {Array} attributes Array of column names to apply the constraint over - * @param {Object} options An object to define the constraint name, type etc + * @param {object} options An object to define the constraint name, type etc * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names * @param {string} [options.defaultValue] The value for the default constraint - * @param {Object} [options.where] Where clause/expression for the CHECK constraint - * @param {Object} [options.references] Object specifying target table, column name to create foreign key constraint + * @param {object} [options.where] Where clause/expression for the CHECK constraint + * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint * @param {string} [options.references.table] Target table name * @param {string} [options.references.field] Target column name * @param {string} [rawTablename] Table name, for backward compatibility @@ -863,7 +863,7 @@ class QueryInterface { * * @param {string} tableName Table name to drop constraint from * @param {string} constraintName Constraint name - * @param {Object} options Query options + * @param {object} options Query options * * @returns {Promise} */ @@ -901,11 +901,11 @@ class QueryInterface { * Upsert * * @param {string} tableName table to upsert on - * @param {Object} insertValues values to be inserted, mapped to field name - * @param {Object} updateValues values to be updated, mapped to field name - * @param {Object} where various conditions + * @param {object} insertValues values to be inserted, mapped to field name + * @param {object} updateValues values to be updated, mapped to field name + * @param {object} where various conditions * @param {Model} model Model to upsert on - * @param {Object} options query options + * @param {object} options query options * * @returns {Promise} Resolves an array with */ @@ -994,8 +994,8 @@ class QueryInterface { * * @param {string} tableName Table name to insert record to * @param {Array} records List of records to insert - * @param {Object} options Various options, please see Model.bulkCreate options - * @param {Object} attributes Various attributes mapped by field name + * @param {object} options Various options, please see Model.bulkCreate options + * @param {object} attributes Various attributes mapped by field name * * @returns {Promise} */ @@ -1033,10 +1033,10 @@ class QueryInterface { * ); * * @param {string} tableName Table name to update - * @param {Object} values Values to be inserted, mapped to field name - * @param {Object} identifier A hash with conditions OR an ID as integer OR a string with conditions - * @param {Object} [options] Various options, please see Model.bulkCreate options - * @param {Object} [attributes] Attributes on return objects if supported by SQL dialect + * @param {object} values Values to be inserted, mapped to field name + * @param {object} identifier A hash with conditions OR an ID as integer OR a string with conditions + * @param {object} [options] Various options, please see Model.bulkCreate options + * @param {object} [attributes] Attributes on return objects if supported by SQL dialect * * @returns {Promise} */ @@ -1096,8 +1096,8 @@ class QueryInterface { * Delete multiple records from a table * * @param {string} tableName table name from where to delete records - * @param {Object} where where conditions to find records to delete - * @param {Object} [options] options + * @param {object} where where conditions to find records to delete + * @param {object} [options] options * @param {boolean} [options.truncate] Use truncate table command * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table. @@ -1259,9 +1259,9 @@ class QueryInterface { * @param {string} language The name of the language that the function is implemented in * @param {string} body Source code of function * @param {Array} optionsArray Extra-options for creation - * @param {Object} [options] query options + * @param {object} [options] query options * @param {boolean} options.force If force is true, any existing functions with the same parameters will be replaced. For postgres, this means using `CREATE OR REPLACE FUNCTION` instead of `CREATE FUNCTION`. Default is false - * @param {Array} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well. + * @param {Array} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well. * * @returns {Promise} */ @@ -1289,7 +1289,7 @@ class QueryInterface { * * @param {string} functionName Name of SQL function to drop * @param {Array} params List of parameters declared for SQL function - * @param {Object} [options] query options + * @param {object} [options] query options * * @returns {Promise} */ @@ -1319,7 +1319,7 @@ class QueryInterface { * @param {string} oldFunctionName Current name of function * @param {Array} params List of parameters declared for SQL function * @param {string} newFunctionName New name of function - * @param {Object} [options] query options + * @param {object} [options] query options * * @returns {Promise} */ diff --git a/lib/sequelize.js b/lib/sequelize.js index ebd92dbd0932..e645d8c7bc9f 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -126,7 +126,7 @@ class Sequelize { * @param {string} [database] The name of the database * @param {string} [username=null] The username which is used to authenticate against the database. * @param {string} [password=null] The password which is used to authenticate against the database. Supports SQLCipher encryption for SQLite. - * @param {Object} [options={}] An object with options. + * @param {object} [options={}] An object with options. * @param {string} [options.host='localhost'] The host of the relational database. * @param {number} [options.port=] The port of the relational database. * @param {string} [options.username=null] The username which is used to authenticate against the database. @@ -135,14 +135,14 @@ class Sequelize { * @param {string} [options.dialect] The dialect of the database you are connecting to. One of mysql, postgres, sqlite and mssql. * @param {string} [options.dialectModule=null] If specified, use this dialect library. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify 'require("pg.js")' here * @param {string} [options.dialectModulePath=null] If specified, load the dialect library from this path. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify '/path/to/pg.js' here - * @param {Object} [options.dialectOptions] An object of additional options, which are passed directly to the connection library + * @param {object} [options.dialectOptions] An object of additional options, which are passed directly to the connection library * @param {string} [options.storage] Only used by sqlite. Defaults to ':memory:' * @param {string} [options.protocol='tcp'] The protocol of the relational database. - * @param {Object} [options.define={}] Default options for model definitions. See {@link Model.init}. - * @param {Object} [options.query={}] Default options for sequelize.query + * @param {object} [options.define={}] Default options for model definitions. See {@link Model.init}. + * @param {object} [options.query={}] Default options for sequelize.query * @param {string} [options.schema=null] A schema to use - * @param {Object} [options.set={}] Default options for sequelize.set - * @param {Object} [options.sync={}] Default options for sequelize.sync + * @param {object} [options.set={}] Default options for sequelize.set + * @param {object} [options.sync={}] Default options for sequelize.sync * @param {string} [options.timezone='+00:00'] The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. Will also accept string versions of timezones used by moment.js (e.g. 'America/Los_Angeles'); this is useful to capture daylight savings time changes. * @param {string|boolean} [options.clientMinMessages='warning'] The PostgreSQL `client_min_messages` session parameter. Set to `false` to not override the database's default. * @param {boolean} [options.standardConformingStrings=true] The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. WARNING: Setting this to false may expose vulnerabilities and is not recommended! @@ -151,7 +151,7 @@ class Sequelize { * @param {boolean} [options.omitNull=false] A flag that defines if null values should be passed to SQL queries or not. * @param {boolean} [options.native=false] A flag that defines if native library shall be used or not. Currently only has an effect for postgres * @param {boolean} [options.replication=false] Use read / write replication. To enable replication, pass an object, with two properties, read and write. Write should be an object (a single server for handling writes), and read an array of object (several servers to handle reads). Each read/write server can have the following properties: `host`, `port`, `username`, `password`, `database` - * @param {Object} [options.pool] sequelize connection pool configuration + * @param {object} [options.pool] sequelize connection pool configuration * @param {number} [options.pool.max=5] Maximum number of connection in pool * @param {number} [options.pool.min=0] Minimum number of connection in pool * @param {number} [options.pool.idle=10000] The maximum time, in milliseconds, that a connection can be idle before being released. @@ -161,12 +161,12 @@ class Sequelize { * @param {boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them. WARNING: Setting this to false may expose vulnerabilities and is not recommended! * @param {string} [options.transactionType='DEFERRED'] Set the default transaction type. See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] Set the default transaction isolation level. See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options. - * @param {Object} [options.retry] Set of flags that control when a query is automatically retried. Accepts all options for [`retry-as-promised`](https://github.com/mickhansen/retry-as-promised). + * @param {object} [options.retry] Set of flags that control when a query is automatically retried. Accepts all options for [`retry-as-promised`](https://github.com/mickhansen/retry-as-promised). * @param {Array} [options.retry.match] Only retry a query if the error matches one of these strings. * @param {number} [options.retry.max] How many times a failing query is automatically retried. Set to 0 to disable retrying on SQL_BUSY error. * @param {boolean} [options.typeValidation=false] Run built-in type validators on insert and update, and select with where clause, e.g. validate that arguments passed to integer fields are integer-like. - * @param {Object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. - * @param {Object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. + * @param {object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. + * @param {object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. * @param {boolean} [options.minifyAliases=false] A flag that defines if aliases should be minified (mostly useful to avoid Postgres alias character limit of 64) * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind patameters in log. */ @@ -387,8 +387,8 @@ class Sequelize { * The table columns are defined by the object that is given as the second argument. Each key of the object represents a column * * @param {string} modelName The name of the model. The model will be stored in `sequelize.models` under this name - * @param {Object} attributes An object, where each attribute is a column of the table. See {@link Model.init} - * @param {Object} [options] These options are merged with the default define options provided to the Sequelize constructor and passed to Model.init() + * @param {object} attributes An object, where each attribute is a column of the table. See {@link Model.init} + * @param {object} [options] These options are merged with the default define options provided to the Sequelize constructor and passed to Model.init() * * @see * {@link Model.init} for a more comprehensive specification of the `options` and `attributes` objects. @@ -505,25 +505,25 @@ class Sequelize { * ``` * * @param {string} sql - * @param {Object} [options={}] Query options. + * @param {object} [options={}] Query options. * @param {boolean} [options.raw] If true, sequelize will not try to format the results of the query, or build an instance of a model from the result * @param {Transaction} [options.transaction=null] The transaction that the query should be executed under * @param {QueryTypes} [options.type='RAW'] The type of query you are executing. The query type affects how results are formatted before they are passed back. The type is a string, but `Sequelize.QueryTypes` is provided as convenience shortcuts. * @param {boolean} [options.nest=false] If true, transforms objects with `.` separated property names into nested objects using [dottie.js](https://github.com/mickhansen/dottie.js). For example { 'user.username': 'john' } becomes { user: { username: 'john' }}. When `nest` is true, the query type is assumed to be `'SELECT'`, unless otherwise specified * @param {boolean} [options.plain=false] Sets the query type to `SELECT` and return a single row - * @param {Object|Array} [options.replacements] Either an object of named parameter replacements in the format `:param` or an array of unnamed replacements to replace `?` in your SQL. - * @param {Object|Array} [options.bind] Either an object of named bind parameter in the format `_param` or an array of unnamed bind parameter to replace `$1, $2, ...` in your SQL. + * @param {object|Array} [options.replacements] Either an object of named parameter replacements in the format `:param` or an array of unnamed replacements to replace `?` in your SQL. + * @param {object|Array} [options.bind] Either an object of named bind parameter in the format `_param` or an array of unnamed bind parameter to replace `$1, $2, ...` in your SQL. * @param {boolean} [options.useMaster=false] Force the query to use the write pool, regardless of the query type. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {new Model()} [options.instance] A sequelize instance used to build the return instance - * @param {Model} [options.model] A sequelize model used to build the returned model instances (used to be called callee) - * @param {Object} [options.retry] Set of flags that control when a query is automatically retried. Accepts all options for [`retry-as-promised`](https://github.com/mickhansen/retry-as-promised). + * @param {Model} [options.instance] A sequelize model instance whose Model is to be used to build the query result + * @param {typeof Model} [options.model] A sequelize model used to build the returned model instances + * @param {object} [options.retry] Set of flags that control when a query is automatically retried. Accepts all options for [`retry-as-promised`](https://github.com/mickhansen/retry-as-promised). * @param {Array} [options.retry.match] Only retry a query if the error matches one of these strings. * @param {Integer} [options.retry.max] How many times a failing query is automatically retried. * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean} [options.supportsSearchPath] If false do not prepend the query with the search_path (Postgres only) * @param {boolean} [options.mapToModel=false] Map returned fields to model's fields if `options.model` or `options.instance` is present. Mapping will occur before building the model instance. - * @param {Object} [options.fieldMap] Map returned fields to arbitrary names for `SELECT` query type. + * @param {object} [options.fieldMap] Map returned fields to arbitrary names for `SELECT` query type. * * @returns {Promise} * @@ -655,8 +655,8 @@ class Sequelize { * Execute a query which would set an environment or user variable. The variables are set per connection, so this function needs a transaction. * Only works for MySQL. * - * @param {Object} variables Object with multiple variables. - * @param {Object} [options] query options. + * @param {object} variables Object with multiple variables. + * @param {object} [options] query options. * @param {Transaction} [options.transaction] The transaction that the query should be executed under * * @memberof Sequelize @@ -709,7 +709,7 @@ class Sequelize { * {@link Model.schema} * * @param {string} schema Name of the schema - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -724,7 +724,7 @@ class Sequelize { * **Note:** this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), * not a database table. In mysql and sqlite, this will show all tables. * - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -740,7 +740,7 @@ class Sequelize { * not a database table. In mysql and sqlite, this drop a table matching the schema name * * @param {string} schema Name of the schema - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -755,7 +755,7 @@ class Sequelize { * **Note:** this is a schema in the [postgres sense of the word](http://www.postgresql.org/docs/9.1/static/ddl-schemas.html), * not a database table. In mysql and sqlite, this is the equivalent of drop all tables. * - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -767,14 +767,14 @@ class Sequelize { /** * Sync all defined models to the DB. * - * @param {Object} [options={}] sync options + * @param {object} [options={}] sync options * @param {boolean} [options.force=false] If force is true, each Model will run `DROP TABLE IF EXISTS`, before it tries to create its own table * @param {RegExp} [options.match] Match a regex against the database name before syncing, a safety check for cases where force: true is used in tests but not live code * @param {boolean|Function} [options.logging=console.log] A function that logs sql queries, or false for no logging * @param {string} [options.schema='public'] The schema that the tables should be created in. This can be overridden for each table in sequelize.define * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean} [options.hooks=true] If hooks is true then beforeSync, afterSync, beforeBulkSync, afterBulkSync hooks will be called - * @param {boolean|Object} [options.alter=false] Alters tables to fit models. Provide an object for additional configuration. Not recommended for production use. If not further configured deletes data in columns that were removed or had their type changed in the model. + * @param {boolean|object} [options.alter=false] Alters tables to fit models. Provide an object for additional configuration. Not recommended for production use. If not further configured deletes data in columns that were removed or had their type changed in the model. * @param {boolean} [options.alter.drop=true] Prevents any drop statements while altering a table when set to `false` * * @returns {Promise} @@ -826,7 +826,7 @@ class Sequelize { * Truncate all tables defined through the sequelize models. * This is done by calling `Model.truncate()` on each model. * - * @param {Object} [options] The options passed to Model.destroy in addition to truncate + * @param {object} [options] The options passed to Model.destroy in addition to truncate * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * @returns {Promise} * @@ -857,7 +857,7 @@ class Sequelize { * @see * {@link Model.drop} for options * - * @param {Object} [options] The options passed to each call to Model.drop + * @param {object} [options] The options passed to each call to Model.drop * @param {boolean|Function} [options.logging] A function that logs sql queries, or false for no logging * * @returns {Promise} @@ -877,7 +877,7 @@ class Sequelize { /** * Test the connection by trying to authenticate. It runs `SELECT 1+1 AS result` query. * - * @param {Object} [options={}] query options + * @param {object} [options={}] query options * * @returns {Promise} */ @@ -984,7 +984,7 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {...string|Object} args Each argument will be joined by AND + * @param {...string|object} args Each argument will be joined by AND * @since v2.0.0-dev3 * @memberof Sequelize * @@ -1000,7 +1000,7 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {...string|Object} args Each argument will be joined by OR + * @param {...string|object} args Each argument will be joined by OR * @since v2.0.0-dev3 * @memberof Sequelize * @@ -1016,7 +1016,7 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {string|Object} conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres/sqlite/mysql json syntax. + * @param {string|object} conditionsOrPath A hash containing strings/numbers or other nested hash, a string using dot notation or a string using postgres/sqlite/mysql json syntax. * @param {string|number|boolean} [value] An optional value to compare against. Produces a string of the form " = ''". * @memberof Sequelize * @@ -1037,9 +1037,9 @@ class Sequelize { * @see * {@link Model.findAll} * - * @param {Object} attr The attribute, which can be either an attribute object from `Model.rawAttributes` or a sequelize object, for example an instance of `sequelize.fn`. For simple string attributes, use the POJO syntax - * @param {Symbol} [comparator='Op.eq'] operator - * @param {string|Object} logic The condition. Can be both a simply type, or a further condition (`or`, `and`, `.literal` etc.) + * @param {object} attr The attribute, which can be either an attribute object from `Model.rawAttributes` or a sequelize object, for example an instance of `sequelize.fn`. For simple string attributes, use the POJO syntax + * @param {symbol} [comparator='Op.eq'] operator + * @param {string|object} logic The condition. Can be both a simply type, or a further condition (`or`, `and`, `.literal` etc.) * @since v2.0.0-dev3 */ static where(attr, comparator, logic) { @@ -1080,7 +1080,7 @@ class Sequelize { * * // Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace * - * @param {Object} [options] Transaction options + * @param {object} [options] Transaction options * @param {string} [options.type='DEFERRED'] See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. See `Sequelize.Deferrable`. PostgreSQL Only @@ -1119,8 +1119,8 @@ class Sequelize { * CLS namespace provided is stored as `Sequelize._cls` * and bluebird Promise is patched to use the namespace, using `cls-bluebird` module. * - * @param {Object} ns CLS namespace - * @returns {Object} Sequelize constructor + * @param {object} ns CLS namespace + * @returns {object} Sequelize constructor */ static useCLS(ns) { // check `ns` is valid CLS namespace @@ -1281,6 +1281,7 @@ Sequelize.Utils = Utils; /** * Operators symbols to be used for querying data + * * @see {@link Operators} */ Sequelize.Op = Op; @@ -1292,18 +1293,21 @@ Sequelize.Promise = Promise; /** * Available table hints to be used for querying data in mssql for table hints + * * @see {@link TableHints} */ Sequelize.TableHints = TableHints; /** * Available index hints to be used for querying data in mysql for index hints + * * @see {@link IndexHints} */ Sequelize.IndexHints = IndexHints; /** * A reference to the sequelize transaction class. Use this to access isolationLevels and types when creating a transaction + * * @see {@link Transaction} * @see {@link Sequelize.transaction} */ @@ -1311,18 +1315,21 @@ Sequelize.Transaction = Transaction; /** * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. + * * @see {@link Sequelize} */ Sequelize.prototype.Sequelize = Sequelize; /** * Available query types for use with `sequelize.query` + * * @see {@link QueryTypes} */ Sequelize.prototype.QueryTypes = Sequelize.QueryTypes = QueryTypes; /** * Exposes the validator.js object, so you can extend it with custom validation functions. The validator is exposed both on the instance, and on the constructor. + * * @see https://github.com/chriso/validator.js */ Sequelize.prototype.Validator = Sequelize.Validator = Validator; @@ -1336,6 +1343,7 @@ for (const dataType in DataTypes) { /** * A reference to the deferrable collection. Use this to access the different deferrable options. + * * @see {@link Transaction.Deferrable} * @see {@link Sequelize#transaction} */ @@ -1343,13 +1351,15 @@ Sequelize.Deferrable = Deferrable; /** * A reference to the sequelize association class. + * * @see {@link Association} */ Sequelize.prototype.Association = Sequelize.Association = Association; /** * Provide alternative version of `inflection` module to be used by `Utils.pluralize` etc. - * @param {Object} _inflection - `inflection` module + * + * @param {object} _inflection - `inflection` module */ Sequelize.useInflection = Utils.useInflection; diff --git a/lib/transaction.js b/lib/transaction.js index 36862c4a1411..a7955db9c18d 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -15,7 +15,7 @@ class Transaction { * Creates a new transaction instance * * @param {Sequelize} sequelize A configured sequelize Instance - * @param {Object} options An object with options + * @param {object} options An object with options * @param {string} [options.type] Sets the type of the transaction. Sqlite only * @param {string} [options.isolationLevel] Sets the isolation level of the transaction. * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. PostgreSQL only @@ -286,7 +286,7 @@ class Transaction { * }); * # The query will now return any rows that aren't locked by another transaction * - * @returns {Object} + * @returns {object} * @property UPDATE * @property SHARE * @property KEY_SHARE Postgres 9.3+ only diff --git a/lib/utils.js b/lib/utils.js index 551fc20e1ddc..c7e9fe106132 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -380,8 +380,8 @@ exports.removeTicks = removeTicks; * address.coordinates.longitude: 12.5964313 * } * - * @param {Object} value an Object - * @returns {Object} a flattened object + * @param {object} value an Object + * @returns {object} a flattened object * @private */ function flattenObjectDeep(value) { @@ -407,6 +407,7 @@ exports.flattenObjectDeep = flattenObjectDeep; /** * Utility functions for representing SQL functions, and columns that should be escaped. * Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead. + * * @private */ class SequelizeMethod {} @@ -488,8 +489,8 @@ exports.Where = Where; /** * getOperators * - * @param {Object} obj - * @returns {Array} All operators properties of obj + * @param {object} obj + * @returns {Array} All operators properties of obj * @private */ function getOperators(obj) { @@ -500,8 +501,8 @@ exports.getOperators = getOperators; /** * getComplexKeys * - * @param {Object} obj - * @returns {Array} All keys including operators + * @param {object} obj + * @returns {Array} All keys including operators * @private */ function getComplexKeys(obj) { @@ -512,7 +513,7 @@ exports.getComplexKeys = getComplexKeys; /** * getComplexSize * - * @param {Object|Array} obj + * @param {object|Array} obj * @returns {number} Length of object properties including operators if obj is array returns its length * @private */ @@ -524,7 +525,7 @@ exports.getComplexSize = getComplexSize; /** * Returns true if a where clause is empty, even with Symbols * - * @param {Object} obj + * @param {object} obj * @returns {boolean} * @private */ @@ -549,7 +550,7 @@ exports.generateEnumName = generateEnumName; /** * Returns an new Object which keys are camelized * - * @param {Object} obj + * @param {object} obj * @returns {string} * @private */ @@ -570,9 +571,9 @@ exports.camelizeObjectKeys = camelizeObjectKeys; * * **Note:** This method mutates `object`. * - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. + * @param {object} object The destination object. + * @param {...object} [sources] The source objects. + * @returns {object} Returns `object`. * @private */ function defaults(object, ...sources) { @@ -602,12 +603,12 @@ exports.defaults = defaults; /** * - * @param {Object} index + * @param {object} index * @param {Array} index.fields * @param {string} [index.name] - * @param {string|Object} tableName + * @param {string|object} tableName * - * @returns {Object} + * @returns {object} * @private */ function nameIndex(index, tableName) { diff --git a/package-lock.json b/package-lock.json index 2e6fb392b484..612ddb3ab37f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2257,9 +2257,9 @@ "dev": true }, "comment-parser": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.5.5.tgz", - "integrity": "sha512-oB3TinFT+PV3p8UwDQt71+HkG03+zwPwikDlKU6ZDmql6QX2zFlQ+G0GGSDqyJhdZi4PSlzFBm+YJ+ebOX3Vgw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.2.tgz", + "integrity": "sha512-4Rjb1FnxtOcv9qsfuaNuVsmmVn4ooVoBHzYfyKteiXwIU84PClyGA5jASoFMwPV93+FPh9spwueXauxFJZkGAg==", "dev": true }, "commondir": { @@ -3480,14 +3480,27 @@ } }, "eslint-plugin-jsdoc": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-4.8.4.tgz", - "integrity": "sha512-VDP+BI2hWpKNNdsJDSPofSQ9q7jGLgWbDMI0LzOeEcfsTjSS7jQtHDUuVLQ5E+OV2MPyQPk/3lnVcHfStXk5yA==", + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-20.4.0.tgz", + "integrity": "sha512-c/fnEpwWLFeQn+A7pb1qLOdyhovpqGCWCeUv1wtzFNL5G+xedl9wHUnXtp3b1sGHolVimi9DxKVTuhK/snXoOw==", "dev": true, "requires": { - "comment-parser": "^0.5.4", - "jsdoctypeparser": "3.1.0", - "lodash": "^4.17.11" + "comment-parser": "^0.7.2", + "debug": "^4.1.1", + "jsdoctypeparser": "^6.1.0", + "lodash": "^4.17.15", + "object.entries-ponyfill": "^1.0.1", + "regextras": "^0.7.0", + "semver": "^6.3.0", + "spdx-expression-parse": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "eslint-plugin-mocha": { @@ -5548,9 +5561,9 @@ "dev": true }, "jsdoctypeparser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-3.1.0.tgz", - "integrity": "sha512-JNbkKpDFqbYjg+IU3FNo7qjX7Opy7CwjHywT32zgAcz/d4lX6Umn5jOHVETUdnNNgGrMk0nEx1gvP0F4M0hzlQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-6.1.0.tgz", + "integrity": "sha512-UCQBZ3xCUBv/PLfwKAJhp6jmGOSLFNKzrotXGNgbKhWvz27wPsCsVeP7gIcHPElQw2agBmynAitXqhxR58XAmA==", "dev": true }, "jsdom": { @@ -10897,6 +10910,12 @@ "object-keys": "^1.0.11" } }, + "object.entries-ponyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.entries-ponyfill/-/object.entries-ponyfill-1.0.1.tgz", + "integrity": "sha1-Kavfd8v70mVm3RqiTp2I9lQz0lY=", + "dev": true + }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -11636,6 +11655,12 @@ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "regextras": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.0.tgz", + "integrity": "sha512-ds+fL+Vhl918gbAUb0k2gVKbTZLsg84Re3DI6p85Et0U0tYME3hyW4nMK8Px4dtDaBA2qNjvG5uWyW7eK5gfmw==", + "dev": true + }, "registry-auth-token": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.0.tgz", diff --git a/package.json b/package.json index 97f26ed18316..2d0f08bbec54 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", "eslint": "^6.8.0", - "eslint-plugin-jsdoc": "^4.1.1", + "eslint-plugin-jsdoc": "^20.4.0", "eslint-plugin-mocha": "^6.2.2", "fs-jetpack": "^2.2.3", "husky": "^1.3.1", From f843582cb07e2f996a700cb2e3802202a2b2a146 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 25 Jan 2020 04:17:10 -0300 Subject: [PATCH 040/414] refactor(tests): use async/await (#11875) --- test/integration/query-interface.test.js | 953 ++++++++++------------- 1 file changed, 429 insertions(+), 524 deletions(-) diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index dd0eb1ecd360..2b7195435a45 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -15,262 +15,229 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('dropAllSchema', () => { - it('should drop all schema', function() { - return this.queryInterface.dropAllSchemas( - { skip: [this.sequelize.config.database] }) - .then(() => { - return this.queryInterface.showAllSchemas(); - }) - .then(schemaNames => { - - return this.queryInterface.createSchema('newSchema') - .then(() => { - return this.queryInterface.showAllSchemas(); - }) - .then(newSchemaNames => { - if (!current.dialect.supports.schemas) return; - expect(newSchemaNames).to.have.length(schemaNames.length + 1); - return this.queryInterface.dropSchema('newSchema'); - }); - }); + it('should drop all schema', async function() { + await this.queryInterface.dropAllSchemas({ + skip: [this.sequelize.config.database] + }); + const schemaNames = await this.queryInterface.showAllSchemas(); + await this.queryInterface.createSchema('newSchema'); + const newSchemaNames = await this.queryInterface.showAllSchemas(); + if (!current.dialect.supports.schemas) return; + expect(newSchemaNames).to.have.length(schemaNames.length + 1); + await this.queryInterface.dropSchema('newSchema'); }); }); describe('showAllTables', () => { - it('should not contain views', function() { - const cleanup = () => { + it('should not contain views', async function() { + async function cleanup() { // NOTE: The syntax "DROP VIEW [IF EXISTS]"" is not part of the standard // and might not be available on all RDBMSs. Therefore "DROP VIEW" is // the compatible option, which can throw an error in case the VIEW does - // not exist. In case of error, it is ignored by reflect()+tap(). - return this.sequelize.query('DROP VIEW V_Fail').reflect(); - }; - return this.queryInterface - .createTable('my_test_table', { name: DataTypes.STRING }) - .tap(cleanup) - .then(() => this.sequelize.query('CREATE VIEW V_Fail AS SELECT 1 Id')) - .then(() => this.queryInterface.showAllTables()) - .tap(cleanup) - .then(tableNames => { - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.deep.equal(['my_test_table']); - }); + // not exist. In case of error, it is ignored. + try { + await this.sequelize.query('DROP VIEW V_Fail'); + } catch (error) { + // Ignore error. + } + } + await this.queryInterface.createTable('my_test_table', { name: DataTypes.STRING }); + await cleanup(); + await this.sequelize.query('CREATE VIEW V_Fail AS SELECT 1 Id'); + let tableNames = await this.queryInterface.showAllTables(); + await cleanup(); + if (tableNames[0] && tableNames[0].tableName) { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.deep.equal(['my_test_table']); }); if (dialect !== 'sqlite' && dialect !== 'postgres') { // NOTE: sqlite doesn't allow querying between databases and // postgres requires creating a new connection to create a new table. - it('should not show tables in other databases', function() { - return this.queryInterface - .createTable('my_test_table1', { name: DataTypes.STRING }) - .then(() => this.sequelize.query('CREATE DATABASE my_test_db')) - .then(() => this.sequelize.query(`CREATE TABLE my_test_db${dialect === 'mssql' ? '.dbo' : ''}.my_test_table2 (id INT)`)) - .then(() => this.queryInterface.showAllTables()) - .tap(() => this.sequelize.query('DROP DATABASE my_test_db')) - .then(tableNames => { - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.deep.equal(['my_test_table1']); - }); + it('should not show tables in other databases', async function() { + await this.queryInterface.createTable('my_test_table1', { name: DataTypes.STRING }); + await this.sequelize.query('CREATE DATABASE my_test_db'); + await this.sequelize.query(`CREATE TABLE my_test_db${dialect === 'mssql' ? '.dbo' : ''}.my_test_table2 (id INT)`); + let tableNames = await this.queryInterface.showAllTables(); + await this.sequelize.query('DROP DATABASE my_test_db'); + if (tableNames[0] && tableNames[0].tableName) { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.deep.equal(['my_test_table1']); }); + } - if (dialect === 'mysql' || dialect === 'mariadb') { - it('should show all tables in all databases', function() { - return this.queryInterface - .createTable('my_test_table1', { name: DataTypes.STRING }) - .then(() => this.sequelize.query('CREATE DATABASE my_test_db')) - .then(() => this.sequelize.query('CREATE TABLE my_test_db.my_test_table2 (id INT)')) - .then(() => this.sequelize.query(this.queryInterface.QueryGenerator.showTablesQuery(), { - raw: true, - type: this.sequelize.QueryTypes.SHOWTABLES - })) - .tap(() => this.sequelize.query('DROP DATABASE my_test_db')) - .then(tableNames => { - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - tableNames.sort(); - expect(tableNames).to.deep.equal(['my_test_table1', 'my_test_table2']); - }); - }); - } + if (dialect === 'mysql' || dialect === 'mariadb') { + it('should show all tables in all databases', async function() { + await this.queryInterface.createTable('my_test_table1', { name: DataTypes.STRING }); + await this.sequelize.query('CREATE DATABASE my_test_db'); + await this.sequelize.query('CREATE TABLE my_test_db.my_test_table2 (id INT)'); + let tableNames = await this.sequelize.query( + this.queryInterface.QueryGenerator.showTablesQuery(), + { + raw: true, + type: this.sequelize.QueryTypes.SHOWTABLES + } + ); + await this.sequelize.query('DROP DATABASE my_test_db'); + if (tableNames[0] && tableNames[0].tableName) { + tableNames = tableNames.map(v => v.tableName); + } + tableNames.sort(); + expect(tableNames).to.deep.equal(['my_test_table1', 'my_test_table2']); + }); } }); describe('renameTable', () => { - it('should rename table', function() { - return this.queryInterface - .createTable('my_test_table', { - name: DataTypes.STRING - }) - .then(() => this.queryInterface.renameTable('my_test_table', 'my_test_table_new')) - .then(() => this.queryInterface.showAllTables()) - .then(tableNames => { - if (dialect === 'mssql' || dialect === 'mariadb') { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.contain('my_test_table_new'); - expect(tableNames).to.not.contain('my_test_table'); - }); + it('should rename table', async function() { + await this.queryInterface.createTable('my_test_table', { + name: DataTypes.STRING + }); + await this.queryInterface.renameTable('my_test_table', 'my_test_table_new'); + let tableNames = await this.queryInterface.showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.contain('my_test_table_new'); + expect(tableNames).to.not.contain('my_test_table'); }); }); describe('dropAllTables', () => { - it('should drop all tables', function() { - const filterMSSQLDefault = tableNames => tableNames.filter(t => t.tableName !== 'spt_values'); - return this.queryInterface.dropAllTables() - .then(() => { - return this.queryInterface.showAllTables(); - }) - .then(tableNames => { - // MSSQL include spt_values table which is system defined, hence cant be dropped - tableNames = filterMSSQLDefault(tableNames); - expect(tableNames).to.be.empty; - return this.queryInterface.createTable('table', { name: DataTypes.STRING }); - }) - .then(() => { - return this.queryInterface.showAllTables(); - }) - .then(tableNames => { - tableNames = filterMSSQLDefault(tableNames); - expect(tableNames).to.have.length(1); - return this.queryInterface.dropAllTables(); - }) - .then(() => { - return this.queryInterface.showAllTables(); - }) - .then(tableNames => { - // MSSQL include spt_values table which is system defined, hence cant be dropped - tableNames = filterMSSQLDefault(tableNames); - expect(tableNames).to.be.empty; - }); + it('should drop all tables', async function() { + + // MSSQL includes `spt_values` table which is system defined, hence can't be dropped + const showAllTablesIgnoringSpecialMSSQLTable = async () => { + const tableNames = await this.queryInterface.showAllTables(); + return tableNames.filter(t => t.tableName !== 'spt_values'); + }; + + await this.queryInterface.dropAllTables(); + + expect( + await showAllTablesIgnoringSpecialMSSQLTable() + ).to.be.empty; + + await this.queryInterface.createTable('table', { name: DataTypes.STRING }); + + expect( + await showAllTablesIgnoringSpecialMSSQLTable() + ).to.have.length(1); + + await this.queryInterface.dropAllTables(); + + expect( + await showAllTablesIgnoringSpecialMSSQLTable() + ).to.be.empty; }); - it('should be able to skip given tables', function() { - return this.queryInterface.createTable('skipme', { + it('should be able to skip given tables', async function() { + await this.queryInterface.createTable('skipme', { name: DataTypes.STRING - }) - .then(() => this.queryInterface.dropAllTables({ skip: ['skipme'] })) - .then(() => this.queryInterface.showAllTables()) - .then(tableNames => { - if (dialect === 'mssql' || dialect === 'mariadb') { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.contain('skipme'); - }); + }); + await this.queryInterface.dropAllTables({ skip: ['skipme'] }); + let tableNames = await this.queryInterface.showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.contain('skipme'); }); }); describe('indexes', () => { - beforeEach(function() { - return this.queryInterface.dropTable('Group').then(() => { - return this.queryInterface.createTable('Group', { - username: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN, - from: DataTypes.STRING - }); + beforeEach(async function() { + await this.queryInterface.dropTable('Group'); + await this.queryInterface.createTable('Group', { + username: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN, + from: DataTypes.STRING }); }); - it('adds, reads and removes an index to the table', function() { - return this.queryInterface.addIndex('Group', ['username', 'isAdmin']).then(() => { - return this.queryInterface.showIndex('Group').then(indexes => { - let indexColumns = _.uniq(indexes.map(index => { return index.name; })); - expect(indexColumns).to.include('group_username_is_admin'); - return this.queryInterface.removeIndex('Group', ['username', 'isAdmin']).then(() => { - return this.queryInterface.showIndex('Group').then(indexes => { - indexColumns = _.uniq(indexes.map(index => { return index.name; })); - expect(indexColumns).to.be.empty; - }); - }); - }); - }); + it('adds, reads and removes an index to the table', async function() { + await this.queryInterface.addIndex('Group', ['username', 'isAdmin']); + let indexes = await this.queryInterface.showIndex('Group'); + let indexColumns = _.uniq(indexes.map(index => index.name)); + expect(indexColumns).to.include('group_username_is_admin'); + await this.queryInterface.removeIndex('Group', ['username', 'isAdmin']); + indexes = await this.queryInterface.showIndex('Group'); + indexColumns = _.uniq(indexes.map(index => index.name)); + expect(indexColumns).to.be.empty; }); - it('works with schemas', function() { - return this.sequelize.createSchema('schema').then(() => { - return this.queryInterface.createTable('table', { - name: { - type: DataTypes.STRING - }, - isAdmin: { - type: DataTypes.STRING - } - }, { - schema: 'schema' - }); - }).then(() => { - return this.queryInterface.addIndex({ - schema: 'schema', - tableName: 'table' - }, ['name', 'isAdmin'], null, 'schema_table').then(() => { - return this.queryInterface.showIndex({ - schema: 'schema', - tableName: 'table' - }).then(indexes => { - expect(indexes.length).to.eq(1); - const index = indexes[0]; - expect(index.name).to.eq('table_name_is_admin'); - }); - }); + it('works with schemas', async function() { + await this.sequelize.createSchema('schema'); + await this.queryInterface.createTable('table', { + name: { + type: DataTypes.STRING + }, + isAdmin: { + type: DataTypes.STRING + } + }, { + schema: 'schema' + }); + await this.queryInterface.addIndex( + { schema: 'schema', tableName: 'table' }, + ['name', 'isAdmin'], + null, + 'schema_table' + ); + const indexes = await this.queryInterface.showIndex({ + schema: 'schema', + tableName: 'table' }); + expect(indexes.length).to.eq(1); + expect(indexes[0].name).to.eq('table_name_is_admin'); }); - it('does not fail on reserved keywords', function() { - return this.queryInterface.addIndex('Group', ['from']); + it('does not fail on reserved keywords', async function() { + await this.queryInterface.addIndex('Group', ['from']); }); }); describe('renameColumn', () => { - it('rename a simple column', function() { + it('rename a simple column', async function() { const Users = this.sequelize.define('_Users', { username: DataTypes.STRING }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); - }).then(() => { - return this.queryInterface.describeTable('_Users'); - }).then(table => { - expect(table).to.have.property('pseudo'); - expect(table).to.not.have.property('username'); - }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); + const table = await this.queryInterface.describeTable('_Users'); + expect(table).to.have.property('pseudo'); + expect(table).to.not.have.property('username'); }); - it('works with schemas', function() { - return this.sequelize.createSchema('archive').then(() => { - const Users = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - tableName: 'Users', - schema: 'archive' - }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn({ - schema: 'archive', - tableName: 'Users' - }, 'username', 'pseudo'); - }); - }).then(() => { - return this.queryInterface.describeTable({ - schema: 'archive', - tableName: 'Users' - }); - }).then(table => { - expect(table).to.have.property('pseudo'); - expect(table).to.not.have.property('username'); + it('works with schemas', async function() { + await this.sequelize.createSchema('archive'); + const Users = this.sequelize.define('User', { + username: DataTypes.STRING + }, { + tableName: 'Users', + schema: 'archive' + }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn({ + schema: 'archive', + tableName: 'Users' + }, 'username', 'pseudo'); + const table = await this.queryInterface.describeTable({ + schema: 'archive', + tableName: 'Users' }); + expect(table).to.have.property('pseudo'); + expect(table).to.not.have.property('username'); }); - it('rename a column non-null without default value', function() { + it('rename a column non-null without default value', async function() { const Users = this.sequelize.define('_Users', { username: { type: DataTypes.STRING, @@ -278,17 +245,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); - }).then(() => { - return this.queryInterface.describeTable('_Users'); - }).then(table => { - expect(table).to.have.property('pseudo'); - expect(table).to.not.have.property('username'); - }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); + const table = await this.queryInterface.describeTable('_Users'); + expect(table).to.have.property('pseudo'); + expect(table).to.not.have.property('username'); }); - it('rename a boolean column non-null without default value', function() { + it('rename a boolean column non-null without default value', async function() { const Users = this.sequelize.define('_Users', { active: { type: DataTypes.BOOLEAN, @@ -297,17 +261,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'active', 'enabled'); - }).then(() => { - return this.queryInterface.describeTable('_Users'); - }).then(table => { - expect(table).to.have.property('enabled'); - expect(table).to.not.have.property('active'); - }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn('_Users', 'active', 'enabled'); + const table = await this.queryInterface.describeTable('_Users'); + expect(table).to.have.property('enabled'); + expect(table).to.not.have.property('active'); }); - it('renames a column primary key autoIncrement column', function() { + it('renames a column primary key autoIncrement column', async function() { const Fruits = this.sequelize.define('Fruit', { fruitId: { type: DataTypes.INTEGER, @@ -317,113 +278,108 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Fruits.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('Fruit', 'fruitId', 'fruit_id'); - }).then(() => { - return this.queryInterface.describeTable('Fruit'); - }).then(table => { - expect(table).to.have.property('fruit_id'); - expect(table).to.not.have.property('fruitId'); - }); + await Fruits.sync({ force: true }); + await this.queryInterface.renameColumn('Fruit', 'fruitId', 'fruit_id'); + const table = await this.queryInterface.describeTable('Fruit'); + expect(table).to.have.property('fruit_id'); + expect(table).to.not.have.property('fruitId'); }); - it('shows a reasonable error message when column is missing', function() { + it('shows a reasonable error message when column is missing', async function() { const Users = this.sequelize.define('_Users', { username: DataTypes.STRING }, { freezeTableName: true }); - const outcome = Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'email', 'pseudo'); - }); - - return expect(outcome).to.be.rejectedWith('Table _Users doesn\'t have the column email'); + await Users.sync({ force: true }); + await expect( + this.queryInterface.renameColumn('_Users', 'email', 'pseudo') + ).to.be.rejectedWith('Table _Users doesn\'t have the column email'); }); }); describe('addColumn', () => { - beforeEach(function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - }); - }); - - it('should be able to add a foreign key reference', function() { - return this.queryInterface.createTable('level', { + beforeEach(async function() { + await this.sequelize.createSchema('archive'); + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.addColumn('users', 'level_id', { - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'set null' - }); - }).then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.have.property('level_id'); }); }); - it('addColumn expected error', function() { - return this.queryInterface.createTable('level2', { + it('should be able to add a foreign key reference', async function() { + await this.queryInterface.createTable('level', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - expect(this.queryInterface.addColumn.bind(this, 'users', 'level_id')).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - expect(this.queryInterface.addColumn.bind(this, null, 'level_id')).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - expect(this.queryInterface.addColumn.bind(this, 'users', null, {})).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); }); + await this.queryInterface.addColumn('users', 'level_id', { + type: DataTypes.INTEGER, + references: { + model: 'level', + key: 'id' + }, + onUpdate: 'cascade', + onDelete: 'set null' + }); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.have.property('level_id'); }); - it('should work with schemas', function() { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { + it('addColumn expected error', async function() { + await this.queryInterface.createTable('level2', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.addColumn({ - tableName: 'users', - schema: 'archive' - }, 'level_id', { - type: DataTypes.INTEGER - }).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.have.property('level_id'); - }); }); + + const testArgs = (...args) => { + expect(() => { + this.queryInterface.addColumn(...args); + throw new Error('Did not throw immediately...'); + }).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); + }; + + testArgs('users', 'level_id'); + testArgs(null, 'level_id'); + testArgs('users', null, {}); + }); + + it('should work with schemas', async function() { + await this.queryInterface.createTable( + { tableName: 'users', schema: 'archive' }, + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + } + } + ); + await this.queryInterface.addColumn( + { tableName: 'users', schema: 'archive' }, + 'level_id', + { type: DataTypes.INTEGER } + ); + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' + }); + expect(table).to.have.property('level_id'); }); - it('should work with enums (1)', function() { - return this.queryInterface.addColumn('users', 'someEnum', DataTypes.ENUM('value1', 'value2', 'value3')); + it('should work with enums (1)', async function() { + await this.queryInterface.addColumn('users', 'someEnum', DataTypes.ENUM('value1', 'value2', 'value3')); }); - it('should work with enums (2)', function() { - return this.queryInterface.addColumn('users', 'someOtherEnum', { + it('should work with enums (2)', async function() { + await this.queryInterface.addColumn('users', 'someOtherEnum', { type: DataTypes.ENUM, values: ['value1', 'value2', 'value3'] }); @@ -431,99 +387,94 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { }); describe('describeForeignKeys', () => { - beforeEach(function() { - return this.queryInterface.createTable('users', { + beforeEach(async function() { + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.createTable('hosts', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - admin: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - } + }); + await this.queryInterface.createTable('hosts', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + admin: { + type: DataTypes.INTEGER, + references: { + model: 'users', + key: 'id' + } + }, + operator: { + type: DataTypes.INTEGER, + references: { + model: 'users', + key: 'id' }, - operator: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - }, - onUpdate: 'cascade' + onUpdate: 'cascade' + }, + owner: { + type: DataTypes.INTEGER, + references: { + model: 'users', + key: 'id' }, - owner: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'set null' - } - }); + onUpdate: 'cascade', + onDelete: 'set null' + } }); }); - it('should get a list of foreign keys for the table', function() { - const sql = this.queryInterface.QueryGenerator.getForeignKeysQuery('hosts', this.sequelize.config.database); - return this.sequelize.query(sql, { type: this.sequelize.QueryTypes.FOREIGNKEYS }).then(fks => { - expect(fks).to.have.length(3); - const keys = Object.keys(fks[0]), - keys2 = Object.keys(fks[1]), - keys3 = Object.keys(fks[2]); - - if (dialect === 'postgres' || dialect === 'postgres-native') { - expect(keys).to.have.length(6); - expect(keys2).to.have.length(7); - expect(keys3).to.have.length(7); - } else if (dialect === 'sqlite') { - expect(keys).to.have.length(8); - } else if (dialect === 'mysql' || dialect === 'mssql') { - expect(keys).to.have.length(12); - } else { - console.log(`This test doesn't support ${dialect}`); - } - return fks; - }).then(fks => { - if (dialect === 'mysql') { - return this.sequelize.query( - this.queryInterface.QueryGenerator.getForeignKeyQuery('hosts', 'admin'), - {} - ) - .then(([fk]) => { - expect(fks[0]).to.deep.eql(fk[0]); - }); - } - return; - }); + it('should get a list of foreign keys for the table', async function() { + + const foreignKeys = await this.sequelize.query( + this.queryInterface.QueryGenerator.getForeignKeysQuery( + 'hosts', + this.sequelize.config.database + ), + { type: this.sequelize.QueryTypes.FOREIGNKEYS } + ); + + expect(foreignKeys).to.have.length(3); + + if (dialect === 'postgres') { + expect(Object.keys(foreignKeys[0])).to.have.length(6); + expect(Object.keys(foreignKeys[1])).to.have.length(7); + expect(Object.keys(foreignKeys[2])).to.have.length(7); + } else if (dialect === 'sqlite') { + expect(Object.keys(foreignKeys[0])).to.have.length(8); + } else if (dialect === 'mysql' || dialect === 'mariadb' || dialect === 'mssql') { + expect(Object.keys(foreignKeys[0])).to.have.length(12); + } else { + throw new Error(`This test doesn't support ${dialect}`); + } + + if (dialect === 'mysql') { + const [foreignKeysViaDirectMySQLQuery] = await this.sequelize.query( + this.queryInterface.QueryGenerator.getForeignKeyQuery('hosts', 'admin') + ); + expect(foreignKeysViaDirectMySQLQuery[0]).to.deep.equal(foreignKeys[0]); + } }); - it('should get a list of foreign key references details for the table', function() { - return this.queryInterface.getForeignKeyReferencesForTable('hosts', this.sequelize.options) - .then(references => { - expect(references).to.have.length(3); - const keys = references.map(reference => { - expect(reference.tableName).to.eql('hosts'); - expect(reference.referencedColumnName).to.eql('id'); - expect(reference.referencedTableName).to.eql('users'); - return reference.columnName; - }); - expect(keys).to.have.same.members(['owner', 'operator', 'admin']); - }); + it('should get a list of foreign key references details for the table', async function() { + const references = await this.queryInterface.getForeignKeyReferencesForTable('hosts', this.sequelize.options); + expect(references).to.have.length(3); + for (const ref of references) { + expect(ref.tableName).to.equal('hosts'); + expect(ref.referencedColumnName).to.equal('id'); + expect(ref.referencedTableName).to.equal('users'); + } + const columnNames = references.map(reference => reference.columnName); + expect(columnNames).to.have.same.members(['owner', 'operator', 'admin']); }); }); describe('constraints', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('users', { username: DataTypes.STRING, email: DataTypes.STRING, @@ -533,203 +484,157 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.Post = this.sequelize.define('posts', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('unique', () => { - it('should add, read & remove unique constraint', function() { - return this.queryInterface.addConstraint('users', ['email'], { - type: 'unique' - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('users_email_uk'); - return this.queryInterface.removeConstraint('users', 'users_email_uk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_email_uk'); - }); + it('should add, read & remove unique constraint', async function() { + await this.queryInterface.addConstraint('users', ['email'], { type: 'unique' }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('users_email_uk'); + await this.queryInterface.removeConstraint('users', 'users_email_uk'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_email_uk'); }); - it('should add a constraint after another', function() { - return this.queryInterface.addConstraint('users', ['username'], { - type: 'unique' - }).then(() => this.queryInterface.addConstraint('users', ['email'], { - type: 'unique' - })) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('users_email_uk'); - expect(constraints).to.include('users_username_uk'); - return this.queryInterface.removeConstraint('users', 'users_email_uk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_email_uk'); - expect(constraints).to.include('users_username_uk'); - return this.queryInterface.removeConstraint('users', 'users_username_uk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_email_uk'); - expect(constraints).to.not.include('users_username_uk'); - }); + it('should add a constraint after another', async function() { + await this.queryInterface.addConstraint('users', ['username'], { type: 'unique' }); + await this.queryInterface.addConstraint('users', ['email'], { type: 'unique' }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('users_email_uk'); + expect(constraints).to.include('users_username_uk'); + await this.queryInterface.removeConstraint('users', 'users_email_uk'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_email_uk'); + expect(constraints).to.include('users_username_uk'); + await this.queryInterface.removeConstraint('users', 'users_username_uk'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_email_uk'); + expect(constraints).to.not.include('users_username_uk'); }); }); if (current.dialect.supports.constraints.check) { describe('check', () => { - it('should add, read & remove check constraint', function() { - return this.queryInterface.addConstraint('users', ['roles'], { + it('should add, read & remove check constraint', async function() { + await this.queryInterface.addConstraint('users', ['roles'], { type: 'check', where: { roles: ['user', 'admin', 'guest', 'moderator'] }, name: 'check_user_roles' - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('check_user_roles'); - return this.queryInterface.removeConstraint('users', 'check_user_roles'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('check_user_roles'); - }); + }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('check_user_roles'); + await this.queryInterface.removeConstraint('users', 'check_user_roles'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('check_user_roles'); }); it('addconstraint missing type', function() { - expect(this.queryInterface.addConstraint.bind(this, 'users', ['roles'], { - where: { roles: ['user', 'admin', 'guest', 'moderator'] }, - name: 'check_user_roles' - })).to.throw(Error, 'Constraint type must be specified through options.type'); + expect(() => { + this.queryInterface.addConstraint('users', ['roles'], { + where: { roles: ['user', 'admin', 'guest', 'moderator'] }, + name: 'check_user_roles' + }); + throw new Error('Did not throw immediately...'); + }).to.throw(Error, 'Constraint type must be specified through options.type'); }); }); } if (current.dialect.supports.constraints.default) { describe('default', () => { - it('should add, read & remove default constraint', function() { - return this.queryInterface.addConstraint('users', ['roles'], { + it('should add, read & remove default constraint', async function() { + await this.queryInterface.addConstraint('users', ['roles'], { type: 'default', defaultValue: 'guest' - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('users_roles_df'); - return this.queryInterface.removeConstraint('users', 'users_roles_df'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_roles_df'); - }); + }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('users_roles_df'); + await this.queryInterface.removeConstraint('users', 'users_roles_df'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_roles_df'); }); }); } - describe('primary key', () => { - it('should add, read & remove primary key constraint', function() { - return this.queryInterface.removeColumn('users', 'id') - .then(() => { - return this.queryInterface.changeColumn('users', 'username', { - type: DataTypes.STRING, - allowNull: false - }); - }) - .then(() => { - return this.queryInterface.addConstraint('users', ['username'], { - type: 'PRIMARY KEY' - }); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - //The name of primaryKey constraint is always PRIMARY in case of mysql - if (dialect === 'mysql' || dialect === 'mariadb') { - expect(constraints).to.include('PRIMARY'); - return this.queryInterface.removeConstraint('users', 'PRIMARY'); - } - expect(constraints).to.include('users_username_pk'); - return this.queryInterface.removeConstraint('users', 'users_username_pk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - if (dialect === 'mysql' || dialect === 'mariadb') { - expect(constraints).to.not.include('PRIMARY'); - } else { - expect(constraints).to.not.include('users_username_pk'); - } - }); + it('should add, read & remove primary key constraint', async function() { + await this.queryInterface.removeColumn('users', 'id'); + await this.queryInterface.changeColumn('users', 'username', { + type: DataTypes.STRING, + allowNull: false + }); + await this.queryInterface.addConstraint('users', ['username'], { + type: 'PRIMARY KEY' + }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + + // The name of primaryKey constraint is always `PRIMARY` in case of MySQL and MariaDB + const expectedConstraintName = dialect === 'mysql' || dialect === 'mariadb' ? 'PRIMARY' : 'users_username_pk'; + + expect(constraints).to.include(expectedConstraintName); + await this.queryInterface.removeConstraint('users', expectedConstraintName); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include(expectedConstraintName); }); }); describe('foreign key', () => { - it('should add, read & remove foreign key constraint', function() { - return this.queryInterface.removeColumn('users', 'id') - .then(() => { - return this.queryInterface.changeColumn('users', 'username', { - type: DataTypes.STRING, - allowNull: false - }); - }) - .then(() => { - return this.queryInterface.addConstraint('users', { - type: 'PRIMARY KEY', - fields: ['username'] - }); - }) - .then(() => { - return this.queryInterface.addConstraint('posts', ['username'], { - references: { - table: 'users', - field: 'username' - }, - onDelete: 'cascade', - onUpdate: 'cascade', - type: 'foreign key' - }); - }) - .then(() => this.queryInterface.showConstraint('posts')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('posts_username_users_fk'); - return this.queryInterface.removeConstraint('posts', 'posts_username_users_fk'); - }) - .then(() => this.queryInterface.showConstraint('posts')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('posts_username_users_fk'); - }); + it('should add, read & remove foreign key constraint', async function() { + await this.queryInterface.removeColumn('users', 'id'); + await this.queryInterface.changeColumn('users', 'username', { + type: DataTypes.STRING, + allowNull: false + }); + await this.queryInterface.addConstraint('users', { + type: 'PRIMARY KEY', + fields: ['username'] + }); + await this.queryInterface.addConstraint('posts', ['username'], { + references: { + table: 'users', + field: 'username' + }, + onDelete: 'cascade', + onUpdate: 'cascade', + type: 'foreign key' + }); + let constraints = await this.queryInterface.showConstraint('posts'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('posts_username_users_fk'); + await this.queryInterface.removeConstraint('posts', 'posts_username_users_fk'); + constraints = await this.queryInterface.showConstraint('posts'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('posts_username_users_fk'); }); }); describe('unknown constraint', () => { - it('should throw non existent constraints as UnknownConstraintError', function() { - const promise = this.queryInterface - .removeConstraint('users', 'unknown__constraint__name', { + it('should throw non existent constraints as UnknownConstraintError', async function() { + try { + await this.queryInterface.removeConstraint('users', 'unknown__constraint__name', { type: 'unique' - }) - .catch(e => { - expect(e.table).to.equal('users'); - expect(e.constraint).to.equal('unknown__constraint__name'); - - throw e; }); - - return expect(promise).to.eventually.be.rejectedWith(Sequelize.UnknownConstraintError); + throw new Error('Error not thrown...'); + } catch (error) { + expect(error).to.be.instanceOf(Sequelize.UnknownConstraintError); + expect(error.table).to.equal('users'); + expect(error.constraint).to.equal('unknown__constraint__name'); + } }); }); }); From 9512ab9126136185d520fa2c38fb3382c61f5a83 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 25 Jan 2020 04:17:47 -0300 Subject: [PATCH 041/414] refactor(tests): use async/await (#11876) --- test/integration/operators.test.js | 181 +++++++++-------------- test/integration/pool.test.js | 222 +++++++++++------------------ 2 files changed, 148 insertions(+), 255 deletions(-) diff --git a/test/integration/operators.test.js b/test/integration/operators.test.js index 9a8717cd7d2d..46baf746cdea 100644 --- a/test/integration/operators.test.js +++ b/test/integration/operators.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), Sequelize = require('../../index'), Op = Sequelize.Op, - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../lib/data-types'), @@ -11,7 +10,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Operators'), () => { describe('REGEXP', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { id: { type: DataTypes.INTEGER, @@ -29,145 +28,93 @@ describe(Support.getTestDialectTeaser('Operators'), () => { timestamps: false }); - return Promise.all([ - this.sequelize.getQueryInterface().createTable('users', { - userId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - }, - full_name: { - type: DataTypes.STRING - } - }) - ]); + await this.sequelize.getQueryInterface().createTable('users', { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + full_name: { + type: DataTypes.STRING + } + }); }); if (dialect === 'mysql' || dialect === 'postgres') { describe('case sensitive', () => { - it('should work with a regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.regexp]: '^Foo' - } - } - }); - }).then(user => { - expect(user).to.be.ok; + it('should work with a regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.regexp]: '^Foo' } + } }); + expect(user).to.be.ok; }); - it('should work with a not regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.notRegexp]: '^Foo' - } - } - }); - }).then(user => { - expect(user).to.not.be.ok; + it('should work with a not regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.notRegexp]: '^Foo' } + } }); + expect(user).to.not.be.ok; }); - it('should properly escape regular expressions', function() { - return this.User.bulkCreate([{ - name: 'John' - }, { - name: 'Bob' - }]).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.notRegexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.regexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users).length(2); + it('should properly escape regular expressions', async function() { + await this.User.bulkCreate([{ name: 'John' }, { name: 'Bob' }]); + await this.User.findAll({ + where: { + name: { [Op.notRegexp]: "Bob'; drop table users --" } + } }); + await this.User.findAll({ + where: { + name: { [Op.regexp]: "Bob'; drop table users --" } + } + }); + expect(await this.User.findAll()).to.have.length(2); }); }); } if (dialect === 'postgres') { describe('case insensitive', () => { - it('should work with a case-insensitive regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.iRegexp]: '^foo' - } - } - }); - }).then(user => { - expect(user).to.be.ok; + it('should work with a case-insensitive regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.iRegexp]: '^foo' } + } }); + expect(user).to.be.ok; }); - it('should work with a case-insensitive not regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.notIRegexp]: '^foo' - } - } - }); - }).then(user => { - expect(user).to.not.be.ok; + it('should work with a case-insensitive not regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.notIRegexp]: '^foo' } + } }); + expect(user).to.not.be.ok; }); - it('should properly escape regular expressions', function() { - return this.User.bulkCreate([{ - name: 'John' - }, { - name: 'Bob' - }]).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.iRegexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.notIRegexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users).length(2); + it('should properly escape regular expressions', async function() { + await this.User.bulkCreate([{ name: 'John' }, { name: 'Bob' }]); + await this.User.findAll({ + where: { + name: { [Op.iRegexp]: "Bob'; drop table users --" } + } + }); + await this.User.findAll({ + where: { + name: { [Op.notIRegexp]: "Bob'; drop table users --" } + } }); + expect(await this.User.findAll()).to.have.length(2); }); }); } diff --git a/test/integration/pool.test.js b/test/integration/pool.test.js index 9131d4d5b393..b0e4542fef9b 100644 --- a/test/integration/pool.test.js +++ b/test/integration/pool.test.js @@ -69,175 +69,118 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { }); describe('network / connection errors', () => { - it('should obtain new connection when old connection is abruptly closed', () => { - const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 5000 + it('should obtain new connection when old connection is abruptly closed', async () => { + function simulateUnexpectedError(connection) { + // should never be returned again + if (dialect === 'mssql') { + connection = unwrapAndAttachMSSQLUniqueId(connection); } - }); + connection.emit('error', { code: 'ECONNRESET' }); + } + const sequelize = Support.createSequelizeInstance({ + pool: { max: 1, idle: 5000 } + }); const cm = sequelize.connectionManager; - let conn; - - return sequelize - .sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - conn = connection; - - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } - - // simulate an unexpected error - // should never be returned again - connection.emit('error', { - code: 'ECONNRESET' - }); - }) - .then(() => { - // Get next available connection - return cm.getConnection(); - }) - .then(connection => { - assertNewConnection(connection, conn); + await sequelize.sync(); - expect(sequelize.connectionManager.pool.size).to.equal(1); - expect(cm.validate(conn)).to.be.not.ok; + const firstConnection = await cm.getConnection(); + simulateUnexpectedError(firstConnection); + const secondConnection = await cm.getConnection(); - return cm.releaseConnection(connection); - }); + assertNewConnection(secondConnection, firstConnection); + expect(cm.pool.size).to.equal(1); + expect(cm.validate(firstConnection)).to.be.not.ok; + + await cm.releaseConnection(secondConnection); }); - it('should obtain new connection when released connection dies inside pool', () => { - const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 5000 + it('should obtain new connection when released connection dies inside pool', async () => { + function simulateUnexpectedError(connection) { + // should never be returned again + if (dialect === 'mssql') { + unwrapAndAttachMSSQLUniqueId(connection).close(); + } else if (dialect === 'postgres') { + connection.end(); + } else { + connection.close(); } - }); + } + const sequelize = Support.createSequelizeInstance({ + pool: { max: 1, idle: 5000 } + }); const cm = sequelize.connectionManager; - let oldConnection; + await sequelize.sync(); - return sequelize - .sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - oldConnection = connection; - - return cm.releaseConnection(connection); - }) - .then(() => { - let connection = oldConnection; - - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } - - // simulate an unexpected error - // should never be returned again - if (dialect.match(/postgres/)) { - connection.end(); - } else { - connection.close(); - } - }) - .then(() => { - // Get next available connection - return cm.getConnection(); - }) - .then(connection => { - assertNewConnection(connection, oldConnection); + const oldConnection = await cm.getConnection(); + await cm.releaseConnection(oldConnection); + simulateUnexpectedError(oldConnection); + const newConnection = await cm.getConnection(); - expect(sequelize.connectionManager.pool.size).to.equal(1); - expect(cm.validate(oldConnection)).to.be.not.ok; + assertNewConnection(newConnection, oldConnection); + expect(cm.pool.size).to.equal(1); + expect(cm.validate(oldConnection)).to.be.not.ok; - return cm.releaseConnection(connection); - }); + await cm.releaseConnection(newConnection); }); }); describe('idle', () => { - it('should maintain connection within idle range', () => { + it('should maintain connection within idle range', async () => { const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 10 - } + pool: { max: 1, idle: 100 } }); - const cm = sequelize.connectionManager; - let conn; + await sequelize.sync(); - return sequelize.sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - conn = connection; + const firstConnection = await cm.getConnection(); - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } + // TODO - Do we really need this call? + unwrapAndAttachMSSQLUniqueId(firstConnection); - // returning connection back to pool - return cm.releaseConnection(conn); - }) - .then(() => { - // Get next available connection - return Sequelize.Promise.delay(9).then(() => cm.getConnection()); - }) - .then(connection => { - assertSameConnection(connection, conn); - expect(cm.validate(conn)).to.be.ok; + // returning connection back to pool + await cm.releaseConnection(firstConnection); + + // Wait a little and then get next available connection + await Sequelize.Promise.delay(90); + const secondConnection = await cm.getConnection(); - return cm.releaseConnection(connection); - }); + assertSameConnection(secondConnection, firstConnection); + expect(cm.validate(firstConnection)).to.be.ok; + + await cm.releaseConnection(secondConnection); }); - it('should get new connection beyond idle range', () => { + it('should get new connection beyond idle range', async () => { const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 100, - evict: 10 - } + pool: { max: 1, idle: 100, evict: 10 } }); - const cm = sequelize.connectionManager; - let conn; + await sequelize.sync(); - return sequelize.sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - conn = connection; + const firstConnection = await cm.getConnection(); - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } + // TODO - Do we really need this call? + unwrapAndAttachMSSQLUniqueId(firstConnection); - // returning connection back to pool - return cm.releaseConnection(conn); - }) - .then(() => { - // Get next available connection - return Sequelize.Promise.delay(110).then(() => cm.getConnection()); - }) - .then(connection => { - assertNewConnection(connection, conn); - expect(cm.validate(conn)).not.to.be.ok; + // returning connection back to pool + await cm.releaseConnection(firstConnection); - return cm.releaseConnection(connection); - }); + // Wait a little and then get next available connection + await Sequelize.Promise.delay(110); + + const secondConnection = await cm.getConnection(); + + assertNewConnection(secondConnection, firstConnection); + expect(cm.validate(firstConnection)).not.to.be.ok; + + await cm.releaseConnection(secondConnection); }); }); describe('acquire', () => { - it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection', function() { + it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection', async function() { this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { dialect, databaseVersion: '1.2.3', @@ -249,11 +192,12 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { this.sinon.stub(this.testInstance.connectionManager, '_connect') .returns(new Sequelize.Promise(() => {})); - return expect(this.testInstance.authenticate()) - .to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); + await expect( + this.testInstance.authenticate() + ).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); }); - it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection for transaction', function() { + it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection for transaction', async function() { this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { dialect, databaseVersion: '1.2.3', @@ -266,9 +210,11 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { this.sinon.stub(this.testInstance.connectionManager, '_connect') .returns(new Sequelize.Promise(() => {})); - return expect(this.testInstance.transaction(() => { - return this.testInstance.transaction(() => {}); - })).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); + await expect( + this.testInstance.transaction(async () => { + await this.testInstance.transaction(() => {}); + }) + ).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); }); }); }); From 566cfa937c56b40bf9372e3c91f24b73ef249cd7 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 25 Jan 2020 04:36:27 -0300 Subject: [PATCH 042/414] docs(model): findOne return value for empty result (#11762) --- lib/model.js | 6 ++---- types/lib/model.d.ts | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/model.js b/lib/model.js index 4ea330adc1ee..a040b30d7279 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1893,9 +1893,7 @@ class Model { } /** - * Search for a single instance. This applies LIMIT 1, so the listener will always be called with a single instance. - * - * __Alias__: _find_ + * Search for a single instance. Returns the first instance found, or null if none can be found. * * @param {object} [options] A hash of options to describe the scope of the search * @param {Transaction} [options.transaction] Transaction to run query under @@ -1904,7 +1902,7 @@ class Model { * @see * {@link Model.findAll} for an explanation of options * - * @returns {Promise} + * @returns {Promise} */ static findOne(options) { if (options !== undefined && !_.isPlainObject(options)) { diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 138843dbebf5..db189970967a 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1789,8 +1789,7 @@ export abstract class Model extends Hooks { ): Promise; /** - * Search for a single instance. This applies LIMIT 1, so the listener will always be called with a single - * instance. + * Search for a single instance. Returns the first instance found, or null if none can be found. */ public static findOne(this: { new (): M } & typeof Model, options: NonNullFindOptions): Promise; public static findOne( From 1a53892c82967795df4d173e674be0a11b6cee42 Mon Sep 17 00:00:00 2001 From: andres112013 Date: Sat, 25 Jan 2020 02:37:59 -0500 Subject: [PATCH 043/414] fix(typings): `comparator` arg of `Sequelize.where` (#11843) --- types/lib/sequelize.d.ts | 4 ++-- types/test/where.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index b9ea929f7296..fd6896e7139d 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1450,7 +1450,7 @@ export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): Or export function json(conditionsOrPath: string | object, value?: string | number | boolean): Json; export type AttributeType = Fn | Col | Literal | ModelAttributeColumnOptions | string; -export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string; +export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string | symbol | null; /** * A way of specifying attr = condition. @@ -1470,7 +1470,7 @@ export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOpe * @param logic The condition. Can be both a simply type, or a further condition (`.or`, `.and`, `.literal` * etc.) */ -export function where(attr: AttributeType, comparator: string, logic: LogicType): Where; +export function where(attr: AttributeType, comparator: string | symbol, logic: LogicType): Where; export function where(attr: AttributeType, logic: LogicType): Where; export default Sequelize; diff --git a/types/test/where.ts b/types/test/where.ts index ad8a204e5598..52f998437105 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -325,3 +325,13 @@ Sequelize.where( [Op.notILike]: Sequelize.literal('LIT') } ) + +Sequelize.where(Sequelize.col("ABS"), Op.is, null); + +Sequelize.where( + Sequelize.fn("ABS", Sequelize.col("age")), + Op.like, + Sequelize.fn("ABS", Sequelize.col("age")) +); + +Sequelize.where(Sequelize.col("ABS"), null); From 6279e8a066569484fceb744a8e9626db5cbcf563 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Wed, 29 Jan 2020 02:33:52 -0300 Subject: [PATCH 044/414] build(docs): save space (#11874) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d0f08bbec54..a98854b4ccc7 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "test-docker": "npm run test-docker-unit && npm run test-docker-integration", "test-docker-unit": "npm run test-unit", "test-docker-integration": "env-cmd $npm_package_options_env_cmd npm run test-integration", - "docs": "rimraf esdoc && esdoc -c docs/esdoc-config.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER && node docs/run-docs-transforms.js && node docs/redirects/create-redirects.js", + "docs": "rimraf esdoc && esdoc -c docs/esdoc-config.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER && node docs/run-docs-transforms.js && node docs/redirects/create-redirects.js && rimraf esdoc/file esdoc/source.html", "teaser": "node scripts/teaser", "test-unit": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.js\"", "test-unit-mariadb": "cross-env DIALECT=mariadb npm run test-unit", From baf7fab38b7901de3d65b65c85a7dbe2ba11e9d0 Mon Sep 17 00:00:00 2001 From: Emiliano Gaston Delgadillo Date: Fri, 31 Jan 2020 23:19:16 -0300 Subject: [PATCH 045/414] docs(model-querying-basics.md): add some commas (#11891) --- docs/manual/core-concepts/model-querying-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index 684066a99c0b..c738993c50d4 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -18,7 +18,7 @@ console.log("Jane's auto-generated ID:", jane.id); The [`Model.create()`](../class/lib/model.js~Model.html#static-method-create) method is a shorthand for building an unsaved instance with [`Model.build()`](../class/lib/model.js~Model.html#static-method-build) and saving the instance with [`instance.save()`](../class/lib/model.js~Model.html#instance-method-save). -It is also possible to define which attributes can be set in the `create` method. This can be especially useful if you create database entries based on a form which can be filled by a user. Using that would for example allow you to restrict the `User` model to set only an username and an address but not an admin flag: +It is also possible to define which attributes can be set in the `create` method. This can be especially useful if you create database entries based on a form which can be filled by a user. Using that would, for example, allow you to restrict the `User` model to set only an username and an address but not an admin flag: ```js const user = await User.create({ From 91416e3b4bcda28b4f862eb3862f70472e66d57a Mon Sep 17 00:00:00 2001 From: Wojtek Date: Sat, 1 Feb 2020 20:17:31 +0100 Subject: [PATCH 046/414] docs(assocs): fix typos in assocs manual (#11888) --- docs/manual/core-concepts/assocs.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md index b2e1d8dc85ad..fe46452adc61 100644 --- a/docs/manual/core-concepts/assocs.md +++ b/docs/manual/core-concepts/assocs.md @@ -73,7 +73,7 @@ In principle, both options are a valid way to establish a One-To-One relationshi ### Goal -For the rest of this example, let's assume that we have two models, `Foo` and `Bar`. We want to setup a One-To-One relationship between them such that `Foo` gets a `barId` column. +For the rest of this example, let's assume that we have two models, `Foo` and `Bar`. We want to setup a One-To-One relationship between them such that `Bar` gets a `fooId` column. ### Implementation @@ -84,7 +84,7 @@ Foo.hasOne(Bar); Bar.belongsTo(Foo); ``` -Since no option was passed, Sequelize will infer what to do from the names of the models. In this case, Sequelize knows that a `barId` column must be added to `Foo`. +Since no option was passed, Sequelize will infer what to do from the names of the models. In this case, Sequelize knows that a `fooId` column must be added to `Bar`. This way, calling `Bar.sync()` after the above will yield the following SQL (on PostgreSQL, for example): @@ -775,4 +775,4 @@ The trick to deciding between `sourceKey` and `targetKey` is just to remember wh * `A.hasOne(B)` and `A.hasMany(B)` keep the foreign key in the target model (`B`), therefore the referenced key is in the source model, hence the usage of `sourceKey`. -* `A.belongsToMany(B)` involves an extra table (the junction table), therefore both `sourceKey` and `targetKey` are usable, with `sourceKey` corresponding to some field in `A` (the source) and `targetKey` corresponding to some field in `B` (the target). \ No newline at end of file +* `A.belongsToMany(B)` involves an extra table (the junction table), therefore both `sourceKey` and `targetKey` are usable, with `sourceKey` corresponding to some field in `A` (the source) and `targetKey` corresponding to some field in `B` (the target). From 0fb4bb11f3771a4b229cc054ee56818c10b5b395 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 6 Feb 2020 03:32:05 -0300 Subject: [PATCH 047/414] refactor(lib): new `joinSQLFragments` util (#11865) --- lib/data-types.js | 11 +- lib/dialects/abstract/query-generator.js | 76 +++-- lib/dialects/mariadb/data-types.js | 2 +- lib/dialects/mariadb/query-generator.js | 30 +- lib/dialects/mssql/query-generator.js | 267 ++++++++++-------- lib/dialects/mysql/data-types.js | 2 +- lib/dialects/mysql/query-generator.js | 197 ++++++++----- lib/utils.js | 1 + lib/utils/join-sql-fragments.js | 83 ++++++ .../dialects/mariadb/query-generator.test.js | 4 +- .../dialects/mssql/query-generator.test.js | 4 +- .../dialects/postgres/query-generator.test.js | 6 +- 12 files changed, 461 insertions(+), 222 deletions(-) create mode 100644 lib/utils/join-sql-fragments.js diff --git a/lib/data-types.js b/lib/data-types.js index e2223ffda3be..780e6ac6a66d 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -10,6 +10,7 @@ const moment = require('moment'); const { logger } = require('./utils/logger'); const warnings = {}; const { classToInvokable } = require('./utils/classToInvokable'); +const { joinSQLFragments } = require('./utils/join-sql-fragments'); class ABSTRACT { toString(options) { @@ -62,7 +63,10 @@ class STRING extends ABSTRACT { this._length = options.length || 255; } toSql() { - return `VARCHAR(${this._length})${this._binary ? ' BINARY' : ''}`; + return joinSQLFragments([ + `VARCHAR(${this._length})`, + this._binary && 'BINARY' + ]); } validate(value) { if (Object.prototype.toString.call(value) !== '[object String]') { @@ -97,7 +101,10 @@ class CHAR extends STRING { super(typeof length === 'object' && length || { length, binary }); } toSql() { - return `CHAR(${this._length})${this._binary ? ' BINARY' : ''}`; + return joinSQLFragments([ + `CHAR(${this._length})`, + this._binary && 'BINARY' + ]); } } diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 690be7c456aa..5d44463b25b2 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -310,7 +310,19 @@ class QueryGenerator { returning += returnValues.returningFragment; } - return `INSERT${ignoreDuplicates} INTO ${this.quoteTable(tableName)} (${attributes}) VALUES ${tuples.join(',')}${onDuplicateKeyUpdate}${onConflictDoNothing}${returning};`; + return Utils.joinSQLFragments([ + 'INSERT', + ignoreDuplicates, + 'INTO', + this.quoteTable(tableName), + `(${attributes})`, + 'VALUES', + tuples.join(','), + onDuplicateKeyUpdate, + onConflictDoNothing, + returning, + ';' + ]); } /** @@ -446,7 +458,15 @@ class QueryGenerator { updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); } - return `UPDATE ${this.quoteTable(tableName)} SET ${updateSetSqlFragments.join(',')}${outputFragment} ${this.whereQuery(where)}${returningFragment}`.trim(); + return Utils.joinSQLFragments([ + 'UPDATE', + this.quoteTable(tableName), + 'SET', + updateSetSqlFragments.join(','), + outputFragment, + this.whereQuery(where), + returningFragment + ]); } /* @@ -570,16 +590,19 @@ class QueryGenerator { } addConstraintQuery(tableName, options) { - options = options || {}; - const constraintSnippet = this.getConstraintSnippet(tableName, options); - if (typeof tableName === 'string') { tableName = this.quoteIdentifiers(tableName); } else { tableName = this.quoteTable(tableName); } - return `ALTER TABLE ${tableName} ADD ${constraintSnippet};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + tableName, + 'ADD', + this.getConstraintSnippet(tableName, options || {}), + ';' + ]); } getConstraintSnippet(tableName, options) { @@ -660,7 +683,12 @@ class QueryGenerator { tableName = this.quoteTable(tableName); } - return `ALTER TABLE ${tableName} DROP CONSTRAINT ${this.quoteIdentifiers(constraintName)}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + tableName, + 'DROP CONSTRAINT', + this.quoteIdentifiers(constraintName) + ]); } /* @@ -1486,7 +1514,11 @@ class QueryGenerator { alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options); } - return `${prefix} AS ${this.quoteIdentifier(alias, true)}`; + return Utils.joinSQLFragments([ + prefix, + 'AS', + this.quoteIdentifier(alias, true) + ]); }); if (include.subQuery && topLevelInfo.subQuery) { for (const attr of includeAttributes) { @@ -1750,9 +1782,11 @@ class QueryGenerator { alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options); } - return `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) - } AS ${ - this.quoteIdentifier(alias)}`; + return Utils.joinSQLFragments([ + `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)}`, + 'AS', + this.quoteIdentifier(alias) + ]); }); const association = include.association; const parentIsTop = !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name; @@ -2130,15 +2164,17 @@ class QueryGenerator { return `CAST(${result} AS ${smth.type.toUpperCase()})`; } if (smth instanceof Utils.Fn) { - return `${smth.fn}(${smth.args.map(arg => { - if (arg instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(arg, tableName, factory, options, prepend); - } - if (_.isPlainObject(arg)) { - return this.whereItemsQuery(arg); - } - return this.escape(typeof arg === 'string' ? arg.replace('$', '$$$') : arg); - }).join(', ')})`; + return `${smth.fn}(${ + smth.args.map(arg => { + if (arg instanceof Utils.SequelizeMethod) { + return this.handleSequelizeMethod(arg, tableName, factory, options, prepend); + } + if (_.isPlainObject(arg)) { + return this.whereItemsQuery(arg); + } + return this.escape(typeof arg === 'string' ? arg.replace('$', '$$$') : arg); + }).join(', ') + })`; } if (smth instanceof Utils.Col) { if (Array.isArray(smth.col) && !factory) { diff --git a/lib/dialects/mariadb/data-types.js b/lib/dialects/mariadb/data-types.js index da2710689b3f..aa4098535631 100644 --- a/lib/dialects/mariadb/data-types.js +++ b/lib/dialects/mariadb/data-types.js @@ -51,7 +51,7 @@ module.exports = BaseTypes => { class DATE extends BaseTypes.DATE { toSql() { - return `DATETIME${this._length ? `(${this._length})` : ''}`; + return this._length ? `DATETIME(${this._length})` : 'DATETIME'; } _stringify(date, options) { date = this._applyTimezone(date, options); diff --git a/lib/dialects/mariadb/query-generator.js b/lib/dialects/mariadb/query-generator.js index 3422dbeb9a04..4c4cdb689418 100644 --- a/lib/dialects/mariadb/query-generator.js +++ b/lib/dialects/mariadb/query-generator.js @@ -1,6 +1,7 @@ 'use strict'; const MySQLQueryGenerator = require('../mysql/query-generator'); +const Utils = require('./../../utils'); class MariaDBQueryGenerator extends MySQLQueryGenerator { createSchema(schema, options) { @@ -9,10 +10,13 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator { collate: null }, options || {}); - const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; - const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; - - return `CREATE SCHEMA IF NOT EXISTS ${this.quoteIdentifier(schema)}${charset}${collate};`; + return Utils.joinSQLFragments([ + 'CREATE SCHEMA IF NOT EXISTS', + this.quoteIdentifier(schema), + options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`, + options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`, + ';' + ]); } dropSchema(schema) { @@ -20,8 +24,22 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator { } showSchemasQuery(options) { - const skip = options.skip && Array.isArray(options.skip) && options.skip.length > 0 ? options.skip : null; - return `SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA'${skip ? skip.reduce( (sql, schemaName) => sql += `,${this.escape(schemaName)}`, '') : ''});`; + const schemasToSkip = [ + '\'MYSQL\'', + '\'INFORMATION_SCHEMA\'', + '\'PERFORMANCE_SCHEMA\'' + ]; + if (options.skip && Array.isArray(options.skip) && options.skip.length > 0) { + for (const schemaName of options.skip) { + schemasToSkip.push(this.escape(schemaName)); + } + } + return Utils.joinSQLFragments([ + 'SELECT SCHEMA_NAME as schema_name', + 'FROM INFORMATION_SCHEMA.SCHEMATA', + `WHERE SCHEMA_NAME NOT IN (${schemasToSkip.join(', ')})`, + ';' + ]); } showTablesQuery(database) { diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 290788987914..2f3ca0afb95b 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -107,10 +107,9 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } createTableQuery(tableName, attributes, options) { - const query = (table, attrs) => `IF OBJECT_ID('${table}', 'U') IS NULL CREATE TABLE ${table} (${attrs})`, - primaryKeys = [], + const primaryKeys = [], foreignKeys = {}, - attrStr = []; + attributesClauseParts = []; let commentStr = ''; @@ -133,24 +132,22 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { if (dataType.includes('REFERENCES')) { // MSSQL doesn't support inline REFERENCES declarations: move to the end match = dataType.match(/^(.+) (REFERENCES.*)$/); - attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); foreignKeys[attr] = match[2]; } else { - attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); } } else if (dataType.includes('REFERENCES')) { // MSSQL doesn't support inline REFERENCES declarations: move to the end match = dataType.match(/^(.+) (REFERENCES.*)$/); - attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1]}`); foreignKeys[attr] = match[2]; } else { - attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType}`); } } } - - let attributesClause = attrStr.join(', '); const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); if (options.uniqueKeys) { @@ -159,22 +156,33 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { if (typeof indexName !== 'string') { indexName = `uniq_${tableName}_${columns.fields.join('_')}`; } - attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; + attributesClauseParts.push(`CONSTRAINT ${ + this.quoteIdentifier(indexName) + } UNIQUE (${ + columns.fields.map(field => this.quoteIdentifier(field)).join(', ') + })`); } }); } if (pkString.length > 0) { - attributesClause += `, PRIMARY KEY (${pkString})`; + attributesClauseParts.push(`PRIMARY KEY (${pkString})`); } for (const fkey in foreignKeys) { if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) { - attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + attributesClauseParts.push(`FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`); } } - return `${query(this.quoteTable(tableName), attributesClause)};${commentStr}`; + const quotedTableName = this.quoteTable(tableName); + + return Utils.joinSQLFragments([ + `IF OBJECT_ID('${quotedTableName}', 'U') IS NULL`, + `CREATE TABLE ${quotedTableName} (${attributesClauseParts.join(', ')})`, + ';', + commentStr + ]); } describeTableQuery(tableName, schema) { @@ -226,8 +234,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } dropTableQuery(tableName) { - const qouteTbl = this.quoteTable(tableName); - return `IF OBJECT_ID('${qouteTbl}', 'U') IS NOT NULL DROP TABLE ${qouteTbl};`; + const quoteTbl = this.quoteTable(tableName); + return Utils.joinSQLFragments([ + `IF OBJECT_ID('${quoteTbl}', 'U') IS NOT NULL`, + 'DROP TABLE', + quoteTbl, + ';' + ]); } addColumnQuery(table, key, dataType) { @@ -244,10 +257,15 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { delete dataType['comment']; } - const def = this.attributeToSQL(dataType, { - context: 'addColumn' - }); - return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${def};${commentStr}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { context: 'addColumn' }), + ';', + commentStr + ]); } commentTemplate(comment, table, column) { @@ -259,7 +277,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } removeColumnQuery(tableName, attributeName) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP COLUMN ${this.quoteIdentifier(attributeName)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP COLUMN', + this.quoteIdentifier(attributeName), + ';' + ]); } changeColumnQuery(tableName, attributes) { @@ -284,21 +308,25 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } } - let finalQuery = ''; - if (attrString.length) { - finalQuery += `ALTER COLUMN ${attrString.join(', ')}`; - finalQuery += constraintString.length ? ' ' : ''; - } - if (constraintString.length) { - finalQuery += `ADD ${constraintString.join(', ')}`; - } - - return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};${commentString}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + attrString.length && `ALTER COLUMN ${attrString.join(', ')}`, + constraintString.length && `ADD ${constraintString.join(', ')}`, + ';', + commentString + ]); } renameColumnQuery(tableName, attrBefore, attributes) { const newName = Object.keys(attributes)[0]; - return `EXEC sp_rename '${this.quoteTable(tableName)}.${attrBefore}', '${newName}', 'COLUMN';`; + return Utils.joinSQLFragments([ + 'EXEC sp_rename', + `'${this.quoteTable(tableName)}.${attrBefore}',`, + `'${newName}',`, + "'COLUMN'", + ';' + ]); } bulkInsertQuery(tableName, attrValueHashes, options, attributes) { @@ -499,19 +527,18 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { deleteQuery(tableName, where, options = {}, model) { const table = this.quoteTable(tableName); + const whereClause = this.getWhereConditions(where, null, model, options); - let whereClause = this.getWhereConditions(where, null, model, options); - let limit = ''; - - if (options.limit) { - limit = ` TOP(${this.escape(options.limit)})`; - } - - if (whereClause) { - whereClause = ` WHERE ${whereClause}`; - } - - return `DELETE${limit} FROM ${table}${whereClause}; SELECT @@ROWCOUNT AS AFFECTEDROWS;`; + return Utils.joinSQLFragments([ + 'DELETE', + options.limit && `TOP(${this.escape(options.limit)})`, + 'FROM', + table, + whereClause && `WHERE ${whereClause}`, + ';', + 'SELECT @@ROWCOUNT AS AFFECTEDROWS', + ';' + ]); } showIndexesQuery(tableName) { @@ -718,20 +745,19 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { getForeignKeyQuery(table, attributeName) { const tableName = table.tableName || table; - let sql = `${this._getForeignKeysQueryPrefix() - } WHERE TB.NAME =${wrapSingleQuote(tableName) - } AND COL.NAME =${wrapSingleQuote(attributeName)}`; - - if (table.schema) { - sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`; - } - - return sql; + return Utils.joinSQLFragments([ + this._getForeignKeysQueryPrefix(), + 'WHERE', + `TB.NAME =${wrapSingleQuote(tableName)}`, + 'AND', + `COL.NAME =${wrapSingleQuote(attributeName)}`, + table.schema && `AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}` + ]); } getPrimaryKeyConstraintQuery(table, attributeName) { const tableName = wrapSingleQuote(table.tableName || table); - return [ + return Utils.joinSQLFragments([ 'SELECT K.TABLE_NAME AS tableName,', 'K.COLUMN_NAME AS columnName,', 'K.CONSTRAINT_NAME AS constraintName', @@ -743,24 +769,39 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { 'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME', 'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'', `AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`, - `AND K.TABLE_NAME = ${tableName};` - ].join(' '); + `AND K.TABLE_NAME = ${tableName}`, + ';' + ]); } dropForeignKeyQuery(tableName, foreignKey) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(foreignKey)}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP', + this.quoteIdentifier(foreignKey) + ]); } getDefaultConstraintQuery(tableName, attributeName) { const quotedTable = this.quoteTable(tableName); - return 'SELECT name FROM sys.default_constraints ' + - `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U') ` + - `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}') ` + - `AND object_id = OBJECT_ID('${quotedTable}', 'U'));`; + return Utils.joinSQLFragments([ + 'SELECT name FROM sys.default_constraints', + `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U')`, + `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}')`, + `AND object_id = OBJECT_ID('${quotedTable}', 'U'))`, + ';' + ]); } dropConstraintQuery(tableName, constraintName) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(constraintName)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP CONSTRAINT', + this.quoteIdentifier(constraintName), + ';' + ]); } setIsolationLevelQuery() { @@ -796,60 +837,64 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) { - let topFragment = ''; - let mainFragment = `SELECT ${attributes.join(', ')} FROM ${tables}`; - - // Handle SQL Server 2008 with TOP instead of LIMIT - if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) { - if (options.limit) { - topFragment = `TOP ${options.limit} `; + const dbVersion = this.sequelize.options.databaseVersion; + const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0'); + + if (isSQLServer2008 && options.offset) { + // For earlier versions of SQL server, we need to nest several queries + // in order to emulate the OFFSET behavior. + // + // 1. The outermost query selects all items from the inner query block. + // This is due to a limitation in SQL server with the use of computed + // columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses. + // 2. The next query handles the LIMIT and OFFSET behavior by getting + // the TOP N rows of the query where the row number is > OFFSET + // 3. The innermost query is the actual set we want information from + + const offset = options.offset || 0; + const isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation; + let orders = { mainQueryOrder: [] }; + if (options.order) { + orders = this.getQueryOrders(options, model, isSubQuery); } - if (options.offset) { - const offset = options.offset || 0, - isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation; - let orders = { mainQueryOrder: [] }; - if (options.order) { - orders = this.getQueryOrders(options, model, isSubQuery); - } - - if (!orders.mainQueryOrder.length) { - orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField)); - } - const tmpTable = mainTableAs ? mainTableAs : 'OffsetTable'; - const whereFragment = where ? ` WHERE ${where}` : ''; - - /* - * For earlier versions of SQL server, we need to nest several queries - * in order to emulate the OFFSET behavior. - * - * 1. The outermost query selects all items from the inner query block. - * This is due to a limitation in SQL server with the use of computed - * columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses. - * 2. The next query handles the LIMIT and OFFSET behavior by getting - * the TOP N rows of the query where the row number is > OFFSET - * 3. The innermost query is the actual set we want information from - */ - const fragment = `SELECT TOP 100 PERCENT ${attributes.join(', ')} FROM ` + - `(SELECT ${topFragment}*` + - ` FROM (SELECT ROW_NUMBER() OVER (ORDER BY ${orders.mainQueryOrder.join(', ')}) as row_num, * ` + - ` FROM ${tables} AS ${tmpTable}${whereFragment})` + - ` AS ${tmpTable} WHERE row_num > ${offset})` + - ` AS ${tmpTable}`; - return fragment; + if (orders.mainQueryOrder.length === 0) { + orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField)); } - mainFragment = `SELECT ${topFragment}${attributes.join(', ')} FROM ${tables}`; - } - if (mainTableAs) { - mainFragment += ` AS ${mainTableAs}`; - } - - if (options.tableHint && TableHints[options.tableHint]) { - mainFragment += ` WITH (${TableHints[options.tableHint]})`; - } - - return mainFragment; + const tmpTable = mainTableAs || 'OffsetTable'; + + return Utils.joinSQLFragments([ + 'SELECT TOP 100 PERCENT', + attributes.join(', '), + 'FROM (', + [ + 'SELECT', + options.limit && `TOP ${options.limit}`, + '* FROM (', + [ + 'SELECT ROW_NUMBER() OVER (', + [ + 'ORDER BY', + orders.mainQueryOrder.join(', ') + ], + `) as row_num, * FROM ${tables} AS ${tmpTable}`, + where && `WHERE ${where}` + ], + `) AS ${tmpTable} WHERE row_num > ${offset}` + ], + `) AS ${tmpTable}` + ]); + } + + return Utils.joinSQLFragments([ + 'SELECT', + isSQLServer2008 && options.limit && `TOP ${options.limit}`, + attributes.join(', '), + `FROM ${tables}`, + mainTableAs && `AS ${mainTableAs}`, + options.tableHint && TableHints[options.tableHint] && `WITH (${TableHints[options.tableHint]})` + ]); } addLimitAndOffset(options, model) { diff --git a/lib/dialects/mysql/data-types.js b/lib/dialects/mysql/data-types.js index 35cae2f82cb1..c0beec964da9 100644 --- a/lib/dialects/mysql/data-types.js +++ b/lib/dialects/mysql/data-types.js @@ -50,7 +50,7 @@ module.exports = BaseTypes => { class DATE extends BaseTypes.DATE { toSql() { - return `DATETIME${this._length ? `(${this._length})` : ''}`; + return this._length ? `DATETIME(${this._length})` : 'DATETIME'; } _stringify(date, options) { date = this._applyTimezone(date, options); diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index 5aff4fc931cd..abbb491a12a0 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -7,21 +7,23 @@ const util = require('util'); const Op = require('../../operators'); -const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i; -const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i; -const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i; -const foreignKeyFields = 'CONSTRAINT_NAME as constraint_name,' - + 'CONSTRAINT_NAME as constraintName,' - + 'CONSTRAINT_SCHEMA as constraintSchema,' - + 'CONSTRAINT_SCHEMA as constraintCatalog,' - + 'TABLE_NAME as tableName,' - + 'TABLE_SCHEMA as tableSchema,' - + 'TABLE_SCHEMA as tableCatalog,' - + 'COLUMN_NAME as columnName,' - + 'REFERENCED_TABLE_SCHEMA as referencedTableSchema,' - + 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog,' - + 'REFERENCED_TABLE_NAME as referencedTableName,' - + 'REFERENCED_COLUMN_NAME as referencedColumnName'; +const JSON_FUNCTION_REGEX = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i; +const JSON_OPERATOR_REGEX = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i; +const TOKEN_CAPTURE_REGEX = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i; +const FOREIGN_KEY_FIELDS = [ + 'CONSTRAINT_NAME as constraint_name', + 'CONSTRAINT_NAME as constraintName', + 'CONSTRAINT_SCHEMA as constraintSchema', + 'CONSTRAINT_SCHEMA as constraintCatalog', + 'TABLE_NAME as tableName', + 'TABLE_SCHEMA as tableSchema', + 'TABLE_SCHEMA as tableCatalog', + 'COLUMN_NAME as columnName', + 'REFERENCED_TABLE_SCHEMA as referencedTableSchema', + 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog', + 'REFERENCED_TABLE_NAME as referencedTableName', + 'REFERENCED_COLUMN_NAME as referencedColumnName' +].join(','); const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']); @@ -41,15 +43,17 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { collate: null }, options || {}); - const database = this.quoteIdentifier(databaseName); - const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; - const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; - - return `${`CREATE DATABASE IF NOT EXISTS ${database}${charset}${collate}`.trim()};`; + return Utils.joinSQLFragments([ + 'CREATE DATABASE IF NOT EXISTS', + this.quoteIdentifier(databaseName), + options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`, + options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`, + ';' + ]); } dropDatabaseQuery(databaseName) { - return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName).trim()};`; + return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName)};`; } createSchema() { @@ -103,12 +107,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { const table = this.quoteTable(tableName); let attributesClause = attrStr.join(', '); - const comment = options.comment && typeof options.comment === 'string' ? ` COMMENT ${this.escape(options.comment)}` : ''; - const engine = options.engine; - const charset = options.charset ? ` DEFAULT CHARSET=${options.charset}` : ''; - const collation = options.collate ? ` COLLATE ${options.collate}` : ''; - const rowFormat = options.rowFormat ? ` ROW_FORMAT=${options.rowFormat}` : ''; - const initialAutoIncrement = options.initialAutoIncrement ? ` AUTO_INCREMENT=${options.initialAutoIncrement}` : ''; const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); if (options.uniqueKeys) { @@ -132,7 +130,18 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } } - return `CREATE TABLE IF NOT EXISTS ${table} (${attributesClause}) ENGINE=${engine}${comment}${charset}${collation}${initialAutoIncrement}${rowFormat};`; + return Utils.joinSQLFragments([ + 'CREATE TABLE IF NOT EXISTS', + table, + `(${attributesClause})`, + `ENGINE=${options.engine}`, + options.comment && typeof options.comment === 'string' && `COMMENT ${this.escape(options.comment)}`, + options.charset && `DEFAULT CHARSET=${options.charset}`, + options.collate && `COLLATE ${options.collate}`, + options.initialAutoIncrement && `AUTO_INCREMENT=${options.initialAutoIncrement}`, + options.rowFormat && `ROW_FORMAT=${options.rowFormat}`, + ';' + ]); } @@ -159,17 +168,28 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } addColumnQuery(table, key, dataType) { - const definition = this.attributeToSQL(dataType, { - context: 'addColumn', - tableName: table, - foreignKey: key - }); - - return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { + context: 'addColumn', + tableName: table, + foreignKey: key + }), + ';' + ]); } removeColumnQuery(tableName, attributeName) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(attributeName)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP', + this.quoteIdentifier(attributeName), + ';' + ]); } changeColumnQuery(tableName, attributes) { @@ -187,16 +207,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } } - let finalQuery = ''; - if (attrString.length) { - finalQuery += `CHANGE ${attrString.join(', ')}`; - finalQuery += constraintString.length ? ' ' : ''; - } - if (constraintString.length) { - finalQuery += `ADD ${constraintString.join(', ')}`; - } - - return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + attrString.length && `CHANGE ${attrString.join(', ')}`, + constraintString.length && `ADD ${constraintString.join(', ')}`, + ';' + ]); } renameColumnQuery(tableName, attrBefore, attributes) { @@ -207,7 +224,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); } - return `ALTER TABLE ${this.quoteTable(tableName)} CHANGE ${attrString.join(', ')};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'CHANGE', + attrString.join(', '), + ';' + ]); } handleSequelizeMethod(smth, tableName, factory, options, prepend) { @@ -300,14 +323,17 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } showIndexesQuery(tableName, options) { - return `SHOW INDEX FROM ${this.quoteTable(tableName)}${(options || {}).database ? ` FROM \`${options.database}\`` : ''}`; + return Utils.joinSQLFragments([ + `SHOW INDEX FROM ${this.quoteTable(tableName)}`, + options && options.database && `FROM \`${options.database}\`` + ]); } showConstraintsQuery(table, constraintName) { const tableName = table.tableName || table; const schemaName = table.schema; - let sql = [ + return Utils.joinSQLFragments([ 'SELECT CONSTRAINT_CATALOG AS constraintCatalog,', 'CONSTRAINT_NAME AS constraintName,', 'CONSTRAINT_SCHEMA AS constraintSchema,', @@ -315,18 +341,11 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { 'TABLE_NAME AS tableName,', 'TABLE_SCHEMA AS tableSchema', 'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS', - `WHERE table_name='${tableName}'` - ].join(' '); - - if (constraintName) { - sql += ` AND constraint_name = '${constraintName}'`; - } - - if (schemaName) { - sql += ` AND TABLE_SCHEMA = '${schemaName}'`; - } - - return `${sql};`; + `WHERE table_name='${tableName}'`, + constraintName && `AND constraint_name = '${constraintName}'`, + schemaName && `AND TABLE_SCHEMA = '${schemaName}'`, + ';' + ]); } removeIndexQuery(tableName, indexNameOrAttributes) { @@ -336,7 +355,12 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); } - return `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)}`; + return Utils.joinSQLFragments([ + 'DROP INDEX', + this.quoteIdentifier(indexName), + 'ON', + this.quoteTable(tableName) + ]); } attributeToSQL(attribute, options) { @@ -444,21 +468,21 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { while (currentIndex < stmt.length) { const string = stmt.substr(currentIndex); - const functionMatches = jsonFunctionRegex.exec(string); + const functionMatches = JSON_FUNCTION_REGEX.exec(string); if (functionMatches) { currentIndex += functionMatches[0].indexOf('('); hasJsonFunction = true; continue; } - const operatorMatches = jsonOperatorRegex.exec(string); + const operatorMatches = JSON_OPERATOR_REGEX.exec(string); if (operatorMatches) { currentIndex += operatorMatches[0].length; hasJsonFunction = true; continue; } - const tokenMatches = tokenCaptureRegex.exec(string); + const tokenMatches = TOKEN_CAPTURE_REGEX.exec(string); if (tokenMatches) { const capturedToken = tokenMatches[1]; if (capturedToken === '(') { @@ -495,7 +519,14 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { */ getForeignKeysQuery(table, schemaName) { const tableName = table.tableName || table; - return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}' AND REFERENCED_TABLE_NAME IS NOT NULL;`; + return Utils.joinSQLFragments([ + 'SELECT', + FOREIGN_KEY_FIELDS, + `FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}'`, + `AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}'`, + 'AND REFERENCED_TABLE_NAME IS NOT NULL', + ';' + ]); } /** @@ -511,12 +542,25 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { const quotedTableName = wrapSingleQuote(table.tableName || table); const quotedColumnName = wrapSingleQuote(columnName); - return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE` - + ` WHERE (REFERENCED_TABLE_NAME = ${quotedTableName}${table.schema - ? ` AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}` - : ''} AND REFERENCED_COLUMN_NAME = ${quotedColumnName})` - + ` OR (TABLE_NAME = ${quotedTableName}${table.schema ? - ` AND TABLE_SCHEMA = ${quotedSchemaName}` : ''} AND COLUMN_NAME = ${quotedColumnName} AND REFERENCED_TABLE_NAME IS NOT NULL)`; + return Utils.joinSQLFragments([ + 'SELECT', + FOREIGN_KEY_FIELDS, + 'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE', + 'WHERE (', + [ + `REFERENCED_TABLE_NAME = ${quotedTableName}`, + table.schema && `AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}`, + `AND REFERENCED_COLUMN_NAME = ${quotedColumnName}` + ], + ') OR (', + [ + `TABLE_NAME = ${quotedTableName}`, + table.schema && `AND TABLE_SCHEMA = ${quotedSchemaName}`, + `AND COLUMN_NAME = ${quotedColumnName}`, + 'AND REFERENCED_TABLE_NAME IS NOT NULL' + ], + ')' + ]); } /** @@ -528,8 +572,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { * @private */ dropForeignKeyQuery(tableName, foreignKey) { - return `ALTER TABLE ${this.quoteTable(tableName)} - DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP FOREIGN KEY', + this.quoteIdentifier(foreignKey), + ';' + ]); } } diff --git a/lib/utils.js b/lib/utils.js index c7e9fe106132..ffd03c236ded 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,6 +12,7 @@ const operatorsSet = new Set(_.values(operators)); let inflection = require('inflection'); exports.classToInvokable = require('./utils/classToInvokable').classToInvokable; +exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments; exports.Promise = Promise; diff --git a/lib/utils/join-sql-fragments.js b/lib/utils/join-sql-fragments.js new file mode 100644 index 000000000000..a53f61b8702f --- /dev/null +++ b/lib/utils/join-sql-fragments.js @@ -0,0 +1,83 @@ +'use strict'; + +function doesNotWantLeadingSpace(str) { + return /^[;,)]/.test(str); +} +function doesNotWantTrailingSpace(str) { + return /\($/.test(str); +} + +/** + * Joins an array of strings with a single space between them, + * except for: + * + * - Strings starting with ';', ',' and ')', which do not get a leading space. + * - Strings ending with '(', which do not get a trailing space. + * + * @param {string[]} parts + * @returns {string} + * @private + */ +function singleSpaceJoinHelper(parts) { + return parts.reduce(({ skipNextLeadingSpace, result }, part) => { + if (skipNextLeadingSpace || doesNotWantLeadingSpace(part)) { + result += part.trim(); + } else { + result += ` ${part.trim()}`; + } + return { + skipNextLeadingSpace: doesNotWantTrailingSpace(part), + result + }; + }, { + skipNextLeadingSpace: true, + result: '' + }).result; +} + +/** + * Joins an array with a single space, auto trimming when needed. + * + * Certain elements do not get leading/trailing spaces. + * + * @param {any[]} array The array to be joined. Falsy values are skipped. If an + * element is another array, this function will be called recursively on that array. + * Otherwise, if a non-string, non-falsy value is present, a TypeError will be thrown. + * + * @returns {string} The joined string. + * + * @private + */ +function joinSQLFragments(array) { + if (array.length === 0) return ''; + + // Skip falsy fragments + array = array.filter(x => x); + + // Resolve recursive calls + array = array.map(fragment => { + if (Array.isArray(fragment)) { + return joinSQLFragments(fragment); + } + return fragment; + }); + + // Ensure strings + for (const fragment of array) { + if (fragment && typeof fragment !== 'string') { + const error = new TypeError(`Tried to construct a SQL string with a non-string, non-falsy fragment (${fragment}).`); + error.args = array; + error.fragment = fragment; + throw error; + } + } + + // Trim fragments + array = array.map(x => x.trim()); + + // Skip full-whitespace fragments (empty after the above trim) + array = array.filter(x => x !== ''); + + return singleSpaceJoinHelper(array); +} +exports.joinSQLFragments = joinSQLFragments; diff --git a/test/unit/dialects/mariadb/query-generator.test.js b/test/unit/dialects/mariadb/query-generator.test.js index 4c1cb34a9db6..190f6da45870 100644 --- a/test/unit/dialects/mariadb/query-generator.test.js +++ b/test/unit/dialects/mariadb/query-generator.test.js @@ -71,11 +71,11 @@ if (dialect === 'mariadb') { }, { arguments: [{ skip: ['test'] }], - expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\',\'test\');' + expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'test\');' }, { arguments: [{ skip: ['test', 'Te\'st2'] }], - expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\',\'test\',\'Te\\\'st2\');' + expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'test\', \'Te\\\'st2\');' } ], diff --git a/test/unit/dialects/mssql/query-generator.test.js b/test/unit/dialects/mssql/query-generator.test.js index 88e8a085b4fa..39fbbd62ac8b 100644 --- a/test/unit/dialects/mssql/query-generator.test.js +++ b/test/unit/dialects/mssql/query-generator.test.js @@ -136,12 +136,12 @@ if (current.dialect.name === 'mssql') { // With offset expectsql(modifiedGen.selectFromTableFragment({ offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' + mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' }); // With both limit and offset expectsql(modifiedGen.selectFromTableFragment({ limit: 10, offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' + mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' }); }); diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js index d82fe2d6500c..40e8ea78d109 100644 --- a/test/unit/dialects/postgres/query-generator.test.js +++ b/test/unit/dialects/postgres/query-generator.test.js @@ -56,7 +56,7 @@ if (dialect.startsWith('postgres')) { { title: 'Should use the plus operator', arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' + expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' }, { title: 'Should use the plus operator with where clause', @@ -71,12 +71,12 @@ if (dialect.startsWith('postgres')) { { title: 'Should use the minus operator', arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *' + expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *' }, { title: 'Should use the minus operator with negative value', arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' + expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' }, { title: 'Should use the minus operator with where clause', From bbf9139dc02d0a96899b7fc94f8649fe9c2916d7 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 13 Feb 2020 03:49:12 -0300 Subject: [PATCH 048/414] other(find-all): throw on empty attributes (#11867) --- lib/dialects/abstract/query-generator.js | 11 ++++++++ lib/dialects/mssql/query-generator.js | 2 ++ test/integration/model/findAll.test.js | 32 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 5d44463b25b2..70675f1735df 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1365,6 +1365,7 @@ class QueryGenerator { } if (subQuery) { + this._throwOnEmptyAttributes(attributes.main, { modelName: model && model.name, as: mainTable.as }); query = `SELECT ${attributes.main.join(', ')} FROM (${subQueryItems.join('')}) AS ${mainTable.as}${mainJoinQueries.join('')}${mainQueryItems.join('')}`; } else { query = mainQueryItems.join(''); @@ -2053,7 +2054,17 @@ class QueryGenerator { return { mainQueryOrder, subQueryOrder }; } + _throwOnEmptyAttributes(attributes, extraInfo = {}) { + if (attributes.length > 0) return; + const asPart = extraInfo.as && `as ${extraInfo.as}` || ''; + const namePart = extraInfo.modelName && `for model '${extraInfo.modelName}'` || ''; + const message = `Attempted a SELECT query ${namePart} ${asPart} without selecting any columns`; + throw new sequelizeError.QueryError(message.replace(/ +/g, ' ')); + } + selectFromTableFragment(options, model, attributes, tables, mainTableAs) { + this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); + let fragment = `SELECT ${attributes.join(', ')} FROM ${tables}`; if (mainTableAs) { diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 2f3ca0afb95b..dfcbcf361a2b 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -837,6 +837,8 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) { + this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); + const dbVersion = this.sequelize.options.databaseVersion; const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0'); diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index 2b937d11c432..dc48af23e631 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -60,6 +60,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); + it('should throw on an attempt to fetch no attributes', function() { + return expect(this.User.findAll({ attributes: [] })).to.be.rejectedWith( + Sequelize.QueryError, + /^Attempted a SELECT query.+without selecting any columns$/ + ); + }); + + it('should not throw if overall attributes are nonempty', function() { + const Post = this.sequelize.define('Post', { foo: DataTypes.STRING }); + const Comment = this.sequelize.define('Comment', { bar: DataTypes.STRING }); + Post.hasMany(Comment, { as: 'comments' }); + return Post.sync({ force: true }) + .then(() => Comment.sync({ force: true })) + .then(() => { + // Should not throw in this case, even + // though `attributes: []` is set for the main model + return Post.findAll({ + raw: true, + attributes: [], + include: [ + { + model: Comment, + as: 'comments', + attributes: [ + [Sequelize.fn('COUNT', Sequelize.col('comments.id')), 'commentCount'] + ] + } + ] + }); + }); + }); + describe('special where conditions/smartWhere object', () => { beforeEach(function() { this.buf = Buffer.alloc(16); From 41aba1fd0fac3b1af9d4c6c3cfc2a670a6239fd6 Mon Sep 17 00:00:00 2001 From: segersniels Date: Mon, 17 Feb 2020 15:54:35 -0800 Subject: [PATCH 049/414] fix(typings): plain option in sequelize.query (#11596) Co-authored-by: Pedro Augusto de Paula Barbosa --- types/lib/sequelize.d.ts | 2 ++ types/test/sequelize.ts | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index fd6896e7139d..8d647f9a4283 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1217,7 +1217,9 @@ export class Sequelize extends Hooks { sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithModel ): Promise; + public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType & { plain: true }): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; + public query(sql: string | { query: string; values: unknown[] }, options: (QueryOptions | QueryOptionsWithType) & { plain: true }): Promise<{ [key: string]: unknown }>; public query(sql: string | { query: string; values: unknown[] }, options?: QueryOptions | QueryOptionsWithType): Promise<[unknown[], unknown]>; /** diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts index 516e39bc5c5a..d17b68c837dc 100644 --- a/types/test/sequelize.ts +++ b/types/test/sequelize.ts @@ -65,3 +65,20 @@ sequelize.query('SELECT * FROM `user`', { type: QueryTypes.RAW }).then(result => const arraysOnly = (a: any[]) => a; arraysOnly(data); }); + +sequelize + .query<{ count: number }>("SELECT COUNT(1) as count FROM `user`", { + type: QueryTypes.SELECT, + plain: true + }) + .then(result => { + result.count.toExponential(); // is a number! + }); + +sequelize + .query("SELECT COUNT(1) as count FROM `user`", { + plain: true + }) + .then(result => { + console.log(result.count); + }); From 5e85724582c3b341f566d978fb4e25a2942434a1 Mon Sep 17 00:00:00 2001 From: Colin Cheng Date: Thu, 20 Feb 2020 20:16:47 +0800 Subject: [PATCH 050/414] feat(index): improve to support multiple fields with operator (#11934) --- lib/dialects/abstract/index.js | 3 +- lib/dialects/abstract/query-generator.js | 17 +++++-- lib/dialects/postgres/index.js | 3 +- test/unit/sql/index.test.js | 61 ++++++++++++++++++++++++ types/lib/query-interface.d.ts | 4 +- 5 files changed, 80 insertions(+), 8 deletions(-) diff --git a/lib/dialects/abstract/index.js b/lib/dialects/abstract/index.js index 618845f7e06f..3dbdc4666844 100644 --- a/lib/dialects/abstract/index.js +++ b/lib/dialects/abstract/index.js @@ -59,7 +59,8 @@ AbstractDialect.prototype.supports = { concurrently: false, type: false, using: true, - functionBased: false + functionBased: false, + operator: false }, joinTableDependent: true, groupedLimit: true, diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 70675f1735df..6f523df0e066 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -505,12 +505,14 @@ class QueryGenerator { } const fieldsSql = options.fields.map(field => { - if (typeof field === 'string') { - return this.quoteIdentifier(field); - } if (field instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(field); } + if (typeof field === 'string') { + field = { + name: field + }; + } let result = ''; if (field.attribute) { @@ -527,6 +529,13 @@ class QueryGenerator { result += ` COLLATE ${this.quoteIdentifier(field.collate)}`; } + if (this._dialect.supports.index.operator) { + const operator = field.operator || options.operator; + if (operator) { + result += ` ${operator}`; + } + } + if (this._dialect.supports.index.length && field.length) { result += `(${field.length})`; } @@ -581,7 +590,7 @@ class QueryGenerator { this._dialect.supports.index.using === 1 && options.using ? `USING ${options.using}` : '', !this._dialect.supports.indexViaAlter ? `ON ${tableName}` : undefined, this._dialect.supports.index.using === 2 && options.using ? `USING ${options.using}` : '', - `(${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''})`, + `(${fieldsSql.join(', ')})`, this._dialect.supports.index.parser && options.parser ? `WITH PARSER ${options.parser}` : undefined, this._dialect.supports.index.where && options.where ? options.where : undefined ); diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index 37ec80533931..5967d334a74d 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -39,7 +39,8 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototy concurrently: true, using: 2, where: true, - functionBased: true + functionBased: true, + operator: true }, inserts: { onConflictDoNothing: ' ON CONFLICT DO NOTHING', diff --git a/test/unit/sql/index.test.js b/test/unit/sql/index.test.js index a54b6dd32327..026ef8f34a5d 100644 --- a/test/unit/sql/index.test.js +++ b/test/unit/sql/index.test.js @@ -171,6 +171,67 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); } + + if (current.dialect.supports.index.operator) { + it('operator with multiple fields', () => { + expectsql(sql.addIndexQuery('table', { + fields: ['column1', 'column2'], + using: 'gist', + operator: 'inet_ops' + }), { + postgres: 'CREATE INDEX "table_column1_column2" ON "table" USING gist ("column1" inet_ops, "column2" inet_ops)' + }); + }); + it('operator in fields', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'column', + operator: 'inet_ops' + }], + using: 'gist' + }), { + postgres: 'CREATE INDEX "table_column" ON "table" USING gist ("column" inet_ops)' + }); + }); + it('operator in fields with order', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'column', + order: 'DESC', + operator: 'inet_ops' + }], + using: 'gist' + }), { + postgres: 'CREATE INDEX "table_column" ON "table" USING gist ("column" inet_ops DESC)' + }); + }); + it('operator in multiple fields #1', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'column1', + order: 'DESC', + operator: 'inet_ops' + }, 'column2'], + using: 'gist' + }), { + postgres: 'CREATE INDEX "table_column1_column2" ON "table" USING gist ("column1" inet_ops DESC, "column2")' + }); + }); + it('operator in multiple fields #2', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'path', + operator: 'text_pattern_ops' + }, 'level', { + name: 'name', + operator: 'varchar_pattern_ops' + }], + using: 'btree' + }), { + postgres: 'CREATE INDEX "table_path_level_name" ON "table" USING btree ("path" text_pattern_ops, "level", "name" varchar_pattern_ops)' + }); + }); + } }); describe('removeIndex', () => { diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index a1ea23154090..c70595847429 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -171,9 +171,9 @@ export interface IndexesOptions { * An array of the fields to index. Each field can either be a string containing the name of the field, * a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `name` * (field name), `length` (create a prefix index of length chars), `order` (the direction the column - * should be sorted in), `collate` (the collation (sort order) for the column) + * should be sorted in), `collate` (the collation (sort order) for the column), `operator` (likes IndexesOptions['operator']) */ - fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string })[]; + fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string; operator?: string })[]; /** * The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and From 0067598e6744f387b597321035eeba095bdf7f09 Mon Sep 17 00:00:00 2001 From: Sushant Date: Thu, 20 Feb 2020 18:33:36 +0530 Subject: [PATCH 051/414] docs: 6.0.0-beta.5 --- docs/manual/other-topics/upgrade-to-v6.md | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index 563f8fc6ccdf..a7217d3186df 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -48,6 +48,32 @@ This method now tests for equality with [`_.isEqual`](https://lodash.com/docs/4. ## Changelog +### 6.0.0-beta.5 + +- fix(find-all): throw on empty attributes [#11867](https://github.com/sequelize/sequelize/pull/11867) +- fix(types): `queryInterface.addIndex` [#11844](https://github.com/sequelize/sequelize/pull/11844) +- fix(types): `plain` option in `sequelize.query` [#11596](https://github.com/sequelize/sequelize/pull/11596) +- fix(types): correct overloaded method order [#11727](https://github.com/sequelize/sequelize/pull/11727) +- fix(types): `comparator` arg of `Sequelize.where` [#11843](https://github.com/sequelize/sequelize/pull/11843) +- fix(types): fix BelongsToManyGetAssociationsMixinOptions [#11818](https://github.com/sequelize/sequelize/pull/11818) +- fix(types): adds `hooks` to `CreateOptions` [#11736](https://github.com/sequelize/sequelize/pull/11736) +- fix(increment): broken queries [#11852](https://github.com/sequelize/sequelize/pull/11852) +- fix(associations): gets on many-to-many with non-primary target key [#11778](https://github.com/sequelize/sequelize11778/pull/) +- fix: properly select SRID if present [#11763](https://github.com/sequelize/sequelize/pull/11763) +- feat(sqlite): automatic path provision for `options.storage` [#11853](https://github.com/sequelize/sequelize/pull/11853) +- feat(postgres): `idle_in_transaction_session_timeout` connection option [#11775](https://github.com/sequelize/sequelize11775/pull/) +- feat(index): improve to support multiple fields with operator [#11934](https://github.com/sequelize/sequelize/pull/11934) +- docs(transactions): fix addIndex example and grammar [#11759](https://github.com/sequelize/sequelize/pull/11759) +- docs(raw-queries): remove outdated info [#11833](https://github.com/sequelize/sequelize/pull/11833) +- docs(optimistic-locking): fix missing manual [#11850](https://github.com/sequelize/sequelize/pull/11850) +- docs(model): findOne return value for empty result [#11762](https://github.com/sequelize/sequelize/pull/11762) +- docs(model-querying-basics.md): add some commas [#11891](https://github.com/sequelize/sequelize/pull/11891) +- docs(manuals): fix missing models-definition page [#11838](https://github.com/sequelize/sequelize/pull/11838) +- docs(manuals): extensive rewrite [#11825](https://github.com/sequelize/sequelize/pull/11825) +- docs(dialect-specific): add MSSQL domain auth example [#11799](https://github.com/sequelize/sequelize/pull/11799) +- docs(associations): fix typos in assocs manual [#11888](https://github.com/sequelize/sequelize/pull/11888) +- docs(associations): fix typo [#11869](https://github.com/sequelize/sequelize/pull/11869) + ### 6.0.0-beta.4 - feat(sync): allow to bypass drop statements when sync with alter enabled [#11708](https://github.com/sequelize/sequelize/pull/11708) From ecf8f144008c10e496ced4f135be075c4fab276a Mon Sep 17 00:00:00 2001 From: Sushant Date: Thu, 20 Feb 2020 18:33:54 +0530 Subject: [PATCH 052/414] 6.0.0-beta.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 612ddb3ab37f..4b24ec4d506e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sequelize", - "version": "6.0.0-beta.4", + "version": "6.0.0-beta.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a98854b4ccc7..77736c07689b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sequelize", "description": "Multi dialect ORM for Node.JS", - "version": "6.0.0-beta.4", + "version": "6.0.0-beta.5", "maintainers": [ "Sascha Depold ", "Jan Aagaard Meier ", From 8a5ecc1decb727abca70fcd3811a7d8733ec4883 Mon Sep 17 00:00:00 2001 From: Tim Mensch Date: Thu, 20 Feb 2020 21:33:38 -0700 Subject: [PATCH 053/414] fix(model.count): distinct without any column generates invalid SQL (#11946) --- lib/model.js | 4 +++- test/integration/model/count.test.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index a040b30d7279..79e527109e1f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2033,7 +2033,9 @@ class Model { if (options.include) { col = `${this.name}.${options.col || this.primaryKeyField}`; } - + if (options.distinct && col === '*') { + col = this.primaryKeyField; + } options.plain = !options.group; options.dataType = new DataTypes.INTEGER(); options.includeIgnoreAttributes = false; diff --git a/test/integration/model/count.test.js b/test/integration/model/count.test.js index e49b0c3d85ae..34a9bf9308ba 100644 --- a/test/integration/model/count.test.js +++ b/test/integration/model/count.test.js @@ -109,6 +109,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); + it('should be able to specify NO column for COUNT() with DISTINCT', function() { + return this.User.bulkCreate([ + { username: 'ember', age: 10 }, + { username: 'angular', age: 20 }, + { username: 'mithril', age: 10 } + ]).then(() => { + return this.User.count({ + distinct: true + }); + }) + .then(count => { + expect(count).to.be.eql(3); + }); + }); + it('should be able to use where clause on included models', function() { const countOptions = { col: 'username', From b2a2134697eb983545f5e717df7d14705a67df33 Mon Sep 17 00:00:00 2001 From: iamdi Date: Fri, 28 Feb 2020 13:44:46 +0700 Subject: [PATCH 054/414] docs(query-interface): removeIndex indexNameOrAttributes (#11947) --- lib/query-interface.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/query-interface.js b/lib/query-interface.js index 1797fbd9f068..bb3a0a19ba06 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -679,7 +679,7 @@ class QueryInterface { * * @param {string[]} tableNames table names * @param {object} [options] Query options - * + * * @returns {Promise} */ getForeignKeysForTables(tableNames, options) { @@ -753,9 +753,9 @@ class QueryInterface { /** * Remove an already existing index from a table * - * @param {string} tableName Table name to drop index from - * @param {string} indexNameOrAttributes Index name - * @param {object} [options] Query options + * @param {string} tableName Table name to drop index from + * @param {string|string[]} indexNameOrAttributes Index name or list of attributes that in the index + * @param {object} [options] Query options * * @returns {Promise} */ @@ -1098,7 +1098,7 @@ class QueryInterface { * @param {string} tableName table name from where to delete records * @param {object} where where conditions to find records to delete * @param {object} [options] options - * @param {boolean} [options.truncate] Use truncate table command + * @param {boolean} [options.truncate] Use truncate table command * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table. * @param {Model} [model] Model From f98c59c1e3b9ec2dd325f60205f9de87326628b5 Mon Sep 17 00:00:00 2001 From: Sushant Date: Mon, 16 Mar 2020 12:05:50 +0530 Subject: [PATCH 055/414] chores: update packages reported by git security (#12005) --- package-lock.json | 45 +++++++++++++++++++++++++++++++++++++-------- package.json | 2 ++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b24ec4d506e..23e8341ccb39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1162,11 +1162,10 @@ "dev": true }, "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true }, "acorn-globals": { "version": "1.0.9", @@ -1176,6 +1175,15 @@ "optional": true, "requires": { "acorn": "^2.1.0" + }, + "dependencies": { + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, + "optional": true + } } }, "acorn-jsx": { @@ -3104,6 +3112,14 @@ "marked": "0.3.19", "minimist": "1.2.0", "taffydb": "2.7.3" + }, + "dependencies": { + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + } } }, "esdoc-accessor-plugin": { @@ -3348,6 +3364,12 @@ "graceful-fs": "^4.1.6" } }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, "repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", @@ -5590,6 +5612,13 @@ "xml-name-validator": ">= 2.0.1 < 3.0.0" }, "dependencies": { + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, + "optional": true + }, "parse5": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", @@ -6420,9 +6449,9 @@ "dev": true }, "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", + "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==", "dev": true }, "marked-terminal": { diff --git a/package.json b/package.json index 77736c07689b..cdc4e37ebe8d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@types/bluebird": "^3.5.26", "@types/node": "^12.7.8", "@types/validator": "^10.11.0", + "acorn": "^7.1.1", "big-integer": "^1.6.45", "chai": "^4.x", "chai-as-promised": "^7.x", @@ -71,6 +72,7 @@ "lint-staged": "^8.1.5", "mariadb": "^2.1.5", "markdownlint-cli": "^0.21.0", + "marked": "^0.8.0", "mocha": "^6.1.4", "mysql2": "^1.6.5", "nyc": "^15.0.0", From a0635e3cef799940e485b59f580c83050b85ac77 Mon Sep 17 00:00:00 2001 From: Evan Date: Mon, 16 Mar 2020 01:40:47 -0600 Subject: [PATCH 056/414] fix(model): updateOnDuplicate handles composite keys (#11984) --- lib/model.js | 12 +-- test/integration/model/bulk-create.test.js | 111 +++++++++++++++++++++ 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/lib/model.js b/lib/model.js index 79e527109e1f..e9291df76778 100644 --- a/lib/model.js +++ b/lib/model.js @@ -858,7 +858,7 @@ class Model { * * @see * Model Basics guide - * + * * @see * Hooks guide * @@ -2708,7 +2708,7 @@ class Model { // Get primary keys for postgres to enable updateOnDuplicate options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value(); if (Object.keys(model.uniqueKeys).length > 0) { - options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map(c => c.fields[0]).value(); + options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length >= 1).map(c => c.fields).reduce(c => c[0]).value(); } } @@ -3842,13 +3842,13 @@ class Model { /** * Validates this instance, and if the validation passes, persists it to the database. - * + * * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). - * + * * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. - * + * * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. - * + * * @param {object} [options] save options * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. diff --git a/test/integration/model/bulk-create.test.js b/test/integration/model/bulk-create.test.js index 874f812b1883..2c58355123f6 100644 --- a/test/integration/model/bulk-create.test.js +++ b/test/integration/model/bulk-create.test.js @@ -586,6 +586,117 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(people[1].name).to.equal('Bob'); }); }); + + it('when the composite primary key column names and model field names are different', function() { + const Person = this.sequelize.define('Person', { + systemId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + field: 'system_id' + }, + system: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + field: 'system' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, {}); + + return Person.sync({ force: true }) + .then(() => { + const inserts = [ + { systemId: 1, system: 'system1', name: 'Alice' } + ]; + return Person.bulkCreate(inserts); + }) + .then(people => { + expect(people.length).to.equal(1); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('Alice'); + + const updates = [ + { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { systemId: 1, system: 'system2', name: 'Bob' } + ]; + + return Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); + }) + .then(people => { + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); + }); + }); + + it('when the primary key column names and model field names are different and have composite unique constraints', function() { + const Person = this.sequelize.define('Person', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + field: 'id' + }, + systemId: { + type: DataTypes.INTEGER, + allowNull: false, + unique: 'system_id_system_unique', + field: 'system_id' + }, + system: { + type: DataTypes.STRING, + allowNull: false, + unique: 'system_id_system_unique', + field: 'system' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, {}); + + return Person.sync({ force: true }) + .then(() => { + const inserts = [ + { id: 1, systemId: 1, system: 'system1', name: 'Alice' } + ]; + return Person.bulkCreate(inserts); + }) + .then(people => { + expect(people.length).to.equal(1); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('Alice'); + + const updates = [ + { id: 1, systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { id: 2, systemId: 1, system: 'system2', name: 'Bob' } + ]; + + return Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); + }) + .then(people => { + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); + }); + }); + }); From 9785c585de010dca015b345dab9caa6b27f952a3 Mon Sep 17 00:00:00 2001 From: Thomas Hoppe Date: Mon, 16 Mar 2020 08:47:05 +0100 Subject: [PATCH 057/414] fix(typings): add type_helpers to file list (#12000) --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cdc4e37ebe8d..a94f72192911 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "files": [ "lib", "types/index.d.ts", - "types/lib" + "types/lib", + "types/type-helpers" ], "license": "MIT", "dependencies": { From cc836ba88491d5f2f1c5d20a51277127ef60deb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Kr=C3=BCger?= Date: Mon, 16 Mar 2020 08:50:07 +0100 Subject: [PATCH 058/414] fix(mssql): cast sql_variant in query generator (#11994) --- lib/dialects/mssql/query-generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index dfcbcf361a2b..6540d1727d35 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -195,7 +195,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { "COLUMN_DEFAULT AS 'Default',", "pk.CONSTRAINT_TYPE AS 'Constraint',", "COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',", - "prop.value AS 'Comment'", + "CAST(prop.value AS NVARCHAR) AS 'Comment'", 'FROM', 'INFORMATION_SCHEMA.TABLES t', 'INNER JOIN', From b739613715c542c9f245ea2a33e5f9cc10edc36a Mon Sep 17 00:00:00 2001 From: MartinJLee Date: Mon, 16 Mar 2020 18:10:53 +1000 Subject: [PATCH 059/414] fix(mssql): set correct scale for float (#11962) --- lib/dialects/mssql/query.js | 9 ++++- test/unit/dialects/mssql/query.test.js | 50 ++++++++++++++++++-------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 1f01c7fef185..3732654d62c1 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -9,6 +9,13 @@ const { logger } = require('../../utils/logger'); const debug = logger.debugContext('sql:mssql'); +function getScale(aNum) { + if (!Number.isFinite(aNum)) return 0; + let e = 1; + while (Math.round(aNum * e) / e !== aNum) e *= 10; + return Math.log10(e); +} + class Query extends AbstractQuery { getInsertIdField() { return 'id'; @@ -27,7 +34,7 @@ class Query extends AbstractQuery { } else { paramType.type = TYPES.Numeric; //Default to a reasonable numeric precision/scale pending more sophisticated logic - paramType.typeOptions = { precision: 30, scale: 15 }; + paramType.typeOptions = { precision: 30, scale: getScale(value) }; } } if (Buffer.isBuffer(value)) { diff --git a/test/unit/dialects/mssql/query.test.js b/test/unit/dialects/mssql/query.test.js index ef151977fb05..90ef90479155 100644 --- a/test/unit/dialects/mssql/query.test.js +++ b/test/unit/dialects/mssql/query.test.js @@ -15,18 +15,22 @@ let sandbox, query; if (dialect === 'mssql') { describe('[MSSQL Specific] Query', () => { - describe('beginTransaction', () => { - beforeEach(() => { - sandbox = sinon.createSandbox(); - const options = { - transaction: { name: 'transactionName' }, - isolationLevel: 'REPEATABLE_READ', - logging: false - }; - sandbox.stub(connectionStub, 'beginTransaction').callsArg(0); - query = new Query(connectionStub, sequelize, options); - }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + const options = { + transaction: { name: 'transactionName' }, + isolationLevel: 'REPEATABLE_READ', + logging: false + }; + sandbox.stub(connectionStub, 'beginTransaction').callsArg(0); + query = new Query(connectionStub, sequelize, options); + }); + + afterEach(() => { + sandbox.restore(); + }); + describe('beginTransaction', () => { it('should call beginTransaction with correct arguments', () => { return query._run(connectionStub, 'BEGIN TRANSACTION') .then(() => { @@ -35,10 +39,6 @@ if (dialect === 'mssql') { expect(connectionStub.beginTransaction.args[0][2]).to.equal(tediousIsolationLevel.REPEATABLE_READ); }); }); - - afterEach(() => { - sandbox.restore(); - }); }); describe('formatBindParameters', () => { @@ -64,5 +64,25 @@ if (dialect === 'mssql') { expect(result[0]).to.equal(expected); }); }); + + describe('getSQLTypeFromJsType', () => { + const TYPES = tedious.TYPES; + it('should return correct parameter type', () => { + expect(query.getSQLTypeFromJsType(2147483647, TYPES)).to.eql({ type: TYPES.Int, typeOptions: {} }); + expect(query.getSQLTypeFromJsType(-2147483648, TYPES)).to.eql({ type: TYPES.Int, typeOptions: {} }); + + expect(query.getSQLTypeFromJsType(2147483648, TYPES)).to.eql({ type: TYPES.BigInt, typeOptions: {} }); + expect(query.getSQLTypeFromJsType(-2147483649, TYPES)).to.eql({ type: TYPES.BigInt, typeOptions: {} }); + + expect(query.getSQLTypeFromJsType(Buffer.from('abc'), TYPES)).to.eql({ type: TYPES.VarBinary, typeOptions: {} }); + }); + + it('should return parameter type correct scale for float', () => { + expect(query.getSQLTypeFromJsType(1.23, TYPES)).to.eql({ type: TYPES.Numeric, typeOptions: { precision: 30, scale: 2 } }); + expect(query.getSQLTypeFromJsType(0.30000000000000004, TYPES)).to.eql({ type: TYPES.Numeric, typeOptions: { precision: 30, scale: 17 } }); + expect(query.getSQLTypeFromJsType(2.5e-15, TYPES)).to.eql({ type: TYPES.Numeric, typeOptions: { precision: 30, scale: 16 } }); + }); + }); + }); } From f1ba280884de0b1d143a0736022b918a6fe4e7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Ricard?= <22248411+mrrinot@users.noreply.github.com> Date: Mon, 16 Mar 2020 09:11:44 +0100 Subject: [PATCH 060/414] feat(postgres): minify include aliases over limit (#11940) --- lib/dialects/abstract/query-generator.js | 7 +++ lib/dialects/postgres/query.js | 12 +++++ .../dialects/postgres/query.test.js | 47 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 6f523df0e066..3924c057f0c2 100755 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1144,6 +1144,7 @@ class QueryGenerator { if (this.options.minifyAliases && !options.aliasesMapping) { options.aliasesMapping = new Map(); options.aliasesByTable = {}; + options.includeAliases = new Map(); } // resolve table name options @@ -1721,6 +1722,12 @@ class QueryGenerator { } } + if (this.options.minifyAliases && asRight.length > 63) { + const alias = `%${topLevelInfo.options.includeAliases.size}`; + + topLevelInfo.options.includeAliases.set(alias, asRight); + } + return { join: include.required ? 'INNER JOIN' : include.right && this._dialect.supports['RIGHT JOIN'] ? 'RIGHT OUTER JOIN' : 'LEFT OUTER JOIN', body: this.quoteTable(tableRight, asRight), diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index dec13a879b43..b15e6bc417e2 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -53,6 +53,18 @@ class Query extends AbstractQuery { if (!_.isEmpty(this.options.searchPath)) { sql = this.sequelize.getQueryInterface().QueryGenerator.setSearchPath(this.options.searchPath) + sql; } + + if (this.sequelize.options.minifyAliases && this.options.includeAliases) { + _.toPairs(this.options.includeAliases) + // Sorting to replace the longest aliases first to prevent alias collision + .sort((a, b) => b[1].length - a[1].length) + .forEach(([alias, original]) => { + const reg = new RegExp(_.escapeRegExp(original), 'g'); + + sql = sql.replace(reg, alias); + }); + } + this.sql = sql; const query = parameters && parameters.length diff --git a/test/integration/dialects/postgres/query.test.js b/test/integration/dialects/postgres/query.test.js index 3f6597eb0c9d..4aa5fd43f05d 100644 --- a/test/integration/dialects/postgres/query.test.js +++ b/test/integration/dialects/postgres/query.test.js @@ -63,5 +63,52 @@ if (dialect.match(/^postgres/)) { expect(res[taskAlias].title).to.be.equal('SuperTask'); }); }); + + it('should throw due to table name being truncated', () => { + const sequelize = Support.createSequelizeInstance({ minifyAliases: true }); + + const User = sequelize.define('user_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', + { + name: DataTypes.STRING, + email: DataTypes.STRING + }, + { + tableName: 'user' + } + ); + const Project = sequelize.define('project_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', + { + name: DataTypes.STRING + }, + { + tableName: 'project' + } + ); + const Company = sequelize.define('company_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', + { + name: DataTypes.STRING + }, + { + tableName: 'company' + } + ); + User.hasMany(Project, { foreignKey: 'userId' }); + Project.belongsTo(Company, { foreignKey: 'companyId' }); + + return sequelize.sync({ force: true }).then(() => { + return Company.create({ name: 'Sequelize' }).then(comp => { + return User.create({ name: 'standard user' }).then(user => { + return Project.create({ name: 'Manhattan', companyId: comp.id, userId: user.id }).then(() => { + return User.findAll({ + include: { + model: Project, + include: Company + } + }); + }); + }); + }); + }); + }); }); } \ No newline at end of file From 761158cc7d2c8f7c0353b3e80be1ad882924baa0 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Mon, 16 Mar 2020 01:12:18 -0700 Subject: [PATCH 061/414] fix(query-interface): allow passing null for query interface insert (#11931) --- types/lib/model.d.ts | 10 +++++----- types/lib/query-interface.d.ts | 4 ++-- types/test/query-interface.ts | 4 +++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index db189970967a..7ce2734b4688 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -151,10 +151,10 @@ export interface WhereOperators { [Op.lte]?: number | string | Date | Literal; /** Example: `[Op.ne]: 20,` becomes `!= 20` */ - [Op.ne]?: string | number | Literal | WhereOperators; + [Op.ne]?: null | string | number | Literal | WhereOperators; /** Example: `[Op.not]: true,` becomes `IS NOT TRUE` */ - [Op.not]?: boolean | string | number | Literal | WhereOperators; + [Op.not]?: null | boolean | string | number | Literal | WhereOperators; /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ [Op.between]?: [number, number]; @@ -2605,11 +2605,11 @@ export abstract class Model extends Hooks { /** * Validates this instance, and if the validation passes, persists it to the database. - * + * * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). - * + * * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. - * + * * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. */ public save(options?: SaveOptions): Promise; diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index c70595847429..6d758f0bc47a 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -451,7 +451,7 @@ export class QueryInterface { /** * Inserts a new record */ - public insert(instance: Model, tableName: string, values: object, options?: QueryOptions): Promise; + public insert(instance: Model | null, tableName: string, values: object, options?: QueryOptions): Promise; /** * Inserts or Updates a record in the database @@ -473,7 +473,7 @@ export class QueryInterface { records: object[], options?: QueryOptions, attributes?: string[] | string - ): Promise; + ): Promise; /** * Updates a row diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index ad95eee09a4b..2c02b8db3926 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -53,7 +53,7 @@ queryInterface.dropTable('nameOfTheExistingTable'); queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); -queryInterface.bulkInsert({ tableName: 'foo', as: 'bar', name: 'as' }, [{}], {}); +const bulkInsertRes: Promise = queryInterface.bulkInsert({ tableName: 'foo', as: 'bar', name: 'as' }, [{}], {}); queryInterface.bulkUpdate({ tableName: 'foo', delimiter: 'bar', as: 'baz', name: 'quz' }, {}, {}); @@ -181,3 +181,5 @@ queryInterface.delete(null, 'Person', { }); queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, Model, {}); + +queryInterface.insert(null, 'test', {}); From c845f0bc4f2a51fe681a271845f22f5673725b69 Mon Sep 17 00:00:00 2001 From: Robin Baum Date: Mon, 16 Mar 2020 11:27:45 +0100 Subject: [PATCH 062/414] feat(tsd-test-setup): add & setup dtslint (#11879) --- package-lock.json | 360 +++++++++++++++++++++++++++++++++++++++ package.json | 3 +- types/test/index.d.ts | 3 + types/test/tsconfig.json | 1 - 4 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 types/test/index.d.ts diff --git a/package-lock.json b/package-lock.json index 23e8341ccb39..05accbc7b516 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1126,6 +1126,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/parsimmon": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@types/parsimmon/-/parsimmon-1.10.1.tgz", + "integrity": "sha512-MoF2IC9oGSgArJwlxdst4XsvWuoYfNUWtBw0kpnCi6K05kV+Ecl7siEeJ40tgCbI9uqEMGQL/NlPMRv6KVkY5Q==", + "dev": true + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -1824,6 +1830,12 @@ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", "dev": true }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -2258,6 +2270,12 @@ "delayed-stream": "~1.0.0" } }, + "command-exists": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz", + "integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==", + "dev": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -2690,6 +2708,16 @@ } } }, + "definitelytyped-header-parser": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.8.2.tgz", + "integrity": "sha512-kQePPP/cqQX3H6DrX5nCo2vMjJeboPsjEG8OOl43TZbTOr9zLlapWJ/oRCLnMCiyERsBRZXyLMtBXGM+1zmtgQ==", + "dev": true, + "requires": { + "@types/parsimmon": "^1.3.0", + "parsimmon": "^1.2.0" + } + }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -2830,6 +2858,240 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, + "dts-critic": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-2.2.4.tgz", + "integrity": "sha512-yGHhVKo66iyPBFUYRyXX1uW+XEG3/HDP1pHCR3VlPl9ho8zRHy6lzS5k+gCSuINqjNsV8UjZSUXUuTuw0wHp7g==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "definitelytyped-header-parser": "^3.8.2", + "semver": "^6.2.0", + "yargs": "^12.0.5" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "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" + } + }, + "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 + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "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.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "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 + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "dtslint": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-2.0.5.tgz", + "integrity": "sha512-73Rixb/W7VbxUb0PPSyAPT63dmaudA4JxcA+NWJvBd+d03kl9j2BW3Ldzz0+SlhWkYRyrJtp2D9IXURNLaWRpw==", + "dev": true, + "requires": { + "definitelytyped-header-parser": "3.8.2", + "dts-critic": "^2.2.4", + "fs-extra": "^6.0.1", + "request": "^2.88.0", + "strip-json-comments": "^2.0.1", + "tslint": "5.14.0", + "typescript": "^3.8.0-dev.20200125" + }, + "dependencies": { + "fs-extra": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "typescript": { + "version": "3.8.0-dev.20200125", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.0-dev.20200125.tgz", + "integrity": "sha512-OuNrOBdkFIZ00TyNbuT+How+3Z4ENfFYA53J/vOk0HQ+J5RrCknd00PE5qwTczyq9gBsGrJQgvA34YIxNCfafA==", + "dev": true + } + } + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -5023,6 +5285,12 @@ "loose-envify": "^1.0.0" } }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -5757,6 +6025,15 @@ "readable-stream": "^2.0.5" } }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, "lcov-result-merger": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-3.1.0.tgz", @@ -6316,6 +6593,15 @@ } } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -6491,6 +6777,25 @@ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + } + } + }, "meow": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", @@ -11035,6 +11340,17 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, "os-name": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", @@ -11188,6 +11504,12 @@ "@types/node": "*" } }, + "parsimmon": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.13.0.tgz", + "integrity": "sha512-5UIrOCW+gjbILkjKPgTgmq8LKf8TT3Iy7kN2VD7OtQ81facKn8B4gG1X94jWqXYZsxG2KbJhrv/Yq/5H6BQn7A==", + "dev": true + }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -13338,6 +13660,44 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, + "tslint": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", + "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index a94f72192911..0b17c05e27a4 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "chai-spies": "^1.x", "cls-hooked": "^4.2.2", "cross-env": "^5.2.1", + "dtslint": "^2.0.5", "env-cmd": "^8.0.2", "esdoc": "^1.1.0", "esdoc-inject-style-plugin": "^1.0.0", @@ -150,7 +151,7 @@ "test-postgresn": "npm run test-postgres-native", "test-mssql": "cross-env DIALECT=mssql npm test", "test-all": "npm run test-mariadb && npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql", - "test-typings": "tsc -b types/tsconfig.json && tsc -b types/test/tsconfig.json", + "test-typings": "tsc -b types/tsconfig.json && dtslint --expectOnly types/test", "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha --require scripts/mocha-bootload -t 30000 --exit \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha --require scripts/mocha-bootload -t 30000 --exit \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", diff --git a/types/test/index.d.ts b/types/test/index.d.ts new file mode 100644 index 000000000000..c6b77f4ff9b4 --- /dev/null +++ b/types/test/index.d.ts @@ -0,0 +1,3 @@ +// Controls typescript version for testing the types + +// TypeScript Version: 3.6 diff --git a/types/test/tsconfig.json b/types/test/tsconfig.json index b14c71d38043..843200282732 100644 --- a/types/test/tsconfig.json +++ b/types/test/tsconfig.json @@ -9,7 +9,6 @@ "sequelize": ["../"], "sequelize/*": ["../"] }, - "types": ["node"], "lib": ["es2016"] }, "include": ["../index.d.ts", "./**/sequelize.d.ts", "./**/*.ts"] From 5822bc793abe1cffd6220ba7888a5f925c51cb02 Mon Sep 17 00:00:00 2001 From: Sushant Date: Thu, 19 Mar 2020 15:15:19 +0530 Subject: [PATCH 063/414] build: support for postgres-12 --- docker-compose.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8e04c61616f8..79635d9b10b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,9 +31,19 @@ services: POSTGRES_PASSWORD: sequelize_test POSTGRES_DB: sequelize_test ports: - - "8991:5432" + - "8991:5432" container_name: postgres-10 + postgres-12: + image: sushantdhiman/postgres:12 + environment: + POSTGRES_USER: sequelize_test + POSTGRES_PASSWORD: sequelize_test + POSTGRES_DB: sequelize_test + ports: + - "8992:5432" + container_name: postgres-12 + # MariaDB mariadb-103: image: mariadb:10.3 From c66663ed70d80a5cf661f99a2139de7c927c5ffe Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 27 Mar 2020 20:05:36 -0700 Subject: [PATCH 064/414] fix(typings): fn is assignable to where (#12040) --- types/lib/model.d.ts | 2 +- types/test/where.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 7ce2734b4688..ddc8be6e907e 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -110,7 +110,7 @@ export interface ScopeOptions { /** * The type accepted by every `where` option */ -export type WhereOptions = WhereAttributeHash | AndOperator | OrOperator | Literal | Where; +export type WhereOptions = WhereAttributeHash | AndOperator | OrOperator | Literal | Fn | Where; /** * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` diff --git a/types/test/where.ts b/types/test/where.ts index 52f998437105..76b499b05aae 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -291,6 +291,8 @@ where = whereFn('test', { // Literal as where where = literal('true') +where = fn('LOWER', 'asd') + MyModel.findAll({ where: literal('true') }) From c071018ab6b283bcb42b929506a71d5c93a64073 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Fri, 3 Apr 2020 23:40:45 -0700 Subject: [PATCH 065/414] feat(sequelize): handle query string host value (#12041) --- lib/sequelize.js | 7 +++++++ test/unit/configuration.test.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/lib/sequelize.js b/lib/sequelize.js index e645d8c7bc9f..2f9c7734ecef 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -211,6 +211,13 @@ class Sequelize { } if (urlParts.query) { + // Allow host query argument to override the url host. + // Enables specifying domain socket hosts which cannot be specified via the typical + // host part of a url. + if (urlParts.query.host) { + options.host = urlParts.query.host; + } + if (options.dialectOptions) Object.assign(options.dialectOptions, urlParts.query); else diff --git a/test/unit/configuration.test.js b/test/unit/configuration.test.js index fbe858f521d3..b2882c698ec5 100644 --- a/test/unit/configuration.test.js +++ b/test/unit/configuration.test.js @@ -179,5 +179,12 @@ describe('Sequelize', () => { expect(dialectOptions.application_name).to.equal('client'); expect(dialectOptions.ssl).to.equal('true'); }); + + it('should use query string host if specified', () => { + const sequelize = new Sequelize('mysql://localhost:9821/dbname?host=example.com'); + + const options = sequelize.options; + expect(options.host).to.equal('example.com'); + }); }); }); From 45570b2811275617595b68d4b1df40da9ec66f38 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 4 Apr 2020 14:35:41 +0530 Subject: [PATCH 066/414] build: update dependencies (#12060) --- package-lock.json | 3465 ++++++++++++++++----------- package.json | 20 +- test/integration/hooks/find.test.js | 2 +- 3 files changed, 2033 insertions(+), 1454 deletions(-) diff --git a/package-lock.json b/package-lock.json index 05accbc7b516..f819fbeab861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,22 +14,23 @@ } }, "@babel/core": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz", - "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", + "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", - "@babel/helpers": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.0", + "@babel/parser": "^7.9.0", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", - "json5": "^2.1.0", + "json5": "^2.1.2", "lodash": "^4.17.13", "resolve": "^1.3.2", "semver": "^5.4.1", @@ -45,12 +46,12 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", + "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { - "@babel/types": "^7.8.3", + "@babel/types": "^7.9.0", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -84,6 +85,70 @@ "@babel/types": "^7.8.3" } }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-replace-supers": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, "@babel/helper-split-export-declaration": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", @@ -93,25 +158,31 @@ "@babel/types": "^7.8.3" } }, + "@babel/helper-validator-identifier": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "dev": true + }, "@babel/helpers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.3.tgz", - "integrity": "sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", + "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", "dev": true, "requires": { "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0" } }, "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", "dev": true, "requires": { + "@babel/helper-validator-identifier": "^7.9.0", "chalk": "^2.0.0", - "esutils": "^2.0.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -124,51 +195,51 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", + "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", "dev": true }, "@babel/runtime": { - "version": "7.7.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.6.tgz", - "integrity": "sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", "dev": true } } }, "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.9.0", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -183,12 +254,12 @@ } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { - "esutils": "^2.0.2", + "@babel/helper-validator-identifier": "^7.9.0", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" }, @@ -535,15 +606,24 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.0.tgz", + "integrity": "sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0" + } + }, "@octokit/endpoint": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.1.tgz", - "integrity": "sha512-nBFhRUb5YzVTCX/iAK1MgQ4uWo89Gu0TH00qQHoYRCsE12dWcG1OiLd7v2EIo2+tpUKPMOQ62QFy9hy9Vg2ULg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.0.tgz", + "integrity": "sha512-3nx+MEYoZeD0uJ+7F/gvELLvQJzLXhep2Az0bBSXagbApDvDW0LWwpnAIY/hb0Jwe17A0fJdz0O12dPh05cj7A==", "dev": true, "requires": { "@octokit/types": "^2.0.0", "is-plain-object": "^3.0.0", - "universal-user-agent": "^4.0.0" + "universal-user-agent": "^5.0.0" }, "dependencies": { "is-plain-object": { @@ -560,25 +640,70 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", "dev": true + }, + "universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } } } }, + "@octokit/plugin-paginate-rest": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz", + "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.1" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz", + "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==", + "dev": true + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz", + "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.1", + "deprecation": "^2.3.1" + } + }, "@octokit/request": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.3.1.tgz", - "integrity": "sha512-5/X0AL1ZgoU32fAepTfEoggFinO3rxsMLtzhlUX+RctLrusn/CApJuGFCd0v7GMFhF+8UiCsTTfsu7Fh1HnEJg==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.3.4.tgz", + "integrity": "sha512-qyj8G8BxQyXjt9Xu6NvfvOr1E0l35lsXtwm3SopsYg/JWXjlsnwqLc8rsD2OLguEL/JjLfBvrXr4az7z8Lch2A==", "dev": true, "requires": { - "@octokit/endpoint": "^5.5.0", - "@octokit/request-error": "^1.0.1", + "@octokit/endpoint": "^6.0.0", + "@octokit/request-error": "^2.0.0", "@octokit/types": "^2.0.0", "deprecation": "^2.0.0", "is-plain-object": "^3.0.0", "node-fetch": "^2.3.0", "once": "^1.4.0", - "universal-user-agent": "^4.0.0" + "universal-user-agent": "^5.0.0" }, "dependencies": { + "@octokit/request-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.0.tgz", + "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, "is-plain-object": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", @@ -593,13 +718,22 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", "dev": true + }, + "universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } } } }, "@octokit/request-error": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.0.tgz", - "integrity": "sha512-DNBhROBYjjV/I9n7A8kVkmQNkqFAMem90dSxqvPq57e2hBr7mNTX98y3R2zDpqMQHVRpBDjsvsfIGgBzy+4PAg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", "dev": true, "requires": { "@octokit/types": "^2.0.0", @@ -608,11 +742,15 @@ } }, "@octokit/rest": { - "version": "16.37.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.37.0.tgz", - "integrity": "sha512-qLPK9FOCK4iVpn6ghknNuv/gDDxXQG6+JBQvoCwWjQESyis9uemakjzN36nvvp8SCny7JuzHI2RV8ChbV5mYdQ==", + "version": "16.43.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz", + "integrity": "sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==", "dev": true, "requires": { + "@octokit/auth-token": "^2.4.0", + "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "2.4.0", "@octokit/request": "^5.2.0", "@octokit/request-error": "^1.0.2", "atob-lite": "^2.0.0", @@ -628,9 +766,9 @@ } }, "@octokit/types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.0.2.tgz", - "integrity": "sha512-StASIL2lgT3TRjxv17z9pAqbnI7HGu9DrJlg3sEBFfCLaMEqp+O3IQPUF6EZtQ4xkAu2ml6kMBBCtGxjvmtmuQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.5.1.tgz", + "integrity": "sha512-q4Wr7RexkPRrkQpXzUYF5Fj/14Mr65RyOHj6B9d/sQACpqGcStkHZj4qMEtlMY5SnD/69jlL9ItGPbDM0dR/dA==", "dev": true, "requires": { "@types/node": ">= 8" @@ -679,9 +817,9 @@ "dev": true }, "@semantic-release/github": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-6.0.1.tgz", - "integrity": "sha512-4/xMKFe7svbv5ltvBxoqPY8fBSPyllVtnf2RMHaddeRKC8C/7FqakwRDmui7jgC3alVrVsRtz/jdTdZjB4J28Q==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-6.0.2.tgz", + "integrity": "sha512-tBE8duwyOB+FXetHucl5wCOlZhNPHN1ipQENxN6roCz22AYYRLRaVYNPjo78F+KNJpb+Gy8DdudH78Qc8VhKtQ==", "dev": true, "requires": { "@octokit/rest": "^16.27.0", @@ -692,7 +830,7 @@ "dir-glob": "^3.0.0", "fs-extra": "^8.0.0", "globby": "^10.0.0", - "http-proxy-agent": "^3.0.0", + "http-proxy-agent": "^4.0.0", "https-proxy-agent": "^4.0.0", "issue-parser": "^6.0.0", "lodash": "^4.17.4", @@ -1033,9 +1171,9 @@ } }, "@sinonjs/commons": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.6.0.tgz", - "integrity": "sha512-w4/WHG7C4WWFyE5geCieFJF6MZkbW4VAriol5KlmQXpAQdxvV0p26sqNZOW6Qyw6Y0l9K4g+cHvvczR2sEEpqg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", + "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -1068,10 +1206,16 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@tootallnate/once": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", + "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "dev": true + }, "@types/bluebird": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz", - "integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==", + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.30.tgz", + "integrity": "sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw==", "dev": true }, "@types/color-name": { @@ -1110,9 +1254,9 @@ "dev": true }, "@types/node": { - "version": "12.12.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.17.tgz", - "integrity": "sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA==" + "version": "12.12.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", + "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -1193,9 +1337,9 @@ } }, "acorn-jsx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", - "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true }, "adal-node": { @@ -1224,10 +1368,13 @@ } }, "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "dev": true, + "requires": { + "debug": "4" + } }, "aggregate-error": { "version": "3.0.1", @@ -1248,12 +1395,12 @@ } }, "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" @@ -1266,18 +1413,26 @@ "dev": true }, "ansi-escapes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", - "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } } }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { @@ -1397,21 +1552,6 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -1458,9 +1598,9 @@ "dev": true }, "async": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz", - "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", "dev": true }, "async-hook-jl": { @@ -1497,9 +1637,9 @@ "dev": true }, "aws4": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", - "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "dev": true }, "babel-code-frame": { @@ -1513,6 +1653,12 @@ "js-tokens": "^3.0.2" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -1532,6 +1678,15 @@ "supports-color": "^2.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -2006,9 +2161,9 @@ } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "ci-info": { @@ -2074,6 +2229,12 @@ "string-width": "^1.0.1" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -2099,6 +2260,15 @@ "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } } } }, @@ -2109,65 +2279,14 @@ "dev": true }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - } + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, "clone": { @@ -2304,6 +2423,12 @@ "dot-prop": "^3.0.0" } }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -2355,15 +2480,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } } } }, @@ -2390,17 +2506,6 @@ "split2": "^2.0.0", "through2": "^3.0.0", "trim-off-newlines": "^1.0.0" - }, - "dependencies": { - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } - } } }, "convert-source-map": { @@ -2461,38 +2566,81 @@ } }, "cross-env": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", - "integrity": "sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.5" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "cross-spawn": "^7.0.1" }, "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true - } - } - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, @@ -2718,20 +2866,6 @@ "parsimmon": "^1.2.0" } }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "dev": true, - "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2870,224 +3004,32 @@ "yargs": "^12.0.5" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "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" - } - }, - "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 - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "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.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "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 - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "dtslint": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-2.0.5.tgz", - "integrity": "sha512-73Rixb/W7VbxUb0PPSyAPT63dmaudA4JxcA+NWJvBd+d03kl9j2BW3Ldzz0+SlhWkYRyrJtp2D9IXURNLaWRpw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-2.0.6.tgz", + "integrity": "sha512-lvuwW8ZSJOIP5Ipm2hGGZ/t1urFwzIwaI46aPxqZ8GgMpdxbzr2iBjNwews+mJygkmR04TT8HnfCa1vcggMIMQ==", "dev": true, "requires": { "definitelytyped-header-parser": "3.8.2", "dts-critic": "^2.2.4", "fs-extra": "^6.0.1", - "request": "^2.88.0", "strip-json-comments": "^2.0.1", "tslint": "5.14.0", - "typescript": "^3.8.0-dev.20200125" + "typescript": "^3.9.0-dev.20200404" }, "dependencies": { - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, "typescript": { - "version": "3.8.0-dev.20200125", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.0-dev.20200125.tgz", - "integrity": "sha512-OuNrOBdkFIZ00TyNbuT+How+3Z4ENfFYA53J/vOk0HQ+J5RrCknd00PE5qwTczyq9gBsGrJQgvA34YIxNCfafA==", + "version": "3.9.0-dev.20200404", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200404.tgz", + "integrity": "sha512-GpLX1PSRP5JPhgecvg5/8Eqw8FO/h9Yh3pGqcsrAWlc1w12zVBzAXKhpzbNmHrxqO/ftXyXnDI9ftjBcfYHYag==", "dev": true } } @@ -3169,9 +3111,9 @@ "dev": true }, "env-ci": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.1.tgz", - "integrity": "sha512-xXgohoOAFFF1Y3EdsSKP7olyH/DLS6ZD3aglV6mDFAXBqBXLJSsZLrOZdYfDs5mOmgNaP3YYynObzwF3QkC24g==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.2.tgz", + "integrity": "sha512-Xc41mKvjouTXD3Oy9AqySz1IeyvJvHZ20Twf5ZLYbNpPPIuCnL/qHCmNlD01LoNy0JTunw9HPYVptD19Ac7Mbw==", "dev": true, "requires": { "execa": "^4.0.0", @@ -3263,53 +3205,104 @@ } }, "env-cmd": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-8.0.2.tgz", - "integrity": "sha512-gHX8MnQXw1iS7dc2KeJdBdxca7spIkxkNwIuORLwm8kDg6xHh5wWnv1Yv3pc64nLZR6kufQSCmwTz16sRmd/rg==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.5" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.3.tgz", - "integrity": "sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-error": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", @@ -3328,26 +3321,19 @@ "dev": true }, "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", "dev": true, "optional": true, "requires": { - "esprima": "^3.1.3", + "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true, - "optional": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3376,11 +3362,28 @@ "taffydb": "2.7.3" }, "dependencies": { + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "marked": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true } } }, @@ -3738,9 +3741,9 @@ "dev": true }, "globals": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", - "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", "dev": true, "requires": { "type-fest": "^0.8.1" @@ -3760,6 +3763,12 @@ "requires": { "ansi-regex": "^4.1.0" } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true } } }, @@ -3788,12 +3797,24 @@ } }, "eslint-plugin-mocha": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz", - "integrity": "sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", + "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", "dev": true, "requires": { - "ramda": "^0.26.1" + "eslint-utils": "^2.0.0", + "ramda": "^0.27.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } } }, "eslint-scope": { @@ -3822,22 +3843,14 @@ "dev": true }, "espree": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", - "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^7.1.0", - "acorn-jsx": "^5.1.0", + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", - "dev": true - } } }, "esprima": { @@ -3847,12 +3860,20 @@ "dev": true }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", + "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "^5.0.0" + }, + "dependencies": { + "estraverse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", + "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "dev": true + } } }, "esrecurse": { @@ -4051,22 +4072,23 @@ "dev": true }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, "fast-glob": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz", - "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", + "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.0", "merge2": "^1.3.0", - "micromatch": "^4.0.2" + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" }, "dependencies": { "braces": { @@ -4115,9 +4137,9 @@ } }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { @@ -4127,18 +4149,18 @@ "dev": true }, "fastq": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", - "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz", + "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==", "dev": true, "requires": { - "reusify": "^1.0.0" + "reusify": "^1.0.4" } }, "figures": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", - "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -4177,13 +4199,13 @@ } }, "find-cache-dir": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", - "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^3.0.0", + "make-dir": "^3.0.2", "pkg-dir": "^4.1.0" }, "dependencies": { @@ -4305,9 +4327,9 @@ } }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, "flush-write-stream": { @@ -4320,12 +4342,6 @@ "readable-stream": "^2.3.6" } }, - "fn-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", - "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=", - "dev": true - }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4428,9 +4444,9 @@ "dev": true }, "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -4465,6 +4481,18 @@ "requires": { "graceful-fs": "^4.1.11", "through2": "^2.0.3" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "fs.realpath": { @@ -4485,17 +4513,6 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "g-status": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/g-status/-/g-status-2.0.2.tgz", - "integrity": "sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "matcher": "^1.0.0", - "simple-git": "^1.85.0" - } - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -4512,6 +4529,12 @@ "wide-align": "^1.1.0" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -4531,6 +4554,15 @@ "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } } } }, @@ -4550,9 +4582,9 @@ "dev": true }, "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "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 }, "get-func-name": { @@ -4619,6 +4651,16 @@ "requires": { "through2": "~2.0.0" } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } } } }, @@ -4633,17 +4675,6 @@ "meow": "^5.0.0", "split2": "^2.0.0", "through2": "^3.0.0" - }, - "dependencies": { - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } - } } }, "glob": { @@ -4661,9 +4692,9 @@ } }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -4723,27 +4754,6 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -4763,15 +4773,16 @@ "dev": true }, "handlebars": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.2.tgz", - "integrity": "sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "dev": true, "requires": { + "minimist": "^1.2.5", "neo-async": "^2.6.0", - "optimist": "^0.6.1", "source-map": "^0.6.1", - "uglify-js": "^3.1.4" + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" }, "dependencies": { "source-map": { @@ -4814,6 +4825,14 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "has-flag": { @@ -4867,9 +4886,9 @@ } }, "hasha": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.1.0.tgz", - "integrity": "sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", "dev": true, "requires": { "is-stream": "^2.0.0", @@ -4897,15 +4916,15 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", - "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "html-escaper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", - "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, "htmlparser2": { @@ -4923,9 +4942,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -4936,12 +4955,13 @@ } }, "http-proxy-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-3.0.0.tgz", - "integrity": "sha512-uGuJaBWQWDQCJI5ip0d/VTYZW0nRrlLWXA4A7P1jrsa+f77rW2yXz315oBt6zGCF6l8C2tlMxY7ffULCj+5FhA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "agent-base": "5", + "@tootallnate/once": "1", + "agent-base": "6", "debug": "4" } }, @@ -4964,6 +4984,14 @@ "requires": { "agent-base": "5", "debug": "4" + }, + "dependencies": { + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + } } }, "human-signals": { @@ -4973,81 +5001,102 @@ "dev": true }, "husky": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", - "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz", + "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", "dev": true, "requires": { - "cosmiconfig": "^5.0.7", - "execa": "^1.0.0", - "find-up": "^3.0.0", - "get-stdin": "^6.0.0", - "is-ci": "^2.0.0", - "pkg-dir": "^3.0.0", - "please-upgrade-node": "^3.1.1", - "read-pkg": "^4.0.1", - "run-node": "^1.0.0", - "slash": "^2.0.0" + "chalk": "^3.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.5.1", + "cosmiconfig": "^6.0.0", + "find-versions": "^3.2.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.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==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "locate-path": { + "chalk": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "color-name": "~1.1.4" } }, - "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, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, "requires": { - "p-limit": "^2.0.0" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, - "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==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "read-pkg": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", - "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "normalize-package-data": "^2.3.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -5229,39 +5278,106 @@ "dev": true }, "inquirer": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz", - "integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", + "chalk": "^3.0.0", "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.15", "mute-stream": "0.0.8", - "run-async": "^2.2.0", + "run-async": "^2.4.0", "rxjs": "^6.5.3", "string-width": "^4.1.0", - "strip-ansi": "^5.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -5274,6 +5390,14 @@ "requires": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" + }, + "dependencies": { + "p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + } } }, "invariant": { @@ -5334,20 +5458,11 @@ "dev": true }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "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", @@ -5369,9 +5484,9 @@ } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, "is-descriptor": { @@ -5412,18 +5527,15 @@ "dev": true }, "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-glob": { @@ -5476,30 +5588,6 @@ "symbol-observable": "^1.1.0" } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -5528,12 +5616,12 @@ "dev": true }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has": "^1.0.1" + "has": "^1.0.3" } }, "is-regexp": { @@ -5661,9 +5749,9 @@ } }, "istanbul-lib-instrument": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.0.tgz", - "integrity": "sha512-Nm4wVHdo7ZXSG30KjZ2Wl5SU/Bw7bDx1PdaiIFzEStdjs0H12mOTncn1GVYuqQSaZxpg87VGBRsVRPGD2cD1AQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", + "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", "dev": true, "requires": { "@babel/core": "^7.7.5", @@ -5725,9 +5813,9 @@ "dev": true }, "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -5807,9 +5895,9 @@ } }, "istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -5933,18 +6021,18 @@ "dev": true }, "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", + "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", "dev": true, "requires": { - "minimist": "^1.2.0" + "minimist": "^1.2.5" } }, "jsonc-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.0.tgz", - "integrity": "sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", "dev": true }, "jsonfile": { @@ -5975,9 +6063,9 @@ } }, "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, "jwa": { @@ -6002,9 +6090,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "klaw": { @@ -6043,6 +6131,18 @@ "through2": "^2.0.3", "vinyl": "^2.1.0", "vinyl-fs": "^3.0.2" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "lead": { @@ -6080,44 +6180,310 @@ } }, "lint-staged": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.2.1.tgz", - "integrity": "sha512-n0tDGR/rTCgQNwXnUf/eWIpPNddGWxC32ANTNYsj2k02iZb7Cz5ox2tytwBu+2r0zDXMEMKw7Y9OD/qsav561A==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.1.tgz", + "integrity": "sha512-wAeu/ePaBAOfwM2+cVbgPWDtn17B0Sxiv0NvNEqDAIvB8Yhvl60vafKFiK4grcYn87K1iK+a0zVoETvKbdT9/Q==", "dev": true, "requires": { - "chalk": "^2.3.1", - "commander": "^2.14.1", - "cosmiconfig": "^5.2.0", - "debug": "^3.1.0", + "chalk": "^3.0.0", + "commander": "^4.0.1", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", "dedent": "^0.7.0", - "del": "^3.0.0", - "execa": "^1.0.0", - "g-status": "^2.0.2", - "is-glob": "^4.0.0", - "is-windows": "^1.0.2", - "listr": "^0.14.2", - "listr-update-renderer": "^0.5.0", - "lodash": "^4.17.11", - "log-symbols": "^2.2.0", - "micromatch": "^3.1.8", - "npm-which": "^3.0.1", - "p-map": "^1.1.1", - "path-is-inside": "^1.0.2", - "pify": "^3.0.0", - "please-upgrade-node": "^3.0.2", - "staged-git-files": "1.1.2", - "string-argv": "^0.0.2", - "stringify-object": "^3.2.2", - "yup": "^0.27.0" + "execa": "^3.4.0", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", + "stringify-object": "^3.3.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "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.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "ms": "^2.1.1" + "isexe": "^2.0.0" } } } @@ -6137,14 +6503,6 @@ "listr-verbose-renderer": "^0.5.0", "p-map": "^2.0.0", "rxjs": "^6.3.3" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } } }, "listr-silent-renderer": { @@ -6169,6 +6527,12 @@ "strip-ansi": "^3.0.1" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -6207,6 +6571,15 @@ "chalk": "^1.0.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -6527,6 +6900,16 @@ "onetime": "^2.0.0", "signal-exit": "^3.0.2" } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + } } } }, @@ -6577,9 +6960,9 @@ "dev": true }, "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { "semver": "^6.0.0" @@ -6624,29 +7007,23 @@ } }, "mariadb": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.1.5.tgz", - "integrity": "sha512-uRHtjc0bg+Rt91LvIaZNo8tLcYs9YPiiW9Mke/BavFzg0MnctJBDkqZW9X59NyrRGDd7Oz9UshTWgmUpfDZc0Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.3.1.tgz", + "integrity": "sha512-suv+ygoiS+tQSKmxgzJsGV9R+USN8g6Ql+GuMo9k7alD6FxOT/lwebLHy63/7yPZfVtlyAitK1tPd7ZoFhN/Sg==", "dev": true, "requires": { "@types/geojson": "^7946.0.7", - "@types/node": "^13.1.4", + "@types/node": ">=8.0.0", "denque": "^1.4.1", - "iconv-lite": "^0.5.0", + "iconv-lite": "^0.5.1", "long": "^4.0.0", "moment-timezone": "^0.5.27" }, "dependencies": { - "@types/node": { - "version": "13.1.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.8.tgz", - "integrity": "sha512-6XzyyNM9EKQW4HKuzbo/CkOIjn/evtCmsU+MUM1xDfJ+3/rNjBttM1NgN7AOQvN6tP1Sl1D1PIKMreTArnxM9A==", - "dev": true - }, "iconv-lite": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz", - "integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", + "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -6735,9 +7112,9 @@ "dev": true }, "marked": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", - "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", "dev": true }, "marked-terminal": { @@ -6762,15 +7139,6 @@ } } }, - "matcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz", - "integrity": "sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.4" - } - }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -6786,14 +7154,6 @@ "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" - }, - "dependencies": { - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - } } }, "meow": { @@ -6853,18 +7213,18 @@ "dev": true }, "mime-db": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", - "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", "dev": true }, "mime-types": { - "version": "2.1.25", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", - "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "dev": true, "requires": { - "mime-db": "1.42.0" + "mime-db": "1.43.0" } }, "mimic-fn": { @@ -6883,9 +7243,9 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "minimist-options": { @@ -6939,26 +7299,18 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } + "minimist": "^1.2.5" } }, "mocha": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", - "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -6973,7 +7325,7 @@ "js-yaml": "3.13.1", "log-symbols": "2.2.0", "minimatch": "3.0.4", - "mkdirp": "0.5.1", + "mkdirp": "0.5.4", "ms": "2.1.1", "node-environment-flags": "1.0.5", "object.assign": "4.1.0", @@ -6981,17 +7333,34 @@ "supports-color": "6.0.0", "which": "1.3.1", "wide-align": "1.1.3", - "yargs": "13.3.0", - "yargs-parser": "13.1.1", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", "yargs-unparser": "1.6.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -7001,6 +7370,12 @@ "ms": "^2.1.1" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -7010,6 +7385,12 @@ "locate-path": "^3.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", @@ -7034,6 +7415,15 @@ "path-exists": "^3.0.0" } }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -7041,9 +7431,9 @@ "dev": true }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -7064,12 +7454,32 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, "supports-color": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", @@ -7079,10 +7489,39 @@ "has-flag": "^3.0.0" } }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "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.1.2" + } + }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -7103,9 +7542,9 @@ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moment-timezone": { - "version": "0.5.27", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz", - "integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==", + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "requires": { "moment": ">= 2.9.0" } @@ -7138,9 +7577,9 @@ }, "dependencies": { "iconv-lite": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz", - "integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", + "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -7213,9 +7652,9 @@ "dev": true }, "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", + "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", "dev": true, "requires": { "debug": "^3.2.6", @@ -7253,16 +7692,27 @@ "dev": true }, "nise": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.2.tgz", - "integrity": "sha512-/6RhOUlicRCbE9s+94qCUsyE+pKlVJ5AhIv+jEE7ESKwnbXqulKZ1FYU+XAtHHWE9TinYvAxDUJAb912PwPoWA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", "dev": true, "requires": { "@sinonjs/formatio": "^3.2.1", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^4.1.0", + "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } } }, "node-emoji": { @@ -7334,9 +7784,9 @@ } }, "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "dev": true, "requires": { "abbrev": "1", @@ -7388,9 +7838,9 @@ } }, "npm": { - "version": "6.13.6", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.6.tgz", - "integrity": "sha512-NomC08kv7HIl1FOyLOe9Hp89kYsOsvx52huVIJ7i8hFW8Xp65lDwe/8wTIrh9q9SaQhA8hTrfXPh3BEL3TmMpw==", + "version": "6.14.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.4.tgz", + "integrity": "sha512-B8UDDbWvdkW6RgXFn8/h2cHJP/u/FPa4HWeGzW23aNEBARN3QPrRaHqPIZW2NSN3fW649gtgUDNZpaRs0zTMPw==", "dev": true, "requires": { "JSONStream": "^1.3.5", @@ -7399,12 +7849,12 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.6", + "bin-links": "^1.1.7", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", "call-limit": "^1.1.1", - "chownr": "^1.1.3", + "chownr": "^1.1.4", "ci-info": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.5.1", @@ -7421,10 +7871,10 @@ "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", "gentle-fs": "^2.3.0", - "glob": "^7.1.4", + "glob": "^7.1.6", "graceful-fs": "^4.2.3", "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.5", + "hosted-git-info": "^2.8.8", "iferr": "^1.0.2", "imurmurhash": "*", "infer-owner": "^1.0.4", @@ -7442,7 +7892,7 @@ "libnpmorg": "^1.0.1", "libnpmsearch": "^2.0.2", "libnpmteam": "^1.0.2", - "libnpx": "^10.2.0", + "libnpx": "^10.2.2", "lock-verify": "^2.1.0", "lockfile": "^1.0.4", "lodash._baseindexof": "*", @@ -7459,9 +7909,9 @@ "lru-cache": "^5.1.1", "meant": "~1.0.1", "mississippi": "^3.0.0", - "mkdirp": "~0.5.1", + "mkdirp": "^0.5.4", "move-concurrently": "^1.0.1", - "node-gyp": "^5.0.5", + "node-gyp": "^5.1.0", "nopt": "~4.0.1", "normalize-package-data": "^2.5.0", "npm-audit-report": "^1.3.2", @@ -7469,10 +7919,10 @@ "npm-install-checks": "^3.0.2", "npm-lifecycle": "^3.1.4", "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.7", + "npm-packlist": "^1.4.8", "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.3", "npm-user-validate": "~1.0.0", "npmlog": "~4.1.2", "once": "~1.4.0", @@ -7489,11 +7939,11 @@ "read-installed": "~4.0.3", "read-package-json": "^2.1.1", "read-package-tree": "^5.3.1", - "readable-stream": "^3.4.0", + "readable-stream": "^3.6.0", "readdir-scoped-modules": "^1.1.0", "request": "^2.88.0", "retry": "^0.12.0", - "rimraf": "^2.6.3", + "rimraf": "^2.7.1", "safe-buffer": "^5.1.2", "semver": "^5.7.1", "sha": "^3.0.0", @@ -7681,7 +8131,7 @@ } }, "bin-links": { - "version": "1.1.6", + "version": "1.1.7", "bundled": true, "dev": true, "requires": { @@ -7794,7 +8244,7 @@ } }, "chownr": { - "version": "1.1.3", + "version": "1.1.4", "bundled": true, "dev": true }, @@ -8100,7 +8550,7 @@ "dev": true }, "deep-extend": { - "version": "0.5.1", + "version": "0.6.0", "bundled": true, "dev": true }, @@ -8234,7 +8684,7 @@ } }, "env-paths": { - "version": "1.0.0", + "version": "2.2.0", "bundled": true, "dev": true }, @@ -8578,7 +9028,7 @@ } }, "get-caller-file": { - "version": "1.0.2", + "version": "1.0.3", "bundled": true, "dev": true }, @@ -8599,7 +9049,7 @@ } }, "glob": { - "version": "7.1.4", + "version": "7.1.6", "bundled": true, "dev": true, "requires": { @@ -8687,7 +9137,7 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.5", + "version": "2.8.8", "bundled": true, "dev": true }, @@ -8803,7 +9253,7 @@ } }, "invert-kv": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "dev": true }, @@ -8823,11 +9273,11 @@ "dev": true }, "is-ci": { - "version": "1.1.0", + "version": "1.2.1", "bundled": true, "dev": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "^1.5.0" }, "dependencies": { "ci-info": { @@ -8899,7 +9349,7 @@ } }, "is-retry-allowed": { - "version": "1.1.0", + "version": "1.2.0", "bundled": true, "dev": true }, @@ -8992,11 +9442,11 @@ "dev": true }, "lcid": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "^2.0.0" } }, "libcipm": { @@ -9169,7 +9619,7 @@ } }, "libnpx": { - "version": "10.2.0", + "version": "10.2.2", "bundled": true, "dev": true, "requires": { @@ -9320,17 +9770,34 @@ "ssri": "^6.0.0" } }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "meant": { "version": "1.0.1", "bundled": true, "dev": true }, "mem": { - "version": "1.1.0", + "version": "4.3.0", "bundled": true, "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "bundled": true, + "dev": true + } } }, "mime-db": { @@ -9346,11 +9813,6 @@ "mime-db": "~1.35.0" } }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -9359,11 +9821,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, "minizlib": { "version": "1.3.3", "bundled": true, @@ -9401,11 +9858,18 @@ } }, "mkdirp": { - "version": "0.5.1", + "version": "0.5.4", "bundled": true, "dev": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } } }, "move-concurrently": { @@ -9438,6 +9902,11 @@ "bundled": true, "dev": true }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, "node-fetch-npm": { "version": "2.0.2", "bundled": true, @@ -9449,36 +9918,21 @@ } }, "node-gyp": { - "version": "5.0.5", + "version": "5.1.0", "bundled": true, "dev": true, "requires": { - "env-paths": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", "tar": "^4.4.12", - "which": "1" - }, - "dependencies": { - "nopt": { - "version": "3.0.6", - "bundled": true, - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true - } + "which": "^1.3.1" } }, "nopt": { @@ -9578,12 +10032,13 @@ } }, "npm-packlist": { - "version": "1.4.7", + "version": "1.4.8", "bundled": true, "dev": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-pick-manifest": { @@ -9597,7 +10052,7 @@ } }, "npm-profile": { - "version": "4.0.2", + "version": "4.0.4", "bundled": true, "dev": true, "requires": { @@ -9607,7 +10062,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.2", + "version": "4.0.3", "bundled": true, "dev": true, "requires": { @@ -9699,13 +10154,41 @@ "dev": true }, "os-locale": { - "version": "2.1.0", + "version": "3.1.0", "bundled": true, "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "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" + } + } } }, "os-tmpdir": { @@ -9722,11 +10205,21 @@ "os-tmpdir": "^1.0.0" } }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, "p-finally": { "version": "1.0.0", "bundled": true, "dev": true }, + "p-is-promise": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, "p-limit": { "version": "1.2.0", "bundled": true, @@ -10004,18 +10497,18 @@ "dev": true }, "rc": { - "version": "1.2.7", + "version": "1.2.8", "bundled": true, "dev": true, "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { - "version": "1.2.0", + "version": "1.2.5", "bundled": true, "dev": true } @@ -10074,7 +10567,7 @@ } }, "readable-stream": { - "version": "3.4.0", + "version": "3.6.0", "bundled": true, "dev": true, "requires": { @@ -10095,7 +10588,7 @@ } }, "registry-auth-token": { - "version": "3.3.2", + "version": "3.4.0", "bundled": true, "dev": true, "requires": { @@ -10159,7 +10652,7 @@ "dev": true }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "dev": true, "requires": { @@ -10458,11 +10951,18 @@ } }, "string_decoder": { - "version": "1.2.0", + "version": "1.3.0", "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } } }, "stringify-package": { @@ -10771,7 +11271,7 @@ } }, "widest-line": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true, "requires": { @@ -10843,7 +11343,7 @@ "dev": true }, "yargs": { - "version": "11.0.0", + "version": "11.1.1", "bundled": true, "dev": true, "requires": { @@ -10851,7 +11351,7 @@ "decamelize": "^1.1.1", "find-up": "^2.1.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", @@ -10894,22 +11394,14 @@ "dev": true }, "npm-packlist": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", - "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npm-path": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", - "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", - "dev": true, - "requires": { - "which": "^1.2.10" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-run-path": { @@ -10921,17 +11413,6 @@ "path-key": "^2.0.0" } }, - "npm-which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", - "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", - "dev": true, - "requires": { - "commander": "^2.9.0", - "npm-path": "^2.0.2", - "which": "^1.2.10" - } - }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -10967,9 +11448,9 @@ "optional": true }, "nyc": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.0.tgz", - "integrity": "sha512-qcLBlNCKMDVuKb7d1fpxjPR8sHeMVX0CHarXAVzrVWoFrigCkYR8xcrjfXSPi5HXM7EU78L6ywO7w1c5rZNCNg==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.1.tgz", + "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==", "dev": true, "requires": { "@istanbuljs/load-nyc-config": "^1.0.0", @@ -10987,10 +11468,9 @@ "istanbul-lib-processinfo": "^2.0.2", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", - "js-yaml": "^3.13.1", + "istanbul-reports": "^3.0.2", "make-dir": "^3.0.0", - "node-preload": "^0.2.0", + "node-preload": "^0.2.1", "p-map": "^3.0.0", "process-on-spawn": "^1.0.0", "resolve-from": "^5.0.0", @@ -10998,7 +11478,6 @@ "signal-exit": "^3.0.2", "spawn-wrap": "^2.0.0", "test-exclude": "^6.0.0", - "uuid": "^3.3.3", "yargs": "^15.0.2" }, "dependencies": { @@ -11060,6 +11539,18 @@ "path-exists": "^4.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -11108,15 +11599,32 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" } }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -11138,9 +11646,9 @@ } }, "yargs": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", - "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -11153,13 +11661,13 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^16.1.0" + "yargs-parser": "^18.1.1" } }, "yargs-parser": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", - "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", + "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -11251,13 +11759,13 @@ "dev": true }, "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "object.pick": { @@ -11293,23 +11801,11 @@ "mimic-fn": "^2.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - } - } + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true }, "optionator": { "version": "0.8.3", @@ -11377,6 +11873,12 @@ "os-tmpdir": "^1.0.0" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, "p-each-series": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", @@ -11407,9 +11909,9 @@ "dev": true }, "p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, "p-limit": { @@ -11431,9 +11933,9 @@ } }, "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true }, "p-reduce": { @@ -11534,12 +12036,6 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -11591,15 +12087,16 @@ "dev": true }, "pg": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.14.0.tgz", - "integrity": "sha512-TLsdOWKFu44vHdejml4Uoo8h0EwCjdIj9Z9kpz7pA5i8iQxOTwVb1+Fy+X86kW5AXKxQpYpYDs4j/qPDbro/lg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.18.2.tgz", + "integrity": "sha512-Mvt0dGYMwvEADNKy5PMQGlzPudKcKKzJds/VbOeZJpb6f/pI3mmoXX0JksPgI3l3JPP/2Apq7F36O63J7mgveA==", "dev": true, "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-pool": "^2.0.7", + "pg-packet-stream": "^1.1.0", + "pg-pool": "^2.0.10", "pg-types": "^2.1.0", "pgpass": "1.x", "semver": "4.3.2" @@ -11634,10 +12131,16 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "dev": true }, + "pg-packet-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz", + "integrity": "sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg==", + "dev": true + }, "pg-pool": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz", - "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.10.tgz", + "integrity": "sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==", "dev": true }, "pg-types": { @@ -11663,9 +12166,9 @@ } }, "picomatch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", - "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, "pify": { @@ -11674,21 +12177,6 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", @@ -11700,49 +12188,49 @@ } }, "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "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": "^3.0.0" + "find-up": "^4.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==", + "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": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.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==", + "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": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "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==", + "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.0.0" + "p-limit": "^2.2.0" } }, "p-try": { @@ -11750,6 +12238,12 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true + }, + "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 } } }, @@ -11822,12 +12316,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "property-expr": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz", - "integrity": "sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==", - "dev": true - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -11835,9 +12323,9 @@ "dev": true }, "psl": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", - "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, "pump": { @@ -11898,9 +12386,9 @@ "dev": true }, "ramda": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", - "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", + "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", "dev": true }, "rc": { @@ -11920,12 +12408,6 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true } } }, @@ -11951,9 +12433,9 @@ } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12013,13 +12495,12 @@ "dev": true }, "registry-auth-token": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.0.tgz", - "integrity": "sha512-7uxS951DeOBOwsv8deX+l7HcjY2VZxaOgHtM6RKzg3HhpE+bJ0O7VbuMJLosC1T5WSFpHm0DuFIbqUl43jHpsA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", + "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", "dev": true, "requires": { - "rc": "^1.2.8", - "safe-buffer": "^5.0.1" + "rc": "^1.2.8" } }, "release-zalgo": { @@ -12050,6 +12531,18 @@ "remove-bom-buffer": "^3.0.0", "safe-buffer": "^5.1.0", "through2": "^2.0.3" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "remove-trailing-separator": { @@ -12086,9 +12579,9 @@ "dev": true }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -12098,7 +12591,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -12108,27 +12601,9 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - } } }, "require-directory": { @@ -12138,15 +12613,15 @@ "dev": true }, "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, "resolve": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", - "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -12228,20 +12703,14 @@ } }, "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", "dev": true, "requires": { "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 - }, "run-parallel": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", @@ -12249,9 +12718,9 @@ "dev": true }, "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -12285,9 +12754,9 @@ "dev": true }, "semantic-release": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-16.0.2.tgz", - "integrity": "sha512-KQmPGJvhB3qn49pFGnAuSm8txOV6nLWrUg/jQG+1CYfg7rrroVKqpZ9mmA6+PjfoFCroQ0Na7Ee5DuzHiR6e/A==", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-16.0.4.tgz", + "integrity": "sha512-qiYHTNStxUs0UUb45ImRIid0Z8HsXwMNbpZXLvABs725SrxtZBgfuemaABnHdKDg7KBsuQMlSdZENaYLvkMqUg==", "dev": true, "requires": { "@semantic-release/commit-analyzer": "^7.0.0", @@ -12437,6 +12906,12 @@ "path-exists": "^4.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", @@ -12447,14 +12922,20 @@ } }, "hosted-git-info": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.2.tgz", - "integrity": "sha512-ezZMWtHXm7Eb7Rq4Mwnx2vs79WUx2QmRg3+ZqeGroKzfDO+EprOcgRPYghsOP9JuYBfK18VojmRTGCg8Ma+ktw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", + "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", "dev": true, "requires": { "lru-cache": "^5.1.1" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -12477,9 +12958,9 @@ } }, "marked": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", - "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", + "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", "dev": true }, "micromatch": { @@ -12586,6 +13067,12 @@ "type-fest": "^0.8.1" } }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12601,6 +13088,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -12640,9 +13138,9 @@ } }, "yargs": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", - "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { "cliui": "^6.0.0", @@ -12655,13 +13153,13 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^16.1.0" + "yargs-parser": "^18.1.1" } }, "yargs-parser": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", - "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", + "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -12671,9 +13169,9 @@ } }, "semver": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.1.tgz", - "integrity": "sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A==" + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==" }, "semver-compare": { "version": "1.0.0", @@ -12711,9 +13209,9 @@ "dev": true }, "sequelize-pool": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-3.0.0.tgz", - "integrity": "sha512-BSA6gmba/cINu+cM2l/cKaTCgtrn6PBElk9dGq8U46vEPkaMTl7GDYlYfsRXztxtlAPCk62gdJ9GHj7H4gcXMg==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-3.1.0.tgz", + "integrity": "sha512-zhOVPYVKo61dQgGewyNOgH9mWIjdU6eglCQUenNFpAww/BMlj2Ci8paOKC6Mdt0yJl2HX08H37N4GzqP7Nsmcw==" }, "set-blocking": { "version": "2.0.0", @@ -12766,9 +13264,9 @@ "dev": true }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, "signale": { @@ -12793,15 +13291,6 @@ } } }, - "simple-git": { - "version": "1.128.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.128.0.tgz", - "integrity": "sha512-bi8ff+gA+GJgmskbkbLBuykkvsuWv0lPEXjCDQkUvr2DrOpsVcowk9BqqQAl8gQkiyLhzgFIKvirDiwWFBDMqg==", - "dev": true, - "requires": { - "debug": "^4.0.1" - } - }, "sinon": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", @@ -12818,15 +13307,15 @@ } }, "sinon-chai": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", - "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", + "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", "dev": true }, "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "slice-ansi": { @@ -12838,14 +13327,6 @@ "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } } }, "snapdragon": { @@ -12977,12 +13458,12 @@ "dev": true }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -13016,9 +13497,9 @@ }, "dependencies": { "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -13092,6 +13573,18 @@ "dev": true, "requires": { "through2": "^2.0.2" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "sprintf-js": { @@ -13140,12 +13633,6 @@ "integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=", "dev": true }, - "staged-git-files": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.2.tgz", - "integrity": "sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==", - "dev": true - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -13178,63 +13665,67 @@ } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, "string-argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", - "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", + "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, "string.prototype.trimleft": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", - "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" } }, "string.prototype.trimright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", - "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", + "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, "string_decoder": { @@ -13258,12 +13749,12 @@ } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { @@ -13291,9 +13782,9 @@ "dev": true }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "supports-color": { @@ -13336,12 +13827,6 @@ "dev": true, "optional": true }, - "synchronous-promise": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.10.tgz", - "integrity": "sha512-6PC+JRGmNjiG3kJ56ZMNWDPL8hjyghF5cMXIFOKg+NiwwEZZIvxTWd0pinWKyD227odg9ygF8xVhhz7gb8Uq7A==", - "dev": true - }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -13366,12 +13851,6 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -13433,9 +13912,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", - "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -13506,13 +13985,12 @@ "dev": true }, "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2 || 3" } }, "through2-filter": { @@ -13523,6 +14001,18 @@ "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "tmp": { @@ -13599,14 +14089,20 @@ "dev": true, "requires": { "through2": "^2.0.3" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=", - "dev": true - }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", @@ -13617,7 +14113,6 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, - "optional": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -13655,9 +14150,9 @@ "dev": true }, "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", "dev": true }, "tslint": { @@ -13744,9 +14239,9 @@ } }, "typescript": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", - "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, "uc.micro": { @@ -13756,9 +14251,9 @@ "dev": true }, "uglify-js": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.5.tgz", - "integrity": "sha512-GFZ3EXRptKGvb/C1Sq6nO1iI7AGcjyqmIyOw0DrD0675e+NNbGO72xmMM2iEBdFbxaTLo70NbjM/Wy54uZIlsg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", + "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", "dev": true, "optional": true, "requires": { @@ -13782,9 +14277,9 @@ "dev": true }, "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", "dev": true }, "union-value": { @@ -13819,9 +14314,9 @@ } }, "universal-user-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.0.tgz", - "integrity": "sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", + "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", "dev": true, "requires": { "os-name": "^3.1.0" @@ -13986,6 +14481,18 @@ "value-or-function": "^3.0.0", "vinyl": "^2.0.0", "vinyl-sourcemap": "^1.1.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "vinyl-sourcemap": { @@ -14035,6 +14542,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "dev": true + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -14042,39 +14555,6 @@ "dev": true, "requires": { "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } } }, "windows-release": { @@ -14087,9 +14567,9 @@ } }, "wkx": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", - "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", "requires": { "@types/node": "*" } @@ -14101,50 +14581,54 @@ "dev": true }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^2.0.0" } } } @@ -14165,9 +14649,9 @@ } }, "write-file-atomic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", - "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { "imurmurhash": "^0.1.4", @@ -14184,9 +14668,9 @@ "optional": true }, "xmldom": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.2.1.tgz", - "integrity": "sha512-kXXiYvmblIgEemGeB75y97FyaZavx6SQhGppLw5TKWAD2Wd0KAly0g23eVLh17YcpxZpnFym1Qk/eaRjy1APPg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", + "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", "dev": true }, "xpath.js": { @@ -14214,30 +14698,113 @@ "dev": true }, "yaml": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", - "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", + "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", "dev": true, "requires": { - "@babel/runtime": "^7.6.3" + "@babel/runtime": "^7.8.7" } }, "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "cliui": "^5.0.0", + "cliui": "^4.0.0", + "decamelize": "^1.2.0", "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", + "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", - "string-width": "^3.0.0", + "string-width": "^2.0.0", "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "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.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "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 + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" }, "dependencies": { "ansi-regex": { @@ -14252,6 +14819,17 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -14267,10 +14845,10 @@ "locate-path": "^3.0.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "locate-path": { @@ -14284,9 +14862,9 @@ } }, "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -14307,6 +14885,12 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -14327,10 +14911,39 @@ "ansi-regex": "^4.1.0" } }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "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.1.2" + } + }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -14338,40 +14951,6 @@ } } } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - }, - "yup": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.27.0.tgz", - "integrity": "sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ==", - "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "fn-name": "~2.0.1", - "lodash": "^4.17.11", - "property-expr": "^1.5.0", - "synchronous-promise": "^2.0.6", - "toposort": "^2.0.2" - } } } } diff --git a/package.json b/package.json index 0b17c05e27a4..fe7fd939084a 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,11 @@ "moment-timezone": "^0.5.21", "retry-as-promised": "^3.2.0", "semver": "^7.1.0", - "sequelize-pool": "^3.0.0", + "sequelize-pool": "^3.1.0", "toposort-class": "^1.0.1", "uuid": "^3.4.0", "validator": "^10.11.0", - "wkx": "^0.4.8" + "wkx": "^0.5.0" }, "devDependencies": { "@commitlint/cli": "^8.3.5", @@ -58,9 +58,9 @@ "chai-datetime": "^1.x", "chai-spies": "^1.x", "cls-hooked": "^4.2.2", - "cross-env": "^5.2.1", + "cross-env": "^7.0.2", "dtslint": "^2.0.5", - "env-cmd": "^8.0.2", + "env-cmd": "^10.1.0", "esdoc": "^1.1.0", "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", @@ -68,20 +68,20 @@ "eslint-plugin-jsdoc": "^20.4.0", "eslint-plugin-mocha": "^6.2.2", "fs-jetpack": "^2.2.3", - "husky": "^1.3.1", + "husky": "^4.2.3", "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", - "lint-staged": "^8.1.5", - "mariadb": "^2.1.5", + "lint-staged": "^10.1.1", + "mariadb": "^2.3.1", "markdownlint-cli": "^0.21.0", - "marked": "^0.8.0", + "marked": "^0.7.0", "mocha": "^6.1.4", "mysql2": "^1.6.5", "nyc": "^15.0.0", "pg": "^7.8.1", "pg-hstore": "^2.x", "pg-types": "^2.0.0", - "rimraf": "^2.6.3", + "rimraf": "^2.7.1", "semantic-release": "^16.0.2", "sinon": "^7.5.0", "sinon-chai": "^3.3.0", @@ -101,7 +101,7 @@ "object relational mapper" ], "options": { - "env_cmd": "./test/config/.docker.env" + "env_cmd": "-f ./test/config/.docker.env" }, "commitlint": { "extends": [ diff --git a/test/integration/hooks/find.test.js b/test/integration/hooks/find.test.js index 0fb2a5f0e6b9..8a3f625a8253 100644 --- a/test/integration/hooks/find.test.js +++ b/test/integration/hooks/find.test.js @@ -32,7 +32,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('allow changing attributes via beforeFind #5675', function() { this.User.beforeFind(options => { options.attributes = { - include: ['id'] + include: [['id', 'my_id']] }; }); return this.User.findAll({}); From cf0e46260f1ddcc56432097629283201e7f33768 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sat, 4 Apr 2020 23:40:05 -0500 Subject: [PATCH 067/414] refactor: remove bluebird Promise.map and chained .map usage (#12064) --- lib/dialects/mysql/query-interface.js | 4 +- lib/dialects/sqlite/query.js | 9 ++- lib/model.js | 66 +++++++++---------- lib/query-interface.js | 17 +++-- lib/sequelize.js | 2 +- package.json | 1 + test/integration/associations/has-one.test.js | 4 +- test/integration/include/findAll.test.js | 24 +++---- test/integration/model.test.js | 5 +- test/integration/model/create.test.js | 16 ++--- test/integration/model/findAll.test.js | 24 +++---- test/integration/model/findOne.test.js | 4 +- test/integration/model/scope/merge.test.js | 6 +- 13 files changed, 91 insertions(+), 91 deletions(-) diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 2ac373a49ec5..b2c33ac599e0 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -37,10 +37,10 @@ function removeColumn(qi, tableName, columnName, options) { // No foreign key constraints found, so we can remove the column return; } - return Promise.map(results, constraint => qi.sequelize.query( + return Promise.all(results.map(constraint => qi.sequelize.query( qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), Object.assign({ raw: true }, options) - )); + ))); }) .then(() => qi.sequelize.query( qi.QueryGenerator.removeColumnQuery(tableName, columnName), diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 60f5c68e2046..4621c5fb5e73 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -279,7 +279,7 @@ class Query extends AbstractQuery { if (!tableNames.length) { return executeSql(); } - return Promise.map(tableNames, tableName => + return Promise.all(tableNames.map(tableName => new Promise(resolve => { tableName = tableName.replace(/`/g, ''); columnTypes[tableName] = {}; @@ -292,8 +292,7 @@ class Query extends AbstractQuery { } resolve(); }); - }) - ).then(executeSql); + }))).then(executeSql); } return executeSql(); }); @@ -425,7 +424,7 @@ class Query extends AbstractQuery { handleShowIndexesQuery(data) { // Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that! - return Promise.map(data.reverse(), item => { + return Promise.all(data.reverse().map(item => { item.fields = []; item.primary = false; item.unique = !!item.unique; @@ -441,7 +440,7 @@ class Query extends AbstractQuery { return item; }); - }); + })); } getDatabaseMethod() { diff --git a/lib/model.js b/lib/model.js index e9291df76778..ba86d1c3d035 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1818,7 +1818,7 @@ class Model { if (!results.length) return original; - return Promise.map(options.include, include => { + return Promise.all(options.include.map(include => { if (!include.separate) { return Model._findSeparate( results.reduce((memo, result) => { @@ -1856,7 +1856,7 @@ class Model { ); } }); - }).return(original); + })).return(original); } /** @@ -2607,11 +2607,10 @@ class Model { const validateOptions = _.clone(options); validateOptions.hooks = options.individualHooks; - return Promise.map(instances, instance => + return Promise.all(instances.map(instance => instance.validate(validateOptions).catch(err => { errors.push(new sequelizeErrors.BulkRecordError(err, instance)); - }) - ).then(() => { + }))).then(() => { delete options.skip; if (errors.length) { throw errors; @@ -2621,7 +2620,7 @@ class Model { }).then(() => { if (options.individualHooks) { // Create each instance individually - return Promise.map(instances, instance => { + return Promise.all(instances.map(instance => { const individualOptions = _.clone(options); delete individualOptions.fields; delete individualOptions.individualHooks; @@ -2630,14 +2629,14 @@ class Model { individualOptions.hooks = true; return instance.save(individualOptions); - }); + })); } return Promise.resolve().then(() => { if (!options.include || !options.include.length) return; // Nested creation for BelongsTo relations - return Promise.map(options.include.filter(include => include.association instanceof BelongsTo), include => { + return Promise.all(options.include.filter(include => include.association instanceof BelongsTo).map(include => { const associationInstances = []; const associationInstanceIndexToInstanceMap = []; @@ -2668,7 +2667,7 @@ class Model { instance[include.association.accessors.set](associationInstance, { save: false, logging: options.logging }); } }); - }); + })); }).then(() => { // Create all in one query // Recreate records from instances to represent any changes made in hooks or validation @@ -2748,8 +2747,8 @@ class Model { if (!options.include || !options.include.length) return; // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.map(options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)), include => { + return Promise.all(options.include.filter(include => !(include.association instanceof BelongsTo || + include.parent && include.parent.association instanceof BelongsToMany)).map(include => { const associationInstances = []; const associationInstanceIndexToInstanceMap = []; @@ -2821,7 +2820,7 @@ class Model { return recursiveBulkCreate(throughInstances, throughOptions); } }); - }); + })); }).then(() => { // map fields back to attributes instances.forEach(instance => { @@ -2925,8 +2924,7 @@ class Model { }).then(() => { // Get daos and run beforeDestroy hook on each record individually if (options.individualHooks) { - return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }) - .map(instance => this.runHooks('beforeDestroy', instance, options).then(() => instance)) + return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }).then(value => Promise.all(value.map(instance => this.runHooks('beforeDestroy', instance, options).then(() => instance)))) .then(_instances => { instances = _instances; }); @@ -2952,7 +2950,7 @@ class Model { }).tap(() => { // Run afterDestroy hook on each record individually if (options.individualHooks) { - return Promise.map(instances, instance => this.runHooks('afterDestroy', instance, options)); + return Promise.all(instances.map(instance => this.runHooks('afterDestroy', instance, options))); } }).tap(() => { // Run after hook @@ -2999,8 +2997,7 @@ class Model { }).then(() => { // Get daos and run beforeRestore hook on each record individually if (options.individualHooks) { - return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }) - .map(instance => this.runHooks('beforeRestore', instance, options).then(() => instance)) + return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }).then(value => Promise.all(value.map(instance => this.runHooks('beforeRestore', instance, options).then(() => instance)))) .then(_instances => { instances = _instances; }); @@ -3018,7 +3015,7 @@ class Model { }).tap(() => { // Run afterDestroy hook on each record individually if (options.individualHooks) { - return Promise.map(instances, instance => this.runHooks('afterRestore', instance, options)); + return Promise.all(instances.map(instance => this.runHooks('afterRestore', instance, options))); } }).tap(() => { // Run after hook @@ -3148,7 +3145,7 @@ class Model { let changedValues; let different = false; - return Promise.map(instances, instance => { + return Promise.all(instances.map(instance => { // Record updates in instances dataValues Object.assign(instance.dataValues, values); // Set the changed fields on the instance @@ -3177,7 +3174,7 @@ class Model { return instance; }); - }).then(_instances => { + })).then(_instances => { instances = _instances; if (!different) { @@ -3192,14 +3189,14 @@ class Model { } // Hooks change values in a different way for each record // Do not run original query but save each record individually - return Promise.map(instances, instance => { + return Promise.all(instances.map(instance => { const individualOptions = _.clone(options); delete individualOptions.individualHooks; individualOptions.hooks = false; individualOptions.validate = false; return instance.save(individualOptions); - }).tap(_instances => { + })).tap(_instances => { instances = _instances; }); }); @@ -3234,9 +3231,9 @@ class Model { }); }).tap(result => { if (options.individualHooks) { - return Promise.map(instances, instance => { + return Promise.all(instances.map(instance => { return this.runHooks('afterUpdate', instance, options); - }).then(() => { + })).then(() => { result[1] = instances; }); } @@ -3722,11 +3719,10 @@ class Model { !options.raw && ( // True when sequelize method - value instanceof Utils.SequelizeMethod || + (value instanceof Utils.SequelizeMethod || // Check for data type type comparators - !(value instanceof Utils.SequelizeMethod) && this.constructor._dataTypeChanges[key] && this.constructor._dataTypeChanges[key].call(this, value, originalValue, options) || - // Check default - !this.constructor._dataTypeChanges[key] && !_.isEqual(value, originalValue) + !(value instanceof Utils.SequelizeMethod) && this.constructor._dataTypeChanges[key] && this.constructor._dataTypeChanges[key].call(this, value, originalValue, options) || // Check default + !this.constructor._dataTypeChanges[key] && !_.isEqual(value, originalValue)) ) ) { this._previousDataValues[key] = originalValue; @@ -3986,7 +3982,7 @@ class Model { if (!this._options.include || !this._options.include.length) return this; // Nested creation for BelongsTo relations - return Promise.map(this._options.include.filter(include => include.association instanceof BelongsTo), include => { + return Promise.all(this._options.include.filter(include => include.association instanceof BelongsTo).map(include => { const instance = this.get(include.as); if (!instance) return Promise.resolve(); @@ -3999,7 +3995,7 @@ class Model { }).value(); return instance.save(includeOptions).then(() => this[include.association.accessors.set](instance, { save: false, logging: options.logging })); - }); + })); }).then(() => { const realFields = options.fields.filter(field => !this.constructor._virtualAttributes.has(field)); if (!realFields.length) return this; @@ -4058,8 +4054,8 @@ class Model { if (!this._options.include || !this._options.include.length) return this; // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.map(this._options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)), include => { + return Promise.all(this._options.include.filter(include => !(include.association instanceof BelongsTo || + include.parent && include.parent.association instanceof BelongsToMany)).map(include => { let instances = this.get(include.as); if (!instances) return Promise.resolve(); @@ -4075,7 +4071,7 @@ class Model { }).value(); // Instances will be updated in place so we can safely treat HasOne like a HasMany - return Promise.map(instances, instance => { + return Promise.all(instances.map(instance => { if (include.association instanceof BelongsToMany) { return instance.save(includeOptions).then(() => { const values = {}; @@ -4102,8 +4098,8 @@ class Model { instance.set(include.association.foreignKey, this.get(include.association.sourceKey || this.constructor.primaryKeyAttribute, { raw: true }), { raw: true }); Object.assign(instance, include.association.scope); return instance.save(includeOptions); - }); - }); + })); + })); }) .tap(result => { // Run after hook diff --git a/lib/query-interface.js b/lib/query-interface.js index bb3a0a19ba06..1e97ba748f5b 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -98,7 +98,9 @@ class QueryInterface { if (!this.QueryGenerator._dialect.supports.schemas) { return this.sequelize.drop(options); } - return this.showAllSchemas(options).map(schemaName => this.dropSchema(schemaName, options)); + return this.showAllSchemas(options).then(schemas => Promise.all( + schemas.map(schemaName => this.dropSchema(schemaName, options)) + )); } /** @@ -368,9 +370,11 @@ class QueryInterface { options = options || {}; - return this.pgListEnums(null, options).map(result => this.sequelize.query( - this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), - Object.assign({}, options, { raw: true }) + return this.pgListEnums(null, options).then(enums => Promise.all( + enums.map(result => this.sequelize.query( + this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), + Object.assign({}, options, { raw: true }) + )) )); } @@ -689,9 +693,8 @@ class QueryInterface { options = Object.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS }); - return Promise.map(tableNames, tableName => - this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options) - ).then(results => { + return Promise.all(tableNames.map(tableName => + this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))).then(results => { const result = {}; tableNames.forEach((tableName, i) => { diff --git a/lib/sequelize.js b/lib/sequelize.js index 2f9c7734ecef..e4235d9075ba 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -854,7 +854,7 @@ class Sequelize { if (options && options.cascade) { return Promise.each(models, truncateModel); } - return Promise.map(models, truncateModel); + return Promise.all(models.map(truncateModel)); } /** diff --git a/package.json b/package.json index fe7fd939084a..c04d00db6a1e 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "mocha": "^6.1.4", "mysql2": "^1.6.5", "nyc": "^15.0.0", + "p-map": "^4.0.0", "pg": "^7.8.1", "pg-hstore": "^2.x", "pg-types": "^2.0.0", diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index e1a2deabdd86..54a372a0da19 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -764,7 +764,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING], Tasks = {}; - return Promise.map(dataTypes, dataType => { + return Promise.all(dataTypes.map(dataType => { const tableName = `TaskXYZ_${dataType.key}`; Tasks[dataType] = this.sequelize.define(tableName, { title: Sequelize.STRING }); @@ -773,7 +773,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { return Tasks[dataType].sync({ force: true }).then(() => { expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); }); - }); + })); }); describe('allows the user to provide an attribute definition object as foreignKey', () => { diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index 6399b115565b..846b728d2d05 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -527,9 +527,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { return promise; })([B, C, D, E, F, G, H]) ).then(([as, b]) => { - return Promise.map(as, a => { + return Promise.all(as.map(a => { return a.setB(b); - }); + })); }).then(() => { return A.findAll({ include: [ @@ -625,9 +625,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { return promise; })([B, C, D, E, F, G, H]) ).then(([as, b]) => { - return Promise.map(as, a => { + return Promise.all(as.map(a => { return a.setB(b); - }); + })); }).then(() => { return A.findAll({ include: [ @@ -970,9 +970,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Promise.join( results.users[0].setGroup(results.groups[1]), results.users[1].setGroup(results.groups[0]), - Promise.map(results.groups, group => { + Promise.all(results.groups.map(group => { return group.setCategories(results.categories); - }) + })) ); }).then(() => { return User.findAll({ @@ -1023,9 +1023,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Promise.join( results.users[0].setTeam(results.groups[1]), results.users[1].setTeam(results.groups[0]), - Promise.map(results.groups, group => { + Promise.all(results.groups.map(group => { return group.setTags(results.categories); - }) + })) ); }).then(() => { return User.findAll({ @@ -1076,9 +1076,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Promise.join( results.users[0].setGroup(results.groups[1]), results.users[1].setGroup(results.groups[0]), - Promise.map(results.groups, group => { + Promise.all(results.groups.map(group => { return group.setCategories(results.categories); - }) + })) ); }).then(() => { return User.findAll({ @@ -1698,9 +1698,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.create({ 'public': true }), Post.create({ 'public': true }) ).then(posts => { - return Promise.map(posts.slice(1, 3), post => { + return Promise.all(posts.slice(1, 3).map(post => { return post.createCategory({ slug: 'food' }); - }); + })); }).then(() => { return Post.findAll({ limit: 2, diff --git a/test/integration/model.test.js b/test/integration/model.test.js index bf575819c3dc..2aa824281d32 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -12,7 +12,8 @@ const chai = require('chai'), Promise = require('bluebird'), current = Support.sequelize, Op = Sequelize.Op, - semver = require('semver'); + semver = require('semver'), + pMap = require('p-map'); describe(Support.getTestDialectTeaser('Model'), () => { before(function() { @@ -2467,7 +2468,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { for (let i = 0; i < 1000; i++) { tasks.push(testAsync); } - return Sequelize.Promise.resolve(tasks).map(entry => { + return pMap(tasks, entry => { return entry(); }, { // Needs to be one less than ??? else the non transaction query won't ever get a connection diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 60577fb9dd80..e9434819435f 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -201,14 +201,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); return User.sync({ force: true }).then(() => { - return Promise.map(_.range(50), i => { + return Promise.all(_.range(50).map(i => { return User.findOrCreate({ where: { email: `unique.email.${i}@sequelizejs.com`, companyId: Math.floor(Math.random() * 5) } }); - }); + })); }); }); @@ -225,22 +225,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); return User.sync({ force: true }).then(() => { - return Promise.map(_.range(50), i => { + return Promise.all(_.range(50).map(i => { return User.findOrCreate({ where: { email: `unique.email.${i}@sequelizejs.com`, companyId: 2 } }); - }).then(() => { - return Promise.map(_.range(50), i => { + })).then(() => { + return Promise.all(_.range(50).map(i => { return User.findOrCreate({ where: { email: `unique.email.${i}@sequelizejs.com`, companyId: 2 } }); - }); + })); }); }); }); @@ -258,14 +258,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); return User.sync({ force: true }).then(() => { - return Promise.map(_.range(50), () => { + return Promise.all(_.range(50).map(() => { return User.findOrCreate({ where: { email: 'unique.email.1@sequelizejs.com', companyId: 2 } }); - }); + })); }); }); } diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index dc48af23e631..30c8b88dff0a 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -1173,7 +1173,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts simply', function() { - return Sequelize.Promise.map([['ASC', 'Asia'], ['DESC', 'Europe']], params => { + return Sequelize.Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(params => { return this.Continent.findAll({ order: [['name', params[0]]] }).then(continents => { @@ -1181,11 +1181,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(continents[0]).to.exist; expect(continents[0].name).to.equal(params[1]); }); - }); + })); }); it('sorts by 1st degree association', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']], params => { + return Sequelize.Promise.all([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']].map(params => { return this.Continent.findAll({ include: [this.Country], order: [[this.Country, 'name', params[0]]] @@ -1197,11 +1197,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(continents[0].countries[0]).to.exist; expect(continents[0].countries[0].name).to.equal(params[2]); }); - }); + })); }); it('sorts simply and by 1st degree association with limit where 1st degree associated instances returned for second one and not the first', function() { - return Sequelize.Promise.map([['ASC', 'Asia', 'Europe', 'England']], params => { + return Sequelize.Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, @@ -1225,11 +1225,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(continents[1].countries[0]).to.exist; expect(continents[1].countries[0].name).to.equal(params[3]); }); - }); + })); }); it('sorts by 2nd degree association', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']], params => { + return Sequelize.Promise.all([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person] }], order: [[this.Country, this.Person, 'lastName', params[0]]] @@ -1244,11 +1244,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(continents[0].countries[0].people[0]).to.exist; expect(continents[0].countries[0].people[0].name).to.equal(params[3]); }); - }); + })); }); it('sorts by 2nd degree association with alias', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']], params => { + return Sequelize.Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[this.Country, { model: this.Person, as: 'residents' }, 'lastName', params[0]]] @@ -1263,11 +1263,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(continents[0].countries[0].residents[0]).to.exist; expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); }); - }); + })); }); it('sorts by 2nd degree association with alias while using limit', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']], params => { + return Sequelize.Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[{ model: this.Country }, { model: this.Person, as: 'residents' }, 'lastName', params[0]]], @@ -1283,7 +1283,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(continents[0].countries[0].residents[0]).to.exist; expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); }); - }); + })); }); }); diff --git a/test/integration/model/findOne.test.js b/test/integration/model/findOne.test.js index c5d8bbdd2bd6..86e9444c54d0 100644 --- a/test/integration/model/findOne.test.js +++ b/test/integration/model/findOne.test.js @@ -250,7 +250,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { let count = 0; return this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]).then(() => { - return Sequelize.Promise.map(permutations, perm => { + return Sequelize.Promise.all(permutations.map(perm => { return this.User.findByPk(perm, { logging(s) { expect(s).to.include(0); @@ -259,7 +259,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }).then(user => { expect(user).to.be.null; }); - }); + })); }).then(() => { expect(count).to.be.equal(permutations.length); }); diff --git a/test/integration/model/scope/merge.test.js b/test/integration/model/scope/merge.test.js index 125a1beadafe..d19c814c6348 100644 --- a/test/integration/model/scope/merge.test.js +++ b/test/integration/model/scope/merge.test.js @@ -148,7 +148,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should merge complex scopes correctly regardless of their order', function() { - return Promise.map(this.scopePermutations, scopes => this.Foo.scope(...scopes).findOne()).then(results => { + return Promise.all(this.scopePermutations.map(scopes => this.Foo.scope(...scopes).findOne())).then(results => { const first = results.shift().toJSON(); for (const result of results) { expect(result.toJSON()).to.deep.equal(first); @@ -157,7 +157,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should merge complex scopes with findAll options correctly regardless of their order', function() { - return Promise.map(this.scopePermutations, ([a, b, c, d]) => this.Foo.scope(a, b, c).findAll(this.scopes[d]).then(x => x[0])).then(results => { + return Promise.all(this.scopePermutations.map(([a, b, c, d]) => this.Foo.scope(a, b, c).findAll(this.scopes[d]).then(x => x[0]))).then(results => { const first = results.shift().toJSON(); for (const result of results) { expect(result.toJSON()).to.deep.equal(first); @@ -166,7 +166,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should merge complex scopes with findOne options correctly regardless of their order', function() { - return Promise.map(this.scopePermutations, ([a, b, c, d]) => this.Foo.scope(a, b, c).findOne(this.scopes[d])).then(results => { + return Promise.all(this.scopePermutations.map(([a, b, c, d]) => this.Foo.scope(a, b, c).findOne(this.scopes[d]))).then(results => { const first = results.shift().toJSON(); for (const result of results) { expect(result.toJSON()).to.deep.equal(first); From 65600861f349b2028775a5b3f35e7ea1d70f80d9 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sat, 4 Apr 2020 23:40:48 -0500 Subject: [PATCH 068/414] fix: eliminate usage of Promise.join (#12063) --- lib/dialects/abstract/connection-manager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index ef98e2ab5a6a..93fa430d6a92 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -178,13 +178,13 @@ class ConnectionManager { debug('connection destroy'); }, destroyAllNow: () => { - return Promise.join( + return Promise.all( this.pool.read.destroyAllNow(), this.pool.write.destroyAllNow() - ).tap(() => { debug('all connections destroyed'); }); + ).then(() => { debug('all connections destroyed'); }); }, drain: () => { - return Promise.join( + return Promise.all( this.pool.write.drain(), this.pool.read.drain() ); From e0343afeca85b14cc89acd5093dd7779d4f660a7 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sat, 4 Apr 2020 23:51:08 -0500 Subject: [PATCH 069/414] refactor: remove bluebird Promise.try and Promise.prototype.reflect usage (#12068) --- lib/dialects/abstract/connection-manager.js | 2 +- lib/instance-validator.js | 46 +++++-------------- lib/model.js | 24 +++++----- lib/sequelize.js | 8 ++-- .../dialects/postgres/query-interface.test.js | 12 ++--- 5 files changed, 35 insertions(+), 57 deletions(-) diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index 93fa430d6a92..4417986674b8 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -293,7 +293,7 @@ class ConnectionManager { * @returns {Promise} */ releaseConnection(connection) { - return Promise.try(() => { + return Promise.resolve().then(() => { this.pool.release(connection); debug('connection released'); }); diff --git a/lib/instance-validator.js b/lib/instance-validator.js index b8b3517cd011..f0d2fcd3b464 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -68,8 +68,8 @@ class InstanceValidator { this.inProgress = true; return Promise.all([ - this._perAttributeValidators().reflect(), - this._customValidators().reflect() + this._perAttributeValidators(), + this._customValidators() ]).then(() => { if (this.errors.length) { throw new sequelizeError.ValidationError(null, this.errors); @@ -116,7 +116,7 @@ class InstanceValidator { /** * Will run all the validators defined per attribute (built-in validators and custom validators) * - * @returns {Promise>} A promise from .reflect(). + * @returns {Promise} * @private */ _perAttributeValidators() { @@ -140,7 +140,7 @@ class InstanceValidator { } if (Object.prototype.hasOwnProperty.call(this.modelInstance.validators, field)) { - validators.push(this._singleAttrValidate(value, field, rawAttribute.allowNull).reflect()); + validators.push(this._singleAttrValidate(value, field, rawAttribute.allowNull)); } }); @@ -150,7 +150,7 @@ class InstanceValidator { /** * Will run all the custom validators defined in the model's options. * - * @returns {Promise>} A promise from .reflect(). + * @returns {Promise} * @private */ _customValidators() { @@ -162,8 +162,7 @@ class InstanceValidator { const valprom = this._invokeCustomValidator(validator, validatorType) // errors are handled in settling, stub this - .catch(() => {}) - .reflect(); + .catch(() => {}); validators.push(valprom); }); @@ -206,7 +205,7 @@ class InstanceValidator { // Custom validators should always run, except if value is null and allowNull is false (see #9143) if (typeof test === 'function') { - validators.push(this._invokeCustomValidator(test, validatorType, true, value, field).reflect()); + validators.push(this._invokeCustomValidator(test, validatorType, true, value, field)); return; } @@ -218,12 +217,14 @@ class InstanceValidator { const validatorPromise = this._invokeBuiltinValidator(value, test, validatorType, field); // errors are handled in settling, stub this validatorPromise.catch(() => {}); - validators.push(validatorPromise.reflect()); + validators.push(validatorPromise); }); return Promise - .all(validators) - .then(results => this._handleReflectedResult(field, value, results)); + .all(validators.map(validator => validator.catch(rejection => { + const isBuiltIn = !!rejection.validatorName; + this._pushError(isBuiltIn, field, rejection, value, rejection.validatorName, rejection.validatorArgs); + }))); } /** @@ -368,29 +369,6 @@ class InstanceValidator { } } - - /** - * Handles the returned result of a Promise.reflect. - * - * If errors are found it populates this.error. - * - * @param {string} field The attribute name. - * @param {string|number} value The data value. - * @param {Array} promiseInspections objects. - * - * @private - */ - _handleReflectedResult(field, value, promiseInspections) { - for (const promiseInspection of promiseInspections) { - if (promiseInspection.isRejected()) { - const rejection = promiseInspection.error(); - const isBuiltIn = !!rejection.validatorName; - - this._pushError(isBuiltIn, field, rejection, value, rejection.validatorName, rejection.validatorArgs); - } - } - } - /** * Signs all errors retaining the original. * diff --git a/lib/model.js b/lib/model.js index ba86d1c3d035..2b9e880caedd 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1279,7 +1279,7 @@ class Model { const attributes = this.tableAttributes; const rawAttributes = this.fieldRawAttributesMap; - return Promise.try(() => { + return Promise.resolve().then(() => { if (options.hooks) { return this.runHooks('beforeSync', options); } @@ -1707,7 +1707,7 @@ class Model { ? options.rejectOnEmpty : this.options.rejectOnEmpty; - return Promise.try(() => { + return Promise.resolve().then(() => { this._injectScope(options); if (options.hooks) { @@ -2021,7 +2021,7 @@ class Model { * @returns {Promise} */ static count(options) { - return Promise.try(() => { + return Promise.resolve().then(() => { options = Utils.cloneDeep(options); options = _.defaults(options, { hooks: true }); options.raw = true; @@ -2459,7 +2459,7 @@ class Model { options.fields = changed; } - return Promise.try(() => { + return Promise.resolve().then(() => { if (options.validate) { return instance.validate(options); } @@ -2487,7 +2487,7 @@ class Model { delete updateValues[this.primaryKeyField]; } - return Promise.try(() => { + return Promise.resolve().then(() => { if (options.hooks) { return this.runHooks('beforeUpsert', values, options); } @@ -2595,7 +2595,7 @@ class Model { } } - return Promise.try(() => { + return Promise.resolve().then(() => { // Run before hook if (options.hooks) { return model.runHooks('beforeBulkCreate', instances, options); @@ -2916,7 +2916,7 @@ class Model { let instances; - return Promise.try(() => { + return Promise.resolve().then(() => { // Run before hook if (options.hooks) { return this.runHooks('beforeBulkDestroy', options); @@ -2989,7 +2989,7 @@ class Model { Utils.mapOptionFieldNames(options, this); - return Promise.try(() => { + return Promise.resolve().then(() => { // Run before hook if (options.hooks) { return this.runHooks('beforeBulkRestore', options); @@ -3092,7 +3092,7 @@ class Model { let instances; let valuesUse; - return Promise.try(() => { + return Promise.resolve().then(() => { // Validate if (options.validate) { const build = this.build(values); @@ -3932,7 +3932,7 @@ class Model { this.dataValues[createdAtAttr] = this.constructor._getDefaultTimestamp(createdAtAttr) || now; } - return Promise.try(() => { + return Promise.resolve().then(() => { // Validate if (options.validate) { return this.validate(options); @@ -4230,7 +4230,7 @@ class Model { force: false }, options); - return Promise.try(() => { + return Promise.resolve().then(() => { // Run before hook if (options.hooks) { return this.constructor.runHooks('beforeDestroy', this, options); @@ -4299,7 +4299,7 @@ class Model { force: false }, options); - return Promise.try(() => { + return Promise.resolve().then(() => { // Run before hook if (options.hooks) { return this.constructor.runHooks('beforeRestore', this, options); diff --git a/lib/sequelize.js b/lib/sequelize.js index e4235d9075ba..d438e0d70d15 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -582,7 +582,7 @@ class Sequelize { options.searchPath = 'DEFAULT'; } - return Promise.try(() => { + return Promise.resolve().then(() => { if (typeof sql === 'object') { if (sql.values !== undefined) { if (options.replacements !== undefined) { @@ -633,7 +633,7 @@ class Sequelize { const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); - return Promise.resolve(retry(() => Promise.try(() => { + return Promise.resolve(retry(() => Promise.resolve().then(() => { if (options.transaction === undefined && Sequelize._cls) { options.transaction = Sequelize._cls.get('transaction'); } @@ -797,7 +797,7 @@ class Sequelize { } } - return Promise.try(() => { + return Promise.resolve().then(() => { if (options.hooks) { return this.runHooks('beforeBulkSync', options); } @@ -1114,7 +1114,7 @@ class Sequelize { .catch(err => { // Rollback transaction if not already finished (commit, rollback, etc) // and reject with original error (ignore any error in rollback) - return Promise.try(() => { + return Promise.resolve().then(() => { if (!transaction.finished) return transaction.rollback().catch(() => {}); }).throw(err); }); diff --git a/test/integration/dialects/postgres/query-interface.test.js b/test/integration/dialects/postgres/query-interface.test.js index 2c6fe19cc00e..53ec75975d84 100644 --- a/test/integration/dialects/postgres/query-interface.test.js +++ b/test/integration/dialects/postgres/query-interface.test.js @@ -18,7 +18,7 @@ if (dialect.match(/^postgres/)) { describe('createSchema', () => { beforeEach(function() { // make sure we don't have a pre-existing schema called testSchema. - return this.queryInterface.dropSchema('testschema').reflect(); + return this.queryInterface.dropSchema('testschema').catch(() => {}); }); it('creates a schema', function() { @@ -64,9 +64,9 @@ if (dialect.match(/^postgres/)) { // ensure the function names we'll use don't exist before we start. // then setup our function to rename return this.queryInterface.dropFunction('rftest1', []) - .reflect() + .catch(() => {}) .then(() => this.queryInterface.dropFunction('rftest2', [])) - .reflect() + .catch(() => {}) .then(() => this.queryInterface.createFunction('rftest1', [], 'varchar', 'plpgsql', 'return \'testreturn\';', {})); }); @@ -87,14 +87,14 @@ if (dialect.match(/^postgres/)) { // test suite causing a failure of afterEach's cleanup to be called. return this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. - .reflect(); + .catch(() => {}); }); after(function() { // cleanup return this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. - .reflect(); + .catch(() => {}); }); it('creates a stored procedure', function() { @@ -211,7 +211,7 @@ if (dialect.match(/^postgres/)) { // make sure we have a droptest function in place. return this.queryInterface.createFunction('droptest', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options) // suppress errors.. this could fail if the function is already there.. thats ok. - .reflect(); + .catch(() => {}); }); it('can drop a function', function() { From 04573fd3aa7231147bd4d4889b43ddebb82ec19e Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 5 Apr 2020 03:12:32 -0500 Subject: [PATCH 070/414] refactor: remove bluebird Promise.prototype.return usage (#12065) --- lib/associations/belongs-to-many.js | 2 +- lib/associations/has-many.js | 6 ++-- lib/dialects/abstract/connection-manager.js | 2 +- lib/hooks.js | 2 +- lib/instance-validator.js | 3 +- lib/model.js | 6 ++-- lib/sequelize.js | 6 ++-- .../associations/belongs-to-many.test.js | 34 +++++++++---------- .../integration/associations/has-many.test.js | 10 +++--- test/integration/hooks/associations.test.js | 4 +-- test/integration/include.test.js | 2 +- test/integration/instance/reload.test.js | 6 ++-- test/support.js | 2 +- 13 files changed, 42 insertions(+), 43 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index eafd8bed3c70..e28e3372b8e3 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -800,7 +800,7 @@ class BelongsToMany extends Association { // Create the related model instance return association.target.create(values, options).then(newAssociatedObject => - sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject) + sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).then(() => newAssociatedObject) ); } diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index d49e881b4406..7290ea4ebad2 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -378,7 +378,7 @@ class HasMany extends Association { )); } - return Utils.Promise.all(promises).return(sourceInstance); + return Utils.Promise.all(promises).then(() => sourceInstance); }); } @@ -408,7 +408,7 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).return(sourceInstance); + return this.target.unscoped().update(update, _.defaults({ where }, options)).then(() => sourceInstance); } /** @@ -434,7 +434,7 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).return(this); + return this.target.unscoped().update(update, _.defaults({ where }, options)).then(() => this); } /** diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index 4417986674b8..e680004aa018 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -309,7 +309,7 @@ class ConnectionManager { _connect(config) { return this.sequelize.runHooks('beforeConnect', config) .then(() => this.dialect.connectionManager.connect(config)) - .then(connection => this.sequelize.runHooks('afterConnect', connection, config).return(connection)); + .then(connection => this.sequelize.runHooks('afterConnect', connection, config).then(() => connection)); } /** diff --git a/lib/hooks.js b/lib/hooks.js index 890a63bfd6e6..24c0832aa940 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -128,7 +128,7 @@ const Hooks = { debug(`running hook ${hookType}`); return hook.apply(this, hookArgs); - }).return(); + }).then(() => undefined); }, /** diff --git a/lib/instance-validator.js b/lib/instance-validator.js index f0d2fcd3b464..2ab567f7c0e0 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -109,8 +109,7 @@ class InstanceValidator { .catch(error => runHooks('validationFailed', this.modelInstance, this.options, error) .then(newError => { throw newError || error; })) ) - .then(() => runHooks('afterValidate', this.modelInstance, this.options)) - .return(this.modelInstance); + .then(() => runHooks('afterValidate', this.modelInstance, this.options)).then(() => this.modelInstance); } /** diff --git a/lib/model.js b/lib/model.js index 2b9e880caedd..2ab58415f2de 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1374,7 +1374,7 @@ class Model { if (options.hooks) { return this.runHooks('afterSync', options); } - }).return(this); + }).then(() => this); } /** @@ -1856,7 +1856,7 @@ class Model { ); } }); - })).return(original); + })).then(() => original); } /** @@ -4357,7 +4357,7 @@ class Model { options.where = Object.assign({}, options.where, identifier); options.instance = this; - return this.constructor.increment(fields, options).return(this); + return this.constructor.increment(fields, options).then(() => this); } /** diff --git a/lib/sequelize.js b/lib/sequelize.js index d438e0d70d15..8a0342d25108 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -826,7 +826,7 @@ class Sequelize { if (options.hooks) { return this.runHooks('afterBulkSync', options); } - }).return(this); + }).then(() => this); } /** @@ -895,7 +895,7 @@ class Sequelize { type: QueryTypes.SELECT }, options); - return this.query('SELECT 1+1 AS result', options).return(); + return this.query('SELECT 1+1 AS result', options).then(() => undefined); } databaseVersion(options) { @@ -1104,7 +1104,7 @@ class Sequelize { const transaction = new Transaction(this, options); - if (!autoCallback) return transaction.prepareEnvironment(false).return(transaction); + if (!autoCallback) return transaction.prepareEnvironment(false).then(() => transaction); // autoCallback provided return Sequelize._clsRun(() => { diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index f1059b31b1f8..a8cf820f891a 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -1556,7 +1556,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Member.create({ member_id: 10, email: 'team@sequelizejs.com' }) ]); }).then(([group, member]) => { - return group.addMember(member).return(group); + return group.addMember(member).then(() => group); }).then(group => { return group.getMembers(); }).then(members => { @@ -1728,7 +1728,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.create({ id: 51, title: 'following up' }) ]); }).then(([user, task1, task2]) => { - return user.setTasks([task1, task2]).return(user); + return user.setTasks([task1, task2]).then(() => user); }).then(user => { return user.getTasks(); }).then(userTasks => { @@ -1867,7 +1867,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return Promise.all([ user.addTask(task1), user.addTask([task2]) - ]).return(user); + ]).then(() => user); }).then(user => { return user.getTasks(); }).then(tasks => { @@ -1965,7 +1965,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.create({ id: 50, title: 'get started' }) ]); }).then(([user, task]) => { - return user.addTask(task.id).return(user); + return user.addTask(task.id).then(() => user); }).then(user => { return user.getTasks(); }).then(tasks => { @@ -2026,7 +2026,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.create({ id: 50, title: 'get started' }) ]); }).then(([user, task]) => { - return user.addTask(task).return(user); + return user.addTask(task).then(() => user); }).then(user => { return user.getTasks(); }).then(tasks => { @@ -2074,7 +2074,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return Promise.all([ user.addTasks(task1), user.addTasks([task2]) - ]).return(user); + ]).then(() => user); }).then(user => { return user.getTasks(); }).then(tasks => { @@ -2213,7 +2213,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.Task.create({ title: 'task1' }), this.Task.create({ title: 'task2' }) ]).then(([user, task1, task2]) => { - return user.setTasks([task1, task2]).return(user); + return user.setTasks([task1, task2]).then(() => user); }).then(user => { return user.setTasks(null, { logging: spy @@ -2391,7 +2391,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }).then(([user, project1, project2]) => { return user.addProjects([project1, project2], { logging: spy - }).return(user); + }).then(() => user); }).then(user => { expect(spy).to.have.been.calledTwice; spy.resetHistory(); @@ -2405,7 +2405,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { expect(spy.calledOnce).to.be.ok; const project = projects[0]; expect(project).to.be.ok; - return project.destroy().return(user); + return project.destroy().then(() => user); }).then(user => { return this.User.findOne({ where: { id: user.id }, @@ -2429,7 +2429,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }).then(([user, project]) => { this.user = user; this.project = project; - return user.addProject(project, { logging: spy }).return(user); + return user.addProject(project, { logging: spy }).then(() => user); }).then(user => { expect(spy.calledTwice).to.be.ok; // Once for SELECT, once for INSERT spy.resetHistory(); @@ -2444,7 +2444,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { expect(project).to.be.ok; return this.user.removeProject(project, { logging: spy - }).return(project); + }).then(() => project); }).then(() => { expect(spy).to.have.been.calledOnce; }); @@ -2486,7 +2486,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { ); }).then(([group, user, project]) => { return user.addProject(project).then(() => { - return group.addUser(user).return(group); + return group.addUser(user).then(() => group); }); }).then(group => { // get the group and include both the users in the group and their project's @@ -2591,7 +2591,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.User.create({ username: 'foo' }), this.Task.create({ title: 'foo' }) ]).then(([user, task]) => { - return user.addTask(task).return(user); + return user.addTask(task).then(() => user); }).then(user => { return user.setTasks(null); }).then(result => { @@ -2622,7 +2622,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.User.create(), this.Project.create() ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).return(user); + return user.addProject(project, { through: { status: 'active', data: 42 } }).then(() => user); }).then(user => { return user.getProjects(); }).then(projects => { @@ -2640,7 +2640,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.User.create(), this.Project.create() ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).return(user); + return user.addProject(project, { through: { status: 'active', data: 42 } }).then(() => user); }).then(() => { return this.User.findAll({ include: [{ @@ -2666,7 +2666,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.User.create(), this.Project.create() ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).return(user); + return user.addProject(project, { through: { status: 'active', data: 42 } }).then(() => user); }).then(user => { return user.getProjects({ joinTableAttributes: ['status'] }); }).then(projects => { @@ -2867,7 +2867,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }) ]); }).then(([worker, tasks]) => { - return worker.setTasks(tasks).return([worker, tasks]); + return worker.setTasks(tasks).then(() => [worker, tasks]); }).then(([worker, tasks]) => { return worker.setTasks(tasks); }); diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index 8a549d0d3e36..b84f5864af0f 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -48,7 +48,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { return Promise.join( user.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }), user.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }) - ).return(user); + ).then(() => user); }).then(user => { return expect(user.countTasks({ attributes: [Task.primaryKeyField, 'title'], @@ -943,7 +943,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { return this.sequelize.sync({ force: true }).then(() => { return Article.create({ title: 'foo' }); }).then(article => { - return article.createLabel({ text: 'bar' }).return(article); + return article.createLabel({ text: 'bar' }).then(() => article); }).then(article => { return Label.findAll({ where: { ArticleId: article.id } }); }).then(labels => { @@ -1027,7 +1027,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { text: 'yolo' }, { fields: ['text'] - }).return(article); + }).then(() => article); }).then(article => { return article.getLabels(); }).then(labels => { @@ -1265,7 +1265,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.create({ title: 'task' }) ]); }).then(([user, task]) => { - return user.setTasks([task]).return(user); + return user.setTasks([task]).then(() => user); }).then(user => { // Changing the id of a DAO requires a little dance since // the `UPDATE` query generated by `save()` uses `id` in the @@ -1321,7 +1321,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.create({ title: 'task' }) ]); }).then(([user, task]) => { - return user.setTasks([task]).return(user); + return user.setTasks([task]).then(() => user); }).then(user => { // Changing the id of a DAO requires a little dance since // the `UPDATE` query generated by `save()` uses `id` in the diff --git a/test/integration/hooks/associations.test.js b/test/integration/hooks/associations.test.js index 8497c73e852d..8cd58014182d 100644 --- a/test/integration/hooks/associations.test.js +++ b/test/integration/hooks/associations.test.js @@ -866,7 +866,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { return Sequelize.Promise.all([ task.addMiniTask(minitask), project.addTask(task) - ]).return(project); + ]).then(() => project); }).then(project => { return project.destroy(); }).then(() => { @@ -921,7 +921,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { return Sequelize.Promise.all([ task.addMiniTask(minitask), project.addTask(task) - ]).return(project); + ]).then(() => project); }).then(project => { return expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText).then(() => { expect(beforeProject).to.be.true; diff --git a/test/integration/include.test.js b/test/integration/include.test.js index 1556402f0177..162ba9b385fc 100755 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -273,7 +273,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Promise.join( props.task.setUser(props.user), props.user.setGroup(props.group) - ).return(props); + ).then(() => props); }).then(props => { return Task.findOne({ where: { diff --git a/test/integration/instance/reload.test.js b/test/integration/instance/reload.test.js index 26bf1144cbbc..193c83919580 100644 --- a/test/integration/instance/reload.test.js +++ b/test/integration/instance/reload.test.js @@ -244,7 +244,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { include: [Shoe] }).then(lePlayer => { expect(lePlayer.Shoe).not.to.be.null; - return lePlayer.Shoe.destroy().return(lePlayer); + return lePlayer.Shoe.destroy().then(() => lePlayer); }).then(lePlayer => { return lePlayer.reload(); }).then(lePlayer => { @@ -277,7 +277,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(leTeam.Players).not.to.be.empty; return leTeam.Players[1].destroy().then(() => { return leTeam.Players[0].destroy(); - }).return(leTeam); + }).then(() => leTeam); }).then(leTeam => { return leTeam.reload(); }).then(leTeam => { @@ -309,7 +309,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { include: [Player] }).then(leTeam => { expect(leTeam.Players).to.have.length(2); - return leTeam.Players[0].destroy().return(leTeam); + return leTeam.Players[0].destroy().then(() => leTeam); }).then(leTeam => { return leTeam.reload(); }).then(leTeam => { diff --git a/test/support.js b/test/support.js index 83975b9621ac..87a9b16f181c 100644 --- a/test/support.js +++ b/test/support.js @@ -44,7 +44,7 @@ const Support = { const options = Object.assign({}, sequelize.options, { storage: p }), _sequelize = new Sequelize(sequelize.config.database, null, null, options); - return _sequelize.sync({ force: true }).return(_sequelize); + return _sequelize.sync({ force: true }).then(() => _sequelize); } return Sequelize.Promise.resolve(sequelize); }, From 33d7feea88d80a9df3db2215b9f95f1b824e9b81 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 5 Apr 2020 03:15:48 -0500 Subject: [PATCH 071/414] refactor: replace bluebird .tap (#12070) --- lib/dialects/abstract/connection-manager.js | 14 ++- lib/dialects/mysql/connection-manager.js | 4 +- lib/dialects/postgres/connection-manager.js | 13 ++- lib/dialects/postgres/query-interface.js | 7 +- lib/dialects/sqlite/connection-manager.js | 3 +- lib/model.js | 95 +++++++++++-------- lib/sequelize.js | 2 +- lib/transaction.js | 10 +- .../integration/dialects/postgres/dao.test.js | 3 +- .../include/findAndCountAll.test.js | 6 +- test/integration/instance/destroy.test.js | 9 +- test/integration/instance/reload.test.js | 6 +- 12 files changed, 101 insertions(+), 71 deletions(-) diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index e680004aa018..8d6dd1e86a66 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -129,7 +129,9 @@ class ConnectionManager { create: () => this._connect(config), destroy: connection => { return this._disconnect(connection) - .tap(() => { debug('connection destroy'); }); + .then(result => { + debug('connection destroy');return result; + }); }, validate: config.pool.validate, max: config.pool.max, @@ -194,8 +196,9 @@ class ConnectionManager { create: () => { // round robin config const nextRead = reads++ % config.replication.read.length; - return this._connect(config.replication.read[nextRead]).tap(connection => { + return this._connect(config.replication.read[nextRead]).then(connection => { connection.queryType = 'read'; + return connection; }); }, destroy: connection => this._disconnect(connection), @@ -209,8 +212,9 @@ class ConnectionManager { write: new Pool({ name: 'sequelize:write', create: () => { - return this._connect(config.replication.write).tap(connection => { + return this._connect(config.replication.write).then(connection => { connection.queryType = 'write'; + return connection; }); }, destroy: connection => this._disconnect(connection), @@ -282,7 +286,9 @@ class ConnectionManager { if (error instanceof TimeoutError) throw new errors.ConnectionAcquireTimeoutError(error); throw error; }); - }).tap(() => { debug('connection acquired'); }); + }).then(result => { + debug('connection acquired');return result; + }); } /** diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index c595790eb78b..c91e0cf00c9e 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -90,7 +90,9 @@ class ConnectionManager extends AbstractConnectionManager { connection.on('error', errorHandler); connection.once('connect', connectHandler); }) - .tap(() => { debug('connection acquired'); }) + .then(result => { + debug('connection acquired');return result; + }) .then(connection => { connection.on('error', error => { switch (error.code) { diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 8181595e63a2..53cdb6b0982f 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -193,7 +193,7 @@ class ConnectionManager extends AbstractConnectionManager { resolve(connection); } }); - }).tap(connection => { + }).then(connection => { let query = ''; if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') { @@ -217,21 +217,24 @@ class ConnectionManager extends AbstractConnectionManager { } if (query) { - return connection.query(query); + return Promise.resolve(connection.query(query)).then(() => connection); } - }).tap(connection => { + return connection; + }).then(connection => { if (Object.keys(this.nameOidMap).length === 0 && this.enumOids.oids.length === 0 && this.enumOids.arrayOids.length === 0) { - return this._refreshDynamicOIDs(connection); + return Promise.resolve(this._refreshDynamicOIDs(connection)).then(() => connection); } - }).tap(connection => { + return connection; + }).then(connection => { // Don't let a Postgres restart (or error) to take down the whole app connection.on('error', error => { connection._invalid = true; debug(`connection error ${error.code || error.message}`); this.pool.destroy(connection); }); + return connection; }); } diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index cd8a582f401b..19ef66123d35 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -144,11 +144,12 @@ function ensureEnums(qi, tableName, attributes, options, model) { return promises .reduce((promise, asyncFunction) => promise.then(asyncFunction), Promise.resolve()) - .tap(() => { - // If ENUM processed, then refresh OIDs + .then(result => { + // If ENUM processed, then refresh OIDs if (promises.length) { - return qi.sequelize.dialect.connectionManager._refreshDynamicOIDs(); + return Promise.resolve(qi.sequelize.dialect.connectionManager._refreshDynamicOIDs()).then(() => result); } + return result; }); }); } diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 47fde4f10a35..5b2de5327842 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -73,7 +73,7 @@ class ConnectionManager extends AbstractConnectionManager { resolve(this.connections[options.inMemory || options.uuid]); } ); - }).tap(connection => { + }).then(connection => { if (this.sequelize.config.password) { // Make it possible to define and use password for sqlite encryption plugin like sqlcipher connection.run(`PRAGMA KEY=${this.sequelize.escape(this.sequelize.config.password)}`); @@ -83,6 +83,7 @@ class ConnectionManager extends AbstractConnectionManager { // explicitly disallowed. It's still opt-in per relation connection.run('PRAGMA FOREIGN_KEYS=ON'); } + return connection; }); } diff --git a/lib/model.js b/lib/model.js index 2ab58415f2de..c4cc0fa6e8b0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1759,10 +1759,11 @@ class Model { }).then(() => { const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) }); return this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); - }).tap(results => { + }).then(results => { if (options.hooks) { - return this.runHooks('afterFind', results, options); + return Promise.resolve(this.runHooks('afterFind', results, options)).then(() => results); } + return results; }).then(results => { //rejectOnEmpty mode @@ -2502,10 +2503,11 @@ class Model { return created; }) - .tap(result => { + .then(result => { if (options.hooks) { - return this.runHooks('afterUpsert', result, options); + return Promise.resolve(this.runHooks('afterUpsert', result, options)).then(() => result); } + return result; }); }); } @@ -2947,16 +2949,18 @@ class Model { return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); } return this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this); - }).tap(() => { + }).then(result => { // Run afterDestroy hook on each record individually if (options.individualHooks) { - return Promise.all(instances.map(instance => this.runHooks('afterDestroy', instance, options))); + return Promise.resolve(Promise.all(instances.map(instance => this.runHooks('afterDestroy', instance, options)))).then(() => result); } - }).tap(() => { + return result; + }).then(result => { // Run after hook if (options.hooks) { - return this.runHooks('afterBulkDestroy', options); + return Promise.resolve(this.runHooks('afterBulkDestroy', options)).then(() => result); } + return result; }); } @@ -3012,16 +3016,18 @@ class Model { attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; options.omitNull = false; return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); - }).tap(() => { + }).then(result => { // Run afterDestroy hook on each record individually if (options.individualHooks) { - return Promise.all(instances.map(instance => this.runHooks('afterRestore', instance, options))); + return Promise.resolve(Promise.all(instances.map(instance => this.runHooks('afterRestore', instance, options)))).then(() => result); } - }).tap(() => { + return result; + }).then(result => { // Run after hook if (options.hooks) { - return this.runHooks('afterBulkRestore', options); + return Promise.resolve(this.runHooks('afterBulkRestore', options)).then(() => result); } + return result; }); } @@ -3196,8 +3202,9 @@ class Model { individualOptions.validate = false; return instance.save(individualOptions); - })).tap(_instances => { + })).then(_instances => { instances = _instances; + return _instances; }); }); }); @@ -3229,22 +3236,24 @@ class Model { return [affectedRows]; }); - }).tap(result => { + }).then(result => { if (options.individualHooks) { - return Promise.all(instances.map(instance => { - return this.runHooks('afterUpdate', instance, options); + return Promise.resolve(Promise.all(instances.map(instance => { + return Promise.resolve(this.runHooks('afterUpdate', instance, options)).then(() => result); })).then(() => { result[1] = instances; - }); + })).then(() => result); } - }).tap(() => { + return result; + }).then(result => { // Run after hook if (options.hooks) { options.attributes = values; - return this.runHooks('afterBulkUpdate', options).then(() => { + return Promise.resolve(this.runHooks('afterBulkUpdate', options).then(() => { delete options.attributes; - }); + })).then(() => result); } + return result; }); } @@ -4049,18 +4058,18 @@ class Model { result.dataValues = Object.assign(result.dataValues, values); return result; }) - .tap(() => { - if (!wasNewRecord) return this; - if (!this._options.include || !this._options.include.length) return this; + .then(result => { + if (!wasNewRecord) return Promise.resolve(this).then(() => result); + if (!this._options.include || !this._options.include.length) return Promise.resolve(this).then(() => result); // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.all(this._options.include.filter(include => !(include.association instanceof BelongsTo || + return Promise.resolve(Promise.all(this._options.include.filter(include => !(include.association instanceof BelongsTo || include.parent && include.parent.association instanceof BelongsToMany)).map(include => { let instances = this.get(include.as); - if (!instances) return Promise.resolve(); + if (!instances) return Promise.resolve(Promise.resolve()).then(() => result); if (!Array.isArray(instances)) instances = [instances]; - if (!instances.length) return Promise.resolve(); + if (!instances.length) return Promise.resolve(Promise.resolve()).then(() => result); const includeOptions = _(Utils.cloneDeep(include)) .omit(['association']) @@ -4071,9 +4080,9 @@ class Model { }).value(); // Instances will be updated in place so we can safely treat HasOne like a HasMany - return Promise.all(instances.map(instance => { + return Promise.resolve(Promise.all(instances.map(instance => { if (include.association instanceof BelongsToMany) { - return instance.save(includeOptions).then(() => { + return Promise.resolve(instance.save(includeOptions).then(() => { const values = {}; values[include.association.foreignKey] = this.get(this.constructor.primaryKeyAttribute, { raw: true }); values[include.association.otherKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); @@ -4092,20 +4101,21 @@ class Model { } } - return include.association.throughModel.create(values, includeOptions); - }); + return Promise.resolve(include.association.throughModel.create(values, includeOptions)).then(() => result); + })).then(() => result); } instance.set(include.association.foreignKey, this.get(include.association.sourceKey || this.constructor.primaryKeyAttribute, { raw: true }), { raw: true }); Object.assign(instance, include.association.scope); - return instance.save(includeOptions); - })); - })); + return Promise.resolve(instance.save(includeOptions)).then(() => result); + }))).then(() => result); + }))).then(() => result); }) - .tap(result => { - // Run after hook + .then(result => { + // Run after hook if (options.hooks) { - return this.constructor.runHooks(`after${hook}`, result, options); + return Promise.resolve(this.constructor.runHooks(`after${hook}`, result, options)).then(() => result); } + return result; }) .then(result => { for (const field of options.fields) { @@ -4138,12 +4148,13 @@ class Model { }); return this.constructor.findOne(options) - .tap(reload => { + .then(reload => { if (!reload) { throw new sequelizeErrors.InstanceError( 'Instance could not be reloaded because it does not exist anymore (find call returned null)' ); } + return reload; }) .then(reload => { // update the internal options of the instance @@ -4254,11 +4265,12 @@ class Model { return this.save(_.defaults({ hooks: false }, options)); } return this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options)); - }).tap(() => { + }).then(result => { // Run after hook if (options.hooks) { - return this.constructor.runHooks('afterDestroy', this, options); + return Promise.resolve(this.constructor.runHooks('afterDestroy', this, options)).then(() => result); } + return result; }); } @@ -4311,11 +4323,12 @@ class Model { this.setDataValue(deletedAtCol, deletedAtDefaultValue); return this.save(Object.assign({}, options, { hooks: false, omitNull: false })); - }).tap(() => { + }).then(result => { // Run after hook if (options.hooks) { - return this.constructor.runHooks('afterRestore', this, options); + return Promise.resolve(this.constructor.runHooks('afterRestore', this, options)).then(() => result); } + return result; }); } diff --git a/lib/sequelize.js b/lib/sequelize.js index 8a0342d25108..d27531e49509 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -1110,7 +1110,7 @@ class Sequelize { return Sequelize._clsRun(() => { return transaction.prepareEnvironment() .then(() => autoCallback(transaction)) - .tap(() => transaction.commit()) + .then(result => Promise.resolve(transaction.commit()).then(() => result)) .catch(err => { // Rollback transaction if not already finished (commit, rollback, etc) // and reject with original error (ignore any error in rollback) diff --git a/lib/transaction.js b/lib/transaction.js index a7955db9c18d..51350756c40b 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -69,10 +69,10 @@ class Transaction { return this.cleanup(); } return null; - }).tap( - () => Promise.each( + }).then( + result => Promise.resolve(Promise.each( this._afterCommitHooks, - hook => Promise.resolve(hook.apply(this, [this]))) + hook => Promise.resolve(hook.apply(this, [this])))).then(() => result) ); } @@ -133,11 +133,11 @@ class Transaction { throw setupErr; })); }) - .tap(() => { + .then(result => { if (useCLS && this.sequelize.constructor._cls) { this.sequelize.constructor._cls.set('transaction', this); } - return null; + return Promise.resolve(null).then(() => result); }); } diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index 6dab43bca149..033a80660222 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -72,9 +72,10 @@ if (dialect.match(/^postgres/)) { }] }); }).get('friends') - .tap(friends => { + .then(friends => { expect(friends).to.have.length(1); expect(friends[0].name).to.equal('John Smythe'); + return friends; }); }); diff --git a/test/integration/include/findAndCountAll.test.js b/test/integration/include/findAndCountAll.test.js index 67e92a131bd9..0617dc09bdd3 100644 --- a/test/integration/include/findAndCountAll.test.js +++ b/test/integration/include/findAndCountAll.test.js @@ -231,13 +231,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Foo.findAndCountAll({ include: [{ model: Bar, required: true }], limit: 2 - }).tap(() => { - return Foo.findAll({ + }).then(result => { + return Promise.resolve(Foo.findAll({ include: [{ model: Bar, required: true }], limit: 2 }).then(items => { expect(items.length).to.equal(2); - }); + })).then(() => result); }); }).then(result => { expect(result.count).to.equal(4); diff --git a/test/integration/instance/destroy.test.js b/test/integration/instance/destroy.test.js index cc8715f515d0..58713466e2d4 100644 --- a/test/integration/instance/destroy.test.js +++ b/test/integration/instance/destroy.test.js @@ -247,11 +247,12 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: 'foo' } }); - }).tap(user => { + }).then(user => { expect(user).to.be.ok; expect(moment.utc(user.deletedAt).startOf('second').toISOString()) .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); expect(user.username).to.equal('foo'); + return user; }).then(user => { // update model and delete again user.username = 'bar'; @@ -345,15 +346,17 @@ describe(Support.getTestDialectTeaser('Instance'), () => { afterSave.resetHistory(); return user.destroy(); - }).tap(() => { + }).then(result => { expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave'); expect(afterSave.callCount).to.equal(0, 'should not call afterSave'); + return result; }).then(user => { // now try with `hooks: true` return user.destroy({ hooks: true }); - }).tap(() => { + }).then(result => { expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave even if `hooks: true`'); expect(afterSave.callCount).to.equal(0, 'should not call afterSave even if `hooks: true`'); + return result; }); }); diff --git a/test/integration/instance/reload.test.js b/test/integration/instance/reload.test.js index 193c83919580..af00f197c71c 100644 --- a/test/integration/instance/reload.test.js +++ b/test/integration/instance/reload.test.js @@ -110,14 +110,14 @@ describe(Support.getTestDialectTeaser('Instance'), () => { return this.User.create({ aNumber: 1, bNumber: 1 - }).tap(user => { - return this.User.update({ + }).then(user => { + return Promise.resolve(this.User.update({ bNumber: 2 }, { where: { id: user.get('id') } - }); + })).then(() => user); }).then(user => { return user.reload({ attributes: ['bNumber'] From 72a940d81daf074264df3dd8e6cb9124b5db94cb Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 5 Apr 2020 23:01:59 -0500 Subject: [PATCH 072/414] refactor: replace bluebird .get (#12071) --- lib/query-interface.js | 2 +- test/integration/data-types.test.js | 4 ++-- test/integration/dialects/postgres/dao.test.js | 2 +- test/integration/dialects/sqlite/dao.test.js | 4 ++-- test/integration/include/paranoid.test.js | 2 +- test/integration/model/scope/associations.test.js | 12 ++++++------ test/integration/sequelize.test.js | 14 +++++++------- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/query-interface.js b/lib/query-interface.js index 1e97ba748f5b..86ab03c40195 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -277,7 +277,7 @@ class QueryInterface { } } - return Promise.all(promises).get(0); + return Promise.all(promises).then(obj => obj[0]); }); } diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js index 127c49c41683..c8b6095ada0f 100644 --- a/test/integration/data-types.test.js +++ b/test/integration/data-types.test.js @@ -47,7 +47,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { dateField: moment('2011 10 31', 'YYYY MM DD') }); }).then(() => { - return User.findAll().get(0); + return User.findAll().then(obj => obj[0]); }).then(user => { expect(parse).to.have.been.called; expect(stringify).to.have.been.called; @@ -87,7 +87,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { field: value }); }).then(() => { - return User.findAll().get(0); + return User.findAll().then(obj => obj[0]); }).then(() => { expect(parse).to.have.been.called; if (options && options.useBindParam) { diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index 033a80660222..3b9ff4527ed4 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -71,7 +71,7 @@ if (dialect.match(/^postgres/)) { name: 'John Smythe' }] }); - }).get('friends') + }).then(obj => obj['friends']) .then(friends => { expect(friends).to.have.length(1); expect(friends[0].name).to.equal('John Smythe'); diff --git a/test/integration/dialects/sqlite/dao.test.js b/test/integration/dialects/sqlite/dao.test.js index 6c9b10666f20..0361d21e64e6 100644 --- a/test/integration/dialects/sqlite/dao.test.js +++ b/test/integration/dialects/sqlite/dao.test.js @@ -52,7 +52,7 @@ if (dialect === 'sqlite') { return this.User.create({ dateField: new Date(2010, 10, 10) }).then(() => { - return this.User.findAll().get(0); + return this.User.findAll().then(obj => obj[0]); }).then(user => { expect(user.get('dateField')).to.be.an.instanceof(Date); expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10)); @@ -67,7 +67,7 @@ if (dialect === 'sqlite') { }, { include: [this.Project] }).then(() => { return this.User.findAll({ include: [this.Project] - }).get(0); + }).then(obj => obj[0]); }).then(user => { expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date); expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5)); diff --git a/test/integration/include/paranoid.test.js b/test/integration/include/paranoid.test.js index eebdb2d64c11..0ae70c008f14 100644 --- a/test/integration/include/paranoid.test.js +++ b/test/integration/include/paranoid.test.js @@ -123,7 +123,7 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { return X.findAll({ include: [Y] - }).get(0); + }).then(obj => obj[0]); }).then(x => { expect(x.ys).to.have.length(0); }); diff --git a/test/integration/model/scope/associations.test.js b/test/integration/model/scope/associations.test.js index 8c47ea857e4e..bdc3960b5513 100644 --- a/test/integration/model/scope/associations.test.js +++ b/test/integration/model/scope/associations.test.js @@ -127,7 +127,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should apply default scope when including an associations', function() { return this.Company.findAll({ include: [this.UserAssociation] - }).get(0).then(company => { + }).then(obj => obj[0]).then(company => { expect(company.users).to.have.length(2); }); }); @@ -135,7 +135,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should apply default scope when including a model', function() { return this.Company.findAll({ include: [{ model: this.ScopeMe, as: 'users' }] - }).get(0).then(company => { + }).then(obj => obj[0]).then(company => { expect(company.users).to.have.length(2); }); }); @@ -143,7 +143,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should be able to include a scoped model', function() { return this.Company.findAll({ include: [{ model: this.ScopeMe.scope('isTony'), as: 'users' }] - }).get(0).then(company => { + }).then(obj => obj[0]).then(company => { expect(company.users).to.have.length(1); expect(company.users[0].get('username')).to.equal('tony'); }); @@ -191,7 +191,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('belongsToMany', function() { - return this.Project.findAll().get(0).then(p => { + return this.Project.findAll().then(obj => obj[0]).then(p => { return p.getCompanies({ scope: false }); }).then(companies => { expect(companies).to.have.length(2); @@ -230,7 +230,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('belongsToMany', function() { - return this.Project.findAll().get(0).then(p => { + return this.Project.findAll().then(obj => obj[0]).then(p => { return p.getCompanies(); }).then(companies => { expect(companies).to.have.length(1); @@ -271,7 +271,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('belongsToMany', function() { - return this.Project.findAll().get(0).then(p => { + return this.Project.findAll().then(obj => obj[0]).then(p => { return p.getCompanies({ scope: 'reversed' }); }).then(companies => { expect(companies).to.have.length(2); diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index cdca083c3d4a..ae360a6cc401 100755 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -590,7 +590,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - return expect(this.sequelize.query(sql, { raw: true, nest: false }).get(0)).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); + return expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); }); it('destructs dot separated attributes when doing a raw query using nest', function() { @@ -609,22 +609,22 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); it('replaces named parameters with the passed object', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) + return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); }); it('replaces named parameters with the passed object and ignore those which does not qualify', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) + return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); }); it('replaces named parameters with the passed object using the same key twice', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) + return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); }); it('replaces named parameters with the passed object having a null property', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).get(0)) + return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: null }]); }); @@ -789,12 +789,12 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (Support.getTestDialect() === 'postgres') { it('replaces named parameters with the passed object and ignores casts', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) + return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); }); it('supports WITH queries', function() { - return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').get(0)) + return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) .to.eventually.deep.equal([{ 'sum': '5050' }]); }); } From 597fc8aee51ae53c92cb7e41b6716ba05080ad93 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 5 Apr 2020 23:06:32 -0500 Subject: [PATCH 073/414] refactor: replace bluebird .each (#12074) --- lib/dialects/sqlite/query-interface.js | 142 ++- lib/hooks.js | 9 +- lib/model.js | 183 ++-- lib/query-interface.js | 96 +- lib/sequelize.js | 71 +- lib/transaction.js | 30 +- .../integration/associations/has-many.test.js | 19 +- test/integration/associations/scope.test.js | 36 +- test/integration/error.test.js | 10 +- test/integration/include/findAll.test.js | 546 +++++------ test/integration/include/schema.test.js | 844 ++++++++---------- test/integration/model.test.js | 6 +- test/integration/model/create.test.js | 18 +- test/unit/model/find-or-create.test.js | 6 +- 14 files changed, 943 insertions(+), 1073 deletions(-) diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index da9a7fdc9b81..1d8b27666656 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -1,7 +1,6 @@ 'use strict'; const _ = require('lodash'); -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const QueryTypes = require('../../query-types'); @@ -27,17 +26,16 @@ const QueryTypes = require('../../query-types'); @since 1.6.0 @private */ -function removeColumn(qi, tableName, attributeName, options) { +async function removeColumn(qi, tableName, attributeName, options) { options = options || {}; - return qi.describeTable(tableName, options).then(fields => { - delete fields[attributeName]; + const fields = await qi.describeTable(tableName, options); + delete fields[attributeName]; - const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); - const subQueries = sql.split(';').filter(q => q !== ''); + const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); + const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); } exports.removeColumn = removeColumn; @@ -55,18 +53,17 @@ exports.removeColumn = removeColumn; @since 1.6.0 @private */ -function changeColumn(qi, tableName, attributes, options) { +async function changeColumn(qi, tableName, attributes, options) { const attributeName = Object.keys(attributes)[0]; options = options || {}; - return qi.describeTable(tableName, options).then(fields => { - fields[attributeName] = attributes[attributeName]; + const fields = await qi.describeTable(tableName, options); + fields[attributeName] = attributes[attributeName]; - const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); - const subQueries = sql.split(';').filter(q => q !== ''); + const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); + const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); } exports.changeColumn = changeColumn; @@ -85,18 +82,17 @@ exports.changeColumn = changeColumn; @since 1.6.0 @private */ -function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options) { +async function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options) { options = options || {}; - return qi.describeTable(tableName, options).then(fields => { - fields[attrNameAfter] = _.clone(fields[attrNameBefore]); - delete fields[attrNameBefore]; + const fields = await qi.describeTable(tableName, options); + fields[attrNameAfter] = _.clone(fields[attrNameBefore]); + delete fields[attrNameBefore]; - const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); - const subQueries = sql.split(';').filter(q => q !== ''); + const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); + const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); } exports.renameColumn = renameColumn; @@ -108,45 +104,42 @@ exports.renameColumn = renameColumn; * * @private */ -function removeConstraint(qi, tableName, constraintName, options) { +async function removeConstraint(qi, tableName, constraintName, options) { let createTableSql; - return qi.showConstraint(tableName, constraintName) - .then(constraints => { - // sqlite can't show only one constraint, so we find here the one to remove - const constraint = constraints.find(constaint => constaint.constraintName === constraintName); - - if (constraint) { - createTableSql = constraint.sql; - constraint.constraintName = qi.QueryGenerator.quoteIdentifier(constraint.constraintName); - let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; - - if (constraint.constraintType === 'FOREIGN KEY') { - const referenceTableName = qi.QueryGenerator.quoteTable(constraint.referenceTableName); - constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => qi.QueryGenerator.quoteIdentifier(columnName)); - const referenceTableKeys = constraint.referenceTableKeys.join(', '); - constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; - constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; - constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; - } - - createTableSql = createTableSql.replace(constraintSnippet, ''); - createTableSql += ';'; - - return qi.describeTable(tableName, options); - } - throw new sequelizeErrors.UnknownConstraintError({ - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - }) - .then(fields => { - const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); - - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); + const constraints = await qi.showConstraint(tableName, constraintName); + // sqlite can't show only one constraint, so we find here the one to remove + const constraint = constraints.find(constaint => constaint.constraintName === constraintName); + + if (!constraint) { + throw new sequelizeErrors.UnknownConstraintError({ + message: `Constraint ${constraintName} on table ${tableName} does not exist`, + constraint: constraintName, + table: tableName }); + } + createTableSql = constraint.sql; + constraint.constraintName = qi.QueryGenerator.quoteIdentifier(constraint.constraintName); + let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; + + if (constraint.constraintType === 'FOREIGN KEY') { + const referenceTableName = qi.QueryGenerator.quoteTable(constraint.referenceTableName); + constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => qi.QueryGenerator.quoteIdentifier(columnName)); + const referenceTableKeys = constraint.referenceTableKeys.join(', '); + constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; + constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; + constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; + } + + createTableSql = createTableSql.replace(constraintSnippet, ''); + createTableSql += ';'; + + const fields = await qi.describeTable(tableName, options); + + const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); + const subQueries = sql.split(';').filter(q => q !== ''); + + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); } exports.removeConstraint = removeConstraint; @@ -157,27 +150,22 @@ exports.removeConstraint = removeConstraint; * * @private */ -function addConstraint(qi, tableName, options) { +async function addConstraint(qi, tableName, options) { const constraintSnippet = qi.QueryGenerator.getConstraintSnippet(tableName, options); const describeCreateTableSql = qi.QueryGenerator.describeCreateTableQuery(tableName); - let createTableSql; - return qi.sequelize.query(describeCreateTableSql, Object.assign({}, options, { type: QueryTypes.SELECT, raw: true })) - .then(constraints => { - const sql = constraints[0].sql; - const index = sql.length - 1; - //Replace ending ')' with constraint snippet - Simulates String.replaceAt - //http://stackoverflow.com/questions/1431094 - createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`; - - return qi.describeTable(tableName, options); - }) - .then(fields => { - const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); - - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); + const constraints = await qi.sequelize.query(describeCreateTableSql, Object.assign({}, options, { type: QueryTypes.SELECT, raw: true })); + let sql = constraints[0].sql; + const index = sql.length - 1; + //Replace ending ')' with constraint snippet - Simulates String.replaceAt + //http://stackoverflow.com/questions/1431094 + const createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`; + + const fields = await qi.describeTable(tableName, options); + sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); + const subQueries = sql.split(';').filter(q => q !== ''); + + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); } exports.addConstraint = addConstraint; diff --git a/lib/hooks.js b/lib/hooks.js index 24c0832aa940..69602a048b4d 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const { logger } = require('./utils/logger'); -const Promise = require('./promise'); const debug = logger.debugContext('hooks'); const hookTypes = { @@ -89,7 +88,7 @@ const Hooks = { }); }, - runHooks(hooks, ...hookArgs) { + async runHooks(hooks, ...hookArgs) { if (!hooks) throw new Error('runHooks requires at least 1 argument'); let hookType; @@ -121,14 +120,14 @@ const Hooks = { } // asynchronous hooks (default) - return Promise.each(hooks, hook => { + for (let hook of hooks) { if (typeof hook === 'object') { hook = hook.fn; } debug(`running hook ${hookType}`); - return hook.apply(this, hookArgs); - }).then(() => undefined); + await hook.apply(this, hookArgs); + } }, /** diff --git a/lib/model.js b/lib/model.js index c4cc0fa6e8b0..596dda608ab6 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1272,109 +1272,102 @@ class Model { * * @returns {Promise} */ - static sync(options) { + static async sync(options) { options = Object.assign({}, this.options, options); options.hooks = options.hooks === undefined ? true : !!options.hooks; const attributes = this.tableAttributes; const rawAttributes = this.fieldRawAttributesMap; - return Promise.resolve().then(() => { - if (options.hooks) { - return this.runHooks('beforeSync', options); - } - }).then(() => { - if (options.force) { - return this.drop(options); - } - }) - .then(() => this.QueryInterface.createTable(this.getTableName(options), attributes, options, this)) - .then(() => { - if (!options.alter) { - return; + if (options.hooks) { + await this.runHooks('beforeSync', options); + } + if (options.force) { + await this.drop(options); + } + await this.QueryInterface.createTable(this.getTableName(options), attributes, options, this); + + if (options.alter) { + const tableInfos = await Promise.all([ + this.QueryInterface.describeTable(this.getTableName(options)), + this.QueryInterface.getForeignKeyReferencesForTable(this.getTableName(options)) + ]); + const columns = tableInfos[0]; + // Use for alter foreign keys + const foreignKeyReferences = tableInfos[1]; + + const removedConstraints = {}; + + for (const columnName in attributes) { + if (!Object.prototype.hasOwnProperty.call(attributes, columnName)) continue; + if (!columns[columnName] && !columns[attributes[columnName].field]) { + await this.QueryInterface.addColumn(this.getTableName(options), attributes[columnName].field || columnName, attributes[columnName]); } - return Promise.all([ - this.QueryInterface.describeTable(this.getTableName(options)), - this.QueryInterface.getForeignKeyReferencesForTable(this.getTableName(options)) - ]) - .then(tableInfos => { - const columns = tableInfos[0]; - // Use for alter foreign keys - const foreignKeyReferences = tableInfos[1]; - - const changes = []; // array of promises to run - const removedConstraints = {}; - - _.each(attributes, (columnDesc, columnName) => { - if (!columns[columnName] && !columns[attributes[columnName].field]) { - changes.push(() => this.QueryInterface.addColumn(this.getTableName(options), attributes[columnName].field || columnName, attributes[columnName])); - } - }); + } - if (options.alter === true || typeof options.alter === 'object' && options.alter.drop !== false) { - _.each(columns, (columnDesc, columnName) => { - const currentAttribute = rawAttributes[columnName]; - if (!currentAttribute) { - changes.push(() => this.QueryInterface.removeColumn(this.getTableName(options), columnName, options)); - } else if (!currentAttribute.primaryKey) { - // Check foreign keys. If it's a foreign key, it should remove constraint first. - const references = currentAttribute.references; - if (currentAttribute.references) { - const database = this.sequelize.config.database; - const schema = this.sequelize.config.schema; - // Find existed foreign keys - _.each(foreignKeyReferences, foreignKeyReference => { - const constraintName = foreignKeyReference.constraintName; - if (!!constraintName - && foreignKeyReference.tableCatalog === database - && (schema ? foreignKeyReference.tableSchema === schema : true) - && foreignKeyReference.referencedTableName === references.model - && foreignKeyReference.referencedColumnName === references.key - && (schema ? foreignKeyReference.referencedTableSchema === schema : true) - && !removedConstraints[constraintName]) { - // Remove constraint on foreign keys. - changes.push(() => this.QueryInterface.removeConstraint(this.getTableName(options), constraintName, options)); - removedConstraints[constraintName] = true; - } - }); - } - changes.push(() => this.QueryInterface.changeColumn(this.getTableName(options), columnName, currentAttribute)); - } - }); + if (options.alter === true || typeof options.alter === 'object' && options.alter.drop !== false) { + for (const columnName in columns) { + if (!Object.prototype.hasOwnProperty.call(columns, columnName)) continue; + const currentAttribute = rawAttributes[columnName]; + if (!currentAttribute) { + await this.QueryInterface.removeColumn(this.getTableName(options), columnName, options); + continue; + } + if (currentAttribute.primaryKey) continue; + // Check foreign keys. If it's a foreign key, it should remove constraint first. + const references = currentAttribute.references; + if (currentAttribute.references) { + const database = this.sequelize.config.database; + const schema = this.sequelize.config.schema; + // Find existed foreign keys + for (const foreignKeyReference of foreignKeyReferences) { + const constraintName = foreignKeyReference.constraintName; + if (!!constraintName + && foreignKeyReference.tableCatalog === database + && (schema ? foreignKeyReference.tableSchema === schema : true) + && foreignKeyReference.referencedTableName === references.model + && foreignKeyReference.referencedColumnName === references.key + && (schema ? foreignKeyReference.referencedTableSchema === schema : true) + && !removedConstraints[constraintName]) { + // Remove constraint on foreign keys. + await this.QueryInterface.removeConstraint(this.getTableName(options), constraintName, options); + removedConstraints[constraintName] = true; + } } - - return Promise.each(changes, f => f()); - }); - }) - .then(() => this.QueryInterface.showIndex(this.getTableName(options), options)) - .then(indexes => { - indexes = this._indexes.filter(item1 => - !indexes.some(item2 => item1.name === item2.name) - ).sort((index1, index2) => { - if (this.sequelize.options.dialect === 'postgres') { - // move concurrent indexes to the bottom to avoid weird deadlocks - if (index1.concurrently === true) return 1; - if (index2.concurrently === true) return -1; } + await this.QueryInterface.changeColumn(this.getTableName(options), columnName, currentAttribute); + } + } + } + let indexes = await this.QueryInterface.showIndex(this.getTableName(options), options); + indexes = this._indexes.filter(item1 => + !indexes.some(item2 => item1.name === item2.name) + ).sort((index1, index2) => { + if (this.sequelize.options.dialect === 'postgres') { + // move concurrent indexes to the bottom to avoid weird deadlocks + if (index1.concurrently === true) return 1; + if (index2.concurrently === true) return -1; + } - return 0; - }); + return 0; + }); - return Promise.each(indexes, index => this.QueryInterface.addIndex( - this.getTableName(options), - Object.assign({ - logging: options.logging, - benchmark: options.benchmark, - transaction: options.transaction, - schema: options.schema - }, index), - this.tableName - )); - }).then(() => { - if (options.hooks) { - return this.runHooks('afterSync', options); - } - }).then(() => this); + for (const index of indexes) { + await this.QueryInterface.addIndex( + this.getTableName(options), + Object.assign({ + logging: options.logging, + benchmark: options.benchmark, + transaction: options.transaction, + schema: options.schema + }, index), + this.tableName + ); + } + if (options.hooks) { + await this.runHooks('afterSync', options); + } + return this; } /** @@ -2341,7 +2334,8 @@ class Model { } return [instance, true]; - }).catch(sequelizeErrors.UniqueConstraintError, err => { + }).catch(err => { + if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; const flattenedWhere = Utils.flattenObjectDeep(options.where); const flattenedWhereKeys = Object.keys(flattenedWhere).map(name => _.last(name.split('.'))); const whereFields = flattenedWhereKeys.map(name => _.get(this.rawAttributes, `${name}.field`, name)); @@ -2413,7 +2407,10 @@ class Model { return this.create(values, options) .then(result => [result, true]) - .catch(sequelizeErrors.UniqueConstraintError, () => this.findOne(options).then(result => [result, false])); + .catch(err => { + if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; + return this.findOne(options).then(result => [result, false]); + }); }); } diff --git a/lib/query-interface.js b/lib/query-interface.js index 86ab03c40195..bf192b0ef220 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -289,48 +289,46 @@ class QueryInterface { * * @returns {Promise} */ - dropAllTables(options) { + async dropAllTables(options) { options = options || {}; const skip = options.skip || []; - const dropAllTables = tableNames => Promise.each(tableNames, tableName => { - // if tableName is not in the Array of tables names then don't drop it - if (!skip.includes(tableName.tableName || tableName)) { - return this.dropTable(tableName, Object.assign({}, options, { cascade: true }) ); - } - }); - - return this.showAllTables(options).then(tableNames => { - if (this.sequelize.options.dialect === 'sqlite') { - return this.sequelize.query('PRAGMA foreign_keys;', options).then(result => { - const foreignKeysAreEnabled = result.foreign_keys === 1; + const dropAllTables = async tableNames => { + for (const tableName of tableNames) { + // if tableName is not in the Array of tables names then don't drop it + if (!skip.includes(tableName.tableName || tableName)) { + await this.dropTable(tableName, Object.assign({}, options, { cascade: true }) ); + } + } + }; - if (foreignKeysAreEnabled) { - return this.sequelize.query('PRAGMA foreign_keys = OFF', options) - .then(() => dropAllTables(tableNames)) - .then(() => this.sequelize.query('PRAGMA foreign_keys = ON', options)); - } - return dropAllTables(tableNames); - }); + const tableNames = await this.showAllTables(options); + if (this.sequelize.options.dialect === 'sqlite') { + const result = await this.sequelize.query('PRAGMA foreign_keys;', options); + const foreignKeysAreEnabled = result.foreign_keys === 1; + + if (foreignKeysAreEnabled) { + await this.sequelize.query('PRAGMA foreign_keys = OFF', options); + await dropAllTables(tableNames); + await this.sequelize.query('PRAGMA foreign_keys = ON', options); + } else { + await dropAllTables(tableNames); } - return this.getForeignKeysForTables(tableNames, options).then(foreignKeys => { - const queries = []; - - tableNames.forEach(tableName => { - let normalizedTableName = tableName; - if (_.isObject(tableName)) { - normalizedTableName = `${tableName.schema}.${tableName.tableName}`; - } + } else { + const foreignKeys = await this.getForeignKeysForTables(tableNames, options); - foreignKeys[normalizedTableName].forEach(foreignKey => { - queries.push(this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey)); - }); - }); + for (const tableName of tableNames) { + let normalizedTableName = tableName; + if (_.isObject(tableName)) { + normalizedTableName = `${tableName.schema}.${tableName.tableName}`; + } - return Promise.each(queries, q => this.sequelize.query(q, options)) - .then(() => dropAllTables(tableNames)); - }); - }); + for (const foreignKey of foreignKeys[normalizedTableName]) { + await this.sequelize.query(this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey)); + } + } + await dropAllTables(tableNames); + } } /** @@ -485,7 +483,7 @@ class QueryInterface { return data; }).catch(e => { if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') { - throw Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); } throw e; @@ -1056,7 +1054,7 @@ class QueryInterface { return this.sequelize.query(sql, options); } - delete(instance, tableName, identifier, options) { + async delete(instance, tableName, identifier, options) { const cascades = []; const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); @@ -1078,21 +1076,15 @@ class QueryInterface { } } - return Promise.each(cascades, cascade => { - return instance[cascade](options).then(instances => { - // Check for hasOne relationship with non-existing associate ("has zero") - if (!instances) { - return Promise.resolve(); - } - - if (!Array.isArray(instances)) instances = [instances]; - - return Promise.each(instances, instance => instance.destroy(options)); - }); - }).then(() => { - options.instance = instance; - return this.sequelize.query(sql, options); - }); + for (const cascade of cascades) { + let instances = await instance[cascade](options); + // Check for hasOne relationship with non-existing associate ("has zero") + if (!instances) continue; + if (!Array.isArray(instances)) instances = [instances]; + for (const instance of instances) await instance.destroy(options); + } + options.instance = instance; + return await this.sequelize.query(sql, options); } /** diff --git a/lib/sequelize.js b/lib/sequelize.js index d27531e49509..0a1fe00764a5 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -786,47 +786,45 @@ class Sequelize { * * @returns {Promise} */ - sync(options) { + async sync(options) { options = _.clone(options) || {}; options.hooks = options.hooks === undefined ? true : !!options.hooks; options = _.defaults(options, this.options.sync, this.options); if (options.match) { if (!options.match.test(this.config.database)) { - return Promise.reject(new Error(`Database "${this.config.database}" does not match sync match parameter "${options.match}"`)); + throw new Error(`Database "${this.config.database}" does not match sync match parameter "${options.match}"`); } } - return Promise.resolve().then(() => { - if (options.hooks) { - return this.runHooks('beforeBulkSync', options); - } - }).then(() => { - if (options.force) { - return this.drop(options); - } - }).then(() => { - const models = []; - - // Topologically sort by foreign key constraints to give us an appropriate - // creation order - this.modelManager.forEachModel(model => { - if (model) { - models.push(model); - } else { - // DB should throw an SQL error if referencing non-existent table - } - }); - - // no models defined, just authenticate - if (!models.length) return this.authenticate(options); + if (options.hooks) { + await this.runHooks('beforeBulkSync', options); + } + if (options.force) { + await this.drop(options); + } + const models = []; - return Promise.each(models, model => model.sync(options)); - }).then(() => { - if (options.hooks) { - return this.runHooks('afterBulkSync', options); + // Topologically sort by foreign key constraints to give us an appropriate + // creation order + this.modelManager.forEachModel(model => { + if (model) { + models.push(model); + } else { + // DB should throw an SQL error if referencing non-existent table } - }).then(() => this); + }); + + // no models defined, just authenticate + if (!models.length) { + await this.authenticate(options); + } else { + for (const model of models) await model.sync(options); + } + if (options.hooks) { + await this.runHooks('afterBulkSync', options); + } + return this; } /** @@ -840,7 +838,7 @@ class Sequelize { * @see * {@link Model.truncate} for more information */ - truncate(options) { + async truncate(options) { const models = []; this.modelManager.forEachModel(model => { @@ -849,12 +847,11 @@ class Sequelize { } }, { reverse: false }); - const truncateModel = model => model.truncate(options); - if (options && options.cascade) { - return Promise.each(models, truncateModel); + for (const model of models) model.truncate(options); + } else { + await Promise.all(models.map(model => model.truncate(options))); } - return Promise.all(models.map(truncateModel)); } /** @@ -869,7 +866,7 @@ class Sequelize { * * @returns {Promise} */ - drop(options) { + async drop(options) { const models = []; this.modelManager.forEachModel(model => { @@ -878,7 +875,7 @@ class Sequelize { } }, { reverse: false }); - return Promise.each(models, model => model.drop(options)); + for (const model of models) await model.drop(options); } /** diff --git a/lib/transaction.js b/lib/transaction.js index 51350756c40b..4ff44a3394f3 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -52,28 +52,24 @@ class Transaction { * * @returns {Promise} */ - commit() { + async commit() { if (this.finished) { - return Promise.reject(new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`)); + throw new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`); } this._clearCls(); - return this - .sequelize - .getQueryInterface() - .commitTransaction(this, this.options) - .finally(() => { - this.finished = 'commit'; - if (!this.parent) { - return this.cleanup(); - } - return null; - }).then( - result => Promise.resolve(Promise.each( - this._afterCommitHooks, - hook => Promise.resolve(hook.apply(this, [this])))).then(() => result) - ); + try { + return await this.sequelize.getQueryInterface().commitTransaction(this, this.options); + } finally { + this.finished = 'commit'; + if (!this.parent) { + await this.cleanup(); + } + for (const hook of this._afterCommitHooks) { + await hook.apply(this, [this]); + } + } } /** diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index b84f5864af0f..cef766345cb2 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -1300,7 +1300,8 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { ctx.task = task; return user.setTasks([task]); }).then(() => { - return ctx.user.destroy().catch(Sequelize.ForeignKeyConstraintError, () => { + return ctx.user.destroy().catch(err => { + if (!(err instanceof Sequelize.ForeignKeyConstraintError)) throw err; // Should fail due to FK violation return Task.findAll(); }); @@ -1329,8 +1330,9 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - .catch(Sequelize.ForeignKeyConstraintError, () => { - // Should fail due to FK violation + .catch(err => { + if (!(err instanceof Sequelize.ForeignKeyConstraintError)) throw err; + // Should fail due to FK violation return Task.findAll(); }); }).then(tasks => { @@ -1362,21 +1364,20 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { expect(Account.rawAttributes.UserId.field).to.equal('UserId'); }); - it('can specify data type for auto-generated relational keys', function() { + it('can specify data type for auto-generated relational keys', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING], Tasks = {}; - return Promise.each(dataTypes, dataType => { + for (const dataType of dataTypes) { const tableName = `TaskXYZ_${dataType.key}`; Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING }); User.hasMany(Tasks[dataType], { foreignKey: 'userId', keyType: dataType, constraints: false }); - return Tasks[dataType].sync({ force: true }).then(() => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); - }); + await Tasks[dataType].sync({ force: true }); + expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); + } }); it('infers the keyType if none provided', function() { diff --git a/test/integration/associations/scope.test.js b/test/integration/associations/scope.test.js index bc55d320fa08..3789fc65728d 100644 --- a/test/integration/associations/scope.test.js +++ b/test/integration/associations/scope.test.js @@ -272,29 +272,25 @@ describe(Support.getTestDialectTeaser('associations'), () => { expect(logs[0]).to.equal(logs[1]); }); }); - it('should created included association with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return this.Post.create({ - comments: [{ - title: 'I am a comment created with a post' - }, { - title: 'I am a second comment created with a post' - }] + it('should created included association with scope values', async function() { + await this.sequelize.sync({ force: true }); + let post = await this.Post.create({ + comments: [{ + title: 'I am a comment created with a post' }, { - include: [{ model: this.Comment, as: 'comments' }] - }); - }).then(post => { - this.post = post; - return post.comments; - }).each(comment => { + title: 'I am a second comment created with a post' + }] + }, { + include: [{ model: this.Comment, as: 'comments' }] + }); + this.post = post; + for (const comment of post.comments) { expect(comment.get('commentable')).to.equal('post'); - }).then(() => { - return this.Post.scope('withComments').findByPk(this.post.id); - }).then(post => { - return post.getComments(); - }).each(comment => { + } + post = await this.Post.scope('withComments').findByPk(this.post.id); + for (const comment of post.comments) { expect(comment.get('commentable')).to.equal('post'); - }); + } }); it('should include associations with operator scope values', function() { return this.sequelize.sync({ force: true }).then(() => { diff --git a/test/integration/error.test.js b/test/integration/error.test.js index e7500cfe3ae5..59ab2ced8c24 100644 --- a/test/integration/error.test.js +++ b/test/integration/error.test.js @@ -312,7 +312,10 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { return this.sequelize.sync({ force: true }).then(() => { return User.create(record); }).then(() => { - return User.create(record).catch(constraintTest.exception, spy); + return User.create(record).catch(err => { + if (!(err instanceof constraintTest.exception)) throw err; + return spy(err); + }); }).then(() => { expect(spy).to.have.been.calledOnce; }); @@ -333,7 +336,10 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { return User.create({ name: 'jan' }); }).then(() => { // If the error was successfully parsed, we can catch it! - return User.create({ name: 'jan' }).catch(Sequelize.UniqueConstraintError, spy); + return User.create({ name: 'jan' }).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + return spy(err); + }); }).then(() => { expect(spy).to.have.been.calledOnce; }); diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index 846b728d2d05..954a33548edc 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -16,7 +16,7 @@ const sortById = function(a, b) { describe(Support.getTestDialectTeaser('Include'), () => { describe('findAll', () => { beforeEach(function() { - this.fixtureA = function() { + this.fixtureA = async function() { const User = this.sequelize.define('User', {}), Company = this.sequelize.define('Company', { name: DataTypes.STRING @@ -84,114 +84,90 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' }, - { name: 'Managers' } - ]).then(() => { - return Group.findAll(); - }), - companies: Company.bulkCreate([ - { name: 'Sequelize' }, - { name: 'Coca Cola' }, - { name: 'Bonanza' }, - { name: 'NYSE' }, - { name: 'Coshopr' } - ]).then(() => { - return Company.findAll(); - }), - ranks: Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, - { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } - ]).then(() => { - return Rank.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' }, - { name: 'D' }, - { name: 'E' } - ]).then(() => { - return Tag.findAll(); - }) - }).then(results => { - const groups = results.groups, - ranks = results.ranks, - tags = results.tags, - companies = results.companies; - - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.props({ - user: User.create(), - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Bed' }, - { title: 'Pen' }, - { title: 'Monitor' } - ]).then(() => { - return Product.findAll(); - }) - }).then(results => { - const user = results.user, - products = results.products, - groupMembers = [ - { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } - ]; - if (i < 3) { - groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); - } - - return Promise.join( - GroupMember.bulkCreate(groupMembers), - user.setProducts([ - products[i * 5 + 0], - products[i * 5 + 1], - products[i * 5 + 3] - ]), - Promise.join( - products[i * 5 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 5 + 1].setTags([ - tags[1] - ]), - products[i * 5 + 0].setCategory(tags[1]), - products[i * 5 + 2].setTags([ - tags[0] - ]), - products[i * 5 + 3].setTags([ - tags[0] - ]) - ), - Promise.join( - products[i * 5 + 0].setCompany(companies[4]), - products[i * 5 + 1].setCompany(companies[3]), - products[i * 5 + 2].setCompany(companies[2]), - products[i * 5 + 3].setCompany(companies[1]), - products[i * 5 + 4].setCompany(companies[0]) - ), - Price.bulkCreate([ - { ProductId: products[i * 5 + 0].id, value: 5 }, - { ProductId: products[i * 5 + 0].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 5 }, - { ProductId: products[i * 5 + 1].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 15 }, - { ProductId: products[i * 5 + 1].id, value: 20 }, - { ProductId: products[i * 5 + 2].id, value: 20 }, - { ProductId: products[i * 5 + 3].id, value: 20 } - ]) - ); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' }, + { name: 'Managers' } + ]); + const groups = await Group.findAll(); + await Company.bulkCreate([ + { name: 'Sequelize' }, + { name: 'Coca Cola' }, + { name: 'Bonanza' }, + { name: 'NYSE' }, + { name: 'Coshopr' } + ]); + const companies = await Company.findAll(); + await Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, + { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } + ]); + const ranks = await Rank.findAll(); + await Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' }, + { name: 'D' }, + { name: 'E' } + ]); + const tags = await Tag.findAll(); + for (const i of [0, 1, 2, 3, 4]) { + const user = await User.create(); + await Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Bed' }, + { title: 'Pen' }, + { title: 'Monitor' } + ]); + const products = await Product.findAll(); + const groupMembers = [ + { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } + ]; + if (i < 3) { + groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); + } + await Promise.all([ + GroupMember.bulkCreate(groupMembers), + user.setProducts([ + products[i * 5 + 0], + products[i * 5 + 1], + products[i * 5 + 3] + ]), + products[i * 5 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 5 + 1].setTags([ + tags[1] + ]), + products[i * 5 + 0].setCategory(tags[1]), + products[i * 5 + 2].setTags([ + tags[0] + ]), + products[i * 5 + 3].setTags([ + tags[0] + ]), + products[i * 5 + 0].setCompany(companies[4]), + products[i * 5 + 1].setCompany(companies[3]), + products[i * 5 + 2].setCompany(companies[2]), + products[i * 5 + 3].setCompany(companies[1]), + products[i * 5 + 4].setCompany(companies[0]), + Price.bulkCreate([ + { ProductId: products[i * 5 + 0].id, value: 5 }, + { ProductId: products[i * 5 + 0].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 5 }, + { ProductId: products[i * 5 + 1].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 15 }, + { ProductId: products[i * 5 + 1].id, value: 20 }, + { ProductId: products[i * 5 + 2].id, value: 20 }, + { ProductId: products[i * 5 + 3].id, value: 20 } + ]) + ]); + } }; }); @@ -324,7 +300,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); }); - it('should support an include with multiple different association types', function() { + it('should support an include with multiple different association types', async function() { const User = this.sequelize.define('User', {}), Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -369,105 +345,92 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } - ]).then(() => { - return Group.findAll(); - }), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } - ]).then(() => { - return Rank.findAll(); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]).then(([groups, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.all([ - User.create(), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - return Promise.all([ - GroupMember.bulkCreate([ - { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - user.setProducts([ - products[i * 2 + 0], - products[i * 2 + 1] - ]), - products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 2 + 1].setTags([ - tags[1] - ]), - products[i * 2 + 0].setCategory(tags[1]), - Price.bulkCreate([ - { ProductId: products[i * 2 + 0].id, value: 5 }, - { ProductId: products[i * 2 + 0].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 5 }, - { ProductId: products[i * 2 + 1].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 15 }, - { ProductId: products[i * 2 + 1].id, value: 20 } - ]) - ]); - }); - }).then(() => { - return User.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - Rank - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - Price - ] } - ], - order: [ - ['id', 'ASC'] - ] - }).then(users => { - users.forEach(user => { - user.Memberships.sort(sortById); - - expect(user.Memberships.length).to.equal(2); - expect(user.Memberships[0].Group.name).to.equal('Developers'); - expect(user.Memberships[0].Rank.canRemove).to.equal(1); - expect(user.Memberships[1].Group.name).to.equal('Designers'); - expect(user.Memberships[1].Rank.canRemove).to.equal(0); - - user.Products.sort(sortById); - expect(user.Products.length).to.equal(2); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[0].Category).to.be.ok; - expect(user.Products[1].Category).not.to.be.ok; - - expect(user.Products[0].Prices.length).to.equal(2); - expect(user.Products[1].Prices.length).to.equal(4); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + User.create(), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()) + ]); + await Promise.all([ + GroupMember.bulkCreate([ + { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } + ]), + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] + ]), + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } + ]) + ]); + const users = await User.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + Rank + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + Price + ] } + ], + order: [ + ['id', 'ASC'] + ] }); - }); + for (const user of users) { + user.Memberships.sort(sortById); + + expect(user.Memberships.length).to.equal(2); + expect(user.Memberships[0].Group.name).to.equal('Developers'); + expect(user.Memberships[0].Rank.canRemove).to.equal(1); + expect(user.Memberships[1].Group.name).to.equal('Designers'); + expect(user.Memberships[1].Rank.canRemove).to.equal(0); + + user.Products.sort(sortById); + expect(user.Products.length).to.equal(2); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[0].Category).to.be.ok; + expect(user.Products[1].Category).not.to.be.ok; + + expect(user.Products[0].Prices.length).to.equal(2); + expect(user.Products[1].Prices.length).to.equal(4); + } + } }); it('should support many levels of belongsTo', function() { @@ -1189,7 +1152,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); }); - it('should be possible to extend the on clause with a where option on nested includes', function() { + it('should be possible to extend the on clause with a where option on nested includes', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING }), @@ -1236,98 +1199,81 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } - ]).then(() => { - return Group.findAll(); - }), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } - ]).then(() => { - return Rank.findAll(); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]).then(([groups, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.props({ - user: User.create({ name: 'FooBarzz' }), - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - }).then(results => { - return Promise.join( - GroupMember.bulkCreate([ - { UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - results.user.setProducts([ - results.products[i * 2 + 0], - results.products[i * 2 + 1] - ]), - Promise.join( - results.products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - results.products[i * 2 + 1].setTags([ - tags[1] - ]), - results.products[i * 2 + 0].setCategory(tags[1]) - ), - Price.bulkCreate([ - { ProductId: results.products[i * 2 + 0].id, value: 5 }, - { ProductId: results.products[i * 2 + 0].id, value: 10 }, - { ProductId: results.products[i * 2 + 1].id, value: 5 }, - { ProductId: results.products[i * 2 + 1].id, value: 10 }, - { ProductId: results.products[i * 2 + 1].id, value: 15 }, - { ProductId: results.products[i * 2 + 1].id, value: 20 } - ]) - ); - }); - }); - }).then(() => { - return User.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - { model: Rank, where: { name: 'Admin' } } - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - { model: Price, where: { - value: { - [Op.gt]: 15 - } - } } - ] } - ], - order: [ - ['id', 'ASC'] - ] - }).then(users => { - users.forEach(user => { - expect(user.Memberships.length).to.equal(1); - expect(user.Memberships[0].Rank.name).to.equal('Admin'); - expect(user.Products.length).to.equal(1); - expect(user.Products[0].Prices.length).to.equal(1); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const user = await User.create({ name: 'FooBarzz' }); + const products = await Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()); + await Promise.all([ + GroupMember.bulkCreate([ + { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } + ]), + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] + ]), + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } + ]) + ]); + } + const users = await User.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + { model: Rank, where: { name: 'Admin' } } + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + { model: Price, where: { + value: { + [Op.gt]: 15 + } + } } + ] } + ], + order: [ + ['id', 'ASC'] + ] }); + for (const user of users) { + expect(user.Memberships.length).to.equal(1); + expect(user.Memberships[0].Rank.name).to.equal('Admin'); + expect(user.Products.length).to.equal(1); + expect(user.Products[0].Prices.length).to.equal(1); + } }); it('should be possible to use limit and a where with a belongsTo include', function() { diff --git a/test/integration/include/schema.test.js b/test/integration/include/schema.test.js index 25b5495a0a76..833721dd9a9a 100644 --- a/test/integration/include/schema.test.js +++ b/test/integration/include/schema.test.js @@ -21,334 +21,301 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { }); beforeEach(function() { - this.fixtureA = function() { - return this.sequelize.dropSchema('account').then(() => { - return this.sequelize.createSchema('account').then(() => { - const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), - Company = this.sequelize.define('Company', { - name: DataTypes.STRING - }, { schema: 'account' }), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }, { schema: 'account' }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { schema: 'account' }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }, { schema: 'account' }), - Customer = this.sequelize.define('Customer', { - name: DataTypes.STRING - }, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - GroupMember = this.sequelize.define('GroupMember', { - - }, { schema: 'account' }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canPost: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }, { schema: 'account' }); + this.fixtureA = async function() { + await this.sequelize.dropSchema('account'); + await this.sequelize.createSchema('account'); + const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), + Company = this.sequelize.define('Company', { + name: DataTypes.STRING + }, { schema: 'account' }), + Product = this.sequelize.define('Product', { + title: DataTypes.STRING + }, { schema: 'account' }), + Tag = this.sequelize.define('Tag', { + name: DataTypes.STRING + }, { schema: 'account' }), + Price = this.sequelize.define('Price', { + value: DataTypes.FLOAT + }, { schema: 'account' }), + Customer = this.sequelize.define('Customer', { + name: DataTypes.STRING + }, { schema: 'account' }), + Group = this.sequelize.define('Group', { + name: DataTypes.STRING + }, { schema: 'account' }), + GroupMember = this.sequelize.define('GroupMember', { + + }, { schema: 'account' }), + Rank = this.sequelize.define('Rank', { + name: DataTypes.STRING, + canInvite: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + canRemove: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + canPost: { + type: DataTypes.INTEGER, + defaultValue: 0 + } + }, { schema: 'account' }); + + this.models = { + AccUser, + Company, + Product, + Tag, + Price, + Customer, + Group, + GroupMember, + Rank + }; + + AccUser.hasMany(Product); + Product.belongsTo(AccUser); + + Product.belongsToMany(Tag, { through: 'product_tag' }); + Tag.belongsToMany(Product, { through: 'product_tag' }); + Product.belongsTo(Tag, { as: 'Category' }); + Product.belongsTo(Company); + + Product.hasMany(Price); + Price.belongsTo(Product); + + AccUser.hasMany(GroupMember, { as: 'Memberships' }); + GroupMember.belongsTo(AccUser); + GroupMember.belongsTo(Rank); + GroupMember.belongsTo(Group); + Group.hasMany(GroupMember, { as: 'Memberships' }); + + await this.sequelize.sync({ force: true }); + const [groups, companies, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' }, + { name: 'Managers' } + ]).then(() => Group.findAll()), + Company.bulkCreate([ + { name: 'Sequelize' }, + { name: 'Coca Cola' }, + { name: 'Bonanza' }, + { name: 'NYSE' }, + { name: 'Coshopr' } + ]).then(() => Company.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, + { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' }, + { name: 'D' }, + { name: 'E' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + AccUser.create(), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Bed' }, + { title: 'Pen' }, + { title: 'Monitor' } + ]).then(() => Product.findAll()) + ]); + const groupMembers = [ + { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } + ]; + if (i < 3) { + groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); + } - this.models = { - AccUser, - Company, - Product, - Tag, - Price, - Customer, - Group, - GroupMember, - Rank - }; - - AccUser.hasMany(Product); - Product.belongsTo(AccUser); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - Product.belongsTo(Company); - - Product.hasMany(Price); - Price.belongsTo(Product); - - AccUser.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(AccUser); - GroupMember.belongsTo(Rank); - GroupMember.belongsTo(Group); - Group.hasMany(GroupMember, { as: 'Memberships' }); - - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' }, - { name: 'Managers' } - ]), - Company.bulkCreate([ - { name: 'Sequelize' }, - { name: 'Coca Cola' }, - { name: 'Bonanza' }, - { name: 'NYSE' }, - { name: 'Coshopr' } - ]), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, - { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' }, - { name: 'D' }, - { name: 'E' } - ]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - Company.findAll(), - Rank.findAll(), - Tag.findAll() - ]); - }).then(([groups, companies, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.all([ - AccUser.create(), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Bed' }, - { title: 'Pen' }, - { title: 'Monitor' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - const groupMembers = [ - { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } - ]; - if (i < 3) { - groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); - } - - return Promise.join( - GroupMember.bulkCreate(groupMembers), - user.setProducts([ - products[i * 5 + 0], - products[i * 5 + 1], - products[i * 5 + 3] - ]), - Promise.join( - products[i * 5 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 5 + 1].setTags([ - tags[1] - ]), - products[i * 5 + 0].setCategory(tags[1]), - products[i * 5 + 2].setTags([ - tags[0] - ]), - products[i * 5 + 3].setTags([ - tags[0] - ]) - ), - Promise.join( - products[i * 5 + 0].setCompany(companies[4]), - products[i * 5 + 1].setCompany(companies[3]), - products[i * 5 + 2].setCompany(companies[2]), - products[i * 5 + 3].setCompany(companies[1]), - products[i * 5 + 4].setCompany(companies[0]) - ), - Price.bulkCreate([ - { ProductId: products[i * 5 + 0].id, value: 5 }, - { ProductId: products[i * 5 + 0].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 5 }, - { ProductId: products[i * 5 + 1].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 15 }, - { ProductId: products[i * 5 + 1].id, value: 20 }, - { ProductId: products[i * 5 + 2].id, value: 20 }, - { ProductId: products[i * 5 + 3].id, value: 20 } - ]) - ); - }); - }); - }); - }); - }); - }); + await Promise.all([ + GroupMember.bulkCreate(groupMembers), + user.setProducts([ + products[i * 5 + 0], + products[i * 5 + 1], + products[i * 5 + 3] + ]), + products[i * 5 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 5 + 1].setTags([ + tags[1] + ]), + products[i * 5 + 0].setCategory(tags[1]), + products[i * 5 + 2].setTags([ + tags[0] + ]), + products[i * 5 + 3].setTags([ + tags[0] + ]), + products[i * 5 + 0].setCompany(companies[4]), + products[i * 5 + 1].setCompany(companies[3]), + products[i * 5 + 2].setCompany(companies[2]), + products[i * 5 + 3].setCompany(companies[1]), + products[i * 5 + 4].setCompany(companies[0]), + Price.bulkCreate([ + { ProductId: products[i * 5 + 0].id, value: 5 }, + { ProductId: products[i * 5 + 0].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 5 }, + { ProductId: products[i * 5 + 1].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 15 }, + { ProductId: products[i * 5 + 1].id, value: 20 }, + { ProductId: products[i * 5 + 2].id, value: 20 }, + { ProductId: products[i * 5 + 3].id, value: 20 } + ]) + ]); + } }; return this.sequelize.createSchema('account'); }); - it('should support an include with multiple different association types', function() { - return this.sequelize.dropSchema('account').then(() => { - return this.sequelize.createSchema('account').then(() => { - const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }, { schema: 'account' }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { schema: 'account' }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - GroupMember = this.sequelize.define('GroupMember', { - - }, { schema: 'account' }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }, { schema: 'account' }); - - AccUser.hasMany(Product); - Product.belongsTo(AccUser); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - - Product.hasMany(Price); - Price.belongsTo(Product); - - AccUser.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(AccUser); - GroupMember.belongsTo(Rank); - GroupMember.belongsTo(Group); - Group.hasMany(GroupMember, { as: 'Memberships' }); - - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } - ]).then(() => { - return Group.findAll(); - }), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } - ]).then(() => { - return Rank.findAll(); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]).then(([groups, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.all([ - AccUser.create(), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - return Promise.all([ - GroupMember.bulkCreate([ - { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - user.setProducts([ - products[i * 2 + 0], - products[i * 2 + 1] - ]), - products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 2 + 1].setTags([ - tags[1] - ]), - products[i * 2 + 0].setCategory(tags[1]), - Price.bulkCreate([ - { ProductId: products[i * 2 + 0].id, value: 5 }, - { ProductId: products[i * 2 + 0].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 5 }, - { ProductId: products[i * 2 + 1].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 15 }, - { ProductId: products[i * 2 + 1].id, value: 20 } - ]) - ]); - }); - }); - }).then(() => { - return AccUser.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - Rank - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - Price - ] } - ], - order: [ - [AccUser.rawAttributes.id, 'ASC'] - ] - }).then(users => { - users.forEach(user => { - expect(user.Memberships).to.be.ok; - user.Memberships.sort(sortById); - - expect(user.Memberships.length).to.equal(2); - expect(user.Memberships[0].Group.name).to.equal('Developers'); - expect(user.Memberships[0].Rank.canRemove).to.equal(1); - expect(user.Memberships[1].Group.name).to.equal('Designers'); - expect(user.Memberships[1].Rank.canRemove).to.equal(0); - - user.Products.sort(sortById); - expect(user.Products.length).to.equal(2); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[0].Category).to.be.ok; - expect(user.Products[1].Category).not.to.be.ok; - - expect(user.Products[0].Prices.length).to.equal(2); - expect(user.Products[1].Prices.length).to.equal(4); - }); - }); - }); - }); + it('should support an include with multiple different association types', async function() { + await this.sequelize.dropSchema('account'); + await this.sequelize.createSchema('account'); + const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), + Product = this.sequelize.define('Product', { + title: DataTypes.STRING + }, { schema: 'account' }), + Tag = this.sequelize.define('Tag', { + name: DataTypes.STRING + }, { schema: 'account' }), + Price = this.sequelize.define('Price', { + value: DataTypes.FLOAT + }, { schema: 'account' }), + Group = this.sequelize.define('Group', { + name: DataTypes.STRING + }, { schema: 'account' }), + GroupMember = this.sequelize.define('GroupMember', { + + }, { schema: 'account' }), + Rank = this.sequelize.define('Rank', { + name: DataTypes.STRING, + canInvite: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + canRemove: { + type: DataTypes.INTEGER, + defaultValue: 0 + } + }, { schema: 'account' }); + + AccUser.hasMany(Product); + Product.belongsTo(AccUser); + + Product.belongsToMany(Tag, { through: 'product_tag' }); + Tag.belongsToMany(Product, { through: 'product_tag' }); + Product.belongsTo(Tag, { as: 'Category' }); + + Product.hasMany(Price); + Price.belongsTo(Product); + + AccUser.hasMany(GroupMember, { as: 'Memberships' }); + GroupMember.belongsTo(AccUser); + GroupMember.belongsTo(Rank); + GroupMember.belongsTo(Group); + Group.hasMany(GroupMember, { as: 'Memberships' }); + + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + AccUser.create(), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()) + ]); + await Promise.all([ + GroupMember.bulkCreate([ + { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } + ]), + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] + ]), + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } + ]) + ]); + const users = await AccUser.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + Rank + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + Price + ] } + ], + order: [ + [AccUser.rawAttributes.id, 'ASC'] + ] }); - }); + for (const user of users) { + expect(user.Memberships).to.be.ok; + user.Memberships.sort(sortById); + + expect(user.Memberships.length).to.equal(2); + expect(user.Memberships[0].Group.name).to.equal('Developers'); + expect(user.Memberships[0].Rank.canRemove).to.equal(1); + expect(user.Memberships[1].Group.name).to.equal('Designers'); + expect(user.Memberships[1].Rank.canRemove).to.equal(0); + + user.Products.sort(sortById); + expect(user.Products.length).to.equal(2); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[0].Category).to.be.ok; + expect(user.Products[1].Category).not.to.be.ok; + + expect(user.Products[0].Prices.length).to.equal(2); + expect(user.Products[1].Prices.length).to.equal(4); + } + } }); - it('should support many levels of belongsTo', function() { + it('should support many levels of belongsTo', async function() { const A = this.sequelize.define('a', {}, { schema: 'account' }), B = this.sequelize.define('b', {}, { schema: 'account' }), C = this.sequelize.define('c', {}, { schema: 'account' }), @@ -377,54 +344,42 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { H ]; - return this.sequelize.sync().then(() => { - return A.bulkCreate([ - {}, {}, {}, {}, {}, {}, {}, {} - ]).then(() => { - let previousInstance; - return Promise.each(singles, model => { - return model.create({}).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - return void 0; - }); - }); - }).then(() => { - return A.findAll(); - }).then(as => { - const promises = []; - as.forEach(a => { - promises.push(a.setB(b)); - }); - return Promise.all(promises); - }).then(() => { - return A.findAll({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, include: [ - { model: H } - ] } - ] } + await this.sequelize.sync(); + await A.bulkCreate([ + {}, {}, {}, {}, {}, {}, {}, {} + ]); + let previousInstance; + for (const model of singles) { + const instance = await model.create({}); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + continue; + } + previousInstance = b = instance; + } + let as = await A.findAll(); + await Promise.all(as.map(a => a.setB(b))); + as = await A.findAll({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(as => { - expect(as.length).to.be.ok; - as.forEach(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); - }); + ] } + ] } + ] + }); + expect(as.length).to.be.ok; + as.forEach(a => { + expect(a.b.c.d.e.f.g.h).to.be.ok; }); }); @@ -912,7 +867,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { }); }); - it('should be possible to extend the on clause with a where option on nested includes', function() { + it('should be possible to extend the on clause with a where option on nested includes', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING }, { schema: 'account' }), @@ -959,96 +914,83 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + User.create({ name: 'FooBarzz' }), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()) + ]); + await Promise.all([ + GroupMember.bulkCreate([ + { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } ]), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } ]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - Rank.findAll(), - Tag.findAll() - ]); - }).then(([groups, ranks, tags]) => { - return Promise.resolve([0, 1, 2, 3, 4]).each(i => { - return Promise.all([ - User.create({ name: 'FooBarzz' }), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - return Promise.all([ - GroupMember.bulkCreate([ - { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - user.setProducts([ - products[i * 2 + 0], - products[i * 2 + 1] - ]), - products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 2 + 1].setTags([ - tags[1] - ]), - products[i * 2 + 0].setCategory(tags[1]), - Price.bulkCreate([ - { ProductId: products[i * 2 + 0].id, value: 5 }, - { ProductId: products[i * 2 + 0].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 5 }, - { ProductId: products[i * 2 + 1].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 15 }, - { ProductId: products[i * 2 + 1].id, value: 20 } - ]) - ]); - }); - }); - }).then(() => { - return User.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - { model: Rank, where: { name: 'Admin' } } - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - { model: Price, where: { - value: { - [Op.gt]: 15 - } - } } - ] } - ], - order: [ - ['id', 'ASC'] - ] - }).then(users => { - users.forEach(user => { - expect(user.Memberships.length).to.equal(1); - expect(user.Memberships[0].Rank.name).to.equal('Admin'); - expect(user.Products.length).to.equal(1); - expect(user.Products[0].Prices.length).to.equal(1); - }); - }); + ]); + const users = await User.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + { model: Rank, where: { name: 'Admin' } } + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + { model: Price, where: { + value: { + [Op.gt]: 15 + } + } } + ] } + ], + order: [ + ['id', 'ASC'] + ] }); - }); + users.forEach(user => { + expect(user.Memberships.length).to.equal(1); + expect(user.Memberships[0].Rank.name).to.equal('Admin'); + expect(user.Products.length).to.equal(1); + expect(user.Products[0].Prices.length).to.equal(1); + }); + } }); it('should be possible to use limit and a where with a belongsTo include', function() { diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 2aa824281d32..319758d0050f 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -287,7 +287,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { await Sequelize.Promise.all([ User.create({ username: 'tobi', email: 'tobi@tobi.me' }), User.create({ username: 'tobi', email: 'tobi@tobi.me' }) - ]).catch(Sequelize.UniqueConstraintError, err => { + ]).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err.message).to.equal('User and email must be unique'); }); }); @@ -321,7 +322,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { await Sequelize.Promise.all([ User.create({ user_id: 1, email: 'tobi@tobi.me' }), User.create({ user_id: 1, email: 'tobi@tobi.me' }) - ]).catch(Sequelize.UniqueConstraintError, err => { + ]).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err.message).to.equal('User and email must be unique'); }); }); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index e9434819435f..312be20b3c1d 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -417,10 +417,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }) .timeout(1000) - .catch(Promise.TimeoutError, e => { + .catch(e => { + if (!(e instanceof Promise.TimeoutError)) throw e; throw new Error(e); }) - .catch(Sequelize.ValidationError, () => { + .catch(err => { + if (!(err instanceof Sequelize.ValidationError)) throw err; return test(times + 1); }); }; @@ -1003,7 +1005,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { return User.sync({ force: true }).then(() => { return User.create({ username: 'foo' }).then(() => { - return User.create({ username: 'foo' }).catch(Sequelize.UniqueConstraintError, err => { + return User.create({ username: 'foo' }).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; }); }); @@ -1020,7 +1023,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { return User.create({ username: 'foo' }); }).then(() => { return User.create({ username: 'fOO' }); - }).catch(Sequelize.UniqueConstraintError, err => { + }).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; }); }); @@ -1040,7 +1044,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { return User.create({ username: 'foo' }); }).then(() => { return User.create({ username: 'foo' }); - }).catch(Sequelize.UniqueConstraintError, err => { + }).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; }); }); @@ -1075,7 +1080,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { return UserNull.sync({ force: true }).then(() => { return UserNull.create({ username: 'foo', smth: 'foo' }).then(() => { - return UserNull.create({ username: 'foo', smth: 'bar' }).catch(Sequelize.UniqueConstraintError, err => { + return UserNull.create({ username: 'foo', smth: 'bar' }).catch(err => { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; }); }); diff --git a/test/unit/model/find-or-create.test.js b/test/unit/model/find-or-create.test.js index 1f435a5f37e3..19e1da7c214a 100644 --- a/test/unit/model/find-or-create.test.js +++ b/test/unit/model/find-or-create.test.js @@ -45,7 +45,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { .then(() => { expect.fail('expected to fail'); }) - .catch(/abort/, () => { + .catch(err => { + if (!/abort/.test(err.message)) throw err; expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); }); @@ -63,7 +64,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { .then(() => { expect.fail('expected to fail'); }) - .catch(/abort/, () => { + .catch(err => { + if (!/abort/.test(err.message)) throw err; expect(this.clsStub.called).to.equal(false); }); }); From 869fdae86cc76a6e2ba63626ef8c6a1ae2635693 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 5 Apr 2020 23:41:23 -0500 Subject: [PATCH 074/414] fix: add missing [] to Promise.all calls (#12077) --- lib/dialects/abstract/connection-manager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index 8d6dd1e86a66..50297bcd3165 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -180,16 +180,16 @@ class ConnectionManager { debug('connection destroy'); }, destroyAllNow: () => { - return Promise.all( + return Promise.all([ this.pool.read.destroyAllNow(), this.pool.write.destroyAllNow() - ).then(() => { debug('all connections destroyed'); }); + ]).then(() => { debug('all connections destroyed'); }); }, drain: () => { - return Promise.all( + return Promise.all([ this.pool.write.drain(), this.pool.read.drain() - ); + ]); }, read: new Pool({ name: 'sequelize:read', From cf5b2f54641db263ea266e08b7bb2e5e8cd18195 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 6 Apr 2020 01:50:09 -0500 Subject: [PATCH 075/414] refactor: replace misc bluebird API usage (#12072) --- lib/dialects/mysql/connection-manager.js | 3 +- lib/dialects/postgres/connection-manager.js | 3 +- lib/dialects/sqlite/connection-manager.js | 3 +- lib/instance-validator.js | 5 +- lib/sequelize.js | 2 +- package-lock.json | 5548 +++++++++-------- package.json | 3 + test/integration/cls.test.js | 5 +- test/integration/configuration.test.js | 13 +- test/integration/include.test.js | 5 +- test/integration/include/findAll.test.js | 25 +- test/integration/include/schema.test.js | 5 +- test/integration/model/create.test.js | 17 +- test/integration/model/findAll.test.js | 9 +- test/integration/pool.test.js | 5 +- .../integration/sequelize.transaction.test.js | 7 +- test/integration/transaction.test.js | 15 +- .../unit/dialects/mssql/resource-lock.test.js | 7 +- 18 files changed, 2864 insertions(+), 2816 deletions(-) diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index c91e0cf00c9e..9b7869e0ed0c 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -8,6 +8,7 @@ const DataTypes = require('../../data-types').mysql; const momentTz = require('moment-timezone'); const debug = logger.debugContext('connection:mysql'); const parserStore = require('../parserStore')('mysql'); +const { promisify } = require('util'); /** * MySQL Connection Manager @@ -144,7 +145,7 @@ class ConnectionManager extends AbstractConnectionManager { return Promise.resolve(); } - return Promise.fromCallback(callback => connection.end(callback)); + return promisify(callback => connection.end(callback))(); } validate(connection) { diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 53cdb6b0982f..7950bca9da59 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -9,6 +9,7 @@ const sequelizeErrors = require('../../errors'); const semver = require('semver'); const dataTypes = require('../../data-types'); const moment = require('moment-timezone'); +const { promisify } = require('util'); class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { @@ -244,7 +245,7 @@ class ConnectionManager extends AbstractConnectionManager { return Promise.resolve(); } - return Promise.fromCallback(callback => connection.end(callback)); + return promisify(callback => connection.end(callback))(); } validate(connection) { diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 5b2de5327842..4708f967f107 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -9,6 +9,7 @@ const debug = logger.debugContext('connection:sqlite'); const dataTypes = require('../../data-types').sqlite; const sequelizeErrors = require('../../errors'); const parserStore = require('../parserStore')('sqlite'); +const { promisify } = require('util'); class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { @@ -27,7 +28,7 @@ class ConnectionManager extends AbstractConnectionManager { _onProcessExit() { const promises = Object.getOwnPropertyNames(this.connections) - .map(connection => Promise.fromCallback(callback => this.connections[connection].close(callback))); + .map(connection => promisify(callback => this.connections[connection].close(callback))()); return Promise .all(promises) diff --git a/lib/instance-validator.js b/lib/instance-validator.js index 2ab567f7c0e0..e28f82257eb5 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -7,6 +7,7 @@ const Promise = require('./promise'); const DataTypes = require('./data-types'); const BelongsTo = require('./associations/belongs-to'); const validator = require('./utils/validator-extras').validator; +const { promisify } = require('util'); /** * Instance Validator. @@ -259,9 +260,9 @@ class InstanceValidator { if (isAsync) { if (optAttrDefined) { - validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs)); + validatorFunction = promisify(validator.bind(this.modelInstance, invokeArgs)); } else { - validatorFunction = Promise.promisify(validator.bind(this.modelInstance)); + validatorFunction = promisify(validator.bind(this.modelInstance)); } return validatorFunction() .catch(e => this._pushError(false, errorKey, e, optValue, validatorType)); diff --git a/lib/sequelize.js b/lib/sequelize.js index 0a1fe00764a5..176f07ad14cf 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -1113,7 +1113,7 @@ class Sequelize { // and reject with original error (ignore any error in rollback) return Promise.resolve().then(() => { if (!transaction.finished) return transaction.rollback().catch(() => {}); - }).throw(err); + }).then(() => { throw err; }); }); }); } diff --git a/package-lock.json b/package-lock.json index f819fbeab861..434398fbfe38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "7.9.0" } }, "@babel/core": { @@ -19,22 +19,22 @@ "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" + "@babel/code-frame": "7.8.3", + "@babel/generator": "7.9.4", + "@babel/helper-module-transforms": "7.9.0", + "@babel/helpers": "7.9.2", + "@babel/parser": "7.9.4", + "@babel/template": "7.8.6", + "@babel/traverse": "7.9.0", + "@babel/types": "7.9.0", + "convert-source-map": "1.7.0", + "debug": "4.1.1", + "gensync": "1.0.0-beta.1", + "json5": "2.1.2", + "lodash": "4.17.15", + "resolve": "1.15.1", + "semver": "5.7.1", + "source-map": "0.5.7" }, "dependencies": { "semver": { @@ -51,10 +51,10 @@ "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { - "@babel/types": "^7.9.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" + "@babel/types": "7.9.0", + "jsesc": "2.5.2", + "lodash": "4.17.15", + "source-map": "0.5.7" }, "dependencies": { "jsesc": { @@ -71,9 +71,9 @@ "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-get-function-arity": "7.8.3", + "@babel/template": "7.8.6", + "@babel/types": "7.9.0" } }, "@babel/helper-get-function-arity": { @@ -82,7 +82,7 @@ "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "7.9.0" } }, "@babel/helper-member-expression-to-functions": { @@ -91,7 +91,7 @@ "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "7.9.0" } }, "@babel/helper-module-imports": { @@ -100,7 +100,7 @@ "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "7.9.0" } }, "@babel/helper-module-transforms": { @@ -109,13 +109,13 @@ "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "7.8.3", + "@babel/helper-replace-supers": "7.8.6", + "@babel/helper-simple-access": "7.8.3", + "@babel/helper-split-export-declaration": "7.8.3", + "@babel/template": "7.8.6", + "@babel/types": "7.9.0", + "lodash": "4.17.15" } }, "@babel/helper-optimise-call-expression": { @@ -124,7 +124,7 @@ "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "7.9.0" } }, "@babel/helper-replace-supers": { @@ -133,10 +133,10 @@ "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/helper-member-expression-to-functions": "7.8.3", + "@babel/helper-optimise-call-expression": "7.8.3", + "@babel/traverse": "7.9.0", + "@babel/types": "7.9.0" } }, "@babel/helper-simple-access": { @@ -145,8 +145,8 @@ "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/template": "7.8.6", + "@babel/types": "7.9.0" } }, "@babel/helper-split-export-declaration": { @@ -155,7 +155,7 @@ "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "7.9.0" } }, "@babel/helper-validator-identifier": { @@ -170,9 +170,9 @@ "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/template": "7.8.6", + "@babel/traverse": "7.9.0", + "@babel/types": "7.9.0" } }, "@babel/highlight": { @@ -181,9 +181,9 @@ "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "7.9.0", + "chalk": "2.4.2", + "js-tokens": "4.0.0" }, "dependencies": { "js-tokens": { @@ -206,7 +206,7 @@ "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "0.13.5" }, "dependencies": { "regenerator-runtime": { @@ -223,9 +223,9 @@ "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "7.8.3", + "@babel/parser": "7.9.4", + "@babel/types": "7.9.0" } }, "@babel/traverse": { @@ -234,15 +234,15 @@ "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "@babel/code-frame": "7.8.3", + "@babel/generator": "7.9.4", + "@babel/helper-function-name": "7.8.3", + "@babel/helper-split-export-declaration": "7.8.3", + "@babel/parser": "7.9.4", + "@babel/types": "7.9.0", + "debug": "4.1.1", + "globals": "11.12.0", + "lodash": "4.17.15" }, "dependencies": { "globals": { @@ -259,9 +259,9 @@ "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "@babel/helper-validator-identifier": "7.9.0", + "lodash": "4.17.15", + "to-fast-properties": "2.0.0" }, "dependencies": { "to-fast-properties": { @@ -278,10 +278,10 @@ "integrity": "sha512-6+L0vbw55UEdht71pgWOE55SRgb+8OHcEwGDB234VlIBFGK9P2QOBU7MHiYJ5cjdjCQ0rReNrGjOHmJ99jwf0w==", "dev": true, "requires": { - "@commitlint/format": "^8.3.4", - "@commitlint/lint": "^8.3.5", - "@commitlint/load": "^8.3.5", - "@commitlint/read": "^8.3.4", + "@commitlint/format": "8.3.4", + "@commitlint/lint": "8.3.5", + "@commitlint/load": "8.3.5", + "@commitlint/read": "8.3.4", "babel-polyfill": "6.26.0", "chalk": "2.4.2", "get-stdin": "7.0.0", @@ -297,7 +297,7 @@ "integrity": "sha512-mFg1Yj2xFDBJJyltGP3RLqZOk89HuNK1ttOcRA9lsTjTVVu4MrNv5sQ1LkW645xn4Vy9zgxlB0CrR4LXEg5QpQ==", "dev": true, "requires": { - "@commitlint/config-angular-type-enum": "^8.3.4" + "@commitlint/config-angular-type-enum": "8.3.4" } }, "@commitlint/config-angular-type-enum": { @@ -327,7 +327,7 @@ "integrity": "sha512-809wlQ/ND6CLZON+w2Rb3YM2TLNDfU2xyyqpZeqzf2reJNpySMSUAeaO/fNDJSOKIsOsR3bI01rGu6hv28k+Nw==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.2" } }, "@commitlint/is-ignored": { @@ -353,10 +353,10 @@ "integrity": "sha512-02AkI0a6PU6rzqUvuDkSi6rDQ2hUgkq9GpmdJqfai5bDbxx2939mK4ZO+7apbIh4H6Pae7EpYi7ffxuJgm+3hQ==", "dev": true, "requires": { - "@commitlint/is-ignored": "^8.3.5", - "@commitlint/parse": "^8.3.4", - "@commitlint/rules": "^8.3.4", - "babel-runtime": "^6.23.0", + "@commitlint/is-ignored": "8.3.5", + "@commitlint/parse": "8.3.4", + "@commitlint/rules": "8.3.4", + "babel-runtime": "6.26.0", "lodash": "4.17.15" } }, @@ -366,13 +366,13 @@ "integrity": "sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw==", "dev": true, "requires": { - "@commitlint/execute-rule": "^8.3.4", - "@commitlint/resolve-extends": "^8.3.5", - "babel-runtime": "^6.23.0", + "@commitlint/execute-rule": "8.3.4", + "@commitlint/resolve-extends": "8.3.5", + "babel-runtime": "6.26.0", "chalk": "2.4.2", - "cosmiconfig": "^5.2.0", + "cosmiconfig": "5.2.1", "lodash": "4.17.15", - "resolve-from": "^5.0.0" + "resolve-from": "5.0.0" } }, "@commitlint/message": { @@ -387,9 +387,9 @@ "integrity": "sha512-b3uQvpUQWC20EBfKSfMRnyx5Wc4Cn778bVeVOFErF/cXQK725L1bYFvPnEjQO/GT8yGVzq2wtLaoEqjm1NJ/Bw==", "dev": true, "requires": { - "conventional-changelog-angular": "^1.3.3", - "conventional-commits-parser": "^3.0.0", - "lodash": "^4.17.11" + "conventional-changelog-angular": "1.6.6", + "conventional-commits-parser": "3.0.8", + "lodash": "4.17.15" } }, "@commitlint/read": { @@ -398,10 +398,10 @@ "integrity": "sha512-FKv1kHPrvcAG5j+OSbd41IWexsbLhfIXpxVC/YwQZO+FR0EHmygxQNYs66r+GnhD1EfYJYM4WQIqd5bJRx6OIw==", "dev": true, "requires": { - "@commitlint/top-level": "^8.3.4", - "@marionebl/sander": "^0.6.0", - "babel-runtime": "^6.23.0", - "git-raw-commits": "^2.0.0" + "@commitlint/top-level": "8.3.4", + "@marionebl/sander": "0.6.1", + "babel-runtime": "6.26.0", + "git-raw-commits": "2.0.3" } }, "@commitlint/resolve-extends": { @@ -410,10 +410,10 @@ "integrity": "sha512-nHhFAK29qiXNe6oH6uG5wqBnCR+BQnxlBW/q5fjtxIaQALgfoNLHwLS9exzbIRFqwJckpR6yMCfgMbmbAOtklQ==", "dev": true, "requires": { - "import-fresh": "^3.0.0", + "import-fresh": "3.2.1", "lodash": "4.17.15", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" + "resolve-from": "5.0.0", + "resolve-global": "1.0.0" } }, "@commitlint/rules": { @@ -422,10 +422,10 @@ "integrity": "sha512-xuC9dlqD5xgAoDFgnbs578cJySvwOSkMLQyZADb1xD5n7BNcUJfP8WjT9W1Aw8K3Wf8+Ym/ysr9FZHXInLeaRg==", "dev": true, "requires": { - "@commitlint/ensure": "^8.3.4", - "@commitlint/message": "^8.3.4", - "@commitlint/to-lines": "^8.3.4", - "babel-runtime": "^6.23.0" + "@commitlint/ensure": "8.3.4", + "@commitlint/message": "8.3.4", + "@commitlint/to-lines": "8.3.4", + "babel-runtime": "6.26.0" } }, "@commitlint/to-lines": { @@ -440,7 +440,7 @@ "integrity": "sha512-nOaeLBbAqSZNpKgEtO6NAxmui1G8ZvLG+0wb4rvv6mWhPDzK1GNZkCd8FUZPahCoJ1iHDoatw7F8BbJLg4nDjg==", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "4.1.0" }, "dependencies": { "find-up": { @@ -449,8 +449,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "locate-path": { @@ -459,7 +459,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "p-limit": { @@ -468,7 +468,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -477,7 +477,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -500,10 +500,10 @@ "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "camelcase": "5.3.1", + "find-up": "4.1.0", + "js-yaml": "3.13.1", + "resolve-from": "5.0.0" }, "dependencies": { "camelcase": { @@ -518,8 +518,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "locate-path": { @@ -528,7 +528,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "p-limit": { @@ -537,7 +537,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -546,7 +546,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -575,9 +575,9 @@ "integrity": "sha1-GViWWHTyS8Ub5Ih1/rUNZC/EH3s=", "dev": true, "requires": { - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" + "graceful-fs": "4.2.3", + "mkdirp": "0.5.5", + "rimraf": "2.7.1" } }, "@nodelib/fs.scandir": { @@ -587,7 +587,7 @@ "dev": true, "requires": { "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" + "run-parallel": "1.1.9" } }, "@nodelib/fs.stat": { @@ -603,7 +603,7 @@ "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" + "fastq": "1.7.0" } }, "@octokit/auth-token": { @@ -612,7 +612,7 @@ "integrity": "sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==", "dev": true, "requires": { - "@octokit/types": "^2.0.0" + "@octokit/types": "2.5.1" } }, "@octokit/endpoint": { @@ -621,9 +621,9 @@ "integrity": "sha512-3nx+MEYoZeD0uJ+7F/gvELLvQJzLXhep2Az0bBSXagbApDvDW0LWwpnAIY/hb0Jwe17A0fJdz0O12dPh05cj7A==", "dev": true, "requires": { - "@octokit/types": "^2.0.0", - "is-plain-object": "^3.0.0", - "universal-user-agent": "^5.0.0" + "@octokit/types": "2.5.1", + "is-plain-object": "3.0.0", + "universal-user-agent": "5.0.0" }, "dependencies": { "is-plain-object": { @@ -632,7 +632,7 @@ "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", "dev": true, "requires": { - "isobject": "^4.0.0" + "isobject": "4.0.0" } }, "isobject": { @@ -647,7 +647,7 @@ "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", "dev": true, "requires": { - "os-name": "^3.1.0" + "os-name": "3.1.0" } } } @@ -658,7 +658,7 @@ "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", "dev": true, "requires": { - "@octokit/types": "^2.0.1" + "@octokit/types": "2.5.1" } }, "@octokit/plugin-request-log": { @@ -673,8 +673,8 @@ "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", "dev": true, "requires": { - "@octokit/types": "^2.0.1", - "deprecation": "^2.3.1" + "@octokit/types": "2.5.1", + "deprecation": "2.3.1" } }, "@octokit/request": { @@ -683,14 +683,14 @@ "integrity": "sha512-qyj8G8BxQyXjt9Xu6NvfvOr1E0l35lsXtwm3SopsYg/JWXjlsnwqLc8rsD2OLguEL/JjLfBvrXr4az7z8Lch2A==", "dev": true, "requires": { - "@octokit/endpoint": "^6.0.0", - "@octokit/request-error": "^2.0.0", - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "is-plain-object": "^3.0.0", - "node-fetch": "^2.3.0", - "once": "^1.4.0", - "universal-user-agent": "^5.0.0" + "@octokit/endpoint": "6.0.0", + "@octokit/request-error": "2.0.0", + "@octokit/types": "2.5.1", + "deprecation": "2.3.1", + "is-plain-object": "3.0.0", + "node-fetch": "2.6.0", + "once": "1.4.0", + "universal-user-agent": "5.0.0" }, "dependencies": { "@octokit/request-error": { @@ -699,9 +699,9 @@ "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==", "dev": true, "requires": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@octokit/types": "2.5.1", + "deprecation": "2.3.1", + "once": "1.4.0" } }, "is-plain-object": { @@ -710,7 +710,7 @@ "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", "dev": true, "requires": { - "isobject": "^4.0.0" + "isobject": "4.0.0" } }, "isobject": { @@ -725,7 +725,7 @@ "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", "dev": true, "requires": { - "os-name": "^3.1.0" + "os-name": "3.1.0" } } } @@ -736,9 +736,9 @@ "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", "dev": true, "requires": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@octokit/types": "2.5.1", + "deprecation": "2.3.1", + "once": "1.4.0" } }, "@octokit/rest": { @@ -747,22 +747,22 @@ "integrity": "sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==", "dev": true, "requires": { - "@octokit/auth-token": "^2.4.0", - "@octokit/plugin-paginate-rest": "^1.1.1", - "@octokit/plugin-request-log": "^1.0.0", + "@octokit/auth-token": "2.4.0", + "@octokit/plugin-paginate-rest": "1.1.2", + "@octokit/plugin-request-log": "1.0.0", "@octokit/plugin-rest-endpoint-methods": "2.4.0", - "@octokit/request": "^5.2.0", - "@octokit/request-error": "^1.0.2", - "atob-lite": "^2.0.0", - "before-after-hook": "^2.0.0", - "btoa-lite": "^1.0.0", - "deprecation": "^2.0.0", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.uniq": "^4.5.0", - "octokit-pagination-methods": "^1.1.0", - "once": "^1.4.0", - "universal-user-agent": "^4.0.0" + "@octokit/request": "5.3.4", + "@octokit/request-error": "1.2.1", + "atob-lite": "2.0.0", + "before-after-hook": "2.1.0", + "btoa-lite": "1.0.0", + "deprecation": "2.3.1", + "lodash.get": "4.4.2", + "lodash.set": "4.3.2", + "lodash.uniq": "4.5.0", + "octokit-pagination-methods": "1.1.0", + "once": "1.4.0", + "universal-user-agent": "4.0.1" } }, "@octokit/types": { @@ -771,7 +771,7 @@ "integrity": "sha512-q4Wr7RexkPRrkQpXzUYF5Fj/14Mr65RyOHj6B9d/sQACpqGcStkHZj4qMEtlMY5SnD/69jlL9ItGPbDM0dR/dA==", "dev": true, "requires": { - "@types/node": ">= 8" + "@types/node": "12.12.34" } }, "@samverschueren/stream-to-observable": { @@ -780,7 +780,7 @@ "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", "dev": true, "requires": { - "any-observable": "^0.3.0" + "any-observable": "0.3.0" } }, "@semantic-release/commit-analyzer": { @@ -789,13 +789,13 @@ "integrity": "sha512-t5wMGByv+SknjP2m3rhWN4vmXoQ16g5VFY8iC4/tcbLPvzxK+35xsTIeUsrVFZv3ymdgAQKIr5J3lKjhF/VZZQ==", "dev": true, "requires": { - "conventional-changelog-angular": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.0.7", - "debug": "^4.0.0", - "import-from": "^3.0.0", - "lodash": "^4.17.4", - "micromatch": "^3.1.10" + "conventional-changelog-angular": "5.0.6", + "conventional-commits-filter": "2.0.2", + "conventional-commits-parser": "3.0.8", + "debug": "4.1.1", + "import-from": "3.0.0", + "lodash": "4.17.15", + "micromatch": "3.1.10" }, "dependencies": { "conventional-changelog-angular": { @@ -804,8 +804,8 @@ "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", "dev": true, "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" + "compare-func": "1.3.2", + "q": "1.5.1" } } } @@ -822,22 +822,22 @@ "integrity": "sha512-tBE8duwyOB+FXetHucl5wCOlZhNPHN1ipQENxN6roCz22AYYRLRaVYNPjo78F+KNJpb+Gy8DdudH78Qc8VhKtQ==", "dev": true, "requires": { - "@octokit/rest": "^16.27.0", - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "bottleneck": "^2.18.1", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "fs-extra": "^8.0.0", - "globby": "^10.0.0", - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^4.0.0", - "issue-parser": "^6.0.0", - "lodash": "^4.17.4", - "mime": "^2.4.3", - "p-filter": "^2.0.0", - "p-retry": "^4.0.0", - "url-join": "^4.0.0" + "@octokit/rest": "16.43.1", + "@semantic-release/error": "2.2.0", + "aggregate-error": "3.0.1", + "bottleneck": "2.19.5", + "debug": "4.1.1", + "dir-glob": "3.0.1", + "fs-extra": "8.1.0", + "globby": "10.0.2", + "http-proxy-agent": "4.0.1", + "https-proxy-agent": "4.0.0", + "issue-parser": "6.0.0", + "lodash": "4.17.15", + "mime": "2.4.4", + "p-filter": "2.1.0", + "p-retry": "4.2.0", + "url-join": "4.0.1" }, "dependencies": { "array-union": { @@ -852,9 +852,9 @@ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "globby": { @@ -863,14 +863,14 @@ "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" + "@types/glob": "7.1.1", + "array-union": "2.1.0", + "dir-glob": "3.0.1", + "fast-glob": "3.2.2", + "glob": "7.1.6", + "ignore": "5.1.4", + "merge2": "1.3.0", + "slash": "3.0.0" } }, "ignore": { @@ -893,19 +893,19 @@ "integrity": "sha512-aqODzbtWpVHO/keinbBMnZEaN/TkdwQvyDWcT0oNbKFpZwLjNjn+QVItoLekF62FLlXXziu2y6V4wnl9FDnujA==", "dev": true, "requires": { - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "execa": "^4.0.0", - "fs-extra": "^8.0.0", - "lodash": "^4.17.15", - "nerf-dart": "^1.0.0", - "normalize-url": "^4.0.0", - "npm": "^6.10.3", - "rc": "^1.2.8", - "read-pkg": "^5.0.0", - "registry-auth-token": "^4.0.0", - "semver": "^6.3.0", - "tempy": "^0.3.0" + "@semantic-release/error": "2.2.0", + "aggregate-error": "3.0.1", + "execa": "4.0.0", + "fs-extra": "8.1.0", + "lodash": "4.17.15", + "nerf-dart": "1.0.0", + "normalize-url": "4.5.0", + "npm": "6.14.4", + "rc": "1.2.8", + "read-pkg": "5.2.0", + "registry-auth-token": "4.1.1", + "semver": "6.3.0", + "tempy": "0.3.0" }, "dependencies": { "cross-spawn": { @@ -914,9 +914,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "execa": { @@ -925,15 +925,15 @@ "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "cross-spawn": "7.0.1", + "get-stream": "5.1.0", + "human-signals": "1.1.1", + "is-stream": "2.0.0", + "merge-stream": "2.0.0", + "npm-run-path": "4.0.1", + "onetime": "5.1.0", + "signal-exit": "3.0.3", + "strip-final-newline": "2.0.0" } }, "fs-extra": { @@ -942,9 +942,9 @@ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "get-stream": { @@ -953,7 +953,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "is-stream": { @@ -968,7 +968,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^3.0.0" + "path-key": "3.1.1" } }, "parse-json": { @@ -977,10 +977,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "7.8.3", + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2", + "lines-and-columns": "1.1.6" } }, "path-key": { @@ -995,10 +995,10 @@ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.0.0", + "type-fest": "0.6.0" } }, "semver": { @@ -1013,7 +1013,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -1034,7 +1034,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -1045,16 +1045,16 @@ "integrity": "sha512-LGjgPBGjjmjap/76O0Md3wc04Y7IlLnzZceLsAkcYRwGQdRPTTFUJKqDQTuieWTs7zfHzQoZqsqPfFxEN+g2+Q==", "dev": true, "requires": { - "conventional-changelog-angular": "^5.0.0", - "conventional-changelog-writer": "^4.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.0.0", - "debug": "^4.0.0", - "get-stream": "^5.0.0", - "import-from": "^3.0.0", - "into-stream": "^5.0.0", - "lodash": "^4.17.4", - "read-pkg-up": "^7.0.0" + "conventional-changelog-angular": "5.0.6", + "conventional-changelog-writer": "4.0.11", + "conventional-commits-filter": "2.0.2", + "conventional-commits-parser": "3.0.8", + "debug": "4.1.1", + "get-stream": "5.1.0", + "import-from": "3.0.0", + "into-stream": "5.1.1", + "lodash": "4.17.15", + "read-pkg-up": "7.0.1" }, "dependencies": { "conventional-changelog-angular": { @@ -1063,8 +1063,8 @@ "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", "dev": true, "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" + "compare-func": "1.3.2", + "q": "1.5.1" } }, "find-up": { @@ -1073,8 +1073,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "get-stream": { @@ -1083,7 +1083,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "locate-path": { @@ -1092,7 +1092,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "p-limit": { @@ -1101,7 +1101,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -1110,7 +1110,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -1125,10 +1125,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "7.8.3", + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2", + "lines-and-columns": "1.1.6" } }, "path-exists": { @@ -1143,10 +1143,10 @@ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.0.0", + "type-fest": "0.6.0" }, "dependencies": { "type-fest": { @@ -1163,9 +1163,9 @@ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" } } } @@ -1185,8 +1185,8 @@ "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "@sinonjs/commons": "1.7.1", + "@sinonjs/samsam": "3.3.3" } }, "@sinonjs/samsam": { @@ -1195,9 +1195,9 @@ "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "@sinonjs/commons": "1.7.1", + "array-from": "2.1.1", + "lodash": "4.17.15" } }, "@sinonjs/text-encoding": { @@ -1242,9 +1242,9 @@ "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", "dev": true, "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" + "@types/events": "3.0.0", + "@types/minimatch": "3.0.3", + "@types/node": "12.12.34" } }, "@types/minimatch": { @@ -1288,16 +1288,6 @@ "integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w==", "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -1324,7 +1314,7 @@ "dev": true, "optional": true, "requires": { - "acorn": "^2.1.0" + "acorn": "2.7.0" }, "dependencies": { "acorn": { @@ -1348,15 +1338,15 @@ "integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=", "dev": true, "requires": { - "@types/node": "^8.0.47", - "async": ">=0.6.0", - "date-utils": "*", - "jws": "3.x.x", - "request": ">= 2.52.0", - "underscore": ">= 1.3.1", - "uuid": "^3.1.0", - "xmldom": ">= 0.1.x", - "xpath.js": "~1.1.0" + "@types/node": "8.10.59", + "async": "3.2.0", + "date-utils": "1.2.21", + "jws": "3.2.2", + "request": "2.88.2", + "underscore": "1.10.2", + "uuid": "3.4.0", + "xmldom": "0.3.0", + "xpath.js": "1.1.0" }, "dependencies": { "@types/node": { @@ -1373,7 +1363,7 @@ "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", "dev": true, "requires": { - "debug": "4" + "debug": "4.1.1" } }, "aggregate-error": { @@ -1382,8 +1372,8 @@ "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "dev": true, "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "clean-stack": "2.2.0", + "indent-string": "4.0.0" }, "dependencies": { "indent-string": { @@ -1400,10 +1390,10 @@ "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "3.1.1", + "fast-json-stable-stringify": "2.1.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" } }, "ansi-colors": { @@ -1418,7 +1408,7 @@ "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "0.11.0" }, "dependencies": { "type-fest": { @@ -1441,7 +1431,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "ansicolors": { @@ -1467,7 +1457,7 @@ "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, "requires": { - "buffer-equal": "^1.0.0" + "buffer-equal": "1.0.0" } }, "append-transform": { @@ -1476,7 +1466,7 @@ "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { - "default-require-extensions": "^3.0.0" + "default-require-extensions": "3.0.0" } }, "aproba": { @@ -1497,8 +1487,8 @@ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.7" } }, "argparse": { @@ -1507,7 +1497,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" } }, "argv-formatter": { @@ -1570,7 +1560,7 @@ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "assert-plus": { @@ -1609,7 +1599,7 @@ "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", "dev": true, "requires": { - "stack-chain": "^1.3.7" + "stack-chain": "1.3.7" } }, "asynckit": { @@ -1648,9 +1638,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.3", + "js-tokens": "3.0.2" }, "dependencies": { "ansi-regex": { @@ -1671,11 +1661,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "strip-ansi": { @@ -1684,7 +1674,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -1701,14 +1691,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.15", + "source-map": "0.5.7", + "trim-right": "1.0.1" } }, "babel-messages": { @@ -1717,7 +1707,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "babel-runtime": "6.26.0" } }, "babel-polyfill": { @@ -1726,9 +1716,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" + "babel-runtime": "6.26.0", + "core-js": "2.6.11", + "regenerator-runtime": "0.10.5" }, "dependencies": { "regenerator-runtime": { @@ -1745,8 +1735,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.6.11", + "regenerator-runtime": "0.11.1" } }, "babel-traverse": { @@ -1755,15 +1745,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.15" }, "dependencies": { "debug": { @@ -1789,10 +1779,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "babel-runtime": "6.26.0", + "esutils": "2.0.3", + "lodash": "4.17.15", + "to-fast-properties": "1.0.3" } }, "babylon": { @@ -1813,13 +1803,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "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" + "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.2", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -1828,7 +1818,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -1837,7 +1827,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -1846,7 +1836,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -1855,9 +1845,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "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.3" } } } @@ -1868,7 +1858,7 @@ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "before-after-hook": { @@ -1895,8 +1885,8 @@ "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", "dev": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2" } }, "bluebird": { @@ -1922,7 +1912,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -1932,16 +1922,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "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.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "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" }, "dependencies": { "extend-shallow": { @@ -1950,7 +1940,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -1997,15 +1987,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "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" + "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.1", + "to-object-path": "0.3.0", + "union-value": "1.0.1", + "unset-value": "1.0.0" } }, "caching-transform": { @@ -2014,10 +2004,10 @@ "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "hasha": "5.2.0", + "make-dir": "3.0.2", + "package-hash": "4.0.0", + "write-file-atomic": "3.0.3" } }, "caller-callsite": { @@ -2026,7 +2016,7 @@ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, "requires": { - "callsites": "^2.0.0" + "callsites": "2.0.0" }, "dependencies": { "callsites": { @@ -2043,7 +2033,7 @@ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, "requires": { - "caller-callsite": "^2.0.0" + "caller-callsite": "2.0.0" } }, "callsites": { @@ -2064,9 +2054,9 @@ "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "camelcase": "4.1.0", + "map-obj": "2.0.0", + "quick-lru": "1.1.0" } }, "cardinal": { @@ -2075,8 +2065,8 @@ "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", "dev": true, "requires": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" + "ansicolors": "0.3.2", + "redeyed": "2.1.1" } }, "caseless": { @@ -2091,12 +2081,12 @@ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.8" } }, "chai-as-promised": { @@ -2105,7 +2095,7 @@ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "check-error": "^1.0.2" + "check-error": "1.0.2" } }, "chai-datetime": { @@ -2114,7 +2104,7 @@ "integrity": "sha1-N0LxiwJMdbdqK37uKRZiMkRnWWw=", "dev": true, "requires": { - "chai": ">1.9.0" + "chai": "4.2.0" } }, "chai-spies": { @@ -2129,9 +2119,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.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "chardet": { @@ -2152,12 +2142,12 @@ "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.10.1", + "lodash": "4.17.15", + "parse5": "3.0.3" } }, "chownr": { @@ -2178,10 +2168,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { "define-property": { @@ -2190,7 +2180,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -2207,7 +2197,7 @@ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "^3.1.0" + "restore-cursor": "3.1.0" } }, "cli-table": { @@ -2226,7 +2216,7 @@ "dev": true, "requires": { "slice-ansi": "0.0.4", - "string-width": "^1.0.1" + "string-width": "1.0.2" }, "dependencies": { "ansi-regex": { @@ -2241,7 +2231,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "slice-ansi": { @@ -2256,9 +2246,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -2267,7 +2257,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -2284,9 +2274,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.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "clone": { @@ -2313,9 +2303,9 @@ "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "inherits": "2.0.4", + "process-nextick-args": "2.0.1", + "readable-stream": "2.3.7" } }, "cls-hooked": { @@ -2324,9 +2314,9 @@ "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", "dev": true, "requires": { - "async-hook-jl": "^1.7.6", - "emitter-listener": "^1.0.1", - "semver": "^5.4.1" + "async-hook-jl": "1.7.6", + "emitter-listener": "1.1.2", + "semver": "5.7.1" }, "dependencies": { "semver": { @@ -2349,8 +2339,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, "color-convert": { @@ -2386,7 +2376,7 @@ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "command-exists": { @@ -2419,8 +2409,8 @@ "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", "dev": true, "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^3.0.0" + "array-ify": "1.0.0", + "dot-prop": "3.0.0" } }, "compare-versions": { @@ -2453,8 +2443,8 @@ "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", "dev": true, "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" + "compare-func": "1.3.2", + "q": "1.5.1" } }, "conventional-changelog-writer": { @@ -2463,16 +2453,16 @@ "integrity": "sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw==", "dev": true, "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.2", - "dateformat": "^3.0.0", - "handlebars": "^4.4.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^5.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^3.0.0" + "compare-func": "1.3.2", + "conventional-commits-filter": "2.0.2", + "dateformat": "3.0.3", + "handlebars": "4.7.6", + "json-stringify-safe": "5.0.1", + "lodash": "4.17.15", + "meow": "5.0.0", + "semver": "6.3.0", + "split": "1.0.1", + "through2": "3.0.1" }, "dependencies": { "semver": { @@ -2489,8 +2479,8 @@ "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==", "dev": true, "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" + "lodash.ismatch": "4.4.0", + "modify-values": "1.0.1" } }, "conventional-commits-parser": { @@ -2499,13 +2489,13 @@ "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==", "dev": true, "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.1", - "lodash": "^4.17.15", - "meow": "^5.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0", - "trim-off-newlines": "^1.0.0" + "is-text-path": "1.0.1", + "JSONStream": "1.3.5", + "lodash": "4.17.15", + "meow": "5.0.0", + "split2": "2.2.0", + "through2": "3.0.1", + "trim-off-newlines": "1.0.1" } }, "convert-source-map": { @@ -2514,7 +2504,7 @@ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "safe-buffer": "5.1.2" } }, "copy-descriptor": { @@ -2541,10 +2531,10 @@ "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" + "import-fresh": "2.0.0", + "is-directory": "0.3.1", + "js-yaml": "3.13.1", + "parse-json": "4.0.0" }, "dependencies": { "import-fresh": { @@ -2553,8 +2543,8 @@ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "caller-path": "2.0.0", + "resolve-from": "3.0.0" } }, "resolve-from": { @@ -2571,7 +2561,7 @@ "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", "dev": true, "requires": { - "cross-spawn": "^7.0.1" + "cross-spawn": "7.0.1" }, "dependencies": { "cross-spawn": { @@ -2580,9 +2570,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "path-key": { @@ -2597,7 +2587,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -2612,7 +2602,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -2623,11 +2613,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.1", + "shebang-command": "1.2.0", + "which": "1.3.1" }, "dependencies": { "semver": { @@ -2650,10 +2640,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", + "boolbase": "1.0.0", + "css-what": "2.1.3", "domutils": "1.5.1", - "nth-check": "~1.0.1" + "nth-check": "1.0.2" } }, "css-what": { @@ -2666,8 +2656,7 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "optional": true + "dev": true }, "cssstyle": { "version": "0.2.37", @@ -2676,7 +2665,7 @@ "dev": true, "optional": true, "requires": { - "cssom": "0.3.x" + "cssom": "0.3.8" } }, "currently-unhandled": { @@ -2685,7 +2674,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "^1.0.1" + "array-find-index": "1.0.2" } }, "dargs": { @@ -2694,7 +2683,7 @@ "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "dashdash": { @@ -2703,7 +2692,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "date-fns": { @@ -2729,7 +2718,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.2" } }, "decamelize": { @@ -2744,8 +2733,8 @@ "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "decamelize": "1.2.0", + "map-obj": "1.0.1" }, "dependencies": { "map-obj": { @@ -2774,7 +2763,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "^4.0.0" + "type-detect": "4.0.8" } }, "deep-extend": { @@ -2795,7 +2784,7 @@ "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { - "strip-bom": "^4.0.0" + "strip-bom": "4.0.0" }, "dependencies": { "strip-bom": { @@ -2812,7 +2801,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "^1.0.12" + "object-keys": "1.1.1" } }, "define-property": { @@ -2821,8 +2810,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -2831,7 +2820,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -2840,7 +2829,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -2849,9 +2838,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "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.3" } } } @@ -2862,10 +2851,16 @@ "integrity": "sha512-kQePPP/cqQX3H6DrX5nCo2vMjJeboPsjEG8OOl43TZbTOr9zLlapWJ/oRCLnMCiyERsBRZXyLMtBXGM+1zmtgQ==", "dev": true, "requires": { - "@types/parsimmon": "^1.3.0", - "parsimmon": "^1.2.0" + "@types/parsimmon": "1.10.1", + "parsimmon": "1.13.0" } }, + "delay": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", + "integrity": "sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA==", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2902,7 +2897,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "detect-libc": { @@ -2923,7 +2918,7 @@ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "path-type": "^4.0.0" + "path-type": "4.0.0" }, "dependencies": { "path-type": { @@ -2940,7 +2935,7 @@ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { - "esutils": "^2.0.2" + "esutils": "2.0.3" } }, "dom-serializer": { @@ -2949,8 +2944,8 @@ "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", "dev": true, "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "domelementtype": "1.3.1", + "entities": "1.1.2" } }, "domelementtype": { @@ -2965,7 +2960,7 @@ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "1.3.1" } }, "domutils": { @@ -2974,8 +2969,8 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "0.1.1", + "domelementtype": "1.3.1" } }, "dot-prop": { @@ -2984,7 +2979,7 @@ "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "dottie": { @@ -2998,10 +2993,10 @@ "integrity": "sha512-yGHhVKo66iyPBFUYRyXX1uW+XEG3/HDP1pHCR3VlPl9ho8zRHy6lzS5k+gCSuINqjNsV8UjZSUXUuTuw0wHp7g==", "dev": true, "requires": { - "command-exists": "^1.2.8", - "definitelytyped-header-parser": "^3.8.2", - "semver": "^6.2.0", - "yargs": "^12.0.5" + "command-exists": "1.2.8", + "definitelytyped-header-parser": "3.8.2", + "semver": "6.3.0", + "yargs": "12.0.5" }, "dependencies": { "semver": { @@ -3019,11 +3014,11 @@ "dev": true, "requires": { "definitelytyped-header-parser": "3.8.2", - "dts-critic": "^2.2.4", - "fs-extra": "^6.0.1", - "strip-json-comments": "^2.0.1", + "dts-critic": "2.2.4", + "fs-extra": "6.0.1", + "strip-json-comments": "2.0.1", "tslint": "5.14.0", - "typescript": "^3.9.0-dev.20200404" + "typescript": "3.9.0-dev.20200404" }, "dependencies": { "typescript": { @@ -3040,7 +3035,7 @@ "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "readable-stream": "2.3.7" } }, "duplexify": { @@ -3049,10 +3044,10 @@ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.4", + "inherits": "2.0.4", + "readable-stream": "2.3.7", + "stream-shift": "1.0.1" } }, "ecc-jsbn": { @@ -3061,8 +3056,8 @@ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "ecdsa-sig-formatter": { @@ -3071,7 +3066,7 @@ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "elegant-spinner": { @@ -3086,7 +3081,7 @@ "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "dev": true, "requires": { - "shimmer": "^1.2.0" + "shimmer": "1.2.1" } }, "emoji-regex": { @@ -3101,7 +3096,7 @@ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "entities": { @@ -3116,8 +3111,8 @@ "integrity": "sha512-Xc41mKvjouTXD3Oy9AqySz1IeyvJvHZ20Twf5ZLYbNpPPIuCnL/qHCmNlD01LoNy0JTunw9HPYVptD19Ac7Mbw==", "dev": true, "requires": { - "execa": "^4.0.0", - "java-properties": "^1.0.0" + "execa": "4.0.0", + "java-properties": "1.0.2" }, "dependencies": { "cross-spawn": { @@ -3126,9 +3121,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "execa": { @@ -3137,15 +3132,15 @@ "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "cross-spawn": "7.0.1", + "get-stream": "5.1.0", + "human-signals": "1.1.1", + "is-stream": "2.0.0", + "merge-stream": "2.0.0", + "npm-run-path": "4.0.1", + "onetime": "5.1.0", + "signal-exit": "3.0.3", + "strip-final-newline": "2.0.0" } }, "get-stream": { @@ -3154,7 +3149,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "is-stream": { @@ -3169,7 +3164,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^3.0.0" + "path-key": "3.1.1" } }, "path-key": { @@ -3184,7 +3179,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -3199,7 +3194,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -3210,8 +3205,8 @@ "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", "dev": true, "requires": { - "commander": "^4.0.0", - "cross-spawn": "^7.0.0" + "commander": "4.1.1", + "cross-spawn": "7.0.1" }, "dependencies": { "commander": { @@ -3226,9 +3221,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "path-key": { @@ -3243,7 +3238,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -3258,7 +3253,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -3269,7 +3264,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "0.2.1" } }, "es-abstract": { @@ -3278,17 +3273,17 @@ "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "es-to-primitive": "1.2.1", + "function-bind": "1.1.1", + "has": "1.0.3", + "has-symbols": "1.0.1", + "is-callable": "1.1.5", + "is-regex": "1.0.5", + "object-inspect": "1.7.0", + "object-keys": "1.1.1", + "object.assign": "4.1.0", + "string.prototype.trimleft": "2.1.2", + "string.prototype.trimright": "2.1.2" } }, "es-to-primitive": { @@ -3297,9 +3292,9 @@ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "1.1.5", + "is-date-object": "1.0.2", + "is-symbol": "1.0.3" } }, "es6-error": { @@ -3327,11 +3322,11 @@ "dev": true, "optional": true, "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "esprima": "4.0.1", + "estraverse": "4.3.0", + "esutils": "2.0.3", + "optionator": "0.8.3", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -3368,9 +3363,9 @@ "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "marked": { @@ -3408,22 +3403,22 @@ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.10.1", + "lodash.assignin": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.defaults": "4.2.0", + "lodash.filter": "4.6.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", + "lodash.reduce": "4.6.0", + "lodash.reject": "4.6.0", + "lodash.some": "4.6.0" } } } @@ -3449,9 +3444,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" + "graceful-fs": "4.2.3", + "jsonfile": "2.4.0", + "klaw": "1.3.1" } }, "jsonfile": { @@ -3460,7 +3455,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.3" } } } @@ -3481,22 +3476,22 @@ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.10.1", + "lodash.assignin": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.defaults": "4.2.0", + "lodash.filter": "4.6.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", + "lodash.reduce": "4.6.0", + "lodash.reject": "4.6.0", + "lodash.some": "4.6.0" } }, "fs-extra": { @@ -3505,9 +3500,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" + "graceful-fs": "4.2.3", + "jsonfile": "2.4.0", + "klaw": "1.3.1" } }, "jsonfile": { @@ -3516,7 +3511,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.3" } } } @@ -3560,12 +3555,12 @@ "integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=", "dev": true, "requires": { - "babel-messages": "^6.8.0", - "babel-runtime": "^6.9.0", - "babel-types": "^6.10.2", - "detect-indent": "^3.0.1", - "lodash": "^4.2.0", - "source-map": "^0.5.0" + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "3.0.1", + "lodash": "4.17.15", + "source-map": "0.5.7" } }, "cheerio": { @@ -3574,22 +3569,22 @@ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.10.1", + "lodash.assignin": "4.2.0", + "lodash.bind": "4.2.1", + "lodash.defaults": "4.2.0", + "lodash.filter": "4.6.0", + "lodash.flatten": "4.4.0", + "lodash.foreach": "4.5.0", + "lodash.map": "4.6.0", + "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", + "lodash.reduce": "4.6.0", + "lodash.reject": "4.6.0", + "lodash.some": "4.6.0" } }, "detect-indent": { @@ -3598,9 +3593,9 @@ "integrity": "sha1-ncXl3bzu+DJXZLlFGwK8bVQIT3U=", "dev": true, "requires": { - "get-stdin": "^4.0.1", - "minimist": "^1.1.0", - "repeating": "^1.1.0" + "get-stdin": "4.0.1", + "minimist": "1.2.5", + "repeating": "1.1.3" } }, "fs-extra": { @@ -3609,9 +3604,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" + "graceful-fs": "4.2.3", + "jsonfile": "2.4.0", + "klaw": "1.3.1" } }, "get-stdin": { @@ -3626,7 +3621,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.3" } }, "marked": { @@ -3641,7 +3636,7 @@ "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.1.0" } }, "taffydb": { @@ -3658,17 +3653,17 @@ "integrity": "sha1-ZhIBysfvhokkkCRG/awVJyU8XU0=", "dev": true, "requires": { - "esdoc-accessor-plugin": "^1.0.0", - "esdoc-brand-plugin": "^1.0.0", - "esdoc-coverage-plugin": "^1.0.0", - "esdoc-external-ecmascript-plugin": "^1.0.0", - "esdoc-integrate-manual-plugin": "^1.0.0", - "esdoc-integrate-test-plugin": "^1.0.0", - "esdoc-lint-plugin": "^1.0.0", - "esdoc-publish-html-plugin": "^1.0.0", - "esdoc-type-inference-plugin": "^1.0.0", - "esdoc-undocumented-identifier-plugin": "^1.0.0", - "esdoc-unexported-identifier-plugin": "^1.0.0" + "esdoc-accessor-plugin": "1.0.0", + "esdoc-brand-plugin": "1.0.1", + "esdoc-coverage-plugin": "1.1.0", + "esdoc-external-ecmascript-plugin": "1.0.0", + "esdoc-integrate-manual-plugin": "1.0.0", + "esdoc-integrate-test-plugin": "1.0.0", + "esdoc-lint-plugin": "1.0.2", + "esdoc-publish-html-plugin": "1.1.2", + "esdoc-type-inference-plugin": "1.0.2", + "esdoc-undocumented-identifier-plugin": "1.0.0", + "esdoc-unexported-identifier-plugin": "1.0.0" } }, "esdoc-type-inference-plugin": { @@ -3695,43 +3690,43 @@ "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "@babel/code-frame": "7.8.3", + "ajv": "6.12.0", + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "debug": "4.1.1", + "doctrine": "3.0.0", + "eslint-scope": "5.0.0", + "eslint-utils": "1.4.3", + "eslint-visitor-keys": "1.1.0", + "espree": "6.2.1", + "esquery": "1.2.0", + "esutils": "2.0.3", + "file-entry-cache": "5.0.1", + "functional-red-black-tree": "1.0.1", + "glob-parent": "5.1.1", + "globals": "12.4.0", + "ignore": "4.0.6", + "import-fresh": "3.2.1", + "imurmurhash": "0.1.4", + "inquirer": "7.1.0", + "is-glob": "4.0.1", + "js-yaml": "3.13.1", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.15", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "natural-compare": "1.4.0", + "optionator": "0.8.3", + "progress": "2.0.3", + "regexpp": "2.0.1", + "semver": "6.3.0", + "strip-ansi": "5.2.0", + "strip-json-comments": "3.0.1", + "table": "5.4.6", + "text-table": "0.2.0", + "v8-compile-cache": "2.1.0" }, "dependencies": { "ansi-regex": { @@ -3746,7 +3741,7 @@ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "0.8.1" } }, "semver": { @@ -3761,7 +3756,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } }, "strip-json-comments": { @@ -3778,14 +3773,14 @@ "integrity": "sha512-c/fnEpwWLFeQn+A7pb1qLOdyhovpqGCWCeUv1wtzFNL5G+xedl9wHUnXtp3b1sGHolVimi9DxKVTuhK/snXoOw==", "dev": true, "requires": { - "comment-parser": "^0.7.2", - "debug": "^4.1.1", - "jsdoctypeparser": "^6.1.0", - "lodash": "^4.17.15", - "object.entries-ponyfill": "^1.0.1", - "regextras": "^0.7.0", - "semver": "^6.3.0", - "spdx-expression-parse": "^3.0.0" + "comment-parser": "0.7.2", + "debug": "4.1.1", + "jsdoctypeparser": "6.1.0", + "lodash": "4.17.15", + "object.entries-ponyfill": "1.0.1", + "regextras": "0.7.0", + "semver": "6.3.0", + "spdx-expression-parse": "3.0.0" }, "dependencies": { "semver": { @@ -3802,8 +3797,8 @@ "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", "dev": true, "requires": { - "eslint-utils": "^2.0.0", - "ramda": "^0.27.0" + "eslint-utils": "2.0.0", + "ramda": "0.27.0" }, "dependencies": { "eslint-utils": { @@ -3812,7 +3807,7 @@ "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "1.1.0" } } } @@ -3823,8 +3818,8 @@ "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "esrecurse": "4.2.1", + "estraverse": "4.3.0" } }, "eslint-utils": { @@ -3833,7 +3828,7 @@ "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "1.1.0" } }, "eslint-visitor-keys": { @@ -3848,9 +3843,9 @@ "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "acorn": "7.1.1", + "acorn-jsx": "5.2.0", + "eslint-visitor-keys": "1.1.0" } }, "esprima": { @@ -3865,7 +3860,7 @@ "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", "dev": true, "requires": { - "estraverse": "^5.0.0" + "estraverse": "5.0.0" }, "dependencies": { "estraverse": { @@ -3882,7 +3877,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "4.3.0" } }, "estraverse": { @@ -3903,13 +3898,13 @@ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "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" + "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.3", + "strip-eof": "1.0.0" } }, "expand-brackets": { @@ -3918,13 +3913,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "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" + "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" }, "dependencies": { "debug": { @@ -3942,7 +3937,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -3951,7 +3946,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "ms": { @@ -3974,8 +3969,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -3984,7 +3979,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -3995,9 +3990,9 @@ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "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": { @@ -4006,14 +4001,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "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" }, "dependencies": { "define-property": { @@ -4022,7 +4017,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -4031,7 +4026,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -4040,7 +4035,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -4049,7 +4044,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -4058,9 +4053,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "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.3" } } } @@ -4083,12 +4078,12 @@ "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", "dev": true, "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" + "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.walk": "1.2.4", + "glob-parent": "5.1.1", + "merge2": "1.3.0", + "micromatch": "4.0.2", + "picomatch": "2.2.2" }, "dependencies": { "braces": { @@ -4097,7 +4092,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "7.0.1" } }, "fill-range": { @@ -4106,7 +4101,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "to-regex-range": "5.0.1" } }, "is-number": { @@ -4121,8 +4116,8 @@ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "3.0.2", + "picomatch": "2.2.2" } }, "to-regex-range": { @@ -4131,7 +4126,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^7.0.0" + "is-number": "7.0.0" } } } @@ -4154,7 +4149,7 @@ "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==", "dev": true, "requires": { - "reusify": "^1.0.4" + "reusify": "1.0.4" } }, "figures": { @@ -4163,7 +4158,7 @@ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "1.0.5" } }, "file-entry-cache": { @@ -4172,7 +4167,7 @@ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "2.0.1" } }, "fill-range": { @@ -4181,10 +4176,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -4193,7 +4188,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -4204,9 +4199,9 @@ "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "commondir": "1.0.1", + "make-dir": "3.0.2", + "pkg-dir": "4.2.0" }, "dependencies": { "find-up": { @@ -4215,8 +4210,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "locate-path": { @@ -4225,7 +4220,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "p-limit": { @@ -4234,7 +4229,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -4243,7 +4238,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -4264,7 +4259,7 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "4.1.0" } } } @@ -4275,7 +4270,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "find-versions": { @@ -4284,7 +4279,7 @@ "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", "dev": true, "requires": { - "semver-regex": "^2.0.0" + "semver-regex": "2.0.0" } }, "flat": { @@ -4293,7 +4288,7 @@ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "is-buffer": "~2.0.3" + "is-buffer": "2.0.4" }, "dependencies": { "is-buffer": { @@ -4310,7 +4305,7 @@ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "flatted": "^2.0.0", + "flatted": "2.0.2", "rimraf": "2.6.3", "write": "1.0.3" }, @@ -4321,7 +4316,7 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } } } @@ -4338,8 +4333,8 @@ "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" + "inherits": "2.0.4", + "readable-stream": "2.3.7" } }, "for-in": { @@ -4354,8 +4349,8 @@ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" + "cross-spawn": "7.0.1", + "signal-exit": "3.0.3" }, "dependencies": { "cross-spawn": { @@ -4364,9 +4359,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "path-key": { @@ -4381,7 +4376,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -4396,7 +4391,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -4413,9 +4408,9 @@ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.26" } }, "fragment-cache": { @@ -4424,7 +4419,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "from2": { @@ -4433,8 +4428,8 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "inherits": "2.0.4", + "readable-stream": "2.3.7" } }, "fromentries": { @@ -4449,9 +4444,9 @@ "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "fs-jetpack": { @@ -4460,8 +4455,8 @@ "integrity": "sha512-MldfoKMz2NwpvP3UFfVXLp4NCncy9yxGamgBK6hofFaisnWoGvgkAyTtKwcq++leztgZuM4ywrZEaUtiyVfWgA==", "dev": true, "requires": { - "minimatch": "^3.0.2", - "rimraf": "^2.6.3" + "minimatch": "3.0.4", + "rimraf": "2.7.1" } }, "fs-minipass": { @@ -4470,7 +4465,7 @@ "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, "requires": { - "minipass": "^2.6.0" + "minipass": "2.9.0" } }, "fs-mkdirp-stream": { @@ -4479,8 +4474,8 @@ "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "graceful-fs": "4.2.3", + "through2": "2.0.5" }, "dependencies": { "through2": { @@ -4489,8 +4484,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -4519,14 +4514,14 @@ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.3", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" }, "dependencies": { "ansi-regex": { @@ -4541,7 +4536,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "string-width": { @@ -4550,9 +4545,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -4561,7 +4556,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -4572,7 +4567,7 @@ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { - "is-property": "^1.0.2" + "is-property": "1.0.2" } }, "gensync": { @@ -4611,7 +4606,7 @@ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "get-value": { @@ -4626,7 +4621,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "git-log-parser": { @@ -4635,12 +4630,12 @@ "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", "dev": true, "requires": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "~0.6.6" + "argv-formatter": "1.0.0", + "spawn-error-forwarder": "1.0.0", + "split2": "1.0.0", + "stream-combiner2": "1.1.1", + "through2": "2.0.5", + "traverse": "0.6.6" }, "dependencies": { "split2": { @@ -4649,7 +4644,7 @@ "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", "dev": true, "requires": { - "through2": "~2.0.0" + "through2": "2.0.5" } }, "through2": { @@ -4658,8 +4653,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -4670,11 +4665,11 @@ "integrity": "sha512-SoSsFL5lnixVzctGEi2uykjA7B5I0AhO9x6kdzvGRHbxsa6JSEgrgy1esRKsfOKE1cgyOJ/KDR2Trxu157sb8w==", "dev": true, "requires": { - "dargs": "^4.0.1", - "lodash.template": "^4.0.2", - "meow": "^5.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0" + "dargs": "4.1.0", + "lodash.template": "4.5.0", + "meow": "5.0.0", + "split2": "2.2.0", + "through2": "3.0.1" } }, "glob": { @@ -4683,12 +4678,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "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" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-parent": { @@ -4697,7 +4692,7 @@ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "is-glob": "4.0.1" } }, "glob-stream": { @@ -4706,16 +4701,16 @@ "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "extend": "3.0.2", + "glob": "7.1.6", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.5.1", + "readable-stream": "2.3.7", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.3.1" }, "dependencies": { "glob-parent": { @@ -4724,8 +4719,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" } }, "is-glob": { @@ -4734,7 +4729,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -4745,7 +4740,7 @@ "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { - "ini": "^1.3.4" + "ini": "1.3.5" } }, "globals": { @@ -4778,11 +4773,11 @@ "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "dev": true, "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" + "minimist": "1.2.5", + "neo-async": "2.6.1", + "source-map": "0.6.1", + "uglify-js": "3.8.1", + "wordwrap": "1.0.0" }, "dependencies": { "source-map": { @@ -4805,8 +4800,8 @@ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "ajv": "6.12.0", + "har-schema": "2.0.0" } }, "has": { @@ -4815,7 +4810,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -4824,7 +4819,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -4859,9 +4854,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" } }, "has-values": { @@ -4870,8 +4865,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "kind-of": { @@ -4880,7 +4875,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -4891,8 +4886,8 @@ "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", "dev": true, "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" + "is-stream": "2.0.0", + "type-fest": "0.8.1" }, "dependencies": { "is-stream": { @@ -4933,12 +4928,12 @@ "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", "dev": true, "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" + "domelementtype": "1.3.1", + "domhandler": "2.4.2", + "domutils": "1.5.1", + "entities": "1.1.2", + "inherits": "2.0.4", + "readable-stream": "3.6.0" }, "dependencies": { "readable-stream": { @@ -4947,9 +4942,9 @@ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "inherits": "2.0.4", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } } } @@ -4960,9 +4955,9 @@ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "@tootallnate/once": "1.0.0", + "agent-base": "6.0.0", + "debug": "4.1.1" } }, "http-signature": { @@ -4971,9 +4966,9 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" } }, "https-proxy-agent": { @@ -4982,8 +4977,8 @@ "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "dev": true, "requires": { - "agent-base": "5", - "debug": "4" + "agent-base": "5.1.1", + "debug": "4.1.1" }, "dependencies": { "agent-base": { @@ -5006,16 +5001,16 @@ "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", "dev": true, "requires": { - "chalk": "^3.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.5.1", - "cosmiconfig": "^6.0.0", - "find-versions": "^3.2.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^4.2.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" + "chalk": "3.0.0", + "ci-info": "2.0.0", + "compare-versions": "3.6.0", + "cosmiconfig": "6.0.0", + "find-versions": "3.2.0", + "opencollective-postinstall": "2.0.2", + "pkg-dir": "4.2.0", + "please-upgrade-node": "3.2.0", + "slash": "3.0.0", + "which-pm-runs": "1.0.0" }, "dependencies": { "ansi-styles": { @@ -5024,8 +5019,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "@types/color-name": "1.1.1", + "color-convert": "2.0.1" } }, "chalk": { @@ -5034,8 +5029,8 @@ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "4.2.1", + "supports-color": "7.1.0" } }, "color-convert": { @@ -5044,7 +5039,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -5059,11 +5054,11 @@ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" + "@types/parse-json": "4.0.0", + "import-fresh": "3.2.1", + "parse-json": "5.0.0", + "path-type": "4.0.0", + "yaml": "1.8.3" } }, "has-flag": { @@ -5078,10 +5073,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "7.8.3", + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2", + "lines-and-columns": "1.1.6" } }, "path-type": { @@ -5096,7 +5091,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "has-flag": "4.0.0" } } } @@ -5117,12 +5112,12 @@ "integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU=", "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "~3.8.1", - "jsdom": "^7.0.2", - "lodash": "^4.1.0" + "css-select": "1.2.0", + "dom-serializer": "0.1.1", + "entities": "1.1.2", + "htmlparser2": "3.8.3", + "jsdom": "7.2.2", + "lodash": "4.17.15" } }, "color-logger": { @@ -5137,7 +5132,7 @@ "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "1.3.1" } }, "htmlparser2": { @@ -5146,11 +5141,11 @@ "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" + "domelementtype": "1.3.1", + "domhandler": "2.3.0", + "domutils": "1.5.1", + "entities": "1.0.0", + "readable-stream": "1.1.14" }, "dependencies": { "entities": { @@ -5173,10 +5168,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.4", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -5193,7 +5188,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "ignore": { @@ -5208,7 +5203,7 @@ "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "3.0.4" } }, "import-fresh": { @@ -5217,8 +5212,8 @@ "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "parent-module": "1.0.1", + "resolve-from": "4.0.0" }, "dependencies": { "resolve-from": { @@ -5235,7 +5230,7 @@ "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", "dev": true, "requires": { - "resolve-from": "^5.0.0" + "resolve-from": "5.0.0" } }, "imurmurhash": { @@ -5261,8 +5256,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -5283,19 +5278,19 @@ "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", + "ansi-escapes": "4.3.1", + "chalk": "3.0.0", + "cli-cursor": "3.1.0", + "cli-width": "2.2.0", + "external-editor": "3.1.0", + "figures": "3.2.0", + "lodash": "4.17.15", "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "run-async": "2.4.0", + "rxjs": "6.5.5", + "string-width": "4.2.0", + "strip-ansi": "6.0.0", + "through": "2.3.8" }, "dependencies": { "ansi-regex": { @@ -5310,8 +5305,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "@types/color-name": "1.1.1", + "color-convert": "2.0.1" } }, "chalk": { @@ -5320,8 +5315,8 @@ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "4.2.1", + "supports-color": "7.1.0" } }, "color-convert": { @@ -5330,7 +5325,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -5357,9 +5352,9 @@ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" } }, "strip-ansi": { @@ -5368,7 +5363,7 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "5.0.0" } }, "supports-color": { @@ -5377,7 +5372,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "has-flag": "4.0.0" } } } @@ -5388,8 +5383,8 @@ "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", "dev": true, "requires": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" + "from2": "2.3.0", + "p-is-promise": "3.0.0" }, "dependencies": { "p-is-promise": { @@ -5406,7 +5401,7 @@ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "^1.0.0" + "loose-envify": "1.4.0" } }, "invert-kv": { @@ -5421,8 +5416,8 @@ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "is-relative": "1.0.0", + "is-windows": "1.0.2" } }, "is-accessor-descriptor": { @@ -5431,7 +5426,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5440,7 +5435,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5469,7 +5464,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5478,7 +5473,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5495,9 +5490,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -5544,7 +5539,7 @@ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-negated-glob": { @@ -5559,7 +5554,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5568,7 +5563,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5585,7 +5580,7 @@ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", "dev": true, "requires": { - "symbol-observable": "^1.1.0" + "symbol-observable": "1.2.0" } }, "is-plain-obj": { @@ -5600,7 +5595,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "is-promise": { @@ -5621,7 +5616,7 @@ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has": "^1.0.3" + "has": "1.0.3" } }, "is-regexp": { @@ -5636,7 +5631,7 @@ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "is-unc-path": "1.0.0" } }, "is-stream": { @@ -5651,7 +5646,7 @@ "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "has-symbols": "1.0.1" } }, "is-text-path": { @@ -5660,7 +5655,7 @@ "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", "dev": true, "requires": { - "text-extensions": "^1.0.0" + "text-extensions": "1.9.0" } }, "is-typedarray": { @@ -5675,7 +5670,7 @@ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" + "unc-path-regex": "0.1.2" } }, "is-utf8": { @@ -5726,11 +5721,11 @@ "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", "dev": true, "requires": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" + "lodash.capitalize": "4.2.1", + "lodash.escaperegexp": "4.1.2", + "lodash.isplainobject": "4.0.6", + "lodash.isstring": "4.0.1", + "lodash.uniqby": "4.7.0" } }, "istanbul-lib-coverage": { @@ -5745,7 +5740,7 @@ "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { - "append-transform": "^2.0.0" + "append-transform": "2.0.0" } }, "istanbul-lib-instrument": { @@ -5754,13 +5749,13 @@ "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", "dev": true, "requires": { - "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "@babel/core": "7.9.0", + "@babel/parser": "7.9.4", + "@babel/template": "7.8.6", + "@babel/traverse": "7.9.0", + "@istanbuljs/schema": "0.1.2", + "istanbul-lib-coverage": "3.0.0", + "semver": "6.3.0" }, "dependencies": { "semver": { @@ -5777,13 +5772,13 @@ "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", "dev": true, "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "archy": "1.0.0", + "cross-spawn": "7.0.1", + "istanbul-lib-coverage": "3.0.0", + "make-dir": "3.0.2", + "p-map": "3.0.0", + "rimraf": "3.0.2", + "uuid": "3.4.0" }, "dependencies": { "cross-spawn": { @@ -5792,9 +5787,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "p-map": { @@ -5803,7 +5798,7 @@ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "aggregate-error": "^3.0.0" + "aggregate-error": "3.0.1" } }, "path-key": { @@ -5818,7 +5813,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "shebang-command": { @@ -5827,7 +5822,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -5842,7 +5837,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -5853,9 +5848,9 @@ "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "istanbul-lib-coverage": "3.0.0", + "make-dir": "3.0.2", + "supports-color": "7.1.0" }, "dependencies": { "has-flag": { @@ -5870,7 +5865,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "has-flag": "4.0.0" } } } @@ -5881,9 +5876,9 @@ "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "debug": "4.1.1", + "istanbul-lib-coverage": "3.0.0", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -5900,8 +5895,8 @@ "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "html-escaper": "2.0.2", + "istanbul-lib-report": "3.0.0" } }, "java-properties": { @@ -5928,8 +5923,8 @@ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.1" } }, "jsbn": { @@ -5951,21 +5946,21 @@ "dev": true, "optional": true, "requires": { - "abab": "^1.0.0", - "acorn": "^2.4.0", - "acorn-globals": "^1.0.4", - "cssom": ">= 0.3.0 < 0.4.0", - "cssstyle": ">= 0.2.29 < 0.3.0", - "escodegen": "^1.6.1", - "nwmatcher": ">= 1.3.7 < 2.0.0", - "parse5": "^1.5.1", - "request": "^2.55.0", - "sax": "^1.1.4", - "symbol-tree": ">= 3.1.0 < 4.0.0", - "tough-cookie": "^2.2.0", - "webidl-conversions": "^2.0.0", - "whatwg-url-compat": "~0.6.5", - "xml-name-validator": ">= 2.0.1 < 3.0.0" + "abab": "1.0.4", + "acorn": "2.7.0", + "acorn-globals": "1.0.9", + "cssom": "0.3.8", + "cssstyle": "0.2.37", + "escodegen": "1.14.1", + "nwmatcher": "1.4.4", + "parse5": "1.5.1", + "request": "2.88.2", + "sax": "1.2.4", + "symbol-tree": "3.2.4", + "tough-cookie": "2.5.0", + "webidl-conversions": "2.0.1", + "whatwg-url-compat": "0.6.5", + "xml-name-validator": "2.0.1" }, "dependencies": { "acorn": { @@ -6026,7 +6021,7 @@ "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "1.2.5" } }, "jsonc-parser": { @@ -6041,7 +6036,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.3" } }, "jsonparse": { @@ -6050,6 +6045,16 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -6076,7 +6081,7 @@ "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "jws": { @@ -6085,8 +6090,8 @@ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "dev": true, "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" + "jwa": "1.4.1", + "safe-buffer": "5.1.2" } }, "kind-of": { @@ -6101,7 +6106,7 @@ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "dev": true, "requires": { - "graceful-fs": "^4.1.9" + "graceful-fs": "4.2.3" } }, "lazystream": { @@ -6110,7 +6115,7 @@ "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, "requires": { - "readable-stream": "^2.0.5" + "readable-stream": "2.3.7" } }, "lcid": { @@ -6119,7 +6124,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "invert-kv": "2.0.0" } }, "lcov-result-merger": { @@ -6128,9 +6133,9 @@ "integrity": "sha512-vGXaMNGZRr4cYvW+xMVg+rg7qd5DX9SbGXl+0S3k85+gRZVK4K7UvxPWzKb/qiMwe+4bx3EOrW2o4mbdb1WnsA==", "dev": true, "requires": { - "through2": "^2.0.3", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.2" + "through2": "2.0.5", + "vinyl": "2.2.0", + "vinyl-fs": "3.0.3" }, "dependencies": { "through2": { @@ -6139,8 +6144,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -6151,7 +6156,7 @@ "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, "requires": { - "flush-write-stream": "^1.0.2" + "flush-write-stream": "1.1.1" } }, "levn": { @@ -6160,8 +6165,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" } }, "lines-and-columns": { @@ -6176,7 +6181,7 @@ "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", "dev": true, "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "1.0.6" } }, "lint-staged": { @@ -6185,19 +6190,19 @@ "integrity": "sha512-wAeu/ePaBAOfwM2+cVbgPWDtn17B0Sxiv0NvNEqDAIvB8Yhvl60vafKFiK4grcYn87K1iK+a0zVoETvKbdT9/Q==", "dev": true, "requires": { - "chalk": "^3.0.0", - "commander": "^4.0.1", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "execa": "^3.4.0", - "listr": "^0.14.3", - "log-symbols": "^3.0.0", - "micromatch": "^4.0.2", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", + "chalk": "3.0.0", + "commander": "4.1.1", + "cosmiconfig": "6.0.0", + "debug": "4.1.1", + "dedent": "0.7.0", + "execa": "3.4.0", + "listr": "0.14.3", + "log-symbols": "3.0.0", + "micromatch": "4.0.2", + "normalize-path": "3.0.0", + "please-upgrade-node": "3.2.0", "string-argv": "0.3.1", - "stringify-object": "^3.3.0" + "stringify-object": "3.3.0" }, "dependencies": { "ansi-styles": { @@ -6206,8 +6211,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "@types/color-name": "1.1.1", + "color-convert": "2.0.1" } }, "braces": { @@ -6216,7 +6221,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "7.0.1" } }, "chalk": { @@ -6225,8 +6230,8 @@ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-styles": "4.2.1", + "supports-color": "7.1.0" } }, "color-convert": { @@ -6235,7 +6240,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -6256,11 +6261,11 @@ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" + "@types/parse-json": "4.0.0", + "import-fresh": "3.2.1", + "parse-json": "5.0.0", + "path-type": "4.0.0", + "yaml": "1.8.3" } }, "cross-spawn": { @@ -6269,9 +6274,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "execa": { @@ -6280,16 +6285,16 @@ "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "cross-spawn": "7.0.1", + "get-stream": "5.1.0", + "human-signals": "1.1.1", + "is-stream": "2.0.0", + "merge-stream": "2.0.0", + "npm-run-path": "4.0.1", + "onetime": "5.1.0", + "p-finally": "2.0.1", + "signal-exit": "3.0.3", + "strip-final-newline": "2.0.0" } }, "fill-range": { @@ -6298,7 +6303,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "to-regex-range": "5.0.1" } }, "get-stream": { @@ -6307,7 +6312,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "has-flag": { @@ -6334,7 +6339,7 @@ "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "2.4.2" }, "dependencies": { "ansi-styles": { @@ -6343,7 +6348,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.3" } }, "chalk": { @@ -6352,9 +6357,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.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "color-convert": { @@ -6384,7 +6389,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -6395,8 +6400,8 @@ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "3.0.2", + "picomatch": "2.2.2" } }, "normalize-path": { @@ -6411,7 +6416,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^3.0.0" + "path-key": "3.1.1" } }, "p-finally": { @@ -6426,10 +6431,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "7.8.3", + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2", + "lines-and-columns": "1.1.6" } }, "path-key": { @@ -6450,7 +6455,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -6465,7 +6470,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "has-flag": "4.0.0" } }, "to-regex-range": { @@ -6474,7 +6479,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^7.0.0" + "is-number": "7.0.0" } }, "which": { @@ -6483,7 +6488,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -6494,15 +6499,15 @@ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" + "@samverschueren/stream-to-observable": "0.3.0", + "is-observable": "1.1.0", + "is-promise": "2.1.0", + "is-stream": "1.1.0", + "listr-silent-renderer": "1.1.1", + "listr-update-renderer": "0.5.0", + "listr-verbose-renderer": "0.5.0", + "p-map": "2.1.0", + "rxjs": "6.5.5" } }, "listr-silent-renderer": { @@ -6517,14 +6522,14 @@ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", "dev": true, "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" + "chalk": "1.1.3", + "cli-truncate": "0.2.1", + "elegant-spinner": "1.0.1", + "figures": "1.7.0", + "indent-string": "3.2.0", + "log-symbols": "1.0.2", + "log-update": "2.3.0", + "strip-ansi": "3.0.1" }, "dependencies": { "ansi-regex": { @@ -6545,11 +6550,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "figures": { @@ -6558,8 +6563,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" } }, "log-symbols": { @@ -6568,7 +6573,7 @@ "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", "dev": true, "requires": { - "chalk": "^1.0.0" + "chalk": "1.1.3" } }, "strip-ansi": { @@ -6577,7 +6582,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -6594,10 +6599,10 @@ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", "dev": true, "requires": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" + "chalk": "2.4.2", + "cli-cursor": "2.1.0", + "date-fns": "1.30.1", + "figures": "2.0.0" }, "dependencies": { "cli-cursor": { @@ -6606,7 +6611,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "2.0.0" } }, "figures": { @@ -6615,7 +6620,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "1.0.5" } }, "mimic-fn": { @@ -6630,7 +6635,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "restore-cursor": { @@ -6639,8 +6644,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "onetime": "2.0.1", + "signal-exit": "3.0.3" } } } @@ -6651,10 +6656,10 @@ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "graceful-fs": "4.2.3", + "parse-json": "4.0.0", + "pify": "3.0.0", + "strip-bom": "3.0.0" } }, "locate-path": { @@ -6663,8 +6668,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": { @@ -6810,8 +6815,8 @@ "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.templatesettings": "4.2.0" } }, "lodash.templatesettings": { @@ -6820,7 +6825,7 @@ "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0" + "lodash._reinterpolate": "3.0.0" } }, "lodash.toarray": { @@ -6847,7 +6852,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "2.4.2" } }, "log-update": { @@ -6856,9 +6861,9 @@ "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" + "ansi-escapes": "3.2.0", + "cli-cursor": "2.1.0", + "wrap-ansi": "3.0.1" }, "dependencies": { "ansi-escapes": { @@ -6873,7 +6878,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "2.0.0" } }, "mimic-fn": { @@ -6888,7 +6893,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "restore-cursor": { @@ -6897,8 +6902,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "onetime": "2.0.1", + "signal-exit": "3.0.3" } }, "wrap-ansi": { @@ -6907,8 +6912,8 @@ "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0" } } } @@ -6931,7 +6936,7 @@ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "js-tokens": "3.0.2" } }, "loud-rejection": { @@ -6940,8 +6945,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.3" } }, "lru-cache": { @@ -6950,7 +6955,7 @@ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "yallist": "^3.0.2" + "yallist": "3.1.1" } }, "macos-release": { @@ -6965,7 +6970,7 @@ "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { - "semver": "^6.0.0" + "semver": "6.3.0" }, "dependencies": { "semver": { @@ -6982,7 +6987,7 @@ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "p-defer": "1.0.0" } }, "map-cache": { @@ -7003,7 +7008,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "mariadb": { @@ -7012,12 +7017,12 @@ "integrity": "sha512-suv+ygoiS+tQSKmxgzJsGV9R+USN8g6Ql+GuMo9k7alD6FxOT/lwebLHy63/7yPZfVtlyAitK1tPd7ZoFhN/Sg==", "dev": true, "requires": { - "@types/geojson": "^7946.0.7", - "@types/node": ">=8.0.0", - "denque": "^1.4.1", - "iconv-lite": "^0.5.1", - "long": "^4.0.0", - "moment-timezone": "^0.5.27" + "@types/geojson": "7946.0.7", + "@types/node": "12.12.34", + "denque": "1.4.1", + "iconv-lite": "0.5.1", + "long": "4.0.0", + "moment-timezone": "0.5.28" }, "dependencies": { "iconv-lite": { @@ -7026,7 +7031,7 @@ "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } } } @@ -7037,11 +7042,11 @@ "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", "dev": true, "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "argparse": "1.0.10", + "entities": "2.0.0", + "linkify-it": "2.2.0", + "mdurl": "1.0.1", + "uc.micro": "1.0.6" }, "dependencies": { "entities": { @@ -7067,19 +7072,19 @@ "integrity": "sha512-gvnczz3W3Wgex851/cIQ/2y8GNhY+EVK8Ael8kRd8hoSQ0ps9xjhtwPwMyJPoiYbAoPxG6vSBFISiysaAbCEZg==", "dev": true, "requires": { - "commander": "~2.9.0", - "deep-extend": "~0.5.1", - "get-stdin": "~5.0.1", - "glob": "~7.1.2", - "ignore": "~5.1.4", - "js-yaml": "~3.13.1", - "jsonc-parser": "~2.2.0", - "lodash.differencewith": "~4.5.0", - "lodash.flatten": "~4.4.0", - "markdownlint": "~0.18.0", - "markdownlint-rule-helpers": "~0.6.0", - "minimatch": "~3.0.4", - "rc": "~1.2.7" + "commander": "2.9.0", + "deep-extend": "0.5.1", + "get-stdin": "5.0.1", + "glob": "7.1.6", + "ignore": "5.1.4", + "js-yaml": "3.13.1", + "jsonc-parser": "2.2.1", + "lodash.differencewith": "4.5.0", + "lodash.flatten": "4.4.0", + "markdownlint": "0.18.0", + "markdownlint-rule-helpers": "0.6.0", + "minimatch": "3.0.4", + "rc": "1.2.8" }, "dependencies": { "commander": { @@ -7088,7 +7093,7 @@ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", "dev": true, "requires": { - "graceful-readlink": ">= 1.0.0" + "graceful-readlink": "1.0.1" } }, "get-stdin": { @@ -7123,12 +7128,12 @@ "integrity": "sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==", "dev": true, "requires": { - "ansi-escapes": "^3.1.0", - "cardinal": "^2.1.1", - "chalk": "^2.4.1", - "cli-table": "^0.3.1", - "node-emoji": "^1.4.1", - "supports-hyperlinks": "^1.0.1" + "ansi-escapes": "3.2.0", + "cardinal": "2.1.1", + "chalk": "2.4.2", + "cli-table": "0.3.1", + "node-emoji": "1.10.0", + "supports-hyperlinks": "1.0.1" }, "dependencies": { "ansi-escapes": { @@ -7151,9 +7156,9 @@ "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" + "map-age-cleaner": "0.1.3", + "mimic-fn": "2.1.0", + "p-is-promise": "2.1.0" } }, "meow": { @@ -7162,15 +7167,15 @@ "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" + "camelcase-keys": "4.2.0", + "decamelize-keys": "1.1.0", + "loud-rejection": "1.6.0", + "minimist-options": "3.0.2", + "normalize-package-data": "2.5.0", + "read-pkg-up": "3.0.0", + "redent": "2.0.0", + "trim-newlines": "2.0.0", + "yargs-parser": "10.1.0" } }, "merge-stream": { @@ -7191,19 +7196,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "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" + "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.3", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "mime": { @@ -7239,7 +7244,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -7254,8 +7259,8 @@ "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", "dev": true, "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "arrify": "1.0.1", + "is-plain-obj": "1.1.0" } }, "minipass": { @@ -7264,8 +7269,8 @@ "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "safe-buffer": "5.1.2", + "yallist": "3.1.1" } }, "minizlib": { @@ -7274,7 +7279,7 @@ "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "dev": true, "requires": { - "minipass": "^2.9.0" + "minipass": "2.9.0" } }, "mixin-deep": { @@ -7283,8 +7288,8 @@ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -7293,7 +7298,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -7304,7 +7309,7 @@ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "1.2.5" } }, "mocha": { @@ -7356,9 +7361,9 @@ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "3.1.0", + "strip-ansi": "5.2.0", + "wrap-ansi": "5.1.0" } }, "debug": { @@ -7367,7 +7372,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.1" } }, "emoji-regex": { @@ -7382,7 +7387,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "get-caller-file": { @@ -7397,12 +7402,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "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" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "locate-path": { @@ -7411,8 +7416,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" } }, "mkdirp": { @@ -7421,7 +7426,7 @@ "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "1.2.5" } }, "ms": { @@ -7436,7 +7441,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -7445,7 +7450,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -7466,9 +7471,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" } }, "strip-ansi": { @@ -7477,7 +7482,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } }, "supports-color": { @@ -7486,7 +7491,7 @@ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "wrap-ansi": { @@ -7495,9 +7500,9 @@ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "3.2.1", + "string-width": "3.1.0", + "strip-ansi": "5.2.0" } }, "yargs": { @@ -7506,16 +7511,16 @@ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "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.1.2" + "cliui": "5.0.0", + "find-up": "3.0.0", + "get-caller-file": "2.0.5", + "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.2" } }, "yargs-parser": { @@ -7524,8 +7529,8 @@ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } @@ -7546,7 +7551,7 @@ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "requires": { - "moment": ">= 2.9.0" + "moment": "2.24.0" } }, "ms": { @@ -7566,14 +7571,14 @@ "integrity": "sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==", "dev": true, "requires": { - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.5.0", - "long": "^4.0.0", - "lru-cache": "^5.1.1", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.1" + "denque": "1.4.1", + "generate-function": "2.3.1", + "iconv-lite": "0.5.1", + "long": "4.0.0", + "lru-cache": "5.1.1", + "named-placeholders": "1.1.2", + "seq-queue": "0.0.5", + "sqlstring": "2.3.1" }, "dependencies": { "iconv-lite": { @@ -7582,7 +7587,7 @@ "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } } } @@ -7593,7 +7598,7 @@ "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", "dev": true, "requires": { - "lru-cache": "^4.1.3" + "lru-cache": "4.1.5" }, "dependencies": { "lru-cache": { @@ -7602,8 +7607,8 @@ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "yallist": { @@ -7626,17 +7631,17 @@ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "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.3", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "native-duplexpair": { @@ -7657,9 +7662,9 @@ "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", "dev": true, "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "3.2.6", + "iconv-lite": "0.4.24", + "sax": "1.2.4" }, "dependencies": { "debug": { @@ -7668,7 +7673,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } } } @@ -7697,11 +7702,11 @@ "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", "dev": true, "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "@sinonjs/formatio": "3.2.2", + "@sinonjs/text-encoding": "0.7.1", + "just-extend": "4.1.0", + "lolex": "5.1.2", + "path-to-regexp": "1.8.0" }, "dependencies": { "lolex": { @@ -7710,7 +7715,7 @@ "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@sinonjs/commons": "1.7.1" } } } @@ -7721,7 +7726,7 @@ "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", "dev": true, "requires": { - "lodash.toarray": "^4.4.0" + "lodash.toarray": "4.4.0" } }, "node-environment-flags": { @@ -7730,8 +7735,8 @@ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", "dev": true, "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" + "object.getownpropertydescriptors": "2.1.0", + "semver": "5.7.1" }, "dependencies": { "semver": { @@ -7754,16 +7759,16 @@ "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", "dev": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "detect-libc": "1.0.3", + "mkdirp": "0.5.5", + "needle": "2.4.1", + "nopt": "4.0.3", + "npm-packlist": "1.4.8", + "npmlog": "4.1.2", + "rc": "1.2.8", + "rimraf": "2.7.1", + "semver": "5.7.1", + "tar": "4.4.13" }, "dependencies": { "semver": { @@ -7780,7 +7785,7 @@ "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { - "process-on-spawn": "^1.0.0" + "process-on-spawn": "1.0.0" } }, "nopt": { @@ -7789,8 +7794,8 @@ "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "dev": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "normalize-package-data": { @@ -7799,10 +7804,10 @@ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.8.8", + "resolve": "1.15.1", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4" }, "dependencies": { "semver": { @@ -7819,7 +7824,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "normalize-url": { @@ -7834,7 +7839,7 @@ "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", "dev": true, "requires": { - "once": "^1.3.2" + "once": "1.4.0" } }, "npm": { @@ -7843,140 +7848,131 @@ "integrity": "sha512-B8UDDbWvdkW6RgXFn8/h2cHJP/u/FPa4HWeGzW23aNEBARN3QPrRaHqPIZW2NSN3fW649gtgUDNZpaRs0zTMPw==", "dev": true, "requires": { - "JSONStream": "^1.3.5", - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "aproba": "^2.0.0", - "archy": "~1.0.0", - "bin-links": "^1.1.7", - "bluebird": "^3.5.5", - "byte-size": "^5.0.1", - "cacache": "^12.0.3", - "call-limit": "^1.1.1", - "chownr": "^1.1.4", - "ci-info": "^2.0.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.5.1", - "cmd-shim": "^3.0.3", - "columnify": "~1.5.4", - "config-chain": "^1.1.12", - "debuglog": "*", - "detect-indent": "~5.0.0", - "detect-newline": "^2.1.0", - "dezalgo": "~1.0.3", - "editor": "~1.0.0", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "fs-vacuum": "~1.2.10", - "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.3.0", - "glob": "^7.1.6", - "graceful-fs": "^4.2.3", - "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.8", - "iferr": "^1.0.2", - "imurmurhash": "*", - "infer-owner": "^1.0.4", - "inflight": "~1.0.6", - "inherits": "^2.0.4", - "ini": "^1.3.5", - "init-package-json": "^1.10.3", - "is-cidr": "^3.0.0", - "json-parse-better-errors": "^1.0.2", - "lazy-property": "~1.0.0", - "libcipm": "^4.0.7", - "libnpm": "^3.0.1", - "libnpmaccess": "^3.0.2", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "libnpx": "^10.2.2", - "lock-verify": "^2.1.0", - "lockfile": "^1.0.4", - "lodash._baseindexof": "*", - "lodash._baseuniq": "~4.6.0", - "lodash._bindcallback": "*", - "lodash._cacheindexof": "*", - "lodash._createcache": "*", - "lodash._getnative": "*", - "lodash.clonedeep": "~4.5.0", - "lodash.restparam": "*", - "lodash.union": "~4.6.0", - "lodash.uniq": "~4.5.0", - "lodash.without": "~4.4.0", - "lru-cache": "^5.1.1", - "meant": "~1.0.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.4", - "move-concurrently": "^1.0.1", - "node-gyp": "^5.1.0", - "nopt": "~4.0.1", - "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.2", - "npm-cache-filename": "~1.0.2", - "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.4", - "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.8", - "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.4", - "npm-registry-fetch": "^4.0.3", - "npm-user-validate": "~1.0.0", - "npmlog": "~4.1.2", - "once": "~1.4.0", - "opener": "^1.5.1", - "osenv": "^0.1.5", - "pacote": "^9.5.12", - "path-is-inside": "~1.0.2", - "promise-inflight": "~1.0.1", - "qrcode-terminal": "^0.12.0", - "query-string": "^6.8.2", - "qw": "~1.0.1", - "read": "~1.0.7", - "read-cmd-shim": "^1.0.5", - "read-installed": "~4.0.3", - "read-package-json": "^2.1.1", - "read-package-tree": "^5.3.1", - "readable-stream": "^3.6.0", - "readdir-scoped-modules": "^1.1.0", - "request": "^2.88.0", - "retry": "^0.12.0", - "rimraf": "^2.7.1", - "safe-buffer": "^5.1.2", - "semver": "^5.7.1", - "sha": "^3.0.0", - "slide": "~1.1.6", - "sorted-object": "~2.0.1", - "sorted-union-stream": "~2.1.3", - "ssri": "^6.0.1", - "stringify-package": "^1.0.1", - "tar": "^4.4.13", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", + "abbrev": "1.1.1", + "ansicolors": "0.3.2", + "ansistyles": "0.1.3", + "aproba": "2.0.0", + "archy": "1.0.0", + "bin-links": "1.1.7", + "bluebird": "3.5.5", + "byte-size": "5.0.1", + "cacache": "12.0.3", + "call-limit": "1.1.1", + "chownr": "1.1.4", + "ci-info": "2.0.0", + "cli-columns": "3.1.2", + "cli-table3": "0.5.1", + "cmd-shim": "3.0.3", + "columnify": "1.5.4", + "config-chain": "1.1.12", + "debuglog": "1.0.1", + "detect-indent": "5.0.0", + "detect-newline": "2.1.0", + "dezalgo": "1.0.3", + "editor": "1.0.0", + "figgy-pudding": "3.5.1", + "find-npm-prefix": "1.0.2", + "fs-vacuum": "1.2.10", + "fs-write-stream-atomic": "1.0.10", + "gentle-fs": "2.3.0", + "glob": "7.1.6", + "graceful-fs": "4.2.3", + "has-unicode": "2.0.1", + "hosted-git-info": "2.8.8", + "iferr": "1.0.2", + "imurmurhash": "0.1.4", + "infer-owner": "1.0.4", + "inflight": "1.0.6", + "inherits": "2.0.4", + "ini": "1.3.5", + "init-package-json": "1.10.3", + "is-cidr": "3.0.0", + "json-parse-better-errors": "1.0.2", + "JSONStream": "1.3.5", + "lazy-property": "1.0.0", + "libcipm": "4.0.7", + "libnpm": "3.0.1", + "libnpmaccess": "3.0.2", + "libnpmhook": "5.0.3", + "libnpmorg": "1.0.1", + "libnpmsearch": "2.0.2", + "libnpmteam": "1.0.2", + "libnpx": "10.2.2", + "lock-verify": "2.1.0", + "lockfile": "1.0.4", + "lodash._baseindexof": "3.1.0", + "lodash._baseuniq": "4.6.0", + "lodash._bindcallback": "3.0.1", + "lodash._cacheindexof": "3.0.2", + "lodash._createcache": "3.1.2", + "lodash._getnative": "3.9.1", + "lodash.clonedeep": "4.5.0", + "lodash.restparam": "3.6.1", + "lodash.union": "4.6.0", + "lodash.uniq": "4.5.0", + "lodash.without": "4.4.0", + "lru-cache": "5.1.1", + "meant": "1.0.1", + "mississippi": "3.0.0", + "mkdirp": "0.5.4", + "move-concurrently": "1.0.1", + "node-gyp": "5.1.0", + "nopt": "4.0.1", + "normalize-package-data": "2.5.0", + "npm-audit-report": "1.3.2", + "npm-cache-filename": "1.0.2", + "npm-install-checks": "3.0.2", + "npm-lifecycle": "3.1.4", + "npm-package-arg": "6.1.1", + "npm-packlist": "1.4.8", + "npm-pick-manifest": "3.0.2", + "npm-profile": "4.0.4", + "npm-registry-fetch": "4.0.3", + "npm-user-validate": "1.0.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "opener": "1.5.1", + "osenv": "0.1.5", + "pacote": "9.5.12", + "path-is-inside": "1.0.2", + "promise-inflight": "1.0.1", + "qrcode-terminal": "0.12.0", + "query-string": "6.8.2", + "qw": "1.0.1", + "read": "1.0.7", + "read-cmd-shim": "1.0.5", + "read-installed": "4.0.3", + "read-package-json": "2.1.1", + "read-package-tree": "5.3.1", + "readable-stream": "3.6.0", + "readdir-scoped-modules": "1.1.0", + "request": "2.88.0", + "retry": "0.12.0", + "rimraf": "2.7.1", + "safe-buffer": "5.1.2", + "semver": "5.7.1", + "sha": "3.0.0", + "slide": "1.1.6", + "sorted-object": "2.0.1", + "sorted-union-stream": "2.1.3", + "ssri": "6.0.1", + "stringify-package": "1.0.1", + "tar": "4.4.13", + "text-table": "0.2.0", + "tiny-relative-date": "1.3.0", "uid-number": "0.0.6", - "umask": "~1.1.0", - "unique-filename": "^1.1.1", - "unpipe": "~1.0.0", - "update-notifier": "^2.5.0", - "uuid": "^3.3.3", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^1.3.1", - "worker-farm": "^1.7.0", - "write-file-atomic": "^2.4.3" + "umask": "1.1.0", + "unique-filename": "1.1.1", + "unpipe": "1.0.0", + "update-notifier": "2.5.0", + "uuid": "3.3.3", + "validate-npm-package-license": "3.0.4", + "validate-npm-package-name": "3.0.0", + "which": "1.3.1", + "worker-farm": "1.7.0", + "write-file-atomic": "2.4.3" }, "dependencies": { - "JSONStream": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "abbrev": { "version": "1.1.1", "bundled": true, @@ -7987,7 +7983,7 @@ "bundled": true, "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "es6-promisify": "5.0.0" } }, "agentkeepalive": { @@ -7995,7 +7991,7 @@ "bundled": true, "dev": true, "requires": { - "humanize-ms": "^1.2.1" + "humanize-ms": "1.2.1" } }, "ajv": { @@ -8003,10 +7999,10 @@ "bundled": true, "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "ansi-align": { @@ -8014,7 +8010,7 @@ "bundled": true, "dev": true, "requires": { - "string-width": "^2.0.0" + "string-width": "2.1.1" } }, "ansi-regex": { @@ -8027,7 +8023,7 @@ "bundled": true, "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.1" } }, "ansicolors": { @@ -8055,8 +8051,8 @@ "bundled": true, "dev": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" }, "dependencies": { "readable-stream": { @@ -8064,13 +8060,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -8078,7 +8074,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8093,7 +8089,7 @@ "bundled": true, "dev": true, "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "assert-plus": { @@ -8127,7 +8123,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "bin-links": { @@ -8135,12 +8131,12 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "^3.5.3", - "cmd-shim": "^3.0.0", - "gentle-fs": "^2.3.0", - "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0", - "write-file-atomic": "^2.3.0" + "bluebird": "3.5.5", + "cmd-shim": "3.0.3", + "gentle-fs": "2.3.0", + "graceful-fs": "4.2.3", + "npm-normalize-package-bin": "1.0.1", + "write-file-atomic": "2.4.3" } }, "bluebird": { @@ -8153,13 +8149,13 @@ "bundled": true, "dev": true, "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "2.4.1", + "cli-boxes": "1.0.0", + "string-width": "2.1.1", + "term-size": "1.2.0", + "widest-line": "2.0.1" } }, "brace-expansion": { @@ -8167,7 +8163,7 @@ "bundled": true, "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -8196,21 +8192,21 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "bluebird": "3.5.5", + "chownr": "1.1.4", + "figgy-pudding": "3.5.1", + "glob": "7.1.6", + "graceful-fs": "4.2.3", + "infer-owner": "1.0.4", + "lru-cache": "5.1.1", + "mississippi": "3.0.0", + "mkdirp": "0.5.4", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.7.1", + "ssri": "6.0.1", + "unique-filename": "1.1.1", + "y18n": "4.0.0" } }, "call-limit": { @@ -8238,9 +8234,9 @@ "bundled": true, "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "chownr": { @@ -8258,7 +8254,7 @@ "bundled": true, "dev": true, "requires": { - "ip-regex": "^2.1.0" + "ip-regex": "2.1.0" } }, "cli-boxes": { @@ -8271,8 +8267,8 @@ "bundled": true, "dev": true, "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" + "string-width": "2.1.1", + "strip-ansi": "3.0.1" } }, "cli-table3": { @@ -8280,9 +8276,9 @@ "bundled": true, "dev": true, "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" + "colors": "1.3.3", + "object-assign": "4.1.1", + "string-width": "2.1.1" } }, "cliui": { @@ -8290,9 +8286,9 @@ "bundled": true, "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" }, "dependencies": { "ansi-regex": { @@ -8305,7 +8301,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -8320,8 +8316,8 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" + "graceful-fs": "4.2.3", + "mkdirp": "0.5.4" } }, "co": { @@ -8339,7 +8335,7 @@ "bundled": true, "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -8358,8 +8354,8 @@ "bundled": true, "dev": true, "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" + "strip-ansi": "3.0.1", + "wcwidth": "1.0.1" } }, "combined-stream": { @@ -8367,7 +8363,7 @@ "bundled": true, "dev": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "concat-map": { @@ -8380,10 +8376,10 @@ "bundled": true, "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "buffer-from": "1.0.0", + "inherits": "2.0.4", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" }, "dependencies": { "readable-stream": { @@ -8391,13 +8387,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -8405,7 +8401,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8415,8 +8411,8 @@ "bundled": true, "dev": true, "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "ini": "1.3.5", + "proto-list": "1.2.4" } }, "configstore": { @@ -8424,12 +8420,12 @@ "bundled": true, "dev": true, "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" + "dot-prop": "4.2.0", + "graceful-fs": "4.2.3", + "make-dir": "1.3.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.4.3", + "xdg-basedir": "3.0.0" } }, "console-control-strings": { @@ -8442,12 +8438,12 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" + "aproba": "1.2.0", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.4", + "rimraf": "2.7.1", + "run-queue": "1.0.3" }, "dependencies": { "aproba": { @@ -8472,7 +8468,7 @@ "bundled": true, "dev": true, "requires": { - "capture-stack-trace": "^1.0.0" + "capture-stack-trace": "1.0.0" } }, "cross-spawn": { @@ -8480,9 +8476,9 @@ "bundled": true, "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "4.1.5", + "shebang-command": "1.2.0", + "which": "1.3.1" }, "dependencies": { "lru-cache": { @@ -8490,8 +8486,8 @@ "bundled": true, "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "yallist": { @@ -8516,7 +8512,7 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "debug": { @@ -8559,7 +8555,7 @@ "bundled": true, "dev": true, "requires": { - "clone": "^1.0.2" + "clone": "1.0.4" } }, "define-properties": { @@ -8567,7 +8563,7 @@ "bundled": true, "dev": true, "requires": { - "object-keys": "^1.0.12" + "object-keys": "1.0.12" } }, "delayed-stream": { @@ -8595,8 +8591,8 @@ "bundled": true, "dev": true, "requires": { - "asap": "^2.0.0", - "wrappy": "1" + "asap": "2.0.6", + "wrappy": "1.0.2" } }, "dot-prop": { @@ -8604,7 +8600,7 @@ "bundled": true, "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "dotenv": { @@ -8622,10 +8618,10 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "inherits": "2.0.4", + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" }, "dependencies": { "readable-stream": { @@ -8633,13 +8629,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -8647,7 +8643,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8658,8 +8654,8 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "editor": { @@ -8672,7 +8668,7 @@ "bundled": true, "dev": true, "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "0.4.23" } }, "end-of-stream": { @@ -8680,7 +8676,7 @@ "bundled": true, "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "env-paths": { @@ -8698,7 +8694,7 @@ "bundled": true, "dev": true, "requires": { - "prr": "~1.0.1" + "prr": "1.0.1" } }, "es-abstract": { @@ -8706,11 +8702,11 @@ "bundled": true, "dev": true, "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "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" } }, "es-to-primitive": { @@ -8718,9 +8714,9 @@ "bundled": true, "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" } }, "es6-promise": { @@ -8733,7 +8729,7 @@ "bundled": true, "dev": true, "requires": { - "es6-promise": "^4.0.3" + "es6-promise": "4.2.8" } }, "escape-string-regexp": { @@ -8746,13 +8742,13 @@ "bundled": true, "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" + "cross-spawn": "5.1.0", + "get-stream": "3.0.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" }, "dependencies": { "get-stream": { @@ -8797,7 +8793,7 @@ "bundled": true, "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "flush-write-stream": { @@ -8805,8 +8801,8 @@ "bundled": true, "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "2.0.4", + "readable-stream": "2.3.6" }, "dependencies": { "readable-stream": { @@ -8814,13 +8810,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -8828,7 +8824,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8843,9 +8839,9 @@ "bundled": true, "dev": true, "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.19" } }, "from2": { @@ -8853,8 +8849,8 @@ "bundled": true, "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "inherits": "2.0.4", + "readable-stream": "2.3.6" }, "dependencies": { "readable-stream": { @@ -8862,13 +8858,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -8876,7 +8872,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8886,7 +8882,7 @@ "bundled": true, "dev": true, "requires": { - "minipass": "^2.6.0" + "minipass": "2.9.0" }, "dependencies": { "minipass": { @@ -8894,8 +8890,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "safe-buffer": "5.1.2", + "yallist": "3.0.3" } } } @@ -8905,9 +8901,9 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" + "graceful-fs": "4.2.3", + "path-is-inside": "1.0.2", + "rimraf": "2.7.1" } }, "fs-write-stream-atomic": { @@ -8915,10 +8911,10 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" + "graceful-fs": "4.2.3", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.3.6" }, "dependencies": { "iferr": { @@ -8931,13 +8927,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -8945,7 +8941,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8965,14 +8961,14 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" }, "dependencies": { "aproba": { @@ -8985,9 +8981,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -9002,17 +8998,17 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^1.1.2", - "chownr": "^1.1.2", - "cmd-shim": "^3.0.3", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "infer-owner": "^1.0.4", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" + "aproba": "1.2.0", + "chownr": "1.1.4", + "cmd-shim": "3.0.3", + "fs-vacuum": "1.2.10", + "graceful-fs": "4.2.3", + "iferr": "0.1.5", + "infer-owner": "1.0.4", + "mkdirp": "0.5.4", + "path-is-inside": "1.0.2", + "read-cmd-shim": "1.0.5", + "slide": "1.1.6" }, "dependencies": { "aproba": { @@ -9037,7 +9033,7 @@ "bundled": true, "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "getpass": { @@ -9045,7 +9041,7 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { @@ -9053,12 +9049,12 @@ "bundled": true, "dev": true, "requires": { - "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" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.4", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "global-dirs": { @@ -9066,7 +9062,7 @@ "bundled": true, "dev": true, "requires": { - "ini": "^1.3.4" + "ini": "1.3.5" } }, "got": { @@ -9074,17 +9070,17 @@ "bundled": true, "dev": true, "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.2.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.1", + "safe-buffer": "5.1.2", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" }, "dependencies": { "get-stream": { @@ -9109,8 +9105,8 @@ "bundled": true, "dev": true, "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has": { @@ -9118,7 +9114,7 @@ "bundled": true, "dev": true, "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-flag": { @@ -9151,7 +9147,7 @@ "bundled": true, "dev": true, "requires": { - "agent-base": "4", + "agent-base": "4.3.0", "debug": "3.1.0" } }, @@ -9160,9 +9156,9 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" } }, "https-proxy-agent": { @@ -9170,8 +9166,8 @@ "bundled": true, "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "4.3.0", + "debug": "3.1.0" } }, "humanize-ms": { @@ -9179,7 +9175,7 @@ "bundled": true, "dev": true, "requires": { - "ms": "^2.0.0" + "ms": "2.1.1" } }, "iconv-lite": { @@ -9187,7 +9183,7 @@ "bundled": true, "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "iferr": { @@ -9200,7 +9196,7 @@ "bundled": true, "dev": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "3.0.4" } }, "import-lazy": { @@ -9223,8 +9219,8 @@ "bundled": true, "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -9242,14 +9238,14 @@ "bundled": true, "dev": true, "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" + "glob": "7.1.6", + "npm-package-arg": "6.1.1", + "promzard": "0.3.0", + "read": "1.0.7", + "read-package-json": "2.1.1", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4", + "validate-npm-package-name": "3.0.0" } }, "invert-kv": { @@ -9277,7 +9273,7 @@ "bundled": true, "dev": true, "requires": { - "ci-info": "^1.5.0" + "ci-info": "1.6.0" }, "dependencies": { "ci-info": { @@ -9292,7 +9288,7 @@ "bundled": true, "dev": true, "requires": { - "cidr-regex": "^2.0.10" + "cidr-regex": "2.0.10" } }, "is-date-object": { @@ -9305,7 +9301,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-installed-globally": { @@ -9313,8 +9309,8 @@ "bundled": true, "dev": true, "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" + "global-dirs": "0.1.1", + "is-path-inside": "1.0.1" } }, "is-npm": { @@ -9332,7 +9328,7 @@ "bundled": true, "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-redirect": { @@ -9345,7 +9341,7 @@ "bundled": true, "dev": true, "requires": { - "has": "^1.0.1" + "has": "1.0.3" } }, "is-retry-allowed": { @@ -9363,7 +9359,7 @@ "bundled": true, "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "1.0.0" } }, "is-typedarray": { @@ -9417,6 +9413,15 @@ "bundled": true, "dev": true }, + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "jsprim": { "version": "1.4.1", "bundled": true, @@ -9433,7 +9438,7 @@ "bundled": true, "dev": true, "requires": { - "package-json": "^4.0.0" + "package-json": "4.0.1" } }, "lazy-property": { @@ -9446,7 +9451,7 @@ "bundled": true, "dev": true, "requires": { - "invert-kv": "^2.0.0" + "invert-kv": "2.0.0" } }, "libcipm": { @@ -9454,21 +9459,21 @@ "bundled": true, "dev": true, "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "ini": "^1.3.5", - "lock-verify": "^2.0.2", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^9.1.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" + "bin-links": "1.1.7", + "bluebird": "3.5.5", + "figgy-pudding": "3.5.1", + "find-npm-prefix": "1.0.2", + "graceful-fs": "4.2.3", + "ini": "1.3.5", + "lock-verify": "2.1.0", + "mkdirp": "0.5.4", + "npm-lifecycle": "3.1.4", + "npm-logical-tree": "1.2.1", + "npm-package-arg": "6.1.1", + "pacote": "9.5.12", + "read-package-json": "2.1.1", + "rimraf": "2.7.1", + "worker-farm": "1.7.0" } }, "libnpm": { @@ -9476,26 +9481,26 @@ "bundled": true, "dev": true, "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.3", - "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.2", - "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmpublish": "^1.1.2", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "lock-verify": "^2.0.2", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", - "npmlog": "^4.1.2", - "pacote": "^9.5.3", - "read-package-json": "^2.0.13", - "stringify-package": "^1.0.0" + "bin-links": "1.1.7", + "bluebird": "3.5.5", + "find-npm-prefix": "1.0.2", + "libnpmaccess": "3.0.2", + "libnpmconfig": "1.2.1", + "libnpmhook": "5.0.3", + "libnpmorg": "1.0.1", + "libnpmpublish": "1.1.2", + "libnpmsearch": "2.0.2", + "libnpmteam": "1.0.2", + "lock-verify": "2.1.0", + "npm-lifecycle": "3.1.4", + "npm-logical-tree": "1.2.1", + "npm-package-arg": "6.1.1", + "npm-profile": "4.0.4", + "npm-registry-fetch": "4.0.3", + "npmlog": "4.1.2", + "pacote": "9.5.12", + "read-package-json": "2.1.1", + "stringify-package": "1.0.1" } }, "libnpmaccess": { @@ -9503,10 +9508,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0" + "aproba": "2.0.0", + "get-stream": "4.1.0", + "npm-package-arg": "6.1.1", + "npm-registry-fetch": "4.0.3" } }, "libnpmconfig": { @@ -9514,9 +9519,9 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "find-up": "^3.0.0", - "ini": "^1.3.5" + "figgy-pudding": "3.5.1", + "find-up": "3.0.0", + "ini": "1.3.5" }, "dependencies": { "find-up": { @@ -9524,7 +9529,7 @@ "bundled": true, "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "locate-path": { @@ -9532,8 +9537,8 @@ "bundled": true, "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": { @@ -9541,7 +9546,7 @@ "bundled": true, "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -9549,7 +9554,7 @@ "bundled": true, "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.0" } }, "p-try": { @@ -9564,10 +9569,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" + "aproba": "2.0.0", + "figgy-pudding": "3.5.1", + "get-stream": "4.1.0", + "npm-registry-fetch": "4.0.3" } }, "libnpmorg": { @@ -9575,10 +9580,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" + "aproba": "2.0.0", + "figgy-pudding": "3.5.1", + "get-stream": "4.1.0", + "npm-registry-fetch": "4.0.3" } }, "libnpmpublish": { @@ -9586,15 +9591,15 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" + "aproba": "2.0.0", + "figgy-pudding": "3.5.1", + "get-stream": "4.1.0", + "lodash.clonedeep": "4.5.0", + "normalize-package-data": "2.5.0", + "npm-package-arg": "6.1.1", + "npm-registry-fetch": "4.0.3", + "semver": "5.7.1", + "ssri": "6.0.1" } }, "libnpmsearch": { @@ -9602,9 +9607,9 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" + "figgy-pudding": "3.5.1", + "get-stream": "4.1.0", + "npm-registry-fetch": "4.0.3" } }, "libnpmteam": { @@ -9612,10 +9617,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" + "aproba": "2.0.0", + "figgy-pudding": "3.5.1", + "get-stream": "4.1.0", + "npm-registry-fetch": "4.0.3" } }, "libnpx": { @@ -9623,14 +9628,14 @@ "bundled": true, "dev": true, "requires": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^11.0.0" + "dotenv": "5.0.1", + "npm-package-arg": "6.1.1", + "rimraf": "2.7.1", + "safe-buffer": "5.1.2", + "update-notifier": "2.5.0", + "which": "1.3.1", + "y18n": "4.0.0", + "yargs": "11.1.1" } }, "locate-path": { @@ -9638,8 +9643,8 @@ "bundled": true, "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" } }, "lock-verify": { @@ -9647,8 +9652,8 @@ "bundled": true, "dev": true, "requires": { - "npm-package-arg": "^6.1.0", - "semver": "^5.4.1" + "npm-package-arg": "6.1.1", + "semver": "5.7.1" } }, "lockfile": { @@ -9656,7 +9661,7 @@ "bundled": true, "dev": true, "requires": { - "signal-exit": "^3.0.2" + "signal-exit": "3.0.2" } }, "lodash._baseindexof": { @@ -9669,8 +9674,8 @@ "bundled": true, "dev": true, "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" + "lodash._createset": "4.0.3", + "lodash._root": "3.0.1" } }, "lodash._bindcallback": { @@ -9688,7 +9693,7 @@ "bundled": true, "dev": true, "requires": { - "lodash._getnative": "^3.0.0" + "lodash._getnative": "3.9.1" } }, "lodash._createset": { @@ -9741,7 +9746,7 @@ "bundled": true, "dev": true, "requires": { - "yallist": "^3.0.2" + "yallist": "3.0.3" } }, "make-dir": { @@ -9749,7 +9754,7 @@ "bundled": true, "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" } }, "make-fetch-happen": { @@ -9757,17 +9762,17 @@ "bundled": true, "dev": true, "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" + "agentkeepalive": "3.5.2", + "cacache": "12.0.3", + "http-cache-semantics": "3.8.1", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.4", + "lru-cache": "5.1.1", + "mississippi": "3.0.0", + "node-fetch-npm": "2.0.2", + "promise-retry": "1.1.1", + "socks-proxy-agent": "4.0.2", + "ssri": "6.0.1" } }, "map-age-cleaner": { @@ -9775,7 +9780,7 @@ "bundled": true, "dev": true, "requires": { - "p-defer": "^1.0.0" + "p-defer": "1.0.0" } }, "meant": { @@ -9788,9 +9793,9 @@ "bundled": true, "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" + "map-age-cleaner": "0.1.3", + "mimic-fn": "2.1.0", + "p-is-promise": "2.1.0" }, "dependencies": { "mimic-fn": { @@ -9810,7 +9815,7 @@ "bundled": true, "dev": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "1.35.0" } }, "minimatch": { @@ -9818,7 +9823,7 @@ "bundled": true, "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minizlib": { @@ -9826,7 +9831,7 @@ "bundled": true, "dev": true, "requires": { - "minipass": "^2.9.0" + "minipass": "2.9.0" }, "dependencies": { "minipass": { @@ -9834,8 +9839,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "safe-buffer": "5.1.2", + "yallist": "3.0.3" } } } @@ -9845,16 +9850,16 @@ "bundled": true, "dev": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "concat-stream": "1.6.2", + "duplexify": "3.6.0", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.3", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "3.0.0", + "pumpify": "1.5.1", + "stream-each": "1.2.2", + "through2": "2.0.3" } }, "mkdirp": { @@ -9862,7 +9867,7 @@ "bundled": true, "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "1.2.5" }, "dependencies": { "minimist": { @@ -9877,12 +9882,12 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "aproba": "1.2.0", + "copy-concurrently": "1.0.5", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.4", + "rimraf": "2.7.1", + "run-queue": "1.0.3" }, "dependencies": { "aproba": { @@ -9912,9 +9917,9 @@ "bundled": true, "dev": true, "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" + "encoding": "0.1.12", + "json-parse-better-errors": "1.0.2", + "safe-buffer": "5.1.2" } }, "node-gyp": { @@ -9922,17 +9927,17 @@ "bundled": true, "dev": true, "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.1.2", - "request": "^2.88.0", - "rimraf": "^2.6.3", - "semver": "^5.7.1", - "tar": "^4.4.12", - "which": "^1.3.1" + "env-paths": "2.2.0", + "glob": "7.1.6", + "graceful-fs": "4.2.3", + "mkdirp": "0.5.4", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "request": "2.88.0", + "rimraf": "2.7.1", + "semver": "5.7.1", + "tar": "4.4.13", + "which": "1.3.1" } }, "nopt": { @@ -9940,8 +9945,8 @@ "bundled": true, "dev": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "normalize-package-data": { @@ -9949,10 +9954,10 @@ "bundled": true, "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.8.8", + "resolve": "1.10.0", + "semver": "5.7.1", + "validate-npm-package-license": "3.0.4" }, "dependencies": { "resolve": { @@ -9960,7 +9965,7 @@ "bundled": true, "dev": true, "requires": { - "path-parse": "^1.0.6" + "path-parse": "1.0.6" } } } @@ -9970,8 +9975,8 @@ "bundled": true, "dev": true, "requires": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" + "cli-table3": "0.5.1", + "console-control-strings": "1.1.0" } }, "npm-bundled": { @@ -9979,7 +9984,7 @@ "bundled": true, "dev": true, "requires": { - "npm-normalize-package-bin": "^1.0.1" + "npm-normalize-package-bin": "1.0.1" } }, "npm-cache-filename": { @@ -9992,7 +9997,7 @@ "bundled": true, "dev": true, "requires": { - "semver": "^2.3.0 || 3.x || 4 || 5" + "semver": "5.7.1" } }, "npm-lifecycle": { @@ -10000,14 +10005,14 @@ "bundled": true, "dev": true, "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.15", - "node-gyp": "^5.0.2", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", + "byline": "5.0.0", + "graceful-fs": "4.2.3", + "node-gyp": "5.1.0", + "resolve-from": "4.0.0", + "slide": "1.1.6", "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" + "umask": "1.1.0", + "which": "1.3.1" } }, "npm-logical-tree": { @@ -10025,10 +10030,10 @@ "bundled": true, "dev": true, "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" + "hosted-git-info": "2.8.8", + "osenv": "0.1.5", + "semver": "5.7.1", + "validate-npm-package-name": "3.0.0" } }, "npm-packlist": { @@ -10036,9 +10041,9 @@ "bundled": true, "dev": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" + "ignore-walk": "3.0.3", + "npm-bundled": "1.1.1", + "npm-normalize-package-bin": "1.0.1" } }, "npm-pick-manifest": { @@ -10046,9 +10051,9 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" + "figgy-pudding": "3.5.1", + "npm-package-arg": "6.1.1", + "semver": "5.7.1" } }, "npm-profile": { @@ -10056,9 +10061,9 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^1.1.2 || 2", - "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^4.0.0" + "aproba": "2.0.0", + "figgy-pudding": "3.5.1", + "npm-registry-fetch": "4.0.3" } }, "npm-registry-fetch": { @@ -10066,13 +10071,13 @@ "bundled": true, "dev": true, "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" + "bluebird": "3.5.5", + "figgy-pudding": "3.5.1", + "JSONStream": "1.3.5", + "lru-cache": "5.1.1", + "make-fetch-happen": "5.0.2", + "npm-package-arg": "6.1.1", + "safe-buffer": "5.2.0" }, "dependencies": { "safe-buffer": { @@ -10087,7 +10092,7 @@ "bundled": true, "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "npm-user-validate": { @@ -10100,10 +10105,10 @@ "bundled": true, "dev": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -10131,8 +10136,8 @@ "bundled": true, "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "define-properties": "1.1.3", + "es-abstract": "1.12.0" } }, "once": { @@ -10140,7 +10145,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "opener": { @@ -10158,9 +10163,9 @@ "bundled": true, "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "execa": "1.0.0", + "lcid": "2.0.0", + "mem": "4.3.0" }, "dependencies": { "cross-spawn": { @@ -10168,11 +10173,11 @@ "bundled": true, "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.7.1", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, "execa": { @@ -10180,13 +10185,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "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" } } } @@ -10201,8 +10206,8 @@ "bundled": true, "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "p-defer": { @@ -10225,7 +10230,7 @@ "bundled": true, "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -10233,7 +10238,7 @@ "bundled": true, "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.2.0" } }, "p-try": { @@ -10246,10 +10251,10 @@ "bundled": true, "dev": true, "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" + "got": "6.7.1", + "registry-auth-token": "3.4.0", + "registry-url": "3.1.0", + "semver": "5.7.1" } }, "pacote": { @@ -10257,36 +10262,36 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-normalize-package-bin": "^1.0.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^3.0.0", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.10", - "unique-filename": "^1.1.1", - "which": "^1.3.1" + "bluebird": "3.5.5", + "cacache": "12.0.3", + "chownr": "1.1.4", + "figgy-pudding": "3.5.1", + "get-stream": "4.1.0", + "glob": "7.1.6", + "infer-owner": "1.0.4", + "lru-cache": "5.1.1", + "make-fetch-happen": "5.0.2", + "minimatch": "3.0.4", + "minipass": "2.9.0", + "mississippi": "3.0.0", + "mkdirp": "0.5.4", + "normalize-package-data": "2.5.0", + "npm-normalize-package-bin": "1.0.1", + "npm-package-arg": "6.1.1", + "npm-packlist": "1.4.8", + "npm-pick-manifest": "3.0.2", + "npm-registry-fetch": "4.0.3", + "osenv": "0.1.5", + "promise-inflight": "1.0.1", + "promise-retry": "1.1.1", + "protoduck": "5.0.1", + "rimraf": "2.7.1", + "safe-buffer": "5.1.2", + "semver": "5.7.1", + "ssri": "6.0.1", + "tar": "4.4.13", + "unique-filename": "1.1.1", + "which": "1.3.1" }, "dependencies": { "minipass": { @@ -10294,8 +10299,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "safe-buffer": "5.1.2", + "yallist": "3.0.3" } } } @@ -10305,9 +10310,9 @@ "bundled": true, "dev": true, "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "cyclist": "0.2.2", + "inherits": "2.0.4", + "readable-stream": "2.3.6" }, "dependencies": { "readable-stream": { @@ -10315,13 +10320,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -10329,7 +10334,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -10389,8 +10394,8 @@ "bundled": true, "dev": true, "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" + "err-code": "1.1.2", + "retry": "0.10.1" }, "dependencies": { "retry": { @@ -10405,7 +10410,7 @@ "bundled": true, "dev": true, "requires": { - "read": "1" + "read": "1.0.7" } }, "proto-list": { @@ -10418,7 +10423,7 @@ "bundled": true, "dev": true, "requires": { - "genfun": "^5.0.0" + "genfun": "5.0.0" } }, "prr": { @@ -10441,8 +10446,8 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } }, "pumpify": { @@ -10450,9 +10455,9 @@ "bundled": true, "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.6.0", + "inherits": "2.0.4", + "pump": "2.0.1" }, "dependencies": { "pump": { @@ -10460,8 +10465,8 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" } } } @@ -10486,9 +10491,9 @@ "bundled": true, "dev": true, "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" + "decode-uri-component": "0.2.0", + "split-on-first": "1.1.0", + "strict-uri-encode": "2.0.0" } }, "qw": { @@ -10501,10 +10506,10 @@ "bundled": true, "dev": true, "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.5", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -10519,7 +10524,7 @@ "bundled": true, "dev": true, "requires": { - "mute-stream": "~0.0.4" + "mute-stream": "0.0.7" } }, "read-cmd-shim": { @@ -10527,7 +10532,7 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2" + "graceful-fs": "4.2.3" } }, "read-installed": { @@ -10535,13 +10540,13 @@ "bundled": true, "dev": true, "requires": { - "debuglog": "^1.0.1", - "graceful-fs": "^4.1.2", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" + "debuglog": "1.0.1", + "graceful-fs": "4.2.3", + "read-package-json": "2.1.1", + "readdir-scoped-modules": "1.1.0", + "semver": "5.7.1", + "slide": "1.1.6", + "util-extend": "1.0.3" } }, "read-package-json": { @@ -10549,11 +10554,11 @@ "bundled": true, "dev": true, "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" + "glob": "7.1.6", + "graceful-fs": "4.2.3", + "json-parse-better-errors": "1.0.2", + "normalize-package-data": "2.5.0", + "npm-normalize-package-bin": "1.0.1" } }, "read-package-tree": { @@ -10561,9 +10566,9 @@ "bundled": true, "dev": true, "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" + "read-package-json": "2.1.1", + "readdir-scoped-modules": "1.1.0", + "util-promisify": "2.1.0" } }, "readable-stream": { @@ -10571,9 +10576,9 @@ "bundled": true, "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "inherits": "2.0.4", + "string_decoder": "1.3.0", + "util-deprecate": "1.0.2" } }, "readdir-scoped-modules": { @@ -10581,10 +10586,10 @@ "bundled": true, "dev": true, "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "graceful-fs": "4.2.3", + "once": "1.4.0" } }, "registry-auth-token": { @@ -10592,8 +10597,8 @@ "bundled": true, "dev": true, "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" + "rc": "1.2.8", + "safe-buffer": "5.1.2" } }, "registry-url": { @@ -10601,7 +10606,7 @@ "bundled": true, "dev": true, "requires": { - "rc": "^1.0.1" + "rc": "1.2.8" } }, "request": { @@ -10609,26 +10614,26 @@ "bundled": true, "dev": true, "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.0", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.3" } }, "require-directory": { @@ -10656,7 +10661,7 @@ "bundled": true, "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "run-queue": { @@ -10664,7 +10669,7 @@ "bundled": true, "dev": true, "requires": { - "aproba": "^1.1.1" + "aproba": "1.2.0" }, "dependencies": { "aproba": { @@ -10694,7 +10699,7 @@ "bundled": true, "dev": true, "requires": { - "semver": "^5.0.3" + "semver": "5.7.1" } }, "set-blocking": { @@ -10707,7 +10712,7 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2" + "graceful-fs": "4.2.3" } }, "shebang-command": { @@ -10715,7 +10720,7 @@ "bundled": true, "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -10744,7 +10749,7 @@ "dev": true, "requires": { "ip": "1.1.5", - "smart-buffer": "^4.1.0" + "smart-buffer": "4.1.0" } }, "socks-proxy-agent": { @@ -10752,8 +10757,8 @@ "bundled": true, "dev": true, "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" + "agent-base": "4.2.1", + "socks": "2.3.3" }, "dependencies": { "agent-base": { @@ -10761,7 +10766,7 @@ "bundled": true, "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "es6-promisify": "5.0.0" } } } @@ -10776,8 +10781,8 @@ "bundled": true, "dev": true, "requires": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" + "from2": "1.3.0", + "stream-iterate": "1.2.0" }, "dependencies": { "from2": { @@ -10785,8 +10790,8 @@ "bundled": true, "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" + "inherits": "2.0.4", + "readable-stream": "1.1.14" } }, "isarray": { @@ -10799,10 +10804,10 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.4", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -10817,8 +10822,8 @@ "bundled": true, "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.3" } }, "spdx-exceptions": { @@ -10831,8 +10836,8 @@ "bundled": true, "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.3" } }, "spdx-license-ids": { @@ -10850,15 +10855,15 @@ "bundled": true, "dev": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "ssri": { @@ -10866,7 +10871,7 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "^3.5.1" + "figgy-pudding": "3.5.1" } }, "stream-each": { @@ -10874,8 +10879,8 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "stream-shift": "1.0.0" } }, "stream-iterate": { @@ -10883,8 +10888,8 @@ "bundled": true, "dev": true, "requires": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" + "readable-stream": "2.3.6", + "stream-shift": "1.0.0" }, "dependencies": { "readable-stream": { @@ -10892,13 +10897,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -10906,7 +10911,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -10921,13 +10926,28 @@ "bundled": true, "dev": true }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, "string-width": { "version": "2.1.1", "bundled": true, "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" }, "dependencies": { "ansi-regex": { @@ -10945,26 +10965,11 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } }, - "string_decoder": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "bundled": true, - "dev": true - } - } - }, "stringify-package": { "version": "1.0.1", "bundled": true, @@ -10975,7 +10980,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-eof": { @@ -10993,7 +10998,7 @@ "bundled": true, "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "tar": { @@ -11001,13 +11006,13 @@ "bundled": true, "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "chownr": "1.1.4", + "fs-minipass": "1.2.7", + "minipass": "2.9.0", + "minizlib": "1.3.3", + "mkdirp": "0.5.4", + "safe-buffer": "5.1.2", + "yallist": "3.0.3" }, "dependencies": { "minipass": { @@ -11015,8 +11020,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "safe-buffer": "5.1.2", + "yallist": "3.0.3" } } } @@ -11026,7 +11031,7 @@ "bundled": true, "dev": true, "requires": { - "execa": "^0.7.0" + "execa": "0.7.0" } }, "text-table": { @@ -11044,8 +11049,8 @@ "bundled": true, "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" }, "dependencies": { "readable-stream": { @@ -11053,13 +11058,13 @@ "bundled": true, "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "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" } }, "string_decoder": { @@ -11067,7 +11072,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -11087,8 +11092,8 @@ "bundled": true, "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "1.1.29", + "punycode": "1.4.1" } }, "tunnel-agent": { @@ -11096,7 +11101,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -11125,7 +11130,7 @@ "bundled": true, "dev": true, "requires": { - "unique-slug": "^2.0.0" + "unique-slug": "2.0.0" } }, "unique-slug": { @@ -11133,7 +11138,7 @@ "bundled": true, "dev": true, "requires": { - "imurmurhash": "^0.1.4" + "imurmurhash": "0.1.4" } }, "unique-string": { @@ -11141,7 +11146,7 @@ "bundled": true, "dev": true, "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "1.0.0" } }, "unpipe": { @@ -11159,16 +11164,16 @@ "bundled": true, "dev": true, "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" + "boxen": "1.3.0", + "chalk": "2.4.1", + "configstore": "3.1.2", + "import-lazy": "2.1.0", + "is-ci": "1.2.1", + "is-installed-globally": "0.1.0", + "is-npm": "1.0.0", + "latest-version": "3.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" } }, "url-parse-lax": { @@ -11176,7 +11181,7 @@ "bundled": true, "dev": true, "requires": { - "prepend-http": "^1.0.1" + "prepend-http": "1.0.4" } }, "util-deprecate": { @@ -11194,7 +11199,7 @@ "bundled": true, "dev": true, "requires": { - "object.getownpropertydescriptors": "^2.0.3" + "object.getownpropertydescriptors": "2.0.3" } }, "uuid": { @@ -11207,8 +11212,8 @@ "bundled": true, "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "validate-npm-package-name": { @@ -11216,7 +11221,7 @@ "bundled": true, "dev": true, "requires": { - "builtins": "^1.0.3" + "builtins": "1.0.3" } }, "verror": { @@ -11224,9 +11229,9 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "wcwidth": { @@ -11234,7 +11239,7 @@ "bundled": true, "dev": true, "requires": { - "defaults": "^1.0.3" + "defaults": "1.0.3" } }, "which": { @@ -11242,7 +11247,7 @@ "bundled": true, "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -11255,7 +11260,7 @@ "bundled": true, "dev": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" }, "dependencies": { "string-width": { @@ -11263,9 +11268,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -11275,7 +11280,7 @@ "bundled": true, "dev": true, "requires": { - "string-width": "^2.1.1" + "string-width": "2.1.1" } }, "worker-farm": { @@ -11283,7 +11288,7 @@ "bundled": true, "dev": true, "requires": { - "errno": "~0.1.7" + "errno": "0.1.7" } }, "wrap-ansi": { @@ -11291,8 +11296,8 @@ "bundled": true, "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "string-width": { @@ -11300,9 +11305,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } } } @@ -11317,9 +11322,9 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "graceful-fs": "4.2.3", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" } }, "xdg-basedir": { @@ -11347,18 +11352,18 @@ "bundled": true, "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.3", + "os-locale": "3.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" }, "dependencies": { "y18n": { @@ -11373,7 +11378,7 @@ "bundled": true, "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } @@ -11384,7 +11389,7 @@ "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "dev": true, "requires": { - "npm-normalize-package-bin": "^1.0.1" + "npm-normalize-package-bin": "1.0.1" } }, "npm-normalize-package-bin": { @@ -11399,9 +11404,9 @@ "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" + "ignore-walk": "3.0.3", + "npm-bundled": "1.1.1", + "npm-normalize-package-bin": "1.0.1" } }, "npm-run-path": { @@ -11410,7 +11415,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "npmlog": { @@ -11419,10 +11424,10 @@ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "nth-check": { @@ -11431,7 +11436,7 @@ "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "dev": true, "requires": { - "boolbase": "~1.0.0" + "boolbase": "1.0.0" } }, "number-is-nan": { @@ -11453,32 +11458,32 @@ "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==", "dev": true, "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" + "@istanbuljs/load-nyc-config": "1.0.0", + "@istanbuljs/schema": "0.1.2", + "caching-transform": "4.0.0", + "convert-source-map": "1.7.0", + "decamelize": "1.2.0", + "find-cache-dir": "3.3.1", + "find-up": "4.1.0", + "foreground-child": "2.0.0", + "glob": "7.1.6", + "istanbul-lib-coverage": "3.0.0", + "istanbul-lib-hook": "3.0.0", + "istanbul-lib-instrument": "4.0.1", + "istanbul-lib-processinfo": "2.0.2", + "istanbul-lib-report": "3.0.0", + "istanbul-lib-source-maps": "4.0.0", + "istanbul-reports": "3.0.2", + "make-dir": "3.0.2", + "node-preload": "0.2.1", + "p-map": "3.0.0", + "process-on-spawn": "1.0.0", + "resolve-from": "5.0.0", + "rimraf": "3.0.2", + "signal-exit": "3.0.3", + "spawn-wrap": "2.0.0", + "test-exclude": "6.0.0", + "yargs": "15.3.1" }, "dependencies": { "ansi-regex": { @@ -11493,8 +11498,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "@types/color-name": "1.1.1", + "color-convert": "2.0.1" } }, "camelcase": { @@ -11509,9 +11514,9 @@ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "string-width": "4.2.0", + "strip-ansi": "6.0.0", + "wrap-ansi": "6.2.0" } }, "color-convert": { @@ -11520,7 +11525,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -11535,8 +11540,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "get-caller-file": { @@ -11557,7 +11562,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "p-limit": { @@ -11566,7 +11571,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -11575,7 +11580,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-map": { @@ -11584,7 +11589,7 @@ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "aggregate-error": "^3.0.0" + "aggregate-error": "3.0.1" } }, "p-try": { @@ -11611,7 +11616,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "string-width": { @@ -11620,9 +11625,9 @@ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" } }, "strip-ansi": { @@ -11631,7 +11636,7 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "5.0.0" } }, "wrap-ansi": { @@ -11640,9 +11645,9 @@ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "4.2.1", + "string-width": "4.2.0", + "strip-ansi": "6.0.0" } }, "yargs": { @@ -11651,17 +11656,17 @@ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "cliui": "6.0.0", + "decamelize": "1.2.0", + "find-up": "4.1.0", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "4.2.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "18.1.2" } }, "yargs-parser": { @@ -11670,8 +11675,8 @@ "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } @@ -11694,9 +11699,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -11705,7 +11710,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "kind-of": { @@ -11714,7 +11719,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -11737,7 +11742,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" } }, "object.assign": { @@ -11746,10 +11751,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.1", + "object-keys": "1.1.1" } }, "object.entries-ponyfill": { @@ -11764,8 +11769,8 @@ "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "define-properties": "1.1.3", + "es-abstract": "1.17.5" } }, "object.pick": { @@ -11774,7 +11779,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "octokit-pagination-methods": { @@ -11789,7 +11794,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "onetime": { @@ -11798,7 +11803,7 @@ "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "mimic-fn": "2.1.0" } }, "opencollective-postinstall": { @@ -11813,12 +11818,12 @@ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "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", - "word-wrap": "~1.2.3" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "word-wrap": "1.2.3" } }, "ordered-read-streams": { @@ -11827,7 +11832,7 @@ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "readable-stream": "2.3.7" } }, "os-homedir": { @@ -11842,9 +11847,9 @@ "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "execa": "1.0.0", + "lcid": "2.0.0", + "mem": "4.3.0" } }, "os-name": { @@ -11853,8 +11858,8 @@ "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", "dev": true, "requires": { - "macos-release": "^2.2.0", - "windows-release": "^3.1.0" + "macos-release": "2.3.0", + "windows-release": "3.2.0" } }, "os-tmpdir": { @@ -11869,8 +11874,8 @@ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "p-defer": { @@ -11891,7 +11896,7 @@ "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", "dev": true, "requires": { - "p-map": "^2.0.0" + "p-map": "2.1.0" }, "dependencies": { "p-map": { @@ -11920,7 +11925,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -11929,7 +11934,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.3.0" } }, "p-map": { @@ -11938,6 +11943,26 @@ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true }, + "p-props": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-props/-/p-props-3.1.0.tgz", + "integrity": "sha512-N9mPfJfnApIlyCmF6H2KKccOsW9a1um2aIZWh/dU6flpJ5uU3fYDPVYfjOPLTbLVnUn+lcTqtlxxCVylM3mYsw==", + "dev": true, + "requires": { + "p-map": "3.0.0" + }, + "dependencies": { + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "3.0.1" + } + } + } + }, "p-reduce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", @@ -11950,8 +11975,17 @@ "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", "dev": true, "requires": { - "@types/retry": "^0.12.0", - "retry": "^0.12.0" + "@types/retry": "0.12.0", + "retry": "0.12.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "1.0.0" } }, "p-try": { @@ -11966,10 +12000,10 @@ "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" + "graceful-fs": "4.2.3", + "hasha": "5.2.0", + "lodash.flattendeep": "4.4.0", + "release-zalgo": "1.0.0" } }, "packet-reader": { @@ -11984,7 +12018,7 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { - "callsites": "^3.0.0" + "callsites": "3.1.0" } }, "parse-json": { @@ -11993,8 +12027,8 @@ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2" } }, "parse5": { @@ -12003,7 +12037,7 @@ "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "12.12.34" } }, "parsimmon": { @@ -12071,7 +12105,7 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" } }, "pathval": { @@ -12095,10 +12129,10 @@ "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-packet-stream": "^1.1.0", - "pg-pool": "^2.0.10", - "pg-types": "^2.1.0", - "pgpass": "1.x", + "pg-packet-stream": "1.1.0", + "pg-pool": "2.0.10", + "pg-types": "2.2.0", + "pgpass": "1.0.2", "semver": "4.3.2" }, "dependencies": { @@ -12122,7 +12156,7 @@ "integrity": "sha512-qpeTpdkguFgfdoidtfeTho1Q1zPVPbtMHgs8eQ+Aan05iLmIs3Z3oo5DOZRclPGoQ4i68I1kCtQSJSa7i0ZVYg==", "dev": true, "requires": { - "underscore": "^1.7.0" + "underscore": "1.10.2" } }, "pg-int8": { @@ -12150,10 +12184,10 @@ "dev": true, "requires": { "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" + "postgres-array": "2.0.0", + "postgres-bytea": "1.0.0", + "postgres-date": "1.0.4", + "postgres-interval": "1.2.0" } }, "pgpass": { @@ -12162,7 +12196,7 @@ "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", "dev": true, "requires": { - "split": "^1.0.0" + "split": "1.0.1" } }, "picomatch": { @@ -12183,8 +12217,8 @@ "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", "dev": true, "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" + "find-up": "2.1.0", + "load-json-file": "4.0.0" } }, "pkg-dir": { @@ -12193,7 +12227,7 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "^4.0.0" + "find-up": "4.1.0" }, "dependencies": { "find-up": { @@ -12202,8 +12236,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "locate-path": { @@ -12212,7 +12246,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "p-limit": { @@ -12221,7 +12255,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -12230,7 +12264,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -12253,7 +12287,7 @@ "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", "dev": true, "requires": { - "semver-compare": "^1.0.0" + "semver-compare": "1.0.0" } }, "posix-character-classes": { @@ -12286,7 +12320,7 @@ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "dev": true, "requires": { - "xtend": "^4.0.0" + "xtend": "4.0.2" } }, "prelude-ls": { @@ -12307,7 +12341,7 @@ "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { - "fromentries": "^1.2.0" + "fromentries": "1.2.0" } }, "progress": { @@ -12334,8 +12368,8 @@ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.4", + "once": "1.4.0" } }, "pumpify": { @@ -12344,9 +12378,9 @@ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.7.1", + "inherits": "2.0.4", + "pump": "2.0.1" }, "dependencies": { "pump": { @@ -12355,8 +12389,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.4", + "once": "1.4.0" } } } @@ -12397,10 +12431,10 @@ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.5", + "strip-json-comments": "2.0.1" }, "dependencies": { "deep-extend": { @@ -12417,9 +12451,9 @@ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "load-json-file": "4.0.0", + "normalize-package-data": "2.5.0", + "path-type": "3.0.0" } }, "read-pkg-up": { @@ -12428,8 +12462,8 @@ "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "find-up": "2.1.0", + "read-pkg": "3.0.0" } }, "readable-stream": { @@ -12438,13 +12472,13 @@ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "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" + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "redent": { @@ -12453,8 +12487,8 @@ "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", "dev": true, "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "indent-string": "3.2.0", + "strip-indent": "2.0.0" } }, "redeyed": { @@ -12463,7 +12497,7 @@ "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", "dev": true, "requires": { - "esprima": "~4.0.0" + "esprima": "4.0.1" } }, "regenerator-runtime": { @@ -12478,8 +12512,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "regexpp": { @@ -12500,7 +12534,7 @@ "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", "dev": true, "requires": { - "rc": "^1.2.8" + "rc": "1.2.8" } }, "release-zalgo": { @@ -12509,7 +12543,7 @@ "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { - "es6-error": "^4.0.1" + "es6-error": "4.1.1" } }, "remove-bom-buffer": { @@ -12518,8 +12552,8 @@ "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" } }, "remove-bom-stream": { @@ -12528,9 +12562,9 @@ "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.2", + "through2": "2.0.5" }, "dependencies": { "through2": { @@ -12539,8 +12573,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -12569,7 +12603,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.1.0" } }, "replace-ext": { @@ -12584,26 +12618,26 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "aws-sign2": "0.7.0", + "aws4": "1.9.1", + "caseless": "0.12.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.26", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.5.0", + "tunnel-agent": "0.6.0", + "uuid": "3.4.0" } }, "require-directory": { @@ -12624,7 +12658,7 @@ "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, "requires": { - "path-parse": "^1.0.6" + "path-parse": "1.0.6" } }, "resolve-from": { @@ -12639,7 +12673,7 @@ "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", "dev": true, "requires": { - "global-dirs": "^0.1.1" + "global-dirs": "0.1.1" } }, "resolve-options": { @@ -12648,7 +12682,7 @@ "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "dev": true, "requires": { - "value-or-function": "^3.0.0" + "value-or-function": "3.0.0" } }, "resolve-url": { @@ -12663,8 +12697,8 @@ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "5.1.0", + "signal-exit": "3.0.3" } }, "ret": { @@ -12684,7 +12718,7 @@ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", "requires": { - "any-promise": "^1.3.0" + "any-promise": "1.3.0" } }, "reusify": { @@ -12699,7 +12733,7 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "run-async": { @@ -12708,7 +12742,7 @@ "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", "dev": true, "requires": { - "is-promise": "^2.1.0" + "is-promise": "2.1.0" } }, "run-parallel": { @@ -12723,7 +12757,7 @@ "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", "dev": true, "requires": { - "tslib": "^1.9.0" + "tslib": "1.11.1" } }, "safe-buffer": { @@ -12738,7 +12772,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -12759,34 +12793,34 @@ "integrity": "sha512-qiYHTNStxUs0UUb45ImRIid0Z8HsXwMNbpZXLvABs725SrxtZBgfuemaABnHdKDg7KBsuQMlSdZENaYLvkMqUg==", "dev": true, "requires": { - "@semantic-release/commit-analyzer": "^7.0.0", - "@semantic-release/error": "^2.2.0", - "@semantic-release/github": "^6.0.0", - "@semantic-release/npm": "^6.0.0", - "@semantic-release/release-notes-generator": "^7.1.2", - "aggregate-error": "^3.0.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.0.0", - "env-ci": "^5.0.0", - "execa": "^4.0.0", - "figures": "^3.0.0", - "find-versions": "^3.0.0", - "get-stream": "^5.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^2.0.0", - "hosted-git-info": "^3.0.0", - "lodash": "^4.17.15", - "marked": "^0.8.0", - "marked-terminal": "^3.2.0", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "p-reduce": "^2.0.0", - "read-pkg-up": "^7.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.1.1", - "semver-diff": "^3.1.1", - "signale": "^1.2.1", - "yargs": "^15.0.1" + "@semantic-release/commit-analyzer": "7.0.0", + "@semantic-release/error": "2.2.0", + "@semantic-release/github": "6.0.2", + "@semantic-release/npm": "6.0.0", + "@semantic-release/release-notes-generator": "7.3.5", + "aggregate-error": "3.0.1", + "cosmiconfig": "6.0.0", + "debug": "4.1.1", + "env-ci": "5.0.2", + "execa": "4.0.0", + "figures": "3.2.0", + "find-versions": "3.2.0", + "get-stream": "5.1.0", + "git-log-parser": "1.2.0", + "hook-std": "2.0.0", + "hosted-git-info": "3.0.4", + "lodash": "4.17.15", + "marked": "0.8.2", + "marked-terminal": "3.3.0", + "micromatch": "4.0.2", + "p-each-series": "2.1.0", + "p-reduce": "2.1.0", + "read-pkg-up": "7.0.1", + "resolve-from": "5.0.0", + "semver": "7.1.3", + "semver-diff": "3.1.1", + "signale": "1.4.0", + "yargs": "15.3.1" }, "dependencies": { "ansi-regex": { @@ -12801,8 +12835,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "@types/color-name": "1.1.1", + "color-convert": "2.0.1" } }, "braces": { @@ -12811,7 +12845,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "7.0.1" } }, "camelcase": { @@ -12826,9 +12860,9 @@ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "string-width": "4.2.0", + "strip-ansi": "6.0.0", + "wrap-ansi": "6.2.0" } }, "color-convert": { @@ -12837,7 +12871,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "color-name": "1.1.4" } }, "color-name": { @@ -12852,11 +12886,11 @@ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" + "@types/parse-json": "4.0.0", + "import-fresh": "3.2.1", + "parse-json": "5.0.0", + "path-type": "4.0.0", + "yaml": "1.8.3" } }, "cross-spawn": { @@ -12865,9 +12899,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "path-key": "3.1.1", + "shebang-command": "2.0.0", + "which": "2.0.2" } }, "execa": { @@ -12876,15 +12910,15 @@ "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "cross-spawn": "7.0.1", + "get-stream": "5.1.0", + "human-signals": "1.1.1", + "is-stream": "2.0.0", + "merge-stream": "2.0.0", + "npm-run-path": "4.0.1", + "onetime": "5.1.0", + "signal-exit": "3.0.3", + "strip-final-newline": "2.0.0" } }, "fill-range": { @@ -12893,7 +12927,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "to-regex-range": "5.0.1" } }, "find-up": { @@ -12902,8 +12936,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "5.0.0", + "path-exists": "4.0.0" } }, "get-caller-file": { @@ -12918,7 +12952,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "^3.0.0" + "pump": "3.0.0" } }, "hosted-git-info": { @@ -12927,7 +12961,7 @@ "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", "dev": true, "requires": { - "lru-cache": "^5.1.1" + "lru-cache": "5.1.1" } }, "is-fullwidth-code-point": { @@ -12954,7 +12988,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "4.1.0" } }, "marked": { @@ -12969,8 +13003,8 @@ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "3.0.2", + "picomatch": "2.2.2" } }, "npm-run-path": { @@ -12979,7 +13013,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^3.0.0" + "path-key": "3.1.1" } }, "p-limit": { @@ -12988,7 +13022,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -12997,7 +13031,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -13012,10 +13046,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" + "@babel/code-frame": "7.8.3", + "error-ex": "1.3.2", + "json-parse-better-errors": "1.0.2", + "lines-and-columns": "1.1.6" } }, "path-exists": { @@ -13042,10 +13076,10 @@ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "@types/normalize-package-data": "2.4.0", + "normalize-package-data": "2.5.0", + "parse-json": "5.0.0", + "type-fest": "0.6.0" }, "dependencies": { "type-fest": { @@ -13062,9 +13096,9 @@ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "find-up": "4.1.0", + "read-pkg": "5.2.0", + "type-fest": "0.8.1" } }, "require-main-filename": { @@ -13079,7 +13113,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^3.0.0" + "shebang-regex": "3.0.0" } }, "shebang-regex": { @@ -13094,9 +13128,9 @@ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "8.0.0", + "is-fullwidth-code-point": "3.0.0", + "strip-ansi": "6.0.0" } }, "strip-ansi": { @@ -13105,7 +13139,7 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "5.0.0" } }, "to-regex-range": { @@ -13114,7 +13148,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^7.0.0" + "is-number": "7.0.0" } }, "which": { @@ -13123,7 +13157,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "wrap-ansi": { @@ -13132,9 +13166,9 @@ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "4.2.1", + "string-width": "4.2.0", + "strip-ansi": "6.0.0" } }, "yargs": { @@ -13143,17 +13177,17 @@ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "cliui": "6.0.0", + "decamelize": "1.2.0", + "find-up": "4.1.0", + "get-caller-file": "2.0.5", + "require-directory": "2.1.1", + "require-main-filename": "2.0.0", + "set-blocking": "2.0.0", + "string-width": "4.2.0", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "18.1.2" } }, "yargs-parser": { @@ -13162,8 +13196,8 @@ "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } @@ -13185,7 +13219,7 @@ "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", "dev": true, "requires": { - "semver": "^6.3.0" + "semver": "6.3.0" }, "dependencies": { "semver": { @@ -13225,10 +13259,10 @@ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -13237,7 +13271,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -13248,7 +13282,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -13275,9 +13309,9 @@ "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, "requires": { - "chalk": "^2.3.2", - "figures": "^2.0.0", - "pkg-conf": "^2.1.0" + "chalk": "2.4.2", + "figures": "2.0.0", + "pkg-conf": "2.1.0" }, "dependencies": { "figures": { @@ -13286,7 +13320,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "escape-string-regexp": "1.0.5" } } } @@ -13297,13 +13331,13 @@ "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", "dev": true, "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" + "@sinonjs/commons": "1.7.1", + "@sinonjs/formatio": "3.2.2", + "@sinonjs/samsam": "3.3.3", + "diff": "3.5.0", + "lolex": "4.2.0", + "nise": "1.5.3", + "supports-color": "5.5.0" } }, "sinon-chai": { @@ -13324,9 +13358,9 @@ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" + "ansi-styles": "3.2.1", + "astral-regex": "1.0.0", + "is-fullwidth-code-point": "2.0.0" } }, "snapdragon": { @@ -13335,14 +13369,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "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" + "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.3", + "use": "3.1.1" }, "dependencies": { "debug": { @@ -13360,7 +13394,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -13369,7 +13403,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "ms": { @@ -13386,9 +13420,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -13397,7 +13431,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -13406,7 +13440,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-data-descriptor": { @@ -13415,7 +13449,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.3" } }, "is-descriptor": { @@ -13424,9 +13458,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "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.3" } } } @@ -13437,7 +13471,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -13446,7 +13480,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -13463,11 +13497,11 @@ "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "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.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" } }, "source-map-url": { @@ -13488,12 +13522,12 @@ "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" + "foreground-child": "2.0.0", + "is-windows": "1.0.2", + "make-dir": "3.0.2", + "rimraf": "3.0.2", + "signal-exit": "3.0.3", + "which": "2.0.2" }, "dependencies": { "rimraf": { @@ -13502,7 +13536,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "which": { @@ -13511,7 +13545,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } } } @@ -13522,8 +13556,8 @@ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.5" } }, "spdx-exceptions": { @@ -13538,8 +13572,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.2.0", + "spdx-license-ids": "3.0.5" } }, "spdx-license-ids": { @@ -13554,7 +13588,7 @@ "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "requires": { - "through": "2" + "through": "2.3.8" } }, "split-string": { @@ -13563,7 +13597,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" } }, "split2": { @@ -13572,7 +13606,7 @@ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", "dev": true, "requires": { - "through2": "^2.0.2" + "through2": "2.0.5" }, "dependencies": { "through2": { @@ -13581,8 +13615,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -13599,9 +13633,9 @@ "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", "dev": true, "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0", - "request": "^2.87.0" + "nan": "2.14.0", + "node-pre-gyp": "0.11.0", + "request": "2.88.2" } }, "sqlstring": { @@ -13616,15 +13650,15 @@ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "stack-chain": { @@ -13639,8 +13673,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -13649,7 +13683,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -13660,8 +13694,8 @@ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" + "duplexer2": "0.1.4", + "readable-stream": "2.3.7" } }, "stream-shift": { @@ -13670,6 +13704,15 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -13682,8 +13725,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.prototype.trimend": { @@ -13692,8 +13735,8 @@ "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "define-properties": "1.1.3", + "es-abstract": "1.17.5" } }, "string.prototype.trimleft": { @@ -13702,9 +13745,9 @@ "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" + "define-properties": "1.1.3", + "es-abstract": "1.17.5", + "string.prototype.trimstart": "1.0.0" } }, "string.prototype.trimright": { @@ -13713,9 +13756,9 @@ "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" + "define-properties": "1.1.3", + "es-abstract": "1.17.5", + "string.prototype.trimend": "1.0.0" } }, "string.prototype.trimstart": { @@ -13724,17 +13767,8 @@ "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" + "define-properties": "1.1.3", + "es-abstract": "1.17.5" } }, "stringify-object": { @@ -13743,9 +13777,9 @@ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" + "get-own-enumerable-property-symbols": "3.0.2", + "is-obj": "1.0.1", + "is-regexp": "1.0.0" } }, "strip-ansi": { @@ -13754,7 +13788,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "strip-bom": { @@ -13793,7 +13827,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } }, "supports-hyperlinks": { @@ -13802,8 +13836,8 @@ "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", "dev": true, "requires": { - "has-flag": "^2.0.0", - "supports-color": "^5.0.0" + "has-flag": "2.0.0", + "supports-color": "5.5.0" }, "dependencies": { "has-flag": { @@ -13833,10 +13867,10 @@ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" + "ajv": "6.12.0", + "lodash": "4.17.15", + "slice-ansi": "2.1.0", + "string-width": "3.1.0" }, "dependencies": { "ansi-regex": { @@ -13857,9 +13891,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" } }, "strip-ansi": { @@ -13868,7 +13902,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } } } @@ -13885,13 +13919,13 @@ "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "chownr": "1.1.4", + "fs-minipass": "1.2.7", + "minipass": "2.9.0", + "minizlib": "1.3.3", + "mkdirp": "0.5.5", + "safe-buffer": "5.1.2", + "yallist": "3.1.1" } }, "tedious": { @@ -13900,15 +13934,15 @@ "integrity": "sha512-+M+mWg/D0a6DEynpl3JHNUqc3w9blSYGN+f+gs7jUfZsdnVYzcDPDzrKV0rjfaM1P22/bKPZ5Lm/2oDHo6/olQ==", "dev": true, "requires": { - "adal-node": "^0.1.22", + "adal-node": "0.1.28", "big-number": "1.0.0", - "bl": "^2.2.0", - "depd": "^1.1.2", - "iconv-lite": "^0.4.23", - "native-duplexpair": "^1.0.0", - "punycode": "^2.1.0", - "readable-stream": "^3.1.1", - "sprintf-js": "^1.1.2" + "bl": "2.2.0", + "depd": "1.1.2", + "iconv-lite": "0.4.24", + "native-duplexpair": "1.0.0", + "punycode": "2.1.1", + "readable-stream": "3.6.0", + "sprintf-js": "1.1.2" }, "dependencies": { "readable-stream": { @@ -13917,9 +13951,9 @@ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "inherits": "2.0.4", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "sprintf-js": { @@ -13942,9 +13976,9 @@ "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", "dev": true, "requires": { - "temp-dir": "^1.0.0", - "type-fest": "^0.3.1", - "unique-string": "^1.0.0" + "temp-dir": "1.0.0", + "type-fest": "0.3.1", + "unique-string": "1.0.0" }, "dependencies": { "type-fest": { @@ -13961,9 +13995,9 @@ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "@istanbuljs/schema": "0.1.2", + "glob": "7.1.6", + "minimatch": "3.0.4" } }, "text-extensions": { @@ -13990,7 +14024,7 @@ "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "dev": true, "requires": { - "readable-stream": "2 || 3" + "readable-stream": "2.3.7" } }, "through2-filter": { @@ -13999,8 +14033,8 @@ "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" + "through2": "2.0.5", + "xtend": "4.0.2" }, "dependencies": { "through2": { @@ -14009,8 +14043,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -14021,7 +14055,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "os-tmpdir": "1.0.2" } }, "to-absolute-glob": { @@ -14030,8 +14064,8 @@ "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "is-absolute": "1.0.0", + "is-negated-glob": "1.0.0" } }, "to-fast-properties": { @@ -14046,7 +14080,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -14055,7 +14089,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -14066,10 +14100,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "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": { @@ -14078,8 +14112,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, "to-through": { @@ -14088,7 +14122,7 @@ "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, "requires": { - "through2": "^2.0.3" + "through2": "2.0.5" }, "dependencies": { "through2": { @@ -14097,8 +14131,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -14114,8 +14148,8 @@ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "1.8.0", + "punycode": "2.1.1" } }, "tr46": { @@ -14161,19 +14195,19 @@ "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.4.2", + "commander": "2.20.3", + "diff": "3.5.0", + "glob": "7.1.6", + "js-yaml": "3.13.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "resolve": "1.15.1", + "semver": "5.7.1", + "tslib": "1.11.1", + "tsutils": "2.29.0" }, "dependencies": { "semver": { @@ -14190,7 +14224,7 @@ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { - "tslib": "^1.8.1" + "tslib": "1.11.1" } }, "tunnel-agent": { @@ -14199,7 +14233,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -14214,7 +14248,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-detect": { @@ -14235,7 +14269,7 @@ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { - "is-typedarray": "^1.0.0" + "is-typedarray": "1.0.0" } }, "typescript": { @@ -14257,8 +14291,8 @@ "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" + "commander": "2.20.3", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -14288,10 +14322,10 @@ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "2.0.1" } }, "unique-stream": { @@ -14300,8 +14334,8 @@ "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" + "json-stable-stringify-without-jsonify": "1.0.1", + "through2-filter": "3.0.0" } }, "unique-string": { @@ -14310,7 +14344,7 @@ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "1.0.0" } }, "universal-user-agent": { @@ -14319,7 +14353,7 @@ "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", "dev": true, "requires": { - "os-name": "^3.1.0" + "os-name": "3.1.0" } }, "universalify": { @@ -14334,8 +14368,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -14344,9 +14378,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -14374,7 +14408,7 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "^2.1.0" + "punycode": "2.1.1" } }, "urix": { @@ -14418,8 +14452,8 @@ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.1.0", + "spdx-expression-parse": "3.0.0" } }, "validator": { @@ -14439,9 +14473,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "vinyl": { @@ -14450,12 +14484,12 @@ "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.3", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } }, "vinyl-fs": { @@ -14464,23 +14498,23 @@ "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.2.3", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.0", + "pumpify": "1.5.1", + "readable-stream": "2.3.7", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.5", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.2.0", + "vinyl-sourcemap": "1.1.0" }, "dependencies": { "through2": { @@ -14489,8 +14523,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "readable-stream": "2.3.7", + "xtend": "4.0.2" } } } @@ -14501,13 +14535,13 @@ "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "dev": true, "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "append-buffer": "1.0.2", + "convert-source-map": "1.7.0", + "graceful-fs": "4.2.3", + "normalize-path": "2.1.1", + "now-and-later": "2.0.1", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.2.0" } }, "webidl-conversions": { @@ -14524,7 +14558,7 @@ "dev": true, "optional": true, "requires": { - "tr46": "~0.0.1" + "tr46": "0.0.3" } }, "which": { @@ -14533,7 +14567,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "which-module": { @@ -14554,7 +14588,7 @@ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "string-width": "2.1.1" } }, "windows-release": { @@ -14563,7 +14597,7 @@ "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", "dev": true, "requires": { - "execa": "^1.0.0" + "execa": "1.0.0" } }, "wkx": { @@ -14571,7 +14605,7 @@ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", "requires": { - "@types/node": "*" + "@types/node": "12.12.34" } }, "word-wrap": { @@ -14592,8 +14626,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "ansi-regex": { @@ -14608,7 +14642,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "string-width": { @@ -14617,9 +14651,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -14628,7 +14662,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -14645,7 +14679,7 @@ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "mkdirp": "0.5.5" } }, "write-file-atomic": { @@ -14654,10 +14688,10 @@ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "imurmurhash": "0.1.4", + "is-typedarray": "1.0.0", + "signal-exit": "3.0.3", + "typedarray-to-buffer": "3.1.5" } }, "xml-name-validator": { @@ -14703,7 +14737,7 @@ "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.7" + "@babel/runtime": "7.9.2" } }, "yargs": { @@ -14712,18 +14746,18 @@ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "3.0.0", + "get-caller-file": "1.0.3", + "os-locale": "3.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "11.1.1" }, "dependencies": { "camelcase": { @@ -14738,7 +14772,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "locate-path": { @@ -14747,8 +14781,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": { @@ -14757,7 +14791,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -14766,7 +14800,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -14781,8 +14815,8 @@ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } @@ -14793,7 +14827,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } }, "yargs-unparser": { @@ -14802,9 +14836,9 @@ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" + "flat": "4.1.0", + "lodash": "4.17.15", + "yargs": "13.3.2" }, "dependencies": { "ansi-regex": { @@ -14825,9 +14859,9 @@ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "3.1.0", + "strip-ansi": "5.2.0", + "wrap-ansi": "5.1.0" } }, "emoji-regex": { @@ -14842,7 +14876,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "get-caller-file": { @@ -14857,8 +14891,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": { @@ -14867,7 +14901,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.2.0" } }, "p-locate": { @@ -14876,7 +14910,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.2.2" } }, "p-try": { @@ -14897,9 +14931,9 @@ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "7.0.3", + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "5.2.0" } }, "strip-ansi": { @@ -14908,7 +14942,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "4.1.0" } }, "wrap-ansi": { @@ -14917,9 +14951,9 @@ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "3.2.1", + "string-width": "3.1.0", + "strip-ansi": "5.2.0" } }, "yargs": { @@ -14928,16 +14962,16 @@ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "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.1.2" + "cliui": "5.0.0", + "find-up": "3.0.0", + "get-caller-file": "2.0.5", + "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.2" } }, "yargs-parser": { @@ -14946,8 +14980,8 @@ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "5.3.1", + "decamelize": "1.2.0" } } } diff --git a/package.json b/package.json index c04d00db6a1e..6772f6906294 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "chai-spies": "^1.x", "cls-hooked": "^4.2.2", "cross-env": "^7.0.2", + "delay": "^4.3.0", "dtslint": "^2.0.5", "env-cmd": "^10.1.0", "esdoc": "^1.1.0", @@ -79,6 +80,8 @@ "mysql2": "^1.6.5", "nyc": "^15.0.0", "p-map": "^4.0.0", + "p-props": "^3.1.0", + "p-timeout": "^3.2.0", "pg": "^7.8.1", "pg-hstore": "^2.x", "pg-types": "^2.0.0", diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js index 83bae78e1fd2..ff3361e2466e 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -6,7 +6,8 @@ const chai = require('chai'), Sequelize = Support.Sequelize, Promise = Sequelize.Promise, cls = require('cls-hooked'), - current = Support.sequelize; + current = Support.sequelize, + delay = require('delay'); if (current.dialect.supports.transactions) { describe(Support.getTestDialectTeaser('CLS (Async hooks)'), () => { @@ -73,7 +74,7 @@ if (current.dialect.supports.transactions) { this.sequelize.transaction(() => { transactionSetup = true; - return Promise.delay(500).then(() => { + return delay(500).then(() => { expect(this.ns.get('transaction')).to.be.ok; transactionEnded = true; }); diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js index 2c63ac341ddf..321b9269ad45 100644 --- a/test/integration/configuration.test.js +++ b/test/integration/configuration.test.js @@ -7,7 +7,8 @@ const chai = require('chai'), dialect = Support.getTestDialect(), Sequelize = Support.Sequelize, fs = require('fs'), - path = require('path'); + path = require('path'), + { promisify } = require('util'); let sqlite3; if (dialect === 'sqlite') { @@ -74,11 +75,11 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { const createTableFoo = 'CREATE TABLE foo (faz TEXT);'; const createTableBar = 'CREATE TABLE bar (baz TEXT);'; - const testAccess = Sequelize.Promise.method(() => { - return Sequelize.Promise.promisify(fs.access)(p, fs.R_OK | fs.W_OK); - }); + const testAccess = () => { + return promisify(fs.access)(p, fs.R_OK | fs.W_OK); + }; - return Sequelize.Promise.promisify(fs.unlink)(p) + return promisify(fs.unlink)(p) .catch(err => { expect(err.code).to.equal('ENOENT'); }) @@ -135,7 +136,7 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { ); }) .finally(() => { - return Sequelize.Promise.promisify(fs.unlink)(p); + return promisify(fs.unlink)(p); }); }); } diff --git a/test/integration/include.test.js b/test/integration/include.test.js index 162ba9b385fc..007451b46f1f 100755 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -8,7 +8,8 @@ const chai = require('chai'), DataTypes = require('../../lib/data-types'), _ = require('lodash'), dialect = Support.getTestDialect(), - current = Support.sequelize; + current = Support.sequelize, + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -265,7 +266,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ task: Task.create(), user: User.create(), group: Group.create() diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index 954a33548edc..480a051c0879 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -7,7 +7,8 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - _ = require('lodash'); + _ = require('lodash'), + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -631,7 +632,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Order); return this.sequelize.sync().then(() => { - return Promise.props({ + return promiseProps({ users: User.bulkCreate([{}, {}, {}]).then(() => { return User.findAll(); }), @@ -712,7 +713,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { Tag.belongsToMany(Product, { through: ProductTag }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ products: Product.bulkCreate([ { title: 'Chair' }, { title: 'Desk' }, @@ -766,7 +767,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([{}, {}]).then(() => { return Group.findAll(); }), @@ -797,7 +798,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } @@ -835,7 +836,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } @@ -916,7 +917,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { Group.hasMany(Category); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } @@ -969,7 +970,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { Group.hasMany(Category, { as: 'Tags' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } @@ -1022,7 +1023,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { Group.hasMany(Category); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } @@ -1071,7 +1072,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasOne(Project, { as: 'LeaderOf' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ projects: Project.bulkCreate([ { title: 'Alpha' }, { title: 'Beta' } @@ -1115,7 +1116,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { Tag.belongsToMany(Product, { through: ProductTag }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ products: Product.bulkCreate([ { title: 'Chair' }, { title: 'Desk' }, @@ -1285,7 +1286,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } diff --git a/test/integration/include/schema.test.js b/test/integration/include/schema.test.js index 833721dd9a9a..ba68c0928944 100644 --- a/test/integration/include/schema.test.js +++ b/test/integration/include/schema.test.js @@ -8,7 +8,8 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'), Promise = Sequelize.Promise, dialect = Support.getTestDialect(), - _ = require('lodash'); + _ = require('lodash'), + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -1002,7 +1003,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ + return promiseProps({ groups: Group.bulkCreate([ { name: 'A' }, { name: 'B' } diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 312be20b3c1d..4df0612f4f7e 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -11,7 +11,8 @@ const chai = require('chai'), Op = Sequelize.Op, _ = require('lodash'), assert = require('assert'), - current = Support.sequelize; + current = Support.sequelize, + pTimeout = require('p-timeout'); describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { @@ -411,19 +412,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { if (times > 10) { return true; } - return this.Student.findOrCreate({ + return pTimeout(this.Student.findOrCreate({ where: { no: 1 } - }) - .timeout(1000) + }), 1000) .catch(e => { - if (!(e instanceof Promise.TimeoutError)) throw e; - throw new Error(e); - }) - .catch(err => { - if (!(err instanceof Sequelize.ValidationError)) throw err; - return test(times + 1); + if (e instanceof Sequelize.ValidationError) return test(times + 1); + if (e instanceof pTimeout.TimeoutError) throw new Error(e); + throw e; }); }; diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index 30c8b88dff0a..ad245780151e 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -11,7 +11,8 @@ const chai = require('chai'), config = require('../../config/config'), _ = require('lodash'), moment = require('moment'), - current = Support.sequelize; + current = Support.sequelize, + promiseProps = require('p-props'); describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { @@ -953,7 +954,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.props({ + return promiseProps({ europe: this.Continent.create({ name: 'Europe' }), england: this.Country.create({ name: 'England' }), coal: this.Industry.create({ name: 'Coal' }), @@ -1138,7 +1139,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.props({ + return promiseProps({ europe: this.Continent.create({ name: 'Europe' }), asia: this.Continent.create({ name: 'Asia' }), england: this.Country.create({ name: 'England' }), @@ -1297,7 +1298,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Industry.belongsToMany(this.Country, { through: this.IndustryCountry }); return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.props({ + return promiseProps({ england: this.Country.create({ name: 'England' }), france: this.Country.create({ name: 'France' }), korea: this.Country.create({ name: 'Korea' }), diff --git a/test/integration/pool.test.js b/test/integration/pool.test.js index b0e4542fef9b..2f786cbdead7 100644 --- a/test/integration/pool.test.js +++ b/test/integration/pool.test.js @@ -6,6 +6,7 @@ const Support = require('./support'); const dialect = Support.getTestDialect(); const sinon = require('sinon'); const Sequelize = Support.Sequelize; +const delay = require('delay'); function assertSameConnection(newConnection, oldConnection) { switch (dialect) { @@ -143,7 +144,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { await cm.releaseConnection(firstConnection); // Wait a little and then get next available connection - await Sequelize.Promise.delay(90); + await delay(90); const secondConnection = await cm.getConnection(); assertSameConnection(secondConnection, firstConnection); @@ -168,7 +169,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { await cm.releaseConnection(firstConnection); // Wait a little and then get next available connection - await Sequelize.Promise.delay(110); + await delay(110); const secondConnection = await cm.getConnection(); diff --git a/test/integration/sequelize.transaction.test.js b/test/integration/sequelize.transaction.test.js index b6f09ef5663f..7127dbe43433 100644 --- a/test/integration/sequelize.transaction.test.js +++ b/test/integration/sequelize.transaction.test.js @@ -5,7 +5,8 @@ const chai = require('chai'), Support = require('./support'), Promise = require('../../lib/promise'), Transaction = require('../../lib/transaction'), - current = Support.sequelize; + current = Support.sequelize, + delay = require('delay'); if (current.dialect.supports.transactions) { @@ -101,7 +102,7 @@ if (current.dialect.supports.transactions) { return Test .create({ name: 'Peter' }, { transaction }) .then(() => { - return Promise.delay(1000).then(() => { + return delay(1000).then(() => { return transaction .commit() .then(() => { return Test.count(); }) @@ -141,7 +142,7 @@ if (current.dialect.supports.transactions) { expect(err).to.be.ok; return t2.rollback(); }), - Promise.delay(100).then(() => { + delay(100).then(() => { return t1.commit(); }) ]); diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index ea140910788a..52283486c8b6 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -9,7 +9,8 @@ const chai = require('chai'), QueryTypes = require('../../lib/query-types'), Transaction = require('../../lib/transaction'), sinon = require('sinon'), - current = Support.sequelize; + current = Support.sequelize, + delay = require('delay'); if (current.dialect.supports.transactions) { @@ -393,7 +394,7 @@ if (current.dialect.supports.transactions) { lock: 'UPDATE', transaction }) - .then(() => Promise.delay(10)) + .then(() => delay(10)) .then(() => { return Task.update({ id: to }, { where: { @@ -513,7 +514,7 @@ if (current.dialect.supports.transactions) { const newTransactionFunc = function() { return sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: { match: ['NO_MATCH'] } }).then(t => { // introduce delay to force the busy state race condition to fail - return Promise.delay(1000).then(() => { + return delay(1000).then(() => { return User.create({ id: null, username: `test ${t.id}` }, { transaction: t }).then(() => { return t.commit(); }); @@ -586,7 +587,7 @@ if (current.dialect.supports.transactions) { username: 'jan' } }).then(() => expect(transactionSpy).to.have.been.called ), // Update should not succeed before transaction has committed - Promise.delay(2000) + delay(2000) .then(() => transaction.commit()) .then(transactionSpy) )); @@ -642,7 +643,7 @@ if (current.dialect.supports.transactions) { transaction: t1 }).then(() => { t1Spy(); - return Promise.delay(2000).then(() => { + return delay(2000).then(() => { return t1.commit(); }); }) @@ -827,7 +828,7 @@ if (current.dialect.supports.transactions) { }, { transaction: t1 }).then(() => { - return Promise.delay(2000).then(() => { + return delay(2000).then(() => { t1Spy(); expect(t1Spy).to.have.been.calledAfter(t2Spy); return t1.commit(); @@ -890,7 +891,7 @@ if (current.dialect.supports.transactions) { }, { transaction: t1 }).then(() => { - return Promise.delay(2000).then(() => { + return delay(2000).then(() => { t1Spy(); return t1.commit(); }); diff --git a/test/unit/dialects/mssql/resource-lock.test.js b/test/unit/dialects/mssql/resource-lock.test.js index bad1f185a531..7aae17940d44 100644 --- a/test/unit/dialects/mssql/resource-lock.test.js +++ b/test/unit/dialects/mssql/resource-lock.test.js @@ -4,7 +4,8 @@ const ResourceLock = require('../../../../lib/dialects/mssql/resource-lock'), Promise = require('../../../../lib/promise'), assert = require('assert'), Support = require('../../support'), - dialect = Support.getTestDialect(); + dialect = Support.getTestDialect(), + delay = require('delay'); if (dialect === 'mssql') { describe('[MSSQL Specific] ResourceLock', () => { @@ -23,7 +24,7 @@ if (dialect === 'mssql') { assert.equal(last, 0); last = 1; - return Promise.delay(15); + return delay(15); }), Promise.using(lock.lock(), resource => { validateResource(resource); @@ -35,7 +36,7 @@ if (dialect === 'mssql') { assert.equal(last, 2); last = 3; - return Promise.delay(5); + return delay(5); }) ]); }); From 330728b2c5688df315e0b43885b73d39fec7d6de Mon Sep 17 00:00:00 2001 From: Ariel Barabas <810664+knoid@users.noreply.github.com> Date: Thu, 9 Apr 2020 01:59:29 -0300 Subject: [PATCH 076/414] fix(types): getForeignKeysForTables argument definition (#12084) --- lib/query-interface.js | 2 +- types/lib/query-interface.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/query-interface.js b/lib/query-interface.js index bf192b0ef220..d19e255e3137 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -677,7 +677,7 @@ class QueryInterface { /** - * Returns all foreign key constraints of a table + * Returns all foreign key constraints of requested tables * * @param {string[]} tableNames table names * @param {object} [options] Query options diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 6d758f0bc47a..4c1852e7b4b1 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -439,9 +439,9 @@ export class QueryInterface { public nameIndexes(indexes: string[], rawTablename: string): Promise; /** - * Returns all foreign key constraints of a table + * Returns all foreign key constraints of requested tables */ - public getForeignKeysForTables(tableNames: string, options?: QueryInterfaceOptions): Promise; + public getForeignKeysForTables(tableNames: string[], options?: QueryInterfaceOptions): Promise; /** * Get foreign key references details for the table From d16de58f833a7339a7c74195e633f43006ad27f1 Mon Sep 17 00:00:00 2001 From: Vidit Kothari Date: Thu, 9 Apr 2020 10:31:25 +0530 Subject: [PATCH 077/414] fix(docs/instances): use correct variable for increment (#12087) --- docs/manual/core-concepts/model-instances.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/core-concepts/model-instances.md b/docs/manual/core-concepts/model-instances.md index 29c3ce942de4..27699dc2412c 100644 --- a/docs/manual/core-concepts/model-instances.md +++ b/docs/manual/core-concepts/model-instances.md @@ -147,7 +147,7 @@ In order to increment/decrement values of an instance without running into concu ```js const jane = await User.create({ name: "Jane", age: 100 }); -const incrementResult = await user.increment('age', { by: 2 }); +const incrementResult = await jane.increment('age', { by: 2 }); // Note: to increment by 1 you can omit the `by` option and just do `user.increment('age')` // In PostgreSQL, `incrementResult` will be the updated user, unless the option @@ -169,4 +169,4 @@ await jane.increment({ await jane.increment(['age', 'cash'], { by: 2 }); ``` -Decrementing works in the exact same way. \ No newline at end of file +Decrementing works in the exact same way. From de9c66a386a42d7224d1ed05216de124fa7804e9 Mon Sep 17 00:00:00 2001 From: Ben Leith Date: Thu, 9 Apr 2020 21:06:06 +1000 Subject: [PATCH 078/414] feat(belongs-to-many): allow creation of paranoid join tables (#12088) --- lib/associations/belongs-to-many.js | 2 +- .../associations/belongs-to-many.test.js | 30 +++++++++++++++++++ types/lib/associations/belongs-to-many.d.ts | 9 +++++- types/test/model.ts | 21 +++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index e28e3372b8e3..7ab3df903266 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -145,7 +145,7 @@ class BelongsToMany extends Association { this.through.model = this.sequelize.define(this.through.model, {}, Object.assign(this.options, { tableName: this.through.model, indexes: [], //we don't want indexes here (as referenced in #2416) - paranoid: false, // A paranoid join table does not make sense + paranoid: this.through.paranoid ? this.through.paranoid : false, // Default to non-paranoid join (referenced in #11991) validate: {} // Don't propagate model-level validations })); } else { diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index a8cf820f891a..8c00c6760410 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -2285,6 +2285,36 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { expect(association.through.model.options.paranoid).not.to.be.ok; }); }); + + it('should allow creation of a paranoid join table', () => { + const paranoidSequelize = Support.createSequelizeInstance({ + define: { + paranoid: true + } + }), + ParanoidUser = paranoidSequelize.define('ParanoidUser', {}), + ParanoidTask = paranoidSequelize.define('ParanoidTask', {}); + + ParanoidUser.belongsToMany(ParanoidTask, { + through: { + model: 'UserTasks', + paranoid: true + } + }); + ParanoidTask.belongsToMany(ParanoidUser, { + through: { + model: 'UserTasks', + paranoid: true + } + }); + + expect(ParanoidUser.options.paranoid).to.be.ok; + expect(ParanoidTask.options.paranoid).to.be.ok; + + _.forEach(ParanoidUser.associations, association => { + expect(association.through.model.options.paranoid).to.be.ok; + }); + }); }); describe('foreign keys', () => { diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index 54524e6d5562..1a4a15a30123 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -21,8 +21,15 @@ import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, Mu export interface ThroughOptions { /** * The model used to join both sides of the N:M association. + * Can be a string if you want the model to be generated by sequelize. */ - model: typeof Model; + model: typeof Model | string; + + /** + * If true the generated join table will be paranoid + * @default false + */ + paranoid?: boolean; /** * A key/value set that will be used for association create and find defaults on the through model. diff --git a/types/test/model.ts b/types/test/model.ts index 7edc6064389b..c08f4a1d8329 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -115,3 +115,24 @@ const someInstance = new SomeModel() someInstance.getOthers({ joinTableAttributes: { include: [ 'id' ] } }) + +/** + * Test for through options in creating a BelongsToMany association + */ +class Film extends Model {} + +class Actor extends Model {} + +Film.belongsToMany(Actor, { + through: { + model: 'FilmActors', + paranoid: true + } +}) + +Actor.belongsToMany(Film, { + through: { + model: 'FilmActors', + paranoid: true + } +}) \ No newline at end of file From a283dc0bfbc262ff5cf97c2d5ef33efac2002a15 Mon Sep 17 00:00:00 2001 From: Sushant Date: Thu, 9 Apr 2020 21:55:41 +0530 Subject: [PATCH 079/414] build: update lock file --- package-lock.json | 5538 +++++++++++++++++++++++---------------------- 1 file changed, 2775 insertions(+), 2763 deletions(-) diff --git a/package-lock.json b/package-lock.json index 434398fbfe38..fa15a126fed8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { - "@babel/highlight": "7.9.0" + "@babel/highlight": "^7.8.3" } }, "@babel/core": { @@ -19,22 +19,22 @@ "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "@babel/generator": "7.9.4", - "@babel/helper-module-transforms": "7.9.0", - "@babel/helpers": "7.9.2", - "@babel/parser": "7.9.4", - "@babel/template": "7.8.6", - "@babel/traverse": "7.9.0", - "@babel/types": "7.9.0", - "convert-source-map": "1.7.0", - "debug": "4.1.1", - "gensync": "1.0.0-beta.1", - "json5": "2.1.2", - "lodash": "4.17.15", - "resolve": "1.15.1", - "semver": "5.7.1", - "source-map": "0.5.7" + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.0", + "@babel/parser": "^7.9.0", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" }, "dependencies": { "semver": { @@ -51,10 +51,10 @@ "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", "dev": true, "requires": { - "@babel/types": "7.9.0", - "jsesc": "2.5.2", - "lodash": "4.17.15", - "source-map": "0.5.7" + "@babel/types": "^7.9.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" }, "dependencies": { "jsesc": { @@ -71,9 +71,9 @@ "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.8.3", - "@babel/template": "7.8.6", - "@babel/types": "7.9.0" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { @@ -82,7 +82,7 @@ "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "7.9.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-member-expression-to-functions": { @@ -91,7 +91,7 @@ "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", "dev": true, "requires": { - "@babel/types": "7.9.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-module-imports": { @@ -100,7 +100,7 @@ "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", "dev": true, "requires": { - "@babel/types": "7.9.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-module-transforms": { @@ -109,13 +109,13 @@ "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", "dev": true, "requires": { - "@babel/helper-module-imports": "7.8.3", - "@babel/helper-replace-supers": "7.8.6", - "@babel/helper-simple-access": "7.8.3", - "@babel/helper-split-export-declaration": "7.8.3", - "@babel/template": "7.8.6", - "@babel/types": "7.9.0", - "lodash": "4.17.15" + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" } }, "@babel/helper-optimise-call-expression": { @@ -124,7 +124,7 @@ "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", "dev": true, "requires": { - "@babel/types": "7.9.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-replace-supers": { @@ -133,10 +133,10 @@ "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "7.8.3", - "@babel/helper-optimise-call-expression": "7.8.3", - "@babel/traverse": "7.9.0", - "@babel/types": "7.9.0" + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/helper-simple-access": { @@ -145,8 +145,8 @@ "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", "dev": true, "requires": { - "@babel/template": "7.8.6", - "@babel/types": "7.9.0" + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { @@ -155,7 +155,7 @@ "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "7.9.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-validator-identifier": { @@ -170,9 +170,9 @@ "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", "dev": true, "requires": { - "@babel/template": "7.8.6", - "@babel/traverse": "7.9.0", - "@babel/types": "7.9.0" + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0" } }, "@babel/highlight": { @@ -181,9 +181,9 @@ "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "7.9.0", - "chalk": "2.4.2", - "js-tokens": "4.0.0" + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" }, "dependencies": { "js-tokens": { @@ -206,7 +206,7 @@ "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", "dev": true, "requires": { - "regenerator-runtime": "0.13.5" + "regenerator-runtime": "^0.13.4" }, "dependencies": { "regenerator-runtime": { @@ -223,9 +223,9 @@ "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "@babel/parser": "7.9.4", - "@babel/types": "7.9.0" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" } }, "@babel/traverse": { @@ -234,15 +234,15 @@ "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "@babel/generator": "7.9.4", - "@babel/helper-function-name": "7.8.3", - "@babel/helper-split-export-declaration": "7.8.3", - "@babel/parser": "7.9.4", - "@babel/types": "7.9.0", - "debug": "4.1.1", - "globals": "11.12.0", - "lodash": "4.17.15" + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" }, "dependencies": { "globals": { @@ -259,9 +259,9 @@ "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "7.9.0", - "lodash": "4.17.15", - "to-fast-properties": "2.0.0" + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" }, "dependencies": { "to-fast-properties": { @@ -278,10 +278,10 @@ "integrity": "sha512-6+L0vbw55UEdht71pgWOE55SRgb+8OHcEwGDB234VlIBFGK9P2QOBU7MHiYJ5cjdjCQ0rReNrGjOHmJ99jwf0w==", "dev": true, "requires": { - "@commitlint/format": "8.3.4", - "@commitlint/lint": "8.3.5", - "@commitlint/load": "8.3.5", - "@commitlint/read": "8.3.4", + "@commitlint/format": "^8.3.4", + "@commitlint/lint": "^8.3.5", + "@commitlint/load": "^8.3.5", + "@commitlint/read": "^8.3.4", "babel-polyfill": "6.26.0", "chalk": "2.4.2", "get-stdin": "7.0.0", @@ -297,7 +297,7 @@ "integrity": "sha512-mFg1Yj2xFDBJJyltGP3RLqZOk89HuNK1ttOcRA9lsTjTVVu4MrNv5sQ1LkW645xn4Vy9zgxlB0CrR4LXEg5QpQ==", "dev": true, "requires": { - "@commitlint/config-angular-type-enum": "8.3.4" + "@commitlint/config-angular-type-enum": "^8.3.4" } }, "@commitlint/config-angular-type-enum": { @@ -327,7 +327,7 @@ "integrity": "sha512-809wlQ/ND6CLZON+w2Rb3YM2TLNDfU2xyyqpZeqzf2reJNpySMSUAeaO/fNDJSOKIsOsR3bI01rGu6hv28k+Nw==", "dev": true, "requires": { - "chalk": "2.4.2" + "chalk": "^2.0.1" } }, "@commitlint/is-ignored": { @@ -353,10 +353,10 @@ "integrity": "sha512-02AkI0a6PU6rzqUvuDkSi6rDQ2hUgkq9GpmdJqfai5bDbxx2939mK4ZO+7apbIh4H6Pae7EpYi7ffxuJgm+3hQ==", "dev": true, "requires": { - "@commitlint/is-ignored": "8.3.5", - "@commitlint/parse": "8.3.4", - "@commitlint/rules": "8.3.4", - "babel-runtime": "6.26.0", + "@commitlint/is-ignored": "^8.3.5", + "@commitlint/parse": "^8.3.4", + "@commitlint/rules": "^8.3.4", + "babel-runtime": "^6.23.0", "lodash": "4.17.15" } }, @@ -366,13 +366,13 @@ "integrity": "sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw==", "dev": true, "requires": { - "@commitlint/execute-rule": "8.3.4", - "@commitlint/resolve-extends": "8.3.5", - "babel-runtime": "6.26.0", + "@commitlint/execute-rule": "^8.3.4", + "@commitlint/resolve-extends": "^8.3.5", + "babel-runtime": "^6.23.0", "chalk": "2.4.2", - "cosmiconfig": "5.2.1", + "cosmiconfig": "^5.2.0", "lodash": "4.17.15", - "resolve-from": "5.0.0" + "resolve-from": "^5.0.0" } }, "@commitlint/message": { @@ -387,9 +387,9 @@ "integrity": "sha512-b3uQvpUQWC20EBfKSfMRnyx5Wc4Cn778bVeVOFErF/cXQK725L1bYFvPnEjQO/GT8yGVzq2wtLaoEqjm1NJ/Bw==", "dev": true, "requires": { - "conventional-changelog-angular": "1.6.6", - "conventional-commits-parser": "3.0.8", - "lodash": "4.17.15" + "conventional-changelog-angular": "^1.3.3", + "conventional-commits-parser": "^3.0.0", + "lodash": "^4.17.11" } }, "@commitlint/read": { @@ -398,10 +398,10 @@ "integrity": "sha512-FKv1kHPrvcAG5j+OSbd41IWexsbLhfIXpxVC/YwQZO+FR0EHmygxQNYs66r+GnhD1EfYJYM4WQIqd5bJRx6OIw==", "dev": true, "requires": { - "@commitlint/top-level": "8.3.4", - "@marionebl/sander": "0.6.1", - "babel-runtime": "6.26.0", - "git-raw-commits": "2.0.3" + "@commitlint/top-level": "^8.3.4", + "@marionebl/sander": "^0.6.0", + "babel-runtime": "^6.23.0", + "git-raw-commits": "^2.0.0" } }, "@commitlint/resolve-extends": { @@ -410,10 +410,10 @@ "integrity": "sha512-nHhFAK29qiXNe6oH6uG5wqBnCR+BQnxlBW/q5fjtxIaQALgfoNLHwLS9exzbIRFqwJckpR6yMCfgMbmbAOtklQ==", "dev": true, "requires": { - "import-fresh": "3.2.1", + "import-fresh": "^3.0.0", "lodash": "4.17.15", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0" + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" } }, "@commitlint/rules": { @@ -422,10 +422,10 @@ "integrity": "sha512-xuC9dlqD5xgAoDFgnbs578cJySvwOSkMLQyZADb1xD5n7BNcUJfP8WjT9W1Aw8K3Wf8+Ym/ysr9FZHXInLeaRg==", "dev": true, "requires": { - "@commitlint/ensure": "8.3.4", - "@commitlint/message": "8.3.4", - "@commitlint/to-lines": "8.3.4", - "babel-runtime": "6.26.0" + "@commitlint/ensure": "^8.3.4", + "@commitlint/message": "^8.3.4", + "@commitlint/to-lines": "^8.3.4", + "babel-runtime": "^6.23.0" } }, "@commitlint/to-lines": { @@ -440,7 +440,7 @@ "integrity": "sha512-nOaeLBbAqSZNpKgEtO6NAxmui1G8ZvLG+0wb4rvv6mWhPDzK1GNZkCd8FUZPahCoJ1iHDoatw7F8BbJLg4nDjg==", "dev": true, "requires": { - "find-up": "4.1.0" + "find-up": "^4.0.0" }, "dependencies": { "find-up": { @@ -449,8 +449,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "locate-path": { @@ -459,7 +459,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -468,7 +468,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -477,7 +477,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-try": { @@ -500,10 +500,10 @@ "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", "dev": true, "requires": { - "camelcase": "5.3.1", - "find-up": "4.1.0", - "js-yaml": "3.13.1", - "resolve-from": "5.0.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "dependencies": { "camelcase": { @@ -518,8 +518,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "locate-path": { @@ -528,7 +528,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -537,7 +537,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -546,7 +546,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-try": { @@ -575,9 +575,9 @@ "integrity": "sha1-GViWWHTyS8Ub5Ih1/rUNZC/EH3s=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "mkdirp": "0.5.5", - "rimraf": "2.7.1" + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" } }, "@nodelib/fs.scandir": { @@ -587,7 +587,7 @@ "dev": true, "requires": { "@nodelib/fs.stat": "2.0.3", - "run-parallel": "1.1.9" + "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { @@ -603,7 +603,7 @@ "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.3", - "fastq": "1.7.0" + "fastq": "^1.6.0" } }, "@octokit/auth-token": { @@ -612,7 +612,7 @@ "integrity": "sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==", "dev": true, "requires": { - "@octokit/types": "2.5.1" + "@octokit/types": "^2.0.0" } }, "@octokit/endpoint": { @@ -621,9 +621,9 @@ "integrity": "sha512-3nx+MEYoZeD0uJ+7F/gvELLvQJzLXhep2Az0bBSXagbApDvDW0LWwpnAIY/hb0Jwe17A0fJdz0O12dPh05cj7A==", "dev": true, "requires": { - "@octokit/types": "2.5.1", - "is-plain-object": "3.0.0", - "universal-user-agent": "5.0.0" + "@octokit/types": "^2.0.0", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^5.0.0" }, "dependencies": { "is-plain-object": { @@ -632,7 +632,7 @@ "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", "dev": true, "requires": { - "isobject": "4.0.0" + "isobject": "^4.0.0" } }, "isobject": { @@ -647,7 +647,7 @@ "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", "dev": true, "requires": { - "os-name": "3.1.0" + "os-name": "^3.1.0" } } } @@ -658,7 +658,7 @@ "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", "dev": true, "requires": { - "@octokit/types": "2.5.1" + "@octokit/types": "^2.0.1" } }, "@octokit/plugin-request-log": { @@ -673,8 +673,8 @@ "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", "dev": true, "requires": { - "@octokit/types": "2.5.1", - "deprecation": "2.3.1" + "@octokit/types": "^2.0.1", + "deprecation": "^2.3.1" } }, "@octokit/request": { @@ -683,14 +683,14 @@ "integrity": "sha512-qyj8G8BxQyXjt9Xu6NvfvOr1E0l35lsXtwm3SopsYg/JWXjlsnwqLc8rsD2OLguEL/JjLfBvrXr4az7z8Lch2A==", "dev": true, "requires": { - "@octokit/endpoint": "6.0.0", - "@octokit/request-error": "2.0.0", - "@octokit/types": "2.5.1", - "deprecation": "2.3.1", - "is-plain-object": "3.0.0", - "node-fetch": "2.6.0", - "once": "1.4.0", - "universal-user-agent": "5.0.0" + "@octokit/endpoint": "^6.0.0", + "@octokit/request-error": "^2.0.0", + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^5.0.0" }, "dependencies": { "@octokit/request-error": { @@ -699,9 +699,9 @@ "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==", "dev": true, "requires": { - "@octokit/types": "2.5.1", - "deprecation": "2.3.1", - "once": "1.4.0" + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" } }, "is-plain-object": { @@ -710,7 +710,7 @@ "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", "dev": true, "requires": { - "isobject": "4.0.0" + "isobject": "^4.0.0" } }, "isobject": { @@ -725,7 +725,7 @@ "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", "dev": true, "requires": { - "os-name": "3.1.0" + "os-name": "^3.1.0" } } } @@ -736,9 +736,9 @@ "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", "dev": true, "requires": { - "@octokit/types": "2.5.1", - "deprecation": "2.3.1", - "once": "1.4.0" + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" } }, "@octokit/rest": { @@ -747,22 +747,22 @@ "integrity": "sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==", "dev": true, "requires": { - "@octokit/auth-token": "2.4.0", - "@octokit/plugin-paginate-rest": "1.1.2", - "@octokit/plugin-request-log": "1.0.0", + "@octokit/auth-token": "^2.4.0", + "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/plugin-request-log": "^1.0.0", "@octokit/plugin-rest-endpoint-methods": "2.4.0", - "@octokit/request": "5.3.4", - "@octokit/request-error": "1.2.1", - "atob-lite": "2.0.0", - "before-after-hook": "2.1.0", - "btoa-lite": "1.0.0", - "deprecation": "2.3.1", - "lodash.get": "4.4.2", - "lodash.set": "4.3.2", - "lodash.uniq": "4.5.0", - "octokit-pagination-methods": "1.1.0", - "once": "1.4.0", - "universal-user-agent": "4.0.1" + "@octokit/request": "^5.2.0", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^2.0.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^4.0.0" } }, "@octokit/types": { @@ -771,7 +771,7 @@ "integrity": "sha512-q4Wr7RexkPRrkQpXzUYF5Fj/14Mr65RyOHj6B9d/sQACpqGcStkHZj4qMEtlMY5SnD/69jlL9ItGPbDM0dR/dA==", "dev": true, "requires": { - "@types/node": "12.12.34" + "@types/node": ">= 8" } }, "@samverschueren/stream-to-observable": { @@ -780,7 +780,7 @@ "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", "dev": true, "requires": { - "any-observable": "0.3.0" + "any-observable": "^0.3.0" } }, "@semantic-release/commit-analyzer": { @@ -789,13 +789,13 @@ "integrity": "sha512-t5wMGByv+SknjP2m3rhWN4vmXoQ16g5VFY8iC4/tcbLPvzxK+35xsTIeUsrVFZv3ymdgAQKIr5J3lKjhF/VZZQ==", "dev": true, "requires": { - "conventional-changelog-angular": "5.0.6", - "conventional-commits-filter": "2.0.2", - "conventional-commits-parser": "3.0.8", - "debug": "4.1.1", - "import-from": "3.0.0", - "lodash": "4.17.15", - "micromatch": "3.1.10" + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.7", + "debug": "^4.0.0", + "import-from": "^3.0.0", + "lodash": "^4.17.4", + "micromatch": "^3.1.10" }, "dependencies": { "conventional-changelog-angular": { @@ -804,8 +804,8 @@ "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", "dev": true, "requires": { - "compare-func": "1.3.2", - "q": "1.5.1" + "compare-func": "^1.3.1", + "q": "^1.5.1" } } } @@ -822,22 +822,22 @@ "integrity": "sha512-tBE8duwyOB+FXetHucl5wCOlZhNPHN1ipQENxN6roCz22AYYRLRaVYNPjo78F+KNJpb+Gy8DdudH78Qc8VhKtQ==", "dev": true, "requires": { - "@octokit/rest": "16.43.1", - "@semantic-release/error": "2.2.0", - "aggregate-error": "3.0.1", - "bottleneck": "2.19.5", - "debug": "4.1.1", - "dir-glob": "3.0.1", - "fs-extra": "8.1.0", - "globby": "10.0.2", - "http-proxy-agent": "4.0.1", - "https-proxy-agent": "4.0.0", - "issue-parser": "6.0.0", - "lodash": "4.17.15", - "mime": "2.4.4", - "p-filter": "2.1.0", - "p-retry": "4.2.0", - "url-join": "4.0.1" + "@octokit/rest": "^16.27.0", + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "bottleneck": "^2.18.1", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "fs-extra": "^8.0.0", + "globby": "^10.0.0", + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^4.0.0", + "issue-parser": "^6.0.0", + "lodash": "^4.17.4", + "mime": "^2.4.3", + "p-filter": "^2.0.0", + "p-retry": "^4.0.0", + "url-join": "^4.0.0" }, "dependencies": { "array-union": { @@ -852,9 +852,9 @@ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "4.0.0", - "universalify": "0.1.2" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "globby": { @@ -863,14 +863,14 @@ "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, "requires": { - "@types/glob": "7.1.1", - "array-union": "2.1.0", - "dir-glob": "3.0.1", - "fast-glob": "3.2.2", - "glob": "7.1.6", - "ignore": "5.1.4", - "merge2": "1.3.0", - "slash": "3.0.0" + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" } }, "ignore": { @@ -893,19 +893,19 @@ "integrity": "sha512-aqODzbtWpVHO/keinbBMnZEaN/TkdwQvyDWcT0oNbKFpZwLjNjn+QVItoLekF62FLlXXziu2y6V4wnl9FDnujA==", "dev": true, "requires": { - "@semantic-release/error": "2.2.0", - "aggregate-error": "3.0.1", - "execa": "4.0.0", - "fs-extra": "8.1.0", - "lodash": "4.17.15", - "nerf-dart": "1.0.0", - "normalize-url": "4.5.0", - "npm": "6.14.4", - "rc": "1.2.8", - "read-pkg": "5.2.0", - "registry-auth-token": "4.1.1", - "semver": "6.3.0", - "tempy": "0.3.0" + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "execa": "^4.0.0", + "fs-extra": "^8.0.0", + "lodash": "^4.17.15", + "nerf-dart": "^1.0.0", + "normalize-url": "^4.0.0", + "npm": "^6.10.3", + "rc": "^1.2.8", + "read-pkg": "^5.0.0", + "registry-auth-token": "^4.0.0", + "semver": "^6.3.0", + "tempy": "^0.3.0" }, "dependencies": { "cross-spawn": { @@ -914,9 +914,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "execa": { @@ -925,15 +925,15 @@ "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { - "cross-spawn": "7.0.1", - "get-stream": "5.1.0", - "human-signals": "1.1.1", - "is-stream": "2.0.0", - "merge-stream": "2.0.0", - "npm-run-path": "4.0.1", - "onetime": "5.1.0", - "signal-exit": "3.0.3", - "strip-final-newline": "2.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, "fs-extra": { @@ -942,9 +942,9 @@ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "4.0.0", - "universalify": "0.1.2" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "get-stream": { @@ -953,7 +953,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "is-stream": { @@ -968,7 +968,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "3.1.1" + "path-key": "^3.0.0" } }, "parse-json": { @@ -977,10 +977,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2", - "lines-and-columns": "1.1.6" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-key": { @@ -995,10 +995,10 @@ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "@types/normalize-package-data": "2.4.0", - "normalize-package-data": "2.5.0", - "parse-json": "5.0.0", - "type-fest": "0.6.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" } }, "semver": { @@ -1013,7 +1013,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -1034,7 +1034,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -1045,16 +1045,16 @@ "integrity": "sha512-LGjgPBGjjmjap/76O0Md3wc04Y7IlLnzZceLsAkcYRwGQdRPTTFUJKqDQTuieWTs7zfHzQoZqsqPfFxEN+g2+Q==", "dev": true, "requires": { - "conventional-changelog-angular": "5.0.6", - "conventional-changelog-writer": "4.0.11", - "conventional-commits-filter": "2.0.2", - "conventional-commits-parser": "3.0.8", - "debug": "4.1.1", - "get-stream": "5.1.0", - "import-from": "3.0.0", - "into-stream": "5.1.1", - "lodash": "4.17.15", - "read-pkg-up": "7.0.1" + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^4.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^4.0.0", + "get-stream": "^5.0.0", + "import-from": "^3.0.0", + "into-stream": "^5.0.0", + "lodash": "^4.17.4", + "read-pkg-up": "^7.0.0" }, "dependencies": { "conventional-changelog-angular": { @@ -1063,8 +1063,8 @@ "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", "dev": true, "requires": { - "compare-func": "1.3.2", - "q": "1.5.1" + "compare-func": "^1.3.1", + "q": "^1.5.1" } }, "find-up": { @@ -1073,8 +1073,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "get-stream": { @@ -1083,7 +1083,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "locate-path": { @@ -1092,7 +1092,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -1101,7 +1101,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -1110,7 +1110,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-try": { @@ -1125,10 +1125,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2", - "lines-and-columns": "1.1.6" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-exists": { @@ -1143,10 +1143,10 @@ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "@types/normalize-package-data": "2.4.0", - "normalize-package-data": "2.5.0", - "parse-json": "5.0.0", - "type-fest": "0.6.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" }, "dependencies": { "type-fest": { @@ -1163,9 +1163,9 @@ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "4.1.0", - "read-pkg": "5.2.0", - "type-fest": "0.8.1" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" } } } @@ -1185,8 +1185,8 @@ "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, "requires": { - "@sinonjs/commons": "1.7.1", - "@sinonjs/samsam": "3.3.3" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, "@sinonjs/samsam": { @@ -1195,9 +1195,9 @@ "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, "requires": { - "@sinonjs/commons": "1.7.1", - "array-from": "2.1.1", - "lodash": "4.17.15" + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" } }, "@sinonjs/text-encoding": { @@ -1242,9 +1242,9 @@ "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", "dev": true, "requires": { - "@types/events": "3.0.0", - "@types/minimatch": "3.0.3", - "@types/node": "12.12.34" + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" } }, "@types/minimatch": { @@ -1288,6 +1288,16 @@ "integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w==", "dev": true }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -1314,7 +1324,7 @@ "dev": true, "optional": true, "requires": { - "acorn": "2.7.0" + "acorn": "^2.1.0" }, "dependencies": { "acorn": { @@ -1338,15 +1348,15 @@ "integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=", "dev": true, "requires": { - "@types/node": "8.10.59", - "async": "3.2.0", - "date-utils": "1.2.21", - "jws": "3.2.2", - "request": "2.88.2", - "underscore": "1.10.2", - "uuid": "3.4.0", - "xmldom": "0.3.0", - "xpath.js": "1.1.0" + "@types/node": "^8.0.47", + "async": ">=0.6.0", + "date-utils": "*", + "jws": "3.x.x", + "request": ">= 2.52.0", + "underscore": ">= 1.3.1", + "uuid": "^3.1.0", + "xmldom": ">= 0.1.x", + "xpath.js": "~1.1.0" }, "dependencies": { "@types/node": { @@ -1363,7 +1373,7 @@ "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", "dev": true, "requires": { - "debug": "4.1.1" + "debug": "4" } }, "aggregate-error": { @@ -1372,8 +1382,8 @@ "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "dev": true, "requires": { - "clean-stack": "2.2.0", - "indent-string": "4.0.0" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" }, "dependencies": { "indent-string": { @@ -1390,10 +1400,10 @@ "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "dev": true, "requires": { - "fast-deep-equal": "3.1.1", - "fast-json-stable-stringify": "2.1.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-colors": { @@ -1408,7 +1418,7 @@ "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "requires": { - "type-fest": "0.11.0" + "type-fest": "^0.11.0" }, "dependencies": { "type-fest": { @@ -1431,7 +1441,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.3" + "color-convert": "^1.9.0" } }, "ansicolors": { @@ -1457,7 +1467,7 @@ "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, "requires": { - "buffer-equal": "1.0.0" + "buffer-equal": "^1.0.0" } }, "append-transform": { @@ -1466,7 +1476,7 @@ "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { - "default-require-extensions": "3.0.0" + "default-require-extensions": "^3.0.0" } }, "aproba": { @@ -1487,8 +1497,8 @@ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.7" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "argparse": { @@ -1497,7 +1507,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "argv-formatter": { @@ -1560,7 +1570,7 @@ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "~2.1.0" } }, "assert-plus": { @@ -1599,7 +1609,7 @@ "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", "dev": true, "requires": { - "stack-chain": "1.3.7" + "stack-chain": "^1.3.7" } }, "asynckit": { @@ -1638,9 +1648,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.3", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "ansi-regex": { @@ -1661,11 +1671,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "strip-ansi": { @@ -1674,7 +1684,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -1691,14 +1701,14 @@ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.15", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" } }, "babel-messages": { @@ -1707,7 +1717,7 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-polyfill": { @@ -1716,9 +1726,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.6.11", - "regenerator-runtime": "0.10.5" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" }, "dependencies": { "regenerator-runtime": { @@ -1735,8 +1745,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.6.11", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-traverse": { @@ -1745,15 +1755,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.15" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" }, "dependencies": { "debug": { @@ -1779,10 +1789,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.3", - "lodash": "4.17.15", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -1803,13 +1813,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "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.2", - "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": { @@ -1818,7 +1828,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -1827,7 +1837,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -1836,7 +1846,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -1845,9 +1855,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.3" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -1858,7 +1868,7 @@ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "before-after-hook": { @@ -1885,8 +1895,8 @@ "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2" + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" } }, "bluebird": { @@ -1912,7 +1922,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1922,16 +1932,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "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": { @@ -1940,7 +1950,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -1987,15 +1997,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "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.1", - "to-object-path": "0.3.0", - "union-value": "1.0.1", - "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": { @@ -2004,10 +2014,10 @@ "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { - "hasha": "5.2.0", - "make-dir": "3.0.2", - "package-hash": "4.0.0", - "write-file-atomic": "3.0.3" + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" } }, "caller-callsite": { @@ -2016,7 +2026,7 @@ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, "requires": { - "callsites": "2.0.0" + "callsites": "^2.0.0" }, "dependencies": { "callsites": { @@ -2033,7 +2043,7 @@ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, "requires": { - "caller-callsite": "2.0.0" + "caller-callsite": "^2.0.0" } }, "callsites": { @@ -2054,9 +2064,9 @@ "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", "dev": true, "requires": { - "camelcase": "4.1.0", - "map-obj": "2.0.0", - "quick-lru": "1.1.0" + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" } }, "cardinal": { @@ -2065,8 +2075,8 @@ "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", "dev": true, "requires": { - "ansicolors": "0.3.2", - "redeyed": "2.1.1" + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" } }, "caseless": { @@ -2081,12 +2091,12 @@ "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "1.1.0", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.8" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, "chai-as-promised": { @@ -2095,7 +2105,7 @@ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "check-error": "1.0.2" + "check-error": "^1.0.2" } }, "chai-datetime": { @@ -2104,7 +2114,7 @@ "integrity": "sha1-N0LxiwJMdbdqK37uKRZiMkRnWWw=", "dev": true, "requires": { - "chai": "4.2.0" + "chai": ">1.9.0" } }, "chai-spies": { @@ -2119,9 +2129,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": { @@ -2142,12 +2152,12 @@ "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.1", - "entities": "1.1.2", - "htmlparser2": "3.10.1", - "lodash": "4.17.15", - "parse5": "3.0.3" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" } }, "chownr": { @@ -2168,10 +2178,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "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": { @@ -2180,7 +2190,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -2197,7 +2207,7 @@ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "3.1.0" + "restore-cursor": "^3.1.0" } }, "cli-table": { @@ -2216,7 +2226,7 @@ "dev": true, "requires": { "slice-ansi": "0.0.4", - "string-width": "1.0.2" + "string-width": "^1.0.1" }, "dependencies": { "ansi-regex": { @@ -2231,7 +2241,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "slice-ansi": { @@ -2246,9 +2256,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": { @@ -2257,7 +2267,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -2274,9 +2284,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" } }, "clone": { @@ -2303,9 +2313,9 @@ "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, "requires": { - "inherits": "2.0.4", - "process-nextick-args": "2.0.1", - "readable-stream": "2.3.7" + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" } }, "cls-hooked": { @@ -2314,9 +2324,9 @@ "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", "dev": true, "requires": { - "async-hook-jl": "1.7.6", - "emitter-listener": "1.1.2", - "semver": "5.7.1" + "async-hook-jl": "^1.7.6", + "emitter-listener": "^1.0.1", + "semver": "^5.4.1" }, "dependencies": { "semver": { @@ -2339,8 +2349,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color-convert": { @@ -2376,7 +2386,7 @@ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "command-exists": { @@ -2409,8 +2419,8 @@ "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", "dev": true, "requires": { - "array-ify": "1.0.0", - "dot-prop": "3.0.0" + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" } }, "compare-versions": { @@ -2443,8 +2453,8 @@ "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", "dev": true, "requires": { - "compare-func": "1.3.2", - "q": "1.5.1" + "compare-func": "^1.3.1", + "q": "^1.5.1" } }, "conventional-changelog-writer": { @@ -2453,16 +2463,16 @@ "integrity": "sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw==", "dev": true, "requires": { - "compare-func": "1.3.2", - "conventional-commits-filter": "2.0.2", - "dateformat": "3.0.3", - "handlebars": "4.7.6", - "json-stringify-safe": "5.0.1", - "lodash": "4.17.15", - "meow": "5.0.0", - "semver": "6.3.0", - "split": "1.0.1", - "through2": "3.0.1" + "compare-func": "^1.3.1", + "conventional-commits-filter": "^2.0.2", + "dateformat": "^3.0.0", + "handlebars": "^4.4.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^5.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^3.0.0" }, "dependencies": { "semver": { @@ -2479,8 +2489,8 @@ "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==", "dev": true, "requires": { - "lodash.ismatch": "4.4.0", - "modify-values": "1.0.1" + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" } }, "conventional-commits-parser": { @@ -2489,13 +2499,13 @@ "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==", "dev": true, "requires": { - "is-text-path": "1.0.1", - "JSONStream": "1.3.5", - "lodash": "4.17.15", - "meow": "5.0.0", - "split2": "2.2.0", - "through2": "3.0.1", - "trim-off-newlines": "1.0.1" + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^5.0.0", + "split2": "^2.0.0", + "through2": "^3.0.0", + "trim-off-newlines": "^1.0.0" } }, "convert-source-map": { @@ -2504,7 +2514,7 @@ "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.1" } }, "copy-descriptor": { @@ -2531,10 +2541,10 @@ "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" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" }, "dependencies": { "import-fresh": { @@ -2543,8 +2553,8 @@ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { - "caller-path": "2.0.0", - "resolve-from": "3.0.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" } }, "resolve-from": { @@ -2561,7 +2571,7 @@ "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", "dev": true, "requires": { - "cross-spawn": "7.0.1" + "cross-spawn": "^7.0.1" }, "dependencies": { "cross-spawn": { @@ -2570,9 +2580,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "path-key": { @@ -2587,7 +2597,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -2602,7 +2612,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -2613,11 +2623,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.7.1", - "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" }, "dependencies": { "semver": { @@ -2640,10 +2650,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.3", + "boolbase": "~1.0.0", + "css-what": "2.1", "domutils": "1.5.1", - "nth-check": "1.0.2" + "nth-check": "~1.0.1" } }, "css-what": { @@ -2656,7 +2666,8 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true + "dev": true, + "optional": true }, "cssstyle": { "version": "0.2.37", @@ -2665,7 +2676,7 @@ "dev": true, "optional": true, "requires": { - "cssom": "0.3.8" + "cssom": "0.3.x" } }, "currently-unhandled": { @@ -2674,7 +2685,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "1.0.2" + "array-find-index": "^1.0.1" } }, "dargs": { @@ -2683,7 +2694,7 @@ "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "dashdash": { @@ -2692,7 +2703,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "date-fns": { @@ -2718,7 +2729,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.1" } }, "decamelize": { @@ -2733,8 +2744,8 @@ "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { - "decamelize": "1.2.0", - "map-obj": "1.0.1" + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" }, "dependencies": { "map-obj": { @@ -2763,7 +2774,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "4.0.8" + "type-detect": "^4.0.0" } }, "deep-extend": { @@ -2784,7 +2795,7 @@ "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { - "strip-bom": "4.0.0" + "strip-bom": "^4.0.0" }, "dependencies": { "strip-bom": { @@ -2801,7 +2812,7 @@ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "object-keys": "1.1.1" + "object-keys": "^1.0.12" } }, "define-property": { @@ -2810,8 +2821,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -2820,7 +2831,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -2829,7 +2840,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -2838,9 +2849,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.3" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -2851,8 +2862,8 @@ "integrity": "sha512-kQePPP/cqQX3H6DrX5nCo2vMjJeboPsjEG8OOl43TZbTOr9zLlapWJ/oRCLnMCiyERsBRZXyLMtBXGM+1zmtgQ==", "dev": true, "requires": { - "@types/parsimmon": "1.10.1", - "parsimmon": "1.13.0" + "@types/parsimmon": "^1.3.0", + "parsimmon": "^1.2.0" } }, "delay": { @@ -2897,7 +2908,7 @@ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "detect-libc": { @@ -2918,7 +2929,7 @@ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "path-type": "4.0.0" + "path-type": "^4.0.0" }, "dependencies": { "path-type": { @@ -2935,7 +2946,7 @@ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { - "esutils": "2.0.3" + "esutils": "^2.0.2" } }, "dom-serializer": { @@ -2944,8 +2955,8 @@ "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", "dev": true, "requires": { - "domelementtype": "1.3.1", - "entities": "1.1.2" + "domelementtype": "^1.3.0", + "entities": "^1.1.1" } }, "domelementtype": { @@ -2960,7 +2971,7 @@ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "dev": true, "requires": { - "domelementtype": "1.3.1" + "domelementtype": "1" } }, "domutils": { @@ -2969,8 +2980,8 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0.1.1", - "domelementtype": "1.3.1" + "dom-serializer": "0", + "domelementtype": "1" } }, "dot-prop": { @@ -2979,7 +2990,7 @@ "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", "dev": true, "requires": { - "is-obj": "1.0.1" + "is-obj": "^1.0.0" } }, "dottie": { @@ -2993,10 +3004,10 @@ "integrity": "sha512-yGHhVKo66iyPBFUYRyXX1uW+XEG3/HDP1pHCR3VlPl9ho8zRHy6lzS5k+gCSuINqjNsV8UjZSUXUuTuw0wHp7g==", "dev": true, "requires": { - "command-exists": "1.2.8", - "definitelytyped-header-parser": "3.8.2", - "semver": "6.3.0", - "yargs": "12.0.5" + "command-exists": "^1.2.8", + "definitelytyped-header-parser": "^3.8.2", + "semver": "^6.2.0", + "yargs": "^12.0.5" }, "dependencies": { "semver": { @@ -3014,11 +3025,11 @@ "dev": true, "requires": { "definitelytyped-header-parser": "3.8.2", - "dts-critic": "2.2.4", - "fs-extra": "6.0.1", - "strip-json-comments": "2.0.1", + "dts-critic": "^2.2.4", + "fs-extra": "^6.0.1", + "strip-json-comments": "^2.0.1", "tslint": "5.14.0", - "typescript": "3.9.0-dev.20200404" + "typescript": "^3.9.0-dev.20200404" }, "dependencies": { "typescript": { @@ -3035,7 +3046,7 @@ "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, "requires": { - "readable-stream": "2.3.7" + "readable-stream": "^2.0.2" } }, "duplexify": { @@ -3044,10 +3055,10 @@ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "dev": true, "requires": { - "end-of-stream": "1.4.4", - "inherits": "2.0.4", - "readable-stream": "2.3.7", - "stream-shift": "1.0.1" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" } }, "ecc-jsbn": { @@ -3056,8 +3067,8 @@ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ecdsa-sig-formatter": { @@ -3066,7 +3077,7 @@ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "elegant-spinner": { @@ -3081,7 +3092,7 @@ "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "dev": true, "requires": { - "shimmer": "1.2.1" + "shimmer": "^1.2.0" } }, "emoji-regex": { @@ -3096,7 +3107,7 @@ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "entities": { @@ -3111,8 +3122,8 @@ "integrity": "sha512-Xc41mKvjouTXD3Oy9AqySz1IeyvJvHZ20Twf5ZLYbNpPPIuCnL/qHCmNlD01LoNy0JTunw9HPYVptD19Ac7Mbw==", "dev": true, "requires": { - "execa": "4.0.0", - "java-properties": "1.0.2" + "execa": "^4.0.0", + "java-properties": "^1.0.0" }, "dependencies": { "cross-spawn": { @@ -3121,9 +3132,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "execa": { @@ -3132,15 +3143,15 @@ "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { - "cross-spawn": "7.0.1", - "get-stream": "5.1.0", - "human-signals": "1.1.1", - "is-stream": "2.0.0", - "merge-stream": "2.0.0", - "npm-run-path": "4.0.1", - "onetime": "5.1.0", - "signal-exit": "3.0.3", - "strip-final-newline": "2.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, "get-stream": { @@ -3149,7 +3160,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "is-stream": { @@ -3164,7 +3175,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "3.1.1" + "path-key": "^3.0.0" } }, "path-key": { @@ -3179,7 +3190,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -3194,7 +3205,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -3205,8 +3216,8 @@ "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", "dev": true, "requires": { - "commander": "4.1.1", - "cross-spawn": "7.0.1" + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" }, "dependencies": { "commander": { @@ -3221,9 +3232,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "path-key": { @@ -3238,7 +3249,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -3253,7 +3264,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -3264,7 +3275,7 @@ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { @@ -3273,17 +3284,17 @@ "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", "dev": true, "requires": { - "es-to-primitive": "1.2.1", - "function-bind": "1.1.1", - "has": "1.0.3", - "has-symbols": "1.0.1", - "is-callable": "1.1.5", - "is-regex": "1.0.5", - "object-inspect": "1.7.0", - "object-keys": "1.1.1", - "object.assign": "4.1.0", - "string.prototype.trimleft": "2.1.2", - "string.prototype.trimright": "2.1.2" + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" } }, "es-to-primitive": { @@ -3292,9 +3303,9 @@ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "is-callable": "1.1.5", - "is-date-object": "1.0.2", - "is-symbol": "1.0.3" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "es6-error": { @@ -3322,11 +3333,11 @@ "dev": true, "optional": true, "requires": { - "esprima": "4.0.1", - "estraverse": "4.3.0", - "esutils": "2.0.3", - "optionator": "0.8.3", - "source-map": "0.6.1" + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "dependencies": { "source-map": { @@ -3363,9 +3374,9 @@ "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "4.0.0", - "universalify": "0.1.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "marked": { @@ -3403,22 +3414,22 @@ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.1", - "entities": "1.1.2", - "htmlparser2": "3.10.1", - "lodash.assignin": "4.2.0", - "lodash.bind": "4.2.1", - "lodash.defaults": "4.2.0", - "lodash.filter": "4.6.0", - "lodash.flatten": "4.4.0", - "lodash.foreach": "4.5.0", - "lodash.map": "4.6.0", - "lodash.merge": "4.6.2", - "lodash.pick": "4.4.0", - "lodash.reduce": "4.6.0", - "lodash.reject": "4.6.0", - "lodash.some": "4.6.0" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" } } } @@ -3444,9 +3455,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "2.4.0", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" } }, "jsonfile": { @@ -3455,7 +3466,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.6" } } } @@ -3476,22 +3487,22 @@ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.1", - "entities": "1.1.2", - "htmlparser2": "3.10.1", - "lodash.assignin": "4.2.0", - "lodash.bind": "4.2.1", - "lodash.defaults": "4.2.0", - "lodash.filter": "4.6.0", - "lodash.flatten": "4.4.0", - "lodash.foreach": "4.5.0", - "lodash.map": "4.6.0", - "lodash.merge": "4.6.2", - "lodash.pick": "4.4.0", - "lodash.reduce": "4.6.0", - "lodash.reject": "4.6.0", - "lodash.some": "4.6.0" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" } }, "fs-extra": { @@ -3500,9 +3511,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "2.4.0", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" } }, "jsonfile": { @@ -3511,7 +3522,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.6" } } } @@ -3555,12 +3566,12 @@ "integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=", "dev": true, "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "3.0.1", - "lodash": "4.17.15", - "source-map": "0.5.7" + "babel-messages": "^6.8.0", + "babel-runtime": "^6.9.0", + "babel-types": "^6.10.2", + "detect-indent": "^3.0.1", + "lodash": "^4.2.0", + "source-map": "^0.5.0" } }, "cheerio": { @@ -3569,22 +3580,22 @@ "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.1", - "entities": "1.1.2", - "htmlparser2": "3.10.1", - "lodash.assignin": "4.2.0", - "lodash.bind": "4.2.1", - "lodash.defaults": "4.2.0", - "lodash.filter": "4.6.0", - "lodash.flatten": "4.4.0", - "lodash.foreach": "4.5.0", - "lodash.map": "4.6.0", - "lodash.merge": "4.6.2", - "lodash.pick": "4.4.0", - "lodash.reduce": "4.6.0", - "lodash.reject": "4.6.0", - "lodash.some": "4.6.0" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" } }, "detect-indent": { @@ -3593,9 +3604,9 @@ "integrity": "sha1-ncXl3bzu+DJXZLlFGwK8bVQIT3U=", "dev": true, "requires": { - "get-stdin": "4.0.1", - "minimist": "1.2.5", - "repeating": "1.1.3" + "get-stdin": "^4.0.1", + "minimist": "^1.1.0", + "repeating": "^1.1.0" } }, "fs-extra": { @@ -3604,9 +3615,9 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "2.4.0", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" } }, "get-stdin": { @@ -3621,7 +3632,7 @@ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.6" } }, "marked": { @@ -3636,7 +3647,7 @@ "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", "dev": true, "requires": { - "is-finite": "1.1.0" + "is-finite": "^1.0.0" } }, "taffydb": { @@ -3653,17 +3664,17 @@ "integrity": "sha1-ZhIBysfvhokkkCRG/awVJyU8XU0=", "dev": true, "requires": { - "esdoc-accessor-plugin": "1.0.0", - "esdoc-brand-plugin": "1.0.1", - "esdoc-coverage-plugin": "1.1.0", - "esdoc-external-ecmascript-plugin": "1.0.0", - "esdoc-integrate-manual-plugin": "1.0.0", - "esdoc-integrate-test-plugin": "1.0.0", - "esdoc-lint-plugin": "1.0.2", - "esdoc-publish-html-plugin": "1.1.2", - "esdoc-type-inference-plugin": "1.0.2", - "esdoc-undocumented-identifier-plugin": "1.0.0", - "esdoc-unexported-identifier-plugin": "1.0.0" + "esdoc-accessor-plugin": "^1.0.0", + "esdoc-brand-plugin": "^1.0.0", + "esdoc-coverage-plugin": "^1.0.0", + "esdoc-external-ecmascript-plugin": "^1.0.0", + "esdoc-integrate-manual-plugin": "^1.0.0", + "esdoc-integrate-test-plugin": "^1.0.0", + "esdoc-lint-plugin": "^1.0.0", + "esdoc-publish-html-plugin": "^1.0.0", + "esdoc-type-inference-plugin": "^1.0.0", + "esdoc-undocumented-identifier-plugin": "^1.0.0", + "esdoc-unexported-identifier-plugin": "^1.0.0" } }, "esdoc-type-inference-plugin": { @@ -3690,43 +3701,43 @@ "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "ajv": "6.12.0", - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "debug": "4.1.1", - "doctrine": "3.0.0", - "eslint-scope": "5.0.0", - "eslint-utils": "1.4.3", - "eslint-visitor-keys": "1.1.0", - "espree": "6.2.1", - "esquery": "1.2.0", - "esutils": "2.0.3", - "file-entry-cache": "5.0.1", - "functional-red-black-tree": "1.0.1", - "glob-parent": "5.1.1", - "globals": "12.4.0", - "ignore": "4.0.6", - "import-fresh": "3.2.1", - "imurmurhash": "0.1.4", - "inquirer": "7.1.0", - "is-glob": "4.0.1", - "js-yaml": "3.13.1", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.15", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "natural-compare": "1.4.0", - "optionator": "0.8.3", - "progress": "2.0.3", - "regexpp": "2.0.1", - "semver": "6.3.0", - "strip-ansi": "5.2.0", - "strip-json-comments": "3.0.1", - "table": "5.4.6", - "text-table": "0.2.0", - "v8-compile-cache": "2.1.0" + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { "ansi-regex": { @@ -3741,7 +3752,7 @@ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", "dev": true, "requires": { - "type-fest": "0.8.1" + "type-fest": "^0.8.1" } }, "semver": { @@ -3756,7 +3767,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } }, "strip-json-comments": { @@ -3773,14 +3784,14 @@ "integrity": "sha512-c/fnEpwWLFeQn+A7pb1qLOdyhovpqGCWCeUv1wtzFNL5G+xedl9wHUnXtp3b1sGHolVimi9DxKVTuhK/snXoOw==", "dev": true, "requires": { - "comment-parser": "0.7.2", - "debug": "4.1.1", - "jsdoctypeparser": "6.1.0", - "lodash": "4.17.15", - "object.entries-ponyfill": "1.0.1", - "regextras": "0.7.0", - "semver": "6.3.0", - "spdx-expression-parse": "3.0.0" + "comment-parser": "^0.7.2", + "debug": "^4.1.1", + "jsdoctypeparser": "^6.1.0", + "lodash": "^4.17.15", + "object.entries-ponyfill": "^1.0.1", + "regextras": "^0.7.0", + "semver": "^6.3.0", + "spdx-expression-parse": "^3.0.0" }, "dependencies": { "semver": { @@ -3797,8 +3808,8 @@ "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", "dev": true, "requires": { - "eslint-utils": "2.0.0", - "ramda": "0.27.0" + "eslint-utils": "^2.0.0", + "ramda": "^0.27.0" }, "dependencies": { "eslint-utils": { @@ -3807,7 +3818,7 @@ "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", "dev": true, "requires": { - "eslint-visitor-keys": "1.1.0" + "eslint-visitor-keys": "^1.1.0" } } } @@ -3818,8 +3829,8 @@ "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.3.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint-utils": { @@ -3828,7 +3839,7 @@ "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "1.1.0" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { @@ -3843,9 +3854,9 @@ "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "7.1.1", - "acorn-jsx": "5.2.0", - "eslint-visitor-keys": "1.1.0" + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -3860,7 +3871,7 @@ "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", "dev": true, "requires": { - "estraverse": "5.0.0" + "estraverse": "^5.0.0" }, "dependencies": { "estraverse": { @@ -3877,7 +3888,7 @@ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.3.0" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -3898,13 +3909,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.3", - "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": { @@ -3913,13 +3924,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "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": { @@ -3937,7 +3948,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -3946,7 +3957,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "ms": { @@ -3969,8 +3980,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -3979,7 +3990,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -3990,9 +4001,9 @@ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "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": { @@ -4001,14 +4012,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "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": { @@ -4017,7 +4028,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -4026,7 +4037,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -4035,7 +4046,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -4044,7 +4055,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -4053,9 +4064,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.3" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -4078,12 +4089,12 @@ "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", - "@nodelib/fs.walk": "1.2.4", - "glob-parent": "5.1.1", - "merge2": "1.3.0", - "micromatch": "4.0.2", - "picomatch": "2.2.2" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" }, "dependencies": { "braces": { @@ -4092,7 +4103,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "7.0.1" + "fill-range": "^7.0.1" } }, "fill-range": { @@ -4101,7 +4112,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "5.0.1" + "to-regex-range": "^5.0.1" } }, "is-number": { @@ -4116,8 +4127,8 @@ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "braces": "3.0.2", - "picomatch": "2.2.2" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "to-regex-range": { @@ -4126,7 +4137,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "7.0.0" + "is-number": "^7.0.0" } } } @@ -4149,7 +4160,7 @@ "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==", "dev": true, "requires": { - "reusify": "1.0.4" + "reusify": "^1.0.4" } }, "figures": { @@ -4158,7 +4169,7 @@ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -4167,7 +4178,7 @@ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "2.0.1" + "flat-cache": "^2.0.1" } }, "fill-range": { @@ -4176,10 +4187,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "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": { @@ -4188,7 +4199,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -4199,9 +4210,9 @@ "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "3.0.2", - "pkg-dir": "4.2.0" + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" }, "dependencies": { "find-up": { @@ -4210,8 +4221,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "locate-path": { @@ -4220,7 +4231,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -4229,7 +4240,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -4238,7 +4249,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-try": { @@ -4259,7 +4270,7 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "4.1.0" + "find-up": "^4.0.0" } } } @@ -4270,7 +4281,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "find-versions": { @@ -4279,7 +4290,7 @@ "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", "dev": true, "requires": { - "semver-regex": "2.0.0" + "semver-regex": "^2.0.0" } }, "flat": { @@ -4288,7 +4299,7 @@ "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", "dev": true, "requires": { - "is-buffer": "2.0.4" + "is-buffer": "~2.0.3" }, "dependencies": { "is-buffer": { @@ -4305,7 +4316,7 @@ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "flatted": "2.0.2", + "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" }, @@ -4316,7 +4327,7 @@ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } } } @@ -4333,8 +4344,8 @@ "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, "requires": { - "inherits": "2.0.4", - "readable-stream": "2.3.7" + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, "for-in": { @@ -4349,8 +4360,8 @@ "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { - "cross-spawn": "7.0.1", - "signal-exit": "3.0.3" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" }, "dependencies": { "cross-spawn": { @@ -4359,9 +4370,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "path-key": { @@ -4376,7 +4387,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -4391,7 +4402,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -4408,9 +4419,9 @@ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.8", - "mime-types": "2.1.26" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, "fragment-cache": { @@ -4419,7 +4430,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "0.2.2" + "map-cache": "^0.2.2" } }, "from2": { @@ -4428,8 +4439,8 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "2.0.4", - "readable-stream": "2.3.7" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, "fromentries": { @@ -4444,9 +4455,9 @@ "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "jsonfile": "4.0.0", - "universalify": "0.1.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs-jetpack": { @@ -4455,8 +4466,8 @@ "integrity": "sha512-MldfoKMz2NwpvP3UFfVXLp4NCncy9yxGamgBK6hofFaisnWoGvgkAyTtKwcq++leztgZuM4ywrZEaUtiyVfWgA==", "dev": true, "requires": { - "minimatch": "3.0.4", - "rimraf": "2.7.1" + "minimatch": "^3.0.2", + "rimraf": "^2.6.3" } }, "fs-minipass": { @@ -4465,7 +4476,7 @@ "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, "requires": { - "minipass": "2.9.0" + "minipass": "^2.6.0" } }, "fs-mkdirp-stream": { @@ -4474,8 +4485,8 @@ "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "through2": "2.0.5" + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" }, "dependencies": { "through2": { @@ -4484,8 +4495,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -4514,14 +4525,14 @@ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.3", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.3" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" }, "dependencies": { "ansi-regex": { @@ -4536,7 +4547,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { @@ -4545,9 +4556,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": { @@ -4556,7 +4567,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -4567,7 +4578,7 @@ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { - "is-property": "1.0.2" + "is-property": "^1.0.2" } }, "gensync": { @@ -4606,7 +4617,7 @@ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "get-value": { @@ -4621,7 +4632,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "git-log-parser": { @@ -4630,12 +4641,12 @@ "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", "dev": true, "requires": { - "argv-formatter": "1.0.0", - "spawn-error-forwarder": "1.0.0", - "split2": "1.0.0", - "stream-combiner2": "1.1.1", - "through2": "2.0.5", - "traverse": "0.6.6" + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" }, "dependencies": { "split2": { @@ -4644,7 +4655,7 @@ "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", "dev": true, "requires": { - "through2": "2.0.5" + "through2": "~2.0.0" } }, "through2": { @@ -4653,8 +4664,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -4665,11 +4676,11 @@ "integrity": "sha512-SoSsFL5lnixVzctGEi2uykjA7B5I0AhO9x6kdzvGRHbxsa6JSEgrgy1esRKsfOKE1cgyOJ/KDR2Trxu157sb8w==", "dev": true, "requires": { - "dargs": "4.1.0", - "lodash.template": "4.5.0", - "meow": "5.0.0", - "split2": "2.2.0", - "through2": "3.0.1" + "dargs": "^4.0.1", + "lodash.template": "^4.0.2", + "meow": "^5.0.0", + "split2": "^2.0.0", + "through2": "^3.0.0" } }, "glob": { @@ -4678,12 +4689,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "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" } }, "glob-parent": { @@ -4692,7 +4703,7 @@ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { - "is-glob": "4.0.1" + "is-glob": "^4.0.1" } }, "glob-stream": { @@ -4701,16 +4712,16 @@ "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", "dev": true, "requires": { - "extend": "3.0.2", - "glob": "7.1.6", - "glob-parent": "3.1.0", - "is-negated-glob": "1.0.0", - "ordered-read-streams": "1.0.1", - "pumpify": "1.5.1", - "readable-stream": "2.3.7", - "remove-trailing-separator": "1.1.0", - "to-absolute-glob": "2.0.2", - "unique-stream": "2.3.1" + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" }, "dependencies": { "glob-parent": { @@ -4719,8 +4730,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" } }, "is-glob": { @@ -4729,7 +4740,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -4740,7 +4751,7 @@ "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { - "ini": "1.3.5" + "ini": "^1.3.4" } }, "globals": { @@ -4773,11 +4784,11 @@ "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "dev": true, "requires": { - "minimist": "1.2.5", - "neo-async": "2.6.1", - "source-map": "0.6.1", - "uglify-js": "3.8.1", - "wordwrap": "1.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" }, "dependencies": { "source-map": { @@ -4800,8 +4811,8 @@ "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "ajv": "6.12.0", - "har-schema": "2.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" } }, "has": { @@ -4810,7 +4821,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -4819,7 +4830,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -4854,9 +4865,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "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": { @@ -4865,8 +4876,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "kind-of": { @@ -4875,7 +4886,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -4886,8 +4897,8 @@ "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", "dev": true, "requires": { - "is-stream": "2.0.0", - "type-fest": "0.8.1" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, "dependencies": { "is-stream": { @@ -4928,12 +4939,12 @@ "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", "dev": true, "requires": { - "domelementtype": "1.3.1", - "domhandler": "2.4.2", - "domutils": "1.5.1", - "entities": "1.1.2", - "inherits": "2.0.4", - "readable-stream": "3.6.0" + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" }, "dependencies": { "readable-stream": { @@ -4942,9 +4953,9 @@ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "inherits": "2.0.4", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } } } @@ -4955,9 +4966,9 @@ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "@tootallnate/once": "1.0.0", - "agent-base": "6.0.0", - "debug": "4.1.1" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" } }, "http-signature": { @@ -4966,9 +4977,9 @@ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.16.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-proxy-agent": { @@ -4977,8 +4988,8 @@ "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "dev": true, "requires": { - "agent-base": "5.1.1", - "debug": "4.1.1" + "agent-base": "5", + "debug": "4" }, "dependencies": { "agent-base": { @@ -5001,16 +5012,16 @@ "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", "dev": true, "requires": { - "chalk": "3.0.0", - "ci-info": "2.0.0", - "compare-versions": "3.6.0", - "cosmiconfig": "6.0.0", - "find-versions": "3.2.0", - "opencollective-postinstall": "2.0.2", - "pkg-dir": "4.2.0", - "please-upgrade-node": "3.2.0", - "slash": "3.0.0", - "which-pm-runs": "1.0.0" + "chalk": "^3.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.5.1", + "cosmiconfig": "^6.0.0", + "find-versions": "^3.2.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" }, "dependencies": { "ansi-styles": { @@ -5019,8 +5030,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "1.1.1", - "color-convert": "2.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "chalk": { @@ -5029,8 +5040,8 @@ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "4.2.1", - "supports-color": "7.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "color-convert": { @@ -5039,7 +5050,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.4" + "color-name": "~1.1.4" } }, "color-name": { @@ -5054,11 +5065,11 @@ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@types/parse-json": "4.0.0", - "import-fresh": "3.2.1", - "parse-json": "5.0.0", - "path-type": "4.0.0", - "yaml": "1.8.3" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, "has-flag": { @@ -5073,10 +5084,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2", - "lines-and-columns": "1.1.6" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-type": { @@ -5091,7 +5102,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "4.0.0" + "has-flag": "^4.0.0" } } } @@ -5112,12 +5123,12 @@ "integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.1", - "entities": "1.1.2", - "htmlparser2": "3.8.3", - "jsdom": "7.2.2", - "lodash": "4.17.15" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "~3.8.1", + "jsdom": "^7.0.2", + "lodash": "^4.1.0" } }, "color-logger": { @@ -5132,7 +5143,7 @@ "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", "dev": true, "requires": { - "domelementtype": "1.3.1" + "domelementtype": "1" } }, "htmlparser2": { @@ -5141,11 +5152,11 @@ "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { - "domelementtype": "1.3.1", - "domhandler": "2.3.0", - "domutils": "1.5.1", - "entities": "1.0.0", - "readable-stream": "1.1.14" + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" }, "dependencies": { "entities": { @@ -5168,10 +5179,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -5188,7 +5199,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "ignore": { @@ -5203,7 +5214,7 @@ "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "import-fresh": { @@ -5212,8 +5223,8 @@ "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { - "parent-module": "1.0.1", - "resolve-from": "4.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "dependencies": { "resolve-from": { @@ -5230,7 +5241,7 @@ "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", "dev": true, "requires": { - "resolve-from": "5.0.0" + "resolve-from": "^5.0.0" } }, "imurmurhash": { @@ -5256,8 +5267,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -5278,19 +5289,19 @@ "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", "dev": true, "requires": { - "ansi-escapes": "4.3.1", - "chalk": "3.0.0", - "cli-cursor": "3.1.0", - "cli-width": "2.2.0", - "external-editor": "3.1.0", - "figures": "3.2.0", - "lodash": "4.17.15", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", "mute-stream": "0.0.8", - "run-async": "2.4.0", - "rxjs": "6.5.5", - "string-width": "4.2.0", - "strip-ansi": "6.0.0", - "through": "2.3.8" + "run-async": "^2.4.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" }, "dependencies": { "ansi-regex": { @@ -5305,8 +5316,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "1.1.1", - "color-convert": "2.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "chalk": { @@ -5315,8 +5326,8 @@ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "4.2.1", - "supports-color": "7.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "color-convert": { @@ -5325,7 +5336,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.4" + "color-name": "~1.1.4" } }, "color-name": { @@ -5352,9 +5363,9 @@ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "8.0.0", - "is-fullwidth-code-point": "3.0.0", - "strip-ansi": "6.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { @@ -5363,7 +5374,7 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "5.0.0" + "ansi-regex": "^5.0.0" } }, "supports-color": { @@ -5372,7 +5383,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "4.0.0" + "has-flag": "^4.0.0" } } } @@ -5383,8 +5394,8 @@ "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", "dev": true, "requires": { - "from2": "2.3.0", - "p-is-promise": "3.0.0" + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" }, "dependencies": { "p-is-promise": { @@ -5401,7 +5412,7 @@ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "1.4.0" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -5416,8 +5427,8 @@ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "is-relative": "1.0.0", - "is-windows": "1.0.2" + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" } }, "is-accessor-descriptor": { @@ -5426,7 +5437,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -5435,7 +5446,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5464,7 +5475,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -5473,7 +5484,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5490,9 +5501,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "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": { @@ -5539,7 +5550,7 @@ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-negated-glob": { @@ -5554,7 +5565,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -5563,7 +5574,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5580,7 +5591,7 @@ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", "dev": true, "requires": { - "symbol-observable": "1.2.0" + "symbol-observable": "^1.1.0" } }, "is-plain-obj": { @@ -5595,7 +5606,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "is-promise": { @@ -5616,7 +5627,7 @@ "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has": "1.0.3" + "has": "^1.0.3" } }, "is-regexp": { @@ -5631,7 +5642,7 @@ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "is-unc-path": "1.0.0" + "is-unc-path": "^1.0.0" } }, "is-stream": { @@ -5646,7 +5657,7 @@ "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "1.0.1" + "has-symbols": "^1.0.1" } }, "is-text-path": { @@ -5655,7 +5666,7 @@ "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", "dev": true, "requires": { - "text-extensions": "1.9.0" + "text-extensions": "^1.0.0" } }, "is-typedarray": { @@ -5670,7 +5681,7 @@ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "unc-path-regex": "0.1.2" + "unc-path-regex": "^0.1.2" } }, "is-utf8": { @@ -5721,11 +5732,11 @@ "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", "dev": true, "requires": { - "lodash.capitalize": "4.2.1", - "lodash.escaperegexp": "4.1.2", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.uniqby": "4.7.0" + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" } }, "istanbul-lib-coverage": { @@ -5740,7 +5751,7 @@ "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { - "append-transform": "2.0.0" + "append-transform": "^2.0.0" } }, "istanbul-lib-instrument": { @@ -5749,13 +5760,13 @@ "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", "dev": true, "requires": { - "@babel/core": "7.9.0", - "@babel/parser": "7.9.4", - "@babel/template": "7.8.6", - "@babel/traverse": "7.9.0", - "@istanbuljs/schema": "0.1.2", - "istanbul-lib-coverage": "3.0.0", - "semver": "6.3.0" + "@babel/core": "^7.7.5", + "@babel/parser": "^7.7.5", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" }, "dependencies": { "semver": { @@ -5772,13 +5783,13 @@ "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", "dev": true, "requires": { - "archy": "1.0.0", - "cross-spawn": "7.0.1", - "istanbul-lib-coverage": "3.0.0", - "make-dir": "3.0.2", - "p-map": "3.0.0", - "rimraf": "3.0.2", - "uuid": "3.4.0" + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" }, "dependencies": { "cross-spawn": { @@ -5787,9 +5798,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "p-map": { @@ -5798,7 +5809,7 @@ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "aggregate-error": "3.0.1" + "aggregate-error": "^3.0.0" } }, "path-key": { @@ -5813,7 +5824,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } }, "shebang-command": { @@ -5822,7 +5833,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -5837,7 +5848,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -5848,9 +5859,9 @@ "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "istanbul-lib-coverage": "3.0.0", - "make-dir": "3.0.2", - "supports-color": "7.1.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "dependencies": { "has-flag": { @@ -5865,7 +5876,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "4.0.0" + "has-flag": "^4.0.0" } } } @@ -5876,9 +5887,9 @@ "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "debug": "4.1.1", - "istanbul-lib-coverage": "3.0.0", - "source-map": "0.6.1" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -5895,8 +5906,8 @@ "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { - "html-escaper": "2.0.2", - "istanbul-lib-report": "3.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, "java-properties": { @@ -5923,8 +5934,8 @@ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.1" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -5946,21 +5957,21 @@ "dev": true, "optional": true, "requires": { - "abab": "1.0.4", - "acorn": "2.7.0", - "acorn-globals": "1.0.9", - "cssom": "0.3.8", - "cssstyle": "0.2.37", - "escodegen": "1.14.1", - "nwmatcher": "1.4.4", - "parse5": "1.5.1", - "request": "2.88.2", - "sax": "1.2.4", - "symbol-tree": "3.2.4", - "tough-cookie": "2.5.0", - "webidl-conversions": "2.0.1", - "whatwg-url-compat": "0.6.5", - "xml-name-validator": "2.0.1" + "abab": "^1.0.0", + "acorn": "^2.4.0", + "acorn-globals": "^1.0.4", + "cssom": ">= 0.3.0 < 0.4.0", + "cssstyle": ">= 0.2.29 < 0.3.0", + "escodegen": "^1.6.1", + "nwmatcher": ">= 1.3.7 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.55.0", + "sax": "^1.1.4", + "symbol-tree": ">= 3.1.0 < 4.0.0", + "tough-cookie": "^2.2.0", + "webidl-conversions": "^2.0.0", + "whatwg-url-compat": "~0.6.5", + "xml-name-validator": ">= 2.0.1 < 3.0.0" }, "dependencies": { "acorn": { @@ -6021,7 +6032,7 @@ "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", "dev": true, "requires": { - "minimist": "1.2.5" + "minimist": "^1.2.5" } }, "jsonc-parser": { @@ -6036,7 +6047,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.6" } }, "jsonparse": { @@ -6045,16 +6056,6 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -6081,7 +6082,7 @@ "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "jws": { @@ -6090,8 +6091,8 @@ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "dev": true, "requires": { - "jwa": "1.4.1", - "safe-buffer": "5.1.2" + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, "kind-of": { @@ -6106,7 +6107,7 @@ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.9" } }, "lazystream": { @@ -6115,7 +6116,7 @@ "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, "requires": { - "readable-stream": "2.3.7" + "readable-stream": "^2.0.5" } }, "lcid": { @@ -6124,7 +6125,7 @@ "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "2.0.0" + "invert-kv": "^2.0.0" } }, "lcov-result-merger": { @@ -6133,9 +6134,9 @@ "integrity": "sha512-vGXaMNGZRr4cYvW+xMVg+rg7qd5DX9SbGXl+0S3k85+gRZVK4K7UvxPWzKb/qiMwe+4bx3EOrW2o4mbdb1WnsA==", "dev": true, "requires": { - "through2": "2.0.5", - "vinyl": "2.2.0", - "vinyl-fs": "3.0.3" + "through2": "^2.0.3", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.2" }, "dependencies": { "through2": { @@ -6144,8 +6145,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -6156,7 +6157,7 @@ "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, "requires": { - "flush-write-stream": "1.1.1" + "flush-write-stream": "^1.0.2" } }, "levn": { @@ -6165,8 +6166,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" } }, "lines-and-columns": { @@ -6181,7 +6182,7 @@ "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", "dev": true, "requires": { - "uc.micro": "1.0.6" + "uc.micro": "^1.0.1" } }, "lint-staged": { @@ -6190,19 +6191,19 @@ "integrity": "sha512-wAeu/ePaBAOfwM2+cVbgPWDtn17B0Sxiv0NvNEqDAIvB8Yhvl60vafKFiK4grcYn87K1iK+a0zVoETvKbdT9/Q==", "dev": true, "requires": { - "chalk": "3.0.0", - "commander": "4.1.1", - "cosmiconfig": "6.0.0", - "debug": "4.1.1", - "dedent": "0.7.0", - "execa": "3.4.0", - "listr": "0.14.3", - "log-symbols": "3.0.0", - "micromatch": "4.0.2", - "normalize-path": "3.0.0", - "please-upgrade-node": "3.2.0", + "chalk": "^3.0.0", + "commander": "^4.0.1", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "execa": "^3.4.0", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", "string-argv": "0.3.1", - "stringify-object": "3.3.0" + "stringify-object": "^3.3.0" }, "dependencies": { "ansi-styles": { @@ -6211,8 +6212,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "1.1.1", - "color-convert": "2.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "braces": { @@ -6221,7 +6222,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "7.0.1" + "fill-range": "^7.0.1" } }, "chalk": { @@ -6230,8 +6231,8 @@ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "4.2.1", - "supports-color": "7.1.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "color-convert": { @@ -6240,7 +6241,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.4" + "color-name": "~1.1.4" } }, "color-name": { @@ -6261,11 +6262,11 @@ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@types/parse-json": "4.0.0", - "import-fresh": "3.2.1", - "parse-json": "5.0.0", - "path-type": "4.0.0", - "yaml": "1.8.3" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, "cross-spawn": { @@ -6274,9 +6275,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "execa": { @@ -6285,16 +6286,16 @@ "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", "dev": true, "requires": { - "cross-spawn": "7.0.1", - "get-stream": "5.1.0", - "human-signals": "1.1.1", - "is-stream": "2.0.0", - "merge-stream": "2.0.0", - "npm-run-path": "4.0.1", - "onetime": "5.1.0", - "p-finally": "2.0.1", - "signal-exit": "3.0.3", - "strip-final-newline": "2.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, "fill-range": { @@ -6303,7 +6304,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "5.0.1" + "to-regex-range": "^5.0.1" } }, "get-stream": { @@ -6312,7 +6313,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "has-flag": { @@ -6339,7 +6340,7 @@ "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "chalk": "2.4.2" + "chalk": "^2.4.2" }, "dependencies": { "ansi-styles": { @@ -6348,7 +6349,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.3" + "color-convert": "^1.9.0" } }, "chalk": { @@ -6357,9 +6358,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" } }, "color-convert": { @@ -6389,7 +6390,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -6400,8 +6401,8 @@ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "braces": "3.0.2", - "picomatch": "2.2.2" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "normalize-path": { @@ -6416,7 +6417,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "3.1.1" + "path-key": "^3.0.0" } }, "p-finally": { @@ -6431,10 +6432,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2", - "lines-and-columns": "1.1.6" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-key": { @@ -6455,7 +6456,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -6470,7 +6471,7 @@ "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "4.0.0" + "has-flag": "^4.0.0" } }, "to-regex-range": { @@ -6479,7 +6480,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "7.0.0" + "is-number": "^7.0.0" } }, "which": { @@ -6488,7 +6489,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -6499,15 +6500,23 @@ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "requires": { - "@samverschueren/stream-to-observable": "0.3.0", - "is-observable": "1.1.0", - "is-promise": "2.1.0", - "is-stream": "1.1.0", - "listr-silent-renderer": "1.1.1", - "listr-update-renderer": "0.5.0", - "listr-verbose-renderer": "0.5.0", - "p-map": "2.1.0", - "rxjs": "6.5.5" + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, "listr-silent-renderer": { @@ -6522,14 +6531,14 @@ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", "dev": true, "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "elegant-spinner": "1.0.1", - "figures": "1.7.0", - "indent-string": "3.2.0", - "log-symbols": "1.0.2", - "log-update": "2.3.0", - "strip-ansi": "3.0.1" + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" }, "dependencies": { "ansi-regex": { @@ -6550,11 +6559,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "figures": { @@ -6563,8 +6572,8 @@ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "log-symbols": { @@ -6573,7 +6582,7 @@ "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", "dev": true, "requires": { - "chalk": "1.1.3" + "chalk": "^1.0.0" } }, "strip-ansi": { @@ -6582,7 +6591,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -6599,10 +6608,10 @@ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", "dev": true, "requires": { - "chalk": "2.4.2", - "cli-cursor": "2.1.0", - "date-fns": "1.30.1", - "figures": "2.0.0" + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" }, "dependencies": { "cli-cursor": { @@ -6611,7 +6620,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "figures": { @@ -6620,7 +6629,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "mimic-fn": { @@ -6635,7 +6644,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "restore-cursor": { @@ -6644,8 +6653,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.3" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } } } @@ -6656,10 +6665,10 @@ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "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": { @@ -6668,8 +6677,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": { @@ -6815,8 +6824,8 @@ "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { - "lodash._reinterpolate": "3.0.0", - "lodash.templatesettings": "4.2.0" + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" } }, "lodash.templatesettings": { @@ -6825,7 +6834,7 @@ "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { - "lodash._reinterpolate": "3.0.0" + "lodash._reinterpolate": "^3.0.0" } }, "lodash.toarray": { @@ -6852,7 +6861,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.4.2" + "chalk": "^2.0.1" } }, "log-update": { @@ -6861,9 +6870,9 @@ "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", "dev": true, "requires": { - "ansi-escapes": "3.2.0", - "cli-cursor": "2.1.0", - "wrap-ansi": "3.0.1" + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" }, "dependencies": { "ansi-escapes": { @@ -6878,7 +6887,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "mimic-fn": { @@ -6893,7 +6902,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "restore-cursor": { @@ -6902,8 +6911,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.3" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "wrap-ansi": { @@ -6912,8 +6921,8 @@ "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" } } } @@ -6936,7 +6945,7 @@ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "loud-rejection": { @@ -6945,8 +6954,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.3" + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" } }, "lru-cache": { @@ -6955,7 +6964,7 @@ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "yallist": "3.1.1" + "yallist": "^3.0.2" } }, "macos-release": { @@ -6970,7 +6979,7 @@ "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { - "semver": "6.3.0" + "semver": "^6.0.0" }, "dependencies": { "semver": { @@ -6987,7 +6996,7 @@ "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { - "p-defer": "1.0.0" + "p-defer": "^1.0.0" } }, "map-cache": { @@ -7008,7 +7017,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "1.0.1" + "object-visit": "^1.0.0" } }, "mariadb": { @@ -7017,12 +7026,12 @@ "integrity": "sha512-suv+ygoiS+tQSKmxgzJsGV9R+USN8g6Ql+GuMo9k7alD6FxOT/lwebLHy63/7yPZfVtlyAitK1tPd7ZoFhN/Sg==", "dev": true, "requires": { - "@types/geojson": "7946.0.7", - "@types/node": "12.12.34", - "denque": "1.4.1", - "iconv-lite": "0.5.1", - "long": "4.0.0", - "moment-timezone": "0.5.28" + "@types/geojson": "^7946.0.7", + "@types/node": ">=8.0.0", + "denque": "^1.4.1", + "iconv-lite": "^0.5.1", + "long": "^4.0.0", + "moment-timezone": "^0.5.27" }, "dependencies": { "iconv-lite": { @@ -7031,7 +7040,7 @@ "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } } } @@ -7042,11 +7051,11 @@ "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", "dev": true, "requires": { - "argparse": "1.0.10", - "entities": "2.0.0", - "linkify-it": "2.2.0", - "mdurl": "1.0.1", - "uc.micro": "1.0.6" + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" }, "dependencies": { "entities": { @@ -7072,19 +7081,19 @@ "integrity": "sha512-gvnczz3W3Wgex851/cIQ/2y8GNhY+EVK8Ael8kRd8hoSQ0ps9xjhtwPwMyJPoiYbAoPxG6vSBFISiysaAbCEZg==", "dev": true, "requires": { - "commander": "2.9.0", - "deep-extend": "0.5.1", - "get-stdin": "5.0.1", - "glob": "7.1.6", - "ignore": "5.1.4", - "js-yaml": "3.13.1", - "jsonc-parser": "2.2.1", - "lodash.differencewith": "4.5.0", - "lodash.flatten": "4.4.0", - "markdownlint": "0.18.0", - "markdownlint-rule-helpers": "0.6.0", - "minimatch": "3.0.4", - "rc": "1.2.8" + "commander": "~2.9.0", + "deep-extend": "~0.5.1", + "get-stdin": "~5.0.1", + "glob": "~7.1.2", + "ignore": "~5.1.4", + "js-yaml": "~3.13.1", + "jsonc-parser": "~2.2.0", + "lodash.differencewith": "~4.5.0", + "lodash.flatten": "~4.4.0", + "markdownlint": "~0.18.0", + "markdownlint-rule-helpers": "~0.6.0", + "minimatch": "~3.0.4", + "rc": "~1.2.7" }, "dependencies": { "commander": { @@ -7093,7 +7102,7 @@ "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", "dev": true, "requires": { - "graceful-readlink": "1.0.1" + "graceful-readlink": ">= 1.0.0" } }, "get-stdin": { @@ -7128,12 +7137,12 @@ "integrity": "sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==", "dev": true, "requires": { - "ansi-escapes": "3.2.0", - "cardinal": "2.1.1", - "chalk": "2.4.2", - "cli-table": "0.3.1", - "node-emoji": "1.10.0", - "supports-hyperlinks": "1.0.1" + "ansi-escapes": "^3.1.0", + "cardinal": "^2.1.1", + "chalk": "^2.4.1", + "cli-table": "^0.3.1", + "node-emoji": "^1.4.1", + "supports-hyperlinks": "^1.0.1" }, "dependencies": { "ansi-escapes": { @@ -7156,9 +7165,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" } }, "meow": { @@ -7167,15 +7176,15 @@ "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", "dev": true, "requires": { - "camelcase-keys": "4.2.0", - "decamelize-keys": "1.1.0", - "loud-rejection": "1.6.0", - "minimist-options": "3.0.2", - "normalize-package-data": "2.5.0", - "read-pkg-up": "3.0.0", - "redent": "2.0.0", - "trim-newlines": "2.0.0", - "yargs-parser": "10.1.0" + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" } }, "merge-stream": { @@ -7196,19 +7205,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "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.3", - "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" } }, "mime": { @@ -7244,7 +7253,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -7259,8 +7268,8 @@ "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", "dev": true, "requires": { - "arrify": "1.0.1", - "is-plain-obj": "1.1.0" + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" } }, "minipass": { @@ -7269,8 +7278,8 @@ "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.1.1" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } }, "minizlib": { @@ -7279,7 +7288,7 @@ "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "dev": true, "requires": { - "minipass": "2.9.0" + "minipass": "^2.9.0" } }, "mixin-deep": { @@ -7288,8 +7297,8 @@ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -7298,7 +7307,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -7309,7 +7318,7 @@ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "1.2.5" + "minimist": "^1.2.5" } }, "mocha": { @@ -7361,9 +7370,9 @@ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "3.1.0", - "strip-ansi": "5.2.0", - "wrap-ansi": "5.1.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" } }, "debug": { @@ -7372,7 +7381,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.1.1" + "ms": "^2.1.1" } }, "emoji-regex": { @@ -7387,7 +7396,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "get-caller-file": { @@ -7402,12 +7411,12 @@ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "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" } }, "locate-path": { @@ -7416,8 +7425,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" } }, "mkdirp": { @@ -7426,7 +7435,7 @@ "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "dev": true, "requires": { - "minimist": "1.2.5" + "minimist": "^1.2.5" } }, "ms": { @@ -7441,7 +7450,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -7450,7 +7459,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.0.0" } }, "p-try": { @@ -7471,9 +7480,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": { @@ -7482,7 +7491,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } }, "supports-color": { @@ -7491,7 +7500,7 @@ "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "wrap-ansi": { @@ -7500,9 +7509,9 @@ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "string-width": "3.1.0", - "strip-ansi": "5.2.0" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" } }, "yargs": { @@ -7511,16 +7520,16 @@ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "5.0.0", - "find-up": "3.0.0", - "get-caller-file": "2.0.5", - "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.2" + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "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.1.2" } }, "yargs-parser": { @@ -7529,8 +7538,8 @@ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -7551,7 +7560,7 @@ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", "requires": { - "moment": "2.24.0" + "moment": ">= 2.9.0" } }, "ms": { @@ -7571,14 +7580,14 @@ "integrity": "sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==", "dev": true, "requires": { - "denque": "1.4.1", - "generate-function": "2.3.1", - "iconv-lite": "0.5.1", - "long": "4.0.0", - "lru-cache": "5.1.1", - "named-placeholders": "1.1.2", - "seq-queue": "0.0.5", - "sqlstring": "2.3.1" + "denque": "^1.4.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.5.0", + "long": "^4.0.0", + "lru-cache": "^5.1.1", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.1" }, "dependencies": { "iconv-lite": { @@ -7587,7 +7596,7 @@ "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } } } @@ -7598,7 +7607,7 @@ "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", "dev": true, "requires": { - "lru-cache": "4.1.5" + "lru-cache": "^4.1.3" }, "dependencies": { "lru-cache": { @@ -7607,8 +7616,8 @@ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "yallist": { @@ -7631,17 +7640,17 @@ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "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.3", - "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" } }, "native-duplexpair": { @@ -7662,9 +7671,9 @@ "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", "dev": true, "requires": { - "debug": "3.2.6", - "iconv-lite": "0.4.24", - "sax": "1.2.4" + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" }, "dependencies": { "debug": { @@ -7673,7 +7682,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.1" } } } @@ -7702,11 +7711,11 @@ "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", "dev": true, "requires": { - "@sinonjs/formatio": "3.2.2", - "@sinonjs/text-encoding": "0.7.1", - "just-extend": "4.1.0", - "lolex": "5.1.2", - "path-to-regexp": "1.8.0" + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" }, "dependencies": { "lolex": { @@ -7715,7 +7724,7 @@ "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", "dev": true, "requires": { - "@sinonjs/commons": "1.7.1" + "@sinonjs/commons": "^1.7.0" } } } @@ -7726,7 +7735,7 @@ "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", "dev": true, "requires": { - "lodash.toarray": "4.4.0" + "lodash.toarray": "^4.4.0" } }, "node-environment-flags": { @@ -7735,8 +7744,8 @@ "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", "dev": true, "requires": { - "object.getownpropertydescriptors": "2.1.0", - "semver": "5.7.1" + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" }, "dependencies": { "semver": { @@ -7759,16 +7768,16 @@ "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", "dev": true, "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.5", - "needle": "2.4.1", - "nopt": "4.0.3", - "npm-packlist": "1.4.8", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.7.1", - "semver": "5.7.1", - "tar": "4.4.13" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" }, "dependencies": { "semver": { @@ -7785,7 +7794,7 @@ "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { - "process-on-spawn": "1.0.0" + "process-on-spawn": "^1.0.0" } }, "nopt": { @@ -7794,8 +7803,8 @@ "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "dev": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "normalize-package-data": { @@ -7804,10 +7813,10 @@ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "hosted-git-info": "2.8.8", - "resolve": "1.15.1", - "semver": "5.7.1", - "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" }, "dependencies": { "semver": { @@ -7824,7 +7833,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "normalize-url": { @@ -7839,7 +7848,7 @@ "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.3.2" } }, "npm": { @@ -7848,131 +7857,140 @@ "integrity": "sha512-B8UDDbWvdkW6RgXFn8/h2cHJP/u/FPa4HWeGzW23aNEBARN3QPrRaHqPIZW2NSN3fW649gtgUDNZpaRs0zTMPw==", "dev": true, "requires": { - "abbrev": "1.1.1", - "ansicolors": "0.3.2", - "ansistyles": "0.1.3", - "aproba": "2.0.0", - "archy": "1.0.0", - "bin-links": "1.1.7", - "bluebird": "3.5.5", - "byte-size": "5.0.1", - "cacache": "12.0.3", - "call-limit": "1.1.1", - "chownr": "1.1.4", - "ci-info": "2.0.0", - "cli-columns": "3.1.2", - "cli-table3": "0.5.1", - "cmd-shim": "3.0.3", - "columnify": "1.5.4", - "config-chain": "1.1.12", - "debuglog": "1.0.1", - "detect-indent": "5.0.0", - "detect-newline": "2.1.0", - "dezalgo": "1.0.3", - "editor": "1.0.0", - "figgy-pudding": "3.5.1", - "find-npm-prefix": "1.0.2", - "fs-vacuum": "1.2.10", - "fs-write-stream-atomic": "1.0.10", - "gentle-fs": "2.3.0", - "glob": "7.1.6", - "graceful-fs": "4.2.3", - "has-unicode": "2.0.1", - "hosted-git-info": "2.8.8", - "iferr": "1.0.2", - "imurmurhash": "0.1.4", - "infer-owner": "1.0.4", - "inflight": "1.0.6", - "inherits": "2.0.4", - "ini": "1.3.5", - "init-package-json": "1.10.3", - "is-cidr": "3.0.0", - "json-parse-better-errors": "1.0.2", - "JSONStream": "1.3.5", - "lazy-property": "1.0.0", - "libcipm": "4.0.7", - "libnpm": "3.0.1", - "libnpmaccess": "3.0.2", - "libnpmhook": "5.0.3", - "libnpmorg": "1.0.1", - "libnpmsearch": "2.0.2", - "libnpmteam": "1.0.2", - "libnpx": "10.2.2", - "lock-verify": "2.1.0", - "lockfile": "1.0.4", - "lodash._baseindexof": "3.1.0", - "lodash._baseuniq": "4.6.0", - "lodash._bindcallback": "3.0.1", - "lodash._cacheindexof": "3.0.2", - "lodash._createcache": "3.1.2", - "lodash._getnative": "3.9.1", - "lodash.clonedeep": "4.5.0", - "lodash.restparam": "3.6.1", - "lodash.union": "4.6.0", - "lodash.uniq": "4.5.0", - "lodash.without": "4.4.0", - "lru-cache": "5.1.1", - "meant": "1.0.1", - "mississippi": "3.0.0", - "mkdirp": "0.5.4", - "move-concurrently": "1.0.1", - "node-gyp": "5.1.0", - "nopt": "4.0.1", - "normalize-package-data": "2.5.0", - "npm-audit-report": "1.3.2", - "npm-cache-filename": "1.0.2", - "npm-install-checks": "3.0.2", - "npm-lifecycle": "3.1.4", - "npm-package-arg": "6.1.1", - "npm-packlist": "1.4.8", - "npm-pick-manifest": "3.0.2", - "npm-profile": "4.0.4", - "npm-registry-fetch": "4.0.3", - "npm-user-validate": "1.0.0", - "npmlog": "4.1.2", - "once": "1.4.0", - "opener": "1.5.1", - "osenv": "0.1.5", - "pacote": "9.5.12", - "path-is-inside": "1.0.2", - "promise-inflight": "1.0.1", - "qrcode-terminal": "0.12.0", - "query-string": "6.8.2", - "qw": "1.0.1", - "read": "1.0.7", - "read-cmd-shim": "1.0.5", - "read-installed": "4.0.3", - "read-package-json": "2.1.1", - "read-package-tree": "5.3.1", - "readable-stream": "3.6.0", - "readdir-scoped-modules": "1.1.0", - "request": "2.88.0", - "retry": "0.12.0", - "rimraf": "2.7.1", - "safe-buffer": "5.1.2", - "semver": "5.7.1", - "sha": "3.0.0", - "slide": "1.1.6", - "sorted-object": "2.0.1", - "sorted-union-stream": "2.1.3", - "ssri": "6.0.1", - "stringify-package": "1.0.1", - "tar": "4.4.13", - "text-table": "0.2.0", - "tiny-relative-date": "1.3.0", + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "^2.0.0", + "archy": "~1.0.0", + "bin-links": "^1.1.7", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.3", + "call-limit": "^1.1.1", + "chownr": "^1.1.4", + "ci-info": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.1", + "cmd-shim": "^3.0.3", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.3.0", + "glob": "^7.1.6", + "graceful-fs": "^4.2.3", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.8", + "iferr": "^1.0.2", + "imurmurhash": "*", + "infer-owner": "^1.0.4", + "inflight": "~1.0.6", + "inherits": "^2.0.4", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^3.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^4.0.7", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "libnpx": "^10.2.2", + "lock-verify": "^2.1.0", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^5.1.1", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.4", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.1.0", + "nopt": "~4.0.1", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.2", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "^3.0.2", + "npm-lifecycle": "^3.1.4", + "npm-package-arg": "^6.1.1", + "npm-packlist": "^1.4.8", + "npm-pick-manifest": "^3.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.3", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^9.5.12", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.8.2", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "^1.0.5", + "read-installed": "~4.0.3", + "read-package-json": "^2.1.1", + "read-package-tree": "^5.3.1", + "readable-stream": "^3.6.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.7.1", + "safe-buffer": "^5.1.2", + "semver": "^5.7.1", + "sha": "^3.0.0", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.1", + "tar": "^4.4.13", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", "uid-number": "0.0.6", - "umask": "1.1.0", - "unique-filename": "1.1.1", - "unpipe": "1.0.0", - "update-notifier": "2.5.0", - "uuid": "3.3.3", - "validate-npm-package-license": "3.0.4", - "validate-npm-package-name": "3.0.0", - "which": "1.3.1", - "worker-farm": "1.7.0", - "write-file-atomic": "2.4.3" + "umask": "~1.1.0", + "unique-filename": "^1.1.1", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.3", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.7.0", + "write-file-atomic": "^2.4.3" }, "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "abbrev": { "version": "1.1.1", "bundled": true, @@ -7983,7 +8001,7 @@ "bundled": true, "dev": true, "requires": { - "es6-promisify": "5.0.0" + "es6-promisify": "^5.0.0" } }, "agentkeepalive": { @@ -7991,7 +8009,7 @@ "bundled": true, "dev": true, "requires": { - "humanize-ms": "1.2.1" + "humanize-ms": "^1.2.1" } }, "ajv": { @@ -7999,10 +8017,10 @@ "bundled": true, "dev": true, "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ansi-align": { @@ -8010,7 +8028,7 @@ "bundled": true, "dev": true, "requires": { - "string-width": "2.1.1" + "string-width": "^2.0.0" } }, "ansi-regex": { @@ -8023,7 +8041,7 @@ "bundled": true, "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "ansicolors": { @@ -8051,8 +8069,8 @@ "bundled": true, "dev": true, "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" }, "dependencies": { "readable-stream": { @@ -8060,13 +8078,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -8074,7 +8092,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -8089,7 +8107,7 @@ "bundled": true, "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": "~2.1.0" } }, "assert-plus": { @@ -8123,7 +8141,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bin-links": { @@ -8131,12 +8149,12 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "3.5.5", - "cmd-shim": "3.0.3", - "gentle-fs": "2.3.0", - "graceful-fs": "4.2.3", - "npm-normalize-package-bin": "1.0.1", - "write-file-atomic": "2.4.3" + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" } }, "bluebird": { @@ -8149,13 +8167,13 @@ "bundled": true, "dev": true, "requires": { - "ansi-align": "2.0.0", - "camelcase": "4.1.0", - "chalk": "2.4.1", - "cli-boxes": "1.0.0", - "string-width": "2.1.1", - "term-size": "1.2.0", - "widest-line": "2.0.1" + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" } }, "brace-expansion": { @@ -8163,7 +8181,7 @@ "bundled": true, "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -8192,21 +8210,21 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "3.5.5", - "chownr": "1.1.4", - "figgy-pudding": "3.5.1", - "glob": "7.1.6", - "graceful-fs": "4.2.3", - "infer-owner": "1.0.4", - "lru-cache": "5.1.1", - "mississippi": "3.0.0", - "mkdirp": "0.5.4", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.7.1", - "ssri": "6.0.1", - "unique-filename": "1.1.1", - "y18n": "4.0.0" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, "call-limit": { @@ -8234,9 +8252,9 @@ "bundled": true, "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chownr": { @@ -8254,7 +8272,7 @@ "bundled": true, "dev": true, "requires": { - "ip-regex": "2.1.0" + "ip-regex": "^2.1.0" } }, "cli-boxes": { @@ -8267,8 +8285,8 @@ "bundled": true, "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "3.0.1" + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" } }, "cli-table3": { @@ -8276,9 +8294,9 @@ "bundled": true, "dev": true, "requires": { - "colors": "1.3.3", - "object-assign": "4.1.1", - "string-width": "2.1.1" + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" } }, "cliui": { @@ -8286,9 +8304,9 @@ "bundled": true, "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" }, "dependencies": { "ansi-regex": { @@ -8301,7 +8319,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -8316,8 +8334,8 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "4.2.3", - "mkdirp": "0.5.4" + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" } }, "co": { @@ -8335,7 +8353,7 @@ "bundled": true, "dev": true, "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -8354,8 +8372,8 @@ "bundled": true, "dev": true, "requires": { - "strip-ansi": "3.0.1", - "wcwidth": "1.0.1" + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" } }, "combined-stream": { @@ -8363,7 +8381,7 @@ "bundled": true, "dev": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "concat-map": { @@ -8376,10 +8394,10 @@ "bundled": true, "dev": true, "requires": { - "buffer-from": "1.0.0", - "inherits": "2.0.4", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" }, "dependencies": { "readable-stream": { @@ -8387,13 +8405,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -8401,7 +8419,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -8411,8 +8429,8 @@ "bundled": true, "dev": true, "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, "configstore": { @@ -8420,12 +8438,12 @@ "bundled": true, "dev": true, "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.2.3", - "make-dir": "1.3.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.4.3", - "xdg-basedir": "3.0.0" + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "console-control-strings": { @@ -8438,12 +8456,12 @@ "bundled": true, "dev": true, "requires": { - "aproba": "1.2.0", - "fs-write-stream-atomic": "1.0.10", - "iferr": "0.1.5", - "mkdirp": "0.5.4", - "rimraf": "2.7.1", - "run-queue": "1.0.3" + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" }, "dependencies": { "aproba": { @@ -8468,7 +8486,7 @@ "bundled": true, "dev": true, "requires": { - "capture-stack-trace": "1.0.0" + "capture-stack-trace": "^1.0.0" } }, "cross-spawn": { @@ -8476,9 +8494,9 @@ "bundled": true, "dev": true, "requires": { - "lru-cache": "4.1.5", - "shebang-command": "1.2.0", - "which": "1.3.1" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "dependencies": { "lru-cache": { @@ -8486,8 +8504,8 @@ "bundled": true, "dev": true, "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "yallist": { @@ -8512,7 +8530,7 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "debug": { @@ -8555,7 +8573,7 @@ "bundled": true, "dev": true, "requires": { - "clone": "1.0.4" + "clone": "^1.0.2" } }, "define-properties": { @@ -8563,7 +8581,7 @@ "bundled": true, "dev": true, "requires": { - "object-keys": "1.0.12" + "object-keys": "^1.0.12" } }, "delayed-stream": { @@ -8591,8 +8609,8 @@ "bundled": true, "dev": true, "requires": { - "asap": "2.0.6", - "wrappy": "1.0.2" + "asap": "^2.0.0", + "wrappy": "1" } }, "dot-prop": { @@ -8600,7 +8618,7 @@ "bundled": true, "dev": true, "requires": { - "is-obj": "1.0.1" + "is-obj": "^1.0.0" } }, "dotenv": { @@ -8618,10 +8636,10 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.4", - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" }, "dependencies": { "readable-stream": { @@ -8629,13 +8647,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -8643,7 +8661,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -8654,8 +8672,8 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1", - "safer-buffer": "2.1.2" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "editor": { @@ -8668,7 +8686,7 @@ "bundled": true, "dev": true, "requires": { - "iconv-lite": "0.4.23" + "iconv-lite": "~0.4.13" } }, "end-of-stream": { @@ -8676,7 +8694,7 @@ "bundled": true, "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "env-paths": { @@ -8694,7 +8712,7 @@ "bundled": true, "dev": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" } }, "es-abstract": { @@ -8702,11 +8720,11 @@ "bundled": true, "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" + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" } }, "es-to-primitive": { @@ -8714,9 +8732,9 @@ "bundled": true, "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" } }, "es6-promise": { @@ -8729,7 +8747,7 @@ "bundled": true, "dev": true, "requires": { - "es6-promise": "4.2.8" + "es6-promise": "^4.0.3" } }, "escape-string-regexp": { @@ -8742,13 +8760,13 @@ "bundled": true, "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.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": "^5.0.1", + "get-stream": "^3.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" }, "dependencies": { "get-stream": { @@ -8793,7 +8811,7 @@ "bundled": true, "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "flush-write-stream": { @@ -8801,8 +8819,8 @@ "bundled": true, "dev": true, "requires": { - "inherits": "2.0.4", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" }, "dependencies": { "readable-stream": { @@ -8810,13 +8828,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -8824,7 +8842,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -8839,9 +8857,9 @@ "bundled": true, "dev": true, "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.19" + "mime-types": "^2.1.12" } }, "from2": { @@ -8849,8 +8867,8 @@ "bundled": true, "dev": true, "requires": { - "inherits": "2.0.4", - "readable-stream": "2.3.6" + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" }, "dependencies": { "readable-stream": { @@ -8858,13 +8876,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -8872,7 +8890,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -8882,7 +8900,7 @@ "bundled": true, "dev": true, "requires": { - "minipass": "2.9.0" + "minipass": "^2.6.0" }, "dependencies": { "minipass": { @@ -8890,8 +8908,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.3" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } } } @@ -8901,9 +8919,9 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "4.2.3", - "path-is-inside": "1.0.2", - "rimraf": "2.7.1" + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" } }, "fs-write-stream-atomic": { @@ -8911,10 +8929,10 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "4.2.3", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.6" + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" }, "dependencies": { "iferr": { @@ -8927,13 +8945,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -8941,7 +8959,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -8961,14 +8979,14 @@ "bundled": true, "dev": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" }, "dependencies": { "aproba": { @@ -8981,9 +8999,9 @@ "bundled": true, "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" } } } @@ -8998,17 +9016,17 @@ "bundled": true, "dev": true, "requires": { - "aproba": "1.2.0", - "chownr": "1.1.4", - "cmd-shim": "3.0.3", - "fs-vacuum": "1.2.10", - "graceful-fs": "4.2.3", - "iferr": "0.1.5", - "infer-owner": "1.0.4", - "mkdirp": "0.5.4", - "path-is-inside": "1.0.2", - "read-cmd-shim": "1.0.5", - "slide": "1.1.6" + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" }, "dependencies": { "aproba": { @@ -9033,7 +9051,7 @@ "bundled": true, "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "getpass": { @@ -9041,7 +9059,7 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { @@ -9049,12 +9067,12 @@ "bundled": true, "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.4", - "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-dirs": { @@ -9062,7 +9080,7 @@ "bundled": true, "dev": true, "requires": { - "ini": "1.3.5" + "ini": "^1.3.4" } }, "got": { @@ -9070,17 +9088,17 @@ "bundled": true, "dev": true, "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.2.0", - "is-stream": "1.1.0", - "lowercase-keys": "1.0.1", - "safe-buffer": "5.1.2", - "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" }, "dependencies": { "get-stream": { @@ -9105,8 +9123,8 @@ "bundled": true, "dev": true, "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.3.0", + "har-schema": "^2.0.0" } }, "has": { @@ -9114,7 +9132,7 @@ "bundled": true, "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-flag": { @@ -9147,7 +9165,7 @@ "bundled": true, "dev": true, "requires": { - "agent-base": "4.3.0", + "agent-base": "4", "debug": "3.1.0" } }, @@ -9156,9 +9174,9 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.2" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-proxy-agent": { @@ -9166,8 +9184,8 @@ "bundled": true, "dev": true, "requires": { - "agent-base": "4.3.0", - "debug": "3.1.0" + "agent-base": "^4.3.0", + "debug": "^3.1.0" } }, "humanize-ms": { @@ -9175,7 +9193,7 @@ "bundled": true, "dev": true, "requires": { - "ms": "2.1.1" + "ms": "^2.0.0" } }, "iconv-lite": { @@ -9183,7 +9201,7 @@ "bundled": true, "dev": true, "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "iferr": { @@ -9196,7 +9214,7 @@ "bundled": true, "dev": true, "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "import-lazy": { @@ -9219,8 +9237,8 @@ "bundled": true, "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -9238,14 +9256,14 @@ "bundled": true, "dev": true, "requires": { - "glob": "7.1.6", - "npm-package-arg": "6.1.1", - "promzard": "0.3.0", - "read": "1.0.7", - "read-package-json": "2.1.1", - "semver": "5.7.1", - "validate-npm-package-license": "3.0.4", - "validate-npm-package-name": "3.0.0" + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" } }, "invert-kv": { @@ -9273,7 +9291,7 @@ "bundled": true, "dev": true, "requires": { - "ci-info": "1.6.0" + "ci-info": "^1.5.0" }, "dependencies": { "ci-info": { @@ -9288,7 +9306,7 @@ "bundled": true, "dev": true, "requires": { - "cidr-regex": "2.0.10" + "cidr-regex": "^2.0.10" } }, "is-date-object": { @@ -9301,7 +9319,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-installed-globally": { @@ -9309,8 +9327,8 @@ "bundled": true, "dev": true, "requires": { - "global-dirs": "0.1.1", - "is-path-inside": "1.0.1" + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" } }, "is-npm": { @@ -9328,7 +9346,7 @@ "bundled": true, "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-redirect": { @@ -9341,7 +9359,7 @@ "bundled": true, "dev": true, "requires": { - "has": "1.0.3" + "has": "^1.0.1" } }, "is-retry-allowed": { @@ -9359,7 +9377,7 @@ "bundled": true, "dev": true, "requires": { - "has-symbols": "1.0.0" + "has-symbols": "^1.0.0" } }, "is-typedarray": { @@ -9413,15 +9431,6 @@ "bundled": true, "dev": true }, - "JSONStream": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "bundled": true, @@ -9438,7 +9447,7 @@ "bundled": true, "dev": true, "requires": { - "package-json": "4.0.1" + "package-json": "^4.0.0" } }, "lazy-property": { @@ -9451,7 +9460,7 @@ "bundled": true, "dev": true, "requires": { - "invert-kv": "2.0.0" + "invert-kv": "^2.0.0" } }, "libcipm": { @@ -9459,21 +9468,21 @@ "bundled": true, "dev": true, "requires": { - "bin-links": "1.1.7", - "bluebird": "3.5.5", - "figgy-pudding": "3.5.1", - "find-npm-prefix": "1.0.2", - "graceful-fs": "4.2.3", - "ini": "1.3.5", - "lock-verify": "2.1.0", - "mkdirp": "0.5.4", - "npm-lifecycle": "3.1.4", - "npm-logical-tree": "1.2.1", - "npm-package-arg": "6.1.1", - "pacote": "9.5.12", - "read-package-json": "2.1.1", - "rimraf": "2.7.1", - "worker-farm": "1.7.0" + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" } }, "libnpm": { @@ -9481,26 +9490,26 @@ "bundled": true, "dev": true, "requires": { - "bin-links": "1.1.7", - "bluebird": "3.5.5", - "find-npm-prefix": "1.0.2", - "libnpmaccess": "3.0.2", - "libnpmconfig": "1.2.1", - "libnpmhook": "5.0.3", - "libnpmorg": "1.0.1", - "libnpmpublish": "1.1.2", - "libnpmsearch": "2.0.2", - "libnpmteam": "1.0.2", - "lock-verify": "2.1.0", - "npm-lifecycle": "3.1.4", - "npm-logical-tree": "1.2.1", - "npm-package-arg": "6.1.1", - "npm-profile": "4.0.4", - "npm-registry-fetch": "4.0.3", - "npmlog": "4.1.2", - "pacote": "9.5.12", - "read-package-json": "2.1.1", - "stringify-package": "1.0.1" + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" } }, "libnpmaccess": { @@ -9508,10 +9517,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "2.0.0", - "get-stream": "4.1.0", - "npm-package-arg": "6.1.1", - "npm-registry-fetch": "4.0.3" + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" } }, "libnpmconfig": { @@ -9519,9 +9528,9 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "3.5.1", - "find-up": "3.0.0", - "ini": "1.3.5" + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" }, "dependencies": { "find-up": { @@ -9529,7 +9538,7 @@ "bundled": true, "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { @@ -9537,8 +9546,8 @@ "bundled": true, "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": { @@ -9546,7 +9555,7 @@ "bundled": true, "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -9554,7 +9563,7 @@ "bundled": true, "dev": true, "requires": { - "p-limit": "2.2.0" + "p-limit": "^2.0.0" } }, "p-try": { @@ -9569,10 +9578,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "2.0.0", - "figgy-pudding": "3.5.1", - "get-stream": "4.1.0", - "npm-registry-fetch": "4.0.3" + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" } }, "libnpmorg": { @@ -9580,10 +9589,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "2.0.0", - "figgy-pudding": "3.5.1", - "get-stream": "4.1.0", - "npm-registry-fetch": "4.0.3" + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" } }, "libnpmpublish": { @@ -9591,15 +9600,15 @@ "bundled": true, "dev": true, "requires": { - "aproba": "2.0.0", - "figgy-pudding": "3.5.1", - "get-stream": "4.1.0", - "lodash.clonedeep": "4.5.0", - "normalize-package-data": "2.5.0", - "npm-package-arg": "6.1.1", - "npm-registry-fetch": "4.0.3", - "semver": "5.7.1", - "ssri": "6.0.1" + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" } }, "libnpmsearch": { @@ -9607,9 +9616,9 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "3.5.1", - "get-stream": "4.1.0", - "npm-registry-fetch": "4.0.3" + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" } }, "libnpmteam": { @@ -9617,10 +9626,10 @@ "bundled": true, "dev": true, "requires": { - "aproba": "2.0.0", - "figgy-pudding": "3.5.1", - "get-stream": "4.1.0", - "npm-registry-fetch": "4.0.3" + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" } }, "libnpx": { @@ -9628,14 +9637,14 @@ "bundled": true, "dev": true, "requires": { - "dotenv": "5.0.1", - "npm-package-arg": "6.1.1", - "rimraf": "2.7.1", - "safe-buffer": "5.1.2", - "update-notifier": "2.5.0", - "which": "1.3.1", - "y18n": "4.0.0", - "yargs": "11.1.1" + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" } }, "locate-path": { @@ -9643,8 +9652,8 @@ "bundled": true, "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lock-verify": { @@ -9652,8 +9661,8 @@ "bundled": true, "dev": true, "requires": { - "npm-package-arg": "6.1.1", - "semver": "5.7.1" + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" } }, "lockfile": { @@ -9661,7 +9670,7 @@ "bundled": true, "dev": true, "requires": { - "signal-exit": "3.0.2" + "signal-exit": "^3.0.2" } }, "lodash._baseindexof": { @@ -9674,8 +9683,8 @@ "bundled": true, "dev": true, "requires": { - "lodash._createset": "4.0.3", - "lodash._root": "3.0.1" + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" } }, "lodash._bindcallback": { @@ -9693,7 +9702,7 @@ "bundled": true, "dev": true, "requires": { - "lodash._getnative": "3.9.1" + "lodash._getnative": "^3.0.0" } }, "lodash._createset": { @@ -9746,7 +9755,7 @@ "bundled": true, "dev": true, "requires": { - "yallist": "3.0.3" + "yallist": "^3.0.2" } }, "make-dir": { @@ -9754,7 +9763,7 @@ "bundled": true, "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "make-fetch-happen": { @@ -9762,17 +9771,17 @@ "bundled": true, "dev": true, "requires": { - "agentkeepalive": "3.5.2", - "cacache": "12.0.3", - "http-cache-semantics": "3.8.1", - "http-proxy-agent": "2.1.0", - "https-proxy-agent": "2.2.4", - "lru-cache": "5.1.1", - "mississippi": "3.0.0", - "node-fetch-npm": "2.0.2", - "promise-retry": "1.1.1", - "socks-proxy-agent": "4.0.2", - "ssri": "6.0.1" + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" } }, "map-age-cleaner": { @@ -9780,7 +9789,7 @@ "bundled": true, "dev": true, "requires": { - "p-defer": "1.0.0" + "p-defer": "^1.0.0" } }, "meant": { @@ -9793,9 +9802,9 @@ "bundled": true, "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": { @@ -9815,7 +9824,7 @@ "bundled": true, "dev": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "~1.35.0" } }, "minimatch": { @@ -9823,7 +9832,7 @@ "bundled": true, "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minizlib": { @@ -9831,7 +9840,7 @@ "bundled": true, "dev": true, "requires": { - "minipass": "2.9.0" + "minipass": "^2.9.0" }, "dependencies": { "minipass": { @@ -9839,8 +9848,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.3" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } } } @@ -9850,16 +9859,16 @@ "bundled": true, "dev": true, "requires": { - "concat-stream": "1.6.2", - "duplexify": "3.6.0", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "3.0.0", - "pumpify": "1.5.1", - "stream-each": "1.2.2", - "through2": "2.0.3" + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" } }, "mkdirp": { @@ -9867,7 +9876,7 @@ "bundled": true, "dev": true, "requires": { - "minimist": "1.2.5" + "minimist": "^1.2.5" }, "dependencies": { "minimist": { @@ -9882,12 +9891,12 @@ "bundled": true, "dev": true, "requires": { - "aproba": "1.2.0", - "copy-concurrently": "1.0.5", - "fs-write-stream-atomic": "1.0.10", - "mkdirp": "0.5.4", - "rimraf": "2.7.1", - "run-queue": "1.0.3" + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" }, "dependencies": { "aproba": { @@ -9917,9 +9926,9 @@ "bundled": true, "dev": true, "requires": { - "encoding": "0.1.12", - "json-parse-better-errors": "1.0.2", - "safe-buffer": "5.1.2" + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" } }, "node-gyp": { @@ -9927,17 +9936,17 @@ "bundled": true, "dev": true, "requires": { - "env-paths": "2.2.0", - "glob": "7.1.6", - "graceful-fs": "4.2.3", - "mkdirp": "0.5.4", - "nopt": "4.0.1", - "npmlog": "4.1.2", - "request": "2.88.0", - "rimraf": "2.7.1", - "semver": "5.7.1", - "tar": "4.4.13", - "which": "1.3.1" + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" } }, "nopt": { @@ -9945,8 +9954,8 @@ "bundled": true, "dev": true, "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "normalize-package-data": { @@ -9954,10 +9963,10 @@ "bundled": true, "dev": true, "requires": { - "hosted-git-info": "2.8.8", - "resolve": "1.10.0", - "semver": "5.7.1", - "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" }, "dependencies": { "resolve": { @@ -9965,7 +9974,7 @@ "bundled": true, "dev": true, "requires": { - "path-parse": "1.0.6" + "path-parse": "^1.0.6" } } } @@ -9975,8 +9984,8 @@ "bundled": true, "dev": true, "requires": { - "cli-table3": "0.5.1", - "console-control-strings": "1.1.0" + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" } }, "npm-bundled": { @@ -9984,7 +9993,7 @@ "bundled": true, "dev": true, "requires": { - "npm-normalize-package-bin": "1.0.1" + "npm-normalize-package-bin": "^1.0.1" } }, "npm-cache-filename": { @@ -9997,7 +10006,7 @@ "bundled": true, "dev": true, "requires": { - "semver": "5.7.1" + "semver": "^2.3.0 || 3.x || 4 || 5" } }, "npm-lifecycle": { @@ -10005,14 +10014,14 @@ "bundled": true, "dev": true, "requires": { - "byline": "5.0.0", - "graceful-fs": "4.2.3", - "node-gyp": "5.1.0", - "resolve-from": "4.0.0", - "slide": "1.1.6", + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", "uid-number": "0.0.6", - "umask": "1.1.0", - "which": "1.3.1" + "umask": "^1.1.0", + "which": "^1.3.1" } }, "npm-logical-tree": { @@ -10030,10 +10039,10 @@ "bundled": true, "dev": true, "requires": { - "hosted-git-info": "2.8.8", - "osenv": "0.1.5", - "semver": "5.7.1", - "validate-npm-package-name": "3.0.0" + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" } }, "npm-packlist": { @@ -10041,9 +10050,9 @@ "bundled": true, "dev": true, "requires": { - "ignore-walk": "3.0.3", - "npm-bundled": "1.1.1", - "npm-normalize-package-bin": "1.0.1" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-pick-manifest": { @@ -10051,9 +10060,9 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "3.5.1", - "npm-package-arg": "6.1.1", - "semver": "5.7.1" + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" } }, "npm-profile": { @@ -10061,9 +10070,9 @@ "bundled": true, "dev": true, "requires": { - "aproba": "2.0.0", - "figgy-pudding": "3.5.1", - "npm-registry-fetch": "4.0.3" + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" } }, "npm-registry-fetch": { @@ -10071,13 +10080,13 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "3.5.5", - "figgy-pudding": "3.5.1", - "JSONStream": "1.3.5", - "lru-cache": "5.1.1", - "make-fetch-happen": "5.0.2", - "npm-package-arg": "6.1.1", - "safe-buffer": "5.2.0" + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" }, "dependencies": { "safe-buffer": { @@ -10092,7 +10101,7 @@ "bundled": true, "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "npm-user-validate": { @@ -10105,10 +10114,10 @@ "bundled": true, "dev": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -10136,8 +10145,8 @@ "bundled": true, "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.12.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "once": { @@ -10145,7 +10154,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "opener": { @@ -10163,9 +10172,9 @@ "bundled": true, "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" }, "dependencies": { "cross-spawn": { @@ -10173,11 +10182,11 @@ "bundled": true, "dev": true, "requires": { - "nice-try": "1.0.5", - "path-key": "2.0.1", - "semver": "5.7.1", - "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" } }, "execa": { @@ -10185,13 +10194,13 @@ "bundled": true, "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" } } } @@ -10206,8 +10215,8 @@ "bundled": true, "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "p-defer": { @@ -10230,7 +10239,7 @@ "bundled": true, "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -10238,7 +10247,7 @@ "bundled": true, "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -10251,10 +10260,10 @@ "bundled": true, "dev": true, "requires": { - "got": "6.7.1", - "registry-auth-token": "3.4.0", - "registry-url": "3.1.0", - "semver": "5.7.1" + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" } }, "pacote": { @@ -10262,36 +10271,36 @@ "bundled": true, "dev": true, "requires": { - "bluebird": "3.5.5", - "cacache": "12.0.3", - "chownr": "1.1.4", - "figgy-pudding": "3.5.1", - "get-stream": "4.1.0", - "glob": "7.1.6", - "infer-owner": "1.0.4", - "lru-cache": "5.1.1", - "make-fetch-happen": "5.0.2", - "minimatch": "3.0.4", - "minipass": "2.9.0", - "mississippi": "3.0.0", - "mkdirp": "0.5.4", - "normalize-package-data": "2.5.0", - "npm-normalize-package-bin": "1.0.1", - "npm-package-arg": "6.1.1", - "npm-packlist": "1.4.8", - "npm-pick-manifest": "3.0.2", - "npm-registry-fetch": "4.0.3", - "osenv": "0.1.5", - "promise-inflight": "1.0.1", - "promise-retry": "1.1.1", - "protoduck": "5.0.1", - "rimraf": "2.7.1", - "safe-buffer": "5.1.2", - "semver": "5.7.1", - "ssri": "6.0.1", - "tar": "4.4.13", - "unique-filename": "1.1.1", - "which": "1.3.1" + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" }, "dependencies": { "minipass": { @@ -10299,8 +10308,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.3" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } } } @@ -10310,9 +10319,9 @@ "bundled": true, "dev": true, "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.4", - "readable-stream": "2.3.6" + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" }, "dependencies": { "readable-stream": { @@ -10320,13 +10329,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -10334,7 +10343,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -10394,8 +10403,8 @@ "bundled": true, "dev": true, "requires": { - "err-code": "1.1.2", - "retry": "0.10.1" + "err-code": "^1.0.0", + "retry": "^0.10.0" }, "dependencies": { "retry": { @@ -10410,7 +10419,7 @@ "bundled": true, "dev": true, "requires": { - "read": "1.0.7" + "read": "1" } }, "proto-list": { @@ -10423,7 +10432,7 @@ "bundled": true, "dev": true, "requires": { - "genfun": "5.0.0" + "genfun": "^5.0.0" } }, "prr": { @@ -10446,8 +10455,8 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "pumpify": { @@ -10455,9 +10464,9 @@ "bundled": true, "dev": true, "requires": { - "duplexify": "3.6.0", - "inherits": "2.0.4", - "pump": "2.0.1" + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" }, "dependencies": { "pump": { @@ -10465,8 +10474,8 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } @@ -10491,9 +10500,9 @@ "bundled": true, "dev": true, "requires": { - "decode-uri-component": "0.2.0", - "split-on-first": "1.1.0", - "strict-uri-encode": "2.0.0" + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" } }, "qw": { @@ -10506,10 +10515,10 @@ "bundled": true, "dev": true, "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.5", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "minimist": { @@ -10524,7 +10533,7 @@ "bundled": true, "dev": true, "requires": { - "mute-stream": "0.0.7" + "mute-stream": "~0.0.4" } }, "read-cmd-shim": { @@ -10532,7 +10541,7 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.2" } }, "read-installed": { @@ -10540,13 +10549,13 @@ "bundled": true, "dev": true, "requires": { - "debuglog": "1.0.1", - "graceful-fs": "4.2.3", - "read-package-json": "2.1.1", - "readdir-scoped-modules": "1.1.0", - "semver": "5.7.1", - "slide": "1.1.6", - "util-extend": "1.0.3" + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" } }, "read-package-json": { @@ -10554,11 +10563,11 @@ "bundled": true, "dev": true, "requires": { - "glob": "7.1.6", - "graceful-fs": "4.2.3", - "json-parse-better-errors": "1.0.2", - "normalize-package-data": "2.5.0", - "npm-normalize-package-bin": "1.0.1" + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" } }, "read-package-tree": { @@ -10566,9 +10575,9 @@ "bundled": true, "dev": true, "requires": { - "read-package-json": "2.1.1", - "readdir-scoped-modules": "1.1.0", - "util-promisify": "2.1.0" + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" } }, "readable-stream": { @@ -10576,9 +10585,9 @@ "bundled": true, "dev": true, "requires": { - "inherits": "2.0.4", - "string_decoder": "1.3.0", - "util-deprecate": "1.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "readdir-scoped-modules": { @@ -10586,10 +10595,10 @@ "bundled": true, "dev": true, "requires": { - "debuglog": "1.0.1", - "dezalgo": "1.0.3", - "graceful-fs": "4.2.3", - "once": "1.4.0" + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" } }, "registry-auth-token": { @@ -10597,8 +10606,8 @@ "bundled": true, "dev": true, "requires": { - "rc": "1.2.8", - "safe-buffer": "5.1.2" + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" } }, "registry-url": { @@ -10606,7 +10615,7 @@ "bundled": true, "dev": true, "requires": { - "rc": "1.2.8" + "rc": "^1.0.1" } }, "request": { @@ -10614,26 +10623,26 @@ "bundled": true, "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.8.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.1.0", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.9.0", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.4.3", - "tunnel-agent": "0.6.0", - "uuid": "3.3.3" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" } }, "require-directory": { @@ -10661,7 +10670,7 @@ "bundled": true, "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } }, "run-queue": { @@ -10669,7 +10678,7 @@ "bundled": true, "dev": true, "requires": { - "aproba": "1.2.0" + "aproba": "^1.1.1" }, "dependencies": { "aproba": { @@ -10699,7 +10708,7 @@ "bundled": true, "dev": true, "requires": { - "semver": "5.7.1" + "semver": "^5.0.3" } }, "set-blocking": { @@ -10712,7 +10721,7 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "4.2.3" + "graceful-fs": "^4.1.2" } }, "shebang-command": { @@ -10720,7 +10729,7 @@ "bundled": true, "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -10749,7 +10758,7 @@ "dev": true, "requires": { "ip": "1.1.5", - "smart-buffer": "4.1.0" + "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { @@ -10757,8 +10766,8 @@ "bundled": true, "dev": true, "requires": { - "agent-base": "4.2.1", - "socks": "2.3.3" + "agent-base": "~4.2.1", + "socks": "~2.3.2" }, "dependencies": { "agent-base": { @@ -10766,7 +10775,7 @@ "bundled": true, "dev": true, "requires": { - "es6-promisify": "5.0.0" + "es6-promisify": "^5.0.0" } } } @@ -10781,8 +10790,8 @@ "bundled": true, "dev": true, "requires": { - "from2": "1.3.0", - "stream-iterate": "1.2.0" + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" }, "dependencies": { "from2": { @@ -10790,8 +10799,8 @@ "bundled": true, "dev": true, "requires": { - "inherits": "2.0.4", - "readable-stream": "1.1.14" + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" } }, "isarray": { @@ -10804,10 +10813,10 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "string_decoder": { @@ -10822,8 +10831,8 @@ "bundled": true, "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.3" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -10836,8 +10845,8 @@ "bundled": true, "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.3" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -10855,15 +10864,15 @@ "bundled": true, "dev": true, "requires": { - "asn1": "0.2.4", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "ssri": { @@ -10871,7 +10880,7 @@ "bundled": true, "dev": true, "requires": { - "figgy-pudding": "3.5.1" + "figgy-pudding": "^3.5.1" } }, "stream-each": { @@ -10879,8 +10888,8 @@ "bundled": true, "dev": true, "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" } }, "stream-iterate": { @@ -10888,8 +10897,8 @@ "bundled": true, "dev": true, "requires": { - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" }, "dependencies": { "readable-stream": { @@ -10897,13 +10906,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -10911,7 +10920,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -10926,28 +10935,13 @@ "bundled": true, "dev": true }, - "string_decoder": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "bundled": true, - "dev": true - } - } - }, "string-width": { "version": "2.1.1", "bundled": true, "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" }, "dependencies": { "ansi-regex": { @@ -10965,11 +10959,26 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, "stringify-package": { "version": "1.0.1", "bundled": true, @@ -10980,7 +10989,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-eof": { @@ -10998,7 +11007,7 @@ "bundled": true, "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "tar": { @@ -11006,13 +11015,13 @@ "bundled": true, "dev": true, "requires": { - "chownr": "1.1.4", - "fs-minipass": "1.2.7", - "minipass": "2.9.0", - "minizlib": "1.3.3", - "mkdirp": "0.5.4", - "safe-buffer": "5.1.2", - "yallist": "3.0.3" + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" }, "dependencies": { "minipass": { @@ -11020,8 +11029,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.3" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" } } } @@ -11031,7 +11040,7 @@ "bundled": true, "dev": true, "requires": { - "execa": "0.7.0" + "execa": "^0.7.0" } }, "text-table": { @@ -11049,8 +11058,8 @@ "bundled": true, "dev": true, "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" }, "dependencies": { "readable-stream": { @@ -11058,13 +11067,13 @@ "bundled": true, "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "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": { @@ -11072,7 +11081,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -11092,8 +11101,8 @@ "bundled": true, "dev": true, "requires": { - "psl": "1.1.29", - "punycode": "1.4.1" + "psl": "^1.1.24", + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -11101,7 +11110,7 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -11130,7 +11139,7 @@ "bundled": true, "dev": true, "requires": { - "unique-slug": "2.0.0" + "unique-slug": "^2.0.0" } }, "unique-slug": { @@ -11138,7 +11147,7 @@ "bundled": true, "dev": true, "requires": { - "imurmurhash": "0.1.4" + "imurmurhash": "^0.1.4" } }, "unique-string": { @@ -11146,7 +11155,7 @@ "bundled": true, "dev": true, "requires": { - "crypto-random-string": "1.0.0" + "crypto-random-string": "^1.0.0" } }, "unpipe": { @@ -11164,16 +11173,16 @@ "bundled": true, "dev": true, "requires": { - "boxen": "1.3.0", - "chalk": "2.4.1", - "configstore": "3.1.2", - "import-lazy": "2.1.0", - "is-ci": "1.2.1", - "is-installed-globally": "0.1.0", - "is-npm": "1.0.0", - "latest-version": "3.1.0", - "semver-diff": "2.1.0", - "xdg-basedir": "3.0.0" + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "url-parse-lax": { @@ -11181,7 +11190,7 @@ "bundled": true, "dev": true, "requires": { - "prepend-http": "1.0.4" + "prepend-http": "^1.0.1" } }, "util-deprecate": { @@ -11199,7 +11208,7 @@ "bundled": true, "dev": true, "requires": { - "object.getownpropertydescriptors": "2.0.3" + "object.getownpropertydescriptors": "^2.0.3" } }, "uuid": { @@ -11212,8 +11221,8 @@ "bundled": true, "dev": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "validate-npm-package-name": { @@ -11221,7 +11230,7 @@ "bundled": true, "dev": true, "requires": { - "builtins": "1.0.3" + "builtins": "^1.0.3" } }, "verror": { @@ -11229,9 +11238,9 @@ "bundled": true, "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "wcwidth": { @@ -11239,7 +11248,7 @@ "bundled": true, "dev": true, "requires": { - "defaults": "1.0.3" + "defaults": "^1.0.3" } }, "which": { @@ -11247,7 +11256,7 @@ "bundled": true, "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -11260,7 +11269,7 @@ "bundled": true, "dev": true, "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" }, "dependencies": { "string-width": { @@ -11268,9 +11277,9 @@ "bundled": true, "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" } } } @@ -11280,7 +11289,7 @@ "bundled": true, "dev": true, "requires": { - "string-width": "2.1.1" + "string-width": "^2.1.1" } }, "worker-farm": { @@ -11288,7 +11297,7 @@ "bundled": true, "dev": true, "requires": { - "errno": "0.1.7" + "errno": "~0.1.7" } }, "wrap-ansi": { @@ -11296,8 +11305,8 @@ "bundled": true, "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "string-width": { @@ -11305,9 +11314,9 @@ "bundled": true, "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" } } } @@ -11322,9 +11331,9 @@ "bundled": true, "dev": true, "requires": { - "graceful-fs": "4.2.3", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" } }, "xdg-basedir": { @@ -11352,18 +11361,18 @@ "bundled": true, "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.3", - "os-locale": "3.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" }, "dependencies": { "y18n": { @@ -11378,7 +11387,7 @@ "bundled": true, "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } } } @@ -11389,7 +11398,7 @@ "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "dev": true, "requires": { - "npm-normalize-package-bin": "1.0.1" + "npm-normalize-package-bin": "^1.0.1" } }, "npm-normalize-package-bin": { @@ -11404,9 +11413,9 @@ "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { - "ignore-walk": "3.0.3", - "npm-bundled": "1.1.1", - "npm-normalize-package-bin": "1.0.1" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-run-path": { @@ -11415,7 +11424,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "npmlog": { @@ -11424,10 +11433,10 @@ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { - "are-we-there-yet": "1.1.5", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "nth-check": { @@ -11436,7 +11445,7 @@ "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "dev": true, "requires": { - "boolbase": "1.0.0" + "boolbase": "~1.0.0" } }, "number-is-nan": { @@ -11458,32 +11467,32 @@ "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==", "dev": true, "requires": { - "@istanbuljs/load-nyc-config": "1.0.0", - "@istanbuljs/schema": "0.1.2", - "caching-transform": "4.0.0", - "convert-source-map": "1.7.0", - "decamelize": "1.2.0", - "find-cache-dir": "3.3.1", - "find-up": "4.1.0", - "foreground-child": "2.0.0", - "glob": "7.1.6", - "istanbul-lib-coverage": "3.0.0", - "istanbul-lib-hook": "3.0.0", - "istanbul-lib-instrument": "4.0.1", - "istanbul-lib-processinfo": "2.0.2", - "istanbul-lib-report": "3.0.0", - "istanbul-lib-source-maps": "4.0.0", - "istanbul-reports": "3.0.2", - "make-dir": "3.0.2", - "node-preload": "0.2.1", - "p-map": "3.0.0", - "process-on-spawn": "1.0.0", - "resolve-from": "5.0.0", - "rimraf": "3.0.2", - "signal-exit": "3.0.3", - "spawn-wrap": "2.0.0", - "test-exclude": "6.0.0", - "yargs": "15.3.1" + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" }, "dependencies": { "ansi-regex": { @@ -11498,8 +11507,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "1.1.1", - "color-convert": "2.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "camelcase": { @@ -11514,9 +11523,9 @@ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "string-width": "4.2.0", - "strip-ansi": "6.0.0", - "wrap-ansi": "6.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, "color-convert": { @@ -11525,7 +11534,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.4" + "color-name": "~1.1.4" } }, "color-name": { @@ -11540,8 +11549,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "get-caller-file": { @@ -11562,7 +11571,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -11571,7 +11580,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -11580,7 +11589,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-map": { @@ -11589,7 +11598,7 @@ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "aggregate-error": "3.0.1" + "aggregate-error": "^3.0.0" } }, "p-try": { @@ -11616,7 +11625,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } }, "string-width": { @@ -11625,9 +11634,9 @@ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "8.0.0", - "is-fullwidth-code-point": "3.0.0", - "strip-ansi": "6.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { @@ -11636,7 +11645,7 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "5.0.0" + "ansi-regex": "^5.0.0" } }, "wrap-ansi": { @@ -11645,9 +11654,9 @@ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "ansi-styles": "4.2.1", - "string-width": "4.2.0", - "strip-ansi": "6.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "yargs": { @@ -11656,17 +11665,17 @@ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { - "cliui": "6.0.0", - "decamelize": "1.2.0", - "find-up": "4.1.0", - "get-caller-file": "2.0.5", - "require-directory": "2.1.1", - "require-main-filename": "2.0.0", - "set-blocking": "2.0.0", - "string-width": "4.2.0", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "18.1.2" + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" } }, "yargs-parser": { @@ -11675,8 +11684,8 @@ "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -11699,9 +11708,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "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": { @@ -11710,7 +11719,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "kind-of": { @@ -11719,7 +11728,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -11742,7 +11751,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.0" } }, "object.assign": { @@ -11751,10 +11760,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "1.1.3", - "function-bind": "1.1.1", - "has-symbols": "1.0.1", - "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.entries-ponyfill": { @@ -11769,8 +11778,8 @@ "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.17.5" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "object.pick": { @@ -11779,7 +11788,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "octokit-pagination-methods": { @@ -11794,7 +11803,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { @@ -11803,7 +11812,7 @@ "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "2.1.0" + "mimic-fn": "^2.1.0" } }, "opencollective-postinstall": { @@ -11818,12 +11827,12 @@ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "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", - "word-wrap": "1.2.3" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" } }, "ordered-read-streams": { @@ -11832,7 +11841,7 @@ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "dev": true, "requires": { - "readable-stream": "2.3.7" + "readable-stream": "^2.0.1" } }, "os-homedir": { @@ -11847,9 +11856,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-name": { @@ -11858,8 +11867,8 @@ "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", "dev": true, "requires": { - "macos-release": "2.3.0", - "windows-release": "3.2.0" + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" } }, "os-tmpdir": { @@ -11874,8 +11883,8 @@ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "p-defer": { @@ -11896,7 +11905,7 @@ "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", "dev": true, "requires": { - "p-map": "2.1.0" + "p-map": "^2.0.0" }, "dependencies": { "p-map": { @@ -11925,7 +11934,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -11934,14 +11943,17 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.3.0" + "p-limit": "^1.1.0" } }, "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } }, "p-props": { "version": "3.1.0", @@ -11949,7 +11961,7 @@ "integrity": "sha512-N9mPfJfnApIlyCmF6H2KKccOsW9a1um2aIZWh/dU6flpJ5uU3fYDPVYfjOPLTbLVnUn+lcTqtlxxCVylM3mYsw==", "dev": true, "requires": { - "p-map": "3.0.0" + "p-map": "^3.0.0" }, "dependencies": { "p-map": { @@ -11958,7 +11970,7 @@ "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "aggregate-error": "3.0.1" + "aggregate-error": "^3.0.0" } } } @@ -11975,8 +11987,8 @@ "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", "dev": true, "requires": { - "@types/retry": "0.12.0", - "retry": "0.12.0" + "@types/retry": "^0.12.0", + "retry": "^0.12.0" } }, "p-timeout": { @@ -11985,7 +11997,7 @@ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "dev": true, "requires": { - "p-finally": "1.0.0" + "p-finally": "^1.0.0" } }, "p-try": { @@ -12000,10 +12012,10 @@ "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "requires": { - "graceful-fs": "4.2.3", - "hasha": "5.2.0", - "lodash.flattendeep": "4.4.0", - "release-zalgo": "1.0.0" + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" } }, "packet-reader": { @@ -12018,7 +12030,7 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { - "callsites": "3.1.0" + "callsites": "^3.0.0" } }, "parse-json": { @@ -12027,8 +12039,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" } }, "parse5": { @@ -12037,7 +12049,7 @@ "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { - "@types/node": "12.12.34" + "@types/node": "*" } }, "parsimmon": { @@ -12105,7 +12117,7 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "pathval": { @@ -12129,10 +12141,10 @@ "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "0.1.3", - "pg-packet-stream": "1.1.0", - "pg-pool": "2.0.10", - "pg-types": "2.2.0", - "pgpass": "1.0.2", + "pg-packet-stream": "^1.1.0", + "pg-pool": "^2.0.10", + "pg-types": "^2.1.0", + "pgpass": "1.x", "semver": "4.3.2" }, "dependencies": { @@ -12156,7 +12168,7 @@ "integrity": "sha512-qpeTpdkguFgfdoidtfeTho1Q1zPVPbtMHgs8eQ+Aan05iLmIs3Z3oo5DOZRclPGoQ4i68I1kCtQSJSa7i0ZVYg==", "dev": true, "requires": { - "underscore": "1.10.2" + "underscore": "^1.7.0" } }, "pg-int8": { @@ -12184,10 +12196,10 @@ "dev": true, "requires": { "pg-int8": "1.0.1", - "postgres-array": "2.0.0", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.4", - "postgres-interval": "1.2.0" + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" } }, "pgpass": { @@ -12196,7 +12208,7 @@ "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", "dev": true, "requires": { - "split": "1.0.1" + "split": "^1.0.0" } }, "picomatch": { @@ -12217,8 +12229,8 @@ "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", "dev": true, "requires": { - "find-up": "2.1.0", - "load-json-file": "4.0.0" + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" } }, "pkg-dir": { @@ -12227,7 +12239,7 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "4.1.0" + "find-up": "^4.0.0" }, "dependencies": { "find-up": { @@ -12236,8 +12248,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "locate-path": { @@ -12246,7 +12258,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -12255,7 +12267,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -12264,7 +12276,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-try": { @@ -12287,7 +12299,7 @@ "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", "dev": true, "requires": { - "semver-compare": "1.0.0" + "semver-compare": "^1.0.0" } }, "posix-character-classes": { @@ -12320,7 +12332,7 @@ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", "dev": true, "requires": { - "xtend": "4.0.2" + "xtend": "^4.0.0" } }, "prelude-ls": { @@ -12341,7 +12353,7 @@ "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { - "fromentries": "1.2.0" + "fromentries": "^1.2.0" } }, "progress": { @@ -12368,8 +12380,8 @@ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { - "end-of-stream": "1.4.4", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "pumpify": { @@ -12378,9 +12390,9 @@ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "3.7.1", - "inherits": "2.0.4", - "pump": "2.0.1" + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" }, "dependencies": { "pump": { @@ -12389,8 +12401,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "1.4.4", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } @@ -12431,10 +12443,10 @@ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.5", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "deep-extend": { @@ -12451,9 +12463,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": { @@ -12462,8 +12474,8 @@ "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "3.0.0" + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } }, "readable-stream": { @@ -12472,13 +12484,13 @@ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.4", - "isarray": "1.0.0", - "process-nextick-args": "2.0.1", - "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" } }, "redent": { @@ -12487,8 +12499,8 @@ "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", "dev": true, "requires": { - "indent-string": "3.2.0", - "strip-indent": "2.0.0" + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" } }, "redeyed": { @@ -12497,7 +12509,7 @@ "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", "dev": true, "requires": { - "esprima": "4.0.1" + "esprima": "~4.0.0" } }, "regenerator-runtime": { @@ -12512,8 +12524,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, "regexpp": { @@ -12534,7 +12546,7 @@ "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", "dev": true, "requires": { - "rc": "1.2.8" + "rc": "^1.2.8" } }, "release-zalgo": { @@ -12543,7 +12555,7 @@ "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", "dev": true, "requires": { - "es6-error": "4.1.1" + "es6-error": "^4.0.1" } }, "remove-bom-buffer": { @@ -12552,8 +12564,8 @@ "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", "dev": true, "requires": { - "is-buffer": "1.1.6", - "is-utf8": "0.2.1" + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" } }, "remove-bom-stream": { @@ -12562,9 +12574,9 @@ "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, "requires": { - "remove-bom-buffer": "3.0.0", - "safe-buffer": "5.1.2", - "through2": "2.0.5" + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" }, "dependencies": { "through2": { @@ -12573,8 +12585,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -12603,7 +12615,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.1.0" + "is-finite": "^1.0.0" } }, "replace-ext": { @@ -12618,26 +12630,26 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.9.1", - "caseless": "0.12.0", - "combined-stream": "1.0.8", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.3.3", - "har-validator": "5.1.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.26", - "oauth-sign": "0.9.0", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.5.0", - "tunnel-agent": "0.6.0", - "uuid": "3.4.0" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" } }, "require-directory": { @@ -12658,7 +12670,7 @@ "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, "requires": { - "path-parse": "1.0.6" + "path-parse": "^1.0.6" } }, "resolve-from": { @@ -12673,7 +12685,7 @@ "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", "dev": true, "requires": { - "global-dirs": "0.1.1" + "global-dirs": "^0.1.1" } }, "resolve-options": { @@ -12682,7 +12694,7 @@ "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "dev": true, "requires": { - "value-or-function": "3.0.0" + "value-or-function": "^3.0.0" } }, "resolve-url": { @@ -12697,8 +12709,8 @@ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "5.1.0", - "signal-exit": "3.0.3" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" } }, "ret": { @@ -12718,7 +12730,7 @@ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", "requires": { - "any-promise": "1.3.0" + "any-promise": "^1.3.0" } }, "reusify": { @@ -12733,7 +12745,7 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } }, "run-async": { @@ -12742,7 +12754,7 @@ "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "run-parallel": { @@ -12757,7 +12769,7 @@ "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", "dev": true, "requires": { - "tslib": "1.11.1" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -12772,7 +12784,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "0.1.15" + "ret": "~0.1.10" } }, "safer-buffer": { @@ -12793,34 +12805,34 @@ "integrity": "sha512-qiYHTNStxUs0UUb45ImRIid0Z8HsXwMNbpZXLvABs725SrxtZBgfuemaABnHdKDg7KBsuQMlSdZENaYLvkMqUg==", "dev": true, "requires": { - "@semantic-release/commit-analyzer": "7.0.0", - "@semantic-release/error": "2.2.0", - "@semantic-release/github": "6.0.2", - "@semantic-release/npm": "6.0.0", - "@semantic-release/release-notes-generator": "7.3.5", - "aggregate-error": "3.0.1", - "cosmiconfig": "6.0.0", - "debug": "4.1.1", - "env-ci": "5.0.2", - "execa": "4.0.0", - "figures": "3.2.0", - "find-versions": "3.2.0", - "get-stream": "5.1.0", - "git-log-parser": "1.2.0", - "hook-std": "2.0.0", - "hosted-git-info": "3.0.4", - "lodash": "4.17.15", - "marked": "0.8.2", - "marked-terminal": "3.3.0", - "micromatch": "4.0.2", - "p-each-series": "2.1.0", - "p-reduce": "2.1.0", - "read-pkg-up": "7.0.1", - "resolve-from": "5.0.0", - "semver": "7.1.3", - "semver-diff": "3.1.1", - "signale": "1.4.0", - "yargs": "15.3.1" + "@semantic-release/commit-analyzer": "^7.0.0", + "@semantic-release/error": "^2.2.0", + "@semantic-release/github": "^6.0.0", + "@semantic-release/npm": "^6.0.0", + "@semantic-release/release-notes-generator": "^7.1.2", + "aggregate-error": "^3.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.0.0", + "env-ci": "^5.0.0", + "execa": "^4.0.0", + "figures": "^3.0.0", + "find-versions": "^3.0.0", + "get-stream": "^5.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^2.0.0", + "hosted-git-info": "^3.0.0", + "lodash": "^4.17.15", + "marked": "^0.8.0", + "marked-terminal": "^3.2.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "p-reduce": "^2.0.0", + "read-pkg-up": "^7.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.1.1", + "semver-diff": "^3.1.1", + "signale": "^1.2.1", + "yargs": "^15.0.1" }, "dependencies": { "ansi-regex": { @@ -12835,8 +12847,8 @@ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "@types/color-name": "1.1.1", - "color-convert": "2.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, "braces": { @@ -12845,7 +12857,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "7.0.1" + "fill-range": "^7.0.1" } }, "camelcase": { @@ -12860,9 +12872,9 @@ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "string-width": "4.2.0", - "strip-ansi": "6.0.0", - "wrap-ansi": "6.2.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, "color-convert": { @@ -12871,7 +12883,7 @@ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "color-name": "1.1.4" + "color-name": "~1.1.4" } }, "color-name": { @@ -12886,11 +12898,11 @@ "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "@types/parse-json": "4.0.0", - "import-fresh": "3.2.1", - "parse-json": "5.0.0", - "path-type": "4.0.0", - "yaml": "1.8.3" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, "cross-spawn": { @@ -12899,9 +12911,9 @@ "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", "dev": true, "requires": { - "path-key": "3.1.1", - "shebang-command": "2.0.0", - "which": "2.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "execa": { @@ -12910,15 +12922,15 @@ "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { - "cross-spawn": "7.0.1", - "get-stream": "5.1.0", - "human-signals": "1.1.1", - "is-stream": "2.0.0", - "merge-stream": "2.0.0", - "npm-run-path": "4.0.1", - "onetime": "5.1.0", - "signal-exit": "3.0.3", - "strip-final-newline": "2.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" } }, "fill-range": { @@ -12927,7 +12939,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "5.0.1" + "to-regex-range": "^5.0.1" } }, "find-up": { @@ -12936,8 +12948,8 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "5.0.0", - "path-exists": "4.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "get-caller-file": { @@ -12952,7 +12964,7 @@ "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", "dev": true, "requires": { - "pump": "3.0.0" + "pump": "^3.0.0" } }, "hosted-git-info": { @@ -12961,7 +12973,7 @@ "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", "dev": true, "requires": { - "lru-cache": "5.1.1" + "lru-cache": "^5.1.1" } }, "is-fullwidth-code-point": { @@ -12988,7 +13000,7 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "4.1.0" + "p-locate": "^4.1.0" } }, "marked": { @@ -13003,8 +13015,8 @@ "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "braces": "3.0.2", - "picomatch": "2.2.2" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "npm-run-path": { @@ -13013,7 +13025,7 @@ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "3.1.1" + "path-key": "^3.0.0" } }, "p-limit": { @@ -13022,7 +13034,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -13031,7 +13043,7 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.2.0" } }, "p-try": { @@ -13046,10 +13058,10 @@ "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "@babel/code-frame": "7.8.3", - "error-ex": "1.3.2", - "json-parse-better-errors": "1.0.2", - "lines-and-columns": "1.1.6" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-exists": { @@ -13076,10 +13088,10 @@ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "@types/normalize-package-data": "2.4.0", - "normalize-package-data": "2.5.0", - "parse-json": "5.0.0", - "type-fest": "0.6.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" }, "dependencies": { "type-fest": { @@ -13096,9 +13108,9 @@ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "4.1.0", - "read-pkg": "5.2.0", - "type-fest": "0.8.1" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" } }, "require-main-filename": { @@ -13113,7 +13125,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "3.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { @@ -13128,9 +13140,9 @@ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "8.0.0", - "is-fullwidth-code-point": "3.0.0", - "strip-ansi": "6.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { @@ -13139,7 +13151,7 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "5.0.0" + "ansi-regex": "^5.0.0" } }, "to-regex-range": { @@ -13148,7 +13160,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "7.0.0" + "is-number": "^7.0.0" } }, "which": { @@ -13157,7 +13169,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "wrap-ansi": { @@ -13166,9 +13178,9 @@ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "ansi-styles": "4.2.1", - "string-width": "4.2.0", - "strip-ansi": "6.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "yargs": { @@ -13177,17 +13189,17 @@ "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { - "cliui": "6.0.0", - "decamelize": "1.2.0", - "find-up": "4.1.0", - "get-caller-file": "2.0.5", - "require-directory": "2.1.1", - "require-main-filename": "2.0.0", - "set-blocking": "2.0.0", - "string-width": "4.2.0", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "18.1.2" + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" } }, "yargs-parser": { @@ -13196,8 +13208,8 @@ "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -13219,7 +13231,7 @@ "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", "dev": true, "requires": { - "semver": "6.3.0" + "semver": "^6.3.0" }, "dependencies": { "semver": { @@ -13259,10 +13271,10 @@ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "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": { @@ -13271,7 +13283,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -13282,7 +13294,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -13309,9 +13321,9 @@ "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, "requires": { - "chalk": "2.4.2", - "figures": "2.0.0", - "pkg-conf": "2.1.0" + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" }, "dependencies": { "figures": { @@ -13320,7 +13332,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } } } @@ -13331,13 +13343,13 @@ "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", "dev": true, "requires": { - "@sinonjs/commons": "1.7.1", - "@sinonjs/formatio": "3.2.2", - "@sinonjs/samsam": "3.3.3", - "diff": "3.5.0", - "lolex": "4.2.0", - "nise": "1.5.3", - "supports-color": "5.5.0" + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" } }, "sinon-chai": { @@ -13358,9 +13370,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": { @@ -13369,14 +13381,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "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.3", - "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": { @@ -13394,7 +13406,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -13403,7 +13415,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "ms": { @@ -13420,9 +13432,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "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": { @@ -13431,7 +13443,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -13440,7 +13452,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -13449,7 +13461,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.3" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -13458,9 +13470,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.3" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -13471,7 +13483,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.2.0" }, "dependencies": { "kind-of": { @@ -13480,7 +13492,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -13497,11 +13509,11 @@ "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "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.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, "source-map-url": { @@ -13522,12 +13534,12 @@ "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { - "foreground-child": "2.0.0", - "is-windows": "1.0.2", - "make-dir": "3.0.2", - "rimraf": "3.0.2", - "signal-exit": "3.0.3", - "which": "2.0.2" + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" }, "dependencies": { "rimraf": { @@ -13536,7 +13548,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "glob": "7.1.6" + "glob": "^7.1.3" } }, "which": { @@ -13545,7 +13557,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } } } @@ -13556,8 +13568,8 @@ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.5" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -13572,8 +13584,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.5" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -13588,7 +13600,7 @@ "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "requires": { - "through": "2.3.8" + "through": "2" } }, "split-string": { @@ -13597,7 +13609,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "3.0.2" + "extend-shallow": "^3.0.0" } }, "split2": { @@ -13606,7 +13618,7 @@ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", "dev": true, "requires": { - "through2": "2.0.5" + "through2": "^2.0.2" }, "dependencies": { "through2": { @@ -13615,8 +13627,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -13633,9 +13645,9 @@ "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", "dev": true, "requires": { - "nan": "2.14.0", - "node-pre-gyp": "0.11.0", - "request": "2.88.2" + "nan": "^2.12.1", + "node-pre-gyp": "^0.11.0", + "request": "^2.87.0" } }, "sqlstring": { @@ -13650,15 +13662,15 @@ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { - "asn1": "0.2.4", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.2", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.2", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "safer-buffer": "2.1.2", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "stack-chain": { @@ -13673,8 +13685,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { "define-property": { @@ -13683,7 +13695,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -13694,8 +13706,8 @@ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, "requires": { - "duplexer2": "0.1.4", - "readable-stream": "2.3.7" + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" } }, "stream-shift": { @@ -13704,15 +13716,6 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -13725,8 +13728,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.prototype.trimend": { @@ -13735,8 +13738,8 @@ "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.17.5" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" } }, "string.prototype.trimleft": { @@ -13745,9 +13748,9 @@ "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.17.5", - "string.prototype.trimstart": "1.0.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" } }, "string.prototype.trimright": { @@ -13756,9 +13759,9 @@ "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.17.5", - "string.prototype.trimend": "1.0.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" } }, "string.prototype.trimstart": { @@ -13767,8 +13770,17 @@ "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", "dev": true, "requires": { - "define-properties": "1.1.3", - "es-abstract": "1.17.5" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" } }, "stringify-object": { @@ -13777,9 +13789,9 @@ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, "requires": { - "get-own-enumerable-property-symbols": "3.0.2", - "is-obj": "1.0.1", - "is-regexp": "1.0.0" + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" } }, "strip-ansi": { @@ -13788,7 +13800,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "strip-bom": { @@ -13827,7 +13839,7 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "supports-hyperlinks": { @@ -13836,8 +13848,8 @@ "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", "dev": true, "requires": { - "has-flag": "2.0.0", - "supports-color": "5.5.0" + "has-flag": "^2.0.0", + "supports-color": "^5.0.0" }, "dependencies": { "has-flag": { @@ -13867,10 +13879,10 @@ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "ajv": "6.12.0", - "lodash": "4.17.15", - "slice-ansi": "2.1.0", - "string-width": "3.1.0" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { "ansi-regex": { @@ -13891,9 +13903,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": { @@ -13902,7 +13914,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } } } @@ -13919,13 +13931,13 @@ "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "dev": true, "requires": { - "chownr": "1.1.4", - "fs-minipass": "1.2.7", - "minipass": "2.9.0", - "minizlib": "1.3.3", - "mkdirp": "0.5.5", - "safe-buffer": "5.1.2", - "yallist": "3.1.1" + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" } }, "tedious": { @@ -13934,15 +13946,15 @@ "integrity": "sha512-+M+mWg/D0a6DEynpl3JHNUqc3w9blSYGN+f+gs7jUfZsdnVYzcDPDzrKV0rjfaM1P22/bKPZ5Lm/2oDHo6/olQ==", "dev": true, "requires": { - "adal-node": "0.1.28", + "adal-node": "^0.1.22", "big-number": "1.0.0", - "bl": "2.2.0", - "depd": "1.1.2", - "iconv-lite": "0.4.24", - "native-duplexpair": "1.0.0", - "punycode": "2.1.1", - "readable-stream": "3.6.0", - "sprintf-js": "1.1.2" + "bl": "^2.2.0", + "depd": "^1.1.2", + "iconv-lite": "^0.4.23", + "native-duplexpair": "^1.0.0", + "punycode": "^2.1.0", + "readable-stream": "^3.1.1", + "sprintf-js": "^1.1.2" }, "dependencies": { "readable-stream": { @@ -13951,9 +13963,9 @@ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "inherits": "2.0.4", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "sprintf-js": { @@ -13976,9 +13988,9 @@ "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", "dev": true, "requires": { - "temp-dir": "1.0.0", - "type-fest": "0.3.1", - "unique-string": "1.0.0" + "temp-dir": "^1.0.0", + "type-fest": "^0.3.1", + "unique-string": "^1.0.0" }, "dependencies": { "type-fest": { @@ -13995,9 +14007,9 @@ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { - "@istanbuljs/schema": "0.1.2", - "glob": "7.1.6", - "minimatch": "3.0.4" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" } }, "text-extensions": { @@ -14024,7 +14036,7 @@ "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "dev": true, "requires": { - "readable-stream": "2.3.7" + "readable-stream": "2 || 3" } }, "through2-filter": { @@ -14033,8 +14045,8 @@ "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, "requires": { - "through2": "2.0.5", - "xtend": "4.0.2" + "through2": "~2.0.0", + "xtend": "~4.0.0" }, "dependencies": { "through2": { @@ -14043,8 +14055,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -14055,7 +14067,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } }, "to-absolute-glob": { @@ -14064,8 +14076,8 @@ "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, "requires": { - "is-absolute": "1.0.0", - "is-negated-glob": "1.0.0" + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" } }, "to-fast-properties": { @@ -14080,7 +14092,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -14089,7 +14101,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -14100,10 +14112,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "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": { @@ -14112,8 +14124,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } }, "to-through": { @@ -14122,7 +14134,7 @@ "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, "requires": { - "through2": "2.0.5" + "through2": "^2.0.3" }, "dependencies": { "through2": { @@ -14131,8 +14143,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -14148,8 +14160,8 @@ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "psl": "1.8.0", - "punycode": "2.1.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { @@ -14195,19 +14207,19 @@ "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "builtin-modules": "1.1.1", - "chalk": "2.4.2", - "commander": "2.20.3", - "diff": "3.5.0", - "glob": "7.1.6", - "js-yaml": "3.13.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "resolve": "1.15.1", - "semver": "5.7.1", - "tslib": "1.11.1", - "tsutils": "2.29.0" + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" }, "dependencies": { "semver": { @@ -14224,7 +14236,7 @@ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { - "tslib": "1.11.1" + "tslib": "^1.8.1" } }, "tunnel-agent": { @@ -14233,7 +14245,7 @@ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -14248,7 +14260,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -14269,7 +14281,7 @@ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "requires": { - "is-typedarray": "1.0.0" + "is-typedarray": "^1.0.0" } }, "typescript": { @@ -14291,8 +14303,8 @@ "dev": true, "optional": true, "requires": { - "commander": "2.20.3", - "source-map": "0.6.1" + "commander": "~2.20.3", + "source-map": "~0.6.1" }, "dependencies": { "source-map": { @@ -14322,10 +14334,10 @@ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "2.0.1" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" } }, "unique-stream": { @@ -14334,8 +14346,8 @@ "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, "requires": { - "json-stable-stringify-without-jsonify": "1.0.1", - "through2-filter": "3.0.0" + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" } }, "unique-string": { @@ -14344,7 +14356,7 @@ "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { - "crypto-random-string": "1.0.0" + "crypto-random-string": "^1.0.0" } }, "universal-user-agent": { @@ -14353,7 +14365,7 @@ "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", "dev": true, "requires": { - "os-name": "3.1.0" + "os-name": "^3.1.0" } }, "universalify": { @@ -14368,8 +14380,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { "has-value": { @@ -14378,9 +14390,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "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": { @@ -14408,7 +14420,7 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "2.1.1" + "punycode": "^2.1.0" } }, "urix": { @@ -14452,8 +14464,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" } }, "validator": { @@ -14473,9 +14485,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "vinyl": { @@ -14484,12 +14496,12 @@ "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", "dev": true, "requires": { - "clone": "2.1.2", - "clone-buffer": "1.0.0", - "clone-stats": "1.0.0", - "cloneable-readable": "1.1.3", - "remove-trailing-separator": "1.1.0", - "replace-ext": "1.0.0" + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" } }, "vinyl-fs": { @@ -14498,23 +14510,23 @@ "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, "requires": { - "fs-mkdirp-stream": "1.0.0", - "glob-stream": "6.1.0", - "graceful-fs": "4.2.3", - "is-valid-glob": "1.0.0", - "lazystream": "1.0.0", - "lead": "1.0.0", - "object.assign": "4.1.0", - "pumpify": "1.5.1", - "readable-stream": "2.3.7", - "remove-bom-buffer": "3.0.0", - "remove-bom-stream": "1.2.0", - "resolve-options": "1.1.0", - "through2": "2.0.5", - "to-through": "2.0.0", - "value-or-function": "3.0.0", - "vinyl": "2.2.0", - "vinyl-sourcemap": "1.1.0" + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" }, "dependencies": { "through2": { @@ -14523,8 +14535,8 @@ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "readable-stream": "2.3.7", - "xtend": "4.0.2" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -14535,13 +14547,13 @@ "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "dev": true, "requires": { - "append-buffer": "1.0.2", - "convert-source-map": "1.7.0", - "graceful-fs": "4.2.3", - "normalize-path": "2.1.1", - "now-and-later": "2.0.1", - "remove-bom-buffer": "3.0.0", - "vinyl": "2.2.0" + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" } }, "webidl-conversions": { @@ -14558,7 +14570,7 @@ "dev": true, "optional": true, "requires": { - "tr46": "0.0.3" + "tr46": "~0.0.1" } }, "which": { @@ -14567,7 +14579,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -14588,7 +14600,7 @@ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "requires": { - "string-width": "2.1.1" + "string-width": "^1.0.2 || 2" } }, "windows-release": { @@ -14597,7 +14609,7 @@ "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", "dev": true, "requires": { - "execa": "1.0.0" + "execa": "^1.0.0" } }, "wkx": { @@ -14605,7 +14617,7 @@ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", "requires": { - "@types/node": "12.12.34" + "@types/node": "*" } }, "word-wrap": { @@ -14626,8 +14638,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": { @@ -14642,7 +14654,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "string-width": { @@ -14651,9 +14663,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": { @@ -14662,7 +14674,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } } } @@ -14679,7 +14691,7 @@ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { - "mkdirp": "0.5.5" + "mkdirp": "^0.5.1" } }, "write-file-atomic": { @@ -14688,10 +14700,10 @@ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "requires": { - "imurmurhash": "0.1.4", - "is-typedarray": "1.0.0", - "signal-exit": "3.0.3", - "typedarray-to-buffer": "3.1.5" + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" } }, "xml-name-validator": { @@ -14737,7 +14749,7 @@ "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", "dev": true, "requires": { - "@babel/runtime": "7.9.2" + "@babel/runtime": "^7.8.7" } }, "yargs": { @@ -14746,18 +14758,18 @@ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "3.0.0", - "get-caller-file": "1.0.3", - "os-locale": "3.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "4.0.0", - "yargs-parser": "11.1.1" + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" }, "dependencies": { "camelcase": { @@ -14772,7 +14784,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "locate-path": { @@ -14781,8 +14793,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": { @@ -14791,7 +14803,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -14800,7 +14812,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.0.0" } }, "p-try": { @@ -14815,8 +14827,8 @@ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -14827,7 +14839,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } }, "yargs-unparser": { @@ -14836,9 +14848,9 @@ "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { - "flat": "4.1.0", - "lodash": "4.17.15", - "yargs": "13.3.2" + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" }, "dependencies": { "ansi-regex": { @@ -14859,9 +14871,9 @@ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "3.1.0", - "strip-ansi": "5.2.0", - "wrap-ansi": "5.1.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" } }, "emoji-regex": { @@ -14876,7 +14888,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "3.0.0" + "locate-path": "^3.0.0" } }, "get-caller-file": { @@ -14891,8 +14903,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": { @@ -14901,7 +14913,7 @@ "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { - "p-try": "2.2.0" + "p-try": "^2.0.0" } }, "p-locate": { @@ -14910,7 +14922,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "2.2.2" + "p-limit": "^2.0.0" } }, "p-try": { @@ -14931,9 +14943,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": { @@ -14942,7 +14954,7 @@ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "4.1.0" + "ansi-regex": "^4.1.0" } }, "wrap-ansi": { @@ -14951,9 +14963,9 @@ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "string-width": "3.1.0", - "strip-ansi": "5.2.0" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" } }, "yargs": { @@ -14962,16 +14974,16 @@ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "5.0.0", - "find-up": "3.0.0", - "get-caller-file": "2.0.5", - "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.2" + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "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.1.2" } }, "yargs-parser": { @@ -14980,8 +14992,8 @@ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { - "camelcase": "5.3.1", - "decamelize": "1.2.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } From 5d51e696734585f56e40e93273028328171a7113 Mon Sep 17 00:00:00 2001 From: Chris Chew Date: Mon, 13 Apr 2020 23:24:12 -0500 Subject: [PATCH 080/414] feat(pool): expose maxUses pool config option (#12101) --- lib/dialects/abstract/connection-manager.js | 9 ++++++--- lib/sequelize.js | 1 + types/lib/sequelize.d.ts | 5 +++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index 50297bcd3165..336124ddb93f 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -138,7 +138,8 @@ class ConnectionManager { min: config.pool.min, acquireTimeoutMillis: config.pool.acquire, idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses }); debug(`pool created with max/min: ${config.pool.max}/${config.pool.min}, no replication`); @@ -207,7 +208,8 @@ class ConnectionManager { min: config.pool.min, acquireTimeoutMillis: config.pool.acquire, idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses }), write: new Pool({ name: 'sequelize:write', @@ -223,7 +225,8 @@ class ConnectionManager { min: config.pool.min, acquireTimeoutMillis: config.pool.acquire, idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses }) }; diff --git a/lib/sequelize.js b/lib/sequelize.js index 176f07ad14cf..8f3785eb3ad0 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -158,6 +158,7 @@ class Sequelize { * @param {number} [options.pool.acquire=60000] The maximum time, in milliseconds, that pool will try to get connection before throwing error * @param {number} [options.pool.evict=1000] The time interval, in milliseconds, after which sequelize-pool will remove idle connections. * @param {Function} [options.pool.validate] A function that validates a connection. Called with client. The default function checks that client is an object, and that its state is not disconnected + * @param {number} [options.pool.maxUses=Infinity] The number of times a connection can be used before discarding it for a replacement, [`used for eventual cluster rebalancing`](https://github.com/sequelize/sequelize-pool). * @param {boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them. WARNING: Setting this to false may expose vulnerabilities and is not recommended! * @param {string} [options.transactionType='DEFERRED'] Set the default transaction type. See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] Set the default transaction isolation level. See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options. diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 8d647f9a4283..2fa0dc4a2a97 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -108,6 +108,11 @@ export interface PoolOptions { */ evict?: number; + /** + * The number of times to use a connection before closing and replacing it. Default is Infinity + */ + maxUses?: number; + /** * A function that validates a connection. Called with client. The default function checks that client is an * object, and that its state is not disconnected From b495d5ed4afc0597725c4bf56bb8382311a5a1e5 Mon Sep 17 00:00:00 2001 From: McGrady Chen Date: Thu, 16 Apr 2020 23:33:23 +0800 Subject: [PATCH 081/414] fix(mssql) insert record failure because of BOOLEAN column type (#12090) --- lib/dialects/mssql/query.js | 2 ++ .../dialects/mssql/regressions.test.js | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 3732654d62c1..2c5748be6ec8 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -36,6 +36,8 @@ class Query extends AbstractQuery { //Default to a reasonable numeric precision/scale pending more sophisticated logic paramType.typeOptions = { precision: 30, scale: getScale(value) }; } + } else if (typeof value === 'boolean') { + paramType.type = TYPES.Bit; } if (Buffer.isBuffer(value)) { paramType.type = TYPES.VarBinary; diff --git a/test/integration/dialects/mssql/regressions.test.js b/test/integration/dialects/mssql/regressions.test.js index 5529ff9a5e5b..670b6e32fa4f 100644 --- a/test/integration/dialects/mssql/regressions.test.js +++ b/test/integration/dialects/mssql/regressions.test.js @@ -132,4 +132,28 @@ if (dialect.match(/^mssql/)) { expect(Number(record.business_id)).to.equals(bigIntValue); }); }); + + it('saves boolean is true, #12090', function() { + const BooleanTable = this.sequelize.define('BooleanTable', { + status: { + type: Sequelize.BOOLEAN, + allowNull: false + } + }, { + freezeTableName: true + }); + + const value = true; + + return BooleanTable.sync({ force: true }) + .then(() => { + return BooleanTable.create({ + status: value + }); + }) + .then(() => BooleanTable.findOne()) + .then(record => { + expect(record.status).to.equals(value); + }); + }); } From bccb447b6ce0b671615a92e332d94cff9aa8b29d Mon Sep 17 00:00:00 2001 From: Mohamed Nainar Date: Sun, 19 Apr 2020 11:55:44 +0530 Subject: [PATCH 082/414] docs(README.md): update install instructions (#12120) --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a29c6ae195c5..4a21d9e30b42 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,16 @@ New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.or ## Installation -```bash -$ npm install --save sequelize # This will install v5 -$ npm install --save sequelize@next # This will install v6-beta +```sh +$ npm i sequelize # This will install v5 +$ npm i sequelize@next # This will install v6-beta # And one of the following: -$ npm install --save pg pg-hstore # Postgres -$ npm install --save mysql2 -$ npm install --save mariadb -$ npm install --save sqlite3 -$ npm install --save tedious # Microsoft SQL Server +$ npm i pg pg-hstore # Postgres +$ npm i mysql2 +$ npm i mariadb +$ npm i sqlite3 +$ npm i tedious # Microsoft SQL Server ``` ## Documentation From 81ce8ee30c33a1bfdaaba42ee5432b681746489b Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 19 Apr 2020 02:38:22 -0500 Subject: [PATCH 083/414] refactor(model): asyncify methods (#12122) --- lib/model.js | 1656 ++++++++++++++--------------- test/integration/instance.test.js | 7 +- test/integration/model.test.js | 7 +- test/unit/increment.test.js | 12 +- test/unit/instance/save.test.js | 6 +- test/unit/model/destroy.test.js | 7 +- test/unit/model/findall.test.js | 5 +- test/unit/model/update.test.js | 6 +- 8 files changed, 801 insertions(+), 905 deletions(-) diff --git a/lib/model.js b/lib/model.js index 596dda608ab6..87215fe56da8 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1380,12 +1380,12 @@ class Model { * * @returns {Promise} */ - static drop(options) { - return this.QueryInterface.dropTable(this.getTableName(options), options); + static async drop(options) { + return await this.QueryInterface.dropTable(this.getTableName(options), options); } - static dropSchema(schema) { - return this.QueryInterface.dropSchema(schema); + static async dropSchema(schema) { + return await this.QueryInterface.dropSchema(schema); } /** @@ -1675,7 +1675,7 @@ class Model { * * @returns {Promise>} */ - static findAll(options) { + static async findAll(options) { if (options !== undefined && !_.isPlainObject(options)) { throw new sequelizeErrors.QueryError('The argument passed to findAll must be an options object, use findByPk if you wish to pass a single primary key value'); } @@ -1700,78 +1700,70 @@ class Model { ? options.rejectOnEmpty : this.options.rejectOnEmpty; - return Promise.resolve().then(() => { - this._injectScope(options); + this._injectScope(options); - if (options.hooks) { - return this.runHooks('beforeFind', options); - } - }).then(() => { - this._conformIncludes(options, this); - this._expandAttributes(options); - this._expandIncludeAll(options); + if (options.hooks) { + await this.runHooks('beforeFind', options); + } + this._conformIncludes(options, this); + this._expandAttributes(options); + this._expandIncludeAll(options); - if (options.hooks) { - return this.runHooks('beforeFindAfterExpandIncludeAll', options); - } - }).then(() => { - options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); + if (options.hooks) { + await this.runHooks('beforeFindAfterExpandIncludeAll', options); + } + options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); - if (options.include) { - options.hasJoin = true; + if (options.include) { + options.hasJoin = true; - this._validateIncludedElements(options, tableNames); + this._validateIncludedElements(options, tableNames); - // If we're not raw, we have to make sure we include the primary key for de-duplication - if ( - options.attributes - && !options.raw - && this.primaryKeyAttribute - && !options.attributes.includes(this.primaryKeyAttribute) - && (!options.group || !options.hasSingleAssociation || options.hasMultiAssociation) - ) { - options.attributes = [this.primaryKeyAttribute].concat(options.attributes); - } + // If we're not raw, we have to make sure we include the primary key for de-duplication + if ( + options.attributes + && !options.raw + && this.primaryKeyAttribute + && !options.attributes.includes(this.primaryKeyAttribute) + && (!options.group || !options.hasSingleAssociation || options.hasMultiAssociation) + ) { + options.attributes = [this.primaryKeyAttribute].concat(options.attributes); } + } - if (!options.attributes) { - options.attributes = Object.keys(this.rawAttributes); - options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); - } + if (!options.attributes) { + options.attributes = Object.keys(this.rawAttributes); + options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); + } - // whereCollection is used for non-primary key updates - this.options.whereCollection = options.where || null; + // whereCollection is used for non-primary key updates + this.options.whereCollection = options.where || null; - Utils.mapFinderOptions(options, this); + Utils.mapFinderOptions(options, this); - options = this._paranoidClause(this, options); + options = this._paranoidClause(this, options); - if (options.hooks) { - return this.runHooks('beforeFindAfterOptions', options); - } - }).then(() => { - const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) }); - return this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); - }).then(results => { - if (options.hooks) { - return Promise.resolve(this.runHooks('afterFind', results, options)).then(() => results); - } - return results; - }).then(results => { + if (options.hooks) { + await this.runHooks('beforeFindAfterOptions', options); + } + const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) }); + const results = await this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); + if (options.hooks) { + await this.runHooks('afterFind', results, options); + } - //rejectOnEmpty mode - if (_.isEmpty(results) && options.rejectOnEmpty) { - if (typeof options.rejectOnEmpty === 'function') { - throw new options.rejectOnEmpty(); - } - if (typeof options.rejectOnEmpty === 'object') { - throw options.rejectOnEmpty; - } - throw new sequelizeErrors.EmptyResultError(); + //rejectOnEmpty mode + if (_.isEmpty(results) && options.rejectOnEmpty) { + if (typeof options.rejectOnEmpty === 'function') { + throw new options.rejectOnEmpty(); } + if (typeof options.rejectOnEmpty === 'object') { + throw options.rejectOnEmpty; + } + throw new sequelizeErrors.EmptyResultError(); + } - return Model._findSeparate(results, options); - }); + return await Model._findSeparate(results, options); } static warnOnInvalidOptions(options, validColumnNames) { @@ -1804,17 +1796,17 @@ class Model { return attributes; } - static _findSeparate(results, options) { - if (!options.include || options.raw || !results) return Promise.resolve(results); + static async _findSeparate(results, options) { + if (!options.include || options.raw || !results) return results; const original = results; if (options.plain) results = [results]; if (!results.length) return original; - return Promise.all(options.include.map(include => { + await Promise.all(options.include.map(async include => { if (!include.separate) { - return Model._findSeparate( + return await Model._findSeparate( results.reduce((memo, result) => { let associations = result.get(include.association.as); @@ -1837,20 +1829,22 @@ class Model { ); } - return include.association.get(results, Object.assign( + const map = await include.association.get(results, Object.assign( {}, _.omit(options, nonCascadingOptions), _.omit(include, ['parent', 'association', 'as', 'originalAttributes']) - )).then(map => { - for (const result of results) { - result.set( - include.association.as, - map[result.get(include.association.sourceKey)], - { raw: true } - ); - } - }); - })).then(() => original); + )); + + for (const result of results) { + result.set( + include.association.as, + map[result.get(include.association.sourceKey)], + { raw: true } + ); + } + })); + + return original; } /** @@ -1866,10 +1860,10 @@ class Model { * * @returns {Promise} */ - static findByPk(param, options) { + static async findByPk(param, options) { // return Promise resolved with null if no arguments are passed if ([null, undefined].includes(param)) { - return Promise.resolve(null); + return null; } options = Utils.cloneDeep(options) || {}; @@ -1883,7 +1877,7 @@ class Model { } // Bypass a possible overloaded findOne - return this.findOne(options); + return await this.findOne(options); } /** @@ -1898,7 +1892,7 @@ class Model { * * @returns {Promise} */ - static findOne(options) { + static async findOne(options) { if (options !== undefined && !_.isPlainObject(options)) { throw new Error('The argument passed to findOne must be an options object, use findByPk if you wish to pass a single primary key value'); } @@ -1917,7 +1911,7 @@ class Model { } // Bypass a possible overloaded findAll. - return this.findAll(_.defaults(options, { + return await this.findAll(_.defaults(options, { plain: true })); } @@ -1938,7 +1932,7 @@ class Model { * * @returns {Promise} Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in which case the complete data result is returned. */ - static aggregate(attribute, aggregateFunction, options) { + static async aggregate(attribute, aggregateFunction, options) { options = Utils.cloneDeep(options); // We need to preserve attributes here as the `injectScope` call would inject non aggregate columns. @@ -1986,12 +1980,11 @@ class Model { Utils.mapOptionFieldNames(options, this); options = this._paranoidClause(this, options); - return this.QueryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this).then( value => { - if (value === null) { - return 0; - } - return value; - }); + const value = await this.QueryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this); + if (value === null) { + return 0; + } + return value; } /** @@ -2014,34 +2007,31 @@ class Model { * * @returns {Promise} */ - static count(options) { - return Promise.resolve().then(() => { - options = Utils.cloneDeep(options); - options = _.defaults(options, { hooks: true }); - options.raw = true; - if (options.hooks) { - return this.runHooks('beforeCount', options); - } - }).then(() => { - let col = options.col || '*'; - if (options.include) { - col = `${this.name}.${options.col || this.primaryKeyField}`; - } - if (options.distinct && col === '*') { - col = this.primaryKeyField; - } - options.plain = !options.group; - options.dataType = new DataTypes.INTEGER(); - options.includeIgnoreAttributes = false; + static async count(options) { + options = Utils.cloneDeep(options); + options = _.defaults(options, { hooks: true }); + options.raw = true; + if (options.hooks) { + await this.runHooks('beforeCount', options); + } + let col = options.col || '*'; + if (options.include) { + col = `${this.name}.${options.col || this.primaryKeyField}`; + } + if (options.distinct && col === '*') { + col = this.primaryKeyField; + } + options.plain = !options.group; + options.dataType = new DataTypes.INTEGER(); + options.includeIgnoreAttributes = false; - // No limit, offset or order for the options max be given to count() - // Set them to null to prevent scopes setting those values - options.limit = null; - options.offset = null; - options.order = null; + // No limit, offset or order for the options max be given to count() + // Set them to null to prevent scopes setting those values + options.limit = null; + options.offset = null; + options.order = null; - return this.aggregate(col, 'count', options); - }); + return await this.aggregate(col, 'count', options); } /** @@ -2080,7 +2070,7 @@ class Model { * * @returns {Promise<{count: number, rows: Model[]}>} */ - static findAndCountAll(options) { + static async findAndCountAll(options) { if (options !== undefined && !_.isPlainObject(options)) { throw new Error('The argument passed to findAndCountAll must be an options object, use findByPk if you wish to pass a single primary key value'); } @@ -2091,14 +2081,15 @@ class Model { countOptions.attributes = undefined; } - return Promise.all([ + const [count, rows] = await Promise.all([ this.count(countOptions), this.findAll(options) - ]) - .then(([count, rows]) => ({ - count, - rows: count === 0 ? [] : rows - })); + ]); + + return { + count, + rows: count === 0 ? [] : rows + }; } /** @@ -2112,8 +2103,8 @@ class Model { * * @returns {Promise<*>} */ - static max(field, options) { - return this.aggregate(field, 'max', options); + static async max(field, options) { + return await this.aggregate(field, 'max', options); } /** @@ -2127,8 +2118,8 @@ class Model { * * @returns {Promise<*>} */ - static min(field, options) { - return this.aggregate(field, 'min', options); + static async min(field, options) { + return await this.aggregate(field, 'min', options); } /** @@ -2142,8 +2133,8 @@ class Model { * * @returns {Promise} */ - static sum(field, options) { - return this.aggregate(field, 'sum', options); + static async sum(field, options) { + return await this.aggregate(field, 'sum', options); } /** @@ -2211,10 +2202,10 @@ class Model { * @returns {Promise} * */ - static create(values, options) { + static async create(values, options) { options = Utils.cloneDeep(options || {}); - return this.build(values, { + return await this.build(values, { isNewRecord: true, attributes: options.fields, include: options.include, @@ -2234,7 +2225,7 @@ class Model { * * @returns {Promise} */ - static findOrBuild(options) { + static async findOrBuild(options) { if (!options || !options.where || arguments.length > 1) { throw new Error( 'Missing where attribute in the options parameter passed to findOrBuild. ' + @@ -2244,20 +2235,19 @@ class Model { let values; - return this.findOne(options).then(instance => { - if (instance === null) { - values = _.clone(options.defaults) || {}; - if (_.isPlainObject(options.where)) { - values = Utils.defaults(values, options.where); - } + let instance = await this.findOne(options); + if (instance === null) { + values = _.clone(options.defaults) || {}; + if (_.isPlainObject(options.where)) { + values = Utils.defaults(values, options.where); + } - instance = this.build(values, options); + instance = this.build(values, options); - return Promise.resolve([instance, true]); - } + return [instance, true]; + } - return Promise.resolve([instance, false]); - }); + return [instance, false]; } /** @@ -2278,7 +2268,7 @@ class Model { * * @returns {Promise} */ - static findOrCreate(options) { + static async findOrCreate(options) { if (!options || !options.where || arguments.length > 1) { throw new Error( 'Missing where attribute in the options parameter passed to findOrCreate. ' + @@ -2308,15 +2298,14 @@ class Model { let values; let transaction; - // Create a transaction or a savepoint, depending on whether a transaction was passed in - return this.sequelize.transaction(options).then(t => { + try { + const t = await this.sequelize.transaction(options); transaction = t; options.transaction = t; - return this.findOne(Utils.defaults({ transaction }, options)); - }).then(instance => { - if (instance !== null) { - return [instance, false]; + const found = await this.findOne(Utils.defaults({ transaction }, options)); + if (found !== null) { + return [found, false]; } values = _.clone(options.defaults) || {}; @@ -2327,14 +2316,15 @@ class Model { options.exception = true; options.returning = true; - return this.create(values, options).then(instance => { - if (instance.get(this.primaryKeyAttribute, { raw: true }) === null) { + try { + const created = await this.create(values, options); + if (created.get(this.primaryKeyAttribute, { raw: true }) === null) { // If the query returned an empty result for the primary key, we know that this was actually a unique constraint violation throw new sequelizeErrors.UniqueConstraintError(); } - return [instance, true]; - }).catch(err => { + return [created, true]; + } catch (err) { if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; const flattenedWhere = Utils.flattenObjectDeep(options.where); const flattenedWhereKeys = Object.keys(flattenedWhere).map(name => _.last(name.split('.'))); @@ -2359,21 +2349,21 @@ class Model { } // Someone must have created a matching instance inside the same transaction since we last did a find. Let's find it! - return this.findOne(Utils.defaults({ + const otherCreated = await this.findOne(Utils.defaults({ transaction: internalTransaction ? null : transaction - }, options)).then(instance => { - // Sanity check, ideally we caught this at the defaultFeilds/err.fields check - // But if we didn't and instance is null, we will throw - if (instance === null) throw err; - return [instance, false]; - }); - }); - }).finally(() => { + }, options)); + + // Sanity check, ideally we caught this at the defaultFeilds/err.fields check + // But if we didn't and instance is null, we will throw + if (otherCreated === null) throw err; + + return [otherCreated, false]; + } + } finally { if (internalTransaction && transaction) { - // If we created a transaction internally (and not just a savepoint), we should clean it up - return transaction.commit(); + await transaction.commit(); } - }); + } } /** @@ -2389,7 +2379,7 @@ class Model { * * @returns {Promise} */ - static findCreateFind(options) { + static async findCreateFind(options) { if (!options || !options.where) { throw new Error( 'Missing where attribute in the options parameter passed to findCreateFind.' @@ -2402,16 +2392,17 @@ class Model { } - return this.findOne(options).then(result => { - if (result) return [result, false]; + const found = await this.findOne(options); + if (found) return [found, false]; - return this.create(values, options) - .then(result => [result, true]) - .catch(err => { - if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; - return this.findOne(options).then(result => [result, false]); - }); - }); + try { + const created = await this.create(values, options); + return [created, true]; + } catch (err) { + if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; + const foundAgain = await this.findOne(options); + return [foundAgain, false]; + } } /** @@ -2438,7 +2429,7 @@ class Model { * * @returns {Promise} Returns a boolean indicating whether the row was created or updated. For MySQL/MariaDB, it returns `true` when inserted and `false` when updated. For Postgres/MSSQL with `options.returning` true, it returns record and created boolean with signature ``. */ - static upsert(values, options) { + static async upsert(values, options) { options = Object.assign({ hooks: true, returning: false, @@ -2457,56 +2448,49 @@ class Model { options.fields = changed; } - return Promise.resolve().then(() => { - if (options.validate) { - return instance.validate(options); - } - }).then(() => { - // Map field names - const updatedDataValues = _.pick(instance.dataValues, changed); - const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this); - const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this); - const now = Utils.now(this.sequelize.options.dialect); - - // Attach createdAt - if (createdAtAttr && !updateValues[createdAtAttr]) { - const field = this.rawAttributes[createdAtAttr].field || createdAtAttr; - insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now; - } - if (updatedAtAttr && !insertValues[updatedAtAttr]) { - const field = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; - insertValues[field] = updateValues[field] = this._getDefaultTimestamp(updatedAtAttr) || now; - } + if (options.validate) { + await instance.validate(options); + } + // Map field names + const updatedDataValues = _.pick(instance.dataValues, changed); + const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this); + const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this); + const now = Utils.now(this.sequelize.options.dialect); - // Build adds a null value for the primary key, if none was given by the user. - // We need to remove that because of some Postgres technicalities. - if (!hasPrimary && this.primaryKeyAttribute && !this.rawAttributes[this.primaryKeyAttribute].defaultValue) { - delete insertValues[this.primaryKeyField]; - delete updateValues[this.primaryKeyField]; - } + // Attach createdAt + if (createdAtAttr && !updateValues[createdAtAttr]) { + const field = this.rawAttributes[createdAtAttr].field || createdAtAttr; + insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now; + } + if (updatedAtAttr && !insertValues[updatedAtAttr]) { + const field = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; + insertValues[field] = updateValues[field] = this._getDefaultTimestamp(updatedAtAttr) || now; + } - return Promise.resolve().then(() => { - if (options.hooks) { - return this.runHooks('beforeUpsert', values, options); - } - }) - .then(() => { - return this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); - }) - .then(([created, primaryKey]) => { - if (options.returning === true && primaryKey) { - return this.findByPk(primaryKey, options).then(record => [record, created]); - } + // Build adds a null value for the primary key, if none was given by the user. + // We need to remove that because of some Postgres technicalities. + if (!hasPrimary && this.primaryKeyAttribute && !this.rawAttributes[this.primaryKeyAttribute].defaultValue) { + delete insertValues[this.primaryKeyField]; + delete updateValues[this.primaryKeyField]; + } - return created; - }) - .then(result => { - if (options.hooks) { - return Promise.resolve(this.runHooks('afterUpsert', result, options)).then(() => result); - } - return result; - }); - }); + if (options.hooks) { + await this.runHooks('beforeUpsert', values, options); + } + const [created, primaryKey] = await this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); + let result; + if (options.returning === true && primaryKey) { + const record = await this.findByPk(primaryKey, options); + result = [record, created]; + } else { + result = created; + } + + if (options.hooks) { + await this.runHooks('afterUpsert', result, options); + return result; + } + return result; } /** @@ -2534,9 +2518,9 @@ class Model { * * @returns {Promise>} */ - static bulkCreate(records, options = {}) { + static async bulkCreate(records, options = {}) { if (!records.length) { - return Promise.resolve([]); + return []; } const dialect = this.sequelize.options.dialect; @@ -2554,7 +2538,7 @@ class Model { const instances = records.map(values => this.build(values, { isNewRecord: true, include: options.include })); - const recursiveBulkCreate = (instances, options) => { + const recursiveBulkCreate = async (instances, options) => { options = Object.assign({ validate: false, hooks: true, @@ -2571,10 +2555,10 @@ class Model { } if (options.ignoreDuplicates && ['mssql'].includes(dialect)) { - return Promise.reject(new Error(`${dialect} does not support the ignoreDuplicates option.`)); + throw new Error(`${dialect} does not support the ignoreDuplicates option.`); } if (options.updateOnDuplicate && (dialect !== 'mysql' && dialect !== 'mariadb' && dialect !== 'sqlite' && dialect !== 'postgres')) { - return Promise.reject(new Error(`${dialect} does not support the updateOnDuplicate option.`)); + throw new Error(`${dialect} does not support the updateOnDuplicate option.`); } const model = options.model; @@ -2590,52 +2574,47 @@ class Model { options.updateOnDuplicate ); } else { - return Promise.reject(new Error('updateOnDuplicate option only supports non-empty array.')); + throw new Error('updateOnDuplicate option only supports non-empty array.'); } } - return Promise.resolve().then(() => { - // Run before hook - if (options.hooks) { - return model.runHooks('beforeBulkCreate', instances, options); - } - }).then(() => { - // Validate - if (options.validate) { - const errors = new Promise.AggregateError(); - const validateOptions = _.clone(options); - validateOptions.hooks = options.individualHooks; - - return Promise.all(instances.map(instance => - instance.validate(validateOptions).catch(err => { - errors.push(new sequelizeErrors.BulkRecordError(err, instance)); - }))).then(() => { - delete options.skip; - if (errors.length) { - throw errors; - } - }); - } - }).then(() => { - if (options.individualHooks) { - // Create each instance individually - return Promise.all(instances.map(instance => { - const individualOptions = _.clone(options); - delete individualOptions.fields; - delete individualOptions.individualHooks; - delete individualOptions.ignoreDuplicates; - individualOptions.validate = false; - individualOptions.hooks = true; + // Run before hook + if (options.hooks) { + await model.runHooks('beforeBulkCreate', instances, options); + } + // Validate + if (options.validate) { + const errors = new Promise.AggregateError(); + const validateOptions = _.clone(options); + validateOptions.hooks = options.individualHooks; + + await Promise.all(instances.map(async instance => { + try { + await instance.validate(validateOptions); + } catch (err) { + errors.push(new sequelizeErrors.BulkRecordError(err, instance)); + } + })); - return instance.save(individualOptions); - })); + delete options.skip; + if (errors.length) { + throw errors; } - - return Promise.resolve().then(() => { - if (!options.include || !options.include.length) return; - - // Nested creation for BelongsTo relations - return Promise.all(options.include.filter(include => include.association instanceof BelongsTo).map(include => { + } + if (options.individualHooks) { + await Promise.all(instances.map(async instance => { + const individualOptions = _.clone(options); + delete individualOptions.fields; + delete individualOptions.individualHooks; + delete individualOptions.ignoreDuplicates; + individualOptions.validate = false; + individualOptions.hooks = true; + + await instance.save(individualOptions); + })); + } else { + if (options.include && options.include.length) { + await Promise.all(options.include.filter(include => include.association instanceof BelongsTo).map(async include => { const associationInstances = []; const associationInstanceIndexToInstanceMap = []; @@ -2658,96 +2637,92 @@ class Model { logging: options.logging }).value(); - return recursiveBulkCreate(associationInstances, includeOptions).then(associationInstances => { - for (const idx in associationInstances) { - const associationInstance = associationInstances[idx]; - const instance = associationInstanceIndexToInstanceMap[idx]; + const createdAssociationInstances = await recursiveBulkCreate(associationInstances, includeOptions); + for (const idx in createdAssociationInstances) { + const associationInstance = createdAssociationInstances[idx]; + const instance = associationInstanceIndexToInstanceMap[idx]; - instance[include.association.accessors.set](associationInstance, { save: false, logging: options.logging }); - } - }); - })); - }).then(() => { - // Create all in one query - // Recreate records from instances to represent any changes made in hooks or validation - records = instances.map(instance => { - const values = instance.dataValues; - - // set createdAt/updatedAt attributes - if (createdAtAttr && !values[createdAtAttr]) { - values[createdAtAttr] = now; - if (!options.fields.includes(createdAtAttr)) { - options.fields.push(createdAtAttr); - } - } - if (updatedAtAttr && !values[updatedAtAttr]) { - values[updatedAtAttr] = now; - if (!options.fields.includes(updatedAtAttr)) { - options.fields.push(updatedAtAttr); - } + instance[include.association.accessors.set](associationInstance, { save: false, logging: options.logging }); } + })); + } - const out = Object.assign({}, Utils.mapValueFieldNames(values, options.fields, model)); - for (const key of model._virtualAttributes) { - delete out[key]; - } - return out; - }); + // Create all in one query + // Recreate records from instances to represent any changes made in hooks or validation + records = instances.map(instance => { + const values = instance.dataValues; - // Map attributes to fields for serial identification - const fieldMappedAttributes = {}; - for (const attr in model.tableAttributes) { - fieldMappedAttributes[model.rawAttributes[attr].field || attr] = model.rawAttributes[attr]; + // set createdAt/updatedAt attributes + if (createdAtAttr && !values[createdAtAttr]) { + values[createdAtAttr] = now; + if (!options.fields.includes(createdAtAttr)) { + options.fields.push(createdAtAttr); + } } - - // Map updateOnDuplicate attributes to fields - if (options.updateOnDuplicate) { - options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr); - // Get primary keys for postgres to enable updateOnDuplicate - options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value(); - if (Object.keys(model.uniqueKeys).length > 0) { - options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length >= 1).map(c => c.fields).reduce(c => c[0]).value(); + if (updatedAtAttr && !values[updatedAtAttr]) { + values[updatedAtAttr] = now; + if (!options.fields.includes(updatedAtAttr)) { + options.fields.push(updatedAtAttr); } } - // Map returning attributes to fields - if (options.returning && Array.isArray(options.returning)) { - options.returning = options.returning.map(attr => _.get(model.rawAttributes[attr], 'field', attr)); + const out = Object.assign({}, Utils.mapValueFieldNames(values, options.fields, model)); + for (const key of model._virtualAttributes) { + delete out[key]; } + return out; + }); - return model.QueryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes).then(results => { - if (Array.isArray(results)) { - results.forEach((result, i) => { - const instance = instances[i]; - - for (const key in result) { - if (!instance || key === model.primaryKeyAttribute && - instance.get(model.primaryKeyAttribute) && - ['mysql', 'mariadb', 'sqlite'].includes(dialect)) { - // The query.js for these DBs is blind, it autoincrements the - // primarykey value, even if it was set manually. Also, it can - // return more results than instances, bug?. - continue; - } - if (Object.prototype.hasOwnProperty.call(result, key)) { - const record = result[key]; + // Map attributes to fields for serial identification + const fieldMappedAttributes = {}; + for (const attr in model.tableAttributes) { + fieldMappedAttributes[model.rawAttributes[attr].field || attr] = model.rawAttributes[attr]; + } - const attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + // Map updateOnDuplicate attributes to fields + if (options.updateOnDuplicate) { + options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr); + // Get primary keys for postgres to enable updateOnDuplicate + options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value(); + if (Object.keys(model.uniqueKeys).length > 0) { + options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length >= 1).map(c => c.fields).reduce(c => c[0]).value(); + } + } - instance.dataValues[attr && attr.fieldName || key] = record; - } - } - }); + // Map returning attributes to fields + if (options.returning && Array.isArray(options.returning)) { + options.returning = options.returning.map(attr => _.get(model.rawAttributes[attr], 'field', attr)); + } + + const results = await model.QueryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes); + if (Array.isArray(results)) { + results.forEach((result, i) => { + const instance = instances[i]; + + for (const key in result) { + if (!instance || key === model.primaryKeyAttribute && + instance.get(model.primaryKeyAttribute) && + ['mysql', 'mariadb', 'sqlite'].includes(dialect)) { + // The query.js for these DBs is blind, it autoincrements the + // primarykey value, even if it was set manually. Also, it can + // return more results than instances, bug?. + continue; + } + if (Object.prototype.hasOwnProperty.call(result, key)) { + const record = result[key]; + + const attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + + instance.dataValues[attr && attr.fieldName || key] = record; + } } - return results; }); - }); - }).then(() => { - if (!options.include || !options.include.length) return; + } + } - // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.all(options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)).map(include => { + if (options.include && options.include.length) { + await Promise.all(options.include.filter(include => !(include.association instanceof BelongsTo || + include.parent && include.parent.association instanceof BelongsToMany)).map(async include => { const associationInstances = []; const associationInstanceIndexToInstanceMap = []; @@ -2778,73 +2753,74 @@ class Model { logging: options.logging }).value(); - return recursiveBulkCreate(associationInstances, includeOptions).then(associationInstances => { - if (include.association instanceof BelongsToMany) { - const valueSets = []; - - for (const idx in associationInstances) { - const associationInstance = associationInstances[idx]; - const instance = associationInstanceIndexToInstanceMap[idx]; - - const values = {}; - values[include.association.foreignKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); - values[include.association.otherKey] = associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }); - - // Include values defined in the association - Object.assign(values, include.association.through.scope); - if (associationInstance[include.association.through.model.name]) { - for (const attr of Object.keys(include.association.through.model.rawAttributes)) { - if (include.association.through.model.rawAttributes[attr]._autoGenerated || - attr === include.association.foreignKey || - attr === include.association.otherKey || - typeof associationInstance[include.association.through.model.name][attr] === undefined) { - continue; - } - values[attr] = associationInstance[include.association.through.model.name][attr]; + const createdAssociationInstances = await recursiveBulkCreate(associationInstances, includeOptions); + if (include.association instanceof BelongsToMany) { + const valueSets = []; + + for (const idx in createdAssociationInstances) { + const associationInstance = createdAssociationInstances[idx]; + const instance = associationInstanceIndexToInstanceMap[idx]; + + const values = {}; + values[include.association.foreignKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); + values[include.association.otherKey] = associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }); + + // Include values defined in the association + Object.assign(values, include.association.through.scope); + if (associationInstance[include.association.through.model.name]) { + for (const attr of Object.keys(include.association.through.model.rawAttributes)) { + if (include.association.through.model.rawAttributes[attr]._autoGenerated || + attr === include.association.foreignKey || + attr === include.association.otherKey || + typeof associationInstance[include.association.through.model.name][attr] === undefined) { + continue; } + values[attr] = associationInstance[include.association.through.model.name][attr]; } - - valueSets.push(values); } - const throughOptions = _(Utils.cloneDeep(include)) - .omit(['association', 'attributes']) - .defaults({ - transaction: options.transaction, - logging: options.logging - }).value(); - throughOptions.model = include.association.throughModel; - const throughInstances = include.association.throughModel.bulkBuild(valueSets, throughOptions); - - return recursiveBulkCreate(throughInstances, throughOptions); - } - }); - })); - }).then(() => { - // map fields back to attributes - instances.forEach(instance => { - for (const attr in model.rawAttributes) { - if (model.rawAttributes[attr].field && - instance.dataValues[model.rawAttributes[attr].field] !== undefined && - model.rawAttributes[attr].field !== attr - ) { - instance.dataValues[attr] = instance.dataValues[model.rawAttributes[attr].field]; - delete instance.dataValues[model.rawAttributes[attr].field]; + valueSets.push(values); } - instance._previousDataValues[attr] = instance.dataValues[attr]; - instance.changed(attr, false); + + const throughOptions = _(Utils.cloneDeep(include)) + .omit(['association', 'attributes']) + .defaults({ + transaction: options.transaction, + logging: options.logging + }).value(); + throughOptions.model = include.association.throughModel; + const throughInstances = include.association.throughModel.bulkBuild(valueSets, throughOptions); + + await recursiveBulkCreate(throughInstances, throughOptions); } - instance.isNewRecord = false; - }); + })); + } - // Run after hook - if (options.hooks) { - return model.runHooks('afterBulkCreate', instances, options); + // map fields back to attributes + instances.forEach(instance => { + for (const attr in model.rawAttributes) { + if (model.rawAttributes[attr].field && + instance.dataValues[model.rawAttributes[attr].field] !== undefined && + model.rawAttributes[attr].field !== attr + ) { + instance.dataValues[attr] = instance.dataValues[model.rawAttributes[attr].field]; + delete instance.dataValues[model.rawAttributes[attr].field]; + } + instance._previousDataValues[attr] = instance.dataValues[attr]; + instance.changed(attr, false); } - }).then(() => instances); + instance.isNewRecord = false; + }); + + // Run after hook + if (options.hooks) { + await model.runHooks('afterBulkCreate', instances, options); + } + + return instances; }; - return recursiveBulkCreate(instances, options); + return await recursiveBulkCreate(instances, options); } /** @@ -2863,10 +2839,10 @@ class Model { * @see * {@link Model.destroy} for more information */ - static truncate(options) { + static async truncate(options) { options = Utils.cloneDeep(options) || {}; options.truncate = true; - return this.destroy(options); + return await this.destroy(options); } /** @@ -2887,7 +2863,7 @@ class Model { * * @returns {Promise} The number of destroyed rows */ - static destroy(options) { + static async destroy(options) { options = Utils.cloneDeep(options); this._injectScope(options); @@ -2913,52 +2889,48 @@ class Model { Utils.mapOptionFieldNames(options, this); options.model = this; + + // Run before hook + if (options.hooks) { + await this.runHooks('beforeBulkDestroy', options); + } let instances; + // Get daos and run beforeDestroy hook on each record individually + if (options.individualHooks) { + instances = await this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }); - return Promise.resolve().then(() => { - // Run before hook - if (options.hooks) { - return this.runHooks('beforeBulkDestroy', options); - } - }).then(() => { - // Get daos and run beforeDestroy hook on each record individually - if (options.individualHooks) { - return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }).then(value => Promise.all(value.map(instance => this.runHooks('beforeDestroy', instance, options).then(() => instance)))) - .then(_instances => { - instances = _instances; - }); - } - }).then(() => { - // Run delete query (or update if paranoid) - if (this._timestampAttributes.deletedAt && !options.force) { - // Set query type appropriately when running soft delete - options.type = QueryTypes.BULKUPDATE; - - const attrValueHash = {}; - const deletedAtAttribute = this.rawAttributes[this._timestampAttributes.deletedAt]; - const field = this.rawAttributes[this._timestampAttributes.deletedAt].field; - const where = { - [field]: Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null - }; - - - attrValueHash[field] = Utils.now(this.sequelize.options.dialect); - return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); - } - return this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this); - }).then(result => { - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - return Promise.resolve(Promise.all(instances.map(instance => this.runHooks('afterDestroy', instance, options)))).then(() => result); - } - return result; - }).then(result => { - // Run after hook - if (options.hooks) { - return Promise.resolve(this.runHooks('afterBulkDestroy', options)).then(() => result); - } - return result; - }); + await Promise.all(instances.map(instance => this.runHooks('beforeDestroy', instance, options))); + } + let result; + // Run delete query (or update if paranoid) + if (this._timestampAttributes.deletedAt && !options.force) { + // Set query type appropriately when running soft delete + options.type = QueryTypes.BULKUPDATE; + + const attrValueHash = {}; + const deletedAtAttribute = this.rawAttributes[this._timestampAttributes.deletedAt]; + const field = this.rawAttributes[this._timestampAttributes.deletedAt].field; + const where = { + [field]: Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null + }; + + + attrValueHash[field] = Utils.now(this.sequelize.options.dialect); + result = await this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); + } else { + result = await this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this); + } + // Run afterDestroy hook on each record individually + if (options.individualHooks) { + await Promise.all( + instances.map(instance => this.runHooks('afterDestroy', instance, options)) + ); + } + // Run after hook + if (options.hooks) { + await this.runHooks('afterBulkDestroy', options); + } + return result; } /** @@ -2975,7 +2947,7 @@ class Model { * * @returns {Promise} */ - static restore(options) { + static async restore(options) { if (!this._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); options = Object.assign({ @@ -2986,46 +2958,40 @@ class Model { options.type = QueryTypes.RAW; options.model = this; - let instances; - Utils.mapOptionFieldNames(options, this); - return Promise.resolve().then(() => { - // Run before hook - if (options.hooks) { - return this.runHooks('beforeBulkRestore', options); - } - }).then(() => { - // Get daos and run beforeRestore hook on each record individually - if (options.individualHooks) { - return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }).then(value => Promise.all(value.map(instance => this.runHooks('beforeRestore', instance, options).then(() => instance)))) - .then(_instances => { - instances = _instances; - }); - } - }).then(() => { - // Run undelete query - const attrValueHash = {}; - const deletedAtCol = this._timestampAttributes.deletedAt; - const deletedAtAttribute = this.rawAttributes[deletedAtCol]; - const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - - attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; - options.omitNull = false; - return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); - }).then(result => { - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - return Promise.resolve(Promise.all(instances.map(instance => this.runHooks('afterRestore', instance, options)))).then(() => result); - } - return result; - }).then(result => { - // Run after hook - if (options.hooks) { - return Promise.resolve(this.runHooks('afterBulkRestore', options)).then(() => result); - } - return result; - }); + // Run before hook + if (options.hooks) { + await this.runHooks('beforeBulkRestore', options); + } + + let instances; + // Get daos and run beforeRestore hook on each record individually + if (options.individualHooks) { + instances = await this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }); + + await Promise.all(instances.map(instance => this.runHooks('beforeRestore', instance, options))); + } + // Run undelete query + const attrValueHash = {}; + const deletedAtCol = this._timestampAttributes.deletedAt; + const deletedAtAttribute = this.rawAttributes[deletedAtCol]; + const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; + + attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; + options.omitNull = false; + const result = await this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); + // Run afterDestroy hook on each record individually + if (options.individualHooks) { + await Promise.all( + instances.map(instance => this.runHooks('afterRestore', instance, options)) + ); + } + // Run after hook + if (options.hooks) { + await this.runHooks('afterBulkRestore', options); + } + return result; } /** @@ -3051,7 +3017,7 @@ class Model { * of affected rows, while the second element is the actual affected rows (only supported in postgres with `options.returning` true). * */ - static update(values, options) { + static async update(values, options) { options = Utils.cloneDeep(options); this._injectScope(options); @@ -3092,166 +3058,136 @@ class Model { options.model = this; - let instances; let valuesUse; + // Validate + if (options.validate) { + const build = this.build(values); + build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); - return Promise.resolve().then(() => { - // Validate - if (options.validate) { - const build = this.build(values); - build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); - - if (options.sideEffects) { - values = Object.assign(values, _.pick(build.get(), build.changed())); - options.fields = _.union(options.fields, Object.keys(values)); - } - - // We want to skip validations for all other fields - options.skip = _.difference(Object.keys(this.rawAttributes), Object.keys(values)); - return build.validate(options).then(attributes => { - options.skip = undefined; - if (attributes && attributes.dataValues) { - values = _.pick(attributes.dataValues, Object.keys(values)); - } - }); + if (options.sideEffects) { + values = Object.assign(values, _.pick(build.get(), build.changed())); + options.fields = _.union(options.fields, Object.keys(values)); } - return null; - }).then(() => { - // Run before hook - if (options.hooks) { - options.attributes = values; - return this.runHooks('beforeBulkUpdate', options).then(() => { - values = options.attributes; - delete options.attributes; - }); + + // We want to skip validations for all other fields + options.skip = _.difference(Object.keys(this.rawAttributes), Object.keys(values)); + const attributes = await build.validate(options); + options.skip = undefined; + if (attributes && attributes.dataValues) { + values = _.pick(attributes.dataValues, Object.keys(values)); } - return null; - }).then(() => { - valuesUse = values; + } + // Run before hook + if (options.hooks) { + options.attributes = values; + await this.runHooks('beforeBulkUpdate', options); + values = options.attributes; + delete options.attributes; + } - // Get instances and run beforeUpdate hook on each record individually - if (options.individualHooks) { - return this.findAll({ - where: options.where, - transaction: options.transaction, - logging: options.logging, - benchmark: options.benchmark, - paranoid: options.paranoid - }).then(_instances => { - instances = _instances; - if (!instances.length) { - return []; - } + valuesUse = values; + + // Get instances and run beforeUpdate hook on each record individually + let instances; + let updateDoneRowByRow = false; + if (options.individualHooks) { + instances = await this.findAll({ + where: options.where, + transaction: options.transaction, + logging: options.logging, + benchmark: options.benchmark, + paranoid: options.paranoid + }); - // Run beforeUpdate hooks on each record and check whether beforeUpdate hook changes values uniformly - // i.e. whether they change values for each record in the same way - let changedValues; - let different = false; + if (instances.length) { + // Run beforeUpdate hooks on each record and check whether beforeUpdate hook changes values uniformly + // i.e. whether they change values for each record in the same way + let changedValues; + let different = false; + + instances = await Promise.all(instances.map(async instance => { + // Record updates in instances dataValues + Object.assign(instance.dataValues, values); + // Set the changed fields on the instance + _.forIn(valuesUse, (newValue, attr) => { + if (newValue !== instance._previousDataValues[attr]) { + instance.setDataValue(attr, newValue); + } + }); - return Promise.all(instances.map(instance => { - // Record updates in instances dataValues - Object.assign(instance.dataValues, values); - // Set the changed fields on the instance - _.forIn(valuesUse, (newValue, attr) => { + // Run beforeUpdate hook + await this.runHooks('beforeUpdate', instance, options); + if (!different) { + const thisChangedValues = {}; + _.forIn(instance.dataValues, (newValue, attr) => { if (newValue !== instance._previousDataValues[attr]) { - instance.setDataValue(attr, newValue); + thisChangedValues[attr] = newValue; } }); - // Run beforeUpdate hook - return this.runHooks('beforeUpdate', instance, options).then(() => { - if (!different) { - const thisChangedValues = {}; - _.forIn(instance.dataValues, (newValue, attr) => { - if (newValue !== instance._previousDataValues[attr]) { - thisChangedValues[attr] = newValue; - } - }); + if (!changedValues) { + changedValues = thisChangedValues; + } else { + different = !_.isEqual(changedValues, thisChangedValues); + } + } - if (!changedValues) { - changedValues = thisChangedValues; - } else { - different = !_.isEqual(changedValues, thisChangedValues); - } - } + return instance; + })); - return instance; - }); - })).then(_instances => { - instances = _instances; - - if (!different) { - const keys = Object.keys(changedValues); - // Hooks do not change values or change them uniformly - if (keys.length) { - // Hooks change values - record changes in valuesUse so they are executed - valuesUse = changedValues; - options.fields = _.union(options.fields, keys); - } - return; - } - // Hooks change values in a different way for each record - // Do not run original query but save each record individually - return Promise.all(instances.map(instance => { - const individualOptions = _.clone(options); - delete individualOptions.individualHooks; - individualOptions.hooks = false; - individualOptions.validate = false; - - return instance.save(individualOptions); - })).then(_instances => { - instances = _instances; - return _instances; - }); - }); - }); - } - }).then(results => { - // Update already done row-by-row - exit - if (results) { - return [results.length, results]; - } + if (!different) { + const keys = Object.keys(changedValues); + // Hooks do not change values or change them uniformly + if (keys.length) { + // Hooks change values - record changes in valuesUse so they are executed + valuesUse = changedValues; + options.fields = _.union(options.fields, keys); + } + } else { + instances = await Promise.all(instances.map(async instance => { + const individualOptions = _.clone(options); + delete individualOptions.individualHooks; + individualOptions.hooks = false; + individualOptions.validate = false; - // only updatedAt is being passed, then skip update - if ( - _.isEmpty(valuesUse) - || Object.keys(valuesUse).length === 1 && valuesUse[this._timestampAttributes.updatedAt] - ) { - return [0]; + return instance.save(individualOptions); + })); + updateDoneRowByRow = true; + } } - + } + let result; + if (updateDoneRowByRow) { + result = [instances.length, instances]; + } else if (_.isEmpty(valuesUse) + || Object.keys(valuesUse).length === 1 && valuesUse[this._timestampAttributes.updatedAt]) { + // only updatedAt is being passed, then skip update + result = [0]; + } else { valuesUse = Utils.mapValueFieldNames(valuesUse, options.fields, this); options = Utils.mapOptionFieldNames(options, this); options.hasTrigger = this.options ? this.options.hasTrigger : false; - // Run query to update all rows - return this.QueryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes).then(affectedRows => { - if (options.returning) { - instances = affectedRows; - return [affectedRows.length, affectedRows]; - } - - return [affectedRows]; - }); - }).then(result => { - if (options.individualHooks) { - return Promise.resolve(Promise.all(instances.map(instance => { - return Promise.resolve(this.runHooks('afterUpdate', instance, options)).then(() => result); - })).then(() => { - result[1] = instances; - })).then(() => result); - } - return result; - }).then(result => { - // Run after hook - if (options.hooks) { - options.attributes = values; - return Promise.resolve(this.runHooks('afterBulkUpdate', options).then(() => { - delete options.attributes; - })).then(() => result); + const affectedRows = await this.QueryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes); + if (options.returning) { + result = [affectedRows.length, affectedRows]; + instances = affectedRows; + } else { + result = [affectedRows]; } - return result; - }); + } + + if (options.individualHooks) { + await Promise.all(instances.map(instance => this.runHooks('afterUpdate', instance, options))); + result[1] = instances; + } + // Run after hook + if (options.hooks) { + options.attributes = values; + await this.runHooks('afterBulkUpdate', options); + delete options.attributes; + } + return result; } /** @@ -3262,8 +3198,8 @@ class Model { * * @returns {Promise} hash of attributes and their types */ - static describe(schema, options) { - return this.QueryInterface.describeTable(this.tableName, Object.assign({ schema: schema || this._schema || undefined }, options)); + static async describe(schema, options) { + return await this.QueryInterface.describeTable(this.tableName, Object.assign({ schema: schema || this._schema || undefined }, options)); } static _getDefaultTimestamp(attr) { @@ -3336,7 +3272,7 @@ class Model { * * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect */ - static increment(fields, options) { + static async increment(fields, options) { options = options || {}; if (typeof fields === 'string') fields = [fields]; if (Array.isArray(fields)) { @@ -3392,24 +3328,22 @@ class Model { } const tableName = this.getTableName(options); - let promise; + let affectedRows; if (isSubtraction) { - promise = this.QueryInterface.decrement( + affectedRows = await this.QueryInterface.decrement( this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options ); } else { - promise = this.QueryInterface.increment( + affectedRows = await this.QueryInterface.increment( this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options ); } - return promise.then(affectedRows => { - if (options.returning) { - return [affectedRows, affectedRows.length]; - } + if (options.returning) { + return [affectedRows, affectedRows.length]; + } - return [affectedRows]; - }); + return [affectedRows]; } /** @@ -3437,12 +3371,12 @@ class Model { * * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect */ - static decrement(fields, options) { + static async decrement(fields, options) { options = _.defaults({ increment: false }, options, { by: 1 }); - return this.increment(fields, options); + return await this.increment(fields, options); } static _optionsMustContainWhere(options) { @@ -3863,7 +3797,7 @@ class Model { * * @returns {Promise} */ - save(options) { + async save(options) { if (arguments.length > 1) { throw new Error('The second argument was removed in favor of the options object.'); } @@ -3938,59 +3872,49 @@ class Model { this.dataValues[createdAtAttr] = this.constructor._getDefaultTimestamp(createdAtAttr) || now; } - return Promise.resolve().then(() => { - // Validate - if (options.validate) { - return this.validate(options); - } - }).then(() => { - // Run before hook - if (options.hooks) { - const beforeHookValues = _.pick(this.dataValues, options.fields); - let ignoreChanged = _.difference(this.changed(), options.fields); // In case of update where it's only supposed to update the passed values and the hook values - let hookChanged; - let afterHookValues; + // Validate + if (options.validate) { + await this.validate(options); + } + // Run before hook + if (options.hooks) { + const beforeHookValues = _.pick(this.dataValues, options.fields); + let ignoreChanged = _.difference(this.changed(), options.fields); // In case of update where it's only supposed to update the passed values and the hook values + let hookChanged; + let afterHookValues; - if (updatedAtAttr && options.fields.includes(updatedAtAttr)) { - ignoreChanged = _.without(ignoreChanged, updatedAtAttr); - } + if (updatedAtAttr && options.fields.includes(updatedAtAttr)) { + ignoreChanged = _.without(ignoreChanged, updatedAtAttr); + } - return this.constructor.runHooks(`before${hook}`, this, options) - .then(() => { - if (options.defaultFields && !this.isNewRecord) { - afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged)); + await this.constructor.runHooks(`before${hook}`, this, options); + if (options.defaultFields && !this.isNewRecord) { + afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged)); - hookChanged = []; - for (const key of Object.keys(afterHookValues)) { - if (afterHookValues[key] !== beforeHookValues[key]) { - hookChanged.push(key); - } - } + hookChanged = []; + for (const key of Object.keys(afterHookValues)) { + if (afterHookValues[key] !== beforeHookValues[key]) { + hookChanged.push(key); + } + } - options.fields = _.uniq(options.fields.concat(hookChanged)); - } + options.fields = _.uniq(options.fields.concat(hookChanged)); + } - if (hookChanged) { - if (options.validate) { - // Validate again + if (hookChanged) { + if (options.validate) { + // Validate again - options.skip = _.difference(Object.keys(this.constructor.rawAttributes), hookChanged); - return this.validate(options).then(() => { - delete options.skip; - }); - } - } - }); + options.skip = _.difference(Object.keys(this.constructor.rawAttributes), hookChanged); + await this.validate(options); + delete options.skip; + } } - }).then(() => { - if (!options.fields.length) return this; - if (!this.isNewRecord) return this; - if (!this._options.include || !this._options.include.length) return this; - - // Nested creation for BelongsTo relations - return Promise.all(this._options.include.filter(include => include.association instanceof BelongsTo).map(include => { + } + if (options.fields.length && this.isNewRecord && this._options.include && this._options.include.length) { + await Promise.all(this._options.include.filter(include => include.association instanceof BelongsTo).map(async include => { const instance = this.get(include.as); - if (!instance) return Promise.resolve(); + if (!instance) return; const includeOptions = _(Utils.cloneDeep(include)) .omit(['association']) @@ -4000,129 +3924,120 @@ class Model { parentRecord: this }).value(); - return instance.save(includeOptions).then(() => this[include.association.accessors.set](instance, { save: false, logging: options.logging })); + await instance.save(includeOptions); + + await this[include.association.accessors.set](instance, { save: false, logging: options.logging }); })); - }).then(() => { - const realFields = options.fields.filter(field => !this.constructor._virtualAttributes.has(field)); - if (!realFields.length) return this; - if (!this.changed() && !this.isNewRecord) return this; + } + const realFields = options.fields.filter(field => !this.constructor._virtualAttributes.has(field)); + if (!realFields.length) return this; + if (!this.changed() && !this.isNewRecord) return this; - const versionFieldName = _.get(this.constructor.rawAttributes[versionAttr], 'field') || versionAttr; - let values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor); - let query = null; - let args = []; - let where; + const versionFieldName = _.get(this.constructor.rawAttributes[versionAttr], 'field') || versionAttr; + let values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor); + let query = null; + let args = []; + let where; - if (this.isNewRecord) { - query = 'insert'; - args = [this, this.constructor.getTableName(options), values, options]; + if (this.isNewRecord) { + query = 'insert'; + args = [this, this.constructor.getTableName(options), values, options]; + } else { + where = this.where(true); + if (versionAttr) { + values[versionFieldName] = parseInt(values[versionFieldName], 10) + 1; + } + query = 'update'; + args = [this, this.constructor.getTableName(options), values, where, options]; + } + + const [result, rowsUpdated] = await this.constructor.QueryInterface[query](...args); + if (versionAttr) { + // Check to see that a row was updated, otherwise it's an optimistic locking error. + if (rowsUpdated < 1) { + throw new sequelizeErrors.OptimisticLockError({ + modelName: this.constructor.name, + values, + where + }); } else { - where = this.where(true); - if (versionAttr) { - values[versionFieldName] = parseInt(values[versionFieldName], 10) + 1; - } - query = 'update'; - args = [this, this.constructor.getTableName(options), values, where, options]; + result.dataValues[versionAttr] = values[versionFieldName]; } + } - return this.constructor.QueryInterface[query](...args) - .then(([result, rowsUpdated])=> { - if (versionAttr) { - // Check to see that a row was updated, otherwise it's an optimistic locking error. - if (rowsUpdated < 1) { - throw new sequelizeErrors.OptimisticLockError({ - modelName: this.constructor.name, - values, - where - }); - } else { - result.dataValues[versionAttr] = values[versionFieldName]; - } - } - - // Transfer database generated values (defaults, autoincrement, etc) - for (const attr of Object.keys(this.constructor.rawAttributes)) { - if (this.constructor.rawAttributes[attr].field && - values[this.constructor.rawAttributes[attr].field] !== undefined && - this.constructor.rawAttributes[attr].field !== attr - ) { - values[attr] = values[this.constructor.rawAttributes[attr].field]; - delete values[this.constructor.rawAttributes[attr].field]; - } - } - values = Object.assign(values, result.dataValues); - - result.dataValues = Object.assign(result.dataValues, values); - return result; - }) - .then(result => { - if (!wasNewRecord) return Promise.resolve(this).then(() => result); - if (!this._options.include || !this._options.include.length) return Promise.resolve(this).then(() => result); + // Transfer database generated values (defaults, autoincrement, etc) + for (const attr of Object.keys(this.constructor.rawAttributes)) { + if (this.constructor.rawAttributes[attr].field && + values[this.constructor.rawAttributes[attr].field] !== undefined && + this.constructor.rawAttributes[attr].field !== attr + ) { + values[attr] = values[this.constructor.rawAttributes[attr].field]; + delete values[this.constructor.rawAttributes[attr].field]; + } + } + values = Object.assign(values, result.dataValues); - // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.resolve(Promise.all(this._options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)).map(include => { - let instances = this.get(include.as); + result.dataValues = Object.assign(result.dataValues, values); + if (wasNewRecord && this._options.include && this._options.include.length) { + await Promise.all( + this._options.include.filter(include => !(include.association instanceof BelongsTo || + include.parent && include.parent.association instanceof BelongsToMany)).map(async include => { + let instances = this.get(include.as); - if (!instances) return Promise.resolve(Promise.resolve()).then(() => result); - if (!Array.isArray(instances)) instances = [instances]; - if (!instances.length) return Promise.resolve(Promise.resolve()).then(() => result); + if (!instances) return; + if (!Array.isArray(instances)) instances = [instances]; - const includeOptions = _(Utils.cloneDeep(include)) - .omit(['association']) - .defaults({ - transaction: options.transaction, - logging: options.logging, - parentRecord: this - }).value(); + const includeOptions = _(Utils.cloneDeep(include)) + .omit(['association']) + .defaults({ + transaction: options.transaction, + logging: options.logging, + parentRecord: this + }).value(); - // Instances will be updated in place so we can safely treat HasOne like a HasMany - return Promise.resolve(Promise.all(instances.map(instance => { - if (include.association instanceof BelongsToMany) { - return Promise.resolve(instance.save(includeOptions).then(() => { - const values = {}; - values[include.association.foreignKey] = this.get(this.constructor.primaryKeyAttribute, { raw: true }); - values[include.association.otherKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); - - // Include values defined in the association - Object.assign(values, include.association.through.scope); - if (instance[include.association.through.model.name]) { - for (const attr of Object.keys(include.association.through.model.rawAttributes)) { - if (include.association.through.model.rawAttributes[attr]._autoGenerated || - attr === include.association.foreignKey || - attr === include.association.otherKey || - typeof instance[include.association.through.model.name][attr] === undefined) { - continue; - } - values[attr] = instance[include.association.through.model.name][attr]; - } + // Instances will be updated in place so we can safely treat HasOne like a HasMany + await Promise.all(instances.map(async instance => { + if (include.association instanceof BelongsToMany) { + await instance.save(includeOptions); + const values0 = {}; + values0[include.association.foreignKey] = this.get(this.constructor.primaryKeyAttribute, { raw: true }); + values0[include.association.otherKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); + + // Include values defined in the association + Object.assign(values0, include.association.through.scope); + if (instance[include.association.through.model.name]) { + for (const attr of Object.keys(include.association.through.model.rawAttributes)) { + if (include.association.through.model.rawAttributes[attr]._autoGenerated || + attr === include.association.foreignKey || + attr === include.association.otherKey || + typeof instance[include.association.through.model.name][attr] === undefined) { + continue; } - - return Promise.resolve(include.association.throughModel.create(values, includeOptions)).then(() => result); - })).then(() => result); + values0[attr] = instance[include.association.through.model.name][attr]; + } } + + await include.association.throughModel.create(values0, includeOptions); + } else { instance.set(include.association.foreignKey, this.get(include.association.sourceKey || this.constructor.primaryKeyAttribute, { raw: true }), { raw: true }); Object.assign(instance, include.association.scope); - return Promise.resolve(instance.save(includeOptions)).then(() => result); - }))).then(() => result); - }))).then(() => result); - }) - .then(result => { - // Run after hook - if (options.hooks) { - return Promise.resolve(this.constructor.runHooks(`after${hook}`, result, options)).then(() => result); - } - return result; + await instance.save(includeOptions); + } + })); }) - .then(result => { - for (const field of options.fields) { - result._previousDataValues[field] = result.dataValues[field]; - this.changed(field, false); - } - this.isNewRecord = false; - return result; - }); - }); + ); + } + // Run after hook + if (options.hooks) { + await this.constructor.runHooks(`after${hook}`, result, options); + } + for (const field of options.fields) { + result._previousDataValues[field] = result.dataValues[field]; + this.changed(field, false); + } + this.isNewRecord = false; + + return result; } /** @@ -4138,31 +4053,27 @@ class Model { * * @returns {Promise} */ - reload(options) { + async reload(options) { options = Utils.defaults({}, options, { where: this.where(), include: this._options.include || null }); - return this.constructor.findOne(options) - .then(reload => { - if (!reload) { - throw new sequelizeErrors.InstanceError( - 'Instance could not be reloaded because it does not exist anymore (find call returned null)' - ); - } - return reload; - }) - .then(reload => { - // update the internal options of the instance - this._options = reload._options; - // re-set instance values - this.set(reload.dataValues, { - raw: true, - reset: true && !options.attributes - }); - return this; - }); + const reloaded = await this.constructor.findOne(options); + if (!reloaded) { + throw new sequelizeErrors.InstanceError( + 'Instance could not be reloaded because it does not exist anymore (find call returned null)' + ); + } + // update the internal options of the instance + this._options = reloaded._options; + // re-set instance values + this.set(reloaded.dataValues, { + raw: true, + reset: true && !options.attributes + }); + + return this; } /** @@ -4177,8 +4088,8 @@ class Model { * * @returns {Promise} */ - validate(options) { - return new InstanceValidator(this, options).validate(); + async validate(options) { + return await new InstanceValidator(this, options).validate(); } /** @@ -4195,7 +4106,7 @@ class Model { * * @returns {Promise} */ - update(values, options) { + async update(values, options) { // Clone values so it doesn't get modified for caller scope and ignore undefined values values = _.omitBy(values, value => value === undefined); @@ -4218,7 +4129,7 @@ class Model { options.defaultFields = options.fields; } - return this.save(options); + return await this.save(options); } /** @@ -4232,43 +4143,41 @@ class Model { * * @returns {Promise} */ - destroy(options) { + async destroy(options) { options = Object.assign({ hooks: true, force: false }, options); - return Promise.resolve().then(() => { - // Run before hook - if (options.hooks) { - return this.constructor.runHooks('beforeDestroy', this, options); - } - }).then(() => { - const where = this.where(true); - - if (this.constructor._timestampAttributes.deletedAt && options.force === false) { - const attributeName = this.constructor._timestampAttributes.deletedAt; - const attribute = this.constructor.rawAttributes[attributeName]; - const defaultValue = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue') - ? attribute.defaultValue - : null; - const currentValue = this.getDataValue(attributeName); - const undefinedOrNull = currentValue == null && defaultValue == null; - if (undefinedOrNull || _.isEqual(currentValue, defaultValue)) { - // only update timestamp if it wasn't already set - this.setDataValue(attributeName, new Date()); - } + // Run before hook + if (options.hooks) { + await this.constructor.runHooks('beforeDestroy', this, options); + } + const where = this.where(true); - return this.save(_.defaults({ hooks: false }, options)); + let result; + if (this.constructor._timestampAttributes.deletedAt && options.force === false) { + const attributeName = this.constructor._timestampAttributes.deletedAt; + const attribute = this.constructor.rawAttributes[attributeName]; + const defaultValue = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue') + ? attribute.defaultValue + : null; + const currentValue = this.getDataValue(attributeName); + const undefinedOrNull = currentValue == null && defaultValue == null; + if (undefinedOrNull || _.isEqual(currentValue, defaultValue)) { + // only update timestamp if it wasn't already set + this.setDataValue(attributeName, new Date()); } - return this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options)); - }).then(result => { - // Run after hook - if (options.hooks) { - return Promise.resolve(this.constructor.runHooks('afterDestroy', this, options)).then(() => result); - } - return result; - }); + + result = await this.save(_.defaults({ hooks: false }, options)); + } else { + result = await this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options)); + } + // Run after hook + if (options.hooks) { + await this.constructor.runHooks('afterDestroy', this, options); + } + return result; } /** @@ -4300,7 +4209,7 @@ class Model { * * @returns {Promise} */ - restore(options) { + async restore(options) { if (!this.constructor._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); options = Object.assign({ @@ -4308,25 +4217,22 @@ class Model { force: false }, options); - return Promise.resolve().then(() => { - // Run before hook - if (options.hooks) { - return this.constructor.runHooks('beforeRestore', this, options); - } - }).then(() => { - const deletedAtCol = this.constructor._timestampAttributes.deletedAt; - const deletedAtAttribute = this.constructor.rawAttributes[deletedAtCol]; - const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - - this.setDataValue(deletedAtCol, deletedAtDefaultValue); - return this.save(Object.assign({}, options, { hooks: false, omitNull: false })); - }).then(result => { - // Run after hook - if (options.hooks) { - return Promise.resolve(this.constructor.runHooks('afterRestore', this, options)).then(() => result); - } + // Run before hook + if (options.hooks) { + await this.constructor.runHooks('beforeRestore', this, options); + } + const deletedAtCol = this.constructor._timestampAttributes.deletedAt; + const deletedAtAttribute = this.constructor.rawAttributes[deletedAtCol]; + const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; + + this.setDataValue(deletedAtCol, deletedAtDefaultValue); + const result = await this.save(Object.assign({}, options, { hooks: false, omitNull: false })); + // Run after hook + if (options.hooks) { + await this.constructor.runHooks('afterRestore', this, options); return result; - }); + } + return result; } /** @@ -4360,14 +4266,16 @@ class Model { * @returns {Promise} * @since 4.0.0 */ - increment(fields, options) { + async increment(fields, options) { const identifier = this.where(); options = Utils.cloneDeep(options); options.where = Object.assign({}, options.where, identifier); options.instance = this; - return this.constructor.increment(fields, options).then(() => this); + await this.constructor.increment(fields, options); + + return this; } /** @@ -4399,12 +4307,12 @@ class Model { * * @returns {Promise} */ - decrement(fields, options) { + async decrement(fields, options) { options = _.defaults({ increment: false }, options, { by: 1 }); - return this.increment(fields, options); + return await this.increment(fields, options); } /** diff --git a/test/integration/instance.test.js b/test/integration/instance.test.js index 29269bc7783a..f51b2bf6ec8c 100644 --- a/test/integration/instance.test.js +++ b/test/integration/instance.test.js @@ -651,10 +651,9 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('restore', () => { - it('returns an error if the model is not paranoid', function() { - return this.User.create({ username: 'Peter', secretValue: '42' }).then(user => { - expect(() => {user.restore();}).to.throw(Error, 'Model is not paranoid'); - }); + it('returns an error if the model is not paranoid', async function() { + const user = await this.User.create({ username: 'Peter', secretValue: '42' }); + await expect(user.restore()).to.be.rejectedWith(Error, 'Model is not paranoid'); }); it('restores a previously deleted model', function() { diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 319758d0050f..5a5af2bebdc2 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -1555,11 +1555,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('restore', () => { - it('synchronously throws an error if the model is not paranoid', async function() { - expect(() => { - this.User.restore({ where: { secretValue: '42' } }); - throw new Error('Did not throw synchronously'); - }).to.throw(Error, 'Model is not paranoid'); + it('rejects with an error if the model is not paranoid', async function() { + await expect(this.User.restore({ where: { secretValue: '42' } })).to.be.rejectedWith(Error, 'Model is not paranoid'); }); it('restores a previously deleted model', async function() { diff --git a/test/unit/increment.test.js b/test/unit/increment.test.js index 2cf276057a2b..4568912ba9e9 100644 --- a/test/unit/increment.test.js +++ b/test/unit/increment.test.js @@ -18,14 +18,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { count: Sequelize.BIGINT }); - it('should reject if options are missing', () => { - return expect(() => Model.increment(['id', 'count'])) - .to.throw('Missing where attribute in the options parameter'); + it('should reject if options are missing', async () => { + await expect(Model.increment(['id', 'count'])) + .to.be.rejectedWith('Missing where attribute in the options parameter'); }); - it('should reject if options.where are missing', () => { - return expect(() => Model.increment(['id', 'count'], { by: 10 })) - .to.throw('Missing where attribute in the options parameter'); + it('should reject if options.where are missing', async () => { + await expect(Model.increment(['id', 'count'], { by: 10 })) + .to.be.rejectedWith('Missing where attribute in the options parameter'); }); }); }); diff --git a/test/unit/instance/save.test.js b/test/unit/instance/save.test.js index 2005eff9cc90..5b87e675514d 100644 --- a/test/unit/instance/save.test.js +++ b/test/unit/instance/save.test.js @@ -9,15 +9,13 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('save', () => { - it('should disallow saves if no primary key values is present', () => { + it('should disallow saves if no primary key values is present', async () => { const Model = current.define('User', { }), instance = Model.build({}, { isNewRecord: false }); - expect(() => { - instance.save(); - }).to.throw(); + await expect(instance.save()).to.be.rejected; }); describe('options tests', () => { diff --git a/test/unit/model/destroy.test.js b/test/unit/model/destroy.test.js index 52728552da36..9a98fcecbb71 100644 --- a/test/unit/model/destroy.test.js +++ b/test/unit/model/destroy.test.js @@ -35,13 +35,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.stubDelete.restore(); }); - it('can detect complex objects', () => { + it('can detect complex objects', async () => { const Where = function() { this.secretValue = '1'; }; - expect(() => { - User.destroy({ where: new Where() }); - }).to.throw(); - + await expect(User.destroy({ where: new Where() })).to.be.rejected; }); }); }); diff --git a/test/unit/model/findall.test.js b/test/unit/model/findall.test.js index dac8db6e9afe..0000b1911728 100644 --- a/test/unit/model/findall.test.js +++ b/test/unit/model/findall.test.js @@ -65,9 +65,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(this.warnOnInvalidOptionsStub.calledOnce).to.equal(true); }); - it('Throws an error when the attributes option is formatted incorrectly', () => { - const errorFunction = Model.findAll.bind(Model, { attributes: 'name' }); - expect(errorFunction).to.throw(sequelizeErrors.QueryError); + it('Throws an error when the attributes option is formatted incorrectly', async () => { + await expect(Model.findAll({ attributes: 'name' })).to.be.rejectedWith(sequelizeErrors.QueryError); }); }); diff --git a/test/unit/model/update.test.js b/test/unit/model/update.test.js index 0dad10210842..356f873fecf8 100644 --- a/test/unit/model/update.test.js +++ b/test/unit/model/update.test.js @@ -46,12 +46,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('can detect complexe objects', function() { + it('can detect complexe objects', async function() { const Where = function() { this.secretValue = '1'; }; - expect(() => { - this.User.update(this.updates, { where: new Where() }); - }).to.throw(); + await expect(this.User.update(this.updates, { where: new Where() })).to.be.rejected; }); }); }); From 5a1472bad9bb3f99aa8844aa1125f7720db1d75e Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 19 Apr 2020 02:39:02 -0500 Subject: [PATCH 084/414] test: replace bluebird .throw calls (#12110) --- test/integration/transaction.test.js | 67 ++++++++++++---------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 52283486c8b6..fb6b92e137c8 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -132,36 +132,27 @@ if (current.dialect.supports.transactions) { }); - it('does not allow queries after commit', function() { - return this.sequelize.transaction().then(t => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }).then(() => { - return t.commit(); - }).then(() => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); - }); - }).throw(new Error('Expected error not thrown')) - .catch(err => { - expect(err.message).to.match(/commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/); - expect(err.sql).to.equal('SELECT 1+1'); - }); + it('does not allow queries after commit', async function() { + const t = await this.sequelize.transaction(); + await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + await t.commit(); + await expect(this.sequelize.query('SELECT 1+1', { transaction: t, raw: true })).to.be.eventually.rejectedWith( + Error, + /commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ + ).and.have.deep.property('sql').that.equal('SELECT 1+1'); }); - it('does not allow queries immediately after commit call', function() { - return expect( - this.sequelize.transaction().then(t => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }).then(() => { - return Promise.join( - expect(t.commit()).to.eventually.be.fulfilled, - this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }) - .throw(new Error('Expected error not thrown')) - .catch(err => { - expect(err.message).to.match(/commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/); - expect(err.sql).to.equal('SELECT 1+1'); - }) - ); - }); - }) - ).to.be.eventually.fulfilled; + it('does not allow queries immediately after commit call', async function() { + await expect(this.sequelize.transaction().then(async t => { + await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + await Promise.all([ + expect(t.commit()).to.eventually.be.fulfilled, + expect(this.sequelize.query('SELECT 1+1', { transaction: t, raw: true })).to.be.eventually.rejectedWith( + Error, + /commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ + ).and.have.deep.property('sql').that.equal('SELECT 1+1') + ]); + })).to.be.eventually.fulfilled; }); it('does not allow queries after rollback', function() { @@ -186,18 +177,16 @@ if (current.dialect.supports.transactions) { .to.eventually.be.rejectedWith('Transaction cannot be rolled back because it never started'); }); - it('does not allow queries immediately after rollback call', function() { - return expect( - this.sequelize.transaction().then(t => { - return Promise.join( + it('does not allow queries immediately after rollback call', async function() { + await expect( + this.sequelize.transaction().then(async t => { + await Promise.all([ expect(t.rollback()).to.eventually.be.fulfilled, - this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }) - .throw(new Error('Expected error not thrown')) - .catch(err => { - expect(err.message).to.match(/rollback has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/); - expect(err.sql).to.equal('SELECT 1+1'); - }) - ); + expect(this.sequelize.query('SELECT 1+1', { transaction: t, raw: true })).to.be.eventually.rejectedWith( + Error, + /rollback has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ + ).and.have.deep.property('sql').that.equal('SELECT 1+1') + ]); }) ).to.eventually.be.fulfilled; }); From 39c350126e07eb2ef7b578d9377abfef08e2f8f2 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 19 Apr 2020 02:52:01 -0500 Subject: [PATCH 085/414] refactor(associations): asyncify methods (#12123) --- lib/associations/belongs-to-many.js | 17 +- lib/associations/belongs-to.js | 41 +++-- lib/associations/has-many.js | 148 +++++++++--------- lib/associations/has-one.js | 84 +++++----- .../associations/belongs-to.test.js | 4 +- 5 files changed, 147 insertions(+), 147 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 7ab3df903266..8858cbf55959 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -608,13 +608,12 @@ class BelongsToMany extends Association { } if (obsoleteAssociations.length > 0) { - const where = Object.assign({ - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]) - }, this.through.scope); promises.push( this.through.model.destroy(_.defaults({ - where + where: Object.assign({ + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]) + }, this.through.scope) }, options)) ); } @@ -724,13 +723,11 @@ class BelongsToMany extends Association { if (throughAttributes instanceof association.through.model) { throughAttributes = {}; } - const where = { + + promises.push(association.through.model.update(attributes, Object.assign(options, { where: { [identifier]: sourceInstance.get(sourceKey), [foreignIdentifier]: assoc.get(targetKey) - }; - - - promises.push(association.through.model.update(attributes, Object.assign(options, { where }))); + } }))); } return Utils.Promise.all(promises); diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index a48813b40974..59efa360d936 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -123,7 +123,7 @@ class BelongsTo extends Association { * * @returns {Promise} */ - get(instances, options) { + async get(instances, options) { const where = {}; let Target = this.target; let instance; @@ -149,7 +149,7 @@ class BelongsTo extends Association { if (instances) { where[this.targetKey] = { - [Op.in]: instances.map(instance => instance.get(this.foreignKey)) + [Op.in]: instances.map(_instance => _instance.get(this.foreignKey)) }; } else { if (this.targetKeyIsPrimary && !options.where) { @@ -164,18 +164,17 @@ class BelongsTo extends Association { where; if (instances) { - return Target.findAll(options).then(results => { - const result = {}; - for (const instance of instances) { - result[instance.get(this.foreignKey, { raw: true })] = null; - } - - for (const instance of results) { - result[instance.get(this.targetKey, { raw: true })] = instance; - } - - return result; - }); + const results = await Target.findAll(options); + const result = {}; + for (const _instance of instances) { + result[_instance.get(this.foreignKey, { raw: true })] = null; + } + + for (const _instance of results) { + result[_instance.get(this.targetKey, { raw: true })] = _instance; + } + + return result; } return Target.findOne(options); @@ -191,7 +190,7 @@ class BelongsTo extends Association { * * @returns {Promise} */ - set(sourceInstance, associatedInstance, options = {}) { + async set(sourceInstance, associatedInstance, options = {}) { let value = associatedInstance; if (associatedInstance instanceof this.target) { @@ -209,7 +208,7 @@ class BelongsTo extends Association { }, options); // passes the changed field to save, so only that field get updated. - return sourceInstance.save(options); + return await sourceInstance.save(options); } /** @@ -224,14 +223,14 @@ class BelongsTo extends Association { * * @returns {Promise} The created target model */ - create(sourceInstance, values, options) { + async create(sourceInstance, values, options) { values = values || {}; options = options || {}; - return this.target.create(values, options) - .then(newAssociatedObject => sourceInstance[this.accessors.set](newAssociatedObject, options) - .then(() => newAssociatedObject) - ); + const newAssociatedObject = await this.target.create(values, options); + await sourceInstance[this.accessors.set](newAssociatedObject, options); + + return newAssociatedObject; } verifyAssociationAlias(alias) { diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 7290ea4ebad2..c5f540fc7536 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -168,7 +168,7 @@ class HasMany extends Association { * * @returns {Promise>} */ - get(instances, options = {}) { + async get(instances, options = {}) { const where = {}; let Model = this.target; @@ -187,7 +187,7 @@ class HasMany extends Association { } if (instances) { - values = instances.map(instance => instance.get(this.sourceKey, { raw: true })); + values = instances.map(_instance => _instance.get(this.sourceKey, { raw: true })); if (options.limit && instances.length > 1) { options.groupedLimit = { @@ -223,20 +223,19 @@ class HasMany extends Association { Model = Model.schema(options.schema, options.schemaDelimiter); } - return Model.findAll(options).then(results => { - if (instance) return results; + const results = await Model.findAll(options); + if (instance) return results; - const result = {}; - for (const instance of instances) { - result[instance.get(this.sourceKey, { raw: true })] = []; - } + const result = {}; + for (const _instance of instances) { + result[_instance.get(this.sourceKey, { raw: true })] = []; + } - for (const instance of results) { - result[instance.get(this.foreignKey, { raw: true })].push(instance); - } + for (const _instance of results) { + result[_instance.get(this.foreignKey, { raw: true })].push(_instance); + } - return result; - }); + return result; } /** @@ -249,7 +248,7 @@ class HasMany extends Association { * * @returns {Promise} */ - count(instance, options) { + async count(instance, options) { options = Utils.cloneDeep(options); options.attributes = [ @@ -264,7 +263,9 @@ class HasMany extends Association { options.raw = true; options.plain = true; - return this.get(instance, options).then(result => parseInt(result.count, 10)); + const result = await this.get(instance, options); + + return parseInt(result.count, 10); } /** @@ -276,7 +277,7 @@ class HasMany extends Association { * * @returns {Promise} */ - has(sourceInstance, targetInstances, options) { + async has(sourceInstance, targetInstances, options) { const where = {}; if (!Array.isArray(targetInstances)) { @@ -305,7 +306,9 @@ class HasMany extends Association { ] }; - return this.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length); + const associatedObjects = await this.get(sourceInstance, options); + + return associatedObjects.length === targetInstances.length; } /** @@ -318,68 +321,69 @@ class HasMany extends Association { * * @returns {Promise} */ - set(sourceInstance, targetInstances, options) { + async set(sourceInstance, targetInstances, options) { if (targetInstances === null) { targetInstances = []; } else { targetInstances = this.toInstanceArray(targetInstances); } - return this.get(sourceInstance, _.defaults({ scope: false, raw: true }, options)).then(oldAssociations => { - const promises = []; - const obsoleteAssociations = oldAssociations.filter(old => - !targetInstances.find(obj => - obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] - ) - ); - const unassociatedObjects = targetInstances.filter(obj => - !oldAssociations.find(old => - obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] - ) - ); - let updateWhere; - let update; + const oldAssociations = await this.get(sourceInstance, _.defaults({ scope: false, raw: true }, options)); + const promises = []; + const obsoleteAssociations = oldAssociations.filter(old => + !targetInstances.find(obj => + obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] + ) + ); + const unassociatedObjects = targetInstances.filter(obj => + !oldAssociations.find(old => + obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] + ) + ); + let updateWhere; + let update; - if (obsoleteAssociations.length > 0) { - update = {}; - update[this.foreignKey] = null; + if (obsoleteAssociations.length > 0) { + update = {}; + update[this.foreignKey] = null; - updateWhere = { - [this.target.primaryKeyAttribute]: obsoleteAssociations.map(associatedObject => - associatedObject[this.target.primaryKeyAttribute] - ) - }; + updateWhere = { + [this.target.primaryKeyAttribute]: obsoleteAssociations.map(associatedObject => + associatedObject[this.target.primaryKeyAttribute] + ) + }; - promises.push(this.target.unscoped().update( - update, - _.defaults({ - where: updateWhere - }, options) - )); - } + promises.push(this.target.unscoped().update( + update, + _.defaults({ + where: updateWhere + }, options) + )); + } - if (unassociatedObjects.length > 0) { - updateWhere = {}; + if (unassociatedObjects.length > 0) { + updateWhere = {}; - update = {}; - update[this.foreignKey] = sourceInstance.get(this.sourceKey); + update = {}; + update[this.foreignKey] = sourceInstance.get(this.sourceKey); - Object.assign(update, this.scope); - updateWhere[this.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject => - unassociatedObject[this.target.primaryKeyAttribute] - ); + Object.assign(update, this.scope); + updateWhere[this.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject => + unassociatedObject[this.target.primaryKeyAttribute] + ); - promises.push(this.target.unscoped().update( - update, - _.defaults({ - where: updateWhere - }, options) - )); - } + promises.push(this.target.unscoped().update( + update, + _.defaults({ + where: updateWhere + }, options) + )); + } - return Utils.Promise.all(promises).then(() => sourceInstance); - }); + await Utils.Promise.all(promises); + + return sourceInstance; } /** @@ -392,7 +396,7 @@ class HasMany extends Association { * * @returns {Promise} */ - add(sourceInstance, targetInstances, options = {}) { + async add(sourceInstance, targetInstances, options = {}) { if (!targetInstances) return Utils.Promise.resolve(); const update = {}; @@ -408,7 +412,9 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).then(() => sourceInstance); + await this.target.unscoped().update(update, _.defaults({ where }, options)); + + return sourceInstance; } /** @@ -420,7 +426,7 @@ class HasMany extends Association { * * @returns {Promise} */ - remove(sourceInstance, targetInstances, options = {}) { + async remove(sourceInstance, targetInstances, options = {}) { const update = { [this.foreignKey]: null }; @@ -434,7 +440,9 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, _.defaults({ where }, options)).then(() => this); + await this.target.unscoped().update(update, _.defaults({ where }, options)); + + return this; } /** @@ -446,7 +454,7 @@ class HasMany extends Association { * * @returns {Promise} */ - create(sourceInstance, values, options = {}) { + async create(sourceInstance, values, options = {}) { if (Array.isArray(options)) { options = { fields: options @@ -466,7 +474,7 @@ class HasMany extends Association { values[this.foreignKey] = sourceInstance.get(this.sourceKey); if (options.fields) options.fields.push(this.foreignKey); - return this.target.create(values, options); + return await this.target.create(values, options); } verifyAssociationAlias(alias) { diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index 3c8f79d13b98..9eaded98c637 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -122,7 +122,7 @@ class HasOne extends Association { * * @returns {Promise} */ - get(instances, options) { + async get(instances, options) { const where = {}; let Target = this.target; @@ -149,7 +149,7 @@ class HasOne extends Association { if (instances) { where[this.foreignKey] = { - [Op.in]: instances.map(instance => instance.get(this.sourceKey)) + [Op.in]: instances.map(_instance => _instance.get(this.sourceKey)) }; } else { where[this.foreignKey] = instance.get(this.sourceKey); @@ -164,18 +164,17 @@ class HasOne extends Association { where; if (instances) { - return Target.findAll(options).then(results => { - const result = {}; - for (const instance of instances) { - result[instance.get(this.sourceKey, { raw: true })] = null; - } + const results = await Target.findAll(options); + const result = {}; + for (const _instance of instances) { + result[_instance.get(this.sourceKey, { raw: true })] = null; + } - for (const instance of results) { - result[instance.get(this.foreignKey, { raw: true })] = instance; - } + for (const _instance of results) { + result[_instance.get(this.foreignKey, { raw: true })] = _instance; + } - return result; - }); + return result; } return Target.findOne(options); @@ -190,45 +189,42 @@ class HasOne extends Association { * * @returns {Promise} */ - set(sourceInstance, associatedInstance, options) { - let alreadyAssociated; - + async set(sourceInstance, associatedInstance, options) { options = Object.assign({}, options, { scope: false }); - return sourceInstance[this.accessors.get](options).then(oldInstance => { - // TODO Use equals method once #5605 is resolved - alreadyAssociated = oldInstance && associatedInstance && this.target.primaryKeyAttributes.every(attribute => - oldInstance.get(attribute, { raw: true }) === (associatedInstance.get ? associatedInstance.get(attribute, { raw: true }) : associatedInstance) - ); + const oldInstance = await sourceInstance[this.accessors.get](options); + // TODO Use equals method once #5605 is resolved + const alreadyAssociated = oldInstance && associatedInstance && this.target.primaryKeyAttributes.every(attribute => + oldInstance.get(attribute, { raw: true }) === (associatedInstance.get ? associatedInstance.get(attribute, { raw: true }) : associatedInstance) + ); - if (oldInstance && !alreadyAssociated) { - oldInstance[this.foreignKey] = null; - return oldInstance.save(Object.assign({}, options, { - fields: [this.foreignKey], - allowNull: [this.foreignKey], - association: true - })); + if (oldInstance && !alreadyAssociated) { + oldInstance[this.foreignKey] = null; + + await oldInstance.save(Object.assign({}, options, { + fields: [this.foreignKey], + allowNull: [this.foreignKey], + association: true + })); + } + if (associatedInstance && !alreadyAssociated) { + if (!(associatedInstance instanceof this.target)) { + const tmpInstance = {}; + tmpInstance[this.target.primaryKeyAttribute] = associatedInstance; + associatedInstance = this.target.build(tmpInstance, { + isNewRecord: false + }); } - }).then(() => { - if (associatedInstance && !alreadyAssociated) { - if (!(associatedInstance instanceof this.target)) { - const tmpInstance = {}; - tmpInstance[this.target.primaryKeyAttribute] = associatedInstance; - associatedInstance = this.target.build(tmpInstance, { - isNewRecord: false - }); - } - Object.assign(associatedInstance, this.scope); - associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceKeyAttribute)); + Object.assign(associatedInstance, this.scope); + associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceKeyAttribute)); - return associatedInstance.save(options); - } + return associatedInstance.save(options); + } - return null; - }); + return null; } /** @@ -243,7 +239,7 @@ class HasOne extends Association { * * @returns {Promise} The created target model */ - create(sourceInstance, values, options) { + async create(sourceInstance, values, options) { values = values || {}; options = options || {}; @@ -261,7 +257,7 @@ class HasOne extends Association { options.fields.push(this.foreignKey); } - return this.target.create(values, options); + return await this.target.create(values, options); } verifyAssociationAlias(alias) { diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index 6b12840acc34..fc70ddd92387 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -369,10 +369,10 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { return Promise.join( Post.create(), Comment.create() - ).then(([post, comment]) => { + ).then(async ([post, comment]) => { expect(comment.get('post_id')).not.to.be.ok; - const setter = comment.setPost(post, { save: false }); + const setter = await comment.setPost(post, { save: false }); expect(setter).to.be.undefined; expect(comment.get('post_id')).to.equal(post.get('id')); From f1d08764032df78353418be00e4538e7b1978a6d Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 19 Apr 2020 03:10:39 -0500 Subject: [PATCH 086/414] refactor(query-interface): asyncify methods (#12126) --- lib/query-interface.js | 456 +++++++++--------- .../dialects/postgres/query-interface.test.js | 67 +-- test/integration/query-interface.test.js | 23 +- 3 files changed, 261 insertions(+), 285 deletions(-) diff --git a/lib/query-interface.js b/lib/query-interface.js index d19e255e3137..36fc29744991 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -37,10 +37,10 @@ class QueryInterface { * * @returns {Promise} */ - createDatabase(database, options) { + async createDatabase(database, options) { options = options || {}; const sql = this.QueryGenerator.createDatabaseQuery(database, options); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -51,10 +51,10 @@ class QueryInterface { * * @returns {Promise} */ - dropDatabase(database, options) { + async dropDatabase(database, options) { options = options || {}; const sql = this.QueryGenerator.dropDatabaseQuery(database); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -65,10 +65,10 @@ class QueryInterface { * * @returns {Promise} */ - createSchema(schema, options) { + async createSchema(schema, options) { options = options || {}; const sql = this.QueryGenerator.createSchema(schema); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -79,10 +79,10 @@ class QueryInterface { * * @returns {Promise} */ - dropSchema(schema, options) { + async dropSchema(schema, options) { options = options || {}; const sql = this.QueryGenerator.dropSchema(schema); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -92,15 +92,14 @@ class QueryInterface { * * @returns {Promise} */ - dropAllSchemas(options) { + async dropAllSchemas(options) { options = options || {}; if (!this.QueryGenerator._dialect.supports.schemas) { return this.sequelize.drop(options); } - return this.showAllSchemas(options).then(schemas => Promise.all( - schemas.map(schemaName => this.dropSchema(schemaName, options)) - )); + const schemas = await this.showAllSchemas(options); + return Promise.all(schemas.map(schemaName => this.dropSchema(schemaName, options))); } /** @@ -110,7 +109,7 @@ class QueryInterface { * * @returns {Promise} */ - showAllSchemas(options) { + async showAllSchemas(options) { options = Object.assign({}, options, { raw: true, type: this.sequelize.QueryTypes.SELECT @@ -118,9 +117,9 @@ class QueryInterface { const showSchemasSql = this.QueryGenerator.showSchemasQuery(options); - return this.sequelize.query(showSchemasSql, options).then(schemaNames => _.flatten( - schemaNames.map(value => value.schema_name ? value.schema_name : value) - )); + const schemaNames = await this.sequelize.query(showSchemasSql, options); + + return _.flatten(schemaNames.map(value => value.schema_name ? value.schema_name : value)); } /** @@ -132,8 +131,8 @@ class QueryInterface { * @returns {Promise} * @private */ - databaseVersion(options) { - return this.sequelize.query( + async databaseVersion(options) { + return await this.sequelize.query( this.QueryGenerator.versionQuery(), Object.assign({}, options, { type: QueryTypes.VERSION }) ); @@ -192,9 +191,8 @@ class QueryInterface { * * @returns {Promise} */ - createTable(tableName, attributes, options, model) { + async createTable(tableName, attributes, options, model) { let sql = ''; - let promise; options = _.clone(options) || {}; @@ -217,9 +215,7 @@ class QueryInterface { // Postgres requires special SQL commands for ENUM/ENUM[] if (this.sequelize.options.dialect === 'postgres') { - promise = PostgresQueryInterface.ensureEnums(this, tableName, attributes, options, model); - } else { - promise = Promise.resolve(); + await PostgresQueryInterface.ensureEnums(this, tableName, attributes, options, model); } if ( @@ -235,7 +231,7 @@ class QueryInterface { attributes = this.QueryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' }); sql = this.QueryGenerator.createTableQuery(tableName, attributes, options); - return promise.then(() => this.sequelize.query(sql, options)); + return await this.sequelize.query(sql, options); } /** @@ -246,39 +242,40 @@ class QueryInterface { * * @returns {Promise} */ - dropTable(tableName, options) { + async dropTable(tableName, options) { // if we're forcing we should be cascading unless explicitly stated otherwise options = _.clone(options) || {}; options.cascade = options.cascade || options.force || false; let sql = this.QueryGenerator.dropTableQuery(tableName, options); - return this.sequelize.query(sql, options).then(() => { - const promises = []; + await this.sequelize.query(sql, options); + const promises = []; - // Since postgres has a special case for enums, we should drop the related - // enum type within the table and attribute - if (this.sequelize.options.dialect === 'postgres') { - const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); + // Since postgres has a special case for enums, we should drop the related + // enum type within the table and attribute + if (this.sequelize.options.dialect === 'postgres') { + const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); - if (instanceTable) { - const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName; + if (instanceTable) { + const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName; - const keys = Object.keys(instanceTable.rawAttributes); - const keyLen = keys.length; + const keys = Object.keys(instanceTable.rawAttributes); + const keyLen = keys.length; - for (let i = 0; i < keyLen; i++) { - if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { - sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]); - options.supportsSearchPath = false; - promises.push(this.sequelize.query(sql, Object.assign({}, options, { raw: true }))); - } + for (let i = 0; i < keyLen; i++) { + if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { + sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]); + options.supportsSearchPath = false; + promises.push(this.sequelize.query(sql, Object.assign({}, options, { raw: true }))); } } } + } - return Promise.all(promises).then(obj => obj[0]); - }); + const obj = await Promise.all(promises); + + return obj[0]; } /** @@ -340,9 +337,9 @@ class QueryInterface { * @returns {Promise} * @private */ - dropEnum(enumName, options) { + async dropEnum(enumName, options) { if (this.sequelize.getDialect() !== 'postgres') { - return Promise.resolve(); + return; } options = options || {}; @@ -361,19 +358,19 @@ class QueryInterface { * @returns {Promise} * @private */ - dropAllEnums(options) { + async dropAllEnums(options) { if (this.sequelize.getDialect() !== 'postgres') { - return Promise.resolve(); + return; } options = options || {}; - return this.pgListEnums(null, options).then(enums => Promise.all( - enums.map(result => this.sequelize.query( - this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), - Object.assign({}, options, { raw: true }) - )) - )); + const enums = await this.pgListEnums(null, options); + + return await Promise.all(enums.map(result => this.sequelize.query( + this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), + Object.assign({}, options, { raw: true }) + ))); } /** @@ -400,10 +397,10 @@ class QueryInterface { * * @returns {Promise} */ - renameTable(before, after, options) { + async renameTable(before, after, options) { options = options || {}; const sql = this.QueryGenerator.renameTableQuery(before, after); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -416,14 +413,15 @@ class QueryInterface { * @returns {Promise} * @private */ - showAllTables(options) { + async showAllTables(options) { options = Object.assign({}, options, { raw: true, type: QueryTypes.SHOWTABLES }); const showTablesSql = this.QueryGenerator.showTablesQuery(this.sequelize.config.database); - return this.sequelize.query(showTablesSql, options).then(tableNames => _.flatten(tableNames)); + const tableNames = await this.sequelize.query(showTablesSql, options); + return _.flatten(tableNames); } /** @@ -451,7 +449,7 @@ class QueryInterface { * * @returns {Promise} */ - describeTable(tableName, options) { + async describeTable(tableName, options) { let schema = null; let schemaDelimiter = null; @@ -470,7 +468,8 @@ class QueryInterface { const sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); options = Object.assign({}, options, { type: QueryTypes.DESCRIBE }); - return this.sequelize.query(sql, options).then(data => { + try { + const data = await this.sequelize.query(sql, options); /* * If no data is returned from the query, then the table name may be wrong. * Query generators that use information_schema for retrieving table info will just return an empty result set, @@ -481,13 +480,13 @@ class QueryInterface { } return data; - }).catch(e => { + } catch (e) { if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') { throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); } throw e; - }); + } } /** @@ -506,14 +505,14 @@ class QueryInterface { * * @returns {Promise} */ - addColumn(table, key, attribute, options) { + async addColumn(table, key, attribute, options) { if (!table || !key || !attribute) { throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); } options = options || {}; attribute = this.sequelize.normalizeAttribute(attribute); - return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options); + return await this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options); } /** @@ -525,21 +524,21 @@ class QueryInterface { * * @returns {Promise} */ - removeColumn(tableName, attributeName, options) { + async removeColumn(tableName, attributeName, options) { options = options || {}; switch (this.sequelize.options.dialect) { case 'sqlite': // sqlite needs some special treatment as it cannot drop a column - return SQLiteQueryInterface.removeColumn(this, tableName, attributeName, options); + return await SQLiteQueryInterface.removeColumn(this, tableName, attributeName, options); case 'mssql': // mssql needs special treatment as it cannot drop a column with a default or foreign key constraint - return MSSQLQueryInterface.removeColumn(this, tableName, attributeName, options); + return await MSSQLQueryInterface.removeColumn(this, tableName, attributeName, options); case 'mysql': case 'mariadb': // mysql/mariadb need special treatment as it cannot drop a column with a foreign key constraint - return MySQLQueryInterface.removeColumn(this, tableName, attributeName, options); + return await MySQLQueryInterface.removeColumn(this, tableName, attributeName, options); default: - return this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options); + return await this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options); } } @@ -553,7 +552,7 @@ class QueryInterface { * * @returns {Promise} */ - changeColumn(tableName, attributeName, dataTypeOrOptions, options) { + async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { const attributes = {}; options = options || {}; @@ -575,7 +574,7 @@ class QueryInterface { }); const sql = this.QueryGenerator.changeColumnQuery(tableName, query); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -588,40 +587,39 @@ class QueryInterface { * * @returns {Promise} */ - renameColumn(tableName, attrNameBefore, attrNameAfter, options) { + async renameColumn(tableName, attrNameBefore, attrNameAfter, options) { options = options || {}; - return this.describeTable(tableName, options).then(data => { - if (!data[attrNameBefore]) { - throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`); - } + let data = await this.describeTable(tableName, options); + if (!data[attrNameBefore]) { + throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`); + } - data = data[attrNameBefore] || {}; + data = data[attrNameBefore] || {}; - const _options = {}; + const _options = {}; - _options[attrNameAfter] = { - attribute: attrNameAfter, - type: data.type, - allowNull: data.allowNull, - defaultValue: data.defaultValue - }; + _options[attrNameAfter] = { + attribute: attrNameAfter, + type: data.type, + allowNull: data.allowNull, + defaultValue: data.defaultValue + }; - // fix: a not-null column cannot have null as default value - if (data.defaultValue === null && !data.allowNull) { - delete _options[attrNameAfter].defaultValue; - } + // fix: a not-null column cannot have null as default value + if (data.defaultValue === null && !data.allowNull) { + delete _options[attrNameAfter].defaultValue; + } - if (this.sequelize.options.dialect === 'sqlite') { - // sqlite needs some special treatment as it cannot rename a column - return SQLiteQueryInterface.renameColumn(this, tableName, attrNameBefore, attrNameAfter, options); - } - const sql = this.QueryGenerator.renameColumnQuery( - tableName, - attrNameBefore, - this.QueryGenerator.attributesToSQL(_options) - ); - return this.sequelize.query(sql, options); - }); + if (this.sequelize.options.dialect === 'sqlite') { + // sqlite needs some special treatment as it cannot rename a column + return SQLiteQueryInterface.renameColumn(this, tableName, attrNameBefore, attrNameAfter, options); + } + const sql = this.QueryGenerator.renameColumnQuery( + tableName, + attrNameBefore, + this.QueryGenerator.attributesToSQL(_options) + ); + return await this.sequelize.query(sql, options); } /** @@ -642,7 +640,7 @@ class QueryInterface { * * @returns {Promise} */ - addIndex(tableName, attributes, options, rawTablename) { + async addIndex(tableName, attributes, options, rawTablename) { // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes) if (!Array.isArray(attributes)) { rawTablename = options; @@ -658,7 +656,7 @@ class QueryInterface { options = Utils.cloneDeep(options); options.fields = attributes; const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename); - return this.sequelize.query(sql, Object.assign({}, options, { supportsSearchPath: false })); + return await this.sequelize.query(sql, Object.assign({}, options, { supportsSearchPath: false })); } /** @@ -670,9 +668,9 @@ class QueryInterface { * @returns {Promise} * @private */ - showIndex(tableName, options) { + async showIndex(tableName, options) { const sql = this.QueryGenerator.showIndexesQuery(tableName, options); - return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWINDEXES })); + return await this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWINDEXES })); } @@ -684,31 +682,31 @@ class QueryInterface { * * @returns {Promise} */ - getForeignKeysForTables(tableNames, options) { + async getForeignKeysForTables(tableNames, options) { if (tableNames.length === 0) { - return Promise.resolve({}); + return {}; } options = Object.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS }); - return Promise.all(tableNames.map(tableName => - this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))).then(results => { - const result = {}; + const results = await Promise.all(tableNames.map(tableName => + this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))); - tableNames.forEach((tableName, i) => { - if (_.isObject(tableName)) { - tableName = `${tableName.schema}.${tableName.tableName}`; - } + const result = {}; - result[tableName] = Array.isArray(results[i]) - ? results[i].map(r => r.constraint_name) - : [results[i] && results[i].constraint_name]; + tableNames.forEach((tableName, i) => { + if (_.isObject(tableName)) { + tableName = `${tableName.schema}.${tableName.tableName}`; + } - result[tableName] = result[tableName].filter(_.identity); - }); + result[tableName] = Array.isArray(results[i]) + ? results[i].map(r => r.constraint_name) + : [results[i] && results[i].constraint_name]; - return result; + result[tableName] = result[tableName].filter(_.identity); }); + + return result; } /** @@ -724,7 +722,7 @@ class QueryInterface { * * @returns {Promise} */ - getForeignKeyReferencesForTable(tableName, options) { + async getForeignKeyReferencesForTable(tableName, options) { const queryOptions = Object.assign({}, options, { type: QueryTypes.FOREIGNKEYS }); @@ -732,21 +730,21 @@ class QueryInterface { switch (this.sequelize.options.dialect) { case 'sqlite': // sqlite needs some special treatment. - return SQLiteQueryInterface.getForeignKeyReferencesForTable(this, tableName, queryOptions); + return await SQLiteQueryInterface.getForeignKeyReferencesForTable(this, tableName, queryOptions); case 'postgres': { // postgres needs some special treatment as those field names returned are all lowercase // in order to keep same result with other dialects. const query = this.QueryGenerator.getForeignKeyReferencesQuery(tableName, catalogName); - return this.sequelize.query(query, queryOptions) - .then(result => result.map(Utils.camelizeObjectKeys)); + const result = await this.sequelize.query(query, queryOptions); + return result.map(Utils.camelizeObjectKeys); } case 'mssql': case 'mysql': case 'mariadb': default: { const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName); - return this.sequelize.query(query, queryOptions); + return await this.sequelize.query(query, queryOptions); } } } @@ -760,10 +758,10 @@ class QueryInterface { * * @returns {Promise} */ - removeIndex(tableName, indexNameOrAttributes, options) { + async removeIndex(tableName, indexNameOrAttributes, options) { options = options || {}; const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -828,7 +826,7 @@ class QueryInterface { * * @returns {Promise} */ - addConstraint(tableName, attributes, options, rawTablename) { + async addConstraint(tableName, attributes, options, rawTablename) { if (!Array.isArray(attributes)) { rawTablename = options; options = attributes; @@ -848,15 +846,15 @@ class QueryInterface { options.fields = attributes; if (this.sequelize.dialect.name === 'sqlite') { - return SQLiteQueryInterface.addConstraint(this, tableName, options, rawTablename); + return await SQLiteQueryInterface.addConstraint(this, tableName, options, rawTablename); } const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - showConstraint(tableName, constraintName, options) { + async showConstraint(tableName, constraintName, options) { const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName); - return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS })); + return await this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS })); } /** @@ -868,23 +866,23 @@ class QueryInterface { * * @returns {Promise} */ - removeConstraint(tableName, constraintName, options) { + async removeConstraint(tableName, constraintName, options) { options = options || {}; switch (this.sequelize.options.dialect) { case 'mysql': case 'mariadb': //does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used - return MySQLQueryInterface.removeConstraint(this, tableName, constraintName, options); + return await MySQLQueryInterface.removeConstraint(this, tableName, constraintName, options); case 'sqlite': - return SQLiteQueryInterface.removeConstraint(this, tableName, constraintName, options); + return await SQLiteQueryInterface.removeConstraint(this, tableName, constraintName, options); default: const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } } - insert(instance, tableName, values, options) { + async insert(instance, tableName, values, options) { options = Utils.cloneDeep(options); options.hasTrigger = instance && instance.constructor.options.hasTrigger; const sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options); @@ -892,10 +890,10 @@ class QueryInterface { options.type = QueryTypes.INSERT; options.instance = instance; - return this.sequelize.query(sql, options).then(results => { - if (instance) results[0].isNewRecord = false; - return results; - }); + const results = await this.sequelize.query(sql, options); + if (instance) results[0].isNewRecord = false; + + return results; } /** @@ -910,7 +908,7 @@ class QueryInterface { * * @returns {Promise} Resolves an array with */ - upsert(tableName, insertValues, updateValues, where, model, options) { + async upsert(tableName, insertValues, updateValues, where, model, options) { const wheres = []; const attributes = Object.keys(insertValues); let indexes = []; @@ -956,27 +954,23 @@ class QueryInterface { options.raw = true; const sql = this.QueryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); - return this.sequelize.query(sql, options).then(result => { - switch (this.sequelize.options.dialect) { - case 'postgres': - return [result.created, result.primary_key]; - - case 'mssql': - return [ - result.$action === 'INSERT', - result[model.primaryKeyField] - ]; - + const result = await this.sequelize.query(sql, options); + switch (this.sequelize.options.dialect) { + case 'postgres': + return [result.created, result.primary_key]; + case 'mssql': + return [ + result.$action === 'INSERT', + result[model.primaryKeyField] + ]; // MySQL returns 1 for inserted, 2 for updated // http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html. - case 'mysql': - case 'mariadb': - return [result === 1, undefined]; - - default: - return [result, undefined]; - } - }); + case 'mysql': + case 'mariadb': + return [result === 1, undefined]; + default: + return [result, undefined]; + } } /** @@ -1000,17 +994,19 @@ class QueryInterface { * * @returns {Promise} */ - bulkInsert(tableName, records, options, attributes) { + async bulkInsert(tableName, records, options, attributes) { options = _.clone(options) || {}; options.type = QueryTypes.INSERT; - return this.sequelize.query( + const results = await this.sequelize.query( this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes), options - ).then(results => results[0]); + ); + + return results[0]; } - update(instance, tableName, values, identifier, options) { + async update(instance, tableName, values, identifier, options) { options = _.clone(options || {}); options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); @@ -1019,7 +1015,7 @@ class QueryInterface { options.type = QueryTypes.UPDATE; options.instance = instance; - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } /** @@ -1041,7 +1037,7 @@ class QueryInterface { * * @returns {Promise} */ - bulkUpdate(tableName, values, identifier, options, attributes) { + async bulkUpdate(tableName, values, identifier, options, attributes) { options = Utils.cloneDeep(options); if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier); @@ -1051,7 +1047,7 @@ class QueryInterface { options.type = QueryTypes.BULKUPDATE; options.model = model; - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } async delete(instance, tableName, identifier, options) { @@ -1081,7 +1077,7 @@ class QueryInterface { // Check for hasOne relationship with non-existing associate ("has zero") if (!instances) continue; if (!Array.isArray(instances)) instances = [instances]; - for (const instance of instances) await instance.destroy(options); + for (const _instance of instances) await _instance.destroy(options); } options.instance = instance; return await this.sequelize.query(sql, options); @@ -1100,7 +1096,7 @@ class QueryInterface { * * @returns {Promise} */ - bulkDelete(tableName, where, options, model) { + async bulkDelete(tableName, where, options, model) { options = Utils.cloneDeep(options); options = _.defaults(options, { limit: null }); @@ -1113,22 +1109,22 @@ class QueryInterface { if (typeof identifier === 'object') where = Utils.cloneDeep(where); - return this.sequelize.query( + return await this.sequelize.query( this.QueryGenerator.deleteQuery(tableName, where, options, model), options ); } - select(model, tableName, optionsArg) { + async select(model, tableName, optionsArg) { const options = Object.assign({}, optionsArg, { type: QueryTypes.SELECT, model }); - return this.sequelize.query( + return await this.sequelize.query( this.QueryGenerator.selectQuery(tableName, options, model), options ); } - increment(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { + async increment(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = Utils.cloneDeep(options); const sql = this.QueryGenerator.arithmeticQuery('+', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); @@ -1136,10 +1132,10 @@ class QueryInterface { options.type = QueryTypes.UPDATE; options.model = model; - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - decrement(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { + async decrement(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = Utils.cloneDeep(options); const sql = this.QueryGenerator.arithmeticQuery('-', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); @@ -1147,10 +1143,10 @@ class QueryInterface { options.type = QueryTypes.UPDATE; options.model = model; - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - rawSelect(tableName, options, attributeSelector, Model) { + async rawSelect(tableName, options, attributeSelector, Model) { options = Utils.cloneDeep(options); options = _.defaults(options, { raw: true, @@ -1164,63 +1160,68 @@ class QueryInterface { throw new Error('Please pass an attribute selector!'); } - return this.sequelize.query(sql, options).then(data => { - if (!options.plain) { - return data; - } + const data = await this.sequelize.query(sql, options); + if (!options.plain) { + return data; + } - const result = data ? data[attributeSelector] : null; + const result = data ? data[attributeSelector] : null; - if (!options || !options.dataType) { - return result; - } + if (!options || !options.dataType) { + return result; + } - const dataType = options.dataType; + const dataType = options.dataType; - if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) { - if (result !== null) { - return parseFloat(result); - } - } - if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) { - return parseInt(result, 10); + if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) { + if (result !== null) { + return parseFloat(result); } - if (dataType instanceof DataTypes.DATE) { - if (result !== null && !(result instanceof Date)) { - return new Date(result); - } + } + if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) { + return parseInt(result, 10); + } + if (dataType instanceof DataTypes.DATE) { + if (result !== null && !(result instanceof Date)) { + return new Date(result); } - return result; - }); + } + return result; } - createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options) { + async createTrigger( + tableName, + triggerName, + timingType, + fireOnArray, + functionName, + functionParams, + optionsArray, + options + ) { const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); options = options || {}; if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - return Promise.resolve(); } - dropTrigger(tableName, triggerName, options) { + async dropTrigger(tableName, triggerName, options) { const sql = this.QueryGenerator.dropTrigger(tableName, triggerName); options = options || {}; if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - return Promise.resolve(); } - renameTrigger(tableName, oldTriggerName, newTriggerName, options) { + async renameTrigger(tableName, oldTriggerName, newTriggerName, options) { const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); options = options || {}; if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - return Promise.resolve(); } /** @@ -1260,14 +1261,13 @@ class QueryInterface { * * @returns {Promise} */ - createFunction(functionName, params, returnType, language, body, optionsArray, options) { + async createFunction(functionName, params, returnType, language, body, optionsArray, options) { const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options); options = options || {}; if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - return Promise.resolve(); } /** @@ -1288,14 +1288,13 @@ class QueryInterface { * * @returns {Promise} */ - dropFunction(functionName, params, options) { + async dropFunction(functionName, params, options) { const sql = this.QueryGenerator.dropFunction(functionName, params); options = options || {}; if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - return Promise.resolve(); } /** @@ -1318,14 +1317,13 @@ class QueryInterface { * * @returns {Promise} */ - renameFunction(oldFunctionName, params, newFunctionName, options) { + async renameFunction(oldFunctionName, params, newFunctionName, options) { const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName); options = options || {}; if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - return Promise.resolve(); } // Helper methods useful for querying @@ -1369,14 +1367,14 @@ class QueryInterface { return this.QueryGenerator.escape(value); } - setIsolationLevel(transaction, value, options) { + async setIsolationLevel(transaction, value, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to set isolation level for a transaction without transaction object!'); } if (transaction.parent || !value) { // Not possible to set a separate isolation level for savepoints - return Promise.resolve(); + return; } options = Object.assign({}, options, { @@ -1387,12 +1385,12 @@ class QueryInterface { parent: transaction.parent }); - if (!sql) return Promise.resolve(); + if (!sql) return; - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - startTransaction(transaction, options) { + async startTransaction(transaction, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to start a transaction without transaction object!'); } @@ -1403,10 +1401,10 @@ class QueryInterface { options.transaction.name = transaction.parent ? transaction.name : undefined; const sql = this.QueryGenerator.startTransactionQuery(transaction); - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - deferConstraints(transaction, options) { + async deferConstraints(transaction, options) { options = Object.assign({}, options, { transaction: transaction.parent || transaction }); @@ -1414,19 +1412,17 @@ class QueryInterface { const sql = this.QueryGenerator.deferConstraintsQuery(options); if (sql) { - return this.sequelize.query(sql, options); + return await this.sequelize.query(sql, options); } - - return Promise.resolve(); } - commitTransaction(transaction, options) { + async commitTransaction(transaction, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to commit a transaction without transaction object!'); } if (transaction.parent) { // Savepoints cannot be committed - return Promise.resolve(); + return; } options = Object.assign({}, options, { @@ -1440,10 +1436,10 @@ class QueryInterface { transaction.finished = 'commit'; - return promise; + return await promise; } - rollbackTransaction(transaction, options) { + async rollbackTransaction(transaction, options) { if (!transaction || !(transaction instanceof Transaction)) { throw new Error('Unable to rollback a transaction without transaction object!'); } @@ -1459,7 +1455,7 @@ class QueryInterface { transaction.finished = 'rollback'; - return promise; + return await promise; } } diff --git a/test/integration/dialects/postgres/query-interface.test.js b/test/integration/dialects/postgres/query-interface.test.js index 53ec75975d84..98c4f7fab770 100644 --- a/test/integration/dialects/postgres/query-interface.test.js +++ b/test/integration/dialects/postgres/query-interface.test.js @@ -128,34 +128,28 @@ if (dialect.match(/^postgres/)) { return Promise.all([ // requires functionName - expect(() => { - return this.queryInterface.createFunction(null, [{ name: 'test' }], 'integer', 'plpgsql', body, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), + expect(this.queryInterface.createFunction(null, [{ name: 'test' }], 'integer', 'plpgsql', body, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), // requires Parameters array - expect(() => { - return this.queryInterface.createFunction('create_job', null, 'integer', 'plpgsql', body, options); - }).to.throw(/function parameters array required/), + expect(this.queryInterface.createFunction('create_job', null, 'integer', 'plpgsql', body, options)) + .to.be.rejectedWith(/function parameters array required/), // requires returnType - expect(() => { - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], null, 'plpgsql', body, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), + expect(this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], null, 'plpgsql', body, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), // requires type in parameter array - expect(() => { - return this.queryInterface.createFunction('create_job', [{ name: 'test' }], 'integer', 'plpgsql', body, options); - }).to.throw(/function or trigger used with a parameter without any type/), + expect(this.queryInterface.createFunction('create_job', [{ name: 'test' }], 'integer', 'plpgsql', body, options)) + .to.be.rejectedWith(/function or trigger used with a parameter without any type/), // requires language - expect(() => { - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', null, body, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), + expect(this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', null, body, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), // requires body - expect(() => { - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', null, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/) + expect(this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', null, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/) ]); }); @@ -176,20 +170,14 @@ if (dialect.match(/^postgres/)) { it('produces an error when options.variables is missing expected parameters', function() { const body = 'return 1;'; - expect(() => { - const options = { variables: 100 }; - return this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], options); - }).to.throw(/expandFunctionVariableList: function variables must be an array/); - - expect(() => { - const options = { variables: [{ name: 'myVar' }] }; - return this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], options); - }).to.throw(/function variable must have a name and type/); - - expect(() => { - const options = { variables: [{ type: 'integer' }] }; - return this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], options); - }).to.throw(/function variable must have a name and type/); + expect(this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: 100 })) + .to.be.rejectedWith(/expandFunctionVariableList: function variables must be an array/); + + expect(this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: [{ name: 'myVar' }] })) + .to.be.rejectedWith(/function variable must have a name and type/); + + expect(this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: [{ type: 'integer' }] })) + .to.be.rejectedWith(/function variable must have a name and type/); }); it('uses declared variables', function() { @@ -229,17 +217,14 @@ if (dialect.match(/^postgres/)) { it('produces an error when missing expected parameters', function() { return Promise.all([ - expect(() => { - return this.queryInterface.dropFunction(); - }).to.throw(/.*requires functionName/), + expect(this.queryInterface.dropFunction()) + .to.be.rejectedWith(/.*requires functionName/), - expect(() => { - return this.queryInterface.dropFunction('droptest'); - }).to.throw(/.*function parameters array required/), + expect(this.queryInterface.dropFunction('droptest')) + .to.be.rejectedWith(/.*function parameters array required/), - expect(() => { - return this.queryInterface.dropFunction('droptest', [{ name: 'test' }]); - }).to.be.throw(/.*function or trigger used with a parameter without any type/) + expect(this.queryInterface.dropFunction('droptest', [{ name: 'test' }])) + .to.be.rejectedWith(/.*function or trigger used with a parameter without any type/) ]); }); }); diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index 2b7195435a45..60e1ca8c1da3 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -339,16 +339,12 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }); - const testArgs = (...args) => { - expect(() => { - this.queryInterface.addColumn(...args); - throw new Error('Did not throw immediately...'); - }).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - }; + const testArgs = (...args) => expect(this.queryInterface.addColumn(...args)) + .to.be.rejectedWith(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - testArgs('users', 'level_id'); - testArgs(null, 'level_id'); - testArgs('users', null, {}); + await testArgs('users', 'level_id'); + await testArgs(null, 'level_id'); + await testArgs('users', null, {}); }); it('should work with schemas', async function() { @@ -539,14 +535,13 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { expect(constraints).to.not.include('check_user_roles'); }); - it('addconstraint missing type', function() { - expect(() => { + it('addconstraint missing type', async function() { + await expect( this.queryInterface.addConstraint('users', ['roles'], { where: { roles: ['user', 'admin', 'guest', 'moderator'] }, name: 'check_user_roles' - }); - throw new Error('Did not throw immediately...'); - }).to.throw(Error, 'Constraint type must be specified through options.type'); + }) + ).to.be.rejectedWith(Error, 'Constraint type must be specified through options.type'); }); }); } From 2924eba1f9811f9f6fb8e919d9668d208bceaf09 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 19 Apr 2020 03:21:10 -0500 Subject: [PATCH 087/414] refactor(sequelize): asyncify methods (#12125) --- lib/sequelize.js | 177 +++++++++++++++-------------- test/integration/sequelize.test.js | 7 +- 2 files changed, 95 insertions(+), 89 deletions(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index 8f3785eb3ad0..7b03c67139dd 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -538,7 +538,7 @@ class Sequelize { * @see {@link Model.build} for more information about instance option. */ - query(sql, options) { + async query(sql, options) { options = Object.assign({}, this.options.query, options); if (options.instance && !options.model) { @@ -583,80 +583,77 @@ class Sequelize { options.searchPath = 'DEFAULT'; } - return Promise.resolve().then(() => { - if (typeof sql === 'object') { - if (sql.values !== undefined) { - if (options.replacements !== undefined) { - throw new Error('Both `sql.values` and `options.replacements` cannot be set at the same time'); - } - options.replacements = sql.values; + if (typeof sql === 'object') { + if (sql.values !== undefined) { + if (options.replacements !== undefined) { + throw new Error('Both `sql.values` and `options.replacements` cannot be set at the same time'); } + options.replacements = sql.values; + } - if (sql.bind !== undefined) { - if (options.bind !== undefined) { - throw new Error('Both `sql.bind` and `options.bind` cannot be set at the same time'); - } - options.bind = sql.bind; + if (sql.bind !== undefined) { + if (options.bind !== undefined) { + throw new Error('Both `sql.bind` and `options.bind` cannot be set at the same time'); } + options.bind = sql.bind; + } - if (sql.query !== undefined) { - sql = sql.query; - } + if (sql.query !== undefined) { + sql = sql.query; } + } - sql = sql.trim(); + sql = sql.trim(); - if (options.replacements && options.bind) { - throw new Error('Both `replacements` and `bind` cannot be set at the same time'); + if (options.replacements && options.bind) { + throw new Error('Both `replacements` and `bind` cannot be set at the same time'); + } + + if (options.replacements) { + if (Array.isArray(options.replacements)) { + sql = Utils.format([sql].concat(options.replacements), this.options.dialect); + } else { + sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); } + } - if (options.replacements) { - if (Array.isArray(options.replacements)) { - sql = Utils.format([sql].concat(options.replacements), this.options.dialect); - } else { - sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); - } + let bindParameters; + + if (options.bind) { + [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); + } + + const checkTransaction = () => { + if (options.transaction && options.transaction.finished && !options.completesTransaction) { + const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)`); + error.sql = sql; + throw error; } + }; - let bindParameters; + const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); - if (options.bind) { - [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); + return retry(async () => { + if (options.transaction === undefined && Sequelize._cls) { + options.transaction = Sequelize._cls.get('transaction'); } - const checkTransaction = () => { - if (options.transaction && options.transaction.finished && !options.completesTransaction) { - const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)`); - error.sql = sql; - throw error; - } - }; + checkTransaction(); - const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); + const connection = await (options.transaction ? options.transaction.connection : this.connectionManager.getConnection(options)); + const query = new this.dialect.Query(connection, this, options); - return Promise.resolve(retry(() => Promise.resolve().then(() => { - if (options.transaction === undefined && Sequelize._cls) { - options.transaction = Sequelize._cls.get('transaction'); + try { + await this.runHooks('beforeQuery', options, query); + await checkTransaction(); + return await query.run(sql, bindParameters); + } finally { + await this.runHooks('afterQuery', options, query); + if (!options.transaction) { + await this.connectionManager.releaseConnection(connection); } - - checkTransaction(); - - return options.transaction - ? options.transaction.connection - : this.connectionManager.getConnection(options); - }).then(connection => { - const query = new this.dialect.Query(connection, this, options); - return this.runHooks('beforeQuery', options, query) - .then(() => checkTransaction()) - .then(() => query.run(sql, bindParameters)) - .finally(() => this.runHooks('afterQuery', options, query)) - .finally(() => { - if (!options.transaction) { - return this.connectionManager.releaseConnection(connection); - } - }); - }), retryOptions)); - }); + } + }, retryOptions); } /** @@ -671,7 +668,7 @@ class Sequelize { * * @returns {Promise} */ - set(variables, options) { + async set(variables, options) { // Prepare options options = Object.assign({}, this.options.set, typeof options === 'object' && options); @@ -693,7 +690,7 @@ class Sequelize { `SET ${ _.map(variables, (v, k) => `@${k} := ${typeof v === 'string' ? `"${v}"` : v}`).join(', ')}`; - return this.query(query, options); + return await this.query(query, options); } /** @@ -722,8 +719,8 @@ class Sequelize { * * @returns {Promise} */ - createSchema(schema, options) { - return this.getQueryInterface().createSchema(schema, options); + async createSchema(schema, options) { + return await this.getQueryInterface().createSchema(schema, options); } /** @@ -737,8 +734,8 @@ class Sequelize { * * @returns {Promise} */ - showAllSchemas(options) { - return this.getQueryInterface().showAllSchemas(options); + async showAllSchemas(options) { + return await this.getQueryInterface().showAllSchemas(options); } /** @@ -753,8 +750,8 @@ class Sequelize { * * @returns {Promise} */ - dropSchema(schema, options) { - return this.getQueryInterface().dropSchema(schema, options); + async dropSchema(schema, options) { + return await this.getQueryInterface().dropSchema(schema, options); } /** @@ -768,8 +765,8 @@ class Sequelize { * * @returns {Promise} */ - dropAllSchemas(options) { - return this.getQueryInterface().dropAllSchemas(options); + async dropAllSchemas(options) { + return await this.getQueryInterface().dropAllSchemas(options); } /** @@ -886,18 +883,20 @@ class Sequelize { * * @returns {Promise} */ - authenticate(options) { + async authenticate(options) { options = Object.assign({ raw: true, plain: true, type: QueryTypes.SELECT }, options); - return this.query('SELECT 1+1 AS result', options).then(() => undefined); + await this.query('SELECT 1+1 AS result', options); + + return; } - databaseVersion(options) { - return this.getQueryInterface().databaseVersion(options); + async databaseVersion(options) { + return await this.getQueryInterface().databaseVersion(options); } /** @@ -1094,7 +1093,7 @@ class Sequelize { * * @returns {Promise} */ - transaction(options, autoCallback) { + async transaction(options, autoCallback) { if (typeof options === 'function') { autoCallback = options; options = undefined; @@ -1102,20 +1101,28 @@ class Sequelize { const transaction = new Transaction(this, options); - if (!autoCallback) return transaction.prepareEnvironment(false).then(() => transaction); + if (!autoCallback) { + await transaction.prepareEnvironment(false); + return transaction; + } // autoCallback provided - return Sequelize._clsRun(() => { - return transaction.prepareEnvironment() - .then(() => autoCallback(transaction)) - .then(result => Promise.resolve(transaction.commit()).then(() => result)) - .catch(err => { - // Rollback transaction if not already finished (commit, rollback, etc) - // and reject with original error (ignore any error in rollback) - return Promise.resolve().then(() => { - if (!transaction.finished) return transaction.rollback().catch(() => {}); - }).then(() => { throw err; }); - }); + return Sequelize._clsRun(async () => { + try { + await transaction.prepareEnvironment(); + const result = await autoCallback(transaction); + await transaction.commit(); + return await result; + } catch (err) { + if (!transaction.finished) { + try { + await transaction.rollback(); + } catch (err0) { + // ignore + } + } + throw err; + } }); } diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index ae360a6cc401..57b81b2a0be5 100755 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -880,10 +880,9 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (dialect === 'mysql') { describe('set', () => { - it("should return an promised error if transaction isn't defined", function() { - expect(() => { - this.sequelize.set({ foo: 'bar' }); - }).to.throw(TypeError, 'options.transaction is required'); + it("should return an promised error if transaction isn't defined", async function() { + await expect(this.sequelize.set({ foo: 'bar' })) + .to.be.rejectedWith(TypeError, 'options.transaction is required'); }); it('one value', function() { From 7b671d28b97856db4ee61accb83d76b51955f142 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 19 Apr 2020 03:22:30 -0500 Subject: [PATCH 088/414] refactor(instance-validator): asyncify methods (#12127) --- lib/instance-validator.js | 99 ++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/lib/instance-validator.js b/lib/instance-validator.js index e28f82257eb5..68d90bb8e745 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -63,19 +63,19 @@ class InstanceValidator { * @returns {Promise} * @private */ - _validate() { + async _validate() { if (this.inProgress) throw new Error('Validations already in progress.'); this.inProgress = true; - return Promise.all([ + await Promise.all([ this._perAttributeValidators(), this._customValidators() - ]).then(() => { - if (this.errors.length) { - throw new sequelizeError.ValidationError(null, this.errors); - } - }); + ]); + + if (this.errors.length) { + throw new sequelizeError.ValidationError(null, this.errors); + } } /** @@ -88,8 +88,8 @@ class InstanceValidator { * @returns {Promise} * @private */ - validate() { - return this.options.hooks ? this._validateAndRunHooks() : this._validate(); + async validate() { + return await (this.options.hooks ? this._validateAndRunHooks() : this._validate()); } /** @@ -102,15 +102,19 @@ class InstanceValidator { * @returns {Promise} * @private */ - _validateAndRunHooks() { + async _validateAndRunHooks() { const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor); - return runHooks('beforeValidate', this.modelInstance, this.options) - .then(() => - this._validate() - .catch(error => runHooks('validationFailed', this.modelInstance, this.options, error) - .then(newError => { throw newError || error; })) - ) - .then(() => runHooks('afterValidate', this.modelInstance, this.options)).then(() => this.modelInstance); + await runHooks('beforeValidate', this.modelInstance, this.options); + + try { + await this._validate(); + } catch (error) { + const newError = await runHooks('validationFailed', this.modelInstance, this.options, error); + throw newError || error; + } + + await runHooks('afterValidate', this.modelInstance, this.options); + return this.modelInstance; } /** @@ -119,7 +123,7 @@ class InstanceValidator { * @returns {Promise} * @private */ - _perAttributeValidators() { + async _perAttributeValidators() { // promisify all attribute invocations const validators = []; @@ -144,7 +148,7 @@ class InstanceValidator { } }); - return Promise.all(validators); + return await Promise.all(validators); } /** @@ -153,7 +157,7 @@ class InstanceValidator { * @returns {Promise} * @private */ - _customValidators() { + async _customValidators() { const validators = []; _.each(this.modelInstance._modelOptions.validate, (validator, validatorType) => { if (this.options.skip.includes(validatorType)) { @@ -167,7 +171,7 @@ class InstanceValidator { validators.push(valprom); }); - return Promise.all(validators); + return await Promise.all(validators); } /** @@ -181,11 +185,11 @@ class InstanceValidator { * * @returns {Promise} A promise, will always resolve, auto populates error on this.error local object. */ - _singleAttrValidate(value, field, allowNull) { + async _singleAttrValidate(value, field, allowNull) { // If value is null and allowNull is false, no validators should run (see #9143) if ((value === null || value === undefined) && !allowNull) { // The schema validator (_validateSchema) has already generated the validation error. Nothing to do here. - return Promise.resolve(); + return; } // Promisify each validator @@ -240,8 +244,7 @@ class InstanceValidator { * * @returns {Promise} A promise. */ - _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) { - let validatorFunction = null; // the validation function to call + async _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) { let isAsync = false; const validatorArity = validator.length; @@ -259,17 +262,21 @@ class InstanceValidator { } if (isAsync) { - if (optAttrDefined) { - validatorFunction = promisify(validator.bind(this.modelInstance, invokeArgs)); - } else { - validatorFunction = promisify(validator.bind(this.modelInstance)); + try { + if (optAttrDefined) { + return await promisify(validator.bind(this.modelInstance, invokeArgs))(); + } + return await promisify(validator.bind(this.modelInstance))(); + } catch (e) { + return this._pushError(false, errorKey, e, optValue, validatorType); } - return validatorFunction() - .catch(e => this._pushError(false, errorKey, e, optValue, validatorType)); } - return Promise - .try(() => validator.call(this.modelInstance, invokeArgs)) - .catch(e => this._pushError(false, errorKey, e, optValue, validatorType)); + + try { + return await validator.call(this.modelInstance, invokeArgs); + } catch (e) { + return this._pushError(false, errorKey, e, optValue, validatorType); + } } /** @@ -284,21 +291,19 @@ class InstanceValidator { * * @returns {object} An object with specific keys to invoke the validator. */ - _invokeBuiltinValidator(value, test, validatorType, field) { - return Promise.try(() => { - // Cast value as string to pass new Validator.js string requirement - const valueString = String(value); - // check if Validator knows that kind of validation test - if (typeof validator[validatorType] !== 'function') { - throw new Error(`Invalid validator function: ${validatorType}`); - } + async _invokeBuiltinValidator(value, test, validatorType, field) { + // Cast value as string to pass new Validator.js string requirement + const valueString = String(value); + // check if Validator knows that kind of validation test + if (typeof validator[validatorType] !== 'function') { + throw new Error(`Invalid validator function: ${validatorType}`); + } - const validatorArgs = this._extractValidatorArgs(test, validatorType, field); + const validatorArgs = this._extractValidatorArgs(test, validatorType, field); - if (!validator[validatorType](valueString, ...validatorArgs)) { - throw Object.assign(new Error(test.msg || `Validation ${validatorType} on ${field} failed`), { validatorName: validatorType, validatorArgs }); - } - }); + if (!validator[validatorType](valueString, ...validatorArgs)) { + throw Object.assign(new Error(test.msg || `Validation ${validatorType} on ${field} failed`), { validatorName: validatorType, validatorArgs }); + } } /** From 1e6f9af1bad84da395d6aa634f9e35fe4c5a6a2c Mon Sep 17 00:00:00 2001 From: Andrew Vereshchak Date: Sun, 19 Apr 2020 18:35:27 +0300 Subject: [PATCH 089/414] refactor(ci): improve database wait script (#12132) --- .travis.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5f8d7be2ff5e..e6b9d76ebe95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,12 +27,13 @@ env: before_script: # setup docker - - "if [ $MARIADB_VER ]; then export MARIADB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mariadb; fi" - - "if [ $MYSQL_VER ]; then export MYSQLDB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mysql; fi" - - "if [ $POSTGRES_VER ] || [ $MARIADB_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MARIADB_VER} ${MYSQL_VER}; fi" - - "if [ $MARIADB_VER ]; then docker run --link ${MARIADB_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" - - "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" - - "if [ $POSTGRES_VER ]; then docker run --link ${POSTGRES_VER}:db -e CHECK_PORT=5432 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" + - if [ $MARIADB_VER ]; then export MARIADB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mariadb; fi + - if [ $MYSQL_VER ]; then export MYSQLDB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mysql; fi + - if [ $POSTGRES_VER ] || [ $MARIADB_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MARIADB_VER} ${MYSQL_VER}; fi + - wait_for() { docker run --net sequelize_default jwilder/dockerize -timeout 2m -wait "$1"; } + - if [ $POSTGRES_VER ]; then wait_for tcp://${POSTGRES_VER}:5432; fi + - if [ $MARIADB_VER ]; then wait_for tcp://${MARIADB_VER}:3306; fi + - if [ $MYSQL_VER ]; then wait_for tcp://${MYSQL_VER}:3306; fi script: - |- From 92cbae90fd628ac87b844ee565811ee3b95097b7 Mon Sep 17 00:00:00 2001 From: Kenny D Date: Mon, 20 Apr 2020 13:03:29 +0700 Subject: [PATCH 090/414] docs(model): correct syntax error in example code (#12137) --- lib/model.js | 2 +- types/lib/model.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/model.js b/lib/model.js index 87215fe56da8..a22a12e20eb8 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2056,7 +2056,7 @@ class Model { * include: [ * { model: Profile, required: true} * ], - * limit 3 + * limit: 3 * }); * * # Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index ddc8be6e907e..53b9b75eb9c6 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1852,7 +1852,7 @@ export abstract class Model extends Hooks { * include: [ * { model: Profile, required: true} * ], - * limit 3 + * limit: 3 * }); * ``` * Because the include for `Profile` has `required` set it will result in an inner join, and only the users From ceb0de261e779bee38f601745738dd21428ff630 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 20 Apr 2020 01:35:57 -0500 Subject: [PATCH 091/414] refactor(dialects/abstract): asyncify methods (#12128) --- lib/dialects/abstract/connection-manager.js | 144 ++++++++++---------- 1 file changed, 69 insertions(+), 75 deletions(-) diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index 336124ddb93f..b30a59c19b32 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -91,15 +91,15 @@ class ConnectionManager { * @private * @returns {Promise} */ - _onProcessExit() { + async _onProcessExit() { if (!this.pool) { - return Promise.resolve(); + return; } - return this.pool.drain().then(() => { - debug('connection drain due to process exit'); - return this.pool.destroyAllNow(); - }); + await this.pool.drain(); + debug('connection drain due to process exit'); + + return await this.pool.destroyAllNow(); } /** @@ -107,13 +107,13 @@ class ConnectionManager { * * @returns {Promise} */ - close() { + async close() { // Mark close of pool - this.getConnection = function getConnection() { - return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!')); + this.getConnection = async function getConnection() { + throw new Error('ConnectionManager.getConnection was called after the connection manager was closed!'); }; - return this._onProcessExit(); + return await this._onProcessExit(); } /** @@ -127,11 +127,10 @@ class ConnectionManager { this.pool = new Pool({ name: 'sequelize', create: () => this._connect(config), - destroy: connection => { - return this._disconnect(connection) - .then(result => { - debug('connection destroy');return result; - }); + destroy: async connection => { + const result = await this._disconnect(connection); + debug('connection destroy'); + return result; }, validate: config.pool.validate, max: config.pool.max, @@ -180,27 +179,26 @@ class ConnectionManager { this.pool[connection.queryType].destroy(connection); debug('connection destroy'); }, - destroyAllNow: () => { - return Promise.all([ + destroyAllNow: async () => { + await Promise.all([ this.pool.read.destroyAllNow(), this.pool.write.destroyAllNow() - ]).then(() => { debug('all connections destroyed'); }); - }, - drain: () => { - return Promise.all([ - this.pool.write.drain(), - this.pool.read.drain() ]); + + debug('all connections destroyed'); }, + drain: async () => Promise.all([ + this.pool.write.drain(), + this.pool.read.drain() + ]), read: new Pool({ name: 'sequelize:read', - create: () => { + create: async () => { // round robin config const nextRead = reads++ % config.replication.read.length; - return this._connect(config.replication.read[nextRead]).then(connection => { - connection.queryType = 'read'; - return connection; - }); + const connection = await this._connect(config.replication.read[nextRead]); + connection.queryType = 'read'; + return connection; }, destroy: connection => this._disconnect(connection), validate: config.pool.validate, @@ -213,11 +211,10 @@ class ConnectionManager { }), write: new Pool({ name: 'sequelize:write', - create: () => { - return this._connect(config.replication.write).then(connection => { - connection.queryType = 'write'; - return connection; - }); + create: async () => { + const connection = await this._connect(config.replication.write); + connection.queryType = 'write'; + return connection; }, destroy: connection => this._disconnect(connection), validate: config.pool.validate, @@ -243,16 +240,14 @@ class ConnectionManager { * * @returns {Promise} */ - getConnection(options) { + async getConnection(options) { options = options || {}; - let promise; if (this.sequelize.options.databaseVersion === 0) { - if (this.versionPromise) { - promise = this.versionPromise; - } else { - promise = this.versionPromise = this._connect(this.config.replication.write || this.config) - .then(connection => { + if (!this.versionPromise) { + this.versionPromise = (async () => { + try { + const connection = await this._connect(this.config.replication.write || this.config); const _options = {}; _options.transaction = { connection }; // Cheat .query to use our private connection @@ -262,36 +257,36 @@ class ConnectionManager { //connection might have set databaseVersion value at initialization, //avoiding a useless round trip if (this.sequelize.options.databaseVersion === 0) { - return this.sequelize.databaseVersion(_options).then(version => { - const parsedVersion = _.get(semver.coerce(version), 'version') || version; - this.sequelize.options.databaseVersion = semver.valid(parsedVersion) - ? parsedVersion - : this.defaultVersion; - this.versionPromise = null; - return this._disconnect(connection); - }); + const version = await this.sequelize.databaseVersion(_options); + const parsedVersion = _.get(semver.coerce(version), 'version') || version; + this.sequelize.options.databaseVersion = semver.valid(parsedVersion) + ? parsedVersion + : this.defaultVersion; } this.versionPromise = null; - return this._disconnect(connection); - }).catch(err => { + return await this._disconnect(connection); + } catch (err) { this.versionPromise = null; throw err; - }); + } + })(); } - } else { - promise = Promise.resolve(); + await this.versionPromise; } - return promise.then(() => { - return this.pool.acquire(options.type, options.useMaster) - .catch(error => { - if (error instanceof TimeoutError) throw new errors.ConnectionAcquireTimeoutError(error); - throw error; - }); - }).then(result => { - debug('connection acquired');return result; - }); + let result; + + try { + result = await this.pool.acquire(options.type, options.useMaster); + } catch (error) { + if (error instanceof TimeoutError) throw new errors.ConnectionAcquireTimeoutError(error); + throw error; + } + + debug('connection acquired'); + + return result; } /** @@ -301,11 +296,9 @@ class ConnectionManager { * * @returns {Promise} */ - releaseConnection(connection) { - return Promise.resolve().then(() => { - this.pool.release(connection); - debug('connection released'); - }); + async releaseConnection(connection) { + this.pool.release(connection); + debug('connection released'); } /** @@ -315,10 +308,11 @@ class ConnectionManager { * @private * @returns {Promise} */ - _connect(config) { - return this.sequelize.runHooks('beforeConnect', config) - .then(() => this.dialect.connectionManager.connect(config)) - .then(connection => this.sequelize.runHooks('afterConnect', connection, config).then(() => connection)); + async _connect(config) { + await this.sequelize.runHooks('beforeConnect', config); + const connection = await this.dialect.connectionManager.connect(config); + await this.sequelize.runHooks('afterConnect', connection, config); + return connection; } /** @@ -328,10 +322,10 @@ class ConnectionManager { * @private * @returns {Promise} */ - _disconnect(connection) { - return this.sequelize.runHooks('beforeDisconnect', connection) - .then(() => this.dialect.connectionManager.disconnect(connection)) - .then(() => this.sequelize.runHooks('afterDisconnect', connection)); + async _disconnect(connection) { + await this.sequelize.runHooks('beforeDisconnect', connection); + await this.dialect.connectionManager.disconnect(connection); + return this.sequelize.runHooks('afterDisconnect', connection); } /** From 722ed5056377e5aa18a1de66cd8accc9969ae65a Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 20 Apr 2020 01:38:18 -0500 Subject: [PATCH 092/414] refactor(dialects/postgres): asyncify methods (#12129) --- lib/dialects/postgres/connection-manager.js | 155 ++++---- lib/dialects/postgres/query-interface.js | 178 +++++----- lib/dialects/postgres/query.js | 371 ++++++++++---------- 3 files changed, 350 insertions(+), 354 deletions(-) diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 7950bca9da59..994b9cb398a0 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -84,7 +84,7 @@ class ConnectionManager extends AbstractConnectionManager { return this.lib.types.getTypeParser(oid, ...args); } - connect(config) { + async connect(config) { config.user = config.username; const connectionConfig = _.pick(config, [ 'user', 'password', 'host', 'database', 'port' @@ -121,7 +121,7 @@ class ConnectionManager extends AbstractConnectionManager { ])); } - return new Promise((resolve, reject) => { + const connection = await new Promise((resolve, reject) => { let responded = false; const connection = new this.lib.Client(connectionConfig); @@ -194,76 +194,71 @@ class ConnectionManager extends AbstractConnectionManager { resolve(connection); } }); - }).then(connection => { - let query = ''; - - if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') { - // Disable escape characters in strings - // see https://github.com/sequelize/sequelize/issues/3545 (security issue) - // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS - query += 'SET standard_conforming_strings=on;'; - } + }); - if (this.sequelize.options.clientMinMessages !== false) { - query += `SET client_min_messages TO ${this.sequelize.options.clientMinMessages};`; - } + let query = ''; - if (!this.sequelize.config.keepDefaultTimezone) { - const isZone = !!moment.tz.zone(this.sequelize.options.timezone); - if (isZone) { - query += `SET TIME ZONE '${this.sequelize.options.timezone}';`; - } else { - query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`; - } - } + if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') { + // Disable escape characters in strings + // see https://github.com/sequelize/sequelize/issues/3545 (security issue) + // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS + query += 'SET standard_conforming_strings=on;'; + } - if (query) { - return Promise.resolve(connection.query(query)).then(() => connection); - } - return connection; - }).then(connection => { - if (Object.keys(this.nameOidMap).length === 0 && - this.enumOids.oids.length === 0 && - this.enumOids.arrayOids.length === 0) { - return Promise.resolve(this._refreshDynamicOIDs(connection)).then(() => connection); + if (this.sequelize.options.clientMinMessages !== false) { + query += `SET client_min_messages TO ${this.sequelize.options.clientMinMessages};`; + } + + if (!this.sequelize.config.keepDefaultTimezone) { + const isZone = !!moment.tz.zone(this.sequelize.options.timezone); + if (isZone) { + query += `SET TIME ZONE '${this.sequelize.options.timezone}';`; + } else { + query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`; } - return connection; - }).then(connection => { - // Don't let a Postgres restart (or error) to take down the whole app - connection.on('error', error => { - connection._invalid = true; - debug(`connection error ${error.code || error.message}`); - this.pool.destroy(connection); - }); - return connection; + } + + if (query) { + await connection.query(query); + } + if (Object.keys(this.nameOidMap).length === 0 && + this.enumOids.oids.length === 0 && + this.enumOids.arrayOids.length === 0) { + await this._refreshDynamicOIDs(connection); + } + // Don't let a Postgres restart (or error) to take down the whole app + connection.on('error', error => { + connection._invalid = true; + debug(`connection error ${error.code || error.message}`); + this.pool.destroy(connection); }); + + return connection; } - disconnect(connection) { + async disconnect(connection) { if (connection._ending) { debug('connection tried to disconnect but was already at ENDING state'); - return Promise.resolve(); + return; } - return promisify(callback => connection.end(callback))(); + return await promisify(callback => connection.end(callback))(); } validate(connection) { return !connection._invalid && !connection._ending; } - _refreshDynamicOIDs(connection) { + async _refreshDynamicOIDs(connection) { const databaseVersion = this.sequelize.options.databaseVersion; const supportedVersion = '8.3.0'; // Check for supported version if ( (databaseVersion && semver.gte(databaseVersion, supportedVersion)) === false) { - return Promise.resolve(); + return; } - // Refresh dynamic OIDs for some types - // These include Geometry / Geography / HStore / Enum / Citext / Range - return (connection || this.sequelize).query( + const results = await (connection || this.sequelize).query( 'WITH ranges AS (' + ' SELECT pg_range.rngtypid, pg_type.typname AS rngtypname,' + ' pg_type.typarray AS rngtyparray, pg_range.rngsubtype' + @@ -273,46 +268,46 @@ class ConnectionManager extends AbstractConnectionManager { ' ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray' + ' FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype' + ' WHERE (pg_type.typtype IN(\'b\', \'e\'));' - ).then(results => { - let result = Array.isArray(results) ? results.pop() : results; - - // When searchPath is prepended then two statements are executed and the result is - // an array of those two statements. First one is the SET search_path and second is - // the SELECT query result. - if (Array.isArray(result)) { - if (result[0].command === 'SET') { - result = result.pop(); - } + ); + + let result = Array.isArray(results) ? results.pop() : results; + + // When searchPath is prepended then two statements are executed and the result is + // an array of those two statements. First one is the SET search_path and second is + // the SELECT query result. + if (Array.isArray(result)) { + if (result[0].command === 'SET') { + result = result.pop(); } + } - const newNameOidMap = {}; - const newEnumOids = { oids: [], arrayOids: [] }; + const newNameOidMap = {}; + const newEnumOids = { oids: [], arrayOids: [] }; - for (const row of result.rows) { - // Mapping enums, handled separatedly - if (row.typtype === 'e') { - newEnumOids.oids.push(row.oid); - if (row.typarray) newEnumOids.arrayOids.push(row.typarray); - continue; - } + for (const row of result.rows) { + // Mapping enums, handled separatedly + if (row.typtype === 'e') { + newEnumOids.oids.push(row.oid); + if (row.typarray) newEnumOids.arrayOids.push(row.typarray); + continue; + } - // Mapping base types and their arrays - newNameOidMap[row.typname] = { oid: row.oid }; - if (row.typarray) newNameOidMap[row.typname].arrayOid = row.typarray; + // Mapping base types and their arrays + newNameOidMap[row.typname] = { oid: row.oid }; + if (row.typarray) newNameOidMap[row.typname].arrayOid = row.typarray; - // Mapping ranges(of base types) and their arrays - if (row.rngtypid) { - newNameOidMap[row.typname].rangeOid = row.rngtypid; - if (row.rngtyparray) newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray; - } + // Mapping ranges(of base types) and their arrays + if (row.rngtypid) { + newNameOidMap[row.typname].rangeOid = row.rngtypid; + if (row.rngtyparray) newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray; } + } - // Replace all OID mappings. Avoids temporary empty OID mappings. - this.nameOidMap = newNameOidMap; - this.enumOids = newEnumOids; + // Replace all OID mappings. Avoids temporary empty OID mappings. + this.nameOidMap = newNameOidMap; + this.enumOids = newEnumOids; - this.refreshTypeParser(dataTypes.postgres); - }); + this.refreshTypeParser(dataTypes.postgres); } _clearDynamicOIDs() { diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 19ef66123d35..b7dfbd903068 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -26,7 +26,7 @@ const _ = require('lodash'); * @returns {Promise} * @private */ -function ensureEnums(qi, tableName, attributes, options, model) { +async function ensureEnums(qi, tableName, attributes, options, model) { const keys = Object.keys(attributes); const keyLen = keys.length; @@ -50,108 +50,106 @@ function ensureEnums(qi, tableName, attributes, options, model) { } } - return Promise.all(promises).then(results => { - promises = []; - let enumIdx = 0; - - // This little function allows us to re-use the same code that prepends or appends new value to enum array - const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { - const valueOptions = _.clone(options); - valueOptions.before = null; - valueOptions.after = null; - - switch (position) { - case 'after': - valueOptions.after = relativeValue; - break; - case 'before': - default: - valueOptions.before = relativeValue; - break; - } + const results = await Promise.all(promises); + promises = []; + let enumIdx = 0; + + // This little function allows us to re-use the same code that prepends or appends new value to enum array + const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { + const valueOptions = _.clone(options); + valueOptions.before = null; + valueOptions.after = null; + + switch (position) { + case 'after': + valueOptions.after = relativeValue; + break; + case 'before': + default: + valueOptions.before = relativeValue; + break; + } - promises.splice(spliceStart, 0, () => { - return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd( - tableName, field, value, valueOptions - ), valueOptions); - }); - }; - - for (i = 0; i < keyLen; i++) { - const attribute = attributes[keys[i]]; - const type = attribute.type; - const enumType = type.type || type; - const field = attribute.field || keys[i]; - - if ( - type instanceof DataTypes.ENUM || - type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM - ) { - // If the enum type doesn't exist then create it - if (!results[enumIdx]) { - promises.push(() => { - return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true })); - }); - } else if (!!results[enumIdx] && !!model) { - const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value); - const vals = enumType.values; - - // Going through already existing values allows us to make queries that depend on those values - // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values - // Then we append the rest of new values AFTER the latest already existing value - // E.g.: [1,2] -> [0,2,1] ==> [1,0,2] - // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4] - // E.g.: [1] -> [0,2,3] ==> [1,0,2,3] - let lastOldEnumValue; - let rightestPosition = -1; - for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) { - const enumVal = enumVals[oldIndex]; - const newIdx = vals.indexOf(enumVal); - lastOldEnumValue = enumVal; - - if (newIdx === -1) { - continue; - } + promises.splice(spliceStart, 0, () => { + return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd( + tableName, field, value, valueOptions + ), valueOptions); + }); + }; - const newValuesBefore = vals.slice(0, newIdx); - const promisesLength = promises.length; - // we go in reverse order so we could stop when we meet old value - for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { - if (~enumVals.indexOf(newValuesBefore[reverseIdx])) { - break; - } + for (i = 0; i < keyLen; i++) { + const attribute = attributes[keys[i]]; + const type = attribute.type; + const enumType = type.type || type; + const field = attribute.field || keys[i]; - addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength); - } + if ( + type instanceof DataTypes.ENUM || + type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM + ) { + // If the enum type doesn't exist then create it + if (!results[enumIdx]) { + promises.push(() => { + return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true })); + }); + } else if (!!results[enumIdx] && !!model) { + const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value); + const vals = enumType.values; + + // Going through already existing values allows us to make queries that depend on those values + // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values + // Then we append the rest of new values AFTER the latest already existing value + // E.g.: [1,2] -> [0,2,1] ==> [1,0,2] + // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4] + // E.g.: [1] -> [0,2,3] ==> [1,0,2,3] + let lastOldEnumValue; + let rightestPosition = -1; + for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) { + const enumVal = enumVals[oldIndex]; + const newIdx = vals.indexOf(enumVal); + lastOldEnumValue = enumVal; + + if (newIdx === -1) { + continue; + } - // we detect the most 'right' position of old value in new enum array so we can append new values to it - if (newIdx > rightestPosition) { - rightestPosition = newIdx; + const newValuesBefore = vals.slice(0, newIdx); + const promisesLength = promises.length; + // we go in reverse order so we could stop when we meet old value + for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { + if (~enumVals.indexOf(newValuesBefore[reverseIdx])) { + break; } + + addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength); } - if (lastOldEnumValue && rightestPosition < vals.length - 1) { - const remainingEnumValues = vals.slice(rightestPosition + 1); - for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { - addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after'); - } + // we detect the most 'right' position of old value in new enum array so we can append new values to it + if (newIdx > rightestPosition) { + rightestPosition = newIdx; } + } - enumIdx++; + if (lastOldEnumValue && rightestPosition < vals.length - 1) { + const remainingEnumValues = vals.slice(rightestPosition + 1); + for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { + addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after'); + } } + + enumIdx++; } } + } - return promises - .reduce((promise, asyncFunction) => promise.then(asyncFunction), Promise.resolve()) - .then(result => { - // If ENUM processed, then refresh OIDs - if (promises.length) { - return Promise.resolve(qi.sequelize.dialect.connectionManager._refreshDynamicOIDs()).then(() => result); - } - return result; - }); - }); + const result = await promises + .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve()); + + // If ENUM processed, then refresh OIDs + if (promises.length) { + await qi.sequelize.dialect.connectionManager._refreshDynamicOIDs(); + } + return result; } diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index b15e6bc417e2..458674564bc6 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -47,7 +47,7 @@ class Query extends AbstractQuery { return [sql, bindParam]; } - run(sql, parameters) { + async run(sql, parameters) { const { connection } = this; if (!_.isEmpty(this.options.searchPath)) { @@ -73,7 +73,11 @@ class Query extends AbstractQuery { const complete = this._logQuery(sql, debug, parameters); - return query.catch(err => { + let queryResult; + + try { + queryResult = await query; + } catch (err) { // set the client so that it will be reaped if the connection resets while executing if (err.code === 'ECONNRESET') { connection._invalid = true; @@ -82,207 +86,206 @@ class Query extends AbstractQuery { err.sql = sql; err.parameters = parameters; throw this.formatError(err); - }) - .then(queryResult => { - complete(); - - let rows = Array.isArray(queryResult) - ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), []) - : queryResult.rows; - const rowCount = Array.isArray(queryResult) - ? queryResult.reduce( - (count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, - 0 - ) - : queryResult.rowCount; - - if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) { - rows = rows - .map(row => _.toPairs(row) - .reduce((acc, [key, value]) => { - const mapping = this.options.aliasesMapping.get(key); - acc[mapping || key] = value; - return acc; - }, {}) - ); - } + } - const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); - const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN'); + complete(); + + let rows = Array.isArray(queryResult) + ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), []) + : queryResult.rows; + const rowCount = Array.isArray(queryResult) + ? queryResult.reduce( + (count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, + 0 + ) + : queryResult.rowCount; + + if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) { + rows = rows + .map(row => _.toPairs(row) + .reduce((acc, [key, value]) => { + const mapping = this.options.aliasesMapping.get(key); + acc[mapping || key] = value; + return acc; + }, {}) + ); + } - if (isRelNameQuery) { - return rows.map(row => ({ - name: row.relname, - tableName: row.relname.split('_')[0] - })); - } - if (isTableNameQuery) { - return rows.map(row => _.values(row)); - } + const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); + const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN'); - if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { - if (rows[0].sequelize_caught_exception !== null) { - throw this.formatError({ - code: '23505', - detail: rows[0].sequelize_caught_exception - }); - } - for (const row of rows) { - delete row.sequelize_caught_exception; - } - } + if (isRelNameQuery) { + return rows.map(row => ({ + name: row.relname, + tableName: row.relname.split('_')[0] + })); + } + if (isTableNameQuery) { + return rows.map(row => _.values(row)); + } - if (this.isShowIndexesQuery()) { - for (const row of rows) { - const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(','); - - // Map column index in table to column name - const columns = _.zipObject( - row.column_indexes, - this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.column_names) - ); - delete row.column_indexes; - delete row.column_names; - - let field; - let attribute; - - // Indkey is the order of attributes in the index, specified by a string of attribute indexes - row.fields = row.indkey.split(' ').map((indKey, index) => { - field = columns[indKey]; - // for functional indices indKey = 0 - if (!field) { - return null; - } - attribute = attributes[index]; - return { - attribute: field, - collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined, - order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined, - length: undefined - }; - }).filter(n => n !== null); - delete row.columns; + if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { + if (rows[0].sequelize_caught_exception !== null) { + throw this.formatError({ + code: '23505', + detail: rows[0].sequelize_caught_exception + }); + } + for (const row of rows) { + delete row.sequelize_caught_exception; + } + } + + if (this.isShowIndexesQuery()) { + for (const row of rows) { + const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(','); + + // Map column index in table to column name + const columns = _.zipObject( + row.column_indexes, + this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.column_names) + ); + delete row.column_indexes; + delete row.column_names; + + let field; + let attribute; + + // Indkey is the order of attributes in the index, specified by a string of attribute indexes + row.fields = row.indkey.split(' ').map((indKey, index) => { + field = columns[indKey]; + // for functional indices indKey = 0 + if (!field) { + return null; } - return rows; - } - if (this.isForeignKeysQuery()) { - const result = []; - for (const row of rows) { - let defParts; - if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) { - row.id = row.constraint_name; - row.table = defParts[2]; - row.from = defParts[1]; - row.to = defParts[3]; - let i; - for (i = 5; i <= 8; i += 3) { - if (/(UPDATE|DELETE)/.test(defParts[i])) { - row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1]; - } - } + attribute = attributes[index]; + return { + attribute: field, + collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined, + order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined, + length: undefined + }; + }).filter(n => n !== null); + delete row.columns; + } + return rows; + } + if (this.isForeignKeysQuery()) { + const result = []; + for (const row of rows) { + let defParts; + if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) { + row.id = row.constraint_name; + row.table = defParts[2]; + row.from = defParts[1]; + row.to = defParts[3]; + let i; + for (i = 5; i <= 8; i += 3) { + if (/(UPDATE|DELETE)/.test(defParts[i])) { + row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1]; } - result.push(row); } - return result; } - if (this.isSelectQuery()) { - let result = rows; - // Postgres will treat tables as case-insensitive, so fix the case - // of the returned values to match attributes - if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { - const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => { - m[k.toLowerCase()] = k; - return m; - }, {}); - result = rows.map(row => { - return _.mapKeys(row, (value, key) => { - const targetAttr = attrsMap[key]; - if (typeof targetAttr === 'string' && targetAttr !== key) { - return targetAttr; - } - return key; - }); - }); + result.push(row); + } + return result; + } + if (this.isSelectQuery()) { + let result = rows; + // Postgres will treat tables as case-insensitive, so fix the case + // of the returned values to match attributes + if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { + const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => { + m[k.toLowerCase()] = k; + return m; + }, {}); + result = rows.map(row => { + return _.mapKeys(row, (value, key) => { + const targetAttr = attrsMap[key]; + if (typeof targetAttr === 'string' && targetAttr !== key) { + return targetAttr; + } + return key; + }); + }); + } + return this.handleSelectQuery(result); + } + if (QueryTypes.DESCRIBE === this.options.type) { + const result = {}; + + for (const row of rows) { + result[row.Field] = { + type: row.Type.toUpperCase(), + allowNull: row.Null === 'YES', + defaultValue: row.Default, + comment: row.Comment, + special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [], + primaryKey: row.Constraint === 'PRIMARY KEY' + }; + + if (result[row.Field].type === 'BOOLEAN') { + result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue]; + + if (result[row.Field].defaultValue === undefined) { + result[row.Field].defaultValue = null; } - return this.handleSelectQuery(result); } - if (QueryTypes.DESCRIBE === this.options.type) { - const result = {}; - - for (const row of rows) { - result[row.Field] = { - type: row.Type.toUpperCase(), - allowNull: row.Null === 'YES', - defaultValue: row.Default, - comment: row.Comment, - special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [], - primaryKey: row.Constraint === 'PRIMARY KEY' - }; - - if (result[row.Field].type === 'BOOLEAN') { - result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue]; - - if (result[row.Field].defaultValue === undefined) { - result[row.Field].defaultValue = null; - } - } - if (typeof result[row.Field].defaultValue === 'string') { - result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, ''); + if (typeof result[row.Field].defaultValue === 'string') { + result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, ''); - if (result[row.Field].defaultValue.includes('::')) { - const split = result[row.Field].defaultValue.split('::'); - if (split[1].toLowerCase() !== 'regclass)') { - result[row.Field].defaultValue = split[0]; - } - } + if (result[row.Field].defaultValue.includes('::')) { + const split = result[row.Field].defaultValue.split('::'); + if (split[1].toLowerCase() !== 'regclass)') { + result[row.Field].defaultValue = split[0]; } } - - return result; - } - if (this.isVersionQuery()) { - return rows[0].server_version; - } - if (this.isShowOrDescribeQuery()) { - return rows; - } - if (QueryTypes.BULKUPDATE === this.options.type) { - if (!this.options.returning) { - return parseInt(rowCount, 10); - } - return this.handleSelectQuery(rows); - } - if (QueryTypes.BULKDELETE === this.options.type) { - return parseInt(rowCount, 10); - } - if (this.isUpsertQuery()) { - return rows[0]; } - if (this.isInsertQuery() || this.isUpdateQuery()) { - if (this.instance && this.instance.dataValues) { - for (const key in rows[0]) { - if (Object.prototype.hasOwnProperty.call(rows[0], key)) { - const record = rows[0][key]; + } + + return result; + } + if (this.isVersionQuery()) { + return rows[0].server_version; + } + if (this.isShowOrDescribeQuery()) { + return rows; + } + if (QueryTypes.BULKUPDATE === this.options.type) { + if (!this.options.returning) { + return parseInt(rowCount, 10); + } + return this.handleSelectQuery(rows); + } + if (QueryTypes.BULKDELETE === this.options.type) { + return parseInt(rowCount, 10); + } + if (this.isUpsertQuery()) { + return rows[0]; + } + if (this.isInsertQuery() || this.isUpdateQuery()) { + if (this.instance && this.instance.dataValues) { + for (const key in rows[0]) { + if (Object.prototype.hasOwnProperty.call(rows[0], key)) { + const record = rows[0][key]; - const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); - this.instance.dataValues[attr && attr.fieldName || key] = record; - } - } + this.instance.dataValues[attr && attr.fieldName || key] = record; } - - return [ - this.instance || rows && (this.options.plain && rows[0] || rows) || undefined, - rowCount - ]; } - if (this.isRawQuery()) { - return [rows, queryResult]; - } - return rows; - }); + } + + return [ + this.instance || rows && (this.options.plain && rows[0] || rows) || undefined, + rowCount + ]; + } + if (this.isRawQuery()) { + return [rows, queryResult]; + } + return rows; } formatError(err) { From 1432cfd1b08483a18b6161c83eb60c0110b4fd22 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 20 Apr 2020 01:40:37 -0500 Subject: [PATCH 093/414] refactor(dialects/mysql): asyncify methods (#12130) --- lib/dialects/mysql/connection-manager.js | 131 +++++++++++------------ lib/dialects/mysql/query-interface.js | 73 ++++++------- lib/dialects/mysql/query.js | 49 ++++----- 3 files changed, 119 insertions(+), 134 deletions(-) diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 9b7869e0ed0c..22439b1833b2 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -54,7 +54,7 @@ class ConnectionManager extends AbstractConnectionManager { * @returns {Promise} * @private */ - connect(config) { + async connect(config) { const connectionConfig = Object.assign({ host: config.host, port: config.port, @@ -68,84 +68,77 @@ class ConnectionManager extends AbstractConnectionManager { supportBigNumbers: true }, config.dialectOptions); - return new Promise((resolve, reject) => { - const connection = this.lib.createConnection(connectionConfig); - - const errorHandler = e => { - // clean up connect & error event if there is error - connection.removeListener('connect', connectHandler); - connection.removeListener('error', connectHandler); - reject(e); - }; - - const connectHandler = () => { - // clean up error event if connected - connection.removeListener('error', errorHandler); - resolve(connection); - }; - - // don't use connection.once for error event handling here - // mysql2 emit error two times in case handshake was failed - // first error is protocol_lost and second is timeout - // if we will use `once.error` node process will crash on 2nd error emit - connection.on('error', errorHandler); - connection.once('connect', connectHandler); - }) - .then(result => { - debug('connection acquired');return result; - }) - .then(connection => { - connection.on('error', error => { - switch (error.code) { - case 'ESOCKET': - case 'ECONNRESET': - case 'EPIPE': - case 'PROTOCOL_CONNECTION_LOST': - this.pool.destroy(connection); - } - }); - - return new Promise((resolve, reject) => { - if (!this.sequelize.config.keepDefaultTimezone) { - // set timezone for this connection - // but named timezone are not directly supported in mysql, so get its offset first - let tzOffset = this.sequelize.options.timezone; - tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') : tzOffset; - return connection.query(`SET time_zone = '${tzOffset}'`, err => { - if (err) { reject(err); } else { resolve(connection); } - }); - } - - // return connection without executing SET time_zone query + try { + const connection = await new Promise((resolve, reject) => { + const connection = this.lib.createConnection(connectionConfig); + + const errorHandler = e => { + // clean up connect & error event if there is error + connection.removeListener('connect', connectHandler); + connection.removeListener('error', connectHandler); + reject(e); + }; + + const connectHandler = () => { + // clean up error event if connected + connection.removeListener('error', errorHandler); resolve(connection); - }); - }) - .catch(err => { - switch (err.code) { - case 'ECONNREFUSED': - throw new SequelizeErrors.ConnectionRefusedError(err); - case 'ER_ACCESS_DENIED_ERROR': - throw new SequelizeErrors.AccessDeniedError(err); - case 'ENOTFOUND': - throw new SequelizeErrors.HostNotFoundError(err); - case 'EHOSTUNREACH': - throw new SequelizeErrors.HostNotReachableError(err); - case 'EINVAL': - throw new SequelizeErrors.InvalidConnectionError(err); - default: - throw new SequelizeErrors.ConnectionError(err); + }; + + // don't use connection.once for error event handling here + // mysql2 emit error two times in case handshake was failed + // first error is protocol_lost and second is timeout + // if we will use `once.error` node process will crash on 2nd error emit + connection.on('error', errorHandler); + connection.once('connect', connectHandler); + }); + + debug('connection acquired'); + connection.on('error', error => { + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + case 'EPIPE': + case 'PROTOCOL_CONNECTION_LOST': + this.pool.destroy(connection); } }); + + if (!this.sequelize.config.keepDefaultTimezone) { + // set timezone for this connection + // but named timezone are not directly supported in mysql, so get its offset first + let tzOffset = this.sequelize.options.timezone; + tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') : tzOffset; + await promisify(cb => connection.query(`SET time_zone = '${tzOffset}'`, cb))(); + } + + return connection; + } catch (err) { + switch (err.code) { + case 'ECONNREFUSED': + throw new SequelizeErrors.ConnectionRefusedError(err); + case 'ER_ACCESS_DENIED_ERROR': + throw new SequelizeErrors.AccessDeniedError(err); + case 'ENOTFOUND': + throw new SequelizeErrors.HostNotFoundError(err); + case 'EHOSTUNREACH': + throw new SequelizeErrors.HostNotReachableError(err); + case 'EINVAL': + throw new SequelizeErrors.InvalidConnectionError(err); + default: + throw new SequelizeErrors.ConnectionError(err); + } + } } - disconnect(connection) { + async disconnect(connection) { // Don't disconnect connections with CLOSED state if (connection._closing) { debug('connection tried to disconnect but was already at CLOSED state'); - return Promise.resolve(); + return; } - return promisify(callback => connection.end(callback))(); + return await promisify(callback => connection.end(callback))(); } validate(connection) { diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index b2c33ac599e0..147467937ec2 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -21,31 +21,29 @@ const sequelizeErrors = require('../../errors'); @private */ -function removeColumn(qi, tableName, columnName, options) { +async function removeColumn(qi, tableName, columnName, options) { options = options || {}; - return qi.sequelize.query( + const [results] = await qi.sequelize.query( qi.QueryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { tableName, schema: qi.sequelize.config.database }, columnName), Object.assign({ raw: true }, options) - ) - .then(([results]) => { - //Exclude primary key constraint - if (!results.length || results[0].constraint_name === 'PRIMARY') { - // No foreign key constraints found, so we can remove the column - return; - } - return Promise.all(results.map(constraint => qi.sequelize.query( - qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), - Object.assign({ raw: true }, options) - ))); - }) - .then(() => qi.sequelize.query( - qi.QueryGenerator.removeColumnQuery(tableName, columnName), + ); + + //Exclude primary key constraint + if (results.length && results[0].constraint_name !== 'PRIMARY') { + await Promise.all(results.map(constraint => qi.sequelize.query( + qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), Object.assign({ raw: true }, options) - )); + ))); + } + + return await qi.sequelize.query( + qi.QueryGenerator.removeColumnQuery(tableName, columnName), + Object.assign({ raw: true }, options) + ); } /** @@ -56,35 +54,34 @@ function removeColumn(qi, tableName, columnName, options) { * * @private */ -function removeConstraint(qi, tableName, constraintName, options) { +async function removeConstraint(qi, tableName, constraintName, options) { const sql = qi.QueryGenerator.showConstraintsQuery( tableName.tableName ? tableName : { tableName, schema: qi.sequelize.config.database }, constraintName); - return qi.sequelize.query(sql, Object.assign({}, options, - { type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS })) - .then(constraints => { - const constraint = constraints[0]; - let query; - if (!constraint || !constraint.constraintType) { - throw new sequelizeErrors.UnknownConstraintError( - { - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - } + const constraints = await qi.sequelize.query(sql, Object.assign({}, options, + { type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS })); + + const constraint = constraints[0]; + let query; + if (!constraint || !constraint.constraintType) { + throw new sequelizeErrors.UnknownConstraintError( + { + message: `Constraint ${constraintName} on table ${tableName} does not exist`, + constraint: constraintName, + table: tableName + }); + } - if (constraint.constraintType === 'FOREIGN KEY') { - query = qi.QueryGenerator.dropForeignKeyQuery(tableName, constraintName); - } else { - query = qi.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); - } + if (constraint.constraintType === 'FOREIGN KEY') { + query = qi.QueryGenerator.dropForeignKeyQuery(tableName, constraintName); + } else { + query = qi.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); + } - return qi.sequelize.query(query, options); - }); + return await qi.sequelize.query(query, options); } exports.removeConstraint = removeConstraint; diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index 80c001fada41..17a5e70c564f 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -1,6 +1,5 @@ 'use strict'; -const Utils = require('../../utils'); const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); const _ = require('lodash'); @@ -27,7 +26,7 @@ class Query extends AbstractQuery { return [sql, bindParam.length > 0 ? bindParam : undefined]; } - run(sql, parameters) { + async run(sql, parameters) { this.sql = sql; const { connection, options } = this; @@ -36,7 +35,7 @@ class Query extends AbstractQuery { const complete = this._logQuery(sql, debug, parameters); - return new Utils.Promise((resolve, reject) => { + const results = await new Promise((resolve, reject) => { const handler = (err, results) => { complete(); @@ -59,16 +58,13 @@ class Query extends AbstractQuery { } else { connection.query({ sql }, handler).setMaxListeners(100); } - }) + }); // Log warnings if we've got them. - .then(results => { - if (showWarnings && results && results.warningStatus > 0) { - return this.logWarnings(results); - } - return results; - }) + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } // Return formatted results... - .then(results => this.formatResults(results)); + return this.formatResults(results); } /** @@ -165,27 +161,26 @@ class Query extends AbstractQuery { return result; } - logWarnings(results) { - return this.run('SHOW WARNINGS').then(warningResults => { - const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `; - const messages = []; - for (const _warningRow of warningResults) { - if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') continue; - for (const _warningResult of _warningRow) { - if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { - messages.push(_warningResult.Message); - } else { - for (const _objectKey of _warningResult.keys()) { - messages.push([_objectKey, _warningResult[_objectKey]].join(': ')); - } + async logWarnings(results) { + const warningResults = await this.run('SHOW WARNINGS'); + const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `; + const messages = []; + for (const _warningRow of warningResults) { + if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') continue; + for (const _warningResult of _warningRow) { + if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { + messages.push(_warningResult.Message); + } else { + for (const _objectKey of _warningResult.keys()) { + messages.push([_objectKey, _warningResult[_objectKey]].join(': ')); } } } + } - this.sequelize.log(warningMessage + messages.join('; '), this.options); + this.sequelize.log(warningMessage + messages.join('; '), this.options); - return results; - }); + return results; } formatError(err) { From 39a1f11fc7cd7be0f063e1f582ea78c35f14c5fe Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 20 Apr 2020 01:45:57 -0500 Subject: [PATCH 094/414] refactor(dialects/mariadb): asyncify methods (#12131) --- lib/dialects/mariadb/connection-manager.js | 73 ++++++++-------- lib/dialects/mariadb/query.js | 98 ++++++++++------------ 2 files changed, 80 insertions(+), 91 deletions(-) diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index c18d952ca3ae..e72ffb8856d7 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -2,7 +2,6 @@ const AbstractConnectionManager = require('../abstract/connection-manager'); const SequelizeErrors = require('../../errors'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const DataTypes = require('../../data-types').mariadb; const momentTz = require('moment-timezone'); @@ -53,7 +52,7 @@ class ConnectionManager extends AbstractConnectionManager { * @returns {Promise} * @private */ - connect(config) { + async connect(config) { // Named timezone is not supported in mariadb, convert to offset let tzOffset = this.sequelize.options.timezone; tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') @@ -89,50 +88,48 @@ class ConnectionManager extends AbstractConnectionManager { } } - return this.lib.createConnection(connectionConfig) - .then(connection => { - this.sequelize.options.databaseVersion = connection.serverVersion(); - debug('connection acquired'); - connection.on('error', error => { - switch (error.code) { - case 'ESOCKET': - case 'ECONNRESET': - case 'EPIPE': - case 'PROTOCOL_CONNECTION_LOST': - this.pool.destroy(connection); - } - }); - return connection; - }) - .catch(err => { - switch (err.code) { - case 'ECONNREFUSED': - throw new SequelizeErrors.ConnectionRefusedError(err); - case 'ER_ACCESS_DENIED_ERROR': - case 'ER_ACCESS_DENIED_NO_PASSWORD_ERROR': - throw new SequelizeErrors.AccessDeniedError(err); - case 'ENOTFOUND': - throw new SequelizeErrors.HostNotFoundError(err); - case 'EHOSTUNREACH': - case 'ENETUNREACH': - case 'EADDRNOTAVAIL': - throw new SequelizeErrors.HostNotReachableError(err); - case 'EINVAL': - throw new SequelizeErrors.InvalidConnectionError(err); - default: - throw new SequelizeErrors.ConnectionError(err); + try { + const connection = await this.lib.createConnection(connectionConfig); + this.sequelize.options.databaseVersion = connection.serverVersion(); + debug('connection acquired'); + connection.on('error', error => { + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + case 'EPIPE': + case 'PROTOCOL_CONNECTION_LOST': + this.pool.destroy(connection); } }); + return connection; + } catch (err) { + switch (err.code) { + case 'ECONNREFUSED': + throw new SequelizeErrors.ConnectionRefusedError(err); + case 'ER_ACCESS_DENIED_ERROR': + case 'ER_ACCESS_DENIED_NO_PASSWORD_ERROR': + throw new SequelizeErrors.AccessDeniedError(err); + case 'ENOTFOUND': + throw new SequelizeErrors.HostNotFoundError(err); + case 'EHOSTUNREACH': + case 'ENETUNREACH': + case 'EADDRNOTAVAIL': + throw new SequelizeErrors.HostNotReachableError(err); + case 'EINVAL': + throw new SequelizeErrors.InvalidConnectionError(err); + default: + throw new SequelizeErrors.ConnectionError(err); + } + } } - disconnect(connection) { + async disconnect(connection) { // Don't disconnect connections with CLOSED state if (!connection.isValid()) { debug('connection tried to disconnect but was already at CLOSED state'); - return Promise.resolve(); + return; } - //wrap native Promise into bluebird - return Promise.resolve(connection.end()); + return await connection.end(); } validate(connection) { diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index 331da7ee8170..2e835970a60e 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -4,7 +4,6 @@ const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); const _ = require('lodash'); const DataTypes = require('../../data-types'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const ER_DUP_ENTRY = 1062; @@ -32,7 +31,7 @@ class Query extends AbstractQuery { return [sql, bindParam.length > 0 ? bindParam : undefined]; } - run(sql, parameters) { + async run(sql, parameters) { this.sql = sql; const { connection, options } = this; @@ -44,39 +43,33 @@ class Query extends AbstractQuery { if (parameters) { debug('parameters(%j)', parameters); } - return Promise.resolve( - connection.query(this.sql, parameters) - .then(results => { - complete(); - - // Log warnings if we've got them. - if (showWarnings && results && results.warningStatus > 0) { - return this.logWarnings(results); - } - return results; - }) - .catch(err => { - // MariaDB automatically rolls-back transactions in the event of a deadlock - if (options.transaction && err.errno === 1213) { - options.transaction.finished = 'rollback'; - } + let results; - complete(); + try { + results = await connection.query(this.sql, parameters); + complete(); - err.sql = sql; - err.parameters = parameters; - throw this.formatError(err); - }) - ) // Log warnings if we've got them. - .then(results => { - if (showWarnings && results && results.warningStatus > 0) { - return this.logWarnings(results); - } - return results; - }) - // Return formatted results... - .then(results => this.formatResults(results)); + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + } catch (err) { + // MariaDB automatically rolls-back transactions in the event of a deadlock + if (options.transaction && err.errno === 1213) { + options.transaction.finished = 'rollback'; + } + + complete(); + + err.sql = sql; + err.parameters = parameters; + throw this.formatError(err); + } + + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + return this.formatResults(results); } /** @@ -196,32 +189,31 @@ class Query extends AbstractQuery { } } - logWarnings(results) { - return this.run('SHOW WARNINGS').then(warningResults => { - const warningMessage = `MariaDB Warnings (${this.connection.uuid - || 'default'}): `; - const messages = []; - for (const _warningRow of warningResults) { - if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] - !== 'function') { - continue; - } - for (const _warningResult of _warningRow) { - if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { - messages.push(_warningResult.Message); - } else { - for (const _objectKey of _warningResult.keys()) { - messages.push( - [_objectKey, _warningResult[_objectKey]].join(': ')); - } + async logWarnings(results) { + const warningResults = await this.run('SHOW WARNINGS'); + const warningMessage = `MariaDB Warnings (${this.connection.uuid + || 'default'}): `; + const messages = []; + for (const _warningRow of warningResults) { + if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] + !== 'function') { + continue; + } + for (const _warningResult of _warningRow) { + if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { + messages.push(_warningResult.Message); + } else { + for (const _objectKey of _warningResult.keys()) { + messages.push( + [_objectKey, _warningResult[_objectKey]].join(': ')); } } } + } - this.sequelize.log(warningMessage + messages.join('; '), this.options); + this.sequelize.log(warningMessage + messages.join('; '), this.options); - return results; - }); + return results; } formatError(err) { From a1ec8a188882feb274f60ff9a1bd399d6bbe1164 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 20 Apr 2020 01:54:08 -0500 Subject: [PATCH 095/414] test: replace Promise.join calls with Promise.all (#12134) --- .../associations/belongs-to-many.test.js | 277 +++++--------- .../associations/belongs-to.test.js | 45 +-- .../integration/associations/has-many.test.js | 300 +++++++-------- test/integration/associations/has-one.test.js | 40 +- test/integration/associations/scope.test.js | 219 +++++------ test/integration/include.test.js | 23 +- test/integration/include/findAll.test.js | 228 ++++++------ .../include/findAndCountAll.test.js | 132 +++---- test/integration/include/findOne.test.js | 58 ++- test/integration/include/limit.test.js | 148 ++++---- test/integration/include/schema.test.js | 4 +- test/integration/include/separate.test.js | 291 +++++++-------- test/integration/model/attributes.test.js | 8 +- .../model/attributes/field.test.js | 13 +- test/integration/model/create.test.js | 163 ++++---- .../model/findAll/groupedLimit.test.js | 16 +- test/integration/model/json.test.js | 351 ++++++++---------- test/integration/transaction.test.js | 175 ++++----- test/unit/model/validation.test.js | 18 +- 19 files changed, 1117 insertions(+), 1392 deletions(-) diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 8c00c6760410..97a36a76221c 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -257,10 +257,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { as: 'users', through: User_has_Group, foreignKey: 'id_group' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - Group.create() - ).then(([user, group]) => { + return Promise.all([User.create(), Group.create()]).then(([user, group]) => { return user.addGroup(group); }).then(() => { return User.findOne({ @@ -307,21 +304,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: User_has_Group }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - Group.create() - ).then(([user, group]) => { + return Promise.all([User.create(), Group.create()]).then(([user, group]) => { return user.addGroup(group); }).then(() => { - return Promise.join( - User.findOne({ - where: {}, - include: [Group] - }), - User.findAll({ - include: [Group] - }) - ); + return Promise.all([User.findOne({ + where: {}, + include: [Group] + }), User.findAll({ + include: [Group] + })]); }).then(([user, users]) => { expect(user.Groups.length).to.be.equal(1); expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.ok; @@ -387,23 +378,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); + return Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); }).then(([users, groups]) => { expect(users.length).to.be.equal(2); expect(users[0].Groups.length).to.be.equal(1); @@ -479,23 +462,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: 'usergroups', targetKey: 'userSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); + return Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); }).then(([users, groups]) => { expect(users.length).to.be.equal(2); expect(users[0].Groups.length).to.be.equal(1); @@ -577,23 +552,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); + return Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); }).then(([users, groups]) => { expect(users.length).to.be.equal(2); expect(users[0].Groups.length).to.be.equal(1); @@ -686,23 +653,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: User_has_Group, sourceKey: 'groupSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); + return Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); }).then(([users, groups]) => { expect(users.length).to.be.equal(2); expect(users[0].Groups.length).to.be.equal(1); @@ -782,19 +741,11 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)) + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]) .then(() => { - return Promise.join( - user1.getGroups(), - user2.getGroups(), - group1.getUsers(), - group2.getUsers() + return Promise.all( + [user1.getGroups(), user2.getGroups(), group1.getUsers(), group2.getUsers()] ); }).then(([groups1, groups2, users1, users2]) => { expect(groups1.length).to.be.equal(1); @@ -863,23 +814,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: 'usergroups', foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); + return Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); }).then(([users, groups]) => { expect(users.length).to.be.equal(2); expect(users[0].Groups.length).to.be.equal(1); @@ -982,23 +925,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(User, { through: User_has_Group, foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); + return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { + return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); + return Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); }).then(([users, groups]) => { expect(users.length).to.be.equal(2); expect(users[0].Groups.length).to.be.equal(1); @@ -1076,40 +1011,28 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(Company, { through: Company_has_Group }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - Group.create(), - Company.create() - ).then(([user, group, company]) => { - return Promise.join( - user.setCompany(company), - company.addGroup(group) - ); + return Promise.all([User.create(), Group.create(), Company.create()]).then(([user, group, company]) => { + return Promise.all([user.setCompany(company), company.addGroup(group)]); }).then(() => { - return Promise.join( - User.findOne({ - where: {}, - include: [ - { model: Company, include: [Group] } - ] - }), - User.findAll({ - include: [ - { model: Company, include: [Group] } - ] - }), - User.findOne({ - where: {}, - include: [ - { model: Company, required: true, include: [Group] } - ] - }), - User.findAll({ - include: [ - { model: Company, required: true, include: [Group] } - ] - }) - ); + return Promise.all([User.findOne({ + where: {}, + include: [ + { model: Company, include: [Group] } + ] + }), User.findAll({ + include: [ + { model: Company, include: [Group] } + ] + }), User.findOne({ + where: {}, + include: [ + { model: Company, required: true, include: [Group] } + ] + }), User.findAll({ + include: [ + { model: Company, required: true, include: [Group] } + ] + })]); }); }); }); @@ -1808,13 +1731,12 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }).then(() => { return Group.create({}); }).then(group => { - return Promise.join( + return Promise.all([ group.createUser({ id: 1 }, { through: { isAdmin: true } }), - group.createUser({ id: 2 }, { through: { isAdmin: false } }), - () => { - return UserGroups.findAll(); - } - ); + group.createUser({ id: 2 }, { through: { isAdmin: false } }) + ]).then(() => { + return UserGroups.findAll(); + }); }).then(userGroups => { userGroups.sort((a, b) => { return a.userId < b.userId ? - 1 : 1; @@ -2413,11 +2335,11 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { const spy = sinon.spy(); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.User.create({ name: 'Matt' }), this.Project.create({ name: 'Good Will Hunting' }), this.Project.create({ name: 'The Departed' }) - ); + ]); }).then(([user, project1, project2]) => { return user.addProjects([project1, project2], { logging: spy @@ -2425,12 +2347,9 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }).then(user => { expect(spy).to.have.been.calledTwice; spy.resetHistory(); - return Promise.join( - user, - user.getProjects({ - logging: spy - }) - ); + return Promise.all([user, user.getProjects({ + logging: spy + })]); }).then(([user, projects]) => { expect(spy.calledOnce).to.be.ok; const project = projects[0]; @@ -2509,11 +2428,11 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Group.create({ groupName: 'The Illuminati' }), this.User.create({ name: 'Matt' }), this.Project.create({ name: 'Good Will Hunting' }) - ); + ]); }).then(([group, user, project]) => { return user.addProject(project).then(() => { return group.addUser(user).then(() => group); @@ -3479,9 +3398,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { it('should load with an alias', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.addPersonwearinghat(hat); }).then(() => { @@ -3507,9 +3427,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { it('should load all', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.addPersonwearinghat(hat); }).then(() => { diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index fc70ddd92387..0308e0e42e4c 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -35,23 +35,19 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.User = Task.belongsTo(User, { as: 'user' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Task.create({ - id: 1, - user: { id: 1 } - }, { - include: [Task.User] - }), - Task.create({ - id: 2, - user: { id: 2 } - }, { - include: [Task.User] - }), - Task.create({ - id: 3 - }) - ); + return Promise.all([Task.create({ + id: 1, + user: { id: 1 } + }, { + include: [Task.User] + }), Task.create({ + id: 2, + user: { id: 2 } + }, { + include: [Task.User] + }), Task.create({ + id: 3 + })]); }).then(tasks => { return Task.User.get(tasks).then(result => { expect(result[tasks[0].id].id).to.equal(tasks[0].user.id); @@ -366,10 +362,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Comment.belongsTo(Post, { foreignKey: 'post_id' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Post.create(), - Comment.create() - ).then(async ([post, comment]) => { + return Promise.all([Post.create(), Comment.create()]).then(async ([post, comment]) => { expect(comment.get('post_id')).not.to.be.ok; const setter = await comment.setPost(post, { save: false }); @@ -1029,9 +1022,10 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { it('should load with an alias', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.setPersonwearinghat(hat); }).then(() => { @@ -1058,9 +1052,10 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { it('should load all', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.setPersonwearinghat(hat); }).then(() => { diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index cef766345cb2..2f712ed54800 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -45,10 +45,10 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { include: [Task] }); }).then(user => { - return Promise.join( + return Promise.all([ user.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }), user.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }) - ).then(() => user); + ]).then(() => user); }).then(user => { return expect(user.countTasks({ attributes: [Task.primaryKeyField, 'title'], @@ -75,29 +75,25 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 3 - }) - ); + return Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 3 + })]); }).then(users => { return User.Tasks.get(users).then(result => { expect(result[users[0].id].length).to.equal(3); @@ -116,27 +112,24 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - tasks: [ - { title: 'b' }, - { title: 'd' }, - { title: 'c' }, - { title: 'a' } - ] - }, { - include: [User.Tasks] - }), - User.create({ - tasks: [ - { title: 'a' }, - { title: 'c' }, - { title: 'b' } - ] - }, { - include: [User.Tasks] - }) - ); + return Promise.all([User.create({ + tasks: [ + { title: 'b' }, + { title: 'd' }, + { title: 'c' }, + { title: 'a' } + ] + }, { + include: [User.Tasks] + }), User.create({ + tasks: [ + { title: 'a' }, + { title: 'c' }, + { title: 'b' } + ] + }, { + include: [User.Tasks] + })]); }).then(users => { return User.Tasks.get(users, { limit: 2, @@ -168,49 +161,46 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - { title: 'b', subtasks: [ - { title: 'c' }, - { title: 'a' } - ] }, - { title: 'd' }, - { title: 'c', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'a', subtasks: [ - { title: 'c' }, - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }), - User.create({ - id: 2, - tasks: [ - { title: 'a', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'c', subtasks: [ - { title: 'a' } - ] }, - { title: 'b', subtasks: [ - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }) - ); + return Promise.all([User.create({ + id: 1, + tasks: [ + { title: 'b', subtasks: [ + { title: 'c' }, + { title: 'a' } + ] }, + { title: 'd' }, + { title: 'c', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'a', subtasks: [ + { title: 'c' }, + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + }), User.create({ + id: 2, + tasks: [ + { title: 'a', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'c', subtasks: [ + { title: 'a' } + ] }, + { title: 'b', subtasks: [ + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + })]); }).then(() => { return User.findAll({ include: [{ @@ -275,27 +265,24 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.Category = Task.belongsTo(Category, { as: 'category', foreignKey: 'categoryId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - tasks: [ - { title: 'b', category: {} }, - { title: 'd', category: {} }, - { title: 'c', category: {} }, - { title: 'a', category: {} } - ] - }, { - include: [{ association: User.Tasks, include: [Task.Category] }] - }), - User.create({ - tasks: [ - { title: 'a', category: {} }, - { title: 'c', category: {} }, - { title: 'b', category: {} } - ] - }, { - include: [{ association: User.Tasks, include: [Task.Category] }] - }) - ); + return Promise.all([User.create({ + tasks: [ + { title: 'b', category: {} }, + { title: 'd', category: {} }, + { title: 'c', category: {} }, + { title: 'a', category: {} } + ] + }, { + include: [{ association: User.Tasks, include: [Task.Category] }] + }), User.create({ + tasks: [ + { title: 'a', category: {} }, + { title: 'c', category: {} }, + { title: 'b', category: {} } + ] + }, { + include: [{ association: User.Tasks, include: [Task.Category] }] + })]); }).then(users => { return User.Tasks.get(users, { limit: 2, @@ -340,49 +327,46 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }).then(() => { return SubTask.sync({ force: true }); }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - { title: 'b', subtasks: [ - { title: 'c' }, - { title: 'a' } - ] }, - { title: 'd' }, - { title: 'c', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'a', subtasks: [ - { title: 'c' }, - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }), - User.create({ - id: 2, - tasks: [ - { title: 'a', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'c', subtasks: [ - { title: 'a' } - ] }, - { title: 'b', subtasks: [ - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }) - ); + return Promise.all([User.create({ + id: 1, + tasks: [ + { title: 'b', subtasks: [ + { title: 'c' }, + { title: 'a' } + ] }, + { title: 'd' }, + { title: 'c', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'a', subtasks: [ + { title: 'c' }, + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + }), User.create({ + id: 2, + tasks: [ + { title: 'a', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'c', subtasks: [ + { title: 'a' } + ] }, + { title: 'b', subtasks: [ + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + })]); }).then(() => { return User.findAll({ include: [{ @@ -1719,9 +1703,10 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { it('should load with an alias', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.addPersonwearinghat(hat); }).then(() => { @@ -1738,9 +1723,10 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { it('should load all', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.addPersonwearinghat(hat); }).then(() => { diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index 54a372a0da19..195828876134 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -33,23 +33,19 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { Player.User = Player.hasOne(User, { as: 'user' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Player.create({ - id: 1, - user: {} - }, { - include: [Player.User] - }), - Player.create({ - id: 2, - user: {} - }, { - include: [Player.User] - }), - Player.create({ - id: 3 - }) - ); + return Promise.all([Player.create({ + id: 1, + user: {} + }, { + include: [Player.User] + }), Player.create({ + id: 2, + user: {} + }, { + include: [Player.User] + }), Player.create({ + id: 3 + })]); }).then(players => { return Player.User.get(players).then(result => { expect(result[players[0].id].id).to.equal(players[0].user.id); @@ -900,9 +896,10 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { it('should load with an alias', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.setPersonwearinghat(hat); }).then(() => { @@ -929,9 +926,10 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { it('should load all', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); + this.Hat.create({ name: 'Baz' }) + ]); }).then(([individual, hat]) => { return individual.setPersonwearinghat(hat); }).then(() => { diff --git a/test/integration/associations/scope.test.js b/test/integration/associations/scope.test.js index 3789fc65728d..fc51d872342c 100644 --- a/test/integration/associations/scope.test.js +++ b/test/integration/associations/scope.test.js @@ -99,16 +99,12 @@ describe(Support.getTestDialectTeaser('associations'), () => { describe('1:1', () => { it('should create, find and include associations with scope values', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Post.create(), - this.Comment.create({ - title: 'I am a comment' - }), - this.Comment.create({ - title: 'I am a main comment', - isMain: true - }) - ); + return Promise.all([this.Post.create(), this.Comment.create({ + title: 'I am a comment' + }), this.Comment.create({ + title: 'I am a main comment', + isMain: true + })]); }).then(([post]) => { this.post = post; return post.createComment({ @@ -170,7 +166,7 @@ describe(Support.getTestDialectTeaser('associations'), () => { describe('1:M', () => { it('should create, find and include associations with scope values', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Post.create(), this.Image.create(), this.Question.create(), @@ -180,18 +176,14 @@ describe(Support.getTestDialectTeaser('associations'), () => { this.Comment.create({ title: 'I am a question comment' }) - ); + ]); }).then(([post, image, question, commentA, commentB]) => { this.post = post; this.image = image; this.question = question; - return Promise.join( - post.createComment({ - title: 'I am a post comment' - }), - image.addComment(commentA), - question.setComments([commentB]) - ); + return Promise.all([post.createComment({ + title: 'I am a post comment' + }), image.addComment(commentA), question.setComments([commentB])]); }).then(() => { return this.Comment.findAll(); }).then(comments => { @@ -202,11 +194,11 @@ describe(Support.getTestDialectTeaser('associations'), () => { return comment.get('commentable'); }).sort()).to.deep.equal(['image', 'post', 'question']); }).then(() => { - return Promise.join( + return Promise.all([ this.post.getComments(), this.image.getComments(), this.question.getComments() - ); + ]); }).then(([postComments, imageComments, questionComments]) => { expect(postComments.length).to.equal(1); expect(postComments[0].get('title')).to.equal('I am a post comment'); @@ -217,27 +209,19 @@ describe(Support.getTestDialectTeaser('associations'), () => { return [postComments[0], imageComments[0], questionComments[0]]; }).then(([postComment, imageComment, questionComment]) => { - return Promise.join( - postComment.getItem(), - imageComment.getItem(), - questionComment.getItem() - ); + return Promise.all([postComment.getItem(), imageComment.getItem(), questionComment.getItem()]); }).then(([post, image, question]) => { expect(post).to.be.instanceof(this.Post); expect(image).to.be.instanceof(this.Image); expect(question).to.be.instanceof(this.Question); }).then(() => { - return Promise.join( - this.Post.findOne({ - include: [this.Comment] - }), - this.Image.findOne({ - include: [this.Comment] - }), - this.Question.findOne({ - include: [this.Comment] - }) - ); + return Promise.all([this.Post.findOne({ + include: [this.Comment] + }), this.Image.findOne({ + include: [this.Comment] + }), this.Question.findOne({ + include: [this.Comment] + })]); }).then(([post, image, question]) => { expect(post.comments.length).to.equal(1); expect(post.comments[0].get('title')).to.equal('I am a post comment'); @@ -294,21 +278,16 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); it('should include associations with operator scope values', function() { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Post.create(), - this.Comment.create({ - title: 'I am a blue comment', - type: 'blue' - }), - this.Comment.create({ - title: 'I am a red comment', - type: 'red' - }), - this.Comment.create({ - title: 'I am a green comment', - type: 'green' - }) - ); + return Promise.all([this.Post.create(), this.Comment.create({ + title: 'I am a blue comment', + type: 'blue' + }), this.Comment.create({ + title: 'I am a red comment', + type: 'red' + }), this.Comment.create({ + title: 'I am a green comment', + type: 'green' + })]); }).then(([post, commentA, commentB, commentC]) => { this.post = post; return post.addComments([commentA, commentB, commentC]); @@ -344,13 +323,10 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); it('should create, find and include associations with scope values', function() { - return Promise.join( - this.Post.sync({ force: true }), - this.Tag.sync({ force: true }) - ).then(() => { + return Promise.all([this.Post.sync({ force: true }), this.Tag.sync({ force: true })]).then(() => { return this.PostTag.sync({ force: true }); }).then(() => { - return Promise.join( + return Promise.all([ this.Post.create(), this.Post.create(), this.Post.create(), @@ -358,29 +334,29 @@ describe(Support.getTestDialectTeaser('associations'), () => { this.Tag.create({ type: 'category' }), this.Tag.create({ type: 'tag' }), this.Tag.create({ type: 'tag' }) - ); + ]); }).then(([postA, postB, postC, categoryA, categoryB, tagA, tagB]) => { this.postA = postA; this.postB = postB; this.postC = postC; - return Promise.join( + return Promise.all([ postA.addCategory(categoryA), postB.setCategories([categoryB]), postC.createCategory(), postA.createTag(), postB.addTag(tagA), postC.setTags([tagB]) - ); + ]); }).then(() => { - return Promise.join( + return Promise.all([ this.postA.getCategories(), this.postA.getTags(), this.postB.getCategories(), this.postB.getTags(), this.postC.getCategories(), this.postC.getTags() - ); + ]); }).then(([postACategories, postATags, postBCategories, postBTags, postCCategories, postCTags]) => { expect(postACategories.length).to.equal(1); expect(postATags.length).to.equal(1); @@ -396,35 +372,31 @@ describe(Support.getTestDialectTeaser('associations'), () => { expect(postCCategories[0].get('type')).to.equal('category'); expect(postCTags[0].get('type')).to.equal('tag'); }).then(() => { - return Promise.join( - this.Post.findOne({ - where: { - id: this.postA.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }), - this.Post.findOne({ - where: { - id: this.postB.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }), - this.Post.findOne({ - where: { - id: this.postC.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }) - ); + return Promise.all([this.Post.findOne({ + where: { + id: this.postA.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + }), this.Post.findOne({ + where: { + id: this.postB.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + }), this.Post.findOne({ + where: { + id: this.postC.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + })]); }).then(([postA, postB, postC]) => { expect(postA.get('categories').length).to.equal(1); expect(postA.get('tags').length).to.equal(1); @@ -532,52 +504,35 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); it('should create, find and include associations with scope values', function() { - return Promise.join( + return Promise.all([ this.Post.sync({ force: true }), this.Image.sync({ force: true }), this.Question.sync({ force: true }), this.Tag.sync({ force: true }) - ).then(() => { + ]).then(() => { return this.ItemTag.sync({ force: true }); }).then(() => { - return Promise.join( + return Promise.all([ this.Post.create(), this.Image.create(), this.Question.create(), this.Tag.create({ name: 'tagA' }), this.Tag.create({ name: 'tagB' }), this.Tag.create({ name: 'tagC' }) - ); + ]); }).then(([post, image, question, tagA, tagB, tagC]) => { this.post = post; this.image = image; this.question = question; - return Promise.join( - post.setTags([tagA]).then(() => { - return Promise.join( - post.createTag({ name: 'postTag' }), - post.addTag(tagB) - ); - }), - image.setTags([tagB]).then(() => { - return Promise.join( - image.createTag({ name: 'imageTag' }), - image.addTag(tagC) - ); - }), - question.setTags([tagC]).then(() => { - return Promise.join( - question.createTag({ name: 'questionTag' }), - question.addTag(tagA) - ); - }) - ); + return Promise.all([post.setTags([tagA]).then(() => { + return Promise.all([post.createTag({ name: 'postTag' }), post.addTag(tagB)]); + }), image.setTags([tagB]).then(() => { + return Promise.all([image.createTag({ name: 'imageTag' }), image.addTag(tagC)]); + }), question.setTags([tagC]).then(() => { + return Promise.all([question.createTag({ name: 'questionTag' }), question.addTag(tagA)]); + })]); }).then(() => { - return Promise.join( - this.post.getTags(), - this.image.getTags(), - this.question.getTags() - ).then(([postTags, imageTags, questionTags]) => { + return Promise.all([this.post.getTags(), this.image.getTags(), this.question.getTags()]).then(([postTags, imageTags, questionTags]) => { expect(postTags.length).to.equal(3); expect(imageTags.length).to.equal(3); expect(questionTags.length).to.equal(3); @@ -594,20 +549,16 @@ describe(Support.getTestDialectTeaser('associations'), () => { return tag.name; }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); }).then(() => { - return Promise.join( - this.Post.findOne({ - where: {}, - include: [this.Tag] - }), - this.Image.findOne({ - where: {}, - include: [this.Tag] - }), - this.Question.findOne({ - where: {}, - include: [this.Tag] - }) - ).then(([post, image, question]) => { + return Promise.all([this.Post.findOne({ + where: {}, + include: [this.Tag] + }), this.Image.findOne({ + where: {}, + include: [this.Tag] + }), this.Question.findOne({ + where: {}, + include: [this.Tag] + })]).then(([post, image, question]) => { expect(post.tags.length).to.equal(3); expect(image.tags.length).to.equal(3); expect(question.tags.length).to.equal(3); diff --git a/test/integration/include.test.js b/test/integration/include.test.js index 007451b46f1f..3829132051c4 100755 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -168,10 +168,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }; return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Person.create(), - Company.create() - ).then(([person, company]) => { + return Promise.all([Person.create(), Company.create()]).then(([person, company]) => { return person.setEmployer(company); }); }).then(() => { @@ -214,14 +211,11 @@ describe(Support.getTestDialectTeaser('Include'), () => { return this.sequelize.sync({ force: true }).then(() => { return User.create().then(user => { - return Promise.join( - user.createTask({ - title: 'trivial' - }), - user.createTask({ - title: 'pursuit' - }) - ); + return Promise.all([user.createTask({ + title: 'trivial' + }), user.createTask({ + title: 'pursuit' + })]); }).then(() => { return User.findOne({ include: [ @@ -271,10 +265,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { user: User.create(), group: Group.create() }).then(props => { - return Promise.join( - props.task.setUser(props.user), - props.user.setGroup(props.group) - ).then(() => props); + return Promise.all([props.task.setUser(props.user), props.user.setGroup(props.group)]).then(() => props); }).then(props => { return Task.findOne({ where: { diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index 480a051c0879..d75edb06d596 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -250,28 +250,20 @@ describe(Support.getTestDialectTeaser('Include'), () => { Tag.belongsToMany(Product, { through: ProductTag }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Set.bulkCreate([ - { title: 'office' } - ]), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ).then(() => { - return Promise.join( - Set.findAll(), - Product.findAll(), - Tag.findAll() - ); + return Promise.all([Set.bulkCreate([ + { title: 'office' } + ]), Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ])]).then(() => { + return Promise.all([Set.findAll(), Product.findAll(), Tag.findAll()]); }).then(([sets, products, tags]) => { - return Promise.join( + return Promise.all([ sets[0].addProducts([products[0], products[1]]), products[0].addTag(tags[0], { priority: 1 }).then(() => { return products[0].addTag(tags[1], { priority: 2 }); @@ -283,7 +275,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }).then(() => { return products[2].addTag(tags[2], { priority: 0 }); }) - ); + ]); }).then(() => { return Set.findAll({ include: [{ @@ -453,44 +445,41 @@ describe(Support.getTestDialectTeaser('Include'), () => { G.belongsTo(H); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - A.bulkCreate([ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ]).then(() => { - return A.findAll(); - }), - (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; - - singles.forEach(model => { - promise = promise.then(() => { - return model.create({}).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); + return Promise.all([A.bulkCreate([ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ]).then(() => { + return A.findAll(); + }), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; + + singles.forEach(model => { + promise = promise.then(() => { + return model.create({}).then(instance => { + if (previousInstance) { + return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { + previousInstance = instance; + }); + } + previousInstance = b = instance; }); }); + }); - promise = promise.then(() => { - return b; - }); + promise = promise.then(() => { + return b; + }); - return promise; - })([B, C, D, E, F, G, H]) - ).then(([as, b]) => { + return promise; + })([B, C, D, E, F, G, H])]).then(([as, b]) => { return Promise.all(as.map(a => { return a.setB(b); })); @@ -545,50 +534,47 @@ describe(Support.getTestDialectTeaser('Include'), () => { G.belongsTo(H); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - A.bulkCreate([ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ]).then(() => { - return A.findAll(); - }), - (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; - - singles.forEach(model => { - const values = {}; - - if (model.name === 'g') { - values.name = 'yolo'; - } + return Promise.all([A.bulkCreate([ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ]).then(() => { + return A.findAll(); + }), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; + + singles.forEach(model => { + const values = {}; + + if (model.name === 'g') { + values.name = 'yolo'; + } - promise = promise.then(() => { - return model.create(values).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); + promise = promise.then(() => { + return model.create(values).then(instance => { + if (previousInstance) { + return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { + previousInstance = instance; + }); + } + previousInstance = b = instance; }); }); + }); - promise = promise.then(() => { - return b; - }); + promise = promise.then(() => { + return b; + }); - return promise; - })([B, C, D, E, F, G, H]) - ).then(([as, b]) => { + return promise; + })([B, C, D, E, F, G, H])]).then(([as, b]) => { return Promise.all(as.map(a => { return a.setB(b); })); @@ -665,7 +651,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { const order2 = results.orders[1]; const order3 = results.orders[2]; - return Promise.join( + return Promise.all([ user1.setItemA(item1), user1.setItemB(item2), user1.setOrder(order3), @@ -675,7 +661,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { user3.setItemA(item1), user3.setItemB(item4), user3.setOrder(order1) - ); + ]); }).then(() => { return User.findAll({ 'include': [ @@ -729,14 +715,14 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Tag.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.products[0].addTag(results.tags[0], { through: { priority: 1 } }), results.products[0].addTag(results.tags[1], { through: { priority: 2 } }), results.products[1].addTag(results.tags[1], { through: { priority: 1 } }), results.products[2].addTag(results.tags[0], { through: { priority: 3 } }), results.products[2].addTag(results.tags[1], { through: { priority: 1 } }), results.products[2].addTag(results.tags[2], { through: { priority: 2 } }) - ); + ]); }).then(() => { return Product.findAll({ include: [ @@ -809,10 +795,10 @@ describe(Support.getTestDialectTeaser('Include'), () => { return User.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[0].setGroup(results.groups[1]), results.users[1].setGroup(results.groups[0]) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -847,10 +833,10 @@ describe(Support.getTestDialectTeaser('Include'), () => { return User.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[0].setGroup(results.groups[1]), results.users[1].setGroup(results.groups[0]) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -931,13 +917,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Category.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[0].setGroup(results.groups[1]), results.users[1].setGroup(results.groups[0]), Promise.all(results.groups.map(group => { return group.setCategories(results.categories); })) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -984,13 +970,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Category.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[0].setTeam(results.groups[1]), results.users[1].setTeam(results.groups[0]), Promise.all(results.groups.map(group => { return group.setTags(results.categories); })) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -1037,13 +1023,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Category.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[0].setGroup(results.groups[1]), results.users[1].setGroup(results.groups[0]), Promise.all(results.groups.map(group => { return group.setCategories(results.categories); })) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -1083,10 +1069,10 @@ describe(Support.getTestDialectTeaser('Include'), () => { return User.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[1].setLeaderOf(results.projects[1]), results.users[0].setLeaderOf(results.projects[0]) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -1132,14 +1118,14 @@ describe(Support.getTestDialectTeaser('Include'), () => { return Tag.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.products[0].addTag(results.tags[0], { priority: 1 }), results.products[0].addTag(results.tags[1], { priority: 2 }), results.products[1].addTag(results.tags[1], { priority: 1 }), results.products[2].addTag(results.tags[0], { priority: 3 }), results.products[2].addTag(results.tags[1], { priority: 1 }), results.products[2].addTag(results.tags[2], { priority: 2 }) - ); + ]); }).then(() => { return Product.findAll({ include: [ @@ -1297,12 +1283,12 @@ describe(Support.getTestDialectTeaser('Include'), () => { return User.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[0].setGroup(results.groups[0]), results.users[1].setGroup(results.groups[0]), results.users[2].setGroup(results.groups[0]), results.users[3].setGroup(results.groups[1]) - ); + ]); }).then(() => { return User.findAll({ include: [ @@ -1639,12 +1625,12 @@ describe(Support.getTestDialectTeaser('Include'), () => { Category.belongsTo(Post); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ Post.create({ 'public': true }), Post.create({ 'public': true }), Post.create({ 'public': true }), Post.create({ 'public': true }) - ).then(posts => { + ]).then(posts => { return Promise.all(posts.slice(1, 3).map(post => { return post.createCategory({ slug: 'food' }); })); @@ -1779,18 +1765,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { Company.hasMany(User); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ User.create({ lastName: 'Albertsen' }), User.create({ lastName: 'Zenith' }), User.create({ lastName: 'Hansen' }), Company.create({ rank: 1 }), Company.create({ rank: 2 }) - ).then(([albertsen, zenith, hansen, company1, company2]) => { - return Promise.join( + ]).then(([albertsen, zenith, hansen, company1, company2]) => { + return Promise.all([ albertsen.setCompany(company1), zenith.setCompany(company2), hansen.setCompany(company2) - ); + ]); }).then(() => { return User.findAll({ include: [ diff --git a/test/integration/include/findAndCountAll.test.js b/test/integration/include/findAndCountAll.test.js index 0617dc09bdd3..18b8cb975b9b 100644 --- a/test/integration/include/findAndCountAll.test.js +++ b/test/integration/include/findAndCountAll.test.js @@ -32,28 +32,24 @@ describe(Support.getTestDialectTeaser('Include'), () => { // Sync them return this.sequelize.sync({ force: true }).then(() => { // Create an enviroment - return Promise.join( - Project.bulkCreate([ - { id: 1, name: 'No tasks' }, - { id: 2, name: 'No tasks no employees' }, - { id: 3, name: 'No employees' }, - { id: 4, name: 'In progress A' }, - { id: 5, name: 'In progress B' }, - { id: 6, name: 'In progress C' } - ]), - Task.bulkCreate([ - { name: 'Important task', fk: 3 }, - { name: 'Important task', fk: 4 }, - { name: 'Important task', fk: 5 }, - { name: 'Important task', fk: 6 } - ]), - Employee.bulkCreate([ - { name: 'Jane Doe', fk: 1 }, - { name: 'John Doe', fk: 4 }, - { name: 'Jane John Doe', fk: 5 }, - { name: 'John Jane Doe', fk: 6 } - ]) - ).then(() =>{ + return Promise.all([Project.bulkCreate([ + { id: 1, name: 'No tasks' }, + { id: 2, name: 'No tasks no employees' }, + { id: 3, name: 'No employees' }, + { id: 4, name: 'In progress A' }, + { id: 5, name: 'In progress B' }, + { id: 6, name: 'In progress C' } + ]), Task.bulkCreate([ + { name: 'Important task', fk: 3 }, + { name: 'Important task', fk: 4 }, + { name: 'Important task', fk: 5 }, + { name: 'Important task', fk: 6 } + ]), Employee.bulkCreate([ + { name: 'Jane Doe', fk: 1 }, + { name: 'John Doe', fk: 4 }, + { name: 'Jane John Doe', fk: 5 }, + { name: 'John Jane Doe', fk: 6 } + ])]).then(() =>{ //Find all projects with tasks and employees const availableProjects = 3; const limit = 2; @@ -101,55 +97,49 @@ describe(Support.getTestDialectTeaser('Include'), () => { return this.sequelize.sync({ force: true }).then(() => { // Create an enviroment - return Promise.join( - User.bulkCreate([ - { name: 'Youtube' }, - { name: 'Facebook' }, - { name: 'Google' }, - { name: 'Yahoo' }, - { name: '404' } - ]), - SomeConnection.bulkCreate([ // Lets count, m: A and u: 1 - { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted - { u: 2, m: 'A', fk: 1 }, - { u: 3, m: 'A', fk: 1 }, - { u: 4, m: 'A', fk: 1 }, - { u: 5, m: 'A', fk: 1 }, - { u: 1, m: 'B', fk: 1 }, - { u: 2, m: 'B', fk: 1 }, - { u: 3, m: 'B', fk: 1 }, - { u: 4, m: 'B', fk: 1 }, - { u: 5, m: 'B', fk: 1 }, - { u: 1, m: 'C', fk: 1 }, - { u: 2, m: 'C', fk: 1 }, - { u: 3, m: 'C', fk: 1 }, - { u: 4, m: 'C', fk: 1 }, - { u: 5, m: 'C', fk: 1 }, - { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted - { u: 4, m: 'A', fk: 2 }, - { u: 2, m: 'A', fk: 2 }, - { u: 1, m: 'A', fk: 3 }, // 3 - { u: 2, m: 'A', fk: 3 }, - { u: 3, m: 'A', fk: 3 }, - { u: 2, m: 'B', fk: 2 }, - { u: 1, m: 'A', fk: 4 }, // 4 - { u: 4, m: 'A', fk: 2 } - ]), - A.bulkCreate([ - { name: 'Just' }, - { name: 'for' }, - { name: 'testing' }, - { name: 'proposes' }, - { name: 'only' } - ]), - B.bulkCreate([ - { name: 'this should not' }, - { name: 'be loaded' } - ]), - C.bulkCreate([ - { name: 'because we only want A' } - ]) - ).then(() => { + return Promise.all([User.bulkCreate([ + { name: 'Youtube' }, + { name: 'Facebook' }, + { name: 'Google' }, + { name: 'Yahoo' }, + { name: '404' } + ]), SomeConnection.bulkCreate([ // Lets count, m: A and u: 1 + { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted + { u: 2, m: 'A', fk: 1 }, + { u: 3, m: 'A', fk: 1 }, + { u: 4, m: 'A', fk: 1 }, + { u: 5, m: 'A', fk: 1 }, + { u: 1, m: 'B', fk: 1 }, + { u: 2, m: 'B', fk: 1 }, + { u: 3, m: 'B', fk: 1 }, + { u: 4, m: 'B', fk: 1 }, + { u: 5, m: 'B', fk: 1 }, + { u: 1, m: 'C', fk: 1 }, + { u: 2, m: 'C', fk: 1 }, + { u: 3, m: 'C', fk: 1 }, + { u: 4, m: 'C', fk: 1 }, + { u: 5, m: 'C', fk: 1 }, + { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted + { u: 4, m: 'A', fk: 2 }, + { u: 2, m: 'A', fk: 2 }, + { u: 1, m: 'A', fk: 3 }, // 3 + { u: 2, m: 'A', fk: 3 }, + { u: 3, m: 'A', fk: 3 }, + { u: 2, m: 'B', fk: 2 }, + { u: 1, m: 'A', fk: 4 }, // 4 + { u: 4, m: 'A', fk: 2 } + ]), A.bulkCreate([ + { name: 'Just' }, + { name: 'for' }, + { name: 'testing' }, + { name: 'proposes' }, + { name: 'only' } + ]), B.bulkCreate([ + { name: 'this should not' }, + { name: 'be loaded' } + ]), C.bulkCreate([ + { name: 'because we only want A' } + ])]).then(() => { // Delete some of conns to prove the concept return SomeConnection.destroy({ where: { m: 'A', diff --git a/test/integration/include/findOne.test.js b/test/integration/include/findOne.test.js index 88793a37758c..4129c3a0803a 100644 --- a/test/integration/include/findOne.test.js +++ b/test/integration/include/findOne.test.js @@ -151,10 +151,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { return this.sequelize .sync({ force: true }) .then(() => { - return Promise.join( - A.create({}), - B.create({}) - ); + return Promise.all([A.create({}), B.create({})]); }) .then(([a, b]) => { return a.addB(b, { through: { name: 'Foobar' } }); @@ -293,39 +290,36 @@ describe(Support.getTestDialectTeaser('Include'), () => { G.belongsTo(H); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - A.create({}), - (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; - - singles.forEach(model => { - const values = {}; - - if (model.name === 'g') { - values.name = 'yolo'; - } + return Promise.all([A.create({}), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; - promise = promise.then(() => { - return model.create(values).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); + singles.forEach(model => { + const values = {}; + + if (model.name === 'g') { + values.name = 'yolo'; + } promise = promise.then(() => { - return b; + return model.create(values).then(instance => { + if (previousInstance) { + return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { + previousInstance = instance; + }); + } + previousInstance = b = instance; + }); }); + }); + + promise = promise.then(() => { + return b; + }); - return promise; - })([B, C, D, E, F, G, H]) - ).then(([a, b]) => { + return promise; + })([B, C, D, E, F, G, H])]).then(([a, b]) => { return a.setB(b); }).then(() => { return A.findOne({ diff --git a/test/integration/include/limit.test.js b/test/integration/include/limit.test.js index d8263ac2dfaf..7f4a1c3397f3 100644 --- a/test/integration/include/limit.test.js +++ b/test/integration/include/limit.test.js @@ -128,15 +128,15 @@ describe(Support.getTestDialectTeaser('Include'), () => { */ it('supports many-to-many association with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), this.User.bulkCreate(build('Alice', 'Bob')) - )) - .then(([projects, users]) => Promise.join( + ])) + .then(([projects, users]) => Promise.all([ projects[0].addUser(users[0]), projects[1].addUser(users[1]), projects[2].addUser(users[0]) - )) + ])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -156,17 +156,17 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports 2 levels of required many-to-many associations', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), this.User.bulkCreate(build('Alice', 'Bob')), this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([projects, users, hobbies]) => Promise.join( + ])) + .then(([projects, users, hobbies]) => Promise.all([ projects[0].addUser(users[0]), projects[1].addUser(users[1]), projects[2].addUser(users[0]), users[0].addHobby(hobbies[0]) - )) + ])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -188,18 +188,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports 2 levels of required many-to-many associations with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), this.User.bulkCreate(build('Alice', 'Bob')), this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([projects, users, hobbies]) => Promise.join( + ])) + .then(([projects, users, hobbies]) => Promise.all([ projects[0].addUser(users[0]), projects[1].addUser(users[1]), projects[2].addUser(users[0]), users[0].addHobby(hobbies[0]), users[1].addHobby(hobbies[1]) - )) + ])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -223,18 +223,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports 2 levels of required many-to-many associations with through.where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), this.User.bulkCreate(build('Alice', 'Bob')), this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([projects, users, hobbies]) => Promise.join( + ])) + .then(([projects, users, hobbies]) => Promise.all([ projects[0].addUser(users[0]), projects[1].addUser(users[1]), projects[2].addUser(users[0]), users[0].addHobby(hobbies[0]), users[1].addHobby(hobbies[1]) - )) + ])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -261,13 +261,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports 3 levels of required many-to-many associations with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Task.bulkCreate(build('alpha', 'bravo', 'charlie')), this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte')), this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([tasks, projects, users, hobbies]) => Promise.join( + ])) + .then(([tasks, projects, users, hobbies]) => Promise.all([ tasks[0].addProject(projects[0]), tasks[1].addProject(projects[1]), tasks[2].addProject(projects[2]), @@ -276,7 +276,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { projects[2].addUser(users[0]), users[0].addHobby(hobbies[0]), users[1].addHobby(hobbies[1]) - )) + ])) .then(() => this.Task.findAll({ include: [{ model: this.Project, @@ -304,14 +304,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required many-to-many association', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), this.User.bulkCreate(build('Alice', 'Bob')) - )) - .then(([projects, users]) => Promise.join( - projects[0].addUser(users[0]), // alpha - projects[2].addUser(users[0]) // charlie - )) + ])) + .then(([projects, users]) => Promise.all([// alpha + projects[0].addUser(users[0]), // charlie + projects[2].addUser(users[0])])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -329,19 +328,19 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports 2 required many-to-many association', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), this.User.bulkCreate(build('Alice', 'Bob', 'David')), this.Task.bulkCreate(build('a', 'c', 'd')) - )) - .then(([projects, users, tasks]) => Promise.join( + ])) + .then(([projects, users, tasks]) => Promise.all([ projects[0].addUser(users[0]), projects[0].addTask(tasks[0]), projects[1].addUser(users[1]), projects[2].addTask(tasks[1]), projects[3].addUser(users[2]), projects[3].addTask(tasks[2]) - )) + ])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -365,14 +364,11 @@ describe(Support.getTestDialectTeaser('Include'), () => { */ it('supports required one-to-many association', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), this.Comment.bulkCreate(build('comment0', 'comment1')) - )) - .then(([posts, comments]) => Promise.join( - posts[0].addComment(comments[0]), - posts[2].addComment(comments[1]) - )) + ])) + .then(([posts, comments]) => Promise.all([posts[0].addComment(comments[0]), posts[2].addComment(comments[1])])) .then(() => this.Post.findAll({ include: [{ model: this.Comment, @@ -390,15 +386,15 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required one-to-many association with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - )) - .then(([posts, comments]) => Promise.join( + ])) + .then(([posts, comments]) => Promise.all([ posts[0].addComment(comments[0]), posts[1].addComment(comments[1]), posts[2].addComment(comments[2]) - )) + ])) .then(() => this.Post.findAll({ include: [{ model: this.Comment, @@ -423,15 +419,15 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required one-to-many association with where clause (findOne)', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - )) - .then(([posts, comments]) => Promise.join( + ])) + .then(([posts, comments]) => Promise.all([ posts[0].addComment(comments[0]), posts[1].addComment(comments[1]), posts[2].addComment(comments[2]) - )) + ])) .then(() => this.Post.findOne({ include: [{ model: this.Comment, @@ -448,18 +444,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports 2 levels of required one-to-many associations', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), this.Post.bulkCreate(build('post0', 'post1', 'post2')), this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - )) - .then(([users, posts, comments]) => Promise.join( + ])) + .then(([users, posts, comments]) => Promise.all([ users[0].addPost(posts[0]), users[1].addPost(posts[1]), users[3].addPost(posts[2]), posts[0].addComment(comments[0]), posts[2].addComment(comments[2]) - )) + ])) .then(() => this.User.findAll({ include: [{ model: this.Post, @@ -484,19 +480,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { */ it('supports required one-to-many association with nested required many-to-many association', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), this.Post.bulkCreate(build('alpha', 'charlie', 'delta')), this.Tag.bulkCreate(build('atag', 'btag', 'dtag')) - )) - .then(([users, posts, tags]) => Promise.join( + ])) + .then(([users, posts, tags]) => Promise.all([ users[0].addPost(posts[0]), users[2].addPost(posts[1]), users[3].addPost(posts[2]), - posts[0].addTag([tags[0]]), posts[2].addTag([tags[2]]) - )) + ])) .then(() => this.User.findAll({ include: [{ model: this.Post, @@ -518,19 +513,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required many-to-many association with nested required one-to-many association', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( + .then(() => Promise.all([ this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), this.User.bulkCreate(build('Alice', 'Bob', 'David')), this.Post.bulkCreate(build('post0', 'post1', 'post2')) - )) - .then(([projects, users, posts]) => Promise.join( + ])) + .then(([projects, users, posts]) => Promise.all([ projects[0].addUser(users[0]), projects[1].addUser(users[1]), projects[3].addUser(users[2]), - users[0].addPost([posts[0]]), users[2].addPost([posts[2]]) - )) + ])) .then(() => this.Project.findAll({ include: [{ model: this.User, @@ -553,20 +547,19 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required many-to-one association with nested many-to-many association with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - + .then(() => Promise.all([ this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([posts, users, hobbies]) => Promise.join( + ])) + .then(([posts, users, hobbies]) => Promise.all([ posts[0].setUser(users[0]), posts[1].setUser(users[1]), posts[3].setUser(users[3]), users[0].addHobby(hobbies[0]), users[1].addHobby(hobbies[1]), users[3].addHobby(hobbies[0]) - )) + ])) .then(() => this.Post.findAll({ include: [{ model: this.User, @@ -590,20 +583,19 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required many-to-one association with nested many-to-many association with through.where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - + .then(() => Promise.all([ this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([posts, users, hobbies]) => Promise.join( + ])) + .then(([posts, users, hobbies]) => Promise.all([ posts[0].setUser(users[0]), posts[1].setUser(users[1]), posts[3].setUser(users[3]), users[0].addHobby(hobbies[0]), users[1].addHobby(hobbies[1]), users[3].addHobby(hobbies[0]) - )) + ])) .then(() => this.Post.findAll({ include: [{ model: this.User, @@ -630,30 +622,27 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required many-to-one association with multiple nested associations with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - + .then(() => Promise.all([ this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2', 'comment3', 'comment4', 'comment5')), this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3', 'post4')), this.User.bulkCreate(build('Alice', 'Bob')), this.Tag.bulkCreate(build('tag0', 'tag1')) - )) - .then(([comments, posts, users, tags]) => Promise.join( + ])) + .then(([comments, posts, users, tags]) => Promise.all([ comments[0].setPost(posts[0]), comments[1].setPost(posts[1]), comments[3].setPost(posts[2]), comments[4].setPost(posts[3]), comments[5].setPost(posts[4]), - posts[0].addTag(tags[0]), posts[3].addTag(tags[0]), posts[4].addTag(tags[0]), posts[1].addTag(tags[1]), - posts[0].setUser(users[0]), posts[2].setUser(users[0]), posts[4].setUser(users[0]), posts[1].setUser(users[1]) - )) + ])) .then(() => this.Comment.findAll({ include: [{ model: this.Post, @@ -682,20 +671,19 @@ describe(Support.getTestDialectTeaser('Include'), () => { it('supports required many-to-one association with nested one-to-many association with where clause', function() { return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - + .then(() => Promise.all([ this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')), this.Post.bulkCreate(build('post0', 'post1', 'post2')), this.Footnote.bulkCreate(build('footnote0', 'footnote1', 'footnote2')) - )) - .then(([comments, posts, footnotes]) => Promise.join( + ])) + .then(([comments, posts, footnotes]) => Promise.all([ comments[0].setPost(posts[0]), comments[1].setPost(posts[1]), comments[2].setPost(posts[2]), posts[0].addFootnote(footnotes[0]), posts[1].addFootnote(footnotes[1]), posts[2].addFootnote(footnotes[2]) - )) + ])) .then(() => this.Comment.findAll({ include: [{ model: this.Post, diff --git a/test/integration/include/schema.test.js b/test/integration/include/schema.test.js index ba68c0928944..7588891a9d77 100644 --- a/test/integration/include/schema.test.js +++ b/test/integration/include/schema.test.js @@ -1014,12 +1014,12 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { return User.findAll(); }) }).then(results => { - return Promise.join( + return Promise.all([ results.users[1].setGroup(results.groups[0]), results.users[2].setGroup(results.groups[0]), results.users[3].setGroup(results.groups[1]), results.users[0].setGroup(results.groups[0]) - ); + ]); }).then(() => { return User.findAll({ include: [ diff --git a/test/integration/include/separate.test.js b/test/integration/include/separate.test.js index de33d846812f..54310f817c85 100755 --- a/test/integration/include/separate.test.js +++ b/test/integration/include/separate.test.js @@ -21,26 +21,23 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - {} - ] - }, { - include: [User.Tasks] - }) - ).then(() => { + return Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {} + ] + }, { + include: [User.Tasks] + })]).then(() => { return User.findAll({ include: [ { association: User.Tasks, separate: true } @@ -193,29 +190,26 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks', foreignKey: 'userId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - {}, - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }) - ).then(() => { + return Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {}, + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + })]).then(() => { return User.findAll({ include: [ { association: User.Tasks, limit: 2 } @@ -245,34 +239,31 @@ if (current.dialect.supports.groupedLimit) { Company.Tasks = Company.hasMany(Task, { as: 'tasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - company: { - tasks: [ - {}, - {}, - {} - ] - } - }, { - include: [ - { association: User.Company, include: [Company.Tasks] } + return Promise.all([User.create({ + id: 1, + company: { + tasks: [ + {}, + {}, + {} ] - }), - User.create({ - id: 2, - company: { - tasks: [ - {} - ] - } - }, { - include: [ - { association: User.Company, include: [Company.Tasks] } + } + }, { + include: [ + { association: User.Company, include: [Company.Tasks] } + ] + }), User.create({ + id: 2, + company: { + tasks: [ + {} ] - }) - ).then(() => { + } + }, { + include: [ + { association: User.Company, include: [Company.Tasks] } + ] + })]).then(() => { return User.findAll({ include: [ { association: User.Company, include: [ @@ -306,28 +297,26 @@ if (current.dialect.supports.groupedLimit) { Task.Project = Task.belongsTo(Project, { as: 'project' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Company.create({ - id: 1, - users: [ - { - tasks: [ - { project: {} }, - { project: {} }, - { project: {} } - ] - } - ] - }, { - include: [ - { association: Company.Users, include: [ - { association: User.Tasks, include: [ - Task.Project - ] } + return Promise.all([Company.create({ + id: 1, + users: [ + { + tasks: [ + { project: {} }, + { project: {} }, + { project: {} } + ] + } + ] + }, { + include: [ + { association: Company.Users, include: [ + { association: User.Tasks, include: [ + Task.Project ] } - ] - }) - ).then(() => { + ] } + ] + })]).then(() => { return Company.findAll({ include: [ { association: Company.Users, include: [ @@ -359,47 +348,44 @@ if (current.dialect.supports.groupedLimit) { Project.Tasks = Project.hasMany(Task, { as: 'tasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - projects: [ - { - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, - { - id: 2, - tasks: [ - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } - ] - }), - User.create({ - id: 2, - projects: [ - { - id: 3, - tasks: [ - {}, - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } - ] - }) - ).then(() => { + return Promise.all([User.create({ + id: 1, + projects: [ + { + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, + { + id: 2, + tasks: [ + {} + ] + } + ] + }, { + include: [ + { association: User.Projects, include: [Project.Tasks] } + ] + }), User.create({ + id: 2, + projects: [ + { + id: 3, + tasks: [ + {}, + {} + ] + } + ] + }, { + include: [ + { association: User.Projects, include: [Project.Tasks] } + ] + })]).then(() => { return User.findAll({ include: [ { association: User.Projects, separate: true, include: [ @@ -445,29 +431,26 @@ if (current.dialect.supports.groupedLimit) { return Support.dropTestSchemas(this.sequelize).then(() => { return this.sequelize.createSchema('archive').then(() => { return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - { id: 1, title: 'b' }, - { id: 2, title: 'd' }, - { id: 3, title: 'c' }, - { id: 4, title: 'a' } - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - { id: 5, title: 'a' }, - { id: 6, title: 'c' }, - { id: 7, title: 'b' } - ] - }, { - include: [User.Tasks] - }) - ); + return Promise.all([User.create({ + id: 1, + tasks: [ + { id: 1, title: 'b' }, + { id: 2, title: 'd' }, + { id: 3, title: 'c' }, + { id: 4, title: 'a' } + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + { id: 5, title: 'a' }, + { id: 6, title: 'c' }, + { id: 7, title: 'b' } + ] + }, { + include: [User.Tasks] + })]); }).then(() => { return User.findAll({ include: [{ model: Task, limit: 2, as: 'tasks', order: [['id', 'ASC']] }], diff --git a/test/integration/model/attributes.test.js b/test/integration/model/attributes.test.js index 4255f4e86e19..95a7c1baf414 100644 --- a/test/integration/model/attributes.test.js +++ b/test/integration/model/attributes.test.js @@ -46,10 +46,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Course.belongsToMany(this.Student, { through: this.Score, foreignKey: 'CourseId' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.Student.create({ no: 1, name: 'ryan' }), this.Course.create({ no: 100, name: 'history' }) - ).then(([student, course]) => { + ]).then(([student, course]) => { return student.addCourse(course, { through: { score: 98, test_value: 1000 } }); }).then(() => { expect(callCount).to.equal(1); @@ -58,10 +58,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }) .then(() => { - return Promise.join( + return Promise.all([ this.Student.build({ no: 1 }).getCourses({ where: { no: 100 } }), this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }) - ); + ]); }) .then(([courses, score]) => { expect(score.test_value).to.equal(1001); diff --git a/test/integration/model/attributes/field.test.js b/test/integration/model/attributes/field.test.js index 8c4d5de372e7..fb32e2aba872 100644 --- a/test/integration/model/attributes/field.test.js +++ b/test/integration/model/attributes/field.test.js @@ -542,14 +542,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should work with a belongsTo association getter', function() { const userId = Math.floor(Math.random() * 100000); - return Promise.join( - this.User.create({ - id: userId - }), - this.Task.create({ - user_id: userId - }) - ).then(([user, task]) => { + return Promise.all([this.User.create({ + id: userId + }), this.Task.create({ + user_id: userId + })]).then(([user, task]) => { return Promise.all([user, task.getUser()]); }).then(([userA, userB]) => { expect(userA.get('id')).to.equal(userB.get('id')); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 4df0612f4f7e..8256d84bed29 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -432,26 +432,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.transactions) { it('works with a transaction', function() { return this.sequelize.transaction().then(transaction => { - return Promise.join( + return Promise.all([ this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), - this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), - (first, second) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1]; + this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }) + ]).then(([first, second]) => { + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1]; - // Depending on execution order and MAGIC either the first OR the second call should return true - expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR + // Depending on execution order and MAGIC either the first OR the second call should return true + expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; - expect(firstInstance.id).to.equal(secondInstance.id); + expect(firstInstance.id).to.equal(secondInstance.id); - return transaction.commit(); - } - ); + return transaction.commit(); + }); }); }); } @@ -512,87 +511,82 @@ describe(Support.getTestDialectTeaser('Model'), () => { username: 'gottlieb' }); }).then(() => { - return Promise.join( - User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - }).then(() => { - throw new Error('I should have ben rejected'); - }).catch(err => { - expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; - expect(err.fields).to.be.ok; - }), - User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - }).then(() => { - throw new Error('I should have ben rejected'); - }).catch(err => { - expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; - expect(err.fields).to.be.ok; - }) - ); + return Promise.all([User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb' + } + }).then(() => { + throw new Error('I should have ben rejected'); + }).catch(err => { + expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; + expect(err.fields).to.be.ok; + }), User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb' + } + }).then(() => { + throw new Error('I should have ben rejected'); + }).catch(err => { + expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; + expect(err.fields).to.be.ok; + })]); }); }); // Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off (dialect !== 'sqlite' ? it : it.skip)('works without a transaction', function() { - return Promise.join( + return Promise.all([ this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - (first, second) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1]; + this.User.findOrCreate({ where: { uniqueName: 'winner' } }) + ]).then(([first, second]) => { + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1]; - // Depending on execution order and MAGIC either the first OR the second call should return true - expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR + // Depending on execution order and MAGIC either the first OR the second call should return true + expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; - expect(firstInstance.id).to.equal(secondInstance.id); - } - ); + expect(firstInstance.id).to.equal(secondInstance.id); + }); }); }); }); describe('findCreateFind', () => { (dialect !== 'sqlite' ? it : it.skip)('should work with multiple concurrent calls', function() { - return Promise.join( + return Promise.all([ this.User.findOrCreate({ where: { uniqueName: 'winner' } }), this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - (first, second, third) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1], - thirdInstance = third[0], - thirdCreated = third[1]; - - expect([firstCreated, secondCreated, thirdCreated].filter(value => { - return value; - }).length).to.equal(1); - - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - expect(thirdInstance).to.be.ok; - - expect(firstInstance.id).to.equal(secondInstance.id); - expect(secondInstance.id).to.equal(thirdInstance.id); - } - ); + this.User.findOrCreate({ where: { uniqueName: 'winner' } }) + ]).then(([first, second, third]) => { + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1], + thirdInstance = third[0], + thirdCreated = third[1]; + + expect([firstCreated, secondCreated, thirdCreated].filter(value => { + return value; + }).length).to.equal(1); + + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; + expect(thirdInstance).to.be.ok; + + expect(firstInstance.id).to.equal(secondInstance.id); + expect(secondInstance.id).to.equal(thirdInstance.id); + }); }); }); @@ -656,13 +650,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { Log.removeAttribute('id'); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Log.create({ level: 'info' }), - Log.bulkCreate([ - { level: 'error' }, - { level: 'debug' } - ]) - ); + return Promise.all([Log.create({ level: 'info' }), Log.bulkCreate([ + { level: 'error' }, + { level: 'debug' } + ])]); }).then(() => { return Log.findAll(); }).then(logs => { diff --git a/test/integration/model/findAll/groupedLimit.test.js b/test/integration/model/findAll/groupedLimit.test.js index aea0c89dc4b2..ec6ce78ae8bc 100644 --- a/test/integration/model/findAll/groupedLimit.test.js +++ b/test/integration/model/findAll/groupedLimit.test.js @@ -51,22 +51,22 @@ if (current.dialect.supports['UNION ALL']) { this.User.Tasks = this.User.hasMany(this.Task); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.User.bulkCreate([{ age: -5 }, { age: 45 }, { age: 7 }, { age: -9 }, { age: 8 }, { age: 15 }, { age: -9 }]), this.Project.bulkCreate([{}, {}]), this.Task.bulkCreate([{}, {}]) - ); + ]); }) .then(() => Promise.all([this.User.findAll(), this.Project.findAll(), this.Task.findAll()])) .then(([users, projects, tasks]) => { this.projects = projects; - return Promise.join( + return Promise.all([ projects[0].setMembers(users.slice(0, 4)), projects[1].setMembers(users.slice(2)), projects[0].setParanoidMembers(users.slice(0, 4)), projects[1].setParanoidMembers(users.slice(2)), users[2].setTasks(tasks) - ); + ]); }); }); @@ -218,19 +218,19 @@ if (current.dialect.supports['UNION ALL']) { this.User.Tasks = this.User.hasMany(this.Task); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ this.User.bulkCreate([{}, {}, {}]), this.Task.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]) - ); + ]); }) .then(() => Promise.all([this.User.findAll(), this.Task.findAll()])) .then(([users, tasks]) => { this.users = users; - return Promise.join( + return Promise.all([ users[0].setTasks(tasks[0]), users[1].setTasks(tasks.slice(1, 4)), users[2].setTasks(tasks.slice(4)) - ); + ]); }); }); diff --git a/test/integration/model/json.test.js b/test/integration/model/json.test.js index e1fa8ef9cd96..df2c6a2e98ae 100644 --- a/test/integration/model/json.test.js +++ b/test/integration/model/json.test.js @@ -210,26 +210,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('find', () => { it('should be possible to query a nested value', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }), - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + })]).then(() => { return this.Event.findAll({ where: { data: { @@ -255,14 +252,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { const now = moment().milliseconds(0).toDate(); const before = moment().milliseconds(0).subtract(1, 'day').toDate(); const after = moment().milliseconds(0).add(1, 'day').toDate(); - return Promise.join( - this.Event.create({ - json: { - user: 'Homer', - lastLogin: now - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + json: { + user: 'Homer', + lastLogin: now + } + })]).then(() => { return this.Event.findAll({ where: { json: { @@ -298,14 +293,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should be possible to query a boolean with array operators', function() { - return Promise.join( - this.Event.create({ - json: { - user: 'Homer', - active: true - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + json: { + user: 'Homer', + active: true + } + })]).then(() => { return this.Event.findAll({ where: { json: { @@ -341,26 +334,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should be possible to query a nested integer value', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - age: 40 - } - }), - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - age: 37 - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + age: 40 + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + age: 37 + } + })]).then(() => { return this.Event.findAll({ where: { data: { @@ -385,26 +375,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should be possible to query a nested null value', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }), - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: null - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: null + } + })]).then(() => { return this.Event.findAll({ where: { data: { @@ -425,31 +412,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('should be possible to query for nested fields with hyphens/dashes, #8718', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - status_report: { - 'red-indicator': { - 'level$$level': true - } - }, - employment: 'Nuclear Safety Inspector' - } - }), - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: null - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + status_report: { + 'red-indicator': { + 'level$$level': true + } + }, + employment: 'Nuclear Safety Inspector' + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: null + } + })]).then(() => { return this.Event.findAll({ where: { data: { @@ -488,26 +472,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { employment: 'Nuclear Safety Inspector' } }).then(() => { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), - this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - }) - ); + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); }).then(() => { return this.Event.findAll({ where: { @@ -555,26 +536,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { employment: 'Nuclear Safety Inspector' } }).then(() => { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), - this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - }) - ); + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); }).then(() => { return this.Event.findAll({ where: { @@ -628,35 +606,31 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Elliot', - last: 'Alderson' - }, - employment: 'Hacker' - } - }), - this.Event.create({ - data: { - name: { - first: 'Christian', - last: 'Slater' - }, - employment: 'Hacker' - } - }), - this.Event.create({ - data: { - name: { - first: ' Tyrell', - last: 'Wellick' - }, - employment: 'CTO' - } - }) - ).then(() => { + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Elliot', + last: 'Alderson' + }, + employment: 'Hacker' + } + }), this.Event.create({ + data: { + name: { + first: 'Christian', + last: 'Slater' + }, + employment: 'Hacker' + } + }), this.Event.create({ + data: { + name: { + first: ' Tyrell', + last: 'Wellick' + }, + employment: 'CTO' + } + })]).then(() => { return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); }).then(() => { return this.Event.destroy(conditionSearch); @@ -753,26 +727,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { employment: 'Nuclear Safety Inspector' } }).then(() => { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), - this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - }) - ); + return Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); }).then(() => { if (current.options.dialect === 'sqlite') { return this.Event.findAll({ diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index fb6b92e137c8..236226c21884 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -487,7 +487,7 @@ if (current.dialect.supports.transactions) { }); }); }; - return Promise.join(newTransactionFunc(), newTransactionFunc()).then(() => { + return Promise.all([newTransactionFunc(), newTransactionFunc()]).then(() => { return User.findAll().then(users => { expect(users.length).to.equal(2); }); @@ -510,7 +510,7 @@ if (current.dialect.supports.transactions) { }); }); }; - return expect(Promise.join(newTransactionFunc(), newTransactionFunc())).to.be.rejectedWith('SQLITE_BUSY: database is locked'); + return expect(Promise.all([newTransactionFunc(), newTransactionFunc()])).to.be.rejectedWith('SQLITE_BUSY: database is locked'); }); }); }); @@ -570,16 +570,14 @@ if (current.dialect.supports.transactions) { }).then(() => { return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }).then(transaction => { return User.findAll( { transaction } ) - .then(() => Promise.join( + .then(() => Promise.all([// Update should not succeed before transaction has committed User.update({ username: 'joe' }, { where: { username: 'jan' } - }).then(() => expect(transactionSpy).to.have.been.called ), // Update should not succeed before transaction has committed - delay(2000) + }).then(() => expect(transactionSpy).to.have.been.called ), delay(2000) .then(() => transaction.commit()) - .then(transactionSpy) - )); + .then(transactionSpy)])); }); }); }); @@ -612,31 +610,27 @@ if (current.dialect.supports.transactions) { return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }).then(t2 => { - return Promise.join( - User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.UPDATE, - transaction: t2 - }).then(() => { - t2Spy(); - return t2.commit().then(() => { - expect(t2Spy).to.have.been.calledAfter(t1Spy); // Find should not succeed before t1 has committed - }); - }), - - t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - t1Spy(); - return delay(2000).then(() => { - return t1.commit(); - }); - }) - ); + return Promise.all([User.findOne({ + where: { + username: 'jan' + }, + lock: t2.LOCK.UPDATE, + transaction: t2 + }).then(() => { + t2Spy(); + return t2.commit().then(() => { + expect(t2Spy).to.have.been.calledAfter(t1Spy); // Find should not succeed before t1 has committed + }); + }), t1Jan.update({ + awesome: true + }, { + transaction: t1 + }).then(() => { + t1Spy(); + return delay(2000).then(() => { + return t1.commit(); + }); + })]); }); }); }); @@ -695,12 +689,12 @@ if (current.dialect.supports.transactions) { Task.belongsToMany(User, { through: 'UserTasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ User.create({ username: 'John' }), - Task.create({ title: 'Get rich', active: false }), - (john, task1) => { - return john.setTasks([task1]); - }) + Task.create({ title: 'Get rich', active: false }) + ]).then(([john, task1]) => { + return john.setTasks([task1]); + }) .then(() => { return this.sequelize.transaction(t1 => { @@ -738,13 +732,13 @@ if (current.dialect.supports.transactions) { Task.belongsToMany(User, { through: 'UserTasks' }); return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( + return Promise.all([ User.create({ username: 'John' }), Task.create({ title: 'Get rich', active: false }), - Task.create({ title: 'Die trying', active: false }), - (john, task1) => { - return john.setTasks([task1]); - }) + Task.create({ title: 'Die trying', active: false }) + ]).then(([john, task1]) => { + return john.setTasks([task1]); + }) .then(() => { return this.sequelize.transaction(t1 => { return User.findOne({ @@ -801,29 +795,26 @@ if (current.dialect.supports.transactions) { transaction: t1 }).then(t1Jan => { return this.sequelize.transaction().then(t2 => { - return Promise.join( - User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.KEY_SHARE, - transaction: t2 - }).then(() => { - t2Spy(); - return t2.commit(); - }), - t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - return delay(2000).then(() => { - t1Spy(); - expect(t1Spy).to.have.been.calledAfter(t2Spy); - return t1.commit(); - }); - }) - ); + return Promise.all([User.findOne({ + where: { + username: 'jan' + }, + lock: t2.LOCK.KEY_SHARE, + transaction: t2 + }).then(() => { + t2Spy(); + return t2.commit(); + }), t1Jan.update({ + awesome: true + }, { + transaction: t1 + }).then(() => { + return delay(2000).then(() => { + t1Spy(); + expect(t1Spy).to.have.been.calledAfter(t2Spy); + return t1.commit(); + }); + })]); }); }); }); @@ -854,38 +845,34 @@ if (current.dialect.supports.transactions) { return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }).then(t2 => { - return Promise.join( - User.findOne({ - where: { - username: 'jan' - }, - transaction: t2 - }).then(t2Jan => { - t2FindSpy(); - return t2Jan.update({ - awesome: false - }, { - transaction: t2 - }).then(() => { - t2UpdateSpy(); - return t2.commit().then(() => { - expect(t2FindSpy).to.have.been.calledBefore(t1Spy); // The find call should have returned - expect(t2UpdateSpy).to.have.been.calledAfter(t1Spy); // But the update call should not happen before the first transaction has committed - }); - }); - }), - - t1Jan.update({ - awesome: true + return Promise.all([User.findOne({ + where: { + username: 'jan' + }, + transaction: t2 + }).then(t2Jan => { + t2FindSpy(); + return t2Jan.update({ + awesome: false }, { - transaction: t1 + transaction: t2 }).then(() => { - return delay(2000).then(() => { - t1Spy(); - return t1.commit(); + t2UpdateSpy(); + return t2.commit().then(() => { + expect(t2FindSpy).to.have.been.calledBefore(t1Spy); // The find call should have returned + expect(t2UpdateSpy).to.have.been.calledAfter(t1Spy); // But the update call should not happen before the first transaction has committed }); - }) - ); + }); + }), t1Jan.update({ + awesome: true + }, { + transaction: t1 + }).then(() => { + return delay(2000).then(() => { + t1Spy(); + return t1.commit(); + }); + })]); }); }); }); diff --git a/test/unit/model/validation.test.js b/test/unit/model/validation.test.js index 37c3263ee936..4c941824806a 100644 --- a/test/unit/model/validation.test.js +++ b/test/unit/model/validation.test.js @@ -309,17 +309,13 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('should allow decimal as scientific notation', () => { - return Promise.join( - expect(User.create({ - number: '2321312301230128391820e219' - })).not.to.be.rejected, - expect(User.create({ - number: '2321312301230128391820e+219' - })).not.to.be.rejected, - expect(User.create({ - number: '2321312301230128391820f219' - })).to.be.rejected - ); + return Promise.all([expect(User.create({ + number: '2321312301230128391820e219' + })).not.to.be.rejected, expect(User.create({ + number: '2321312301230128391820e+219' + })).not.to.be.rejected, expect(User.create({ + number: '2321312301230128391820f219' + })).to.be.rejected]); }); it('should allow string as a number', () => { From 740b428669b6c31e328e5d258d742206d51cbf1b Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Tue, 21 Apr 2020 11:11:01 -0700 Subject: [PATCH 096/414] test: show additional information for queries that did not complete (#12145) --- test/integration/support.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/support.js b/test/integration/support.js index f646600b5d53..0cc31c0390be 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -21,7 +21,9 @@ afterEach(function() { if (runningQueries.size === 0) { return; } - throw new Error(`Expected 0 running queries. ${runningQueries.size} queries still running in ${this.currentTest.fullTitle()}`); + let msg = `Expected 0 running queries. ${runningQueries.size} queries still running in ${this.currentTest.fullTitle()}\n`; + msg += `Queries:\n${[...runningQueries].map(query => `${query.uuid}: ${query.sql}`).join('\n')}`; + throw new Error(msg); }); module.exports = Support; From e59b3ff23d9c768994c502ee5be60279cb46a7e5 Mon Sep 17 00:00:00 2001 From: Sam Dhondt <53523625+sam-dt@users.noreply.github.com> Date: Wed, 22 Apr 2020 09:51:38 +0200 Subject: [PATCH 097/414] types: correct Model.init return type (#12148) --- types/lib/model.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 53b9b75eb9c6..af6d3380a80d 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1591,8 +1591,9 @@ export abstract class Model extends Hooks { * An object, where each attribute is a column of the table. Each column can be either a DataType, a * string or a type-description object, with the properties described below: * @param options These options are merged with the default define options provided to the Sequelize constructor + * @return Return the initialized model */ - public static init(this: ModelCtor, attributes: ModelAttributes, options: InitOptions): void; + public static init(this: ModelCtor, attributes: ModelAttributes, options: InitOptions): Model; /** * Remove attribute from model definition From ce5bc374dac4ffb58006134010475f060643ed0d Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Wed, 22 Apr 2020 12:03:04 -0500 Subject: [PATCH 098/414] refactor(mssql/connection-manager): eliminate using/disposer calls (#12136) --- lib/dialects/mssql/async-queue.js | 46 ++++++++++ lib/dialects/mssql/connection-manager.js | 28 ++---- lib/dialects/mssql/query.js | 2 +- lib/dialects/mssql/resource-lock.js | 25 ----- package-lock.json | 20 ++-- .../dialects/mssql/query-queue.test.js | 91 +++++++++++++++++++ test/integration/pool.test.js | 17 ++-- .../unit/dialects/mssql/resource-lock.test.js | 69 -------------- 8 files changed, 165 insertions(+), 133 deletions(-) create mode 100644 lib/dialects/mssql/async-queue.js delete mode 100644 lib/dialects/mssql/resource-lock.js delete mode 100644 test/unit/dialects/mssql/resource-lock.test.js diff --git a/lib/dialects/mssql/async-queue.js b/lib/dialects/mssql/async-queue.js new file mode 100644 index 000000000000..adebefc08a8d --- /dev/null +++ b/lib/dialects/mssql/async-queue.js @@ -0,0 +1,46 @@ +'use strict'; + +const BaseError = require('../../errors/base-error'); +const ConnectionError = require('../../errors/connection-error'); + +/** + * Thrown when a connection to a database is closed while an operation is in progress + */ +class AsyncQueueError extends BaseError { + constructor(message) { + super(message); + this.name = 'SequelizeAsyncQueueError'; + } +} + +exports.AsyncQueueError = AsyncQueueError; + +class AsyncQueue { + constructor() { + this.previous = Promise.resolve(); + this.closed = false; + this.rejectCurrent = () => {}; + } + close() { + this.closed = true; + this.rejectCurrent(new ConnectionError(new AsyncQueueError('the connection was closed before this query could finish executing'))); + } + enqueue(asyncFunction) { + // This outer promise might seems superflous since down below we return asyncFunction().then(resolve, reject). + // However, this ensures that this.previous will never be a rejected promise so the queue will + // always keep going, while still communicating rejection from asyncFunction to the user. + return new Promise((resolve, reject) => { + this.previous = this.previous.then( + () => { + this.rejectCurrent = reject; + if (this.closed) { + return reject(new ConnectionError(new AsyncQueueError('the connection was closed before this query could be executed'))); + } + return asyncFunction().then(resolve, reject); + } + ); + }); + } +} + +exports.default = AsyncQueue; diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index 9960f0d9f7e6..b4e247b1d8c5 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -1,7 +1,7 @@ 'use strict'; const AbstractConnectionManager = require('../abstract/connection-manager'); -const ResourceLock = require('./resource-lock'); +const AsyncQueue = require('./async-queue').default; const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const sequelizeErrors = require('../../errors'); @@ -61,8 +61,8 @@ class ConnectionManager extends AbstractConnectionManager { return new Promise((resolve, reject) => { const connection = new this.lib.Connection(connectionConfig); + connection.queue = new AsyncQueue(); connection.lib = this.lib; - const resourceLock = new ResourceLock(connection); const connectHandler = error => { connection.removeListener('end', endHandler); @@ -71,7 +71,7 @@ class ConnectionManager extends AbstractConnectionManager { if (error) return reject(error); debug('connection acquired'); - resolve(resourceLock); + resolve(connection); }; const endHandler = () => { @@ -102,7 +102,7 @@ class ConnectionManager extends AbstractConnectionManager { switch (error.code) { case 'ESOCKET': case 'ECONNRESET': - this.pool.destroy(resourceLock); + this.pool.destroy(connection); } }); @@ -143,19 +143,14 @@ class ConnectionManager extends AbstractConnectionManager { }); } - disconnect(connectionLock) { - /** - * Abstract connection may try to disconnect raw connection used for fetching version - */ - const connection = connectionLock.unwrap - ? connectionLock.unwrap() - : connectionLock; - + disconnect(connection) { // Don't disconnect a connection that is already disconnected if (connection.closed) { return Promise.resolve(); } + connection.queue.close(); + return new Promise(resolve => { connection.on('end', resolve); connection.close(); @@ -163,14 +158,7 @@ class ConnectionManager extends AbstractConnectionManager { }); } - validate(connectionLock) { - /** - * Abstract connection may try to validate raw connection used for fetching version - */ - const connection = connectionLock.unwrap - ? connectionLock.unwrap() - : connectionLock; - + validate(connection) { return connection && connection.loggedIn; } } diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 2c5748be6ec8..59011bc83927 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -114,7 +114,7 @@ class Query extends AbstractQuery { } run(sql, parameters) { - return Promise.using(this.connection.lock(), connection => this._run(connection, sql, parameters)); + return this.connection.queue.enqueue(() => this._run(this.connection, sql, parameters)); } static formatBindParameters(sql, values, dialect) { diff --git a/lib/dialects/mssql/resource-lock.js b/lib/dialects/mssql/resource-lock.js deleted file mode 100644 index 53f4229a880f..000000000000 --- a/lib/dialects/mssql/resource-lock.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const Promise = require('../../promise'); - -class ResourceLock { - constructor(resource) { - this.resource = resource; - this.previous = Promise.resolve(resource); - } - - unwrap() { - return this.resource; - } - - lock() { - const lock = this.previous; - let resolve; - this.previous = new Promise(r => { - resolve = r; - }); - return lock.disposer(resolve); - } -} - -module.exports = ResourceLock; diff --git a/package-lock.json b/package-lock.json index fa15a126fed8..3400437500cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11928,15 +11928,6 @@ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", @@ -11944,6 +11935,17 @@ "dev": true, "requires": { "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + } } }, "p-map": { diff --git a/test/integration/dialects/mssql/query-queue.test.js b/test/integration/dialects/mssql/query-queue.test.js index a083bc076599..c58acccb01d4 100644 --- a/test/integration/dialects/mssql/query-queue.test.js +++ b/test/integration/dialects/mssql/query-queue.test.js @@ -5,6 +5,9 @@ const chai = require('chai'), Promise = require('../../../../lib/promise'), DataTypes = require('../../../../lib/data-types'), Support = require('../../support'), + Sequelize = require('../../../../lib/sequelize'), + ConnectionError = require('../../../../lib/errors/connection-error'), + { AsyncQueueError } = require('../../../../lib/dialects/mssql/async-queue'), dialect = Support.getTestDialect(); if (dialect.match(/^mssql/)) { @@ -33,5 +36,93 @@ if (dialect.match(/^mssql/)) { ]); })).not.to.be.rejected; }); + + it('requests that reject should not affect future requests', async function() { + const User = this.User; + + await expect(this.sequelize.transaction(async t => { + await expect(User.create({ + username: new Date() + })).to.be.rejected; + await expect(User.findOne({ + transaction: t + })).not.to.be.rejected; + })).not.to.be.rejected; + }); + + it('closing the connection should reject pending requests', async function() { + const User = this.User; + + let promise; + + await expect(this.sequelize.transaction(t => + promise = Promise.all([ + expect(this.sequelize.dialect.connectionManager.disconnect(t.connection)).to.be.fulfilled, + expect(User.findOne({ + transaction: t + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed') + .and.have.property('parent').that.instanceOf(AsyncQueueError), + expect(User.findOne({ + transaction: t + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed') + .and.have.property('parent').that.instanceOf(AsyncQueueError) + ]) + )).to.be.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed'); + + await expect(promise).not.to.be.rejected; + }); + + it('closing the connection should reject in-progress requests', async function() { + const User = this.User; + + let promise; + + await expect(this.sequelize.transaction(async t => { + const wrappedExecSql = t.connection.execSql; + t.connection.execSql = (...args) => { + this.sequelize.dialect.connectionManager.disconnect(t.connection); + return wrappedExecSql(...args); + }; + return promise = expect(User.findOne({ + transaction: t + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could finish executing') + .and.have.property('parent').that.instanceOf(AsyncQueueError); + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed') + .and.have.property('parent').that.instanceOf(AsyncQueueError); + + await expect(promise).not.to.be.rejected; + }); + + describe('unhandled rejections', () => { + let onUnhandledRejection; + + afterEach(() => { + process.removeListener('unhandledRejection', onUnhandledRejection); + }); + + it("unhandled rejection should occur if user doesn't catch promise returned from query", async function() { + const User = this.User; + const rejectionPromise = new Promise((resolve, reject) => { + onUnhandledRejection = reject; + }); + process.on('unhandledRejection', onUnhandledRejection); + User.create({ + username: new Date() + }); + await expect(rejectionPromise).to.be.rejectedWith( + Sequelize.ValidationError, 'string violation: username cannot be an array or an object'); + }); + + it('no unhandled rejections should occur as long as user catches promise returned from query', async function() { + const User = this.User; + const unhandledRejections = []; + onUnhandledRejection = error => unhandledRejections.push(error); + process.on('unhandledRejection', onUnhandledRejection); + await expect(User.create({ + username: new Date() + })).to.be.rejectedWith(Sequelize.ValidationError); + expect(unhandledRejections).to.deep.equal([]); + }); + }); }); } diff --git a/test/integration/pool.test.js b/test/integration/pool.test.js index 2f786cbdead7..a20416f763e1 100644 --- a/test/integration/pool.test.js +++ b/test/integration/pool.test.js @@ -20,7 +20,7 @@ function assertSameConnection(newConnection, oldConnection) { break; case 'mssql': - expect(newConnection.unwrap().dummyId).to.equal(oldConnection.unwrap().dummyId).and.to.be.ok; + expect(newConnection.dummyId).to.equal(oldConnection.dummyId).and.to.be.ok; break; default: @@ -40,8 +40,8 @@ function assertNewConnection(newConnection, oldConnection) { break; case 'mssql': - expect(newConnection.unwrap().dummyId).to.not.be.ok; - expect(oldConnection.unwrap().dummyId).to.be.ok; + expect(newConnection.dummyId).to.not.be.ok; + expect(oldConnection.dummyId).to.be.ok; break; default: @@ -49,9 +49,8 @@ function assertNewConnection(newConnection, oldConnection) { } } -function unwrapAndAttachMSSQLUniqueId(connection) { +function attachMSSQLUniqueId(connection) { if (dialect === 'mssql') { - connection = connection.unwrap(); connection.dummyId = Math.random(); } @@ -74,7 +73,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { function simulateUnexpectedError(connection) { // should never be returned again if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); + connection = attachMSSQLUniqueId(connection); } connection.emit('error', { code: 'ECONNRESET' }); } @@ -100,7 +99,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { function simulateUnexpectedError(connection) { // should never be returned again if (dialect === 'mssql') { - unwrapAndAttachMSSQLUniqueId(connection).close(); + attachMSSQLUniqueId(connection).close(); } else if (dialect === 'postgres') { connection.end(); } else { @@ -138,7 +137,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { const firstConnection = await cm.getConnection(); // TODO - Do we really need this call? - unwrapAndAttachMSSQLUniqueId(firstConnection); + attachMSSQLUniqueId(firstConnection); // returning connection back to pool await cm.releaseConnection(firstConnection); @@ -163,7 +162,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { const firstConnection = await cm.getConnection(); // TODO - Do we really need this call? - unwrapAndAttachMSSQLUniqueId(firstConnection); + attachMSSQLUniqueId(firstConnection); // returning connection back to pool await cm.releaseConnection(firstConnection); diff --git a/test/unit/dialects/mssql/resource-lock.test.js b/test/unit/dialects/mssql/resource-lock.test.js deleted file mode 100644 index 7aae17940d44..000000000000 --- a/test/unit/dialects/mssql/resource-lock.test.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -const ResourceLock = require('../../../../lib/dialects/mssql/resource-lock'), - Promise = require('../../../../lib/promise'), - assert = require('assert'), - Support = require('../../support'), - dialect = Support.getTestDialect(), - delay = require('delay'); - -if (dialect === 'mssql') { - describe('[MSSQL Specific] ResourceLock', () => { - it('should process requests serially', () => { - const expected = {}; - const lock = new ResourceLock(expected); - let last = 0; - - function validateResource(actual) { - assert.equal(actual, expected); - } - - return Promise.all([ - Promise.using(lock.lock(), resource => { - validateResource(resource); - assert.equal(last, 0); - last = 1; - - return delay(15); - }), - Promise.using(lock.lock(), resource => { - validateResource(resource); - assert.equal(last, 1); - last = 2; - }), - Promise.using(lock.lock(), resource => { - validateResource(resource); - assert.equal(last, 2); - last = 3; - - return delay(5); - }) - ]); - }); - - it('should still return resource after failure', () => { - const expected = {}; - const lock = new ResourceLock(expected); - - function validateResource(actual) { - assert.equal(actual, expected); - } - - return Promise.all([ - Promise.using(lock.lock(), resource => { - validateResource(resource); - - throw new Error('unexpected error'); - }).catch(() => {}), - Promise.using(lock.lock(), validateResource) - ]); - }); - - it('should be able to.lock resource without waiting on lock', () => { - const expected = {}; - const lock = new ResourceLock(expected); - - assert.equal(lock.unwrap(), expected); - }); - }); -} From 2dcd198bdc6b43a1c51075dcee288f366b3b6cc2 Mon Sep 17 00:00:00 2001 From: JacobLey <37151850+JacobLey@users.noreply.github.com> Date: Wed, 22 Apr 2020 13:05:02 -0400 Subject: [PATCH 099/414] fix(include): separate queries are not sub-queries (#12144) --- lib/model.js | 6 ++-- test/integration/include/separate.test.js | 43 +++++++++++++++++++++++ test/unit/model/include.test.js | 20 +++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/lib/model.js b/lib/model.js index a22a12e20eb8..bad54c4dd2c0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -547,7 +547,7 @@ class Model { include.subQuery = false; } else { include.subQueryFilter = false; - include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired; + include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired && !include.separate; } } @@ -1312,7 +1312,7 @@ class Model { if (!currentAttribute) { await this.QueryInterface.removeColumn(this.getTableName(options), columnName, options); continue; - } + } if (currentAttribute.primaryKey) continue; // Check foreign keys. If it's a foreign key, it should remove constraint first. const references = currentAttribute.references; @@ -3086,7 +3086,7 @@ class Model { } valuesUse = values; - + // Get instances and run beforeUpdate hook on each record individually let instances; let updateDoneRowByRow = false; diff --git a/test/integration/include/separate.test.js b/test/integration/include/separate.test.js index 54310f817c85..fc5150e210e3 100755 --- a/test/integration/include/separate.test.js +++ b/test/integration/include/separate.test.js @@ -477,6 +477,49 @@ if (current.dialect.supports.groupedLimit) { }); }); }); + + it('should work with required non-separate parent and required child', async function() { + const User = this.sequelize.define('User', {}); + const Task = this.sequelize.define('Task', {}); + const Company = this.sequelize.define('Company', {}); + + Task.User = Task.belongsTo(User); + User.Tasks = User.hasMany(Task); + User.Company = User.belongsTo(Company); + + await this.sequelize.sync({ force: true }); + + const task = await Task.create({ id: 1 }); + const user = await task.createUser({ id: 2 }); + await user.createCompany({ id: 3 }); + + const results = await Task.findAll({ + include: [{ + association: Task.User, + required: true, + include: [{ + association: User.Tasks, + attributes: ['UserId'], + separate: true, + include: [{ + association: Task.User, + attributes: ['id'], + required: true, + include: [{ + association: User.Company + }] + }] + }] + }] + }); + + expect(results.length).to.equal(1); + expect(results[0].id).to.equal(1); + expect(results[0].User.id).to.equal(2); + expect(results[0].User.Tasks.length).to.equal(1); + expect(results[0].User.Tasks[0].User.id).to.equal(2); + expect(results[0].User.Tasks[0].User.Company.id).to.equal(3); + }); }); }); } diff --git a/test/unit/model/include.test.js b/test/unit/model/include.test.js index ab78b6e8f550..6fc39725f795 100644 --- a/test/unit/model/include.test.js +++ b/test/unit/model/include.test.js @@ -405,6 +405,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(options.include[0].subQueryFilter).to.equal(false); }); + it('should not tag a separate hasMany association with subQuery true', function() { + const options = Sequelize.Model._validateIncludedElements({ + model: this.Company, + include: [ + { + association: this.Company.Employees, + separate: true, + include: [ + { association: this.User.Tasks, required: true } + ] + } + ], + required: true + }); + + expect(options.subQuery).to.equal(false); + expect(options.include[0].subQuery).to.equal(false); + expect(options.include[0].subQueryFilter).to.equal(false); + }); + it('should tag a hasMany association with where', function() { const options = Sequelize.Model._validateIncludedElements({ model: this.User, From 80de9f6007a4bd6cc3dde0aac8f86fce8a1dab63 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Wed, 22 Apr 2020 20:30:07 -0700 Subject: [PATCH 100/414] build: update dev dependencies (#12155) --- package-lock.json | 4195 ++++++++++++++++++--------------------------- package.json | 21 +- 2 files changed, 1711 insertions(+), 2505 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3400437500cd..c23f65127288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -462,15 +462,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -483,8 +474,7 @@ "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 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "path-exists": { "version": "4.0.0", @@ -531,15 +521,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -552,8 +533,7 @@ "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 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "path-exists": { "version": "4.0.0", @@ -578,6 +558,17 @@ "graceful-fs": "^4.1.3", "mkdirp": "^0.5.1", "rimraf": "^2.5.2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "@nodelib/fs.scandir": { @@ -615,36 +606,46 @@ "@octokit/types": "^2.0.0" } }, - "@octokit/endpoint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.0.tgz", - "integrity": "sha512-3nx+MEYoZeD0uJ+7F/gvELLvQJzLXhep2Az0bBSXagbApDvDW0LWwpnAIY/hb0Jwe17A0fJdz0O12dPh05cj7A==", + "@octokit/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-2.5.0.tgz", + "integrity": "sha512-uvzmkemQrBgD8xuGbjhxzJN1darJk9L2cS+M99cHrDG2jlSVpxNJVhoV86cXdYBqdHCc9Z995uLCczaaHIYA6Q==", "dev": true, "requires": { + "@octokit/auth-token": "^2.4.0", + "@octokit/graphql": "^4.3.1", + "@octokit/request": "^5.4.0", "@octokit/types": "^2.0.0", + "before-after-hook": "^2.1.0", + "universal-user-agent": "^5.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.1.tgz", + "integrity": "sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A==", + "dev": true, + "requires": { + "@octokit/types": "^2.11.1", "is-plain-object": "^3.0.0", "universal-user-agent": "^5.0.0" + } + }, + "@octokit/graphql": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.3.1.tgz", + "integrity": "sha512-hCdTjfvrK+ilU2keAdqNBWOk+gm1kai1ZcdjRfB30oA3/T6n53UVJb7w0L5cR3/rhU91xT3HSqCd+qbvH06yxA==", + "dev": true, + "requires": { + "@octokit/request": "^5.3.0", + "@octokit/types": "^2.0.0", + "universal-user-agent": "^4.0.0" }, "dependencies": { - "is-plain-object": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", - "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", - "dev": true, - "requires": { - "isobject": "^4.0.0" - } - }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "dev": true - }, "universal-user-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", - "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", + "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", "dev": true, "requires": { "os-name": "^3.1.0" @@ -653,12 +654,12 @@ } }, "@octokit/plugin-paginate-rest": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz", - "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.1.0.tgz", + "integrity": "sha512-7+/7urDH8cy6DmTwkewysf7/Or9dFtwZK7aQOc/IImjyeHJy+C8CEKOPo7L5Qb+66HyAr/4p/zV76LMVMuiRtA==", "dev": true, "requires": { - "@octokit/types": "^2.0.1" + "@octokit/types": "^2.9.0" } }, "@octokit/plugin-request-log": { @@ -668,72 +669,35 @@ "dev": true }, "@octokit/plugin-rest-endpoint-methods": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz", - "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.8.0.tgz", + "integrity": "sha512-LUkTgZ53adPFC/Hw6mxvAtShUtGy3zbpcfCAJMWAN7SvsStV4p6TK7TocSv0Aak4TNmDLhbShTagGhpgz9mhYw==", "dev": true, "requires": { - "@octokit/types": "^2.0.1", + "@octokit/types": "^2.12.1", "deprecation": "^2.3.1" } }, "@octokit/request": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.3.4.tgz", - "integrity": "sha512-qyj8G8BxQyXjt9Xu6NvfvOr1E0l35lsXtwm3SopsYg/JWXjlsnwqLc8rsD2OLguEL/JjLfBvrXr4az7z8Lch2A==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.2.tgz", + "integrity": "sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw==", "dev": true, "requires": { - "@octokit/endpoint": "^6.0.0", + "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.0.0", - "@octokit/types": "^2.0.0", + "@octokit/types": "^2.11.1", "deprecation": "^2.0.0", "is-plain-object": "^3.0.0", "node-fetch": "^2.3.0", "once": "^1.4.0", "universal-user-agent": "^5.0.0" - }, - "dependencies": { - "@octokit/request-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.0.tgz", - "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==", - "dev": true, - "requires": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "is-plain-object": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", - "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", - "dev": true, - "requires": { - "isobject": "^4.0.0" - } - }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "dev": true - }, - "universal-user-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", - "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", - "dev": true, - "requires": { - "os-name": "^3.1.0" - } - } } }, "@octokit/request-error": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", - "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.0.tgz", + "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==", "dev": true, "requires": { "@octokit/types": "^2.0.0", @@ -742,33 +706,21 @@ } }, "@octokit/rest": { - "version": "16.43.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz", - "integrity": "sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==", + "version": "17.5.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.5.2.tgz", + "integrity": "sha512-ceTWIkTmZMOCeFbpWyZz0vMSnSxWFm/g2BF8bqe47RkTFDsE2t9FnHV6qQKOWDTOXKe5KybQcLXyJQhFIJLweg==", "dev": true, "requires": { - "@octokit/auth-token": "^2.4.0", - "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/core": "^2.4.3", + "@octokit/plugin-paginate-rest": "^2.1.0", "@octokit/plugin-request-log": "^1.0.0", - "@octokit/plugin-rest-endpoint-methods": "2.4.0", - "@octokit/request": "^5.2.0", - "@octokit/request-error": "^1.0.2", - "atob-lite": "^2.0.0", - "before-after-hook": "^2.0.0", - "btoa-lite": "^1.0.0", - "deprecation": "^2.0.0", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.uniq": "^4.5.0", - "octokit-pagination-methods": "^1.1.0", - "once": "^1.4.0", - "universal-user-agent": "^4.0.0" + "@octokit/plugin-rest-endpoint-methods": "3.8.0" } }, "@octokit/types": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.5.1.tgz", - "integrity": "sha512-q4Wr7RexkPRrkQpXzUYF5Fj/14Mr65RyOHj6B9d/sQACpqGcStkHZj4qMEtlMY5SnD/69jlL9ItGPbDM0dR/dA==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.12.1.tgz", + "integrity": "sha512-LRLR1tjbcCfAmUElvTmMvLEzstpx6Xt/aQVTg2xvd+kHA2Ekp1eWl5t+gU7bcwjXHYEAzh4hH4WH+kS3vh+wRw==", "dev": true, "requires": { "@types/node": ">= 8" @@ -784,9 +736,9 @@ } }, "@semantic-release/commit-analyzer": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-7.0.0.tgz", - "integrity": "sha512-t5wMGByv+SknjP2m3rhWN4vmXoQ16g5VFY8iC4/tcbLPvzxK+35xsTIeUsrVFZv3ymdgAQKIr5J3lKjhF/VZZQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz", + "integrity": "sha512-5bJma/oB7B4MtwUkZC2Bf7O1MHfi4gWe4mA+MIQ3lsEV0b422Bvl1z5HRpplDnMLHH3EXMoRdEng6Ds5wUqA3A==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -795,7 +747,7 @@ "debug": "^4.0.0", "import-from": "^3.0.0", "lodash": "^4.17.4", - "micromatch": "^3.1.10" + "micromatch": "^4.0.2" }, "dependencies": { "conventional-changelog-angular": { @@ -817,21 +769,21 @@ "dev": true }, "@semantic-release/github": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-6.0.2.tgz", - "integrity": "sha512-tBE8duwyOB+FXetHucl5wCOlZhNPHN1ipQENxN6roCz22AYYRLRaVYNPjo78F+KNJpb+Gy8DdudH78Qc8VhKtQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.0.5.tgz", + "integrity": "sha512-1nJCMeomspRIXKiFO3VXtkUMbIBEreYLFNBdWoLjvlUNcEK0/pEbupEZJA3XHfJuSzv43u3OLpPhF/JBrMuv+A==", "dev": true, "requires": { - "@octokit/rest": "^16.27.0", + "@octokit/rest": "^17.0.0", "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "bottleneck": "^2.18.1", "debug": "^4.0.0", "dir-glob": "^3.0.0", - "fs-extra": "^8.0.0", - "globby": "^10.0.0", + "fs-extra": "^9.0.0", + "globby": "^11.0.0", "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", "issue-parser": "^6.0.0", "lodash": "^4.17.4", "mime": "^2.4.3", @@ -840,78 +792,61 @@ "url-join": "^4.0.0" }, "dependencies": { - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", "dev": true, "requires": { + "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" } }, - "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", "dev": true, "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" } }, - "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true } } }, "@semantic-release/npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-6.0.0.tgz", - "integrity": "sha512-aqODzbtWpVHO/keinbBMnZEaN/TkdwQvyDWcT0oNbKFpZwLjNjn+QVItoLekF62FLlXXziu2y6V4wnl9FDnujA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.5.tgz", + "integrity": "sha512-D+oEmsx9aHE1q806NFQwSC9KdBO8ri/VO99eEz0wWbX2jyLqVyWr7t0IjKC8aSnkkQswg/4KN/ZjfF6iz1XOpw==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "execa": "^4.0.0", - "fs-extra": "^8.0.0", + "fs-extra": "^9.0.0", "lodash": "^4.17.15", "nerf-dart": "^1.0.0", - "normalize-url": "^4.0.0", + "normalize-url": "^5.0.0", "npm": "^6.10.3", "rc": "^1.2.8", "read-pkg": "^5.0.0", "registry-auth-token": "^4.0.0", - "semver": "^6.3.0", - "tempy": "^0.3.0" + "semver": "^7.1.2", + "tempy": "^0.5.0" }, "dependencies": { "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -937,14 +872,15 @@ } }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", "dev": true, "requires": { + "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" } }, "get-stream": { @@ -962,6 +898,16 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -1001,12 +947,6 @@ "type-fest": "^0.6.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1028,6 +968,12 @@ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1040,9 +986,9 @@ } }, "@semantic-release/release-notes-generator": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-7.3.5.tgz", - "integrity": "sha512-LGjgPBGjjmjap/76O0Md3wc04Y7IlLnzZceLsAkcYRwGQdRPTTFUJKqDQTuieWTs7zfHzQoZqsqPfFxEN+g2+Q==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.1.tgz", + "integrity": "sha512-bOoTiH6SiiR0x2uywSNR7uZcRDl22IpZhj+Q5Bn0v+98MFtOMhCxFhbrKQjhbYoZw7vps1mvMRmFkp/g6R9cvQ==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -1095,15 +1041,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -1113,12 +1050,6 @@ "p-limit": "^2.2.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": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -1171,33 +1102,42 @@ } }, "@sinonjs/commons": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", - "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz", + "integrity": "sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==", "dev": true, "requires": { "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", "dev": true, "requires": { "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "@sinonjs/samsam": "^5.0.2" } }, "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", + "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", "dev": true, "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, "@sinonjs/text-encoding": { @@ -1207,9 +1147,9 @@ "dev": true }, "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, "@types/bluebird": { @@ -1221,14 +1161,7 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/geojson": { "version": "7946.0.7", @@ -1236,27 +1169,10 @@ "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==", "dev": true }, - "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, "@types/node": { - "version": "12.12.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", - "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==" + "version": "12.12.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.37.tgz", + "integrity": "sha512-4mXKoDptrXAwZErQHrLzpe0FN/0Wmf5JRniSVIdwUrtDf9wnmEV1teCNLBo/TwuXhkK/bVegoEn/wmb+x0AuPg==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -1461,6 +1377,24 @@ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, "append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -1516,46 +1450,22 @@ "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", "dev": true }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, "arrify": { @@ -1585,12 +1495,6 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -1618,16 +1522,10 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "atob-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", - "integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=", + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, "aws-sign2": { @@ -1807,61 +1705,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "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": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1889,6 +1732,12 @@ "integrity": "sha512-cHUzdT+mMXd1ozht8n5ZwBlNiPO/4zCqqkyp3lF1TMPsRJLXUbQ7cKnfXRkrW475H5SOtSOP0HFeihNbpa53MQ==", "dev": true }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, "bl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", @@ -1927,32 +1776,12 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "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.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" } }, "browser-stdout": { @@ -1961,12 +1790,6 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=", - "dev": true - }, "buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -1991,23 +1814,6 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "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": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -2160,6 +1966,29 @@ "parse5": "^3.0.1" } }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -2172,29 +2001,6 @@ "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", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -2343,16 +2149,6 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2390,9 +2186,9 @@ } }, "command-exists": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz", - "integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", "dev": true }, "commander": { @@ -2429,12 +2225,6 @@ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "dev": true }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2517,12 +2307,6 @@ "safe-buffer": "~5.1.1" } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "core-js": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", @@ -2639,9 +2423,9 @@ } }, "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, "css-select": { @@ -2735,8 +2519,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decamelize-keys": { "version": "1.1.0", @@ -2756,12 +2539,6 @@ } } }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -2815,51 +2592,10 @@ "object-keys": "^1.0.12" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "definitelytyped-header-parser": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.8.2.tgz", - "integrity": "sha512-kQePPP/cqQX3H6DrX5nCo2vMjJeboPsjEG8OOl43TZbTOr9zLlapWJ/oRCLnMCiyERsBRZXyLMtBXGM+1zmtgQ==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.9.0.tgz", + "integrity": "sha512-slbwZ5h5lasB12t+9EAGYr060aCMqEXp6cwD7CoTriK40HNDYU56/XQ6S4sbjBK8ReGRMnB/uDx0elKkb4kuQA==", "dev": true, "requires": { "@types/parsimmon": "^1.3.0", @@ -2999,43 +2735,111 @@ "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, "dts-critic": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-2.2.4.tgz", - "integrity": "sha512-yGHhVKo66iyPBFUYRyXX1uW+XEG3/HDP1pHCR3VlPl9ho8zRHy6lzS5k+gCSuINqjNsV8UjZSUXUuTuw0wHp7g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.0.2.tgz", + "integrity": "sha512-wkRb9FOBXNwpB14nxHbevbyGm42KhA2YGtYsIrfDMvVWDaRMQVYodYtUbuNAH7WUPFMd0ywaqnmzGJl+E6yjsg==", "dev": true, "requires": { "command-exists": "^1.2.8", "definitelytyped-header-parser": "^3.8.2", + "rimraf": "^3.0.2", "semver": "^6.2.0", + "typescript": "^3.7.5", "yargs": "^12.0.5" }, "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "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-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" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, "dtslint": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-2.0.6.tgz", - "integrity": "sha512-lvuwW8ZSJOIP5Ipm2hGGZ/t1urFwzIwaI46aPxqZ8GgMpdxbzr2iBjNwews+mJygkmR04TT8HnfCa1vcggMIMQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-3.4.2.tgz", + "integrity": "sha512-qvZN5jI849zG8cjirBzyetK2ZGRa3rO8ExhcirqDlkas251Wt8TlZFKvW8wDGQ++fHLA8omINNxWvPBCb8AXhA==", "dev": true, "requires": { - "definitelytyped-header-parser": "3.8.2", - "dts-critic": "^2.2.4", + "definitelytyped-header-parser": "3.9.0", + "dts-critic": "^3.0.2", "fs-extra": "^6.0.1", + "json-stable-stringify": "^1.0.1", "strip-json-comments": "^2.0.1", "tslint": "5.14.0", - "typescript": "^3.9.0-dev.20200404" + "typescript": "^3.9.0-dev.20200422", + "yargs": "^15.1.0" }, "dependencies": { "typescript": { - "version": "3.9.0-dev.20200404", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200404.tgz", - "integrity": "sha512-GpLX1PSRP5JPhgecvg5/8Eqw8FO/h9Yh3pGqcsrAWlc1w12zVBzAXKhpzbNmHrxqO/ftXyXnDI9ftjBcfYHYag==", + "version": "3.9.0-dev.20200422", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200422.tgz", + "integrity": "sha512-zfFmKSi+oqoPl/wYc12gGFHw5KtZ8hhMNTHrspoTPUJ9iFvienUQWyWUua0tgwsmIfR4yytLnrkzKdxs/NLwGw==", "dev": true } } @@ -3098,8 +2902,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "end-of-stream": { "version": "1.4.4", @@ -3127,9 +2930,9 @@ }, "dependencies": { "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -3918,83 +3721,12 @@ "strip-eof": "^1.0.0" } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "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": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -4006,71 +3738,6 @@ "tmp": "^0.0.33" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -4095,51 +3762,6 @@ "merge2": "^1.3.0", "micromatch": "^4.0.2", "picomatch": "^2.2.1" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } } }, "fast-json-stable-stringify": { @@ -4182,26 +3804,12 @@ } }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "to-regex-range": "^5.0.1" } }, "find-cache-dir": { @@ -4219,7 +3827,6 @@ "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" @@ -4229,25 +3836,14 @@ "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-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.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" } @@ -4255,23 +3851,12 @@ "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 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "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 - }, - "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" - } + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" } } }, @@ -4348,12 +3933,6 @@ "readable-stream": "^2.3.6" } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -4424,15 +4003,6 @@ "mime-types": "^2.1.12" } }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -4468,6 +4038,17 @@ "requires": { "minimatch": "^3.0.2", "rimraf": "^2.6.3" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "fs-minipass": { @@ -4620,12 +4201,6 @@ "pump": "^3.0.0" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -4760,12 +4335,34 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, + "globby": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", + "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, "graceful-readlink": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", @@ -4859,38 +4456,6 @@ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "hasha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", @@ -4983,21 +4548,13 @@ } }, "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { - "agent-base": "5", + "agent-base": "6", "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true - } } }, "human-signals": { @@ -5007,14 +4564,14 @@ "dev": true }, "husky": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz", - "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", + "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", "dev": true, "requires": { - "chalk": "^3.0.0", + "chalk": "^4.0.0", "ci-info": "^2.0.0", - "compare-versions": "^3.5.1", + "compare-versions": "^3.6.0", "cosmiconfig": "^6.0.0", "find-versions": "^3.2.0", "opencollective-postinstall": "^2.0.2", @@ -5035,9 +4592,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5431,32 +4988,21 @@ "is-windows": "^1.0.1" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -5469,63 +5015,18 @@ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "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", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5560,24 +5061,10 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "is-obj": { "version": "1.0.1", @@ -5601,12 +5088,12 @@ "dev": true }, "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "^4.0.0" } }, "is-promise": { @@ -5715,9 +5202,9 @@ "dev": true }, "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", "dev": true }, "isstream": { @@ -5818,15 +5305,6 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6014,6 +5492,15 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -6050,6 +5537,12 @@ "graceful-fs": "^4.1.6" } }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -6095,12 +5588,6 @@ "safe-buffer": "^5.0.1" } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", @@ -6186,17 +5673,17 @@ } }, "lint-staged": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.1.tgz", - "integrity": "sha512-wAeu/ePaBAOfwM2+cVbgPWDtn17B0Sxiv0NvNEqDAIvB8Yhvl60vafKFiK4grcYn87K1iK+a0zVoETvKbdT9/Q==", + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.7.tgz", + "integrity": "sha512-ZkK8t9Ep/AHuJQKV95izSa+DqotftGnSsNeEmCSqbQ6j4C4H0jDYhEZqVOGD1Q2Oe227igbqjMWycWyYbQtpoA==", "dev": true, "requires": { - "chalk": "^3.0.0", - "commander": "^4.0.1", + "chalk": "^4.0.0", + "commander": "^5.0.0", "cosmiconfig": "^6.0.0", "debug": "^4.1.1", "dedent": "^0.7.0", - "execa": "^3.4.0", + "execa": "^4.0.0", "listr": "^0.14.3", "log-symbols": "^3.0.0", "micromatch": "^4.0.2", @@ -6216,19 +5703,10 @@ "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6251,9 +5729,9 @@ "dev": true }, "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.0.0.tgz", + "integrity": "sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ==", "dev": true }, "cosmiconfig": { @@ -6270,9 +5748,9 @@ } }, "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -6281,9 +5759,9 @@ } }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", + "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -6293,20 +5771,10 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", @@ -6322,89 +5790,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "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.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6420,12 +5811,6 @@ "path-key": "^3.0.0" } }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "parse-json": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -6474,15 +5859,6 @@ "has-flag": "^4.0.0" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6806,12 +6182,6 @@ "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", "dev": true }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true - }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -6843,12 +6213,6 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -6856,12 +6220,12 @@ "dev": true }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "^2.4.2" } }, "log-update": { @@ -6927,12 +6291,6 @@ } } }, - "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true - }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -6999,27 +6357,12 @@ "p-defer": "^1.0.0" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, "map-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, "mariadb": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.3.1.tgz", @@ -7067,18 +6410,18 @@ } }, "markdownlint": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.18.0.tgz", - "integrity": "sha512-nQAfK9Pbq0ZRoMC/abNGterEnV3kL8MZmi0WHhw8WJKoIbsm3cXGufGsxzCRvjW15cxe74KWcxRSKqwplS26Bw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.19.0.tgz", + "integrity": "sha512-+MsWOnYVUH4klcKM7iRx5cno9FQMDAb6FC6mWlZkeXPwIaK6Z5Vd9VkXkykPidRqmLHU2wI+MNyfUMnUCBw3pQ==", "dev": true, "requires": { "markdown-it": "10.0.0" } }, "markdownlint-cli": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.21.0.tgz", - "integrity": "sha512-gvnczz3W3Wgex851/cIQ/2y8GNhY+EVK8Ael8kRd8hoSQ0ps9xjhtwPwMyJPoiYbAoPxG6vSBFISiysaAbCEZg==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.22.0.tgz", + "integrity": "sha512-qRg6tK5dXWqkaFvEstz9YSQal1ECMgofrSZgdBOaPWG8cD50pk8Hs0ZpBCJ6SCHPKF71pCdtuSL2u82sIx2XWA==", "dev": true, "requires": { "commander": "~2.9.0", @@ -7090,8 +6433,8 @@ "jsonc-parser": "~2.2.0", "lodash.differencewith": "~4.5.0", "lodash.flatten": "~4.4.0", - "markdownlint": "~0.18.0", - "markdownlint-rule-helpers": "~0.6.0", + "markdownlint": "~0.19.0", + "markdownlint-rule-helpers": "~0.7.0", "minimatch": "~3.0.4", "rc": "~1.2.7" }, @@ -7120,67 +6463,111 @@ } }, "markdownlint-rule-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.6.0.tgz", - "integrity": "sha512-LiZVAbg9/cqkBHtLNNqHV3xuy4Y2L/KuGU6+ZXqCT9NnCdEkIoxeI5/96t+ExquBY0iHy2CVWxPH16nG1RKQVQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.7.0.tgz", + "integrity": "sha512-xZByWJNBaCMHo7nYPv/5aO8Jt68YcMvyouFXhuXmJzbqCsQy8rfCj0kYcv22kdK5PwAgMdbHg0hyTdURbUZtJw==", "dev": true }, "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", "dev": true }, "marked-terminal": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz", - "integrity": "sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.0.0.tgz", + "integrity": "sha512-mzU3VD7aVz12FfGoKFAceijehA6Ocjfg3rVimvJbFAB/NOYCsuzRVtq3PSFdPmWI5mhdGeEh3/aMJ5DSxAz94Q==", "dev": true, "requires": { - "ansi-escapes": "^3.1.0", + "ansi-escapes": "^4.3.0", "cardinal": "^2.1.1", - "chalk": "^2.4.1", + "chalk": "^3.0.0", "cli-table": "^0.3.1", - "node-emoji": "^1.4.1", - "supports-hyperlinks": "^1.0.1" + "node-emoji": "^1.10.0", + "supports-hyperlinks": "^2.0.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - } - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", "read-pkg-up": "^3.0.0", "redent": "^2.0.0", "trim-newlines": "^2.0.0", @@ -7200,24 +6587,13 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "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" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "mime": { @@ -7291,27 +6667,6 @@ "minipass": "^2.9.0" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -7322,13 +6677,14 @@ } }, "mocha": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", - "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", "dev": true, "requires": { "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", + "chokidar": "3.3.0", "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", @@ -7337,11 +6693,11 @@ "growl": "1.10.5", "he": "1.2.0", "js-yaml": "3.13.1", - "log-symbols": "2.2.0", + "log-symbols": "3.0.0", "minimatch": "3.0.4", - "mkdirp": "0.5.4", + "mkdirp": "0.5.3", "ms": "2.1.1", - "node-environment-flags": "1.0.5", + "node-environment-flags": "1.0.6", "object.assign": "4.1.0", "strip-json-comments": "2.0.1", "supports-color": "6.0.0", @@ -7430,9 +6786,9 @@ } }, "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -7444,15 +6800,6 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "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", @@ -7462,12 +6809,6 @@ "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 - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -7634,25 +6975,6 @@ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "dev": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "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.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, "native-duplexpair": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", @@ -7706,27 +7028,16 @@ "dev": true }, "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz", + "integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==", "dev": true, "requires": { - "@sinonjs/formatio": "^3.2.1", + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } } }, "node-emoji": { @@ -7739,9 +7050,9 @@ } }, "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", "dev": true, "requires": { "object.getownpropertydescriptors": "^2.0.3", @@ -7780,6 +7091,15 @@ "tar": "^4" }, "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -7837,9 +7157,9 @@ } }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-5.0.0.tgz", + "integrity": "sha512-bAEm2fx8Dq/a35Z6PIRkkBBJvR56BbEJvhpNtvCZ4W9FyORSna77fn+xtYFjqk5JpBS+fMnAOG/wFgkQBmB7hw==", "dev": true }, "now-and-later": { @@ -7984,7 +7304,8 @@ "dependencies": { "JSONStream": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, "requires": { "jsonparse": "^1.2.0", @@ -7993,12 +7314,14 @@ }, "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, "agent-base": { "version": "4.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "requires": { "es6-promisify": "^5.0.0" @@ -8006,7 +7329,8 @@ }, "agentkeepalive": { "version": "3.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "dev": true, "requires": { "humanize-ms": "^1.2.1" @@ -8014,7 +7338,8 @@ }, "ajv": { "version": "5.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { "co": "^4.6.0", @@ -8025,7 +7350,8 @@ }, "ansi-align": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "dev": true, "requires": { "string-width": "^2.0.0" @@ -8033,12 +7359,14 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "3.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -8046,27 +7374,32 @@ }, "ansicolors": { "version": "0.3.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, "ansistyles": { "version": "0.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", "dev": true }, "aproba": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, "archy": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "requires": { "delegates": "^1.0.0", @@ -8075,7 +7408,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8089,7 +7423,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8099,12 +7434,14 @@ }, "asap": { "version": "2.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", "dev": true }, "asn1": { "version": "0.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { "safer-buffer": "~2.1.0" @@ -8112,32 +7449,38 @@ }, "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "asynckit": { "version": "0.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "aws-sign2": { "version": "0.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { "version": "1.8.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, "requires": { @@ -8146,7 +7489,8 @@ }, "bin-links": { "version": "1.1.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-/eaLaTu7G7/o7PV04QPy1HRT65zf+1tFkPGv0sPTV0tRwufooYBQO3zrcyGgm+ja+ZtBf2GEuKjDRJ2pPG+yqA==", "dev": true, "requires": { "bluebird": "^3.5.3", @@ -8159,12 +7503,14 @@ }, "bluebird": { "version": "3.5.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true }, "boxen": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "dev": true, "requires": { "ansi-align": "^2.0.0", @@ -8178,7 +7524,8 @@ }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -8187,27 +7534,32 @@ }, "buffer-from": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "dev": true }, "builtins": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", "dev": true }, "byline": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", "dev": true }, "byte-size": { "version": "5.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", "dev": true }, "cacache": { "version": "12.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { "bluebird": "^3.5.5", @@ -8229,27 +7581,32 @@ }, "call-limit": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==", "dev": true }, "camelcase": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, "capture-stack-trace": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", "dev": true }, "caseless": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, "chalk": { "version": "2.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -8259,17 +7616,20 @@ }, "chownr": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "ci-info": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, "cidr-regex": { "version": "2.0.10", - "bundled": true, + "resolved": false, + "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", "dev": true, "requires": { "ip-regex": "^2.1.0" @@ -8277,12 +7637,14 @@ }, "cli-boxes": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, "cli-columns": { "version": "3.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", "dev": true, "requires": { "string-width": "^2.0.0", @@ -8291,7 +7653,8 @@ }, "cli-table3": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "dev": true, "requires": { "colors": "^1.1.2", @@ -8301,7 +7664,8 @@ }, "cliui": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { "string-width": "^2.1.1", @@ -8311,12 +7675,14 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -8326,12 +7692,14 @@ }, "clone": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "cmd-shim": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -8340,17 +7708,20 @@ }, "co": { "version": "4.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "color-convert": { "version": "1.9.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { "color-name": "^1.1.1" @@ -8358,18 +7729,21 @@ }, "color-name": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { "version": "1.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "dev": true, "optional": true }, "columnify": { "version": "1.5.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "dev": true, "requires": { "strip-ansi": "^3.0.0", @@ -8378,7 +7752,8 @@ }, "combined-stream": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { "delayed-stream": "~1.0.0" @@ -8386,12 +7761,14 @@ }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "concat-stream": { "version": "1.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -8402,7 +7779,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8416,7 +7794,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8426,7 +7805,8 @@ }, "config-chain": { "version": "1.1.12", - "bundled": true, + "resolved": false, + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, "requires": { "ini": "^1.3.4", @@ -8435,7 +7815,8 @@ }, "configstore": { "version": "3.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "dev": true, "requires": { "dot-prop": "^4.1.0", @@ -8448,12 +7829,14 @@ }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "copy-concurrently": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { "aproba": "^1.1.1", @@ -8466,24 +7849,28 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "iferr": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true } } }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "create-error-class": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, "requires": { "capture-stack-trace": "^1.0.0" @@ -8491,7 +7878,8 @@ }, "cross-spawn": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -8501,7 +7889,8 @@ "dependencies": { "lru-cache": { "version": "4.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -8510,24 +7899,28 @@ }, "yallist": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true } } }, "crypto-random-string": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, "cyclist": { "version": "0.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, "dashdash": { "version": "1.14.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -8535,7 +7928,8 @@ }, "debug": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -8543,34 +7937,40 @@ "dependencies": { "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } }, "debuglog": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", "dev": true }, "decamelize": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decode-uri-component": { "version": "0.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "defaults": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { "clone": "^1.0.2" @@ -8578,7 +7978,8 @@ }, "define-properties": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { "object-keys": "^1.0.12" @@ -8586,27 +7987,32 @@ }, "delayed-stream": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, "detect-indent": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true }, "detect-newline": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, "dezalgo": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", "dev": true, "requires": { "asap": "^2.0.0", @@ -8615,7 +8021,8 @@ }, "dot-prop": { "version": "4.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { "is-obj": "^1.0.0" @@ -8623,17 +8030,20 @@ }, "dotenv": { "version": "5.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", "dev": true }, "duplexer3": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, "duplexify": { "version": "3.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "dev": true, "requires": { "end-of-stream": "^1.0.0", @@ -8644,7 +8054,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8658,7 +8069,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8668,7 +8080,8 @@ }, "ecc-jsbn": { "version": "0.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "optional": true, "requires": { @@ -8678,12 +8091,14 @@ }, "editor": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", "dev": true }, "encoding": { "version": "0.1.12", - "bundled": true, + "resolved": false, + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "dev": true, "requires": { "iconv-lite": "~0.4.13" @@ -8691,7 +8106,8 @@ }, "end-of-stream": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -8699,17 +8115,20 @@ }, "env-paths": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", "dev": true }, "err-code": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", "dev": true }, "errno": { "version": "0.1.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { "prr": "~1.0.1" @@ -8717,7 +8136,8 @@ }, "es-abstract": { "version": "1.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { "es-to-primitive": "^1.1.1", @@ -8729,7 +8149,8 @@ }, "es-to-primitive": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -8739,12 +8160,14 @@ }, "es6-promise": { "version": "4.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, "es6-promisify": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { "es6-promise": "^4.0.3" @@ -8752,12 +8175,14 @@ }, "escape-string-regexp": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "execa": { "version": "0.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { "cross-spawn": "^5.0.1", @@ -8771,44 +8196,52 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } } }, "extend": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extsprintf": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, "fast-deep-equal": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, "figgy-pudding": { "version": "3.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, "find-npm-prefix": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==", "dev": true }, "find-up": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" @@ -8816,7 +8249,8 @@ }, "flush-write-stream": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -8825,7 +8259,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8839,7 +8274,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8849,12 +8285,14 @@ }, "forever-agent": { "version": "0.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, "form-data": { "version": "2.3.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -8864,7 +8302,8 @@ }, "from2": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -8873,7 +8312,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8887,7 +8327,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8897,7 +8338,8 @@ }, "fs-minipass": { "version": "1.2.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, "requires": { "minipass": "^2.6.0" @@ -8905,7 +8347,8 @@ "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -8916,7 +8359,8 @@ }, "fs-vacuum": { "version": "1.2.10", - "bundled": true, + "resolved": false, + "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -8926,7 +8370,8 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "bundled": true, + "resolved": false, + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -8937,12 +8382,14 @@ "dependencies": { "iferr": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8956,7 +8403,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8966,17 +8414,20 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "function-bind": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { "aproba": "^1.0.3", @@ -8991,12 +8442,14 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -9008,12 +8461,14 @@ }, "genfun": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", "dev": true }, "gentle-fs": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-3k2CgAmPxuz7S6nKK+AqFE2AdM1QuwqKLPKzIET3VRwK++3q96MsNFobScDjlCrq97ZJ8y5R725MOlm6ffUCjg==", "dev": true, "requires": { "aproba": "^1.1.2", @@ -9031,24 +8486,28 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "iferr": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true } } }, "get-caller-file": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-stream": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { "pump": "^3.0.0" @@ -9056,7 +8515,8 @@ }, "getpass": { "version": "0.1.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -9064,7 +8524,8 @@ }, "glob": { "version": "7.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -9077,7 +8538,8 @@ }, "global-dirs": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { "ini": "^1.3.4" @@ -9085,7 +8547,8 @@ }, "got": { "version": "6.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { "create-error-class": "^3.0.0", @@ -9103,24 +8566,28 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } } }, "graceful-fs": { "version": "4.2.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, "har-schema": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, "har-validator": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "dev": true, "requires": { "ajv": "^5.3.0", @@ -9129,7 +8596,8 @@ }, "has": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "^1.1.1" @@ -9137,32 +8605,38 @@ }, "has-flag": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true }, "hosted-git-info": { "version": "2.8.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "http-cache-semantics": { "version": "3.8.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true }, "http-proxy-agent": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, "requires": { "agent-base": "4", @@ -9171,7 +8645,8 @@ }, "http-signature": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -9181,7 +8656,8 @@ }, "https-proxy-agent": { "version": "2.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { "agent-base": "^4.3.0", @@ -9190,7 +8666,8 @@ }, "humanize-ms": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "dev": true, "requires": { "ms": "^2.0.0" @@ -9198,7 +8675,8 @@ }, "iconv-lite": { "version": "0.4.23", - "bundled": true, + "resolved": false, + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -9206,12 +8684,14 @@ }, "iferr": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==", "dev": true }, "ignore-walk": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "requires": { "minimatch": "^3.0.4" @@ -9219,22 +8699,26 @@ }, "import-lazy": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", "dev": true }, "imurmurhash": { "version": "0.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "infer-owner": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -9243,17 +8727,20 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "init-package-json": { "version": "1.10.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", "dev": true, "requires": { "glob": "^7.1.1", @@ -9268,27 +8755,32 @@ }, "invert-kv": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ip": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, "ip-regex": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, "is-callable": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-ci": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { "ci-info": "^1.5.0" @@ -9296,14 +8788,16 @@ "dependencies": { "ci-info": { "version": "1.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true } } }, "is-cidr": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", "dev": true, "requires": { "cidr-regex": "^2.0.10" @@ -9311,12 +8805,14 @@ }, "is-date-object": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -9324,7 +8820,8 @@ }, "is-installed-globally": { "version": "0.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "dev": true, "requires": { "global-dirs": "^0.1.0", @@ -9333,17 +8830,20 @@ }, "is-npm": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", "dev": true }, "is-obj": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, "is-path-inside": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { "path-is-inside": "^1.0.1" @@ -9351,12 +8851,14 @@ }, "is-redirect": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, "is-regex": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { "has": "^1.0.1" @@ -9364,17 +8866,20 @@ }, "is-retry-allowed": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", "dev": true }, "is-stream": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "is-symbol": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "dev": true, "requires": { "has-symbols": "^1.0.0" @@ -9382,58 +8887,69 @@ }, "is-typedarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isstream": { "version": "0.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, "jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true, "optional": true }, "json-parse-better-errors": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema": { "version": "0.2.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, "json-schema-traverse": { "version": "0.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, "json-stringify-safe": { "version": "5.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "jsonparse": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, "jsprim": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { "assert-plus": "1.0.0", @@ -9444,7 +8960,8 @@ }, "latest-version": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "dev": true, "requires": { "package-json": "^4.0.0" @@ -9452,12 +8969,14 @@ }, "lazy-property": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", "dev": true }, "lcid": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { "invert-kv": "^2.0.0" @@ -9465,7 +8984,8 @@ }, "libcipm": { "version": "4.0.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-fTq33otU3PNXxxCTCYCYe7V96o59v/o7bvtspmbORXpgFk+wcWrGf5x6tBgui5gCed/45/wtPomBsZBYm5KbIw==", "dev": true, "requires": { "bin-links": "^1.1.2", @@ -9487,7 +9007,8 @@ }, "libnpm": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", "dev": true, "requires": { "bin-links": "^1.1.2", @@ -9514,7 +9035,8 @@ }, "libnpmaccess": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", "dev": true, "requires": { "aproba": "^2.0.0", @@ -9525,7 +9047,8 @@ }, "libnpmconfig": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -9535,7 +9058,8 @@ "dependencies": { "find-up": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { "locate-path": "^3.0.0" @@ -9543,7 +9067,8 @@ }, "locate-path": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { "p-locate": "^3.0.0", @@ -9552,7 +9077,8 @@ }, "p-limit": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -9560,7 +9086,8 @@ }, "p-locate": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { "p-limit": "^2.0.0" @@ -9568,14 +9095,16 @@ }, "p-try": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true } } }, "libnpmhook": { "version": "5.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", "dev": true, "requires": { "aproba": "^2.0.0", @@ -9586,7 +9115,8 @@ }, "libnpmorg": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", "dev": true, "requires": { "aproba": "^2.0.0", @@ -9597,7 +9127,8 @@ }, "libnpmpublish": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", "dev": true, "requires": { "aproba": "^2.0.0", @@ -9613,7 +9144,8 @@ }, "libnpmsearch": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -9623,7 +9155,8 @@ }, "libnpmteam": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", "dev": true, "requires": { "aproba": "^2.0.0", @@ -9634,7 +9167,8 @@ }, "libnpx": { "version": "10.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ujaYToga1SAX5r7FU5ShMFi88CWpY75meNZtr6RtEyv4l2ZK3+Wgvxq2IqlwWBiDZOqhumdeiocPS1aKrCMe3A==", "dev": true, "requires": { "dotenv": "^5.0.1", @@ -9649,7 +9183,8 @@ }, "locate-path": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", @@ -9658,7 +9193,8 @@ }, "lock-verify": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", "dev": true, "requires": { "npm-package-arg": "^6.1.0", @@ -9667,7 +9203,8 @@ }, "lockfile": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", "dev": true, "requires": { "signal-exit": "^3.0.2" @@ -9675,12 +9212,14 @@ }, "lodash._baseindexof": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", "dev": true }, "lodash._baseuniq": { "version": "4.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", "dev": true, "requires": { "lodash._createset": "~4.0.0", @@ -9689,17 +9228,20 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", "dev": true }, "lodash._cacheindexof": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", "dev": true }, "lodash._createcache": { "version": "3.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", "dev": true, "requires": { "lodash._getnative": "^3.0.0" @@ -9707,52 +9249,62 @@ }, "lodash._createset": { "version": "4.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", "dev": true }, "lodash._getnative": { "version": "3.9.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", "dev": true }, "lodash._root": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, "lodash.clonedeep": { "version": "4.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, "lodash.restparam": { "version": "3.6.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", "dev": true }, "lodash.union": { "version": "4.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", "dev": true }, "lodash.uniq": { "version": "4.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, "lodash.without": { "version": "4.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", "dev": true }, "lowercase-keys": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, "lru-cache": { "version": "5.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { "yallist": "^3.0.2" @@ -9760,7 +9312,8 @@ }, "make-dir": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { "pify": "^3.0.0" @@ -9768,7 +9321,8 @@ }, "make-fetch-happen": { "version": "5.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "dev": true, "requires": { "agentkeepalive": "^3.4.1", @@ -9786,7 +9340,8 @@ }, "map-age-cleaner": { "version": "0.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { "p-defer": "^1.0.0" @@ -9794,12 +9349,14 @@ }, "meant": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==", "dev": true }, "mem": { "version": "4.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", @@ -9809,19 +9366,22 @@ "dependencies": { "mimic-fn": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true } } }, "mime-db": { "version": "1.35.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", "dev": true }, "mime-types": { "version": "2.1.19", - "bundled": true, + "resolved": false, + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { "mime-db": "~1.35.0" @@ -9829,7 +9389,8 @@ }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -9837,7 +9398,8 @@ }, "minizlib": { "version": "1.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "dev": true, "requires": { "minipass": "^2.9.0" @@ -9845,7 +9407,8 @@ "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -9856,7 +9419,8 @@ }, "mississippi": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "dev": true, "requires": { "concat-stream": "^1.5.0", @@ -9873,7 +9437,8 @@ }, "mkdirp": { "version": "0.5.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -9881,14 +9446,16 @@ "dependencies": { "minimist": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } }, "move-concurrently": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { "aproba": "^1.1.1", @@ -9901,29 +9468,34 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true } } }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "mute-stream": { "version": "0.0.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, "nice-try": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "node-fetch-npm": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", "dev": true, "requires": { "encoding": "^0.1.11", @@ -9933,7 +9505,8 @@ }, "node-gyp": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", "dev": true, "requires": { "env-paths": "^2.2.0", @@ -9951,7 +9524,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "requires": { "abbrev": "1", @@ -9960,7 +9534,8 @@ }, "normalize-package-data": { "version": "2.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -9971,7 +9546,8 @@ "dependencies": { "resolve": { "version": "1.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -9981,7 +9557,8 @@ }, "npm-audit-report": { "version": "1.3.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-abeqS5ONyXNaZJPGAf6TOUMNdSe1Y6cpc9MLBRn+CuUoYbfdca6AxOyXVlfIv9OgKX+cacblbG5w7A6ccwoTPw==", "dev": true, "requires": { "cli-table3": "^0.5.0", @@ -9990,7 +9567,8 @@ }, "npm-bundled": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "dev": true, "requires": { "npm-normalize-package-bin": "^1.0.1" @@ -9998,12 +9576,14 @@ }, "npm-cache-filename": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", "dev": true }, "npm-install-checks": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", "dev": true, "requires": { "semver": "^2.3.0 || 3.x || 4 || 5" @@ -10011,7 +9591,8 @@ }, "npm-lifecycle": { "version": "3.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A==", "dev": true, "requires": { "byline": "^5.0.0", @@ -10026,17 +9607,20 @@ }, "npm-logical-tree": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==", "dev": true }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, "npm-package-arg": { "version": "6.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", "dev": true, "requires": { "hosted-git-info": "^2.7.1", @@ -10047,7 +9631,8 @@ }, "npm-packlist": { "version": "1.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { "ignore-walk": "^3.0.1", @@ -10057,7 +9642,8 @@ }, "npm-pick-manifest": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -10067,7 +9653,8 @@ }, "npm-profile": { "version": "4.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", "dev": true, "requires": { "aproba": "^1.1.2 || 2", @@ -10077,7 +9664,8 @@ }, "npm-registry-fetch": { "version": "4.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -10091,14 +9679,16 @@ "dependencies": { "safe-buffer": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", "dev": true } } }, "npm-run-path": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { "path-key": "^2.0.0" @@ -10106,12 +9696,14 @@ }, "npm-user-validate": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=", "dev": true }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -10122,27 +9714,32 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "oauth-sign": { "version": "0.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object-keys": { "version": "1.0.12", - "bundled": true, + "resolved": false, + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, "object.getownpropertydescriptors": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -10151,7 +9748,8 @@ }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -10159,17 +9757,20 @@ }, "opener": { "version": "1.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", "dev": true }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { "execa": "^1.0.0", @@ -10179,7 +9780,8 @@ "dependencies": { "cross-spawn": { "version": "6.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -10191,7 +9793,8 @@ }, "execa": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { "cross-spawn": "^6.0.0", @@ -10207,12 +9810,14 @@ }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { "os-homedir": "^1.0.0", @@ -10221,22 +9826,26 @@ }, "p-defer": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-finally": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-is-promise": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, "p-limit": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", "dev": true, "requires": { "p-try": "^1.0.0" @@ -10244,7 +9853,8 @@ }, "p-locate": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" @@ -10252,12 +9862,14 @@ }, "p-try": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "package-json": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "dev": true, "requires": { "got": "^6.7.1", @@ -10268,7 +9880,8 @@ }, "pacote": { "version": "9.5.12", - "bundled": true, + "resolved": false, + "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "dev": true, "requires": { "bluebird": "^3.5.3", @@ -10305,7 +9918,8 @@ "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -10316,7 +9930,8 @@ }, "parallel-transform": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "dev": true, "requires": { "cyclist": "~0.2.2", @@ -10326,7 +9941,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -10340,7 +9956,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -10350,57 +9967,68 @@ }, "path-exists": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-is-inside": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, "path-key": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "path-parse": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "performance-now": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, "pify": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "prepend-http": { "version": "1.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, "promise-inflight": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, "promise-retry": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", "dev": true, "requires": { "err-code": "^1.0.0", @@ -10409,14 +10037,16 @@ "dependencies": { "retry": { "version": "0.10.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", "dev": true } } }, "promzard": { "version": "0.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", "dev": true, "requires": { "read": "1" @@ -10424,12 +10054,14 @@ }, "proto-list": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "dev": true }, "protoduck": { "version": "5.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", "dev": true, "requires": { "genfun": "^5.0.0" @@ -10437,22 +10069,26 @@ }, "prr": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, "psl": { "version": "1.1.29", - "bundled": true, + "resolved": false, + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", "dev": true }, "pump": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -10461,7 +10097,8 @@ }, "pumpify": { "version": "1.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { "duplexify": "^3.6.0", @@ -10471,7 +10108,8 @@ "dependencies": { "pump": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -10482,22 +10120,26 @@ }, "punycode": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, "qrcode-terminal": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "dev": true }, "qs": { "version": "6.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "query-string": { "version": "6.8.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", "dev": true, "requires": { "decode-uri-component": "^0.2.0", @@ -10507,12 +10149,14 @@ }, "qw": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=", "dev": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { "deep-extend": "^0.6.0", @@ -10523,14 +10167,16 @@ "dependencies": { "minimist": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } }, "read": { "version": "1.0.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "dev": true, "requires": { "mute-stream": "~0.0.4" @@ -10538,7 +10184,8 @@ }, "read-cmd-shim": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", "dev": true, "requires": { "graceful-fs": "^4.1.2" @@ -10546,7 +10193,8 @@ }, "read-installed": { "version": "4.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", "dev": true, "requires": { "debuglog": "^1.0.1", @@ -10560,7 +10208,8 @@ }, "read-package-json": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", "dev": true, "requires": { "glob": "^7.1.1", @@ -10572,7 +10221,8 @@ }, "read-package-tree": { "version": "5.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", "dev": true, "requires": { "read-package-json": "^2.0.0", @@ -10582,7 +10232,8 @@ }, "readable-stream": { "version": "3.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -10592,7 +10243,8 @@ }, "readdir-scoped-modules": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "dev": true, "requires": { "debuglog": "^1.0.1", @@ -10603,7 +10255,8 @@ }, "registry-auth-token": { "version": "3.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { "rc": "^1.1.6", @@ -10612,7 +10265,8 @@ }, "registry-url": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "dev": true, "requires": { "rc": "^1.0.1" @@ -10620,7 +10274,8 @@ }, "request": { "version": "2.88.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -10647,27 +10302,32 @@ }, "require-directory": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, "resolve-from": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "retry": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, "rimraf": { "version": "2.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { "glob": "^7.1.3" @@ -10675,7 +10335,8 @@ }, "run-queue": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { "aproba": "^1.1.1" @@ -10683,29 +10344,34 @@ "dependencies": { "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true } } }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "semver": { "version": "5.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "semver-diff": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "dev": true, "requires": { "semver": "^5.0.3" @@ -10713,12 +10379,14 @@ }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "sha": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", "dev": true, "requires": { "graceful-fs": "^4.1.2" @@ -10726,7 +10394,8 @@ }, "shebang-command": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -10734,27 +10403,32 @@ }, "shebang-regex": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "slide": { "version": "1.1.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", "dev": true }, "smart-buffer": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", "dev": true }, "socks": { "version": "2.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", "dev": true, "requires": { "ip": "1.1.5", @@ -10763,7 +10437,8 @@ }, "socks-proxy-agent": { "version": "4.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "dev": true, "requires": { "agent-base": "~4.2.1", @@ -10772,7 +10447,8 @@ "dependencies": { "agent-base": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "dev": true, "requires": { "es6-promisify": "^5.0.0" @@ -10782,12 +10458,14 @@ }, "sorted-object": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", "dev": true }, "sorted-union-stream": { "version": "2.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", "dev": true, "requires": { "from2": "^1.3.0", @@ -10796,7 +10474,8 @@ "dependencies": { "from2": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", "dev": true, "requires": { "inherits": "~2.0.1", @@ -10805,12 +10484,14 @@ }, "isarray": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, "readable-stream": { "version": "1.1.14", - "bundled": true, + "resolved": false, + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -10821,14 +10502,16 @@ }, "string_decoder": { "version": "0.10.31", - "bundled": true, + "resolved": false, + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } } }, "spdx-correct": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -10837,12 +10520,14 @@ }, "spdx-exceptions": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, "spdx-expression-parse": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -10851,17 +10536,20 @@ }, "spdx-license-ids": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, "split-on-first": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "dev": true }, "sshpk": { "version": "1.14.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "dev": true, "requires": { "asn1": "~0.2.3", @@ -10877,7 +10565,8 @@ }, "ssri": { "version": "6.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -10885,7 +10574,8 @@ }, "stream-each": { "version": "1.2.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -10894,7 +10584,8 @@ }, "stream-iterate": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", "dev": true, "requires": { "readable-stream": "^2.1.5", @@ -10903,7 +10594,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -10917,7 +10609,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -10927,17 +10620,20 @@ }, "stream-shift": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, "strict-uri-encode": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", "dev": true }, "string-width": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -10946,17 +10642,20 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -10966,7 +10665,8 @@ }, "string_decoder": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { "safe-buffer": "~5.2.0" @@ -10974,19 +10674,22 @@ "dependencies": { "safe-buffer": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", "dev": true } } }, "stringify-package": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", "dev": true }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -10994,17 +10697,20 @@ }, "strip-eof": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "supports-color": { "version": "5.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11012,7 +10718,8 @@ }, "tar": { "version": "4.4.13", - "bundled": true, + "resolved": false, + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "dev": true, "requires": { "chownr": "^1.1.1", @@ -11026,7 +10733,8 @@ "dependencies": { "minipass": { "version": "2.9.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -11037,7 +10745,8 @@ }, "term-size": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "dev": true, "requires": { "execa": "^0.7.0" @@ -11045,17 +10754,20 @@ }, "text-table": { "version": "0.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "through": { "version": "2.3.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, "through2": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { "readable-stream": "^2.1.5", @@ -11064,7 +10776,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -11078,7 +10791,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -11088,17 +10802,20 @@ }, "timed-out": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, "tiny-relative-date": { "version": "1.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", "dev": true }, "tough-cookie": { "version": "2.4.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "requires": { "psl": "^1.1.24", @@ -11107,7 +10824,8 @@ }, "tunnel-agent": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -11115,28 +10833,33 @@ }, "tweetnacl": { "version": "0.14.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true, "optional": true }, "typedarray": { "version": "0.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, "uid-number": { "version": "0.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", "dev": true }, "umask": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", "dev": true }, "unique-filename": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "dev": true, "requires": { "unique-slug": "^2.0.0" @@ -11144,7 +10867,8 @@ }, "unique-slug": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "dev": true, "requires": { "imurmurhash": "^0.1.4" @@ -11152,7 +10876,8 @@ }, "unique-string": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { "crypto-random-string": "^1.0.0" @@ -11160,17 +10885,20 @@ }, "unpipe": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, "unzip-response": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", "dev": true }, "update-notifier": { "version": "2.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "dev": true, "requires": { "boxen": "^1.2.1", @@ -11187,7 +10915,8 @@ }, "url-parse-lax": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { "prepend-http": "^1.0.1" @@ -11195,17 +10924,20 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "util-extend": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", "dev": true }, "util-promisify": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", "dev": true, "requires": { "object.getownpropertydescriptors": "^2.0.3" @@ -11213,12 +10945,14 @@ }, "uuid": { "version": "3.3.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", "dev": true }, "validate-npm-package-license": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -11227,7 +10961,8 @@ }, "validate-npm-package-name": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", "dev": true, "requires": { "builtins": "^1.0.3" @@ -11235,7 +10970,8 @@ }, "verror": { "version": "1.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -11245,7 +10981,8 @@ }, "wcwidth": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, "requires": { "defaults": "^1.0.3" @@ -11253,7 +10990,8 @@ }, "which": { "version": "1.3.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -11261,12 +10999,14 @@ }, "which-module": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "requires": { "string-width": "^1.0.2" @@ -11274,7 +11014,8 @@ "dependencies": { "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -11286,7 +11027,8 @@ }, "widest-line": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { "string-width": "^2.1.1" @@ -11294,7 +11036,8 @@ }, "worker-farm": { "version": "1.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "dev": true, "requires": { "errno": "~0.1.7" @@ -11302,7 +11045,8 @@ }, "wrap-ansi": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { "string-width": "^1.0.1", @@ -11311,7 +11055,8 @@ "dependencies": { "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -11323,12 +11068,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "write-file-atomic": { "version": "2.4.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -11338,27 +11085,32 @@ }, "xdg-basedir": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, "xtend": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true }, "y18n": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true }, "yargs": { "version": "11.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", "dev": true, "requires": { "cliui": "^4.0.0", @@ -11377,14 +11129,16 @@ "dependencies": { "y18n": { "version": "3.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true } } }, "yargs-parser": { "version": "9.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { "camelcase": "^4.1.0" @@ -11498,14 +11252,12 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -11514,14 +11266,12 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -11532,7 +11282,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -11540,8 +11289,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "find-up": { "version": "4.1.0", @@ -11556,14 +11304,12 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "locate-path": { "version": "5.0.0", @@ -11574,15 +11320,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -11604,8 +11341,7 @@ "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 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "path-exists": { "version": "4.0.0", @@ -11616,23 +11352,12 @@ "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -11643,7 +11368,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -11652,37 +11376,16 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, "yargs-parser": { "version": "18.1.2", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -11702,37 +11405,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "object-inspect": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", @@ -11745,15 +11417,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", @@ -11782,21 +11445,6 @@ "es-abstract": "^1.17.0-next.1" } }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "octokit-pagination-methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", - "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", - "dev": true - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -11928,6 +11576,21 @@ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + }, + "dependencies": { + "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==" + } + } + }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", @@ -12060,12 +11723,6 @@ "integrity": "sha512-5UIrOCW+gjbILkjKPgTgmq8LKf8TT3Iy7kN2VD7OtQ81facKn8B4gG1X94jWqXYZsxG2KbJhrv/Yq/5H6BQn7A==", "dev": true }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -12263,15 +11920,6 @@ "p-locate": "^4.1.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -12281,12 +11929,6 @@ "p-limit": "^2.2.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 - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -12304,12 +11946,6 @@ "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", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -12495,6 +12131,15 @@ "util-deprecate": "~1.0.1" } }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, "redent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", @@ -12520,16 +12165,6 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", @@ -12599,18 +12234,6 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", @@ -12699,12 +12322,6 @@ "value-or-function": "^3.0.0" } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -12715,12 +12332,6 @@ "signal-exit": "^3.0.2" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -12742,9 +12353,9 @@ "dev": true }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -12780,15 +12391,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -12802,16 +12404,16 @@ "dev": true }, "semantic-release": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-16.0.4.tgz", - "integrity": "sha512-qiYHTNStxUs0UUb45ImRIid0Z8HsXwMNbpZXLvABs725SrxtZBgfuemaABnHdKDg7KBsuQMlSdZENaYLvkMqUg==", + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.0.7.tgz", + "integrity": "sha512-F6FzJI1yiGavzCTXir4yPthK/iozZPJ4myUYndiHhSHbmOcCSJ2m7V+P6sFwVpDpQKQp1Q31M68sTJ/Q/27Bow==", "dev": true, "requires": { - "@semantic-release/commit-analyzer": "^7.0.0", + "@semantic-release/commit-analyzer": "^8.0.0", "@semantic-release/error": "^2.2.0", - "@semantic-release/github": "^6.0.0", - "@semantic-release/npm": "^6.0.0", - "@semantic-release/release-notes-generator": "^7.1.2", + "@semantic-release/github": "^7.0.0", + "@semantic-release/npm": "^7.0.0", + "@semantic-release/release-notes-generator": "^9.0.0", "aggregate-error": "^3.0.0", "cosmiconfig": "^6.0.0", "debug": "^4.0.0", @@ -12824,76 +12426,19 @@ "hook-std": "^2.0.0", "hosted-git-info": "^3.0.0", "lodash": "^4.17.15", - "marked": "^0.8.0", - "marked-terminal": "^3.2.0", + "marked": "^1.0.0", + "marked-terminal": "^4.0.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "p-reduce": "^2.0.0", "read-pkg-up": "^7.0.0", "resolve-from": "^5.0.0", - "semver": "^7.1.1", + "semver": "^7.3.2", "semver-diff": "^3.1.1", "signale": "^1.2.1", "yargs": "^15.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -12908,9 +12453,9 @@ } }, "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -12935,15 +12480,6 @@ "strip-final-newline": "^2.0.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -12954,12 +12490,6 @@ "path-exists": "^4.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, "get-stream": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", @@ -12978,18 +12508,6 @@ "lru-cache": "^5.1.1" } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -13005,22 +12523,6 @@ "p-locate": "^4.1.0" } }, - "marked": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", - "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -13030,15 +12532,6 @@ "path-key": "^3.0.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -13048,12 +12541,6 @@ "p-limit": "^2.2.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": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -13115,12 +12602,6 @@ "type-fest": "^0.8.1" } }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -13136,35 +12617,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13173,53 +12625,13 @@ "requires": { "isexe": "^2.0.0" } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, - "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "semver": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", - "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==" + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" }, "semver-compare": { "version": "1.0.0", @@ -13267,29 +12679,6 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -13340,18 +12729,41 @@ } }, "sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", + "integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" + "@sinonjs/commons": "^1.7.2", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.0.3", + "diff": "^4.0.2", + "nise": "^4.0.1", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "sinon-chai": { @@ -13377,153 +12789,12 @@ "is-fullwidth-code-point": "^2.0.0" } }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "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": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "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" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", @@ -13541,18 +12812,9 @@ "make-dir": "^3.0.0", "rimraf": "^3.0.0", "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, + "which": "^2.0.1" + }, + "dependencies": { "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13605,15 +12867,6 @@ "through": "2" } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -13681,27 +12934,6 @@ "integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=", "dev": true }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "stream-combiner2": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", @@ -13735,9 +12967,9 @@ } }, "string.prototype.trimend": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", - "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { "define-properties": "^1.1.3", @@ -13767,9 +12999,9 @@ } }, "string.prototype.trimstart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", - "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "requires": { "define-properties": "^1.1.3", @@ -13845,20 +13077,29 @@ } }, "supports-hyperlinks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", - "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", "dev": true, "requires": { - "has-flag": "^2.0.0", - "supports-color": "^5.0.0" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, "dependencies": { "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -13979,26 +13220,33 @@ } }, "temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "dev": true }, "tempy": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", - "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.5.0.tgz", + "integrity": "sha512-VEY96x7gbIRfsxqsafy2l5yVxxp3PhwAGoWMyC2D2Zt5DmEv+2tGiPOrquNRpf21hhGnKLVEsuqleqiZmKG/qw==", "dev": true, "requires": { - "temp-dir": "^1.0.0", - "type-fest": "^0.3.1", - "unique-string": "^1.0.0" + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.12.0", + "unique-string": "^2.0.0" }, "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", "dev": true } } @@ -14088,46 +13336,13 @@ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" } }, "to-through": { @@ -14299,23 +13514,13 @@ "dev": true }, "uglify-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", - "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", + "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } + "commander": "~2.20.3" } }, "unc-path-regex": { @@ -14330,18 +13535,6 @@ "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", "dev": true }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -14353,18 +13546,18 @@ } }, "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, "requires": { - "crypto-random-string": "^1.0.0" + "crypto-random-string": "^2.0.0" } }, "universal-user-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", - "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", "dev": true, "requires": { "os-name": "^3.1.0" @@ -14376,46 +13569,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -14425,24 +13578,12 @@ "punycode": "^2.1.0" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -14606,9 +13747,9 @@ } }, "windows-release": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", - "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz", + "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==", "dev": true, "requires": { "execa": "^1.0.0" @@ -14755,78 +13896,159 @@ } }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^6.0.0", "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" }, "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "color-name": "~1.1.4" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "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": { - "p-try": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "p-locate": { + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { "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==", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "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-limit": "^2.0.0" + "p-locate": "^4.1.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==", + "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 + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -14909,15 +14131,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "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", @@ -14927,12 +14140,6 @@ "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 - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", diff --git a/package.json b/package.json index 6772f6906294..7c13f6dfb5b5 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "moment": "^2.24.0", "moment-timezone": "^0.5.21", "retry-as-promised": "^3.2.0", - "semver": "^7.1.0", + "semver": "^7.3.2", "sequelize-pool": "^3.1.0", "toposort-class": "^1.0.1", "uuid": "^3.4.0", @@ -49,7 +49,7 @@ "@commitlint/cli": "^8.3.5", "@commitlint/config-angular": "^8.3.4", "@types/bluebird": "^3.5.26", - "@types/node": "^12.7.8", + "@types/node": "^12.12.37", "@types/validator": "^10.11.0", "acorn": "^7.1.1", "big-integer": "^1.6.45", @@ -60,7 +60,7 @@ "cls-hooked": "^4.2.2", "cross-env": "^7.0.2", "delay": "^4.3.0", - "dtslint": "^2.0.5", + "dtslint": "^3.4.2", "env-cmd": "^10.1.0", "esdoc": "^1.1.0", "esdoc-inject-style-plugin": "^1.0.0", @@ -69,14 +69,13 @@ "eslint-plugin-jsdoc": "^20.4.0", "eslint-plugin-mocha": "^6.2.2", "fs-jetpack": "^2.2.3", - "husky": "^4.2.3", + "husky": "^4.2.5", "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", - "lint-staged": "^10.1.1", + "lint-staged": "^10.1.7", "mariadb": "^2.3.1", - "markdownlint-cli": "^0.21.0", - "marked": "^0.7.0", - "mocha": "^6.1.4", + "markdownlint-cli": "^0.22.0", + "mocha": "^7.1.1", "mysql2": "^1.6.5", "nyc": "^15.0.0", "p-map": "^4.0.0", @@ -85,9 +84,9 @@ "pg": "^7.8.1", "pg-hstore": "^2.x", "pg-types": "^2.0.0", - "rimraf": "^2.7.1", - "semantic-release": "^16.0.2", - "sinon": "^7.5.0", + "rimraf": "^3.0.2", + "semantic-release": "^17.0.7", + "sinon": "^9.0.2", "sinon-chai": "^3.3.0", "sqlite3": "^4.0.6", "tedious": "6.0.0", From 303b453af693f732778eaa44d937b87d51deb97c Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Thu, 23 Apr 2020 01:17:43 -0500 Subject: [PATCH 101/414] refactor(transaction): asyncify methods (#12156) --- lib/transaction.js | 86 ++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/lib/transaction.js b/lib/transaction.js index 4ff44a3394f3..c8f55c2703aa 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -64,7 +64,7 @@ class Transaction { } finally { this.finished = 'commit'; if (!this.parent) { - await this.cleanup(); + this.cleanup(); } for (const hook of this._afterCommitHooks) { await hook.apply(this, [this]); @@ -77,30 +77,30 @@ class Transaction { * * @returns {Promise} */ - rollback() { + async rollback() { if (this.finished) { - return Promise.reject(new Error(`Transaction cannot be rolled back because it has been finished with state: ${this.finished}`)); + throw new Error(`Transaction cannot be rolled back because it has been finished with state: ${this.finished}`); } if (!this.connection) { - return Promise.reject(new Error('Transaction cannot be rolled back because it never started')); + throw new Error('Transaction cannot be rolled back because it never started'); } this._clearCls(); - return this - .sequelize - .getQueryInterface() - .rollbackTransaction(this, this.options) - .finally(() => { - if (!this.parent) { - return this.cleanup(); - } - return this; - }); + try { + return await this + .sequelize + .getQueryInterface() + .rollbackTransaction(this, this.options); + } finally { + if (!this.parent) { + this.cleanup(); + } + } } - prepareEnvironment(useCLS) { + async prepareEnvironment(useCLS) { let connectionPromise; if (useCLS === undefined) { @@ -117,47 +117,49 @@ class Transaction { connectionPromise = this.sequelize.connectionManager.getConnection(acquireOptions); } - return connectionPromise - .then(connection => { - this.connection = connection; - this.connection.uuid = this.id; - }) - .then(() => { - return this.begin() - .then(() => this.setDeferrable()) - .catch(setupErr => this.rollback().finally(() => { - throw setupErr; - })); - }) - .then(result => { - if (useCLS && this.sequelize.constructor._cls) { - this.sequelize.constructor._cls.set('transaction', this); - } - return Promise.resolve(null).then(() => result); - }); + let result; + const connection = await connectionPromise; + this.connection = connection; + this.connection.uuid = this.id; + + try { + await this.begin(); + result = await this.setDeferrable(); + } catch (setupErr) { + try { + result = await this.rollback(); + } finally { + throw setupErr; // eslint-disable-line no-unsafe-finally + } + } + + if (useCLS && this.sequelize.constructor._cls) { + this.sequelize.constructor._cls.set('transaction', this); + } + + return result; } - setDeferrable() { + async setDeferrable() { if (this.options.deferrable) { - return this + return await this .sequelize .getQueryInterface() .deferConstraints(this, this.options); } } - begin() { + async begin() { const queryInterface = this.sequelize.getQueryInterface(); if ( this.sequelize.dialect.supports.settingIsolationLevelDuringTransaction ) { - return queryInterface.startTransaction(this, this.options).then(() => { - return queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); - }); + await queryInterface.startTransaction(this, this.options); + return queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); } - return queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options).then(() => { - return queryInterface.startTransaction(this, this.options); - }); + await queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); + + return queryInterface.startTransaction(this, this.options); } cleanup() { From cae6b4dd9ceeb5f691b6673e5e912a450edae4ba Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Thu, 23 Apr 2020 01:20:57 -0500 Subject: [PATCH 102/414] refactor(sqlite/query): simplify promise usage (#12157) --- lib/dialects/sqlite/query.js | 42 +++++++++++++++++------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 4621c5fb5e73..33e826927133 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -232,36 +232,34 @@ class Query extends AbstractQuery { } - return new Promise(resolve => { + return new Promise((resolve, reject) => { const columnTypes = {}; conn.serialize(() => { const executeSql = () => { if (sql.startsWith('-- ')) { return resolve(); } - resolve(new Promise((resolve, reject) => { - const query = this; - // cannot use arrow function here because the function is bound to the statement - function afterExecute(executionError, results) { - try { - complete(); - // `this` is passed from sqlite, we have no control over this. - // eslint-disable-next-line no-invalid-this - resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); - return; - } catch (error) { - reject(error); - } + const query = this; + // cannot use arrow function here because the function is bound to the statement + function afterExecute(executionError, results) { + try { + complete(); + // `this` is passed from sqlite, we have no control over this. + // eslint-disable-next-line no-invalid-this + resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); + return; + } catch (error) { + reject(error); } + } - if (method === 'exec') { - // exec does not support bind parameter - conn[method](sql, afterExecute); - } else { - if (!parameters) parameters = []; - conn[method](sql, parameters, afterExecute); - } - })); + if (method === 'exec') { + // exec does not support bind parameter + conn[method](sql, afterExecute); + } else { + if (!parameters) parameters = []; + conn[method](sql, parameters, afterExecute); + } return null; }; From 2e42d66e16d077aeed1b97e593526f3f98e100b5 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Wed, 22 Apr 2020 23:48:42 -0700 Subject: [PATCH 103/414] ci(install): use package lock for install (#12154) --- .travis.yml | 2 +- CONTRIBUTING.md | 4 +- appveyor.yml | 7 +- package-lock.json | 1856 ++++++++++++++++++++------------------------- 4 files changed, 811 insertions(+), 1058 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6b9d76ebe95..bf4979cc2266 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ branches: cache: npm install: - - npm install + - npm ci - |- if [ "$DIALECT" = "postgres-native" ]; then npm install pg-native; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f922bd1929c6..2b9f8541ca76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,11 +36,11 @@ Here comes a little surprise: You need [Node.JS](http://nodejs.org). ### 2. Install the dependencies -Just "cd" into sequelize directory and run `npm install`, see an example below: +Just "cd" into sequelize directory and run `npm ci`, see an example below: ```sh $ cd path/to/sequelize -$ npm install +$ npm ci ``` ### 3. Database diff --git a/appveyor.yml b/appveyor.yml index e65bba3af492..efe08259bce5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,12 +14,7 @@ environment: install: - ps: Install-Product node $env:NODE_VERSION x64 - - ps: | - $pkg = ConvertFrom-Json (Get-Content -Raw package.json) - $pkg.devDependencies.PSObject.Properties.Remove('sqlite3') - $pkg.devDependencies.PSObject.Properties.Remove('pg-native') - ConvertTo-Json $pkg | Out-File package.json -Encoding UTF8 - - npm install + - npm ci build: off diff --git a/package-lock.json b/package-lock.json index c23f65127288..65471518334a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,12 +46,12 @@ } }, "@babel/generator": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", - "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", + "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", "dev": true, "requires": { - "@babel/types": "^7.9.0", + "@babel/types": "^7.9.5", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -66,14 +66,14 @@ } }, "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.9.5" } }, "@babel/helper-get-function-arity": { @@ -159,9 +159,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", "dev": true }, "@babel/helpers": { @@ -229,17 +229,17 @@ } }, "@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", + "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", + "@babel/generator": "^7.9.5", + "@babel/helper-function-name": "^7.9.5", "@babel/helper-split-export-declaration": "^7.8.3", "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/types": "^7.9.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -254,12 +254,12 @@ } }, "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.9.5", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" }, @@ -462,6 +462,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -474,7 +483,8 @@ "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==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "path-exists": { "version": "4.0.0", @@ -521,6 +531,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -533,7 +552,8 @@ "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==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "path-exists": { "version": "4.0.0", @@ -843,17 +863,6 @@ "tempy": "^0.5.0" }, "dependencies": { - "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "execa": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", @@ -929,12 +938,6 @@ "lines-and-columns": "^1.1.6" } }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -947,21 +950,6 @@ "type-fest": "^0.6.0" } }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -973,15 +961,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -1041,6 +1020,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -1050,6 +1038,12 @@ "p-limit": "^2.2.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": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -1161,7 +1155,8 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/geojson": { "version": "7946.0.7", @@ -1276,9 +1271,9 @@ }, "dependencies": { "@types/node": { - "version": "8.10.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", + "version": "8.10.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", + "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==", "dev": true } } @@ -1311,9 +1306,9 @@ } }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -1974,6 +1969,7 @@ "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", + "fsevents": "~2.1.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -2079,9 +2075,9 @@ } }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "cliui": { @@ -2356,70 +2352,17 @@ "dev": true, "requires": { "cross-spawn": "^7.0.1" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, "crypto-random-string": { @@ -2519,7 +2462,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decamelize-keys": { "version": "1.1.0", @@ -2773,6 +2717,15 @@ "path-exists": "^3.0.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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", @@ -2782,6 +2735,12 @@ "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 + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -2902,7 +2861,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "end-of-stream": { "version": "1.4.4", @@ -2929,17 +2889,6 @@ "java-properties": "^1.0.0" }, "dependencies": { - "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "execa": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", @@ -2980,36 +2929,6 @@ "requires": { "path-key": "^3.0.0" } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -3028,47 +2947,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true - }, - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -3182,12 +3060,6 @@ "universalify": "^0.1.0" } }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -3438,12 +3310,6 @@ "graceful-fs": "^4.1.6" } }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, "repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", @@ -3549,6 +3415,27 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", @@ -3558,12 +3445,33 @@ "type-fest": "^0.8.1" } }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -3574,10 +3482,19 @@ } }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", + "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -3669,18 +3586,18 @@ "dev": true }, "esquery": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz", - "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^5.0.0" + "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz", - "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", "dev": true } } @@ -3719,6 +3636,57 @@ "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "extend": { @@ -3821,43 +3789,6 @@ "commondir": "^1.0.1", "make-dir": "^3.0.2", "pkg-dir": "^4.1.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==", - "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==", - "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==", - "requires": { - "p-limit": "^2.2.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==" - }, - "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==" - } } }, "find-up": { @@ -3941,49 +3872,6 @@ "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^3.0.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "forever-agent": { @@ -4088,6 +3976,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -5279,17 +5174,6 @@ "uuid": "^3.3.3" }, "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -5298,36 +5182,6 @@ "requires": { "aggregate-error": "^3.0.0" } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -5514,9 +5368,9 @@ "dev": true }, "json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -5747,17 +5601,6 @@ "yaml": "^1.7.2" } }, - "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "execa": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", @@ -5823,31 +5666,10 @@ "lines-and-columns": "^1.1.6" } }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, "supports-color": { @@ -5858,15 +5680,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -6332,9 +6145,9 @@ "dev": true }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" @@ -6469,9 +6282,9 @@ "dev": true }, "marked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", - "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, "marked-terminal": { @@ -6800,6 +6613,15 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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", @@ -6809,6 +6631,12 @@ "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 + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -6844,6 +6672,15 @@ "has-flag": "^3.0.0" } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -6970,9 +6807,9 @@ } }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "dev": true }, "native-duplexpair": { @@ -7304,7 +7141,7 @@ "dependencies": { "JSONStream": { "version": "1.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, "requires": { @@ -7314,13 +7151,13 @@ }, "abbrev": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, "agent-base": { "version": "4.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "requires": { @@ -7329,7 +7166,7 @@ }, "agentkeepalive": { "version": "3.5.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "dev": true, "requires": { @@ -7338,7 +7175,7 @@ }, "ajv": { "version": "5.5.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { @@ -7350,7 +7187,7 @@ }, "ansi-align": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "dev": true, "requires": { @@ -7359,13 +7196,13 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "3.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { @@ -7374,31 +7211,31 @@ }, "ansicolors": { "version": "0.3.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, "ansistyles": { "version": "0.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", "dev": true }, "aproba": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, "archy": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "are-we-there-yet": { "version": "1.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, "requires": { @@ -7408,7 +7245,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -7423,7 +7260,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -7434,13 +7271,13 @@ }, "asap": { "version": "2.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", "dev": true }, "asn1": { "version": "0.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { @@ -7449,37 +7286,37 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "asynckit": { "version": "0.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "aws-sign2": { "version": "0.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { "version": "1.8.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, "balanced-match": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, @@ -7489,7 +7326,7 @@ }, "bin-links": { "version": "1.1.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.7.tgz", "integrity": "sha512-/eaLaTu7G7/o7PV04QPy1HRT65zf+1tFkPGv0sPTV0tRwufooYBQO3zrcyGgm+ja+ZtBf2GEuKjDRJ2pPG+yqA==", "dev": true, "requires": { @@ -7503,13 +7340,13 @@ }, "bluebird": { "version": "3.5.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true }, "boxen": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "dev": true, "requires": { @@ -7524,7 +7361,7 @@ }, "brace-expansion": { "version": "1.1.11", - "resolved": false, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { @@ -7534,31 +7371,31 @@ }, "buffer-from": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", "dev": true }, "builtins": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", "dev": true }, "byline": { "version": "5.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", "dev": true }, "byte-size": { "version": "5.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", "dev": true }, "cacache": { "version": "12.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { @@ -7581,31 +7418,31 @@ }, "call-limit": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz", "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==", "dev": true }, "camelcase": { "version": "4.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, "capture-stack-trace": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", "dev": true }, "caseless": { "version": "0.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, "chalk": { "version": "2.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { @@ -7616,19 +7453,19 @@ }, "chownr": { "version": "1.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "ci-info": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, "cidr-regex": { "version": "2.0.10", - "resolved": false, + "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz", "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", "dev": true, "requires": { @@ -7637,13 +7474,13 @@ }, "cli-boxes": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", "dev": true }, "cli-columns": { "version": "3.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz", "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", "dev": true, "requires": { @@ -7653,7 +7490,7 @@ }, "cli-table3": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "dev": true, "requires": { @@ -7664,7 +7501,7 @@ }, "cliui": { "version": "4.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { @@ -7675,13 +7512,13 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { @@ -7692,13 +7529,13 @@ }, "clone": { "version": "1.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "cmd-shim": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz", "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", "dev": true, "requires": { @@ -7708,19 +7545,19 @@ }, "co": { "version": "4.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "color-convert": { "version": "1.9.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { @@ -7729,20 +7566,20 @@ }, "color-name": { "version": "1.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { "version": "1.3.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "dev": true, "optional": true }, "columnify": { "version": "1.5.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "dev": true, "requires": { @@ -7752,7 +7589,7 @@ }, "combined-stream": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, "requires": { @@ -7761,13 +7598,13 @@ }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "concat-stream": { "version": "1.6.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { @@ -7779,7 +7616,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -7794,7 +7631,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -7805,7 +7642,7 @@ }, "config-chain": { "version": "1.1.12", - "resolved": false, + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, "requires": { @@ -7815,7 +7652,7 @@ }, "configstore": { "version": "3.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "dev": true, "requires": { @@ -7829,13 +7666,13 @@ }, "console-control-strings": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "copy-concurrently": { "version": "1.0.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { @@ -7849,13 +7686,13 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "iferr": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true } @@ -7863,13 +7700,13 @@ }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "create-error-class": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, "requires": { @@ -7878,7 +7715,7 @@ }, "cross-spawn": { "version": "5.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { @@ -7889,7 +7726,7 @@ "dependencies": { "lru-cache": { "version": "4.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { @@ -7899,7 +7736,7 @@ }, "yallist": { "version": "2.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true } @@ -7907,19 +7744,19 @@ }, "crypto-random-string": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, "cyclist": { "version": "0.2.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, "dashdash": { "version": "1.14.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { @@ -7928,7 +7765,7 @@ }, "debug": { "version": "3.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { @@ -7937,7 +7774,7 @@ "dependencies": { "ms": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } @@ -7945,31 +7782,31 @@ }, "debuglog": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", "dev": true }, "decamelize": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decode-uri-component": { "version": "0.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, "deep-extend": { "version": "0.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "defaults": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { @@ -7978,7 +7815,7 @@ }, "define-properties": { "version": "1.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { @@ -7987,31 +7824,31 @@ }, "delayed-stream": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, "detect-indent": { "version": "5.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true }, "detect-newline": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, "dezalgo": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", "dev": true, "requires": { @@ -8021,7 +7858,7 @@ }, "dot-prop": { "version": "4.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { @@ -8030,19 +7867,19 @@ }, "dotenv": { "version": "5.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", "dev": true }, "duplexer3": { "version": "0.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, "duplexify": { "version": "3.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "dev": true, "requires": { @@ -8054,7 +7891,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8069,7 +7906,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8080,7 +7917,7 @@ }, "ecc-jsbn": { "version": "0.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "optional": true, @@ -8091,13 +7928,13 @@ }, "editor": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", "dev": true }, "encoding": { "version": "0.1.12", - "resolved": false, + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "dev": true, "requires": { @@ -8106,7 +7943,7 @@ }, "end-of-stream": { "version": "1.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { @@ -8115,19 +7952,19 @@ }, "env-paths": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", "dev": true }, "err-code": { "version": "1.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", "dev": true }, "errno": { "version": "0.1.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { @@ -8136,7 +7973,7 @@ }, "es-abstract": { "version": "1.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { @@ -8149,7 +7986,7 @@ }, "es-to-primitive": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { @@ -8160,13 +7997,13 @@ }, "es6-promise": { "version": "4.2.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, "es6-promisify": { "version": "5.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -8175,13 +8012,13 @@ }, "escape-string-regexp": { "version": "1.0.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "execa": { "version": "0.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { @@ -8196,7 +8033,7 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": false, + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } @@ -8204,43 +8041,43 @@ }, "extend": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extsprintf": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, "fast-deep-equal": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, "figgy-pudding": { "version": "3.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, "find-npm-prefix": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==", "dev": true }, "find-up": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { @@ -8249,7 +8086,7 @@ }, "flush-write-stream": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { @@ -8259,7 +8096,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8274,7 +8111,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8285,13 +8122,13 @@ }, "forever-agent": { "version": "0.6.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, "form-data": { "version": "2.3.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "dev": true, "requires": { @@ -8302,7 +8139,7 @@ }, "from2": { "version": "2.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { @@ -8312,7 +8149,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8327,7 +8164,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8338,7 +8175,7 @@ }, "fs-minipass": { "version": "1.2.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, "requires": { @@ -8347,7 +8184,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { @@ -8359,7 +8196,7 @@ }, "fs-vacuum": { "version": "1.2.10", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz", "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", "dev": true, "requires": { @@ -8370,7 +8207,7 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { @@ -8382,13 +8219,13 @@ "dependencies": { "iferr": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8403,7 +8240,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8414,19 +8251,19 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "function-bind": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { @@ -8442,13 +8279,13 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -8461,13 +8298,13 @@ }, "genfun": { "version": "5.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", "dev": true }, "gentle-fs": { "version": "2.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.0.tgz", "integrity": "sha512-3k2CgAmPxuz7S6nKK+AqFE2AdM1QuwqKLPKzIET3VRwK++3q96MsNFobScDjlCrq97ZJ8y5R725MOlm6ffUCjg==", "dev": true, "requires": { @@ -8486,13 +8323,13 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "iferr": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true } @@ -8500,13 +8337,13 @@ }, "get-caller-file": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-stream": { "version": "4.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { @@ -8515,7 +8352,7 @@ }, "getpass": { "version": "0.1.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { @@ -8524,7 +8361,7 @@ }, "glob": { "version": "7.1.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { @@ -8538,7 +8375,7 @@ }, "global-dirs": { "version": "0.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { @@ -8547,7 +8384,7 @@ }, "got": { "version": "6.7.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -8566,7 +8403,7 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": false, + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } @@ -8574,19 +8411,19 @@ }, "graceful-fs": { "version": "4.2.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, "har-schema": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, "har-validator": { "version": "5.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "dev": true, "requires": { @@ -8596,7 +8433,7 @@ }, "has": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { @@ -8605,37 +8442,37 @@ }, "has-flag": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true }, "hosted-git-info": { "version": "2.8.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "http-cache-semantics": { "version": "3.8.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true }, "http-proxy-agent": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, "requires": { @@ -8645,7 +8482,7 @@ }, "http-signature": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { @@ -8656,7 +8493,7 @@ }, "https-proxy-agent": { "version": "2.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { @@ -8666,7 +8503,7 @@ }, "humanize-ms": { "version": "1.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "dev": true, "requires": { @@ -8675,7 +8512,7 @@ }, "iconv-lite": { "version": "0.4.23", - "resolved": false, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { @@ -8684,13 +8521,13 @@ }, "iferr": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz", "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==", "dev": true }, "ignore-walk": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "requires": { @@ -8699,25 +8536,25 @@ }, "import-lazy": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", "dev": true }, "imurmurhash": { "version": "0.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "infer-owner": { "version": "1.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { @@ -8727,19 +8564,19 @@ }, "inherits": { "version": "2.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "ini": { "version": "1.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "init-package-json": { "version": "1.10.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", "dev": true, "requires": { @@ -8755,31 +8592,31 @@ }, "invert-kv": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ip": { "version": "1.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, "ip-regex": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, "is-callable": { "version": "1.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-ci": { "version": "1.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { @@ -8788,7 +8625,7 @@ "dependencies": { "ci-info": { "version": "1.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true } @@ -8796,7 +8633,7 @@ }, "is-cidr": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.0.0.tgz", "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", "dev": true, "requires": { @@ -8805,13 +8642,13 @@ }, "is-date-object": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { @@ -8820,7 +8657,7 @@ }, "is-installed-globally": { "version": "0.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "dev": true, "requires": { @@ -8830,19 +8667,19 @@ }, "is-npm": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", "dev": true }, "is-obj": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, "is-path-inside": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { @@ -8851,13 +8688,13 @@ }, "is-redirect": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, "is-regex": { "version": "1.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { @@ -8866,19 +8703,19 @@ }, "is-retry-allowed": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", "dev": true }, "is-stream": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, "is-symbol": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "dev": true, "requires": { @@ -8887,68 +8724,68 @@ }, "is-typedarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isstream": { "version": "0.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, "jsbn": { "version": "0.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true, "optional": true }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema": { "version": "0.2.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, "json-schema-traverse": { "version": "0.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, "json-stringify-safe": { "version": "5.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "jsonparse": { "version": "1.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, "jsprim": { "version": "1.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { @@ -8960,7 +8797,7 @@ }, "latest-version": { "version": "3.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "dev": true, "requires": { @@ -8969,13 +8806,13 @@ }, "lazy-property": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz", "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", "dev": true }, "lcid": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { @@ -8984,7 +8821,7 @@ }, "libcipm": { "version": "4.0.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.7.tgz", "integrity": "sha512-fTq33otU3PNXxxCTCYCYe7V96o59v/o7bvtspmbORXpgFk+wcWrGf5x6tBgui5gCed/45/wtPomBsZBYm5KbIw==", "dev": true, "requires": { @@ -9007,7 +8844,7 @@ }, "libnpm": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz", "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", "dev": true, "requires": { @@ -9035,7 +8872,7 @@ }, "libnpmaccess": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz", "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", "dev": true, "requires": { @@ -9047,7 +8884,7 @@ }, "libnpmconfig": { "version": "1.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz", "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", "dev": true, "requires": { @@ -9058,7 +8895,7 @@ "dependencies": { "find-up": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { @@ -9067,7 +8904,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { @@ -9077,7 +8914,7 @@ }, "p-limit": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { @@ -9086,7 +8923,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { @@ -9095,7 +8932,7 @@ }, "p-try": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true } @@ -9103,7 +8940,7 @@ }, "libnpmhook": { "version": "5.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz", "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", "dev": true, "requires": { @@ -9115,7 +8952,7 @@ }, "libnpmorg": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz", "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", "dev": true, "requires": { @@ -9127,7 +8964,7 @@ }, "libnpmpublish": { "version": "1.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz", "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", "dev": true, "requires": { @@ -9144,7 +8981,7 @@ }, "libnpmsearch": { "version": "2.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz", "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", "dev": true, "requires": { @@ -9155,7 +8992,7 @@ }, "libnpmteam": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz", "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", "dev": true, "requires": { @@ -9167,7 +9004,7 @@ }, "libnpx": { "version": "10.2.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.2.tgz", "integrity": "sha512-ujaYToga1SAX5r7FU5ShMFi88CWpY75meNZtr6RtEyv4l2ZK3+Wgvxq2IqlwWBiDZOqhumdeiocPS1aKrCMe3A==", "dev": true, "requires": { @@ -9183,7 +9020,7 @@ }, "locate-path": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { @@ -9193,7 +9030,7 @@ }, "lock-verify": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz", "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", "dev": true, "requires": { @@ -9203,7 +9040,7 @@ }, "lockfile": { "version": "1.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", "dev": true, "requires": { @@ -9212,13 +9049,13 @@ }, "lodash._baseindexof": { "version": "3.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", "dev": true }, "lodash._baseuniq": { "version": "4.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", "dev": true, "requires": { @@ -9228,19 +9065,19 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", "dev": true }, "lodash._cacheindexof": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", "dev": true }, "lodash._createcache": { "version": "3.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", "dev": true, "requires": { @@ -9249,61 +9086,61 @@ }, "lodash._createset": { "version": "4.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", "dev": true }, "lodash._getnative": { "version": "3.9.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", "dev": true }, "lodash._root": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, "lodash.clonedeep": { "version": "4.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, "lodash.restparam": { "version": "3.6.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", "dev": true }, "lodash.union": { "version": "4.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", "dev": true }, "lodash.uniq": { "version": "4.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, "lodash.without": { "version": "4.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz", "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", "dev": true }, "lowercase-keys": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, "lru-cache": { "version": "5.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { @@ -9312,7 +9149,7 @@ }, "make-dir": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { @@ -9321,7 +9158,7 @@ }, "make-fetch-happen": { "version": "5.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "dev": true, "requires": { @@ -9340,7 +9177,7 @@ }, "map-age-cleaner": { "version": "0.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "dev": true, "requires": { @@ -9349,13 +9186,13 @@ }, "meant": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz", "integrity": "sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==", "dev": true }, "mem": { "version": "4.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { @@ -9366,7 +9203,7 @@ "dependencies": { "mimic-fn": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true } @@ -9374,13 +9211,13 @@ }, "mime-db": { "version": "1.35.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", "dev": true }, "mime-types": { "version": "2.1.19", - "resolved": false, + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { @@ -9389,7 +9226,7 @@ }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { @@ -9398,7 +9235,7 @@ }, "minizlib": { "version": "1.3.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "dev": true, "requires": { @@ -9407,7 +9244,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { @@ -9419,7 +9256,7 @@ }, "mississippi": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "dev": true, "requires": { @@ -9437,7 +9274,7 @@ }, "mkdirp": { "version": "0.5.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", "dev": true, "requires": { @@ -9446,7 +9283,7 @@ "dependencies": { "minimist": { "version": "1.2.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } @@ -9454,7 +9291,7 @@ }, "move-concurrently": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { @@ -9468,7 +9305,7 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true } @@ -9476,25 +9313,25 @@ }, "ms": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, "mute-stream": { "version": "0.0.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, "nice-try": { "version": "1.0.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "node-fetch-npm": { "version": "2.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", "dev": true, "requires": { @@ -9505,7 +9342,7 @@ }, "node-gyp": { "version": "5.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz", "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", "dev": true, "requires": { @@ -9524,7 +9361,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "requires": { @@ -9534,7 +9371,7 @@ }, "normalize-package-data": { "version": "2.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { @@ -9546,7 +9383,7 @@ "dependencies": { "resolve": { "version": "1.10.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "dev": true, "requires": { @@ -9557,7 +9394,7 @@ }, "npm-audit-report": { "version": "1.3.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.2.tgz", "integrity": "sha512-abeqS5ONyXNaZJPGAf6TOUMNdSe1Y6cpc9MLBRn+CuUoYbfdca6AxOyXVlfIv9OgKX+cacblbG5w7A6ccwoTPw==", "dev": true, "requires": { @@ -9567,7 +9404,7 @@ }, "npm-bundled": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "dev": true, "requires": { @@ -9576,13 +9413,13 @@ }, "npm-cache-filename": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", "dev": true }, "npm-install-checks": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz", "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", "dev": true, "requires": { @@ -9591,7 +9428,7 @@ }, "npm-lifecycle": { "version": "3.1.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.4.tgz", "integrity": "sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A==", "dev": true, "requires": { @@ -9607,19 +9444,19 @@ }, "npm-logical-tree": { "version": "1.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz", "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==", "dev": true }, "npm-normalize-package-bin": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, "npm-package-arg": { "version": "6.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", "dev": true, "requires": { @@ -9631,7 +9468,7 @@ }, "npm-packlist": { "version": "1.4.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "dev": true, "requires": { @@ -9642,7 +9479,7 @@ }, "npm-pick-manifest": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "dev": true, "requires": { @@ -9653,7 +9490,7 @@ }, "npm-profile": { "version": "4.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz", "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", "dev": true, "requires": { @@ -9664,7 +9501,7 @@ }, "npm-registry-fetch": { "version": "4.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz", "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", "dev": true, "requires": { @@ -9679,7 +9516,7 @@ "dependencies": { "safe-buffer": { "version": "5.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", "dev": true } @@ -9687,7 +9524,7 @@ }, "npm-run-path": { "version": "2.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { @@ -9696,13 +9533,13 @@ }, "npm-user-validate": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.0.tgz", "integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=", "dev": true }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { @@ -9714,31 +9551,31 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "oauth-sign": { "version": "0.9.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object-keys": { "version": "1.0.12", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, "object.getownpropertydescriptors": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { @@ -9748,7 +9585,7 @@ }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { @@ -9757,19 +9594,19 @@ }, "opener": { "version": "1.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", "dev": true }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "3.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { @@ -9780,7 +9617,7 @@ "dependencies": { "cross-spawn": { "version": "6.0.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { @@ -9793,7 +9630,7 @@ }, "execa": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { @@ -9810,13 +9647,13 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, "osenv": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "requires": { @@ -9826,25 +9663,25 @@ }, "p-defer": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", "dev": true }, "p-finally": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-is-promise": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, "p-limit": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", "dev": true, "requires": { @@ -9853,7 +9690,7 @@ }, "p-locate": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { @@ -9862,13 +9699,13 @@ }, "p-try": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "package-json": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "dev": true, "requires": { @@ -9880,7 +9717,7 @@ }, "pacote": { "version": "9.5.12", - "resolved": false, + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "dev": true, "requires": { @@ -9918,7 +9755,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { @@ -9930,7 +9767,7 @@ }, "parallel-transform": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "dev": true, "requires": { @@ -9941,7 +9778,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9956,7 +9793,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -9967,67 +9804,67 @@ }, "path-exists": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-is-inside": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, "path-key": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "path-parse": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "performance-now": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, "pify": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "prepend-http": { "version": "1.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, "promise-inflight": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, "promise-retry": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", "dev": true, "requires": { @@ -10037,7 +9874,7 @@ "dependencies": { "retry": { "version": "0.10.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", "dev": true } @@ -10045,7 +9882,7 @@ }, "promzard": { "version": "0.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", "dev": true, "requires": { @@ -10054,13 +9891,13 @@ }, "proto-list": { "version": "1.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", "dev": true }, "protoduck": { "version": "5.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", "dev": true, "requires": { @@ -10069,25 +9906,25 @@ }, "prr": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, "psl": { "version": "1.1.29", - "resolved": false, + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", "dev": true }, "pump": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { @@ -10097,7 +9934,7 @@ }, "pumpify": { "version": "1.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { @@ -10108,7 +9945,7 @@ "dependencies": { "pump": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { @@ -10120,25 +9957,25 @@ }, "punycode": { "version": "1.4.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, "qrcode-terminal": { "version": "0.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "dev": true }, "qs": { "version": "6.5.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "query-string": { "version": "6.8.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", "dev": true, "requires": { @@ -10149,13 +9986,13 @@ }, "qw": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz", "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=", "dev": true }, "rc": { "version": "1.2.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { @@ -10167,7 +10004,7 @@ "dependencies": { "minimist": { "version": "1.2.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } @@ -10175,7 +10012,7 @@ }, "read": { "version": "1.0.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "dev": true, "requires": { @@ -10184,7 +10021,7 @@ }, "read-cmd-shim": { "version": "1.0.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz", "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", "dev": true, "requires": { @@ -10193,7 +10030,7 @@ }, "read-installed": { "version": "4.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", "dev": true, "requires": { @@ -10208,7 +10045,7 @@ }, "read-package-json": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", "dev": true, "requires": { @@ -10221,7 +10058,7 @@ }, "read-package-tree": { "version": "5.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", "dev": true, "requires": { @@ -10232,7 +10069,7 @@ }, "readable-stream": { "version": "3.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { @@ -10243,7 +10080,7 @@ }, "readdir-scoped-modules": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "dev": true, "requires": { @@ -10255,7 +10092,7 @@ }, "registry-auth-token": { "version": "3.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { @@ -10265,7 +10102,7 @@ }, "registry-url": { "version": "3.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "dev": true, "requires": { @@ -10274,7 +10111,7 @@ }, "request": { "version": "2.88.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "requires": { @@ -10302,31 +10139,31 @@ }, "require-directory": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, "resolve-from": { "version": "4.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "retry": { "version": "0.12.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, "rimraf": { "version": "2.7.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { @@ -10335,7 +10172,7 @@ }, "run-queue": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { @@ -10344,7 +10181,7 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true } @@ -10352,25 +10189,25 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "semver": { "version": "5.7.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "semver-diff": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "dev": true, "requires": { @@ -10379,13 +10216,13 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "sha": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz", "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", "dev": true, "requires": { @@ -10394,7 +10231,7 @@ }, "shebang-command": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { @@ -10403,31 +10240,31 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "slide": { "version": "1.1.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", "dev": true }, "smart-buffer": { "version": "4.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", "dev": true }, "socks": { "version": "2.3.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", "dev": true, "requires": { @@ -10437,7 +10274,7 @@ }, "socks-proxy-agent": { "version": "4.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "dev": true, "requires": { @@ -10447,7 +10284,7 @@ "dependencies": { "agent-base": { "version": "4.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "dev": true, "requires": { @@ -10458,13 +10295,13 @@ }, "sorted-object": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", "dev": true }, "sorted-union-stream": { "version": "2.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz", "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", "dev": true, "requires": { @@ -10474,7 +10311,7 @@ "dependencies": { "from2": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz", "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", "dev": true, "requires": { @@ -10484,13 +10321,13 @@ }, "isarray": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, "readable-stream": { "version": "1.1.14", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -10502,7 +10339,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } @@ -10510,7 +10347,7 @@ }, "spdx-correct": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { @@ -10520,13 +10357,13 @@ }, "spdx-exceptions": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { @@ -10536,19 +10373,19 @@ }, "spdx-license-ids": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, "split-on-first": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "dev": true }, "sshpk": { "version": "1.14.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "dev": true, "requires": { @@ -10565,7 +10402,7 @@ }, "ssri": { "version": "6.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { @@ -10574,7 +10411,7 @@ }, "stream-each": { "version": "1.2.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", "dev": true, "requires": { @@ -10584,7 +10421,7 @@ }, "stream-iterate": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz", "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", "dev": true, "requires": { @@ -10594,7 +10431,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -10609,7 +10446,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -10620,19 +10457,19 @@ }, "stream-shift": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, "strict-uri-encode": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", "dev": true }, "string-width": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { @@ -10642,19 +10479,19 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "strip-ansi": { "version": "4.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { @@ -10665,7 +10502,7 @@ }, "string_decoder": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { @@ -10674,7 +10511,7 @@ "dependencies": { "safe-buffer": { "version": "5.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", "dev": true } @@ -10682,13 +10519,13 @@ }, "stringify-package": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", "dev": true }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -10697,19 +10534,19 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "supports-color": { "version": "5.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { @@ -10718,7 +10555,7 @@ }, "tar": { "version": "4.4.13", - "resolved": false, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "dev": true, "requires": { @@ -10733,7 +10570,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "requires": { @@ -10745,7 +10582,7 @@ }, "term-size": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "dev": true, "requires": { @@ -10754,19 +10591,19 @@ }, "text-table": { "version": "0.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "through": { "version": "2.3.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, "through2": { "version": "2.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { @@ -10776,7 +10613,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -10791,7 +10628,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -10802,19 +10639,19 @@ }, "timed-out": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, "tiny-relative-date": { "version": "1.3.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", "dev": true }, "tough-cookie": { "version": "2.4.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "requires": { @@ -10824,7 +10661,7 @@ }, "tunnel-agent": { "version": "0.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, "requires": { @@ -10833,32 +10670,32 @@ }, "tweetnacl": { "version": "0.14.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true, "optional": true }, "typedarray": { "version": "0.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, "uid-number": { "version": "0.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", "dev": true }, "umask": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", "dev": true }, "unique-filename": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "dev": true, "requires": { @@ -10867,7 +10704,7 @@ }, "unique-slug": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "dev": true, "requires": { @@ -10876,7 +10713,7 @@ }, "unique-string": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "dev": true, "requires": { @@ -10885,19 +10722,19 @@ }, "unpipe": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, "unzip-response": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", "dev": true }, "update-notifier": { "version": "2.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "dev": true, "requires": { @@ -10915,7 +10752,7 @@ }, "url-parse-lax": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, "requires": { @@ -10924,19 +10761,19 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "util-extend": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", "dev": true }, "util-promisify": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", "dev": true, "requires": { @@ -10945,13 +10782,13 @@ }, "uuid": { "version": "3.3.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", "dev": true }, "validate-npm-package-license": { "version": "3.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { @@ -10961,7 +10798,7 @@ }, "validate-npm-package-name": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", "dev": true, "requires": { @@ -10970,7 +10807,7 @@ }, "verror": { "version": "1.10.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { @@ -10981,7 +10818,7 @@ }, "wcwidth": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "dev": true, "requires": { @@ -10990,7 +10827,7 @@ }, "which": { "version": "1.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { @@ -10999,13 +10836,13 @@ }, "which-module": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "wide-align": { "version": "1.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, "requires": { @@ -11014,7 +10851,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -11027,7 +10864,7 @@ }, "widest-line": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { @@ -11036,7 +10873,7 @@ }, "worker-farm": { "version": "1.7.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "dev": true, "requires": { @@ -11045,7 +10882,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -11055,7 +10892,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -11068,13 +10905,13 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "write-file-atomic": { "version": "2.4.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { @@ -11085,31 +10922,31 @@ }, "xdg-basedir": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, "xtend": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true }, "y18n": { "version": "4.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true }, "yargs": { "version": "11.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", "dev": true, "requires": { @@ -11129,7 +10966,7 @@ "dependencies": { "y18n": { "version": "3.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true } @@ -11137,7 +10974,7 @@ }, "yargs-parser": { "version": "9.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { @@ -11179,6 +11016,14 @@ "dev": true, "requires": { "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + } } }, "npmlog": { @@ -11249,48 +11094,6 @@ "yargs": "^15.0.2" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -11301,16 +11104,6 @@ "path-exists": "^4.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -11320,6 +11113,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -11341,55 +11143,14 @@ "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==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "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 - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -11577,18 +11338,12 @@ "dev": true }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "requires": { - "p-try": "^2.0.0" - }, - "dependencies": { - "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==" - } + "p-try": "^1.0.0" } }, "p-locate": { @@ -11598,17 +11353,6 @@ "dev": true, "requires": { "p-limit": "^1.1.0" - }, - "dependencies": { - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - } } }, "p-map": { @@ -11742,9 +11486,9 @@ "dev": true }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { @@ -11920,6 +11664,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -11929,6 +11682,12 @@ "p-limit": "^2.2.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 + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11959,9 +11718,9 @@ "dev": true }, "postgres-date": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz", - "integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz", + "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==", "dev": true }, "postgres-interval": { @@ -12290,9 +12049,9 @@ "dev": true }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -12452,17 +12211,6 @@ "yaml": "^1.7.2" } }, - "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, "execa": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", @@ -12523,6 +12271,12 @@ "p-locate": "^4.1.0" } }, + "marked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", + "dev": true + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -12532,6 +12286,15 @@ "path-key": "^3.0.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -12541,6 +12304,12 @@ "p-limit": "^2.2.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": "5.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", @@ -12559,12 +12328,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -12601,30 +12364,6 @@ "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -12680,18 +12419,18 @@ "dev": true }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "shimmer": { @@ -12813,17 +12552,6 @@ "rimraf": "^3.0.0", "signal-exit": "^3.0.2", "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "spdx-correct": { @@ -12837,9 +12565,9 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { @@ -12906,9 +12634,9 @@ } }, "sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", + "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", "dev": true }, "sshpk": { @@ -13717,9 +13445,9 @@ } }, "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -13887,12 +13615,12 @@ "dev": true }, "yaml": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", - "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz", + "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==", "dev": true, "requires": { - "@babel/runtime": "^7.8.7" + "@babel/runtime": "^7.9.2" } }, "yargs": { @@ -13993,6 +13721,15 @@ "p-locate": "^4.1.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -14002,6 +13739,12 @@ "p-limit": "^2.2.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 + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -14131,6 +13874,15 @@ "path-exists": "^3.0.0" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "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", @@ -14140,6 +13892,12 @@ "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 + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", From c130d070c2b724454bcc786aee28623e6756a62c Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Thu, 23 Apr 2020 18:24:03 -0700 Subject: [PATCH 104/414] test: debug transaction leak (#12153) --- lib/hooks.js | 1 + lib/sequelize.js | 7 +++--- test/integration/support.js | 36 ++++++++++++++++++++++++---- test/integration/transaction.test.js | 2 +- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 69602a048b4d..7d80dc390436 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -33,6 +33,7 @@ const hookTypes = { beforeFindAfterOptions: { params: 1 }, afterFind: { params: 2 }, beforeCount: { params: 1 }, + transactionCreated: { params: 1, sync: true, noModel: true }, beforeDefine: { params: 2, sync: true, noModel: true }, afterDefine: { params: 1, sync: true, noModel: true }, beforeInit: { params: 2, sync: true, noModel: true }, diff --git a/lib/sequelize.js b/lib/sequelize.js index 7b03c67139dd..5ea5b5e59045 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -644,11 +644,11 @@ class Sequelize { const query = new this.dialect.Query(connection, this, options); try { - await this.runHooks('beforeQuery', options, query); - await checkTransaction(); + await this.runHooks('beforeQuery', options, query, sql); + checkTransaction(); return await query.run(sql, bindParameters); } finally { - await this.runHooks('afterQuery', options, query); + await this.runHooks('afterQuery', options, query, sql); if (!options.transaction) { await this.connectionManager.releaseConnection(connection); } @@ -1100,6 +1100,7 @@ class Sequelize { } const transaction = new Transaction(this, options); + this.runHooks('transactionCreated', transaction); if (!autoCallback) { await transaction.prepareEnvironment(false); diff --git a/test/integration/support.js b/test/integration/support.js index 0cc31c0390be..4d2d10352e7f 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -2,14 +2,29 @@ const Support = require('../support'); -const runningQueries = new Set(); +const runningQueries = new Map(); +const runningTransactions = new Map(); // map transaction option to queries. before(function() { - this.sequelize.addHook('beforeQuery', (options, query) => { - runningQueries.add(query); + this.sequelize.addHook('transactionCreated', t => { // tracking race condition, remove me if no longer present. + t.trace = new Error().stack; }); - this.sequelize.addHook('afterQuery', (options, query) => { + this.sequelize.addHook('beforeQuery', (options, query, sql) => { + runningQueries.set(query, options); + if (options.transaction) { + const queryList = runningTransactions.get(options.transaction.id); + if (queryList) { + queryList.push(sql); + } else { + runningTransactions.set(options.transaction.id, [sql]); + } + } + }); + this.sequelize.addHook('afterQuery', (options, query, sql) => { runningQueries.delete(query); + if (options.transaction && ['COMMIT', 'ROLLBACK'].includes(sql)) { + runningTransactions.delete(options.transaction.id); + } }); }); @@ -22,7 +37,18 @@ afterEach(function() { return; } let msg = `Expected 0 running queries. ${runningQueries.size} queries still running in ${this.currentTest.fullTitle()}\n`; - msg += `Queries:\n${[...runningQueries].map(query => `${query.uuid}: ${query.sql}`).join('\n')}`; + msg += 'Queries:\n\n'; + for (const [query, options] of runningQueries) { + msg += `${query.uuid}: ${query.sql}\n`; + if (options.transaction) { + const relatedTransaction = runningTransactions.get(options.transaction.id); + if (relatedTransaction) { + msg += options.transaction.trace; + msg += 'In transaction:\n\n'; + msg += relatedTransaction.join('\n'); + } + } + } throw new Error(msg); }); diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 236226c21884..f3a78bd3f68c 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -549,7 +549,7 @@ if (current.dialect.supports.transactions) { return User.findAll({ transaction }) .then(users => expect( users ).to.have.lengthOf(0)) .then(() => User.create({ username: 'jan' })) // Create a User outside of the transaction - .then(() => User.findAll({ transaction })) + .then(() => User.findAll({ transaction })) .then(users => expect( users ).to.have.lengthOf(0)); // We SHOULD NOT see the created user inside the transaction }); }) From a0e244f9bf7ef3e0e3950d98b225b002e8954c32 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 24 Apr 2020 05:05:45 -0700 Subject: [PATCH 105/414] refactor: use native versions (#12159) --- docs/manual-utils.js | 4 ++-- lib/associations/mixin.js | 2 +- lib/data-types.js | 2 +- lib/dialects/abstract/query.js | 4 ++-- lib/dialects/postgres/query-generator.js | 6 +----- lib/dialects/postgres/query.js | 2 +- lib/dialects/sqlite/query-generator.js | 2 +- lib/errors/association-error.js | 1 - lib/errors/base-error.js | 1 - lib/errors/bulk-record-error.js | 1 - lib/errors/connection-error.js | 1 - lib/errors/connection/access-denied-error.js | 1 - .../connection/connection-acquire-timeout-error.js | 1 - lib/errors/connection/connection-refused-error.js | 1 - lib/errors/connection/connection-timed-out-error.js | 1 - lib/errors/connection/host-not-found-error.js | 1 - lib/errors/connection/host-not-reachable-error.js | 1 - lib/errors/connection/invalid-connection-error.js | 1 - lib/errors/database-error.js | 1 - lib/errors/database/exclusion-constraint-error.js | 1 - lib/errors/database/foreign-key-constraint-error.js | 1 - lib/errors/database/timeout-error.js | 1 - lib/errors/database/unknown-constraint-error.js | 1 - lib/errors/eager-loading-error.js | 1 - lib/errors/empty-result-error.js | 1 - lib/errors/instance-error.js | 1 - lib/errors/optimistic-lock-error.js | 1 - lib/errors/query-error.js | 1 - lib/errors/sequelize-scope-error.js | 1 - lib/errors/validation-error.js | 1 - lib/errors/validation/unique-constraint-error.js | 1 - lib/instance-validator.js | 2 +- lib/query-interface.js | 4 ++-- lib/utils.js | 2 +- test/integration/associations/self.test.js | 7 +++---- test/integration/model/create.test.js | 9 ++------- 36 files changed, 18 insertions(+), 52 deletions(-) diff --git a/docs/manual-utils.js b/docs/manual-utils.js index 04109eda59a7..a09ce52d216a 100644 --- a/docs/manual-utils.js +++ b/docs/manual-utils.js @@ -7,7 +7,7 @@ const assert = require('assert'); function getDeclaredManuals() { const declaredManualGroups = require('./manual-groups.json'); - return _.flatten(_.values(declaredManualGroups)).map(file => { + return _.flatten(Object.values(declaredManualGroups)).map(file => { return normalize(`./docs/manual/${file}`); }); } @@ -34,4 +34,4 @@ function checkManuals() { } } -module.exports = { getDeclaredManuals, getAllManuals, checkManuals }; \ No newline at end of file +module.exports = { getDeclaredManuals, getAllManuals, checkManuals }; diff --git a/lib/associations/mixin.js b/lib/associations/mixin.js index 60601d77d82b..062de0edea92 100644 --- a/lib/associations/mixin.js +++ b/lib/associations/mixin.js @@ -75,7 +75,7 @@ const Mixin = { }, getAssociations(target) { - return _.values(this.associations).filter(association => association.target.name === target.name); + return Object.values(this.associations).filter(association => association.target.name === target.name); }, getAssociationForAlias(target, alias) { diff --git a/lib/data-types.js b/lib/data-types.js index 780e6ac6a66d..fb8593699d56 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -1041,7 +1041,7 @@ dialectMap.mariadb = require('./dialects/mariadb/data-types')(DataTypes); dialectMap.sqlite = require('./dialects/sqlite/data-types')(DataTypes); dialectMap.mssql = require('./dialects/mssql/data-types')(DataTypes); -const dialectList = _.values(dialectMap); +const dialectList = Object.values(dialectMap); for (const dataTypes of dialectList) { _.each(dataTypes, (DataType, key) => { diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 14a9be92fc55..991d79db61ae 100755 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -206,7 +206,7 @@ class AbstractQuery { } handleShowTablesQuery(results) { - return _.flatten(results.map(resultSet => _.values(resultSet))); + return _.flatten(results.map(resultSet => Object.values(resultSet))); } isShowIndexesQuery() { @@ -334,7 +334,7 @@ class AbstractQuery { const logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters; const startTime = Date.now(); let logParameter = ''; - + if (logQueryParameters && parameters) { const delimiter = sql.endsWith(';') ? '' : ';'; let paramStr; diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index b20d01f25b63..c9dea5ed2e39 100755 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -384,7 +384,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { throw new Error('Cannot LIMIT delete without a model.'); } - const pks = _.values(model.primaryKeys).map(pk => this.quoteIdentifier(pk.field)).join(','); + const pks = Object.values(model.primaryKeys).map(pk => this.quoteIdentifier(pk.field)).join(','); primaryKeys = model.primaryKeyAttributes.length > 1 ? `(${pks})` : pks; primaryKeysSelection = pks; @@ -840,10 +840,6 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return matches.slice(0, -1); } - padInt(i) { - return i < 10 ? `0${i.toString()}` : i.toString(); - } - dataTypeMapping(tableName, attr, dataType) { if (dataType.includes('PRIMARY KEY')) { dataType = dataType.replace('PRIMARY KEY', ''); diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 458674564bc6..048eedc5c809 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -121,7 +121,7 @@ class Query extends AbstractQuery { })); } if (isTableNameQuery) { - return rows.map(row => _.values(row)); + return rows.map(row => Object.values(row)); } if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index a4f734934ec6..9fa931bec208 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -23,7 +23,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { options = options || {}; const primaryKeys = []; - const needsMultiplePrimaryKeys = _.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; + const needsMultiplePrimaryKeys = Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; const attrArray = []; for (const attr in attributes) { diff --git a/lib/errors/association-error.js b/lib/errors/association-error.js index 0a273d7c7f59..d4ef83f9ae52 100644 --- a/lib/errors/association-error.js +++ b/lib/errors/association-error.js @@ -9,7 +9,6 @@ class AssociationError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeAssociationError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/base-error.js b/lib/errors/base-error.js index 15f0cd80a245..ef0060113513 100644 --- a/lib/errors/base-error.js +++ b/lib/errors/base-error.js @@ -11,7 +11,6 @@ class BaseError extends Error { constructor(message) { super(message); this.name = 'SequelizeBaseError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js index cda33985cfa4..4e30fe6d907e 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -15,7 +15,6 @@ class BulkRecordError extends BaseError { this.name = 'SequelizeBulkRecordError'; this.errors = error; this.record = record; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection-error.js b/lib/errors/connection-error.js index 0536dbfe4926..4a3a8a38e989 100644 --- a/lib/errors/connection-error.js +++ b/lib/errors/connection-error.js @@ -16,7 +16,6 @@ class ConnectionError extends BaseError { */ this.parent = parent; this.original = parent; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/access-denied-error.js b/lib/errors/connection/access-denied-error.js index bfa5f8e85b6e..c6bc2e8f72f8 100644 --- a/lib/errors/connection/access-denied-error.js +++ b/lib/errors/connection/access-denied-error.js @@ -9,7 +9,6 @@ class AccessDeniedError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeAccessDeniedError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/connection-acquire-timeout-error.js b/lib/errors/connection/connection-acquire-timeout-error.js index 0d9e63b1d2ab..661707b93116 100644 --- a/lib/errors/connection/connection-acquire-timeout-error.js +++ b/lib/errors/connection/connection-acquire-timeout-error.js @@ -9,7 +9,6 @@ class ConnectionAcquireTimeoutError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeConnectionAcquireTimeoutError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/connection-refused-error.js b/lib/errors/connection/connection-refused-error.js index 9b121b82f793..0c689c11aab6 100644 --- a/lib/errors/connection/connection-refused-error.js +++ b/lib/errors/connection/connection-refused-error.js @@ -9,7 +9,6 @@ class ConnectionRefusedError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeConnectionRefusedError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/connection-timed-out-error.js b/lib/errors/connection/connection-timed-out-error.js index 84171801d162..2a2004b9ab70 100644 --- a/lib/errors/connection/connection-timed-out-error.js +++ b/lib/errors/connection/connection-timed-out-error.js @@ -9,7 +9,6 @@ class ConnectionTimedOutError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeConnectionTimedOutError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/host-not-found-error.js b/lib/errors/connection/host-not-found-error.js index ac2600130469..c0493aff9280 100644 --- a/lib/errors/connection/host-not-found-error.js +++ b/lib/errors/connection/host-not-found-error.js @@ -9,7 +9,6 @@ class HostNotFoundError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeHostNotFoundError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/host-not-reachable-error.js b/lib/errors/connection/host-not-reachable-error.js index eee27beb4cc1..a6bab854f143 100644 --- a/lib/errors/connection/host-not-reachable-error.js +++ b/lib/errors/connection/host-not-reachable-error.js @@ -9,7 +9,6 @@ class HostNotReachableError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeHostNotReachableError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/connection/invalid-connection-error.js b/lib/errors/connection/invalid-connection-error.js index 21c50973b96e..538303f31c38 100644 --- a/lib/errors/connection/invalid-connection-error.js +++ b/lib/errors/connection/invalid-connection-error.js @@ -9,7 +9,6 @@ class InvalidConnectionError extends ConnectionError { constructor(parent) { super(parent); this.name = 'SequelizeInvalidConnectionError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database-error.js b/lib/errors/database-error.js index a0623a020e26..c7059d3ba9e0 100644 --- a/lib/errors/database-error.js +++ b/lib/errors/database-error.js @@ -29,7 +29,6 @@ class DatabaseError extends BaseError { * @type {Array} */ this.parameters = parent.parameters; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/exclusion-constraint-error.js b/lib/errors/database/exclusion-constraint-error.js index 581b36efbfb4..f77e52aab3ea 100644 --- a/lib/errors/database/exclusion-constraint-error.js +++ b/lib/errors/database/exclusion-constraint-error.js @@ -17,7 +17,6 @@ class ExclusionConstraintError extends DatabaseError { this.constraint = options.constraint; this.fields = options.fields; this.table = options.table; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/foreign-key-constraint-error.js b/lib/errors/database/foreign-key-constraint-error.js index ba50973c8557..9bdf02ad0c14 100644 --- a/lib/errors/database/foreign-key-constraint-error.js +++ b/lib/errors/database/foreign-key-constraint-error.js @@ -19,7 +19,6 @@ class ForeignKeyConstraintError extends DatabaseError { this.value = options.value; this.index = options.index; this.reltype = options.reltype; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/timeout-error.js b/lib/errors/database/timeout-error.js index 59dc600a060e..b67933b50f77 100644 --- a/lib/errors/database/timeout-error.js +++ b/lib/errors/database/timeout-error.js @@ -9,7 +9,6 @@ class TimeoutError extends DatabaseError { constructor(parent) { super(parent); this.name = 'SequelizeTimeoutError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/database/unknown-constraint-error.js b/lib/errors/database/unknown-constraint-error.js index 33d6f42985b7..e11fa666c7ef 100644 --- a/lib/errors/database/unknown-constraint-error.js +++ b/lib/errors/database/unknown-constraint-error.js @@ -17,7 +17,6 @@ class UnknownConstraintError extends DatabaseError { this.constraint = options.constraint; this.fields = options.fields; this.table = options.table; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/eager-loading-error.js b/lib/errors/eager-loading-error.js index 6963e21d5bf2..928af9c447bd 100644 --- a/lib/errors/eager-loading-error.js +++ b/lib/errors/eager-loading-error.js @@ -9,7 +9,6 @@ class EagerLoadingError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeEagerLoadingError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/empty-result-error.js b/lib/errors/empty-result-error.js index ffb2fd1bcff5..c967817d0690 100644 --- a/lib/errors/empty-result-error.js +++ b/lib/errors/empty-result-error.js @@ -9,7 +9,6 @@ class EmptyResultError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeEmptyResultError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/instance-error.js b/lib/errors/instance-error.js index 6f20e36ec729..913ce1e3b3b7 100644 --- a/lib/errors/instance-error.js +++ b/lib/errors/instance-error.js @@ -9,7 +9,6 @@ class InstanceError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeInstanceError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/optimistic-lock-error.js b/lib/errors/optimistic-lock-error.js index 103ed7ea2626..0c0ff3eb7db4 100644 --- a/lib/errors/optimistic-lock-error.js +++ b/lib/errors/optimistic-lock-error.js @@ -28,7 +28,6 @@ class OptimisticLockError extends BaseError { * @type {object} */ this.where = options.where; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/query-error.js b/lib/errors/query-error.js index c48617ac1571..3ec05cc3712a 100644 --- a/lib/errors/query-error.js +++ b/lib/errors/query-error.js @@ -9,7 +9,6 @@ class QueryError extends BaseError { constructor(message) { super(message); this.name = 'SequelizeQueryError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/sequelize-scope-error.js b/lib/errors/sequelize-scope-error.js index f3ffd58ed7dc..f7a40ad58c71 100644 --- a/lib/errors/sequelize-scope-error.js +++ b/lib/errors/sequelize-scope-error.js @@ -9,7 +9,6 @@ class SequelizeScopeError extends BaseError { constructor(parent) { super(parent); this.name = 'SequelizeScopeError'; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 0cddef5d51af..185a5d76c835 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -30,7 +30,6 @@ class ValidationError extends BaseError { } else if (this.errors.length > 0 && this.errors[0].message) { this.message = this.errors.map(err => `${err.type || err.origin}: ${err.message}`).join(',\n'); } - Error.captureStackTrace(this, this.constructor); } /** diff --git a/lib/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js index c02f842ea781..a509e88ff4fb 100644 --- a/lib/errors/validation/unique-constraint-error.js +++ b/lib/errors/validation/unique-constraint-error.js @@ -19,7 +19,6 @@ class UniqueConstraintError extends ValidationError { this.parent = options.parent; this.original = options.parent; this.sql = options.parent.sql; - Error.captureStackTrace(this, this.constructor); } } diff --git a/lib/instance-validator.js b/lib/instance-validator.js index 68d90bb8e745..a6acfdac4270 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -344,7 +344,7 @@ class InstanceValidator { */ _validateSchema(rawAttribute, field, value) { if (rawAttribute.allowNull === false && (value === null || value === undefined)) { - const association = _.values(this.modelInstance.constructor.associations).find(association => association instanceof BelongsTo && association.foreignKey === rawAttribute.fieldName); + const association = Object.values(this.modelInstance.constructor.associations).find(association => association instanceof BelongsTo && association.foreignKey === rawAttribute.fieldName); if (!association || !this.modelInstance.get(association.associationAccessor)) { const validators = this.modelInstance.validators[field]; const errMsg = _.get(validators, 'notNull.msg', `${this.modelInstance.constructor.name}.${field} cannot be null`); diff --git a/lib/query-interface.js b/lib/query-interface.js index 36fc29744991..b95d61ed71ee 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -296,7 +296,7 @@ class QueryInterface { if (!skip.includes(tableName.tableName || tableName)) { await this.dropTable(tableName, Object.assign({}, options, { cascade: true }) ); } - } + } }; const tableNames = await this.showAllTables(options); @@ -556,7 +556,7 @@ class QueryInterface { const attributes = {}; options = options || {}; - if (_.values(DataTypes).includes(dataTypeOrOptions)) { + if (Object.values(DataTypes).includes(dataTypeOrOptions)) { attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true }; } else { attributes[attributeName] = dataTypeOrOptions; diff --git a/lib/utils.js b/lib/utils.js index ffd03c236ded..1e4fcbb14d99 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -7,7 +7,7 @@ const uuidv1 = require('uuid/v1'); const uuidv4 = require('uuid/v4'); const Promise = require('./promise'); const operators = require('./operators'); -const operatorsSet = new Set(_.values(operators)); +const operatorsSet = new Set(Object.values(operators)); let inflection = require('inflection'); diff --git a/test/integration/associations/self.test.js b/test/integration/associations/self.test.js index 69c5988b62b8..a020fa3f98f4 100644 --- a/test/integration/associations/self.test.js +++ b/test/integration/associations/self.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, - _ = require('lodash'); + Promise = Sequelize.Promise; describe(Support.getTestDialectTeaser('Self'), () => { it('supports freezeTableName', function() { @@ -52,7 +51,7 @@ describe(Support.getTestDialectTeaser('Self'), () => { Person.belongsToMany(Person, { as: 'Parents', through: 'Family', foreignKey: 'ChildId', otherKey: 'PersonId' }); Person.belongsToMany(Person, { as: 'Childs', through: 'Family', foreignKey: 'PersonId', otherKey: 'ChildId' }); - const foreignIdentifiers = _.values(Person.associations).map(v => v.foreignIdentifier); + const foreignIdentifiers = Object.values(Person.associations).map(v => v.foreignIdentifier); const rawAttributes = Object.keys(this.sequelize.models.Family.rawAttributes); expect(foreignIdentifiers.length).to.equal(2); @@ -94,7 +93,7 @@ describe(Support.getTestDialectTeaser('Self'), () => { Person.belongsToMany(Person, { as: 'Parents', through: Family, foreignKey: 'preexisting_child', otherKey: 'preexisting_parent' }); Person.belongsToMany(Person, { as: 'Children', through: Family, foreignKey: 'preexisting_parent', otherKey: 'preexisting_child' }); - const foreignIdentifiers = _.values(Person.associations).map(v => v.foreignIdentifier); + const foreignIdentifiers = Object.values(Person.associations).map(v => v.foreignIdentifier); const rawAttributes = Object.keys(Family.rawAttributes); expect(foreignIdentifiers.length).to.equal(2); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 8256d84bed29..ced6cff4f1dc 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -925,13 +925,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { return userWithDefaults.sync({ force: true }).then(() => { return userWithDefaults.create({}).then(user => { return userWithDefaults.findByPk(user.id).then(user => { - const now = new Date(), - pad = function(number) { - if (number > 9) { - return number; - } - return `0${number}`; - }; + const now = new Date(); + const pad = number => number.toString().padStart(2, '0'); expect(user.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); }); From 69c269b516209cbdcd33de5b716dab6048821fa1 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 24 Apr 2020 13:45:03 -0700 Subject: [PATCH 106/414] test(cls): fix flaky test and remove relevant tracing code (#12165) --- lib/hooks.js | 1 - lib/sequelize.js | 5 ++--- test/integration/cls.test.js | 35 +++++++++++++++-------------------- test/integration/support.js | 35 +++++------------------------------ 4 files changed, 22 insertions(+), 54 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 7d80dc390436..69602a048b4d 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -33,7 +33,6 @@ const hookTypes = { beforeFindAfterOptions: { params: 1 }, afterFind: { params: 2 }, beforeCount: { params: 1 }, - transactionCreated: { params: 1, sync: true, noModel: true }, beforeDefine: { params: 2, sync: true, noModel: true }, afterDefine: { params: 1, sync: true, noModel: true }, beforeInit: { params: 2, sync: true, noModel: true }, diff --git a/lib/sequelize.js b/lib/sequelize.js index 5ea5b5e59045..a01d6b8f4d3d 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -644,11 +644,11 @@ class Sequelize { const query = new this.dialect.Query(connection, this, options); try { - await this.runHooks('beforeQuery', options, query, sql); + await this.runHooks('beforeQuery', options, query); checkTransaction(); return await query.run(sql, bindParameters); } finally { - await this.runHooks('afterQuery', options, query, sql); + await this.runHooks('afterQuery', options, query); if (!options.transaction) { await this.connectionManager.releaseConnection(connection); } @@ -1100,7 +1100,6 @@ class Sequelize { } const transaction = new Transaction(this, options); - this.runHooks('transactionCreated', transaction); if (!autoCallback) { await transaction.prepareEnvironment(false); diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js index ff3361e2466e..86f70ab314e3 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -64,23 +64,21 @@ if (current.dialect.supports.transactions) { }); }); - it('does not leak variables to the outer scope', function() { + it('does not leak variables to the outer scope', async function() { // This is a little tricky. We want to check the values in the outer scope, when the transaction has been successfully set up, but before it has been comitted. // We can't just call another function from inside that transaction, since that would transfer the context to that function - exactly what we are trying to prevent; let transactionSetup = false, transactionEnded = false; - this.sequelize.transaction(() => { + const clsTask = this.sequelize.transaction(async () => { transactionSetup = true; - - return delay(500).then(() => { - expect(this.ns.get('transaction')).to.be.ok; - transactionEnded = true; - }); + await delay(500); + expect(this.ns.get('transaction')).to.be.ok; + transactionEnded = true; }); - return new Promise(resolve => { + await new Promise(resolve => { // Wait for the transaction to be setup const interval = setInterval(() => { if (transactionSetup) { @@ -88,22 +86,19 @@ if (current.dialect.supports.transactions) { resolve(); } }, 200); - }).then(() => { - expect(transactionEnded).not.to.be.ok; + }); + expect(transactionEnded).not.to.be.ok; - expect(this.ns.get('transaction')).not.to.be.ok; + expect(this.ns.get('transaction')).not.to.be.ok; - // Just to make sure it didn't change between our last check and the assertion - expect(transactionEnded).not.to.be.ok; - }); + // Just to make sure it didn't change between our last check and the assertion + expect(transactionEnded).not.to.be.ok; + await clsTask; // ensure we don't leak the promise }); - it('does not leak variables to the following promise chain', function() { - return this.sequelize.transaction(() => { - return Promise.resolve(); - }).then(() => { - expect(this.ns.get('transaction')).not.to.be.ok; - }); + it('does not leak variables to the following promise chain', async function() { + await this.sequelize.transaction(() => {}); + expect(this.ns.get('transaction')).not.to.be.ok; }); it('does not leak outside findOrCreate', function() { diff --git a/test/integration/support.js b/test/integration/support.js index 4d2d10352e7f..7d884bf63476 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -2,29 +2,14 @@ const Support = require('../support'); -const runningQueries = new Map(); -const runningTransactions = new Map(); // map transaction option to queries. +const runningQueries = new Set(); before(function() { - this.sequelize.addHook('transactionCreated', t => { // tracking race condition, remove me if no longer present. - t.trace = new Error().stack; + this.sequelize.addHook('beforeQuery', (options, query) => { + runningQueries.add(query); }); - this.sequelize.addHook('beforeQuery', (options, query, sql) => { - runningQueries.set(query, options); - if (options.transaction) { - const queryList = runningTransactions.get(options.transaction.id); - if (queryList) { - queryList.push(sql); - } else { - runningTransactions.set(options.transaction.id, [sql]); - } - } - }); - this.sequelize.addHook('afterQuery', (options, query, sql) => { + this.sequelize.addHook('afterQuery', (options, query) => { runningQueries.delete(query); - if (options.transaction && ['COMMIT', 'ROLLBACK'].includes(sql)) { - runningTransactions.delete(options.transaction.id); - } }); }); @@ -38,17 +23,7 @@ afterEach(function() { } let msg = `Expected 0 running queries. ${runningQueries.size} queries still running in ${this.currentTest.fullTitle()}\n`; msg += 'Queries:\n\n'; - for (const [query, options] of runningQueries) { - msg += `${query.uuid}: ${query.sql}\n`; - if (options.transaction) { - const relatedTransaction = runningTransactions.get(options.transaction.id); - if (relatedTransaction) { - msg += options.transaction.trace; - msg += 'In transaction:\n\n'; - msg += relatedTransaction.join('\n'); - } - } - } + msg += [...runningQueries].map(query => `${query.uuid}: ${query.sql}`).join('\n'); throw new Error(msg); }); From db20040c7df9877520c164289073e91328b0ca05 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 24 Apr 2020 23:10:01 -0700 Subject: [PATCH 107/414] fix(model): fix unchained promise in association logic in bulkCreate (#12163) --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index bad54c4dd2c0..adf76bd652ed 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2642,7 +2642,7 @@ class Model { const associationInstance = createdAssociationInstances[idx]; const instance = associationInstanceIndexToInstanceMap[idx]; - instance[include.association.accessors.set](associationInstance, { save: false, logging: options.logging }); + await include.association.set(instance, associationInstance, { save: false, logging: options.logging }); } })); } From 55678d2efeb50b69b8fe069941e8c40be96a8d9d Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Fri, 24 Apr 2020 23:10:48 -0700 Subject: [PATCH 108/414] fix(types): make between operator accept date ranges (#12162) --- types/lib/model.d.ts | 4 ++-- types/test/where.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index af6d3380a80d..54991a3cf3b5 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -157,7 +157,7 @@ export interface WhereOperators { [Op.not]?: null | boolean | string | number | Literal | WhereOperators; /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ - [Op.between]?: [number, number]; + [Op.between]?: Rangable; /** Example: `[Op.in]: [1, 2],` becomes `IN [1, 2]` */ [Op.in]?: (string | number | Literal)[] | Literal; @@ -222,7 +222,7 @@ export interface WhereOperators { [Op.notILike]?: string | Literal | AnyOperator | AllOperator; /** Example: `[Op.notBetween]: [11, 15],` becomes `NOT BETWEEN 11 AND 15` */ - [Op.notBetween]?: [number, number]; + [Op.notBetween]?: Rangable; /** * Strings starts with value. diff --git a/types/test/where.ts b/types/test/where.ts index 76b499b05aae..1c33e9d9b2d5 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -201,7 +201,7 @@ MyModel.findAll({ [Op.lt]: 10, // id < 10 [Op.lte]: 10, // id <= 10 [Op.ne]: 20, // id != 20 - [Op.between]: [6, 10], // BETWEEN 6 AND 10 + [Op.between]: [6, 10] || [new Date(), new Date()], // BETWEEN 6 AND 10 [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 [Op.in]: [1, 2], // IN [1, 2] [Op.notIn]: [1, 2], // NOT IN [1, 2] @@ -324,7 +324,7 @@ Sequelize.where( [Op.contains]: Sequelize.literal('LIT'), [Op.contained]: Sequelize.literal('LIT'), [Op.gt]: Sequelize.literal('LIT'), - [Op.notILike]: Sequelize.literal('LIT') + [Op.notILike]: Sequelize.literal('LIT'), } ) From 2e032e0bfce55c5ee0c3cdb9c84bc4449465d063 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 25 Apr 2020 12:28:55 +0530 Subject: [PATCH 109/414] docs(association): document uniqueKey for belongs to many (#12166) --- docs/manual/core-concepts/assocs.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md index fe46452adc61..bebbc8af5136 100644 --- a/docs/manual/core-concepts/assocs.md +++ b/docs/manual/core-concepts/assocs.md @@ -308,6 +308,12 @@ CREATE TABLE IF NOT EXISTS "ActorMovies" ( Unlike One-To-One and One-To-Many relationships, the defaults for both `ON UPDATE` and `ON DELETE` are `CASCADE` for Many-To-Many relationships. +Belongs-To-Many creates a unique key when primary key is not present on through model. This unique key name can be overridden using **uniqueKey** option. + +```js +Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' }) +``` + ## Basics of queries involving associations With the basics of defining associations covered, we can look at queries involving associations. The most common queries on this matter are the *read* queries (i.e. SELECTs). Later on, other types of queries will be shown. diff --git a/package.json b/package.json index 7c13f6dfb5b5..a6fa4fcc5331 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "semantic-release": "^17.0.7", "sinon": "^9.0.2", "sinon-chai": "^3.3.0", - "sqlite3": "^4.0.6", + "sqlite3": "^4.1.1", "tedious": "6.0.0", "typescript": "^3.6.3" }, From e0a8f23b9fb22d72552733b95cdd51967325da37 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 25 Apr 2020 12:49:52 +0530 Subject: [PATCH 110/414] docs(association): options.through.where support --- lib/associations/belongs-to-many.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 8858cbf55959..2a5e1a094f76 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -411,6 +411,7 @@ class BelongsToMany extends Association { * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model + * @param {object} [options.through.where] An optional where clause applied to through model (join table) * * @returns {Promise>} */ From adeb73a6d9faa0764e017c0a46c87afb257f2211 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sat, 25 Apr 2020 06:38:12 -0500 Subject: [PATCH 111/414] refactor: remove bluebird (#12140) --- lib/associations/belongs-to-many.js | 6 +-- lib/associations/has-many.js | 4 +- lib/dialects/abstract/connection-manager.js | 1 - lib/dialects/mssql/connection-manager.js | 1 - lib/dialects/mssql/query.js | 1 - lib/dialects/mysql/connection-manager.js | 1 - lib/dialects/mysql/query-interface.js | 1 - lib/dialects/postgres/connection-manager.js | 1 - lib/dialects/postgres/query-interface.js | 1 - lib/dialects/postgres/query.js | 1 - lib/dialects/sqlite/connection-manager.js | 3 +- lib/dialects/sqlite/query.js | 1 - lib/errors/aggregate-error.js | 34 ++++++++++++++ lib/errors/bulk-record-error.js | 2 +- lib/errors/index.js | 2 + lib/instance-validator.js | 1 - lib/model.js | 7 ++- lib/promise.js | 7 --- lib/query-interface.js | 1 - lib/sequelize.js | 11 ----- lib/transaction.js | 2 - lib/utils.js | 3 -- package-lock.json | 25 +++++----- package.json | 1 - scripts/mocha-bootload | 1 - test/integration/associations/alias.test.js | 4 +- .../associations/belongs-to-many.test.js | 25 +++++----- .../associations/belongs-to.test.js | 1 - .../integration/associations/has-many.test.js | 1 - test/integration/associations/has-one.test.js | 1 - test/integration/associations/scope.test.js | 1 - test/integration/associations/self.test.js | 4 +- test/integration/cls.test.js | 1 - test/integration/configuration.test.js | 8 ++-- test/integration/data-types.test.js | 2 +- .../abstract/connection-manager.test.js | 1 - .../dialects/mssql/query-queue.test.js | 16 +------ .../dialects/mssql/regressions.test.js | 2 +- .../integration/dialects/postgres/dao.test.js | 9 ++-- .../sqlite/connection-manager.test.js | 11 ++++- test/integration/dialects/sqlite/dao.test.js | 4 +- test/integration/hooks/associations.test.js | 14 +++--- test/integration/hooks/bulkOperation.test.js | 3 +- test/integration/hooks/create.test.js | 5 +- test/integration/hooks/hooks.test.js | 3 +- test/integration/include.test.js | 1 - test/integration/include/findAll.test.js | 1 - .../include/findAndCountAll.test.js | 3 +- test/integration/include/findOne.test.js | 1 - test/integration/include/limit.test.js | 1 - test/integration/include/paranoid.test.js | 3 +- test/integration/include/schema.test.js | 1 - test/integration/include/separate.test.js | 4 +- test/integration/instance.validations.test.js | 2 +- test/integration/instance/decrement.test.js | 5 +- test/integration/instance/increment.test.js | 3 +- test/integration/json.test.js | 10 ++-- test/integration/model.test.js | 8 ++-- test/integration/model/attributes.test.js | 1 - .../model/attributes/field.test.js | 1 - .../model/attributes/types.test.js | 1 - test/integration/model/bulk-create.test.js | 13 ++--- test/integration/model/create.test.js | 1 - test/integration/model/findAll.test.js | 36 +++++++------- .../model/findAll/groupedLimit.test.js | 5 +- .../model/findAll/separate.test.js | 5 +- test/integration/model/findOne.test.js | 3 +- test/integration/model/increment.test.js | 3 +- test/integration/model/json.test.js | 1 - test/integration/model/schema.test.js | 3 +- .../integration/model/scope/aggregate.test.js | 3 +- .../model/scope/associations.test.js | 1 - test/integration/model/scope/count.test.js | 1 - test/integration/model/scope/merge.test.js | 1 - test/integration/model/upsert.test.js | 1 - test/integration/pool.test.js | 4 +- .../integration/sequelize.transaction.test.js | 1 - test/integration/timezone.test.js | 4 +- test/integration/transaction.test.js | 3 +- test/support.js | 47 ++++++++++++++++--- test/unit/associations/has-many.test.js | 1 - test/unit/errors.test.js | 27 +++++++++++ test/unit/hooks.test.js | 4 +- test/unit/instance/set.test.js | 2 - test/unit/model/find-and-count-all.test.js | 7 +-- test/unit/model/find-create-find.test.js | 4 +- test/unit/model/validation.test.js | 3 +- test/unit/promise.test.js | 16 ------- test/unit/transaction.test.js | 4 +- types/lib/errors.d.ts | 17 +++++++ 90 files changed, 250 insertions(+), 252 deletions(-) create mode 100644 lib/errors/aggregate-error.js delete mode 100644 lib/promise.js delete mode 100644 test/unit/promise.test.js diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 2a5e1a094f76..ba96d618f5e8 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -637,7 +637,7 @@ class BelongsToMany extends Association { promises.push(this.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); } - return Utils.Promise.all(promises); + return Promise.all(promises); }; return this.through.model.findAll(_.defaults({ where, raw: true }, options)) @@ -662,7 +662,7 @@ class BelongsToMany extends Association { */ add(sourceInstance, newInstances, options) { // If newInstances is null or undefined, no-op - if (!newInstances) return Utils.Promise.resolve(); + if (!newInstances) return Promise.resolve(); options = _.clone(options) || {}; @@ -731,7 +731,7 @@ class BelongsToMany extends Association { } }))); } - return Utils.Promise.all(promises); + return Promise.all(promises); }; return association.through.model.findAll(_.defaults({ where, raw: true }, options)) diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index c5f540fc7536..723533e92d32 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -381,7 +381,7 @@ class HasMany extends Association { )); } - await Utils.Promise.all(promises); + await Promise.all(promises); return sourceInstance; } @@ -397,7 +397,7 @@ class HasMany extends Association { * @returns {Promise} */ async add(sourceInstance, targetInstances, options = {}) { - if (!targetInstances) return Utils.Promise.resolve(); + if (!targetInstances) return Promise.resolve(); const update = {}; diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index b30a59c19b32..c897efd8f964 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -3,7 +3,6 @@ const { Pool, TimeoutError } = require('sequelize-pool'); const _ = require('lodash'); const semver = require('semver'); -const Promise = require('../../promise'); const errors = require('../../errors'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('pool'); diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index b4e247b1d8c5..7d6f36d2ad55 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -2,7 +2,6 @@ const AbstractConnectionManager = require('../abstract/connection-manager'); const AsyncQueue = require('./async-queue').default; -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const sequelizeErrors = require('../../errors'); const DataTypes = require('../../data-types').mssql; diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 59011bc83927..7c6e9350bfca 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -1,6 +1,5 @@ 'use strict'; -const Promise = require('../../promise'); const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); const parserStore = require('../parserStore')('mssql'); diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 22439b1833b2..f9cebd5fbf7a 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -2,7 +2,6 @@ const AbstractConnectionManager = require('../abstract/connection-manager'); const SequelizeErrors = require('../../errors'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const DataTypes = require('../../data-types').mysql; const momentTz = require('moment-timezone'); diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 147467937ec2..1f68482fd709 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -8,7 +8,6 @@ @private */ -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); /** diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 994b9cb398a0..62ac3c68e71c 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -4,7 +4,6 @@ const _ = require('lodash'); const AbstractConnectionManager = require('../abstract/connection-manager'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('connection:pg'); -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const semver = require('semver'); const dataTypes = require('../../data-types'); diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index b7dfbd903068..ee3aefe0ee42 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -1,7 +1,6 @@ 'use strict'; const DataTypes = require('../../data-types'); -const Promise = require('../../promise'); const QueryTypes = require('../../query-types'); const _ = require('lodash'); diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 048eedc5c809..3cfc388a6157 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -2,7 +2,6 @@ const AbstractQuery = require('../abstract/query'); const QueryTypes = require('../../query-types'); -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const _ = require('lodash'); const { logger } = require('../../utils/logger'); diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 4708f967f107..60ceece11806 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -3,7 +3,6 @@ const fs = require('fs'); const path = require('path'); const AbstractConnectionManager = require('../abstract/connection-manager'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('connection:sqlite'); const dataTypes = require('../../data-types').sqlite; @@ -22,7 +21,7 @@ class ConnectionManager extends AbstractConnectionManager { } this.connections = {}; - this.lib = this._loadDialectModule('sqlite3').verbose(); + this.lib = this._loadDialectModule('sqlite3'); this.refreshTypeParser(dataTypes); } diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 33e826927133..60e2abf40ad2 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const Utils = require('../../utils'); -const Promise = require('../../promise'); const AbstractQuery = require('../abstract/query'); const QueryTypes = require('../../query-types'); const sequelizeErrors = require('../../errors'); diff --git a/lib/errors/aggregate-error.js b/lib/errors/aggregate-error.js new file mode 100644 index 000000000000..4a6eb94a4311 --- /dev/null +++ b/lib/errors/aggregate-error.js @@ -0,0 +1,34 @@ +'use strict'; + +const BaseError = require('./base-error'); + +/** + * A wrapper for multiple Errors + * + * @param {Error[]} [errors] Array of errors + * + * @property errors {Error[]} + */ +class AggregateError extends BaseError { + constructor(errors) { + super(); + this.errors = errors; + this.name = 'AggregateError'; + } + + toString() { + const message = `AggregateError of:\n${ + this.errors.map(error => + error === this + ? '[Circular AggregateError]' + : error instanceof AggregateError + ? String(error).replace(/\n$/, '').replace(/^/mg, ' ') + : String(error).replace(/^/mg, ' ').substring(2) + + ).join('\n') + }\n`; + return message; + } +} + +module.exports = AggregateError; diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js index 4e30fe6d907e..c095754040ae 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -4,7 +4,7 @@ const BaseError = require('./base-error'); /** * Thrown when bulk operation fails, it represent per record level error. - * Used with Promise.AggregateError + * Used with AggregateError * * @param {Error} error Error for a given record/instance * @param {object} record DAO instance that error belongs to diff --git a/lib/errors/index.js b/lib/errors/index.js index 8ada8c76bba8..16316a5acad4 100644 --- a/lib/errors/index.js +++ b/lib/errors/index.js @@ -2,6 +2,8 @@ exports.BaseError = require('./base-error'); +exports.AggregateError = require('./aggregate-error'); +exports.AsyncQueueError = require('../dialects/mssql/async-queue').AsyncQueueError; exports.AssociationError = require('./association-error'); exports.BulkRecordError = require('./bulk-record-error'); exports.ConnectionError = require('./connection-error'); diff --git a/lib/instance-validator.js b/lib/instance-validator.js index a6acfdac4270..665363ef9b66 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -3,7 +3,6 @@ const _ = require('lodash'); const Utils = require('./utils'); const sequelizeError = require('./errors'); -const Promise = require('./promise'); const DataTypes = require('./data-types'); const BelongsTo = require('./associations/belongs-to'); const validator = require('./utils/validator-extras').validator; diff --git a/lib/model.js b/lib/model.js index adf76bd652ed..21cd5420ad6c 100644 --- a/lib/model.js +++ b/lib/model.js @@ -11,7 +11,6 @@ const BelongsToMany = require('./associations/belongs-to-many'); const InstanceValidator = require('./instance-validator'); const QueryTypes = require('./query-types'); const sequelizeErrors = require('./errors'); -const Promise = require('./promise'); const Association = require('./associations/base'); const HasMany = require('./associations/has-many'); const DataTypes = require('./data-types'); @@ -2500,7 +2499,7 @@ class Model { * and SQLite do not make it easy to obtain back automatically generated IDs and other default values in a way that can be mapped to multiple records. * To obtain Instances for the newly created values, you will need to query for them again. * - * If validation fails, the promise is rejected with an array-like [AggregateError](http://bluebirdjs.com/docs/api/aggregateerror.html) + * If validation fails, the promise is rejected with an array-like AggregateError * * @param {Array} records List of objects (key/value pairs) to create instances from * @param {object} [options] Bulk create options @@ -2584,7 +2583,7 @@ class Model { } // Validate if (options.validate) { - const errors = new Promise.AggregateError(); + const errors = []; const validateOptions = _.clone(options); validateOptions.hooks = options.individualHooks; @@ -2598,7 +2597,7 @@ class Model { delete options.skip; if (errors.length) { - throw errors; + throw new sequelizeErrors.AggregateError(errors); } } if (options.individualHooks) { diff --git a/lib/promise.js b/lib/promise.js deleted file mode 100644 index 4bc16c1fee12..000000000000 --- a/lib/promise.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const Promise = require('bluebird').getNewLibraryCopy(); - -module.exports = Promise; -module.exports.Promise = Promise; -module.exports.default = Promise; diff --git a/lib/query-interface.js b/lib/query-interface.js index b95d61ed71ee..6976e54039ec 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -9,7 +9,6 @@ const MSSQLQueryInterface = require('./dialects/mssql/query-interface'); const MySQLQueryInterface = require('./dialects/mysql/query-interface'); const PostgresQueryInterface = require('./dialects/postgres/query-interface'); const Transaction = require('./transaction'); -const Promise = require('./promise'); const QueryTypes = require('./query-types'); const Op = require('./operators'); diff --git a/lib/sequelize.js b/lib/sequelize.js index a01d6b8f4d3d..b7ecde809e93 100755 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -16,7 +16,6 @@ const QueryTypes = require('./query-types'); const TableHints = require('./table-hints'); const IndexHints = require('./index-hints'); const sequelizeErrors = require('./errors'); -const Promise = require('./promise'); const Hooks = require('./hooks'); const Association = require('./associations/index'); const Validator = require('./utils/validator-extras').validator; @@ -1129,7 +1128,6 @@ class Sequelize { /** * Use CLS with Sequelize. * CLS namespace provided is stored as `Sequelize._cls` - * and bluebird Promise is patched to use the namespace, using `cls-bluebird` module. * * @param {object} ns CLS namespace * @returns {object} Sequelize constructor @@ -1141,10 +1139,6 @@ class Sequelize { // save namespace as `Sequelize._cls` this._cls = ns; - Promise.config({ - asyncHooks: true - }); - // return Sequelize for chaining return this; } @@ -1298,11 +1292,6 @@ Sequelize.Utils = Utils; */ Sequelize.Op = Op; -/** - * A handy reference to the bluebird Promise class - */ -Sequelize.Promise = Promise; - /** * Available table hints to be used for querying data in mssql for table hints * diff --git a/lib/transaction.js b/lib/transaction.js index c8f55c2703aa..147d3a65951b 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,7 +1,5 @@ 'use strict'; -const Promise = require('./promise'); - /** * The transaction object is used to identify a running transaction. * It is created by calling `Sequelize.transaction()`. diff --git a/lib/utils.js b/lib/utils.js index 1e4fcbb14d99..d119c62d7239 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -5,7 +5,6 @@ const SqlString = require('./sql-string'); const _ = require('lodash'); const uuidv1 = require('uuid/v1'); const uuidv4 = require('uuid/v4'); -const Promise = require('./promise'); const operators = require('./operators'); const operatorsSet = new Set(Object.values(operators)); @@ -14,8 +13,6 @@ let inflection = require('inflection'); exports.classToInvokable = require('./utils/classToInvokable').classToInvokable; exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments; -exports.Promise = Promise; - function useInflection(_inflection) { inflection = _inflection; } diff --git a/package-lock.json b/package-lock.json index 65471518334a..43865331c0a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1743,11 +1743,6 @@ "safe-buffer": "^5.1.1" } }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -11337,15 +11332,6 @@ "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", "dev": true }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", @@ -11353,6 +11339,17 @@ "dev": true, "requires": { "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + } } }, "p-map": { diff --git a/package.json b/package.json index a6fa4fcc5331..9caa4c457294 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ ], "license": "MIT", "dependencies": { - "bluebird": "^3.7.1", "debug": "^4.1.1", "dottie": "^2.0.0", "inflection": "1.12.0", diff --git a/scripts/mocha-bootload b/scripts/mocha-bootload index 6cd71e1c672b..e69de29bb2d1 100644 --- a/scripts/mocha-bootload +++ b/scripts/mocha-bootload @@ -1 +0,0 @@ -require('any-promise/register/bluebird'); \ No newline at end of file diff --git a/test/integration/associations/alias.test.js b/test/integration/associations/alias.test.js index e838d048c134..b6be24065700 100644 --- a/test/integration/associations/alias.test.js +++ b/test/integration/associations/alias.test.js @@ -2,9 +2,7 @@ const chai = require('chai'), expect = chai.expect, - Support = require('../support'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise; + Support = require('../support'); describe(Support.getTestDialectTeaser('Alias'), () => { it('should uppercase the first letter in alias getter, but not in eager loading', function() { diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 97a36a76221c..9edec9f92173 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -7,7 +7,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), _ = require('lodash'), sinon = require('sinon'), - Promise = Sequelize.Promise, Op = Sequelize.Op, current = Support.sequelize, dialect = Support.getTestDialect(); @@ -2855,7 +2854,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { // Test setup return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ + return Promise.all([ Worker.create({}), Task.bulkCreate([{}, {}, {}]).then(() => { return Task.findAll(); @@ -2885,7 +2884,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { // Test setup return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ + return Promise.all([ Worker.create({}), Task.bulkCreate([{}, {}, {}, {}, {}]).then(() => { return Task.findAll(); @@ -3151,26 +3150,26 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { const ctx = {}; return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.join( + return Promise.all([ this.User.create({ id: 67, username: 'foo' }), this.Task.create({ id: 52, title: 'task' }), this.User.create({ id: 89, username: 'bar' }), this.Task.create({ id: 42, title: 'kast' }) - ); + ]); }).then(([user1, task1, user2, task2]) => { ctx.user1 = user1; ctx.task1 = task1; ctx.user2 = user2; ctx.task2 = task2; - return Sequelize.Promise.join( + return Promise.all([ user1.setTasks([task1]), task2.setUsers([user2]) - ); + ]); }).then(() => { - return Sequelize.Promise.join( + return Promise.all([ expect(ctx.user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint ctx.task2.destroy() - ); + ]); }).then(() => { return this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }); }).then(usertasks => { @@ -3289,14 +3288,14 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }) .then(() => { - return Sequelize.Promise.all([ + return Promise.all([ User.create({ name: 'Khsama' }), User.create({ name: 'Vivek' }), User.create({ name: 'Satya' }) ]); }) .then(users => { - return Sequelize.Promise.all([ + return Promise.all([ users[0].addFan(users[1]), users[1].addUser(users[2]), users[2].addFan(users[0]) @@ -3329,13 +3328,13 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }) .then(() => { - return Sequelize.Promise.all([ + return Promise.all([ User.create({ name: 'Jalrangi' }), User.create({ name: 'Sargrahi' }) ]); }) .then(users => { - return Sequelize.Promise.all([ + return Promise.all([ users[0].addFollower(users[1]), users[1].addFollower(users[0]), users[0].addInvitee(users[1]), diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index 0308e0e42e4c..d305ecd49d9d 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -6,7 +6,6 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, current = Support.sequelize, dialect = Support.getTestDialect(); diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index 2f712ed54800..bb0f22eb97f0 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -7,7 +7,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), moment = require('moment'), sinon = require('sinon'), - Promise = Sequelize.Promise, Op = Sequelize.Op, current = Support.sequelize, _ = require('lodash'), diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index 195828876134..a119e1c9c9f2 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, current = Support.sequelize, dialect = Support.getTestDialect(); diff --git a/test/integration/associations/scope.test.js b/test/integration/associations/scope.test.js index fc51d872342c..536559660670 100644 --- a/test/integration/associations/scope.test.js +++ b/test/integration/associations/scope.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, Op = Sequelize.Op; describe(Support.getTestDialectTeaser('associations'), () => { diff --git a/test/integration/associations/self.test.js b/test/integration/associations/self.test.js index a020fa3f98f4..8ee2d9489d72 100644 --- a/test/integration/associations/self.test.js +++ b/test/integration/associations/self.test.js @@ -3,9 +3,7 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - DataTypes = require('../../../lib/data-types'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise; + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Self'), () => { it('supports freezeTableName', function() { diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js index 86f70ab314e3..9372cefe0d81 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('./support'), Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, cls = require('cls-hooked'), current = Support.sequelize, delay = require('delay'); diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js index 321b9269ad45..fa315f32bbdb 100644 --- a/test/integration/configuration.test.js +++ b/test/integration/configuration.test.js @@ -100,12 +100,12 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { expect(sequelizeReadOnly.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READONLY); expect(sequelizeReadWrite.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READWRITE); - return Sequelize.Promise.join( + return Promise.all([ sequelizeReadOnly.query(createTableFoo) .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file'), sequelizeReadWrite.query(createTableFoo) .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file') - ); + ]); }) .then(() => { // By default, sqlite creates a connection that's READWRITE | CREATE @@ -129,11 +129,11 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { } }); - return Sequelize.Promise.join( + return Promise.all([ sequelizeReadOnly.query(createTableBar) .should.be.rejectedWith(Error, 'SQLITE_READONLY: attempt to write a readonly database'), sequelizeReadWrite.query(createTableBar) - ); + ]); }) .finally(() => { return promisify(fs.unlink)(p); diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js index c8b6095ada0f..0804a211e132 100644 --- a/test/integration/data-types.test.js +++ b/test/integration/data-types.test.js @@ -385,7 +385,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { return; } - return new Sequelize.Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (/^postgres/.test(dialect)) { current.query('SELECT PostGIS_Lib_Version();') .then(result => { diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index d735c4394b7f..b70a4a071d38 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -21,7 +21,6 @@ describe('Connection Manager', () => { beforeEach(() => { sandbox = sinon.createSandbox(); - sandbox.usingPromise(require('bluebird')); }); afterEach(() => { diff --git a/test/integration/dialects/mssql/query-queue.test.js b/test/integration/dialects/mssql/query-queue.test.js index c58acccb01d4..2b0ccfec8d60 100644 --- a/test/integration/dialects/mssql/query-queue.test.js +++ b/test/integration/dialects/mssql/query-queue.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), expect = chai.expect, - Promise = require('../../../../lib/promise'), DataTypes = require('../../../../lib/data-types'), Support = require('../../support'), Sequelize = require('../../../../lib/sequelize'), @@ -94,18 +93,9 @@ if (dialect.match(/^mssql/)) { }); describe('unhandled rejections', () => { - let onUnhandledRejection; - - afterEach(() => { - process.removeListener('unhandledRejection', onUnhandledRejection); - }); - it("unhandled rejection should occur if user doesn't catch promise returned from query", async function() { const User = this.User; - const rejectionPromise = new Promise((resolve, reject) => { - onUnhandledRejection = reject; - }); - process.on('unhandledRejection', onUnhandledRejection); + const rejectionPromise = Support.nextUnhandledRejection(); User.create({ username: new Date() }); @@ -115,9 +105,7 @@ if (dialect.match(/^mssql/)) { it('no unhandled rejections should occur as long as user catches promise returned from query', async function() { const User = this.User; - const unhandledRejections = []; - onUnhandledRejection = error => unhandledRejections.push(error); - process.on('unhandledRejection', onUnhandledRejection); + const unhandledRejections = Support.captureUnhandledRejections(); await expect(User.create({ username: new Date() })).to.be.rejectedWith(Sequelize.ValidationError); diff --git a/test/integration/dialects/mssql/regressions.test.js b/test/integration/dialects/mssql/regressions.test.js index 670b6e32fa4f..cfdbd0782fbc 100644 --- a/test/integration/dialects/mssql/regressions.test.js +++ b/test/integration/dialects/mssql/regressions.test.js @@ -53,7 +53,7 @@ if (dialect.match(/^mssql/)) { { UserName: 'Aryamaan' } ], { returning: true })) .then(([vyom, shakti, nikita, arya]) => { - return Sequelize.Promise.all([ + return Promise.all([ vyom.createLoginLog(), shakti.createLoginLog(), nikita.createLoginLog(), diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index 3b9ff4527ed4..e8254961f666 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Support = require('../../support'), Sequelize = Support.Sequelize, Op = Sequelize.Op, - Promise = Sequelize.Promise, dialect = Support.getTestDialect(), DataTypes = require('../../../../lib/data-types'), sequelize = require('../../../../lib/sequelize'); @@ -94,7 +93,7 @@ if (dialect.match(/^postgres/)) { describe('json', () => { it('should be able to retrieve a row with ->> operator', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) .then(() => { @@ -106,7 +105,7 @@ if (dialect.match(/^postgres/)) { }); it('should be able to query using the nested query language', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) .then(() => { @@ -120,7 +119,7 @@ if (dialect.match(/^postgres/)) { }); it('should be able to query using dot syntax', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) .then(() => { @@ -132,7 +131,7 @@ if (dialect.match(/^postgres/)) { }); it('should be able to query using dot syntax with uppercase name', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })]) .then(() => { diff --git a/test/integration/dialects/sqlite/connection-manager.test.js b/test/integration/dialects/sqlite/connection-manager.test.js index 5c525abbd32b..60f132ed3e42 100644 --- a/test/integration/dialects/sqlite/connection-manager.test.js +++ b/test/integration/dialects/sqlite/connection-manager.test.js @@ -33,11 +33,20 @@ if (dialect === 'sqlite') { return User.create({ username: 'user2' }, { transaction }); }); }) - .then(() => { + .then(async () => { expect(jetpack.exists(fileName)).to.be.equal('file'); expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + // move wal file content to main database + // so those files can be removed on connection close + // https://www.sqlite.org/wal.html#ckpt + await sequelize.query('PRAGMA wal_checkpoint'); + + // wal, shm files exist after checkpoint + expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); + expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + return sequelize.close(); }) .then(() => { diff --git a/test/integration/dialects/sqlite/dao.test.js b/test/integration/dialects/sqlite/dao.test.js index 0361d21e64e6..354a343aff87 100644 --- a/test/integration/dialects/sqlite/dao.test.js +++ b/test/integration/dialects/sqlite/dao.test.js @@ -77,7 +77,7 @@ if (dialect === 'sqlite') { describe('json', () => { it('should be able to retrieve a row with json_extract function', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) ]).then(() => { @@ -91,7 +91,7 @@ if (dialect === 'sqlite') { }); it('should be able to retrieve a row by json_type function', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: ['kate', 'joe'] }) ]).then(() => { diff --git a/test/integration/hooks/associations.test.js b/test/integration/hooks/associations.test.js index 8cd58014182d..f175eefde0a9 100644 --- a/test/integration/hooks/associations.test.js +++ b/test/integration/hooks/associations.test.js @@ -3,8 +3,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise, DataTypes = require('../../../lib/data-types'), sinon = require('sinon'), dialect = Support.getTestDialect(); @@ -716,7 +714,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { return Promise.resolve(); }); - return Sequelize.Promise.all([ + return Promise.all([ this.Projects.create({ title: 'New Project' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) ]).then(([project, minitask]) => { @@ -772,7 +770,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { return Promise.resolve(); }); - return Sequelize.Promise.all([ + return Promise.all([ this.Projects.create({ title: 'New Project' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) ]).then(([project, minitask]) => { @@ -858,12 +856,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { return Promise.resolve(); }); - return Sequelize.Promise.all([ + return Promise.all([ this.Projects.create({ title: 'New Project' }), this.Tasks.create({ title: 'New Task' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) ]).then(([project, task, minitask]) => { - return Sequelize.Promise.all([ + return Promise.all([ task.addMiniTask(minitask), project.addTask(task) ]).then(() => project); @@ -913,12 +911,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterMiniTask = true; }); - return Sequelize.Promise.all([ + return Promise.all([ this.Projects.create({ title: 'New Project' }), this.Tasks.create({ title: 'New Task' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) ]).then(([project, task, minitask]) => { - return Sequelize.Promise.all([ + return Promise.all([ task.addMiniTask(minitask), project.addTask(task) ]).then(() => project); diff --git a/test/integration/hooks/bulkOperation.test.js b/test/integration/hooks/bulkOperation.test.js index c0700016db80..4a34d6078802 100644 --- a/test/integration/hooks/bulkOperation.test.js +++ b/test/integration/hooks/bulkOperation.test.js @@ -4,8 +4,7 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { beforeEach(function() { diff --git a/test/integration/hooks/create.test.js b/test/integration/hooks/create.test.js index a6c96d3dc3f0..f6645f3d9437 100644 --- a/test/integration/hooks/create.test.js +++ b/test/integration/hooks/create.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = Support.Sequelize, - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { beforeEach(function() { @@ -111,7 +110,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { A.belongsToMany(B, { through: 'a_b' }); return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ + return Promise.all([ A.create({ name: 'a' }), B.create({ name: 'b' }) ]).then(([a, b]) => { diff --git a/test/integration/hooks/hooks.test.js b/test/integration/hooks/hooks.test.js index 275a24ab80bc..db456d413d5e 100644 --- a/test/integration/hooks/hooks.test.js +++ b/test/integration/hooks/hooks.test.js @@ -6,8 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'), Sequelize = Support.Sequelize, dialect = Support.getTestDialect(), - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { beforeEach(function() { diff --git a/test/integration/include.test.js b/test/integration/include.test.js index 3829132051c4..f54a2507e34a 100755 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), Sequelize = require('../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('./support'), DataTypes = require('../../lib/data-types'), diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index d75edb06d596..efbd4c77fac1 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), Op = Sequelize.Op, - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), diff --git a/test/integration/include/findAndCountAll.test.js b/test/integration/include/findAndCountAll.test.js index 18b8cb975b9b..9da2f2a66ee4 100644 --- a/test/integration/include/findAndCountAll.test.js +++ b/test/integration/include/findAndCountAll.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), sinon = require('sinon'), Support = require('../support'), Op = Support.Sequelize.Op, - DataTypes = require('../../../lib/data-types'), - Promise = require('bluebird'); + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Include'), () => { before(function() { diff --git a/test/integration/include/findOne.test.js b/test/integration/include/findOne.test.js index 4129c3a0803a..5c315899d9f6 100644 --- a/test/integration/include/findOne.test.js +++ b/test/integration/include/findOne.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, DataTypes = require('../../../lib/data-types'), _ = require('lodash'); diff --git a/test/integration/include/limit.test.js b/test/integration/include/limit.test.js index 7f4a1c3397f3..d1e3e0d921d8 100644 --- a/test/integration/include/limit.test.js +++ b/test/integration/include/limit.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - Promise = Sequelize.Promise, Op = Sequelize.Op; describe(Support.getTestDialectTeaser('Include'), () => { diff --git a/test/integration/include/paranoid.test.js b/test/integration/include/paranoid.test.js index 0ae70c008f14..633a1bdd4ff5 100644 --- a/test/integration/include/paranoid.test.js +++ b/test/integration/include/paranoid.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), Support = require('../support'), - Sequelize = require('../../../index'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Paranoid'), () => { @@ -106,7 +105,7 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { X.hasMany(Y); return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ + return Promise.all([ X.create(), Y.create() ]); diff --git a/test/integration/include/schema.test.js b/test/integration/include/schema.test.js index 7588891a9d77..c0ceeb18e53c 100644 --- a/test/integration/include/schema.test.js +++ b/test/integration/include/schema.test.js @@ -6,7 +6,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - Promise = Sequelize.Promise, dialect = Support.getTestDialect(), _ = require('lodash'), promiseProps = require('p-props'); diff --git a/test/integration/include/separate.test.js b/test/integration/include/separate.test.js index fc5150e210e3..709aa3c36791 100755 --- a/test/integration/include/separate.test.js +++ b/test/integration/include/separate.test.js @@ -4,11 +4,9 @@ const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), Support = require('../support'), - Sequelize = require('../../../index'), DataTypes = require('../../../lib/data-types'), current = Support.sequelize, - dialect = Support.getTestDialect(), - Promise = Sequelize.Promise; + dialect = Support.getTestDialect(); if (current.dialect.supports.groupedLimit) { describe(Support.getTestDialectTeaser('Include'), () => { diff --git a/test/integration/instance.validations.test.js b/test/integration/instance.validations.test.js index 7fd178324ce9..192fbbc08638 100644 --- a/test/integration/instance.validations.test.js +++ b/test/integration/instance.validations.test.js @@ -701,7 +701,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Sequelize.Promise.all([ + return Promise.all([ expect(User.build({ password: 'short', salt: '42' diff --git a/test/integration/instance/decrement.test.js b/test/integration/instance/decrement.test.js index 525b6dfb490c..438fc8fafe06 100644 --- a/test/integration/instance/decrement.test.js +++ b/test/integration/instance/decrement.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), expect = chai.expect, - Sequelize = require('../../../index'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), sinon = require('sinon'), @@ -147,7 +146,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { it('should still work right with other concurrent increments', function() { return this.User.findByPk(1).then(user1 => { - return Sequelize.Promise.all([ + return Promise.all([ user1.decrement(['aNumber'], { by: 2 }), user1.decrement(['aNumber'], { by: 2 }), user1.decrement(['aNumber'], { by: 2 }) @@ -172,7 +171,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { it('with negative value', function() { return this.User.findByPk(1).then(user1 => { - return Sequelize.Promise.all([ + return Promise.all([ user1.decrement('aNumber', { by: -2 }), user1.decrement(['aNumber', 'bNumber'], { by: -2 }), user1.decrement({ 'aNumber': -1, 'bNumber': -2 }) diff --git a/test/integration/instance/increment.test.js b/test/integration/instance/increment.test.js index 87826728293f..18dd46a29978 100644 --- a/test/integration/instance/increment.test.js +++ b/test/integration/instance/increment.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), expect = chai.expect, - Sequelize = require('../../../index'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), sinon = require('sinon'), @@ -157,7 +156,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { it('should still work right with other concurrent increments', function() { return this.User.findByPk(1).then(user1 => { - return Sequelize.Promise.all([ + return Promise.all([ user1.increment(['aNumber'], { by: 2 }), user1.increment(['aNumber'], { by: 2 }), user1.increment(['aNumber'], { by: 2 }) diff --git a/test/integration/json.test.js b/test/integration/json.test.js index 9deb81e81ab8..c831bbf286b3 100644 --- a/test/integration/json.test.js +++ b/test/integration/json.test.js @@ -146,7 +146,7 @@ describe('model', () => { }); it('should be able to retrieve a row based on the values of the json document', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) ]).then(() => { @@ -160,7 +160,7 @@ describe('model', () => { }); it('should be able to query using the nested query language', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) ]).then(() => { @@ -173,7 +173,7 @@ describe('model', () => { }); it('should be able to query using dot notation', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) ]).then(() => { @@ -184,7 +184,7 @@ describe('model', () => { }); it('should be able to query using dot notation with uppercase name', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } }) ]).then(() => { @@ -198,7 +198,7 @@ describe('model', () => { }); it('should be able to query array using property accessor', function() { - return Sequelize.Promise.all([ + return Promise.all([ this.User.create({ username: 'swen', emergency_contact: ['kate', 'joe'] }), this.User.create({ username: 'anna', emergency_contact: [{ name: 'joe' }] }) ]).then(() => { diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 5a5af2bebdc2..6be7538e37b0 100755 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -6,10 +6,10 @@ const chai = require('chai'), Support = require('./support'), DataTypes = require('../../lib/data-types'), dialect = Support.getTestDialect(), + errors = require('../../lib/errors'), sinon = require('sinon'), _ = require('lodash'), moment = require('moment'), - Promise = require('bluebird'), current = Support.sequelize, Op = Sequelize.Op, semver = require('semver'), @@ -284,7 +284,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); await User.sync({ force: true }); - await Sequelize.Promise.all([ + await Promise.all([ User.create({ username: 'tobi', email: 'tobi@tobi.me' }), User.create({ username: 'tobi', email: 'tobi@tobi.me' }) ]).catch(err => { @@ -319,7 +319,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { email: { type: Sequelize.STRING, unique: 'user_and_email_index' } }); - await Sequelize.Promise.all([ + await Promise.all([ User.create({ user_id: 1, email: 'tobi@tobi.me' }), User.create({ user_id: 1, email: 'tobi@tobi.me' }) ]).catch(err => { @@ -2558,7 +2558,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.bulkCreate(data, { validate: true, individualHooks: true - })).to.be.rejectedWith(Promise.AggregateError); + })).to.be.rejectedWith(errors.AggregateError); }); }); diff --git a/test/integration/model/attributes.test.js b/test/integration/model/attributes.test.js index 95a7c1baf414..e5aa22ca8fb0 100644 --- a/test/integration/model/attributes.test.js +++ b/test/integration/model/attributes.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'); diff --git a/test/integration/model/attributes/field.test.js b/test/integration/model/attributes/field.test.js index fb32e2aba872..8292d823a56c 100644 --- a/test/integration/model/attributes/field.test.js +++ b/test/integration/model/attributes/field.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../../support'), DataTypes = require('../../../../lib/data-types'), diff --git a/test/integration/model/attributes/types.test.js b/test/integration/model/attributes/types.test.js index 82dc166a68d0..94541cbb3a4a 100644 --- a/test/integration/model/attributes/types.test.js +++ b/test/integration/model/attributes/types.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../../support'), dialect = Support.getTestDialect(); diff --git a/test/integration/model/bulk-create.test.js b/test/integration/model/bulk-create.test.js index 2c58355123f6..faabff6ffbdf 100644 --- a/test/integration/model/bulk-create.test.js +++ b/test/integration/model/bulk-create.test.js @@ -2,8 +2,8 @@ const chai = require('chai'), Sequelize = require('../../../index'), + AggregateError = require('../../../lib/errors/aggregate-error'), Op = Sequelize.Op, - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), @@ -321,13 +321,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { { name: 'foo', code: '123' }, { code: '1234' }, { name: 'bar', code: '1' } - ], { validate: true }).catch(errors => { + ], { validate: true }).catch(error => { const expectedValidationError = 'Validation len on code failed'; const expectedNotNullError = 'notNull Violation: Task.name cannot be null'; - expect(errors).to.be.instanceof(Promise.AggregateError); - expect(errors.toString()).to.include(expectedValidationError) + expect(error).to.be.instanceof(AggregateError); + expect(error.toString()).to.include(expectedValidationError) .and.to.include(expectedNotNullError); + const { errors } = error; expect(errors).to.have.length(2); const e0name0 = errors[0].errors.get('name')[0]; @@ -983,8 +984,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { .then(() => { expect.fail(); }, error => { - expect(error.length).to.equal(1); - expect(error[0].message).to.match(/.*always invalid.*/); + expect(error.errors.length).to.equal(1); + expect(error.errors[0].message).to.match(/.*always invalid.*/); }); }); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index ced6cff4f1dc..0c08969785c5 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index ad245780151e..2ed6d3cc4bfe 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -963,7 +963,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { _.forEach(r, (item, itemName) => { this[itemName] = item; }); - return Sequelize.Promise.all([ + return Promise.all([ this.england.setContinent(this.europe), this.england.addIndustry(this.coal), this.bob.setCountry(this.england), @@ -1050,18 +1050,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Kingdom.belongsToMany(this.Animal, { through: this.AnimalKingdom }); return this.sequelize.sync({ force: true }) - .then(() => Sequelize.Promise.all([ + .then(() => Promise.all([ this.Animal.create({ name: 'Dog', age: 20 }), this.Animal.create({ name: 'Cat', age: 30 }), this.Animal.create({ name: 'Peacock', age: 25 }), this.Animal.create({ name: 'Fish', age: 100 }) ])) - .then(([a1, a2, a3, a4]) => Sequelize.Promise.all([ + .then(([a1, a2, a3, a4]) => Promise.all([ this.Kingdom.create({ name: 'Earth' }), this.Kingdom.create({ name: 'Water' }), this.Kingdom.create({ name: 'Wind' }) ]).then(([k1, k2, k3]) => - Sequelize.Promise.all([ + Promise.all([ k1.addAnimals([a1, a2]), k2.addAnimals([a4]), k3.addAnimals([a3]) @@ -1154,7 +1154,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this[itemName] = item; }); - return Sequelize.Promise.all([ + return Promise.all([ this.england.setContinent(this.europe), this.france.setContinent(this.europe), this.korea.setContinent(this.asia), @@ -1174,7 +1174,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts simply', function() { - return Sequelize.Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(params => { + return Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(params => { return this.Continent.findAll({ order: [['name', params[0]]] }).then(continents => { @@ -1186,7 +1186,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts by 1st degree association', function() { - return Sequelize.Promise.all([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']].map(params => { + return Promise.all([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']].map(params => { return this.Continent.findAll({ include: [this.Country], order: [[this.Country, 'name', params[0]]] @@ -1202,7 +1202,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts simply and by 1st degree association with limit where 1st degree associated instances returned for second one and not the first', function() { - return Sequelize.Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(params => { + return Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, @@ -1230,7 +1230,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts by 2nd degree association', function() { - return Sequelize.Promise.all([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']].map(params => { + return Promise.all([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person] }], order: [[this.Country, this.Person, 'lastName', params[0]]] @@ -1249,7 +1249,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts by 2nd degree association with alias', function() { - return Sequelize.Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { + return Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[this.Country, { model: this.Person, as: 'residents' }, 'lastName', params[0]]] @@ -1268,7 +1268,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts by 2nd degree association with alias while using limit', function() { - return Sequelize.Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { + return Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { return this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[{ model: this.Country }, { model: this.Person, as: 'residents' }, 'lastName', params[0]]], @@ -1310,7 +1310,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this[itemName] = item; }); - return Sequelize.Promise.all([ + return Promise.all([ this.england.addIndustry(this.energy, { through: { numYears: 20 } }), this.england.addIndustry(this.media, { through: { numYears: 40 } }), this.france.addIndustry(this.media, { through: { numYears: 80 } }), @@ -1321,7 +1321,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('sorts by 1st degree association', function() { - return Sequelize.Promise.map([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']], params => { + return Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(params => { return this.Country.findAll({ include: [this.Industry], order: [[this.Industry, 'name', params[0]]] @@ -1333,11 +1333,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(countries[0].industries[0]).to.exist; expect(countries[0].industries[0].name).to.equal(params[2]); }); - }); + })); }); it('sorts by 1st degree association while using limit', function() { - return Sequelize.Promise.map([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']], params => { + return Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(params => { return this.Country.findAll({ include: [this.Industry], order: [ @@ -1352,11 +1352,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(countries[0].industries[0]).to.exist; expect(countries[0].industries[0].name).to.equal(params[2]); }); - }); + })); }); it('sorts by through table attribute', function() { - return Sequelize.Promise.map([['ASC', 'England', 'Energy'], ['DESC', 'France', 'Media']], params => { + return Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'France', 'Media']].map(params => { return this.Country.findAll({ include: [this.Industry], order: [[this.Industry, this.IndustryCountry, 'numYears', params[0]]] @@ -1368,7 +1368,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(countries[0].industries[0]).to.exist; expect(countries[0].industries[0].name).to.equal(params[2]); }); - }); + })); }); }); }); diff --git a/test/integration/model/findAll/groupedLimit.test.js b/test/integration/model/findAll/groupedLimit.test.js index ec6ce78ae8bc..558c273f1c26 100644 --- a/test/integration/model/findAll/groupedLimit.test.js +++ b/test/integration/model/findAll/groupedLimit.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../../support'), Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, DataTypes = require('../../../../lib/data-types'), current = Support.sequelize, _ = require('lodash'); @@ -182,10 +181,10 @@ if (current.dialect.supports['UNION ALL']) { expect(users).to.have.length(5); expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); - return Sequelize.Promise.join( + return Promise.all([ this.projects[0].setParanoidMembers(users.slice(0, 2)), this.projects[1].setParanoidMembers(users.slice(4)) - ); + ]); }).then(() => { return this.User.findAll({ attributes: ['id'], diff --git a/test/integration/model/findAll/separate.test.js b/test/integration/model/findAll/separate.test.js index 15f5a2fe60fa..8ea9b50c625c 100644 --- a/test/integration/model/findAll/separate.test.js +++ b/test/integration/model/findAll/separate.test.js @@ -4,7 +4,6 @@ const chai = require('chai'); const expect = chai.expect; const Support = require('../../support'); const DataTypes = require('../../../../lib/data-types'); -const Sequelize = require('../../../../lib/sequelize'); const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { @@ -24,13 +23,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { LevelThree.belongsTo(LevelTwo); return current.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ + return Promise.all([ Project.create({ name: 'testProject' }), LevelTwo.create({ name: 'testL21' }), LevelTwo.create({ name: 'testL22' }) ]); }).then(([project, level21, level22]) => { - return Sequelize.Promise.all([ + return Promise.all([ project.addLevelTwo(level21), project.addLevelTwo(level22) ]); diff --git a/test/integration/model/findOne.test.js b/test/integration/model/findOne.test.js index 86e9444c54d0..57e253769c06 100644 --- a/test/integration/model/findOne.test.js +++ b/test/integration/model/findOne.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), dialect = Support.getTestDialect(), @@ -250,7 +249,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { let count = 0; return this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]).then(() => { - return Sequelize.Promise.all(permutations.map(perm => { + return Promise.all(permutations.map(perm => { return this.User.findByPk(perm, { logging(s) { expect(s).to.include(0); diff --git a/test/integration/model/increment.test.js b/test/integration/model/increment.test.js index 1f986bb4085e..a43abb0debf2 100644 --- a/test/integration/model/increment.test.js +++ b/test/integration/model/increment.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = Support.Sequelize, DataTypes = require('../../../lib/data-types'), sinon = require('sinon'); @@ -77,7 +76,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should still work right with other concurrent increments', function() { return this.User.findAll().then(aUsers => { - return Sequelize.Promise.all([ + return Promise.all([ this.User[method](['aNumber'], { by: 2, where: {} }), this.User[method](['aNumber'], { by: 2, where: {} }), this.User[method](['aNumber'], { by: 2, where: {} }) diff --git a/test/integration/model/json.test.js b/test/integration/model/json.test.js index df2c6a2e98ae..0f712c9c86cb 100644 --- a/test/integration/model/json.test.js +++ b/test/integration/model/json.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), Op = Sequelize.Op, - Promise = Sequelize.Promise, moment = require('moment'), expect = chai.expect, Support = require('../support'), diff --git a/test/integration/model/schema.test.js b/test/integration/model/schema.test.js index 5bfbb040e120..80e6577166b0 100644 --- a/test/integration/model/schema.test.js +++ b/test/integration/model/schema.test.js @@ -6,8 +6,7 @@ const chai = require('chai'), dialect = Support.getTestDialect(), DataTypes = require('../../../lib/data-types'), current = Support.sequelize, - Op = Support.Sequelize.Op, - Promise = Support.Sequelize.Promise; + Op = Support.Sequelize.Op; const SCHEMA_ONE = 'schema_one'; const SCHEMA_TWO = 'schema_two'; diff --git a/test/integration/model/scope/aggregate.test.js b/test/integration/model/scope/aggregate.test.js index 7e6aa2100ea3..97fb1146ae88 100644 --- a/test/integration/model/scope/aggregate.test.js +++ b/test/integration/model/scope/aggregate.test.js @@ -4,8 +4,7 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Op = Sequelize.Op, expect = chai.expect, - Support = require('../../support'), - Promise = require('../../../../lib/promise'); + Support = require('../../support'); describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { diff --git a/test/integration/model/scope/associations.test.js b/test/integration/model/scope/associations.test.js index bdc3960b5513..fc32523d73f3 100644 --- a/test/integration/model/scope/associations.test.js +++ b/test/integration/model/scope/associations.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Op = Sequelize.Op, expect = chai.expect, - Promise = Sequelize.Promise, Support = require('../../support'); describe(Support.getTestDialectTeaser('Model'), () => { diff --git a/test/integration/model/scope/count.test.js b/test/integration/model/scope/count.test.js index 8cba88c92f77..a869b703ddcb 100644 --- a/test/integration/model/scope/count.test.js +++ b/test/integration/model/scope/count.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Op = Sequelize.Op, expect = chai.expect, - Promise = require('../../../../lib/promise'), Support = require('../../support'); describe(Support.getTestDialectTeaser('Model'), () => { diff --git a/test/integration/model/scope/merge.test.js b/test/integration/model/scope/merge.test.js index d19c814c6348..5fe36704a82b 100644 --- a/test/integration/model/scope/merge.test.js +++ b/test/integration/model/scope/merge.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../../support'), combinatorics = require('js-combinatorics'); diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index 597fcd4afdb5..11d10d486ddf 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), diff --git a/test/integration/pool.test.js b/test/integration/pool.test.js index a20416f763e1..0bd4bdfa1202 100644 --- a/test/integration/pool.test.js +++ b/test/integration/pool.test.js @@ -190,7 +190,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { }); this.sinon.stub(this.testInstance.connectionManager, '_connect') - .returns(new Sequelize.Promise(() => {})); + .returns(new Promise(() => {})); await expect( this.testInstance.authenticate() @@ -208,7 +208,7 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { }); this.sinon.stub(this.testInstance.connectionManager, '_connect') - .returns(new Sequelize.Promise(() => {})); + .returns(new Promise(() => {})); await expect( this.testInstance.transaction(async () => { diff --git a/test/integration/sequelize.transaction.test.js b/test/integration/sequelize.transaction.test.js index 7127dbe43433..ddc55461e2e5 100644 --- a/test/integration/sequelize.transaction.test.js +++ b/test/integration/sequelize.transaction.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('./support'), - Promise = require('../../lib/promise'), Transaction = require('../../lib/transaction'), current = Support.sequelize, delay = require('delay'); diff --git a/test/integration/timezone.test.js b/test/integration/timezone.test.js index c7bb83aee87c..92f9f11491d3 100644 --- a/test/integration/timezone.test.js +++ b/test/integration/timezone.test.js @@ -3,9 +3,7 @@ const chai = require('chai'), expect = chai.expect, Support = require('./support'), - dialect = Support.getTestDialect(), - Sequelize = require('../../index'), - Promise = Sequelize.Promise; + dialect = Support.getTestDialect(); if (dialect !== 'sqlite') { // Sqlite does not support setting timezone diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index f3a78bd3f68c..78cd334b3d92 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Support = require('./support'), dialect = Support.getTestDialect(), Sequelize = require('../../index'), - Promise = Sequelize.Promise, QueryTypes = require('../../lib/query-types'), Transaction = require('../../lib/transaction'), sinon = require('sinon'), @@ -169,7 +168,7 @@ if (current.dialect.supports.transactions) { it('should not rollback if connection was not acquired', function() { this.sinon.stub(this.sequelize.connectionManager, '_connect') - .returns(new Sequelize.Promise(() => {})); + .returns(new Promise(() => {})); const transaction = new Transaction(this.sequelize); diff --git a/test/support.js b/test/support.js index 87a9b16f181c..80788f51d9ae 100644 --- a/test/support.js +++ b/test/support.js @@ -8,9 +8,6 @@ const Config = require('./config/config'); const chai = require('chai'); const expect = chai.expect; const AbstractQueryGenerator = require('../lib/dialects/abstract/query-generator'); -const sinon = require('sinon'); - -sinon.usingPromise(require('bluebird')); chai.use(require('chai-spies')); chai.use(require('chai-datetime')); @@ -24,15 +21,53 @@ process.on('uncaughtException', e => { console.error('An unhandled exception occurred:'); throw e; }); -Sequelize.Promise.onPossiblyUnhandledRejection(e => { + +let onNextUnhandledRejection = null; +let unhandledRejections = null; + +process.on('unhandledRejection', e => { + if (unhandledRejections) { + unhandledRejections.push(e); + } + const onNext = onNextUnhandledRejection; + if (onNext) { + onNextUnhandledRejection = null; + onNext(e); + } + if (onNext || unhandledRejections) return; console.error('An unhandled rejection occurred:'); throw e; }); -Sequelize.Promise.longStackTraces(); + +afterEach(() => { + onNextUnhandledRejection = null; + unhandledRejections = null; +}); const Support = { Sequelize, + /** + * Returns a Promise that will reject with the next unhandled rejection that occurs + * during this test (instead of failing the test) + */ + nextUnhandledRejection() { + return new Promise((resolve, reject) => onNextUnhandledRejection = reject); + }, + + /** + * Pushes all unhandled rejections that occur during this test onto destArray + * (instead of failing the test). + * + * @param {Error[]} destArray the array to push unhandled rejections onto. If you omit this, + * one will be created and returned for you. + * + * @returns {Error[]} destArray + */ + captureUnhandledRejections(destArray = []) { + return unhandledRejections = destArray; + }, + prepareTransactionTest(sequelize) { const dialect = Support.getTestDialect(); @@ -46,7 +81,7 @@ const Support = { return _sequelize.sync({ force: true }).then(() => _sequelize); } - return Sequelize.Promise.resolve(sequelize); + return Promise.resolve(sequelize); }, createSequelizeInstance(options) { diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js index e17a52e2df9d..334ae27b023b 100644 --- a/test/unit/associations/has-many.test.js +++ b/test/unit/associations/has-many.test.js @@ -6,7 +6,6 @@ const chai = require('chai'), stub = sinon.stub, _ = require('lodash'), Support = require('../support'), - Promise = Support.Sequelize.Promise, DataTypes = require('../../../lib/data-types'), HasMany = require('../../../lib/associations/has-many'), Op = require('../../../lib/operators'), diff --git a/test/unit/errors.test.js b/test/unit/errors.test.js index 9e84edb3a54e..b8a7c1e12186 100644 --- a/test/unit/errors.test.js +++ b/test/unit/errors.test.js @@ -52,4 +52,31 @@ describe('errors', () => { expect(stackParts[1]).to.match(/^ {4}at throwError \(.*errors.test.js:\d+:\d+\)$/); }); }); + + describe('AggregateError', () => { + it('get .message works', () => { + const { AggregateError } = errors; + expect(String( + new AggregateError([ + new Error('foo'), + new Error('bar\nbaz'), + new AggregateError([ + new Error('this\nis\na\ntest'), + new Error('qux') + ]) + ]) + )).to.equal( + `AggregateError of: + Error: foo + Error: bar + baz + AggregateError of: + Error: this + is + a + test + Error: qux +`); + }); + }); }); diff --git a/test/unit/hooks.test.js b/test/unit/hooks.test.js index 5e5d60ab466e..a05d3e22d1d4 100644 --- a/test/unit/hooks.test.js +++ b/test/unit/hooks.test.js @@ -3,8 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), expect = chai.expect, - Sequelize = require('../../index'), - Promise = Sequelize.Promise, Support = require('./support'), _ = require('lodash'), current = Support.sequelize; @@ -353,7 +351,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('promises', () => { it('can return a promise', function() { this.Model.beforeBulkCreate(() => { - return Sequelize.Promise.resolve(); + return Promise.resolve(); }); return expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; diff --git a/test/unit/instance/set.test.js b/test/unit/instance/set.test.js index 80d952c3a65b..7bde622f52c1 100644 --- a/test/unit/instance/set.test.js +++ b/test/unit/instance/set.test.js @@ -3,8 +3,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise, DataTypes = require('../../../lib/data-types'), current = Support.sequelize, sinon = require('sinon'); diff --git a/test/unit/model/find-and-count-all.test.js b/test/unit/model/find-and-count-all.test.js index 2c53fffddf69..e89203af5730 100644 --- a/test/unit/model/find-and-count-all.test.js +++ b/test/unit/model/find-and-count-all.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), current = Support.sequelize, sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'), - Promise = require('bluebird').getNewLibraryCopy(); + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { describe('findAndCountAll', () => { @@ -14,9 +13,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { before(function() { this.stub = sinon.stub(); - Promise.onPossiblyUnhandledRejection(() => { - this.stub(); - }); + process.on('unhandledRejection', this.stub); this.User = current.define('User', { username: DataTypes.STRING, diff --git a/test/unit/model/find-create-find.test.js b/test/unit/model/find-create-find.test.js index 907e22dfab4f..4099601f6828 100644 --- a/test/unit/model/find-create-find.test.js +++ b/test/unit/model/find-create-find.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), UniqueConstraintError = require('../../../lib/errors').UniqueConstraintError, current = Support.sequelize, - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Model'), () => { describe('findCreateFind', () => { @@ -14,7 +13,6 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.sinon = sinon.createSandbox(); - this.sinon.usingPromise(Promise); }); afterEach(function() { diff --git a/test/unit/model/validation.test.js b/test/unit/model/validation.test.js index 4c941824806a..5caf5f778c87 100644 --- a/test/unit/model/validation.test.js +++ b/test/unit/model/validation.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), sinon = require('sinon'), expect = chai.expect, Sequelize = require('../../../index'), - Promise = Sequelize.Promise, Op = Sequelize.Op, Support = require('../support'), current = Support.sequelize, @@ -273,7 +272,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').callsFake(() => new Promise.resolve([User.build({}), 1])); + this.stub = sinon.stub(current, 'query').callsFake(() => Promise.resolve([User.build({}), 1])); }); after(function() { diff --git a/test/unit/promise.test.js b/test/unit/promise.test.js deleted file mode 100644 index effbd76b70eb..000000000000 --- a/test/unit/promise.test.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, - Bluebird = require('bluebird'); - -describe('Promise', () => { - it('should be an independent copy of bluebird library', () => { - expect(Promise.prototype.then).to.be.a('function'); - expect(Promise).to.not.equal(Bluebird); - expect(Promise.prototype).to.not.equal(Bluebird.prototype); - }); -}); diff --git a/test/unit/transaction.test.js b/test/unit/transaction.test.js index 78e59a9609fa..ab23a3c9296d 100644 --- a/test/unit/transaction.test.js +++ b/test/unit/transaction.test.js @@ -47,7 +47,7 @@ describe('Transaction', () => { }; return current.transaction(() => { expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - return Sequelize.Promise.resolve(); + return Promise.resolve(); }); }); @@ -71,7 +71,7 @@ describe('Transaction', () => { }; return current.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED }, () => { expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - return Sequelize.Promise.resolve(); + return Promise.resolve(); }); }); }); diff --git a/types/lib/errors.d.ts b/types/lib/errors.d.ts index ff5e9151b226..d8e2a7c599e9 100644 --- a/types/lib/errors.d.ts +++ b/types/lib/errors.d.ts @@ -175,3 +175,20 @@ export class InvalidConnectionError extends ConnectionError {} * Thrown when a connection to a database times out */ export class ConnectionTimedOutError extends ConnectionError {} + +/** + * Thrown when queued operations were aborted because a connection was closed + */ +export class AsyncQueueError extends BaseError {} + +export class AggregateError extends BaseError { + /** + * AggregateError. A wrapper for multiple errors. + * + * @param {Error[]} errors The aggregated errors that occurred + */ + constructor(errors: Error[]); + + /** the aggregated errors that occurred */ + public readonly errors: Error[]; +} From 7610345e577e5e6692f536a0424866259d650c22 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 25 Apr 2020 17:24:52 +0530 Subject: [PATCH 112/414] docs(v6-guide): bluebird removal API changes --- docs/manual/other-topics/upgrade-to-v6.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index a7217d3186df..3ee554b00b2e 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -20,7 +20,9 @@ You should now use [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) packag Sequelize.useCLS(namespace); ``` -[Bluebird now supports `async_hooks`](https://github.com/petkaantonov/bluebird/issues/1403). This configuration will automatically be enabled when invoking `Sequelize.useCLS`. This way, using [`cls-bluebird`](https://www.npmjs.com/package/cls-bluebird) is no longer necessary. +### Promise + +Bluebird has been removed. Public API now returns native promises. `Sequelize.Promise` is no longer available. ### Model @@ -46,6 +48,10 @@ This method now tests for equality with [`_.isEqual`](https://lodash.com/docs/4. await instance.save(); // will save ``` +#### `Model.bulkCreate()` + +This method now throws `Sequelize.AggregateError` instead of `Bluebird.AggregateError`. All errors are now exposed as `errors` key. + ## Changelog ### 6.0.0-beta.5 From 5a807c514c00364d0fcd56ae682a06750c398df8 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 25 Apr 2020 09:05:12 -0700 Subject: [PATCH 113/414] docs: remove remaining bluebird references (#12167) --- CONTRIBUTING.md | 2 +- docs/manual/core-concepts/getting-started.md | 6 +++--- docs/manual/other-topics/typescript.md | 5 +---- package-lock.json | 6 ------ package.json | 1 - types/index.d.ts | 1 - types/lib/associations/belongs-to-many.d.ts | 4 +--- types/lib/associations/belongs-to.d.ts | 1 - types/lib/associations/has-many.d.ts | 1 - types/lib/associations/has-one.d.ts | 1 - types/lib/connection-manager.d.ts | 2 -- types/lib/model.d.ts | 3 +-- types/lib/promise.d.ts | 6 ------ types/lib/query-interface.d.ts | 1 - types/lib/sequelize.d.ts | 8 +------- types/lib/transaction.d.ts | 1 - types/lib/utils.d.ts | 2 -- types/test/count.ts | 2 +- types/test/e2e/docs-example.ts | 2 -- types/test/promise.ts | 6 ------ 20 files changed, 9 insertions(+), 52 deletions(-) delete mode 100644 types/lib/promise.d.ts delete mode 100644 types/test/promise.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b9f8541ca76..fb1ef24ce34a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ We're glad to get pull request if any functionality is missing or something is b * Make sure that all existing tests pass * Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md#coding-guidelines) * Add some tests for your new functionality or a test exhibiting the bug you are solving. Ideally all new tests should not pass _without_ your changes. - - Use [promise style](http://bluebirdjs.com/docs/why-promises.html) in all new tests. Specifically this means: + - Use [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) in all new tests. Specifically this means: - don't use `EventEmitter`, `QueryChainer` or the `success`, `done` and `error` events - don't use a done callback in your test, just return the promise chain. - Small bugfixes and direct backports to the 4.x branch are accepted without tests. diff --git a/docs/manual/core-concepts/getting-started.md b/docs/manual/core-concepts/getting-started.md index eaff1c48a0d3..46a9760255d3 100644 --- a/docs/manual/core-concepts/getting-started.md +++ b/docs/manual/core-concepts/getting-started.md @@ -104,8 +104,8 @@ const sequelize = new Sequelize('sqlite::memory:', { }); ``` -## Bluebird Promises and async/await +## Promises and async/await -Most of the methods provided by Sequelize are asynchronous and therefore return Promises. They are all [Bluebird](http://bluebirdjs.com) Promises, so you can use the rich Bluebird API (for example, using `finally`, `tap`, `tapCatch`, `map`, `mapSeries`, etc) out of the box. You can access the Bluebird constructor used internally by Sequelize with `Sequelize.Promise`, if you want to set any Bluebird specific options. +Most of the methods provided by Sequelize are asynchronous and therefore return Promises. They are all [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) , so you can use the Promise API (for example, using `then`, `catch`, `finally`) out of the box. -Of course, using `async` and `await` works normally as well. \ No newline at end of file +Of course, using `async` and `await` works normally as well. diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index c478b9ac95d3..7a40468f9729 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -8,9 +8,8 @@ As Sequelize heavily relies on runtime property assignments, TypeScript won't be In order to avoid installation bloat for non TS users, you must install the following typing packages manually: -- `@types/node` (this is universally required) +- `@types/node` (this is universally required in node projects) - `@types/validator` -- `@types/bluebird` ## Usage @@ -129,8 +128,6 @@ Address.belongsTo(User, {targetKey: 'id'}); User.hasOne(Address,{sourceKey: 'id'}); async function stuff() { - // Please note that when using async/await you lose the `bluebird` promise context - // and you fall back to native const newUser = await User.create({ name: 'Johnny', preferredName: 'John', diff --git a/package-lock.json b/package-lock.json index 43865331c0a1..cd54ad20ba28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1146,12 +1146,6 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, - "@types/bluebird": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.30.tgz", - "integrity": "sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw==", - "dev": true - }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", diff --git a/package.json b/package.json index 9caa4c457294..2266233994cf 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "devDependencies": { "@commitlint/cli": "^8.3.5", "@commitlint/config-angular": "^8.3.4", - "@types/bluebird": "^3.5.26", "@types/node": "^12.12.37", "@types/validator": "^10.11.0", "acorn": "^7.1.1", diff --git a/types/index.d.ts b/types/index.d.ts index 7bcd710bc997..6bd346a8af13 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -15,6 +15,5 @@ export * from './lib/associations/index'; export * from './lib/errors'; export { BaseError as Error } from './lib/errors'; export { useInflection } from './lib/utils'; -export { Promise } from './lib/promise'; export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable }; export { Validator as validator } from './lib/utils/validator-extras'; diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index 1a4a15a30123..2b642d62043f 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -11,8 +11,6 @@ import { Transactionable, WhereOptions, } from '../model'; -import { Promise } from '../promise'; -import { Transaction } from '../transaction'; import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, MultiAssociationAccessors } from './base'; /** @@ -21,7 +19,7 @@ import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, Mu export interface ThroughOptions { /** * The model used to join both sides of the N:M association. - * Can be a string if you want the model to be generated by sequelize. + * Can be a string if you want the model to be generated by sequelize. */ model: typeof Model | string; diff --git a/types/lib/associations/belongs-to.d.ts b/types/lib/associations/belongs-to.d.ts index 26c87aab4e2e..a88f33221703 100644 --- a/types/lib/associations/belongs-to.d.ts +++ b/types/lib/associations/belongs-to.d.ts @@ -1,6 +1,5 @@ import { DataType } from '../data-types'; import { CreateOptions, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; -import { Promise } from '../promise'; import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; // type ModelCtor = InstanceType; diff --git a/types/lib/associations/has-many.d.ts b/types/lib/associations/has-many.d.ts index c0baa9d58313..bbe68276d509 100644 --- a/types/lib/associations/has-many.d.ts +++ b/types/lib/associations/has-many.d.ts @@ -8,7 +8,6 @@ import { ModelCtor, Transactionable, } from '../model'; -import { Promise } from '../promise'; import { Association, ManyToManyOptions, MultiAssociationAccessors } from './base'; /** diff --git a/types/lib/associations/has-one.d.ts b/types/lib/associations/has-one.d.ts index 4711f8e9bfb4..07c846f36bab 100644 --- a/types/lib/associations/has-one.d.ts +++ b/types/lib/associations/has-one.d.ts @@ -1,6 +1,5 @@ import { DataType } from '../data-types'; import { CreateOptions, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; -import { Promise } from '../promise'; import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; /** diff --git a/types/lib/connection-manager.d.ts b/types/lib/connection-manager.d.ts index 071bb32a6fd8..8bb7674918b9 100644 --- a/types/lib/connection-manager.d.ts +++ b/types/lib/connection-manager.d.ts @@ -1,5 +1,3 @@ -import { Promise } from './promise'; - export interface GetConnectionOptions { /** * Set which replica to use. Available options are `read` and `write` diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 54991a3cf3b5..efca8bd72db8 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -4,9 +4,8 @@ import { DataType } from './data-types'; import { Deferrable } from './deferrable'; import { HookReturn, Hooks, ModelHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; -import { Promise } from './promise'; import { QueryOptions, IndexesOptions } from './query-interface'; -import { Config, Options, Sequelize, SyncOptions } from './sequelize'; +import { Sequelize, SyncOptions } from './sequelize'; import { Transaction, LOCK } from './transaction'; import { Col, Fn, Literal, Where } from './utils'; import Op = require('./operators'); diff --git a/types/lib/promise.d.ts b/types/lib/promise.d.ts deleted file mode 100644 index 2ee910deb9e2..000000000000 --- a/types/lib/promise.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// tslint:disable-next-line:no-implicit-dependencies -import * as Bluebird from 'bluebird'; - -export const Promise: typeof Bluebird; -export type Promise = Bluebird; -export default Promise; diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 4c1852e7b4b1..407b5074838d 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -1,6 +1,5 @@ import { DataType } from './data-types'; import { Logging, Model, ModelAttributeColumnOptions, ModelAttributes, Transactionable, WhereOptions, Filterable, Poolable } from './model'; -import { Promise } from './promise'; import QueryTypes = require('./query-types'); import { Sequelize, RetryOptions } from './sequelize'; import { Transaction } from './transaction'; diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 2fa0dc4a2a97..22f1c660ce43 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1,5 +1,4 @@ import * as DataTypes from './data-types'; -import * as Deferrable from './deferrable'; import { HookReturn, Hooks, SequelizeHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; import { @@ -23,15 +22,10 @@ import { Hookable, } from './model'; import { ModelManager } from './model-manager'; -import * as Op from './operators'; -import { Promise } from './promise'; import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType } from './query-interface'; import QueryTypes = require('./query-types'); import { Transaction, TransactionOptions } from './transaction'; import { Cast, Col, Fn, Json, Literal, Where } from './utils'; -// tslint:disable-next-line:no-duplicate-imports -import * as Utils from './utils'; -import { validator } from './utils/validator-extras'; import { ConnectionManager } from './connection-manager'; /** @@ -777,7 +771,7 @@ export class Sequelize extends Hooks { /** * Use CLS with Sequelize. * CLS namespace provided is stored as `Sequelize._cls` - * and bluebird Promise is patched to use the namespace, using `cls-bluebird` module. + * and Promise is patched to use the namespace, using `cls-hooked` module. * * @param namespace */ diff --git a/types/lib/transaction.d.ts b/types/lib/transaction.d.ts index 73ac6f703ef6..5fc40a3c6491 100644 --- a/types/lib/transaction.d.ts +++ b/types/lib/transaction.d.ts @@ -1,6 +1,5 @@ import { Deferrable } from './deferrable'; import { Logging } from './model'; -import { Promise } from './promise'; import { Sequelize } from './sequelize'; /** diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts index 7c8169befd8c..468162fad0a3 100644 --- a/types/lib/utils.d.ts +++ b/types/lib/utils.d.ts @@ -115,5 +115,3 @@ export class Where extends SequelizeMethod { constructor(attr: object, comparator: string, logic: string | object); constructor(attr: object, logic: string | object); } - -export { Promise } from './promise'; diff --git a/types/test/count.ts b/types/test/count.ts index 2859203d1683..0892c3a7e402 100644 --- a/types/test/count.ts +++ b/types/test/count.ts @@ -1,4 +1,4 @@ -import { Model, Promise, Op } from 'sequelize'; +import { Model, Op } from 'sequelize'; class MyModel extends Model {} diff --git a/types/test/e2e/docs-example.ts b/types/test/e2e/docs-example.ts index a8427e8fccd9..9619ffdcc238 100644 --- a/types/test/e2e/docs-example.ts +++ b/types/test/e2e/docs-example.ts @@ -120,8 +120,6 @@ Address.belongsTo(User, {targetKey: 'id'}); User.hasOne(Address,{sourceKey: 'id'}); async function stuff() { - // Please note that when using async/await you lose the `bluebird` promise context - // and you fall back to native const newUser = await User.create({ name: 'Johnny', preferredName: 'John', diff --git a/types/test/promise.ts b/types/test/promise.ts deleted file mode 100644 index 82f8b0da9921..000000000000 --- a/types/test/promise.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Promise } from 'sequelize'; - -let promise: Promise = Promise.resolve(1); -promise.then((arg: number) => ({})).then((a: {}) => void 0); - -promise = new Promise(resolve => resolve()); From 0f4a8d9731006258851511ffd52017d66c1d428b Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 25 Apr 2020 10:53:33 -0700 Subject: [PATCH 114/414] build: remove unused dev dependencies (#12169) --- .eslintrc.json | 6 +- package-lock.json | 5822 +++++++++------------------ package.json | 4 - test/integration/data-types.test.js | 3 +- test/support.js | 5 +- 5 files changed, 1963 insertions(+), 3877 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 38854c60ba32..d7307d5ef4e9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -30,7 +30,6 @@ "new-cap": [ "error", { - "capIsNewExceptionPattern": "^BigInt", "properties": false } ], @@ -105,13 +104,14 @@ } }, "parserOptions": { - "ecmaVersion": 2018, + "ecmaVersion": 2020, "sourceType": "script" }, "plugins": ["mocha", "jsdoc"], "env": { "node": true, "mocha": true, - "es6": true + "es6": true, + "es2020": true } } diff --git a/package-lock.json b/package-lock.json index cd54ad20ba28..44f6d8c634e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -674,12 +674,12 @@ } }, "@octokit/plugin-paginate-rest": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.1.0.tgz", - "integrity": "sha512-7+/7urDH8cy6DmTwkewysf7/Or9dFtwZK7aQOc/IImjyeHJy+C8CEKOPo7L5Qb+66HyAr/4p/zV76LMVMuiRtA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.0.tgz", + "integrity": "sha512-KoNxC3PLNar8UJwR+1VMQOw2IoOrrFdo5YOiDKnBhpVbKpw+zkBKNMNKwM44UWL25Vkn0Sl3nYIEGKY+gW5ebw==", "dev": true, "requires": { - "@octokit/types": "^2.9.0" + "@octokit/types": "^2.12.1" } }, "@octokit/plugin-request-log": { @@ -726,13 +726,13 @@ } }, "@octokit/rest": { - "version": "17.5.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.5.2.tgz", - "integrity": "sha512-ceTWIkTmZMOCeFbpWyZz0vMSnSxWFm/g2BF8bqe47RkTFDsE2t9FnHV6qQKOWDTOXKe5KybQcLXyJQhFIJLweg==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.6.0.tgz", + "integrity": "sha512-knh+4hPBA26AMXflFRupTPT3u9NcQmQzeBJl4Gcuf14Gn7dUh6Loc1ICWF0Pz18A6ElFZQt+wB9tFINSruIa+g==", "dev": true, "requires": { "@octokit/core": "^2.4.3", - "@octokit/plugin-paginate-rest": "^2.1.0", + "@octokit/plugin-paginate-rest": "^2.2.0", "@octokit/plugin-request-log": "^1.0.0", "@octokit/plugin-rest-endpoint-methods": "3.8.0" } @@ -1217,10 +1217,11 @@ "dev": true }, "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, + "optional": true }, "acorn-globals": { "version": "1.0.9", @@ -1230,15 +1231,6 @@ "optional": true, "requires": { "acorn": "^2.1.0" - }, - "dependencies": { - "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true - } } }, "acorn-jsx": { @@ -1281,6 +1273,15 @@ "debug": "4" } }, + "agentkeepalive": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -1311,6 +1312,15 @@ "uri-js": "^4.2.2" } }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -1337,8 +1347,7 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { "version": "3.2.1", @@ -1355,6 +1364,12 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, + "ansistyles": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", + "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", + "dev": true + }, "any-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", @@ -1463,11 +1478,16 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1475,8 +1495,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -1698,7 +1717,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -1709,18 +1727,25 @@ "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", "dev": true }, - "big-integer": { - "version": "1.6.48", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", - "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", - "dev": true - }, "big-number": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/big-number/-/big-number-1.0.0.tgz", "integrity": "sha512-cHUzdT+mMXd1ozht8n5ZwBlNiPO/4zCqqkyp3lF1TMPsRJLXUbQ7cKnfXRkrW475H5SOtSOP0HFeihNbpa53MQ==", "dev": true }, + "bin-links": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.7.tgz", + "integrity": "sha512-/eaLaTu7G7/o7PV04QPy1HRT65zf+1tFkPGv0sPTV0tRwufooYBQO3zrcyGgm+ja+ZtBf2GEuKjDRJ2pPG+yqA==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0" + } + }, "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", @@ -1737,6 +1762,12 @@ "safe-buffer": "^5.1.1" } }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1749,6 +1780,21 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "dev": true }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1786,6 +1832,12 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", "dev": true }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "dev": true + }, "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -1798,6 +1850,46 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", + "dev": true + }, + "byte-size": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", + "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", + "dev": true + }, + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -1810,6 +1902,12 @@ "write-file-atomic": "^3.0.0" } }, + "call-limit": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz", + "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==", + "dev": true + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -1845,8 +1943,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "camelcase-keys": { "version": "4.2.0", @@ -1859,6 +1956,12 @@ "quick-lru": "^1.0.0" } }, + "capture-stack-trace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "dev": true + }, "cardinal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", @@ -1907,12 +2010,6 @@ "chai": ">1.9.0" } }, - "chai-spies": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.0.0.tgz", - "integrity": "sha512-elF2ZUczBsFoP07qCfMO/zeggs8pqCf3fZGyK5+2X4AndS8jycZYID91ztD9oQ7d/0tnS963dPkd0frQEThDsg==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1986,12 +2083,36 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "cidr-regex": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz", + "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "cli-columns": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz", + "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -2010,6 +2131,16 @@ "colors": "1.0.3" } }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -2073,7 +2204,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, "requires": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0", @@ -2128,11 +2258,20 @@ } } }, + "cmd-shim": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz", + "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "color-convert": { "version": "1.9.3", @@ -2152,8 +2291,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.0.3", @@ -2161,6 +2299,15 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, + "columnify": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", + "dev": true, + "requires": { + "wcwidth": "^1.0.0" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2216,6 +2363,38 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "xdg-basedir": "^3.0.0" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2292,6 +2471,19 @@ "safe-buffer": "~5.1.1" } }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "run-queue": "^1.0.0" + } + }, "core-js": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", @@ -2301,8 +2493,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -2334,6 +2525,15 @@ } } }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "cross-env": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", @@ -2404,6 +2604,12 @@ "array-find-index": "^1.0.1" } }, + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + }, "dargs": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", @@ -2417,7 +2623,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -2448,11 +2653,16 @@ "ms": "^2.1.1" } }, + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "dev": true + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decamelize-keys": { "version": "1.1.0", @@ -2472,6 +2682,12 @@ } } }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -2516,6 +2732,12 @@ } } }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2544,8 +2766,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -2586,6 +2807,22 @@ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2662,6 +2899,12 @@ "is-obj": "^1.0.0" } }, + "dotenv": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", + "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", + "dev": true + }, "dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", @@ -2780,15 +3023,12 @@ "json-stable-stringify": "^1.0.1", "strip-json-comments": "^2.0.1", "tslint": "5.14.0", - "typescript": "^3.9.0-dev.20200422", "yargs": "^15.1.0" }, "dependencies": { "typescript": { - "version": "3.9.0-dev.20200422", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200422.tgz", - "integrity": "sha512-zfFmKSi+oqoPl/wYc12gGFHw5KtZ8hhMNTHrspoTPUJ9iFvienUQWyWUua0tgwsmIfR4yytLnrkzKdxs/NLwGw==", - "dev": true + "version": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200425.tgz", + "integrity": "sha512-YjP3XoXMSJvza4COs+jXEu1ctdG8+vnieJvv6xJB5iFJIRpzSIF0YBTLn0TOu4oDZe9LyTtjMQkV8k/eemBnpA==" } } }, @@ -2801,6 +3041,12 @@ "readable-stream": "^2.0.2" } }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -2817,7 +3063,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -2832,6 +3077,12 @@ "safe-buffer": "^5.0.1" } }, + "editor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", + "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", + "dev": true + }, "elegant-spinner": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", @@ -2853,11 +3104,19 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -2939,6 +3198,27 @@ } } }, + "env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "dev": true + }, + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2984,6 +3264,19 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3566,6 +3859,14 @@ "acorn": "^7.1.1", "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + } } }, "esprima": { @@ -3616,7 +3917,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -3631,7 +3931,6 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -3643,20 +3942,17 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -3664,14 +3960,12 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -3724,8 +4018,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -3742,6 +4035,12 @@ "reusify": "^1.0.4" } }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -3780,11 +4079,16 @@ "pkg-dir": "^4.1.0" } }, + "find-npm-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", + "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==", + "dev": true + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, "requires": { "locate-path": "^2.0.0" } @@ -3959,6 +4263,28 @@ } } }, + "fs-vacuum": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz", + "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3975,8 +4301,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -4046,17 +4371,41 @@ "is-property": "^1.0.2" } }, + "genfun": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "dev": true + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", "dev": true }, + "gentle-fs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.0.tgz", + "integrity": "sha512-3k2CgAmPxuz7S6nKK+AqFE2AdM1QuwqKLPKzIET3VRwK++3q96MsNFobScDjlCrq97ZJ8y5R725MOlm6ffUCjg==", + "dev": true, + "requires": { + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + } + }, "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 + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-func-name": { "version": "2.0.0", @@ -4080,7 +4429,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -4089,7 +4437,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -4241,6 +4588,33 @@ } } }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -4300,7 +4674,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -4325,14 +4698,12 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-unicode": { "version": "2.0.1", @@ -4409,6 +4780,12 @@ } } }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -4447,6 +4824,15 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, "husky": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", @@ -4643,6 +5029,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -4685,6 +5077,12 @@ "resolve-from": "^5.0.0" } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4697,6 +5095,12 @@ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", "dev": true }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, "inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", @@ -4715,14 +5119,27 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "init-package-json": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", + "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", + "dev": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } }, "inquirer": { "version": "7.1.0", @@ -4859,7 +5276,18 @@ "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, "is-absolute": { @@ -4899,6 +5327,32 @@ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.0.0.tgz", + "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", + "dev": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", @@ -4926,8 +5380,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "4.0.1", @@ -4938,12 +5391,28 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4953,8 +5422,7 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-observable": { "version": "1.1.0", @@ -4965,6 +5433,15 @@ "symbol-observable": "^1.1.0" } }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -4983,7 +5460,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "integrity": "sha512-NECAi6wp6CgMesHuVUEK8JwjCvm/tvnn5pCbB42JOHp3mgUizN0nagXu4HEqQZBkieGEQ+jVcMKWqoVd6CDbLQ==", "dev": true }, "is-property": { @@ -4992,6 +5469,12 @@ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "dev": true }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", @@ -5016,11 +5499,16 @@ "is-unc-path": "^1.0.0" } }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.3", @@ -5076,14 +5564,12 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "4.0.0", @@ -5262,8 +5748,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdoctypeparser": { "version": "6.1.0", @@ -5295,13 +5780,6 @@ "xml-name-validator": ">= 2.0.1 < 3.0.0" }, "dependencies": { - "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true - }, "parse5": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", @@ -5440,6 +5918,21 @@ "graceful-fs": "^4.1.9" } }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz", + "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", + "dev": true + }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -5453,7 +5946,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, "requires": { "invert-kv": "^2.0.0" } @@ -5500,6 +5992,195 @@ "type-check": "~0.3.2" } }, + "libcipm": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.7.tgz", + "integrity": "sha512-fTq33otU3PNXxxCTCYCYe7V96o59v/o7bvtspmbORXpgFk+wcWrGf5x6tBgui5gCed/45/wtPomBsZBYm5KbIw==", + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz", + "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz", + "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", + "dev": true, + "requires": { + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz", + "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "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 + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz", + "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz", + "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", + "dev": true, + "requires": { + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz", + "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz", + "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz", + "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.2.tgz", + "integrity": "sha512-ujaYToga1SAX5r7FU5ShMFi88CWpY75meNZtr6RtEyv4l2ZK3+Wgvxq2IqlwWBiDZOqhumdeiocPS1aKrCMe3A==", + "dev": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "y18n": "^4.0.0" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -5572,9 +6253,9 @@ "dev": true }, "commander": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.0.0.tgz", - "integrity": "sha512-JrDGPAKjMGSP1G0DUoaceEJ3DZgAfr/q6X7FVk4+U5KxUSKviYGM2k6zWkfyyBHy5rAtzgYJFa1ro2O9PtoxwQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true }, "cosmiconfig": { @@ -5853,23 +6534,95 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, + "lock-verify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz", + "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", + "dev": true, + "requires": { + "npm-package-arg": "^6.1.0" + } + }, + "lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lodash._baseindexof": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", + "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "dev": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", + "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", + "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, "lodash.assignin": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", @@ -5888,6 +6641,12 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -5984,6 +6743,12 @@ "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", "dev": true }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -6015,12 +6780,30 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", "dev": true }, + "lodash.without": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz", + "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", + "dev": true + }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -6118,6 +6901,12 @@ "signal-exit": "^3.0.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6150,13 +6939,29 @@ } } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "make-fetch-happen": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", + "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" } }, "map-obj": { @@ -6277,17 +7082,17 @@ "dev": true }, "marked-terminal": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.0.0.tgz", - "integrity": "sha512-mzU3VD7aVz12FfGoKFAceijehA6Ocjfg3rVimvJbFAB/NOYCsuzRVtq3PSFdPmWI5mhdGeEh3/aMJ5DSxAz94Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.0.tgz", + "integrity": "sha512-5KllfAOW02WS6hLRQ7cNvGOxvKW1BKuXELH4EtbWfyWgxQhROoMxEvuQ/3fTgkNjledR0J48F4HbapvYp1zWkQ==", "dev": true, "requires": { - "ansi-escapes": "^4.3.0", + "ansi-escapes": "^4.3.1", "cardinal": "^2.1.1", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "cli-table": "^0.3.1", "node-emoji": "^1.10.0", - "supports-hyperlinks": "^2.0.0" + "supports-hyperlinks": "^2.1.0" }, "dependencies": { "ansi-styles": { @@ -6301,9 +7106,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6348,11 +7153,16 @@ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, + "meant": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz", + "integrity": "sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==", + "dev": true + }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -6405,25 +7215,24 @@ "dev": true }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "dev": true }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimatch": { "version": "3.0.4", @@ -6437,8 +7246,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "3.0.2", @@ -6469,6 +7277,23 @@ "minipass": "^2.9.0" } }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0" + } + }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -6730,6 +7555,19 @@ "moment": ">= 2.9.0" } }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "run-queue": "^1.0.3" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6850,8 +7688,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nise": { "version": "4.0.3", @@ -6899,6 +7736,33 @@ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "dev": true }, + "node-fetch-npm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", + "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz", + "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "tar": "^4.4.12" + } + }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -7128,45 +7992,18 @@ "write-file-atomic": "^2.4.3" }, "dependencies": { - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, "requires": { "es6-promisify": "^5.0.0" } }, - "agentkeepalive": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", - "dev": true, - "requires": { - "humanize-ms": "^1.2.1" - } - }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, "requires": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -7174,41 +8011,10 @@ "json-schema-traverse": "^0.3.0" } }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "^2.0.0" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", - "dev": true - }, - "ansistyles": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", - "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "2.0.0", @@ -7216,3624 +8022,381 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, - "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "dev": true, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "requires": { + "color-name": "^1.1.1" + } + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { - "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" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { - "safer-buffer": "~2.1.0" + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "optional": true, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "requires": { - "tweetnacl": "^0.14.3" + "is-obj": "^1.0.0" } }, - "bin-links": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.7.tgz", - "integrity": "sha512-/eaLaTu7G7/o7PV04QPy1HRT65zf+1tFkPGv0sPTV0tRwufooYBQO3zrcyGgm+ja+ZtBf2GEuKjDRJ2pPG+yqA==", - "dev": true, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { - "bluebird": "^3.5.3", - "cmd-shim": "^3.0.0", - "gentle-fs": "^2.3.0", - "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0", - "write-file-atomic": "^2.3.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.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" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + } } }, - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", - "dev": true + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "dev": true, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" + "agent-base": "4", + "debug": "3.1.0" } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "agent-base": "^4.3.0", + "debug": "^3.1.0" } }, - "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", - "dev": true + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "iferr": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz", + "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==", "dev": true }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", - "dev": true + "is-callable": { + "version": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, - "byte-size": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", - "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", - "dev": true + "is-date-object": { + "version": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, - "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", - "dev": true, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "number-is-nan": "^1.0.0" } }, - "call-limit": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz", - "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true + "is-regex": { + "version": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } }, - "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "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 - }, - "cidr-regex": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz", - "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0" - } - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "dev": true - }, - "cli-columns": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz", - "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", - "dev": true, - "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" - } - }, - "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "cmd-shim": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz", - "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "^1.1.1" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", - "dev": true, - "optional": true - }, - "columnify": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", - "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", - "dev": true, - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - } - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "dev": true, - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", - "dev": true - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "dotenv": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", - "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", - "dev": true - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "editor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", - "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", - "dev": true - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "dev": true, - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "env-paths": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", - "dev": true - }, - "err-code": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "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" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", - "dev": true - }, - "find-npm-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", - "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==", - "dev": true - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "requires": { - "minipass": "^2.6.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "fs-vacuum": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz", - "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - }, - "dependencies": { - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "genfun": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", - "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", - "dev": true - }, - "gentle-fs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.0.tgz", - "integrity": "sha512-3k2CgAmPxuz7S6nKK+AqFE2AdM1QuwqKLPKzIET3VRwK++3q96MsNFobScDjlCrq97ZJ8y5R725MOlm6ffUCjg==", - "dev": true, - "requires": { - "aproba": "^1.1.2", - "chownr": "^1.1.2", - "cmd-shim": "^3.0.3", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "infer-owner": "^1.0.4", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - } - } - }, - "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 - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "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-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, - "requires": { - "ini": "^1.3.4" - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "dev": true, - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true - }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "dev": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "iferr": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz", - "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "init-package-json": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", - "dev": true, - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - }, - "dependencies": { - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - } - } - }, - "is-cidr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.0.0.tgz", - "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", - "dev": true, - "requires": { - "cidr-regex": "^2.0.10" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "dev": true, - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "^4.0.0" - } - }, - "lazy-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz", - "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "libcipm": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.7.tgz", - "integrity": "sha512-fTq33otU3PNXxxCTCYCYe7V96o59v/o7bvtspmbORXpgFk+wcWrGf5x6tBgui5gCed/45/wtPomBsZBYm5KbIw==", - "dev": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "ini": "^1.3.5", - "lock-verify": "^2.0.2", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^9.1.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" - } - }, - "libnpm": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz", - "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", - "dev": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.3", - "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.2", - "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmpublish": "^1.1.2", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "lock-verify": "^2.0.2", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", - "npmlog": "^4.1.2", - "pacote": "^9.5.3", - "read-package-json": "^2.0.13", - "stringify-package": "^1.0.0" - } - }, - "libnpmaccess": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz", - "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmconfig": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz", - "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "find-up": "^3.0.0", - "ini": "^1.3.5" - }, - "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 - } - } - }, - "libnpmhook": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz", - "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmorg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz", - "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmpublish": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz", - "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" - } - }, - "libnpmsearch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz", - "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmteam": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz", - "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpx": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.2.tgz", - "integrity": "sha512-ujaYToga1SAX5r7FU5ShMFi88CWpY75meNZtr6RtEyv4l2ZK3+Wgvxq2IqlwWBiDZOqhumdeiocPS1aKrCMe3A==", - "dev": true, - "requires": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^11.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lock-verify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz", - "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", - "dev": true, - "requires": { - "npm-package-arg": "^6.1.0", - "semver": "^5.4.1" - } - }, - "lockfile": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", - "dev": true, - "requires": { - "signal-exit": "^3.0.2" - } - }, - "lodash._baseindexof": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", - "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", - "dev": true - }, - "lodash._baseuniq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", - "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", - "dev": true, - "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true - }, - "lodash._cacheindexof": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", - "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", - "dev": true - }, - "lodash._createcache": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", - "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0" - } - }, - "lodash._createset": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", - "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "lodash.without": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz", - "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", - "dev": true - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "make-fetch-happen": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", - "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", - "dev": true, - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "meant": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz", - "integrity": "sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - } - } - }, - "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", - "dev": true, - "requires": { - "mime-db": "~1.35.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "requires": { - "minipass": "^2.9.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } - } - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-fetch-npm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" - } - }, - "node-gyp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz", - "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.1.2", - "request": "^2.88.0", - "rimraf": "^2.6.3", - "semver": "^5.7.1", - "tar": "^4.4.12", - "which": "^1.3.1" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "npm-audit-report": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.2.tgz", - "integrity": "sha512-abeqS5ONyXNaZJPGAf6TOUMNdSe1Y6cpc9MLBRn+CuUoYbfdca6AxOyXVlfIv9OgKX+cacblbG5w7A6ccwoTPw==", - "dev": true, - "requires": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-cache-filename": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", - "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", - "dev": true - }, - "npm-install-checks": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz", - "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", - "dev": true, - "requires": { - "semver": "^2.3.0 || 3.x || 4 || 5" - } - }, - "npm-lifecycle": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.4.tgz", - "integrity": "sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A==", - "dev": true, - "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.15", - "node-gyp": "^5.0.2", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" - } - }, - "npm-logical-tree": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz", - "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==", - "dev": true - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true - }, - "npm-package-arg": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", - "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", - "dev": true, - "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "dev": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-pick-manifest": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", - "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" - } - }, - "npm-profile": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz", - "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", - "dev": true, - "requires": { - "aproba": "^1.1.2 || 2", - "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^4.0.0" - } - }, - "npm-registry-fetch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz", - "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", - "dev": true, - "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - } - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npm-user-validate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.0.tgz", - "integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=", - "dev": true - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", - "dev": true - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "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" - } - } - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "pacote": { - "version": "9.5.12", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", - "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-normalize-package-bin": "^1.0.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^3.0.0", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.10", - "unique-filename": "^1.1.1", - "which": "^1.3.1" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "promise-retry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", - "dev": true, - "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true - } - } - }, - "promzard": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", - "dev": true, - "requires": { - "read": "1" - } - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true - }, - "protoduck": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", - "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", - "dev": true, - "requires": { - "genfun": "^5.0.0" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "qrcode-terminal": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "query-string": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", - "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, - "qw": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz", - "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=", - "dev": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-cmd-shim": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz", - "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "read-installed": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", - "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "graceful-fs": "^4.1.2", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - } - }, - "read-package-json": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", - "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", - "dev": true, - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "read-package-tree": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", - "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", - "dev": true, - "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "registry-auth-token": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", - "dev": true, - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "^1.0.1" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true, - "requires": { - "semver": "^5.0.3" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "sha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz", - "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", - "dev": true - }, - "smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", - "dev": true - }, - "socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "dev": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", - "dev": true, - "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "dependencies": { - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - } - } - }, - "sorted-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", - "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", - "dev": true - }, - "sorted-union-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz", - "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", - "dev": true, - "requires": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" - }, - "dependencies": { - "from2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz", - "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", - "dev": true - }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-iterate": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz", - "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", - "dev": true, + "is-symbol": { + "version": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "requires": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "has-symbols": "^1.0.0" } }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, - "string_decoder": { + "make-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - } + "pify": "^3.0.0" } }, - "stringify-package": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", - "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", - "dev": true + "mime-db": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "object-keys": { + "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" }, - "strip-json-comments": { + "path-key": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true, - "requires": { - "execa": "^0.7.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, - "tiny-relative-date": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", - "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", - "dev": true + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "requires": { + "rc": "^1.1.6", "safe-buffer": "^5.0.1" } }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", - "dev": true - }, - "umask": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", - "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", - "dev": true + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "unique-slug": "^2.0.0" + "glob": "^7.1.3" } }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { - "crypto-random-string": "^1.0.0" + "semver": "^5.0.3" } }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "dev": true, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" + "shebang-regex": "^1.0.0" } }, - "url-parse-lax": { + "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { - "prepend-http": "^1.0.1" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util-extend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", - "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", - "dev": true + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, - "util-promisify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", - "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", - "dev": true, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "object.getownpropertydescriptors": "^2.0.3" + "ansi-regex": "^2.0.0" } }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "has-flag": "^3.0.0" } }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { - "builtins": "^1.0.3" + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "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.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": { + "version": "1.1.1", + "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.0" + } + } } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "psl": "^1.1.24", + "punycode": "^1.4.1" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "requires": { - "defaults": "^1.0.3" + "crypto-random-string": "^1.0.0" } }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "wide-align": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "dev": true, "requires": { "string-width": "^1.0.2" }, @@ -10842,48 +8405,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", - "dev": true, - "requires": { - "string-width": "^2.1.1" - } - }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10892,12 +8413,6 @@ } } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", @@ -10909,35 +8424,15 @@ "signal-exit": "^3.0.2" } }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", - "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.1.1", @@ -10956,8 +8451,7 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" } } }, @@ -10965,13 +8459,22 @@ "version": "9.0.2", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, "requires": { "camelcase": "^4.1.0" } } } }, + "npm-audit-report": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.2.tgz", + "integrity": "sha512-abeqS5ONyXNaZJPGAf6TOUMNdSe1Y6cpc9MLBRn+CuUoYbfdca6AxOyXVlfIv9OgKX+cacblbG5w7A6ccwoTPw==", + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, "npm-bundled": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", @@ -10981,12 +8484,55 @@ "npm-normalize-package-bin": "^1.0.1" } }, + "npm-cache-filename": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", + "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", + "dev": true + }, + "npm-install-checks": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz", + "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", + "dev": true + }, + "npm-lifecycle": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.4.tgz", + "integrity": "sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A==", + "dev": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz", + "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==", + "dev": true + }, "npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "validate-npm-package-name": "^3.0.0" + } + }, "npm-packlist": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", @@ -10998,11 +8544,54 @@ "npm-normalize-package-bin": "^1.0.1" } }, + "npm-pick-manifest": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0" + } + }, + "npm-profile": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz", + "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", + "dev": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz", + "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + } + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" }, @@ -11010,11 +8599,16 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" } } }, + "npm-user-validate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.0.tgz", + "integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=", + "dev": true + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -11039,8 +8633,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwmatcher": { "version": "1.4.4", @@ -11199,7 +8792,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -11219,6 +8811,12 @@ "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", "dev": true }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "dev": true + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -11252,7 +8850,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, "requires": { "execa": "^1.0.0", "lcid": "^2.0.0", @@ -11288,8 +8885,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" }, "p-each-series": { "version": "2.1.0", @@ -11317,33 +8913,27 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, "requires": { "p-limit": "^1.1.0" - }, - "dependencies": { - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - } } }, "p-map": { @@ -11403,8 +8993,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "package-hash": { "version": "4.0.0", @@ -11418,12 +9007,68 @@ "release-zalgo": "^1.0.0" } }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-url": "^3.0.3" + } + }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", "dev": true }, + "pacote": { + "version": "9.5.12", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", + "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "safe-buffer": "^5.1.2", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1" + } + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11467,8 +9112,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", @@ -11476,6 +9120,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -11614,8 +9264,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "pkg-conf": { "version": "2.1.0", @@ -11729,11 +9378,16 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "process-on-spawn": { "version": "1.0.0", @@ -11741,32 +9395,83 @@ "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { - "fromentries": "^1.2.0" + "fromentries": "^1.2.0" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + } + } + }, + "promzard": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", + "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "dev": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "protoduck": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "dev": true, + "requires": { + "genfun": "^5.0.0" } }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -11807,18 +9512,41 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, + "qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "dev": true + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, + "query-string": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", + "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, "quick-lru": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, + "qw": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz", + "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=", + "dev": true + }, "ramda": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", @@ -11829,7 +9557,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -11840,11 +9567,66 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" } } }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz", + "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", + "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", + "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", + "dev": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -11881,6 +9663,18 @@ "util-deprecate": "~1.0.1" } }, + "readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "readdirp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", @@ -11936,6 +9730,15 @@ "rc": "^1.2.8" } }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -12030,14 +9833,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "resolve": { "version": "1.17.0", @@ -12126,6 +9927,15 @@ "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", "dev": true }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, "rxjs": { "version": "6.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", @@ -12138,14 +9948,12 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.2.4", @@ -12406,8 +10214,16 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "sha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz", + "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } }, "shebang-command": { "version": "2.0.0", @@ -12433,8 +10249,7 @@ "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "signale": { "version": "1.4.0", @@ -12519,6 +10334,101 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "smart-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", + "dev": true + }, + "socks": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", + "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", + "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz", + "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", + "dev": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz", + "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -12586,6 +10496,12 @@ "through": "2" } }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "dev": true + }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -12647,6 +10563,15 @@ "tweetnacl": "~0.14.0" } }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, "stack-chain": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", @@ -12663,12 +10588,38 @@ "readable-stream": "^2.0.2" } }, + "stream-each": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", + "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz", + "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + } + }, "stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "dev": true + }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -12679,7 +10630,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -12747,11 +10697,16 @@ "is-regexp": "^1.0.0" } }, + "stringify-package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", + "dev": true + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -12765,8 +10720,7 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-final-newline": { "version": "2.0.0", @@ -12783,8 +10737,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "5.5.0", @@ -12970,6 +10923,12 @@ } } }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13030,6 +10989,18 @@ } } }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", + "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -13187,8 +11158,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { "version": "0.3.2", @@ -13211,6 +11181,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -13242,6 +11218,18 @@ "commander": "~2.20.3" } }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true + }, + "umask": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", + "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", + "dev": true + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -13254,6 +11242,24 @@ "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", "dev": true }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -13288,6 +11294,35 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "xdg-basedir": "^3.0.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -13303,12 +11338,35 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", "dev": true }, + "util-promisify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", + "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -13330,6 +11388,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, "validator": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", @@ -13418,6 +11485,15 @@ "vinyl": "^2.0.0" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", @@ -13447,8 +11523,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "which-pm-runs": { "version": "1.0.0", @@ -13465,6 +11540,15 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, "windows-release": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz", @@ -13494,11 +11578,19 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -13507,14 +11599,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -13523,7 +11613,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -13534,7 +11623,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -13544,8 +11632,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -13568,6 +11655,12 @@ "typedarray-to-buffer": "^3.1.5" } }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, "xml-name-validator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", @@ -13590,8 +11683,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", diff --git a/package.json b/package.json index 2266233994cf..dcb344f21465 100644 --- a/package.json +++ b/package.json @@ -49,12 +49,9 @@ "@commitlint/config-angular": "^8.3.4", "@types/node": "^12.12.37", "@types/validator": "^10.11.0", - "acorn": "^7.1.1", - "big-integer": "^1.6.45", "chai": "^4.x", "chai-as-promised": "^7.x", "chai-datetime": "^1.x", - "chai-spies": "^1.x", "cls-hooked": "^4.2.2", "cross-env": "^7.0.2", "delay": "^4.3.0", @@ -81,7 +78,6 @@ "p-timeout": "^3.2.0", "pg": "^7.8.1", "pg-hstore": "^2.x", - "pg-types": "^2.0.0", "rimraf": "^3.0.2", "semantic-release": "^17.0.7", "sinon": "^9.0.2", diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js index 0804a211e132..10839de8c94c 100644 --- a/test/integration/data-types.test.js +++ b/test/integration/data-types.test.js @@ -12,7 +12,6 @@ const chai = require('chai'), uuid = require('uuid'), DataTypes = require('../../lib/data-types'), dialect = Support.getTestDialect(), - BigInt = require('big-integer'), semver = require('semver'); describe(Support.getTestDialectTeaser('DataTypes'), () => { @@ -233,7 +232,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { age: Sequelize.BIGINT }); - const age = BigInt(Number.MAX_SAFE_INTEGER).add(Number.MAX_SAFE_INTEGER); + const age = BigInt(Number.MAX_SAFE_INTEGER) * 2n; return User.sync({ force: true }).then(() => { return User.create({ age }); diff --git a/test/support.js b/test/support.js index 80788f51d9ae..1fabd8d44cea 100644 --- a/test/support.js +++ b/test/support.js @@ -9,7 +9,6 @@ const chai = require('chai'); const expect = chai.expect; const AbstractQueryGenerator = require('../lib/dialects/abstract/query-generator'); -chai.use(require('chai-spies')); chai.use(require('chai-datetime')); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -61,9 +60,9 @@ const Support = { * * @param {Error[]} destArray the array to push unhandled rejections onto. If you omit this, * one will be created and returned for you. - * + * * @returns {Error[]} destArray - */ + */ captureUnhandledRejections(destArray = []) { return unhandledRejections = destArray; }, From e4510eae5c69a9f594c297728d4ba075113be08d Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 25 Apr 2020 23:24:36 +0530 Subject: [PATCH 115/414] docs: database version support info (#12168) --- ENGINE.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 ENGINE.md diff --git a/ENGINE.md b/ENGINE.md new file mode 100644 index 000000000000..a41171724084 --- /dev/null +++ b/ENGINE.md @@ -0,0 +1,10 @@ +# Database Engine Support + +## v6-beta +| Engine | Minimum supported version | +| ------------ | :------------: | +| Postgre | [9.5 ](https://www.postgresql.org/docs/9.5/ ) | +| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | +| MariaDB | [10.1](https://mariadb.com/kb/en/changes-improvements-in-mariadb-101/) | +| Microsoft SQL | ? | +| SQLite | [3.0](https://www.sqlite.org/version3.html) From e7d5f0ac7b13a7d388cf60cd989e0b3a77a8809d Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 25 Apr 2020 23:42:55 +0530 Subject: [PATCH 116/414] test: fix sscce support --- package-lock.json | 710 ++++++++++++++++++++++++++++++++++++++++++---- test/support.js | 10 +- 2 files changed, 663 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44f6d8c634e6..0f43bd90d781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1743,7 +1743,21 @@ "cmd-shim": "^3.0.0", "gentle-fs": "^2.3.0", "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0" + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" + }, + "dependencies": { + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } } }, "binary-extensions": { @@ -1885,9 +1899,21 @@ "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "caching-transform": { @@ -2110,7 +2136,25 @@ "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", "dev": true, "requires": { - "string-width": "^2.0.0" + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "cli-cursor": { @@ -2137,8 +2181,18 @@ "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "dev": true, "requires": { + "colors": "^1.1.2", "object-assign": "^4.1.0", "string-width": "^2.1.1" + }, + "dependencies": { + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "optional": true + } } }, "cli-truncate": { @@ -2305,7 +2359,25 @@ "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "dev": true, "requires": { + "strip-ansi": "^3.0.0", "wcwidth": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "combined-stream": { @@ -2391,8 +2463,58 @@ "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "dev": true, "requires": { + "dot-prop": "^4.1.0", "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } } }, "console-control-strings": { @@ -2481,7 +2603,19 @@ "fs-write-stream-atomic": "^1.0.8", "iferr": "^0.1.5", "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", "run-queue": "^1.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "core-js": { @@ -2736,7 +2870,18 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true + "dev": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } }, "define-properties": { "version": "1.1.3", @@ -3023,12 +3168,15 @@ "json-stable-stringify": "^1.0.1", "strip-json-comments": "^2.0.1", "tslint": "5.14.0", + "typescript": "^3.9.0-dev.20200425", "yargs": "^15.1.0" }, "dependencies": { "typescript": { - "version": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200425.tgz", - "integrity": "sha512-YjP3XoXMSJvza4COs+jXEu1ctdG8+vnieJvv6xJB5iFJIRpzSIF0YBTLn0TOu4oDZe9LyTtjMQkV8k/eemBnpA==" + "version": "3.9.0-dev.20200425", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200425.tgz", + "integrity": "sha512-YjP3XoXMSJvza4COs+jXEu1ctdG8+vnieJvv6xJB5iFJIRpzSIF0YBTLn0TOu4oDZe9LyTtjMQkV8k/eemBnpA==", + "dev": true } } }, @@ -4270,7 +4418,19 @@ "dev": true, "requires": { "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1" + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "fs-write-stream-atomic": { @@ -5137,8 +5297,17 @@ "promzard": "^0.3.0", "read": "~1.0.1", "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", "validate-npm-package-license": "^3.0.1", "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "inquirer": { @@ -5460,7 +5629,7 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha512-NECAi6wp6CgMesHuVUEK8JwjCvm/tvnn5pCbB42JOHp3mgUizN0nagXu4HEqQZBkieGEQ+jVcMKWqoVd6CDbLQ==", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, "is-property": { @@ -6011,7 +6180,19 @@ "npm-package-arg": "^6.1.0", "pacote": "^9.1.0", "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", "worker-farm": "^1.6.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "libnpm": { @@ -6048,9 +6229,18 @@ "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", "dev": true, "requires": { + "aproba": "^2.0.0", "get-stream": "^4.0.0", "npm-package-arg": "^6.1.0", "npm-registry-fetch": "^4.0.0" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + } } }, "libnpmconfig": { @@ -6115,9 +6305,18 @@ "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", "dev": true, "requires": { + "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", "get-stream": "^4.0.0", "npm-registry-fetch": "^4.0.0" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + } } }, "libnpmorg": { @@ -6126,9 +6325,18 @@ "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", "dev": true, "requires": { + "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", "get-stream": "^4.0.0", "npm-registry-fetch": "^4.0.0" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + } } }, "libnpmpublish": { @@ -6137,13 +6345,29 @@ "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", "dev": true, "requires": { + "aproba": "^2.0.0", "figgy-pudding": "^3.5.1", "get-stream": "^4.0.0", "lodash.clonedeep": "^4.5.0", "normalize-package-data": "^2.4.0", "npm-package-arg": "^6.1.0", "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", "ssri": "^6.0.1" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "libnpmsearch": { @@ -6163,9 +6387,18 @@ "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", "dev": true, "requires": { + "aproba": "^2.0.0", "figgy-pudding": "^3.4.1", "get-stream": "^4.0.0", "npm-registry-fetch": "^4.0.0" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + } } }, "libnpx": { @@ -6176,44 +6409,104 @@ "requires": { "dotenv": "^5.0.1", "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", "safe-buffer": "^5.1.0", "update-notifier": "^2.3.0", - "y18n": "^4.0.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "lint-staged": { - "version": "10.1.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.7.tgz", - "integrity": "sha512-ZkK8t9Ep/AHuJQKV95izSa+DqotftGnSsNeEmCSqbQ6j4C4H0jDYhEZqVOGD1Q2Oe227igbqjMWycWyYbQtpoA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "commander": "^5.0.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "execa": "^4.0.0", - "listr": "^0.14.3", - "log-symbols": "^3.0.0", - "micromatch": "^4.0.2", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yargs": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", + "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "lint-staged": { + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.7.tgz", + "integrity": "sha512-ZkK8t9Ep/AHuJQKV95izSa+DqotftGnSsNeEmCSqbQ6j4C4H0jDYhEZqVOGD1Q2Oe227igbqjMWycWyYbQtpoA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "execa": "^4.0.0", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", "stringify-object": "^3.3.0" }, "dependencies": { @@ -6545,7 +6838,16 @@ "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", "dev": true, "requires": { - "npm-package-arg": "^6.1.0" + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "lockfile": { @@ -6948,12 +7250,60 @@ "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", "promise-retry": "^1.1.1", "socks-proxy-agent": "^4.0.0", "ssri": "^6.0.0" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "map-age-cleaner": { @@ -7291,7 +7641,20 @@ "parallel-transform": "^1.1.0", "pump": "^3.0.0", "pumpify": "^1.3.3", - "stream-each": "^1.1.0" + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "mkdirp": { @@ -7565,7 +7928,19 @@ "copy-concurrently": "^1.0.0", "fs-write-stream-atomic": "^1.0.8", "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", "run-queue": "^1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "ms": { @@ -7760,7 +8135,36 @@ "nopt": "^4.0.1", "npmlog": "^4.1.2", "request": "^2.88.0", - "tar": "^4.4.12" + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "node-pre-gyp": { @@ -8005,7 +8409,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "^4.6.0", "fast-deep-equal": "^1.0.0", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.3.0" @@ -8494,7 +8897,18 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz", "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", - "dev": true + "dev": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } }, "npm-lifecycle": { "version": "3.1.4", @@ -8505,9 +8919,28 @@ "byline": "^5.0.0", "graceful-fs": "^4.1.15", "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", "slide": "^1.1.6", "uid-number": "0.0.6", - "umask": "^1.1.0" + "umask": "^1.1.0", + "which": "^1.3.1" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "npm-logical-tree": { @@ -8530,7 +8963,16 @@ "requires": { "hosted-git-info": "^2.7.1", "osenv": "^0.1.5", + "semver": "^5.6.0", "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "npm-packlist": { @@ -8551,7 +8993,16 @@ "dev": true, "requires": { "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0" + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "npm-profile": { @@ -9014,7 +9465,27 @@ "dev": true, "requires": { "got": "^6.7.1", - "registry-url": "^3.0.3" + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + }, + "dependencies": { + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "packet-reader": { @@ -9052,10 +9523,39 @@ "promise-inflight": "^1.0.1", "promise-retry": "^1.1.1", "protoduck": "^5.0.1", + "rimraf": "^2.6.2", "safe-buffer": "^5.1.2", + "semver": "^5.6.0", "ssri": "^6.0.1", "tar": "^4.4.10", - "unique-filename": "^1.1.1" + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "parallel-transform": { @@ -9599,8 +10099,17 @@ "graceful-fs": "^4.1.2", "read-package-json": "^2.0.0", "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", "slide": "~1.1.3", "util-extend": "^1.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "read-package-json": { @@ -10927,7 +11436,84 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true + "dev": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.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" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } }, "test-exclude": { "version": "6.0.0", @@ -11320,7 +11906,25 @@ "is-installed-globally": "^0.1.0", "is-npm": "^1.0.0", "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + } } }, "uri-js": { diff --git a/test/support.js b/test/support.js index 1fabd8d44cea..5b65b74eaf10 100644 --- a/test/support.js +++ b/test/support.js @@ -38,10 +38,12 @@ process.on('unhandledRejection', e => { throw e; }); -afterEach(() => { - onNextUnhandledRejection = null; - unhandledRejections = null; -}); +if (global.afterEach) { + afterEach(() => { + onNextUnhandledRejection = null; + unhandledRejections = null; + }); +} const Support = { Sequelize, From c1e7317a00fd9c4700eb39196b4d26ad61091699 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sat, 25 Apr 2020 23:46:02 -0500 Subject: [PATCH 117/414] refactor(lib): asyncify remaining methods (#12173) --- lib/associations/belongs-to-many.js | 59 +++++----- lib/dialects/mssql/connection-manager.js | 108 ++++++++--------- lib/dialects/mssql/query-interface.js | 61 ++++------ lib/dialects/sqlite/connection-manager.js | 42 +++---- lib/dialects/sqlite/query-interface.js | 22 ++-- lib/dialects/sqlite/query.js | 135 +++++++++++----------- 6 files changed, 207 insertions(+), 220 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index ba96d618f5e8..f261d2fba1f7 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -415,7 +415,7 @@ class BelongsToMany extends Association { * * @returns {Promise>} */ - get(instance, options) { + async get(instance, options) { options = Utils.cloneDeep(options) || {}; const through = this.through; @@ -483,7 +483,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - count(instance, options) { + async count(instance, options) { const sequelize = this.target.sequelize; options = Utils.cloneDeep(options); @@ -494,7 +494,9 @@ class BelongsToMany extends Association { options.raw = true; options.plain = true; - return this.get(instance, options).then(result => parseInt(result.count, 10)); + const result = await this.get(instance, options); + + return parseInt(result.count, 10); } /** @@ -506,7 +508,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - has(sourceInstance, instances, options) { + async has(sourceInstance, instances, options) { if (!Array.isArray(instances)) { instances = [instances]; } @@ -535,10 +537,10 @@ class BelongsToMany extends Association { ] }; - return this.get(sourceInstance, options).then(associatedObjects => - _.differenceWith(instancePrimaryKeys, associatedObjects, - (a, b) => _.isEqual(a[this.targetKey], b[this.targetKey])).length === 0 - ); + const associatedObjects = await this.get(sourceInstance, options); + + return _.differenceWith(instancePrimaryKeys, associatedObjects, + (a, b) => _.isEqual(a[this.targetKey], b[this.targetKey])).length === 0; } /** @@ -553,7 +555,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - set(sourceInstance, newAssociatedObjects, options) { + async set(sourceInstance, newAssociatedObjects, options) { options = options || {}; const sourceKey = this.sourceKey; @@ -640,12 +642,13 @@ class BelongsToMany extends Association { return Promise.all(promises); }; - return this.through.model.findAll(_.defaults({ where, raw: true }, options)) - .then(currentRows => updateAssociations(currentRows)) - .catch(error => { - if (error instanceof EmptyResultError) return updateAssociations([]); - throw error; - }); + try { + const currentRows = await this.through.model.findAll(_.defaults({ where, raw: true }, options)); + return await updateAssociations(currentRows); + } catch (error) { + if (error instanceof EmptyResultError) return updateAssociations([]); + throw error; + } } /** @@ -660,7 +663,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - add(sourceInstance, newInstances, options) { + async add(sourceInstance, newInstances, options) { // If newInstances is null or undefined, no-op if (!newInstances) return Promise.resolve(); @@ -734,13 +737,14 @@ class BelongsToMany extends Association { return Promise.all(promises); }; - return association.through.model.findAll(_.defaults({ where, raw: true }, options)) - .then(currentRows => updateAssociations(currentRows)) - .then(([associations]) => associations) - .catch(error => { - if (error instanceof EmptyResultError) return updateAssociations(); - throw error; - }); + try { + const currentRows = await association.through.model.findAll(_.defaults({ where, raw: true }, options)); + const [associations] = await updateAssociations(currentRows); + return associations; + } catch (error) { + if (error instanceof EmptyResultError) return updateAssociations(); + throw error; + } } /** @@ -777,7 +781,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - create(sourceInstance, values, options) { + async create(sourceInstance, values, options) { const association = this; options = options || {}; @@ -797,9 +801,10 @@ class BelongsToMany extends Association { } // Create the related model instance - return association.target.create(values, options).then(newAssociatedObject => - sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).then(() => newAssociatedObject) - ); + const newAssociatedObject = await association.target.create(values, options); + + await sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])); + return newAssociatedObject; } verifyAssociationAlias(alias) { diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index 7d6f36d2ad55..a5b3ad6a7577 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -25,7 +25,7 @@ class ConnectionManager extends AbstractConnectionManager { parserStore.clear(); } - connect(config) { + async connect(config) { const connectionConfig = { server: config.host, authentication: { @@ -58,57 +58,59 @@ class ConnectionManager extends AbstractConnectionManager { Object.assign(connectionConfig.options, config.dialectOptions.options); } - return new Promise((resolve, reject) => { - const connection = new this.lib.Connection(connectionConfig); - connection.queue = new AsyncQueue(); - connection.lib = this.lib; - - const connectHandler = error => { - connection.removeListener('end', endHandler); - connection.removeListener('error', errorHandler); - - if (error) return reject(error); - - debug('connection acquired'); - resolve(connection); - }; - - const endHandler = () => { - connection.removeListener('connect', connectHandler); - connection.removeListener('error', errorHandler); - reject(new Error('Connection was closed by remote server')); - }; - - const errorHandler = error => { - connection.removeListener('connect', connectHandler); - connection.removeListener('end', endHandler); - reject(error); - }; - - connection.once('error', errorHandler); - connection.once('end', endHandler); - connection.once('connect', connectHandler); - - /* - * Permanently attach this event before connection is even acquired - * tedious sometime emits error even after connect(with error). - * - * If we dont attach this even that unexpected error event will crash node process - * - * E.g. connectTimeout is set higher than requestTimeout - */ - connection.on('error', error => { - switch (error.code) { - case 'ESOCKET': - case 'ECONNRESET': - this.pool.destroy(connection); + try { + return await new Promise((resolve, reject) => { + const connection = new this.lib.Connection(connectionConfig); + connection.queue = new AsyncQueue(); + connection.lib = this.lib; + + const connectHandler = error => { + connection.removeListener('end', endHandler); + connection.removeListener('error', errorHandler); + + if (error) return reject(error); + + debug('connection acquired'); + resolve(connection); + }; + + const endHandler = () => { + connection.removeListener('connect', connectHandler); + connection.removeListener('error', errorHandler); + reject(new Error('Connection was closed by remote server')); + }; + + const errorHandler = error => { + connection.removeListener('connect', connectHandler); + connection.removeListener('end', endHandler); + reject(error); + }; + + connection.once('error', errorHandler); + connection.once('end', endHandler); + connection.once('connect', connectHandler); + + /* + * Permanently attach this event before connection is even acquired + * tedious sometime emits error even after connect(with error). + * + * If we dont attach this even that unexpected error event will crash node process + * + * E.g. connectTimeout is set higher than requestTimeout + */ + connection.on('error', error => { + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + this.pool.destroy(connection); + } + }); + + if (config.dialectOptions && config.dialectOptions.debug) { + connection.on('debug', debugTedious.log.bind(debugTedious)); } }); - - if (config.dialectOptions && config.dialectOptions.debug) { - connection.on('debug', debugTedious.log.bind(debugTedious)); - } - }).catch(error => { + } catch (error) { if (!error.code) { throw new sequelizeErrors.ConnectionError(error); } @@ -139,13 +141,13 @@ class ConnectionManager extends AbstractConnectionManager { default: throw new sequelizeErrors.ConnectionError(error); } - }); + } } - disconnect(connection) { + async disconnect(connection) { // Don't disconnect a connection that is already disconnected if (connection.closed) { - return Promise.resolve(); + return; } connection.queue.close(); diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index 35eb1404d871..120863888d16 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -20,47 +20,32 @@ @private */ -const removeColumn = function(qi, tableName, attributeName, options) { +const removeColumn = async function(qi, tableName, attributeName, options) { options = Object.assign({ raw: true }, options || {}); const findConstraintSql = qi.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); - return qi.sequelize.query(findConstraintSql, options) - .then(([results]) => { - if (!results.length) { - // No default constraint found -- we can cleanly remove the column - return; - } - const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, results[0].name); - return qi.sequelize.query(dropConstraintSql, options); - }) - .then(() => { - const findForeignKeySql = qi.QueryGenerator.getForeignKeyQuery(tableName, attributeName); - return qi.sequelize.query(findForeignKeySql, options); - }) - .then(([results]) => { - if (!results.length) { - // No foreign key constraints found, so we can remove the column - return; - } - const dropForeignKeySql = qi.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); - return qi.sequelize.query(dropForeignKeySql, options); - }) - .then(() => { - //Check if the current column is a primaryKey - const primaryKeyConstraintSql = qi.QueryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); - return qi.sequelize.query(primaryKeyConstraintSql, options); - }) - .then(([result]) => { - if (!result.length) { - return; - } - const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, result[0].constraintName); - return qi.sequelize.query(dropConstraintSql, options); - }) - .then(() => { - const removeSql = qi.QueryGenerator.removeColumnQuery(tableName, attributeName); - return qi.sequelize.query(removeSql, options); - }); + const [results0] = await qi.sequelize.query(findConstraintSql, options); + if (results0.length) { + // No default constraint found -- we can cleanly remove the column + const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, results0[0].name); + await qi.sequelize.query(dropConstraintSql, options); + } + const findForeignKeySql = qi.QueryGenerator.getForeignKeyQuery(tableName, attributeName); + const [results] = await qi.sequelize.query(findForeignKeySql, options); + if (results.length) { + // No foreign key constraints found, so we can remove the column + const dropForeignKeySql = qi.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); + await qi.sequelize.query(dropForeignKeySql, options); + } + //Check if the current column is a primaryKey + const primaryKeyConstraintSql = qi.QueryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); + const [result] = await qi.sequelize.query(primaryKeyConstraintSql, options); + if (result.length) { + const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, result[0].constraintName); + await qi.sequelize.query(dropConstraintSql, options); + } + const removeSql = qi.QueryGenerator.removeColumnQuery(tableName, attributeName); + return qi.sequelize.query(removeSql, options); }; module.exports = { diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 60ceece11806..ecb4371c034a 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -25,13 +25,12 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(dataTypes); } - _onProcessExit() { - const promises = Object.getOwnPropertyNames(this.connections) - .map(connection => promisify(callback => this.connections[connection].close(callback))()); - - return Promise - .all(promises) - .then(() => super._onProcessExit.call(this)); + async _onProcessExit() { + await Promise.all( + Object.getOwnPropertyNames(this.connections) + .map(connection => promisify(callback => this.connections[connection].close(callback))()) + ); + return super._onProcessExit.call(this); } // Expose this as a method so that the parsing may be updated when the user has added additional, custom types @@ -43,7 +42,7 @@ class ConnectionManager extends AbstractConnectionManager { parserStore.clear(); } - getConnection(options) { + async getConnection(options) { options = options || {}; options.uuid = options.uuid || 'default'; options.storage = this.sequelize.options.storage || this.sequelize.options.host || ':memory:'; @@ -55,7 +54,7 @@ class ConnectionManager extends AbstractConnectionManager { options.readWriteMode = dialectOptions && dialectOptions.mode || defaultReadWriteMode; if (this.connections[options.inMemory || options.uuid]) { - return Promise.resolve(this.connections[options.inMemory || options.uuid]); + return this.connections[options.inMemory || options.uuid]; } if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) { @@ -63,7 +62,7 @@ class ConnectionManager extends AbstractConnectionManager { fs.mkdirSync(path.dirname(options.storage), { recursive: true }); } - return new Promise((resolve, reject) => { + const connection = await new Promise((resolve, reject) => { this.connections[options.inMemory || options.uuid] = new this.lib.Database( options.storage, options.readWriteMode, @@ -73,18 +72,19 @@ class ConnectionManager extends AbstractConnectionManager { resolve(this.connections[options.inMemory || options.uuid]); } ); - }).then(connection => { - if (this.sequelize.config.password) { - // Make it possible to define and use password for sqlite encryption plugin like sqlcipher - connection.run(`PRAGMA KEY=${this.sequelize.escape(this.sequelize.config.password)}`); - } - if (this.sequelize.options.foreignKeys !== false) { - // Make it possible to define and use foreign key constraints unless - // explicitly disallowed. It's still opt-in per relation - connection.run('PRAGMA FOREIGN_KEYS=ON'); - } - return connection; }); + + if (this.sequelize.config.password) { + // Make it possible to define and use password for sqlite encryption plugin like sqlcipher + connection.run(`PRAGMA KEY=${this.sequelize.escape(this.sequelize.config.password)}`); + } + if (this.sequelize.options.foreignKeys !== false) { + // Make it possible to define and use foreign key constraints unless + // explicitly disallowed. It's still opt-in per relation + connection.run('PRAGMA FOREIGN_KEYS=ON'); + } + + return connection; } releaseConnection(connection, force) { diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 1d8b27666656..72a94d27e56a 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -177,20 +177,18 @@ exports.addConstraint = addConstraint; * @private * @returns {Promise} */ -function getForeignKeyReferencesForTable(qi, tableName, options) { +async function getForeignKeyReferencesForTable(qi, tableName, options) { const database = qi.sequelize.config.database; const query = qi.QueryGenerator.getForeignKeysQuery(tableName, database); - return qi.sequelize.query(query, options) - .then(result => { - return result.map(row => ({ - tableName, - columnName: row.from, - referencedTableName: row.table, - referencedColumnName: row.to, - tableCatalog: database, - referencedTableCatalog: database - })); - }); + const result = await qi.sequelize.query(query, options); + return result.map(row => ({ + tableName, + columnName: row.from, + referencedTableName: row.table, + referencedColumnName: row.to, + tableCatalog: database, + referencedTableCatalog: database + })); } exports.getForeignKeyReferencesForTable = getForeignKeyReferencesForTable; diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 60e2abf40ad2..685509a0f34a 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -216,7 +216,7 @@ class Query extends AbstractQuery { return result; } - run(sql, parameters) { + async run(sql, parameters) { const conn = this.connection; this.sql = sql; const method = this.getDatabaseMethod(); @@ -231,69 +231,67 @@ class Query extends AbstractQuery { } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => conn.serialize(async () => { const columnTypes = {}; - conn.serialize(() => { - const executeSql = () => { - if (sql.startsWith('-- ')) { - return resolve(); - } - const query = this; - // cannot use arrow function here because the function is bound to the statement - function afterExecute(executionError, results) { - try { - complete(); - // `this` is passed from sqlite, we have no control over this. - // eslint-disable-next-line no-invalid-this - resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); - return; - } catch (error) { - reject(error); - } + const executeSql = () => { + if (sql.startsWith('-- ')) { + return resolve(); + } + const query = this; + // cannot use arrow function here because the function is bound to the statement + function afterExecute(executionError, results) { + try { + complete(); + // `this` is passed from sqlite, we have no control over this. + // eslint-disable-next-line no-invalid-this + resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); + return; + } catch (error) { + reject(error); } + } - if (method === 'exec') { - // exec does not support bind parameter - conn[method](sql, afterExecute); - } else { - if (!parameters) parameters = []; - conn[method](sql, parameters, afterExecute); - } - return null; - }; + if (method === 'exec') { + // exec does not support bind parameter + conn[method](sql, afterExecute); + } else { + if (!parameters) parameters = []; + conn[method](sql, parameters, afterExecute); + } + return null; + }; - if (this.getDatabaseMethod() === 'all') { - let tableNames = []; - if (this.options && this.options.tableNames) { - tableNames = this.options.tableNames; - } else if (/FROM `(.*?)`/i.exec(this.sql)) { - tableNames.push(/FROM `(.*?)`/i.exec(this.sql)[1]); - } + if (this.getDatabaseMethod() === 'all') { + let tableNames = []; + if (this.options && this.options.tableNames) { + tableNames = this.options.tableNames; + } else if (/FROM `(.*?)`/i.exec(this.sql)) { + tableNames.push(/FROM `(.*?)`/i.exec(this.sql)[1]); + } - // If we already have the metadata for the table, there's no need to ask for it again - tableNames = tableNames.filter(tableName => !(tableName in columnTypes) && tableName !== 'sqlite_master'); + // If we already have the metadata for the table, there's no need to ask for it again + tableNames = tableNames.filter(tableName => !(tableName in columnTypes) && tableName !== 'sqlite_master'); - if (!tableNames.length) { - return executeSql(); - } - return Promise.all(tableNames.map(tableName => - new Promise(resolve => { - tableName = tableName.replace(/`/g, ''); - columnTypes[tableName] = {}; - - conn.all(`PRAGMA table_info(\`${tableName}\`)`, (err, results) => { - if (!err) { - for (const result of results) { - columnTypes[tableName][result.name] = result.type; - } - } - resolve(); - }); - }))).then(executeSql); + if (!tableNames.length) { + return executeSql(); } - return executeSql(); - }); - }); + await Promise.all(tableNames.map(tableName => + new Promise(resolve => { + tableName = tableName.replace(/`/g, ''); + columnTypes[tableName] = {}; + + conn.all(`PRAGMA table_info(\`${tableName}\`)`, (err, results) => { + if (!err) { + for (const result of results) { + columnTypes[tableName][result.name] = result.type; + } + } + resolve(); + }); + }))); + } + return executeSql(); + })); } parseConstraintsFromSql(sql) { @@ -419,24 +417,23 @@ class Query extends AbstractQuery { } } - handleShowIndexesQuery(data) { + async handleShowIndexesQuery(data) { // Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that! - return Promise.all(data.reverse().map(item => { + return Promise.all(data.reverse().map(async item => { item.fields = []; item.primary = false; item.unique = !!item.unique; item.constraintName = item.name; - return this.run(`PRAGMA INDEX_INFO(\`${item.name}\`)`).then(columns => { - for (const column of columns) { - item.fields[column.seqno] = { - attribute: column.name, - length: undefined, - order: undefined - }; - } + const columns = await this.run(`PRAGMA INDEX_INFO(\`${item.name}\`)`); + for (const column of columns) { + item.fields[column.seqno] = { + attribute: column.name, + length: undefined, + order: undefined + }; + } - return item; - }); + return item; })); } From 1bc36cade6e4db1080d47b5e0aa4fafcd16a0e91 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 26 Apr 2020 11:30:51 +0530 Subject: [PATCH 118/414] build: install peer dependencies --- package-lock.json | 43 +++++++++++++++++++++++++++++++++++-------- package.json | 2 ++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f43bd90d781..1c939d8d11c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1217,11 +1217,10 @@ "dev": true }, "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true }, "acorn-globals": { "version": "1.0.9", @@ -1231,6 +1230,15 @@ "optional": true, "requires": { "acorn": "^2.1.0" + }, + "dependencies": { + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, + "optional": true + } } }, "acorn-jsx": { @@ -3490,6 +3498,12 @@ "universalify": "^0.1.0" } }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -3740,6 +3754,12 @@ "graceful-fs": "^4.1.6" } }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, "repeating": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", @@ -5949,6 +5969,13 @@ "xml-name-validator": ">= 2.0.1 < 3.0.0" }, "dependencies": { + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "dev": true, + "optional": true + }, "parse5": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", @@ -7426,9 +7453,9 @@ "dev": true }, "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", + "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", "dev": true }, "marked-terminal": { diff --git a/package.json b/package.json index dcb344f21465..a4ac43c89b33 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@commitlint/config-angular": "^8.3.4", "@types/node": "^12.12.37", "@types/validator": "^10.11.0", + "acorn": "^7.1.1", "chai": "^4.x", "chai-as-promised": "^7.x", "chai-datetime": "^1.x", @@ -70,6 +71,7 @@ "lint-staged": "^10.1.7", "mariadb": "^2.3.1", "markdownlint-cli": "^0.22.0", + "marked": "^1.0.0", "mocha": "^7.1.1", "mysql2": "^1.6.5", "nyc": "^15.0.0", From 541d2facc691faa93e60538eedfda7166a59eb5b Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 26 Apr 2020 16:31:59 +0530 Subject: [PATCH 119/414] test(transaction): row locking (#12174) --- docker-compose.yml | 2 +- lib/dialects/abstract/query-generator.js | 0 lib/dialects/abstract/query.js | 0 lib/dialects/postgres/query-generator.js | 0 lib/sequelize.js | 0 test/integration/include.test.js | 0 test/integration/include/separate.test.js | 0 test/integration/model.test.js | 0 test/integration/sequelize.test.js | 0 test/integration/transaction.test.js | 138 +++++++++++----------- test/unit/sql/generateJoin.test.js | 0 test/unit/sql/order.test.js | 0 test/unit/sql/select.test.js | 0 test/unit/sql/where.test.js | 0 14 files changed, 70 insertions(+), 70 deletions(-) mode change 100755 => 100644 lib/dialects/abstract/query-generator.js mode change 100755 => 100644 lib/dialects/abstract/query.js mode change 100755 => 100644 lib/dialects/postgres/query-generator.js mode change 100755 => 100644 lib/sequelize.js mode change 100755 => 100644 test/integration/include.test.js mode change 100755 => 100644 test/integration/include/separate.test.js mode change 100755 => 100644 test/integration/model.test.js mode change 100755 => 100644 test/integration/sequelize.test.js mode change 100755 => 100644 test/unit/sql/generateJoin.test.js mode change 100755 => 100644 test/unit/sql/order.test.js mode change 100755 => 100644 test/unit/sql/select.test.js mode change 100755 => 100644 test/unit/sql/where.test.js diff --git a/docker-compose.yml b/docker-compose.yml index 79635d9b10b3..31ea58d455e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,7 +43,7 @@ services: ports: - "8992:5432" container_name: postgres-12 - + # MariaDB mariadb-103: image: mariadb:10.3 diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js old mode 100755 new mode 100644 diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js old mode 100755 new mode 100644 diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js old mode 100755 new mode 100644 diff --git a/lib/sequelize.js b/lib/sequelize.js old mode 100755 new mode 100644 diff --git a/test/integration/include.test.js b/test/integration/include.test.js old mode 100755 new mode 100644 diff --git a/test/integration/include/separate.test.js b/test/integration/include/separate.test.js old mode 100755 new mode 100644 diff --git a/test/integration/model.test.js b/test/integration/model.test.js old mode 100755 new mode 100644 diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js old mode 100755 new mode 100644 diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 78cd334b3d92..3ac395726c58 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -108,24 +108,34 @@ if (current.dialect.supports.transactions) { //Promise rejection test is specific to postgres if (dialect === 'postgres') { - it('do not rollback if already committed', function() { + it('do not rollback if already committed', async function() { const SumSumSum = this.sequelize.define('transaction', { - value: { - type: Support.Sequelize.DECIMAL(10, 3), - field: 'value' - } - }), - transTest = val => { - return this.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }, t => { - return SumSumSum.sum('value', { transaction: t }).then(() => { - return SumSumSum.create({ value: -val }, { transaction: t }); - }); - }); - }; - // Attention: this test is a bit racy. If you find a nicer way to test this: go ahead - return SumSumSum.sync({ force: true }).then(() => { - return expect(Promise.all([transTest(80), transTest(80), transTest(80)])).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'); + value: { + type: Support.Sequelize.DECIMAL(10, 3), + field: 'value' + } }); + + const transTest = val => { + return this.sequelize.transaction({ + isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE + }, async t => { + await SumSumSum.sum('value', { transaction: t }); + await SumSumSum.create({ value: -val }, { transaction: t }); + }); + }; + + await SumSumSum.sync({ force: true }); + + await expect( + Promise.all([ + transTest(80), + transTest(80) + ]) + ).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'); + + // one row was created, another was rejected due to rollback + expect(await SumSumSum.count()).to.equal(1); }); } @@ -821,64 +831,54 @@ if (current.dialect.supports.transactions) { }); } - it('supports for share', function() { + it('supports for share', async function() { const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }), - t1Spy = sinon.spy(), - t2FindSpy = sinon.spy(), - t2UpdateSpy = sinon.spy(); + username: Support.Sequelize.STRING, + awesome: Support.Sequelize.BOOLEAN + }, { timestamps: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findOne({ - where: { - username: 'jan' - }, - lock: t1.LOCK.SHARE, - transaction: t1 - }).then(t1Jan => { - return this.sequelize.transaction({ - isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED - }).then(t2 => { - return Promise.all([User.findOne({ - where: { - username: 'jan' - }, - transaction: t2 - }).then(t2Jan => { - t2FindSpy(); - return t2Jan.update({ - awesome: false - }, { - transaction: t2 - }).then(() => { - t2UpdateSpy(); - return t2.commit().then(() => { - expect(t2FindSpy).to.have.been.calledBefore(t1Spy); // The find call should have returned - expect(t2UpdateSpy).to.have.been.calledAfter(t1Spy); // But the update call should not happen before the first transaction has committed - }); - }); - }), t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - return delay(2000).then(() => { - t1Spy(); - return t1.commit(); - }); - })]); - }); - }); - }); + const t1CommitSpy = sinon.spy(); + const t2FindSpy = sinon.spy(); + const t2UpdateSpy = sinon.spy(); + + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'jan' }); + + const t1 = await this.sequelize.transaction(); + const t1Jan = await User.findByPk(user.id, { + lock: t1.LOCK.SHARE, + transaction: t1 }); + + const t2 = await this.sequelize.transaction({ + isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED + }); + + await Promise.all([ + User.findByPk(user.id, { + transaction: t2 + }).then(async t2Jan => { + t2FindSpy(); + + await t2Jan.update({ awesome: false }, { transaction: t2 }); + t2UpdateSpy(); + + await t2.commit(); + }), + t1Jan.update({ awesome: true }, { transaction: t1 }).then(async () => { + await delay(2000); + t1CommitSpy(); + await t1.commit(); + }) + ]); + + // (t2) find call should have returned before (t1) commit + expect(t2FindSpy).to.have.been.calledBefore(t1CommitSpy); + + // But (t2) update call should not happen before (t1) commit + expect(t2UpdateSpy).to.have.been.calledAfter(t1CommitSpy); }); }); } }); - } diff --git a/test/unit/sql/generateJoin.test.js b/test/unit/sql/generateJoin.test.js old mode 100755 new mode 100644 diff --git a/test/unit/sql/order.test.js b/test/unit/sql/order.test.js old mode 100755 new mode 100644 diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js old mode 100755 new mode 100644 diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js old mode 100755 new mode 100644 From f1e451e0d3a21d8945321ff5195c6a43ccd905d8 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 26 Apr 2020 17:25:57 +0530 Subject: [PATCH 120/414] refactor: remove sequelize.import helper (#12175) --- docs/manual/other-topics/upgrade-to-v6.md | 6 ++-- lib/sequelize.js | 34 ----------------------- test/integration/sequelize.test.js | 22 --------------- 3 files changed, 4 insertions(+), 58 deletions(-) diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index 3ee554b00b2e..b45985a4b516 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -20,9 +20,11 @@ You should now use [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) packag Sequelize.useCLS(namespace); ``` -### Promise +### Sequelize -Bluebird has been removed. Public API now returns native promises. `Sequelize.Promise` is no longer available. +- Bluebird has been removed. Public API now returns native promises. +- `Sequelize.Promise` is no longer available. +- `sequelize.import` method has been removed. ### Model diff --git a/lib/sequelize.js b/lib/sequelize.js index b7ecde809e93..f10e50eea059 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -346,8 +346,6 @@ class Sequelize { this.modelManager = new ModelManager(this); this.connectionManager = this.dialect.connectionManager; - this.importCache = {}; - Sequelize.runHooks('afterInit', this); } @@ -462,38 +460,6 @@ class Sequelize { return !!this.modelManager.models.find(model => model.name === modelName); } - /** - * Imports a model defined in another file. Imported models are cached, so multiple - * calls to import with the same path will not load the file multiple times. - * - * @tutorial https://github.com/sequelize/express-example - * - * @param {string} importPath The path to the file that holds the model you want to import. If the part is relative, it will be resolved relatively to the calling file - * - * @returns {Model} Imported model, returned from cache if was already imported - */ - import(importPath) { - // is it a relative path? - if (path.normalize(importPath) !== path.resolve(importPath)) { - // make path relative to the caller - const callerFilename = Utils.stack()[1].getFileName(); - const callerPath = path.dirname(callerFilename); - - importPath = path.resolve(callerPath, importPath); - } - - if (!this.importCache[importPath]) { - let defineCall = arguments.length > 1 ? arguments[1] : require(importPath); - if (typeof defineCall === 'object') { - // ES6 module compatibility - defineCall = defineCall.default; - } - this.importCache[importPath] = defineCall(this, DataTypes); - } - - return this.importCache[importPath]; - } - /** * Execute a query on the DB, optionally bypassing all the Sequelize goodness. * diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index 57b81b2a0be5..7f7934c6aae4 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -1224,28 +1224,6 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); }); - describe('import', () => { - it('imports a dao definition from a file absolute path', function() { - const Project = this.sequelize.import('assets/project'); - expect(Project).to.exist; - }); - - it('imports a dao definition with a default export', function() { - const Project = this.sequelize.import('assets/es6project'); - expect(Project).to.exist; - }); - - it('imports a dao definition from a function', function() { - const Project = this.sequelize.import('Project', (sequelize, DataTypes) => { - return sequelize.define(`Project${parseInt(Math.random() * 9999999999999999, 10)}`, { - name: DataTypes.STRING - }); - }); - - expect(Project).to.exist; - }); - }); - describe('define', () => { it('raises an error if no values are defined', function() { expect(() => { From 5dcc0d516f8b27eebf6723e8228a3558dc26d5de Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Mon, 27 Apr 2020 23:42:44 -0500 Subject: [PATCH 121/414] fix(mssql): tedious v9 requires connect call (#12182) --- lib/dialects/mssql/connection-manager.js | 3 ++ .../dialects/mssql/connection-manager.test.js | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index a5b3ad6a7577..32f336bf3e5e 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -61,6 +61,9 @@ class ConnectionManager extends AbstractConnectionManager { try { return await new Promise((resolve, reject) => { const connection = new this.lib.Connection(connectionConfig); + if (connection.state === connection.STATE.INITIALIZED) { + connection.connect(); + } connection.queue = new AsyncQueue(); connection.lib = this.lib; diff --git a/test/unit/dialects/mssql/connection-manager.test.js b/test/unit/dialects/mssql/connection-manager.test.js index bad513bfceb4..ccf57ecb6bca 100644 --- a/test/unit/dialects/mssql/connection-manager.test.js +++ b/test/unit/dialects/mssql/connection-manager.test.js @@ -39,6 +39,8 @@ if (dialect === 'mssql') { it('connectionManager._connect() does not delete `domain` from config.dialectOptions', function() { this.connectionStub.returns({ + STATE: {}, + state: '', once(event, cb) { if (event === 'connect') { setTimeout(() => { @@ -58,6 +60,8 @@ if (dialect === 'mssql') { it('connectionManager._connect() should reject if end was called and connect was not', function() { this.connectionStub.returns({ + STATE: {}, + state: '', once(event, cb) { if (event === 'end') { setTimeout(() => { @@ -75,5 +79,29 @@ if (dialect === 'mssql') { expect(err.parent.message).to.equal('Connection was closed by remote server'); }); }); + + it('connectionManager._connect() should call connect if state is initialized', function() { + const connectStub = sinon.stub(); + const INITIALIZED = { name: 'INITIALIZED' }; + this.connectionStub.returns({ + STATE: { INITIALIZED }, + state: INITIALIZED, + connect: connectStub, + once(event, cb) { + if (event === 'connect') { + setTimeout(() => { + cb(); + }, 500); + } + }, + removeListener: () => {}, + on: () => {} + }); + + return this.instance.dialect.connectionManager._connect(this.config) + .then(() => { + expect(connectStub.called).to.equal(true); + }); + }); }); } From a5a60f9ff1ca0b0e8d4743f82b4047227153fadb Mon Sep 17 00:00:00 2001 From: Luis Date: Wed, 29 Apr 2020 00:56:16 -0500 Subject: [PATCH 122/414] docs(assocs): use and instead of 'a nd' (#12191) --- docs/manual/core-concepts/assocs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md index bebbc8af5136..495c809eb656 100644 --- a/docs/manual/core-concepts/assocs.md +++ b/docs/manual/core-concepts/assocs.md @@ -229,7 +229,7 @@ Team.hasMany(Player, { Player.belongsTo(Team); ``` -Like One-To-One relationships, `ON DELETE` defaults to `SET NULL`a nd `ON UPDATE` defaults to `CASCADE`. +Like One-To-One relationships, `ON DELETE` defaults to `SET NULL` and `ON UPDATE` defaults to `CASCADE`. ## Many-To-Many relationships From e78da7f9c5ab34e285cc0c07cb53ba4a28b31e17 Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Sat, 2 May 2020 03:00:45 -0300 Subject: [PATCH 123/414] docs(association): use correct scope name (#12204) --- docs/manual/advanced-association-concepts/association-scopes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/advanced-association-concepts/association-scopes.md b/docs/manual/advanced-association-concepts/association-scopes.md index cfd7353a546c..42224f5e0cf6 100644 --- a/docs/manual/advanced-association-concepts/association-scopes.md +++ b/docs/manual/advanced-association-concepts/association-scopes.md @@ -58,7 +58,7 @@ Bar.addScope('open', { } }); Foo.hasMany(Bar); -Foo.hasMany(Bar.scope('testTitleScope'), { as: 'openBars' }); +Foo.hasMany(Bar.scope('open'), { as: 'openBars' }); ``` With the above code, `myFoo.getOpenBars()` yields the same SQL shown above. \ No newline at end of file From 4c674e299b1a689d6ec8aee660e06c2f423307a4 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 2 May 2020 14:29:29 +0530 Subject: [PATCH 124/414] fix(scope): don't modify original scope definition (#12207) --- lib/model.js | 3 ++- test/unit/model/scope.test.js | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/model.js b/lib/model.js index 21cd5420ad6c..cf04f04dddf5 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1562,7 +1562,8 @@ class Model { if (scope) { this._conformIncludes(scope, this); - this._assignOptions(self._scope, scope); + // clone scope so it doesn't get modified + this._assignOptions(self._scope, Utils.cloneDeep(scope)); self._scopeNames.push(scopeName ? scopeName : 'defaultScope'); } else { throw new sequelizeErrors.SequelizeScopeError(`Invalid scope ${scopeName} called.`); diff --git a/test/unit/model/scope.test.js b/test/unit/model/scope.test.js index 87e471a773fb..c479cc19de7e 100644 --- a/test/unit/model/scope.test.js +++ b/test/unit/model/scope.test.js @@ -207,6 +207,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); + it('should be keep original scope definition clean', () => { + expect(Company.scope('projects', 'users', 'alsoUsers')._scope).to.deep.equal({ + include: [ + { model: Project }, + { model: User, where: { something: 42 } } + ] + }); + + expect(Company.options.scopes.alsoUsers).to.deep.equal({ + include: [ + { model: User, where: { something: 42 } } + ] + }); + + expect(Company.options.scopes.users).to.deep.equal({ + include: [ + { model: User } + ] + }); + }); + it('should be able to override the default scope', () => { expect(Company.scope('somethingTrue')._scope).to.deep.equal(scopes.somethingTrue); }); @@ -516,4 +537,4 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); }); -}); \ No newline at end of file +}); From 8d9e58b42a070736c2632b7386de10a2e22f91c8 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 2 May 2020 15:09:07 +0530 Subject: [PATCH 125/414] fix(sync): pass options to all query methods (#12208) --- lib/model.js | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/model.js b/lib/model.js index cf04f04dddf5..81b3badd79ca 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1284,23 +1284,25 @@ class Model { if (options.force) { await this.drop(options); } - await this.QueryInterface.createTable(this.getTableName(options), attributes, options, this); + + const tableName = this.getTableName(options); + + await this.QueryInterface.createTable(tableName, attributes, options, this); if (options.alter) { const tableInfos = await Promise.all([ - this.QueryInterface.describeTable(this.getTableName(options)), - this.QueryInterface.getForeignKeyReferencesForTable(this.getTableName(options)) + this.QueryInterface.describeTable(tableName, options), + this.QueryInterface.getForeignKeyReferencesForTable(tableName, options) ]); const columns = tableInfos[0]; // Use for alter foreign keys const foreignKeyReferences = tableInfos[1]; - const removedConstraints = {}; for (const columnName in attributes) { if (!Object.prototype.hasOwnProperty.call(attributes, columnName)) continue; if (!columns[columnName] && !columns[attributes[columnName].field]) { - await this.QueryInterface.addColumn(this.getTableName(options), attributes[columnName].field || columnName, attributes[columnName]); + await this.QueryInterface.addColumn(tableName, attributes[columnName].field || columnName, attributes[columnName], options); } } @@ -1309,7 +1311,7 @@ class Model { if (!Object.prototype.hasOwnProperty.call(columns, columnName)) continue; const currentAttribute = rawAttributes[columnName]; if (!currentAttribute) { - await this.QueryInterface.removeColumn(this.getTableName(options), columnName, options); + await this.QueryInterface.removeColumn(tableName, columnName, options); continue; } if (currentAttribute.primaryKey) continue; @@ -1329,16 +1331,16 @@ class Model { && (schema ? foreignKeyReference.referencedTableSchema === schema : true) && !removedConstraints[constraintName]) { // Remove constraint on foreign keys. - await this.QueryInterface.removeConstraint(this.getTableName(options), constraintName, options); + await this.QueryInterface.removeConstraint(tableName, constraintName, options); removedConstraints[constraintName] = true; } } } - await this.QueryInterface.changeColumn(this.getTableName(options), columnName, currentAttribute); + await this.QueryInterface.changeColumn(tableName, columnName, currentAttribute, options); } } } - let indexes = await this.QueryInterface.showIndex(this.getTableName(options), options); + let indexes = await this.QueryInterface.showIndex(tableName, options); indexes = this._indexes.filter(item1 => !indexes.some(item2 => item1.name === item2.name) ).sort((index1, index2) => { @@ -1352,20 +1354,13 @@ class Model { }); for (const index of indexes) { - await this.QueryInterface.addIndex( - this.getTableName(options), - Object.assign({ - logging: options.logging, - benchmark: options.benchmark, - transaction: options.transaction, - schema: options.schema - }, index), - this.tableName - ); + await this.QueryInterface.addIndex(tableName, Object.assign({}, options, index)); } + if (options.hooks) { await this.runHooks('afterSync', options); } + return this; } From 6fb74c8ae6b034edc2a07962270a2e9751f4174a Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 2 May 2020 16:59:57 +0530 Subject: [PATCH 126/414] fix(query-generator): handle literal for substring based operators (#12210) --- lib/dialects/abstract/query-generator.js | 20 +++++++++++++------- test/unit/sql/where.test.js | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 3924c057f0c2..243fdf032ce8 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -425,7 +425,7 @@ class QueryGenerator { * @param {object} incrementAmountsByField A plain-object with attribute-value-pairs * @param {object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs * @param {object} options - * + * * @private */ arithmeticQuery(operator, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { @@ -1724,7 +1724,7 @@ class QueryGenerator { if (this.options.minifyAliases && asRight.length > 63) { const alias = `%${topLevelInfo.options.includeAliases.size}`; - + topLevelInfo.options.includeAliases.set(alias, asRight); } @@ -2593,14 +2593,20 @@ class QueryGenerator { return this._joinKeyValue(key, value.map(identifier => this.quoteIdentifier(identifier)).join('.'), comparator, options.prefix); case Op.startsWith: - comparator = this.OperatorMap[Op.like]; - return this._joinKeyValue(key, this.escape(`${value}%`), comparator, options.prefix); case Op.endsWith: - comparator = this.OperatorMap[Op.like]; - return this._joinKeyValue(key, this.escape(`%${value}`), comparator, options.prefix); case Op.substring: comparator = this.OperatorMap[Op.like]; - return this._joinKeyValue(key, this.escape(`%${value}%`), comparator, options.prefix); + + if (value instanceof Utils.Literal) { + value = value.val; + } + + let pattern = `${value}%`; + + if (prop === Op.endsWith) pattern = `%${value}`; + if (prop === Op.substring) pattern = `%${value}%`; + + return this._joinKeyValue(key, this.escape(pattern), comparator, options.prefix); } const escapeOptions = { diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js index 4d303130e09a..3d96ed6e4efd 100644 --- a/test/unit/sql/where.test.js +++ b/test/unit/sql/where.test.js @@ -441,6 +441,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: "[username] LIKE 'swagger%'", mssql: "[username] LIKE N'swagger%'" }); + + testsql('username', { + [Op.startsWith]: current.literal('swagger') + }, { + default: "[username] LIKE 'swagger%'", + mssql: "[username] LIKE N'swagger%'" + }); }); describe('Op.endsWith', () => { @@ -450,6 +457,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: "[username] LIKE '%swagger'", mssql: "[username] LIKE N'%swagger'" }); + + testsql('username', { + [Op.endsWith]: current.literal('swagger') + }, { + default: "[username] LIKE '%swagger'", + mssql: "[username] LIKE N'%swagger'" + }); }); describe('Op.substring', () => { @@ -459,6 +473,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: "[username] LIKE '%swagger%'", mssql: "[username] LIKE N'%swagger%'" }); + + testsql('username', { + [Op.substring]: current.literal('swagger') + }, { + default: "[username] LIKE '%swagger%'", + mssql: "[username] LIKE N'%swagger%'" + }); }); describe('Op.between', () => { From 5a4d260000bcd147b6a3bb2b381bbd3fe3b34529 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 2 May 2020 17:25:04 +0530 Subject: [PATCH 127/414] fix(model.reload): ignore options.where and always use this.where() (#12211) --- lib/model.js | 5 +++-- test/integration/instance/reload.test.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/model.js b/lib/model.js index 81b3badd79ca..c25f194f1d81 100644 --- a/lib/model.js +++ b/lib/model.js @@ -4049,8 +4049,9 @@ class Model { * @returns {Promise} */ async reload(options) { - options = Utils.defaults({}, options, { - where: this.where(), + options = Utils.defaults({ + where: this.where() + }, options, { include: this._options.include || null }); diff --git a/test/integration/instance/reload.test.js b/test/integration/instance/reload.test.js index af00f197c71c..b7ff5cb233f2 100644 --- a/test/integration/instance/reload.test.js +++ b/test/integration/instance/reload.test.js @@ -91,6 +91,20 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); }); + it('should use default internal where', async function() { + const user = await this.User.create({ username: 'Balak Bukhara' }); + const anotherUser = await this.User.create({ username: 'John Smith' }); + + const primaryKey = user.get('id'); + + await user.reload(); + expect(user.get('id')).to.equal(primaryKey); + + // options.where should be ignored + await user.reload({ where: { id: anotherUser.get('id') } }); + expect(user.get('id')).to.equal(primaryKey).and.not.equal(anotherUser.get('id')); + }); + it('should update the values on all references to the DAO', function() { return this.User.create({ username: 'John Doe' }).then(originalUser => { return this.User.findByPk(originalUser.id).then(updater => { From 777ec0987ea3d74dc29c31da6d6ba5886de59300 Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Sun, 3 May 2020 01:31:07 -0300 Subject: [PATCH 128/414] fix(mssql): use uppercase for engine table and columns (#12212) --- lib/dialects/mssql/query-generator.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 6540d1727d35..67a7c886879f 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -201,12 +201,12 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { 'INNER JOIN', 'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA', 'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ', - 'cu.column_name, tc.constraint_type ', - 'FROM information_schema.TABLE_CONSTRAINTS tc ', - 'JOIN information_schema.KEY_COLUMN_USAGE cu ', + 'cu.column_name, tc.CONSTRAINT_TYPE ', + 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ', + 'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu ', 'ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name ', 'and tc.constraint_name=cu.constraint_name ', - 'and tc.constraint_type=\'PRIMARY KEY\') pk ', + 'and tc.CONSTRAINT_TYPE=\'PRIMARY KEY\') pk ', 'ON pk.table_schema=c.table_schema ', 'AND pk.table_name=c.table_name ', 'AND pk.column_name=c.column_name ', From 6640ba2a01b63d842ff517d981ac1e56d3772b4f Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 2 May 2020 21:43:54 -0700 Subject: [PATCH 129/414] refactor: use object spread instead of Object.assign (#12213) --- .eslintrc.json | 3 +- lib/associations/belongs-to-many.js | 41 +++--- lib/associations/belongs-to.js | 7 +- lib/associations/has-many.js | 14 +- lib/associations/has-one.js | 9 +- lib/associations/mixin.js | 4 +- lib/dialects/abstract/query-generator.js | 17 +-- .../abstract/query-generator/operators.js | 2 +- lib/dialects/abstract/query.js | 7 +- lib/dialects/mariadb/connection-manager.js | 7 +- lib/dialects/mariadb/query-generator.js | 7 +- lib/dialects/mariadb/query.js | 2 +- lib/dialects/mssql/query-generator.js | 8 +- lib/dialects/mssql/query-interface.js | 2 +- lib/dialects/mysql/connection-manager.js | 7 +- lib/dialects/mysql/query-generator.js | 19 +-- lib/dialects/mysql/query-interface.js | 12 +- lib/dialects/mysql/query.js | 2 +- lib/dialects/postgres/query-generator.js | 11 +- lib/dialects/postgres/query-interface.js | 4 +- lib/dialects/sqlite/query-interface.js | 12 +- lib/model.js | 130 +++++++++--------- lib/query-interface.js | 60 ++++---- lib/sequelize.js | 20 +-- lib/transaction.js | 7 +- lib/utils/logger.js | 7 +- .../dialects/mariadb/errors.test.js | 2 +- .../integration/dialects/mysql/errors.test.js | 2 +- .../postgres/connection-manager.test.js | 2 +- .../dialects/postgres/query.test.js | 4 +- test/integration/sequelize.test.js | 12 +- test/integration/sequelize/deferrable.test.js | 2 +- test/support.js | 2 +- .../dialects/mariadb/query-generator.test.js | 2 +- .../dialects/mysql/query-generator.test.js | 2 +- .../dialects/postgres/query-generator.test.js | 2 +- .../dialects/sqlite/query-generator.test.js | 2 +- 37 files changed, 233 insertions(+), 222 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index d7307d5ef4e9..38d78cf4ea61 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -94,7 +94,8 @@ "no-unused-expressions": "error", "no-sequences": "error", "no-self-compare": "error", - "no-case-declarations": "off" + "no-case-declarations": "off", + "prefer-object-spread": "error" }, "settings": { "jsdoc": { diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index f261d2fba1f7..8a70c1d256ee 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -73,7 +73,7 @@ class BelongsToMany extends Association { this.associationType = 'BelongsToMany'; this.targetAssociation = null; this.sequelize = source.sequelize; - this.through = Object.assign({}, this.options.through); + this.through = { ...this.options.through }; this.isMultiAssociation = true; this.doubleLinked = false; @@ -153,7 +153,7 @@ class BelongsToMany extends Association { } } - this.options = Object.assign(this.options, _.pick(this.through.model.options, [ + Object.assign(this.options, _.pick(this.through.model.options, [ 'timestamps', 'createdAt', 'updatedAt', 'deletedAt', 'paranoid' ])); @@ -335,8 +335,8 @@ class BelongsToMany extends Association { if (!targetAttribute.onUpdate) targetAttribute.onUpdate = 'CASCADE'; } - this.through.model.rawAttributes[this.foreignKey] = Object.assign(this.through.model.rawAttributes[this.foreignKey], sourceAttribute); - this.through.model.rawAttributes[this.otherKey] = Object.assign(this.through.model.rawAttributes[this.otherKey], targetAttribute); + Object.assign(this.through.model.rawAttributes[this.foreignKey], sourceAttribute); + Object.assign(this.through.model.rawAttributes[this.otherKey], targetAttribute); this.through.model.refreshAttributes(); @@ -513,13 +513,13 @@ class BelongsToMany extends Association { instances = [instances]; } - options = Object.assign({ - raw: true - }, options, { + options = { + raw: true, + ...options, scope: false, attributes: [this.targetKey], joinTableAttributes: [] - }); + }; const instancePrimaryKeys = instances.map(instance => { if (instance instanceof this.target) { @@ -562,16 +562,16 @@ class BelongsToMany extends Association { const targetKey = this.targetKey; const identifier = this.identifier; const foreignIdentifier = this.foreignIdentifier; - let where = {}; if (newAssociatedObjects === null) { newAssociatedObjects = []; } else { newAssociatedObjects = this.toInstanceArray(newAssociatedObjects); } - - where[identifier] = sourceInstance.get(sourceKey); - where = Object.assign(where, this.through.scope); + const where = { + [identifier]: sourceInstance.get(sourceKey), + ...this.through.scope + }; const updateAssociations = currentRows => { const obsoleteAssociations = []; @@ -613,10 +613,11 @@ class BelongsToMany extends Association { if (obsoleteAssociations.length > 0) { promises.push( this.through.model.destroy(_.defaults({ - where: Object.assign({ + where: { [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]) - }, this.through.scope) + [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]), + ...this.through.scope + } }, options)) ); } @@ -631,12 +632,11 @@ class BelongsToMany extends Association { attributes = _.defaults(attributes, unassociatedObject[this.through.model.name], defaultAttributes); Object.assign(attributes, this.through.scope); - attributes = Object.assign(attributes, this.through.scope); return attributes; }); - promises.push(this.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); + promises.push(this.through.model.bulkCreate(bulk, { validate: true, ...options })); } return Promise.all(promises); @@ -680,11 +680,10 @@ class BelongsToMany extends Association { const where = { [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: newInstances.map(newInstance => newInstance.get(targetKey)) + [foreignIdentifier]: newInstances.map(newInstance => newInstance.get(targetKey)), + ...association.through.scope }; - Object.assign(where, association.through.scope); - const updateAssociations = currentRows => { const promises = []; const unassociatedObjects = []; @@ -717,7 +716,7 @@ class BelongsToMany extends Association { return attributes; }); - promises.push(association.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); + promises.push(association.through.model.bulkCreate(bulk, { validate: true, ...options })); } for (const assoc of changedAssociations) { diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index 59efa360d936..a0cd340d0e7c 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -201,11 +201,12 @@ class BelongsTo extends Association { if (options.save === false) return; - options = Object.assign({ + options = { fields: [this.foreignKey], allowNull: [this.foreignKey], - association: true - }, options); + association: true, + ...options + }; // passes the changed field to save, so only that field get updated. return await sourceInstance.save(options); diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 723533e92d32..814a2072b6c9 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -180,7 +180,7 @@ class HasMany extends Association { instances = undefined; } - options = Object.assign({}, options); + options = { ...options }; if (this.scope) { Object.assign(where, this.scope); @@ -284,11 +284,12 @@ class HasMany extends Association { targetInstances = [targetInstances]; } - options = Object.assign({}, options, { + options = { + ...options, scope: false, attributes: [this.target.primaryKeyAttribute], raw: true - }); + }; where[Op.or] = targetInstances.map(instance => { if (instance instanceof this.target) { @@ -399,12 +400,13 @@ class HasMany extends Association { async add(sourceInstance, targetInstances, options = {}) { if (!targetInstances) return Promise.resolve(); - const update = {}; targetInstances = this.toInstanceArray(targetInstances); - update[this.foreignKey] = sourceInstance.get(this.sourceKey); - Object.assign(update, this.scope); + const update = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + ...this.scope + }; const where = { [this.target.primaryKeyAttribute]: targetInstances.map(unassociatedObject => diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index 9eaded98c637..cad9f3af36e8 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -190,9 +190,7 @@ class HasOne extends Association { * @returns {Promise} */ async set(sourceInstance, associatedInstance, options) { - options = Object.assign({}, options, { - scope: false - }); + options = { ...options, scope: false }; const oldInstance = await sourceInstance[this.accessors.get](options); // TODO Use equals method once #5605 is resolved @@ -203,11 +201,12 @@ class HasOne extends Association { if (oldInstance && !alreadyAssociated) { oldInstance[this.foreignKey] = null; - await oldInstance.save(Object.assign({}, options, { + await oldInstance.save({ + ...options, fields: [this.foreignKey], allowNull: [this.foreignKey], association: true - })); + }); } if (associatedInstance && !alreadyAssociated) { if (!(associatedInstance instanceof this.target)) { diff --git a/lib/associations/mixin.js b/lib/associations/mixin.js index 062de0edea92..76646e2d93b4 100644 --- a/lib/associations/mixin.js +++ b/lib/associations/mixin.js @@ -24,7 +24,7 @@ const Mixin = { options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); options.useHooks = options.hooks; - options = Object.assign(options, _.omit(source.options, ['hooks'])); + Object.assign(options, _.omit(source.options, ['hooks'])); if (options.useHooks) { this.runHooks('beforeAssociate', { source, target, type: HasMany }, options); @@ -55,7 +55,7 @@ const Mixin = { options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); options.useHooks = options.hooks; options.timestamps = options.timestamps === undefined ? this.sequelize.options.timestamps : options.timestamps; - options = Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope'])); + Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope'])); if (options.useHooks) { this.runHooks('beforeAssociate', { source, target, type: BelongsToMany }, options); diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 243fdf032ce8..de83ad02b242 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1204,7 +1204,7 @@ class QueryGenerator { if (!mainTable.as) { mainTable.as = mainTable.quotedName; } - const where = Object.assign({}, options.where); + const where = { ...options.where }; let groupedLimitOrder, whereKey, include, @@ -1224,9 +1224,10 @@ class QueryGenerator { association: options.groupedLimit.on.manyFromSource, duplicating: false, // The UNION'ed query may contain duplicates, but each sub-query cannot required: true, - where: Object.assign({ - [Op.placeholder]: true - }, options.groupedLimit.through && options.groupedLimit.through.where) + where: { + [Op.placeholder]: true, + ...options.groupedLimit.through && options.groupedLimit.through.where + } }], model }); @@ -1932,7 +1933,7 @@ class QueryGenerator { return; } - nestedIncludes = [Object.assign({}, child, { include: nestedIncludes, attributes: [] })]; + nestedIncludes = [{ ...child, include: nestedIncludes, attributes: [] }]; child = parent; } @@ -2009,7 +2010,7 @@ class QueryGenerator { * are preserved. */ _getRequiredClosure(include) { - const copy = Object.assign({}, include, { attributes: [], include: [] }); + const copy = { ...include, attributes: [], include: [] }; if (Array.isArray(include.include)) { copy.include = include.include @@ -2265,7 +2266,7 @@ class QueryGenerator { const tmp = {}; const field = options.model.rawAttributes[keyParts[0]]; _.set(tmp, keyParts.slice(1), value); - return this.whereItemQuery(field.field || keyParts[0], tmp, Object.assign({ field }, options)); + return this.whereItemQuery(field.field || keyParts[0], tmp, { field, ...options }); } } @@ -2429,7 +2430,7 @@ class QueryGenerator { const where = { [op]: value[op] }; - items.push(this.whereItemQuery(key, where, Object.assign({}, options, { json: false }))); + items.push(this.whereItemQuery(key, where, { ...options, json: false })); }); _.forOwn(value, (item, prop) => { diff --git a/lib/dialects/abstract/query-generator/operators.js b/lib/dialects/abstract/query-generator/operators.js index 0bff9bad7c74..baef21654810 100644 --- a/lib/dialects/abstract/query-generator/operators.js +++ b/lib/dialects/abstract/query-generator/operators.js @@ -51,7 +51,7 @@ const OperatorHelpers = { if (!aliases || _.isEmpty(aliases)) { this.OperatorsAliasMap = false; } else { - this.OperatorsAliasMap = Object.assign({}, aliases); + this.OperatorsAliasMap = { ...aliases }; } }, diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 991d79db61ae..221266911ef2 100644 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -15,12 +15,13 @@ class AbstractQuery { this.instance = options.instance; this.model = options.model; this.sequelize = sequelize; - this.options = Object.assign({ + this.options = { plain: false, raw: false, // eslint-disable-next-line no-console - logging: console.log - }, options || {}); + logging: console.log, + ...options + }; this.checkLoggingOption(); } diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index e72ffb8856d7..eba46cd3eb88 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -68,13 +68,10 @@ class ConnectionManager extends AbstractConnectionManager { typeCast: ConnectionManager._typecast.bind(this), bigNumberStrings: false, supportBigNumbers: true, - foundRows: false + foundRows: false, + ...config.dialectOptions }; - if (config.dialectOptions) { - Object.assign(connectionConfig, config.dialectOptions); - } - if (!this.sequelize.config.keepDefaultTimezone) { // set timezone for this connection if (connectionConfig.initSql) { diff --git a/lib/dialects/mariadb/query-generator.js b/lib/dialects/mariadb/query-generator.js index 4c4cdb689418..2d2048dc59ec 100644 --- a/lib/dialects/mariadb/query-generator.js +++ b/lib/dialects/mariadb/query-generator.js @@ -5,10 +5,11 @@ const Utils = require('./../../utils'); class MariaDBQueryGenerator extends MySQLQueryGenerator { createSchema(schema, options) { - options = Object.assign({ + options = { charset: null, - collate: null - }, options || {}); + collate: null, + ...options + }; return Utils.joinSQLFragments([ 'CREATE SCHEMA IF NOT EXISTS', diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index 2e835970a60e..35c2993d8c1b 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -14,7 +14,7 @@ const debug = logger.debugContext('sql:mariadb'); class Query extends AbstractQuery { constructor(connection, sequelize, options) { - super(connection, sequelize, Object.assign({ showWarnings: false }, options)); + super(connection, sequelize, { showWarnings: false, ...options }); } static formatBindParameters(sql, values, dialect) { diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 67a7c886879f..b8ea6cde99a2 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -16,9 +16,7 @@ const throwMethodUndefined = function(methodName) { class MSSQLQueryGenerator extends AbstractQueryGenerator { createDatabaseQuery(databaseName, options) { - options = Object.assign({ - collate: null - }, options || {}); + options = { collate: null, ...options }; const collation = options.collate ? `COLLATE ${this.escape(options.collate)}` : ''; @@ -838,14 +836,14 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) { this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); - + const dbVersion = this.sequelize.options.databaseVersion; const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0'); if (isSQLServer2008 && options.offset) { // For earlier versions of SQL server, we need to nest several queries // in order to emulate the OFFSET behavior. - // + // // 1. The outermost query selects all items from the inner query block. // This is due to a limitation in SQL server with the use of computed // columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses. diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index 120863888d16..3ebb39fc0a90 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -21,7 +21,7 @@ @private */ const removeColumn = async function(qi, tableName, attributeName, options) { - options = Object.assign({ raw: true }, options || {}); + options = { raw: true, ...options }; const findConstraintSql = qi.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); const [results0] = await qi.sequelize.query(findConstraintSql, options); diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index f9cebd5fbf7a..8fdb979d463f 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -54,7 +54,7 @@ class ConnectionManager extends AbstractConnectionManager { * @private */ async connect(config) { - const connectionConfig = Object.assign({ + const connectionConfig = { host: config.host, port: config.port, user: config.username, @@ -64,8 +64,9 @@ class ConnectionManager extends AbstractConnectionManager { timezone: this.sequelize.options.timezone, typeCast: ConnectionManager._typecast.bind(this), bigNumberStrings: false, - supportBigNumbers: true - }, config.dialectOptions); + supportBigNumbers: true, + ...config.dialectOptions + }; try { const connection = await new Promise((resolve, reject) => { diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index abbb491a12a0..ab2de8bf09cc 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -31,17 +31,19 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { constructor(options) { super(options); - this.OperatorMap = Object.assign({}, this.OperatorMap, { + this.OperatorMap = { + ...this.OperatorMap, [Op.regexp]: 'REGEXP', [Op.notRegexp]: 'NOT REGEXP' - }); + }; } createDatabaseQuery(databaseName, options) { - options = Object.assign({ + options = { charset: null, - collate: null - }, options || {}); + collate: null, + ...options + }; return Utils.joinSQLFragments([ 'CREATE DATABASE IF NOT EXISTS', @@ -69,11 +71,12 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } createTableQuery(tableName, attributes, options) { - options = Object.assign({ + options = { engine: 'InnoDB', charset: null, - rowFormat: null - }, options || {}); + rowFormat: null, + ...options + }; const primaryKeys = []; const foreignKeys = {}; diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 1f68482fd709..3b2c1fa680d6 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -28,20 +28,20 @@ async function removeColumn(qi, tableName, columnName, options) { tableName, schema: qi.sequelize.config.database }, columnName), - Object.assign({ raw: true }, options) + { raw: true, ...options } ); //Exclude primary key constraint if (results.length && results[0].constraint_name !== 'PRIMARY') { await Promise.all(results.map(constraint => qi.sequelize.query( qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), - Object.assign({ raw: true }, options) + { raw: true, ...options } ))); } return await qi.sequelize.query( qi.QueryGenerator.removeColumnQuery(tableName, columnName), - Object.assign({ raw: true }, options) + { raw: true, ...options } ); } @@ -60,8 +60,10 @@ async function removeConstraint(qi, tableName, constraintName, options) { schema: qi.sequelize.config.database }, constraintName); - const constraints = await qi.sequelize.query(sql, Object.assign({}, options, - { type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS })); + const constraints = await qi.sequelize.query(sql, { + ...options, + type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS + }); const constraint = constraints[0]; let query; diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index 17a5e70c564f..7e8b73aff708 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -10,7 +10,7 @@ const debug = logger.debugContext('sql:mysql'); class Query extends AbstractQuery { constructor(connection, sequelize, options) { - super(connection, sequelize, Object.assign({ showWarnings: false }, options)); + super(connection, sequelize, { showWarnings: false, ...options }); } static formatBindParameters(sql, values, dialect) { diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index c9dea5ed2e39..7ef85ccca7a9 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -13,10 +13,11 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { } createDatabaseQuery(databaseName, options) { - options = Object.assign({ + options = { encoding: null, - collate: null - }, options || {}); + collate: null, + ...options + }; const values = { database: this.quoteTable(databaseName), @@ -56,7 +57,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { } createTableQuery(tableName, attributes, options) { - options = Object.assign({}, options || {}); + options = { ...options }; //Postgres 9.0 does not support CREATE TABLE IF NOT EXISTS, 9.1 and above do const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0); @@ -600,7 +601,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { for (const key in attributes) { const attribute = attributes[key]; - result[attribute.field || key] = this.attributeToSQL(attribute, Object.assign({ key }, options || {})); + result[attribute.field || key] = this.attributeToSQL(attribute, { key, ...options }); } return result; diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index ee3aefe0ee42..646ba45818c9 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -44,7 +44,7 @@ async function ensureEnums(qi, tableName, attributes, options, model) { sql = qi.QueryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); promises.push(qi.sequelize.query( sql, - Object.assign({}, options, { plain: true, raw: true, type: QueryTypes.SELECT }) + { ...options, plain: true, raw: true, type: QueryTypes.SELECT } )); } } @@ -89,7 +89,7 @@ async function ensureEnums(qi, tableName, attributes, options, model) { // If the enum type doesn't exist then create it if (!results[enumIdx]) { promises.push(() => { - return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true })); + return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), { ...options, raw: true }); }); } else if (!!results[enumIdx] && !!model) { const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value); diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 72a94d27e56a..4fd889cb60ff 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -35,7 +35,7 @@ async function removeColumn(qi, tableName, attributeName, options) { const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); const subQueries = sql.split(';').filter(q => q !== ''); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); } exports.removeColumn = removeColumn; @@ -63,7 +63,7 @@ async function changeColumn(qi, tableName, attributes, options) { const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); const subQueries = sql.split(';').filter(q => q !== ''); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); } exports.changeColumn = changeColumn; @@ -92,7 +92,7 @@ async function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, option const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); const subQueries = sql.split(';').filter(q => q !== ''); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); } exports.renameColumn = renameColumn; @@ -139,7 +139,7 @@ async function removeConstraint(qi, tableName, constraintName, options) { const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); const subQueries = sql.split(';').filter(q => q !== ''); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); } exports.removeConstraint = removeConstraint; @@ -154,7 +154,7 @@ async function addConstraint(qi, tableName, options) { const constraintSnippet = qi.QueryGenerator.getConstraintSnippet(tableName, options); const describeCreateTableSql = qi.QueryGenerator.describeCreateTableQuery(tableName); - const constraints = await qi.sequelize.query(describeCreateTableSql, Object.assign({}, options, { type: QueryTypes.SELECT, raw: true })); + const constraints = await qi.sequelize.query(describeCreateTableSql, { ...options, type: QueryTypes.SELECT, raw: true }); let sql = constraints[0].sql; const index = sql.length - 1; //Replace ending ')' with constraint snippet - Simulates String.replaceAt @@ -165,7 +165,7 @@ async function addConstraint(qi, tableName, options) { sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); const subQueries = sql.split(';').filter(q => q !== ''); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options)); + for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); } exports.addConstraint = addConstraint; diff --git a/lib/model.js b/lib/model.js index c25f194f1d81..f1966a97dba6 100644 --- a/lib/model.js +++ b/lib/model.js @@ -83,11 +83,12 @@ class Model { * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` */ constructor(values = {}, options = {}) { - options = Object.assign({ + options = { isNewRecord: true, _schema: this.constructor._schema, - _schemaDelimiter: this.constructor._schemaDelimiter - }, options || {}); + _schemaDelimiter: this.constructor._schemaDelimiter, + ...options + }; if (options.attributes) { options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); @@ -951,7 +952,7 @@ class Model { } delete options.modelName; - this.options = Object.assign({ + this.options = { timestamps: true, validate: {}, freezeTableName: false, @@ -963,8 +964,9 @@ class Model { schemaDelimiter: '', defaultScope: {}, scopes: {}, - indexes: [] - }, options); + indexes: [], + ...options + }; // if you call "define" multiple times for the same modelName, do not clutter the factory if (this.sequelize.isDefined(this.name)) { @@ -1272,7 +1274,7 @@ class Model { * @returns {Promise} */ static async sync(options) { - options = Object.assign({}, this.options, options); + options = { ...this.options, ...options }; options.hooks = options.hooks === undefined ? true : !!options.hooks; const attributes = this.tableAttributes; @@ -1354,7 +1356,7 @@ class Model { }); for (const index of indexes) { - await this.QueryInterface.addIndex(tableName, Object.assign({}, options, index)); + await this.QueryInterface.addIndex(tableName, { ...options, ...index }); } if (options.hooks) { @@ -1451,9 +1453,7 @@ class Model { * @param {boolean} [options.override=false] override old scope if already defined */ static addScope(name, scope, options) { - options = Object.assign({ - override: false - }, options); + options = { override: false, ...options }; if ((name === 'defaultScope' && Object.keys(this.options.defaultScope).length > 0 || name in this.options.scopes) && options.override === false) { throw new Error(`The scope ${name} already exists. Pass { override: true } as options to silence this error`); @@ -1741,7 +1741,7 @@ class Model { if (options.hooks) { await this.runHooks('beforeFindAfterOptions', options); } - const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) }); + const selectOptions = { ...options, tableNames: Object.keys(tableNames) }; const results = await this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); if (options.hooks) { await this.runHooks('afterFind', results, options); @@ -1816,19 +1816,19 @@ class Model { } return memo; }, []), - Object.assign( - {}, - _.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain', 'scope'), - { include: include.include || [] } - ) + { + + ..._.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain', 'scope'), + include: include.include || [] + } ); } - const map = await include.association.get(results, Object.assign( - {}, - _.omit(options, nonCascadingOptions), - _.omit(include, ['parent', 'association', 'as', 'originalAttributes']) - )); + const map = await include.association.get(results, { + + ..._.omit(options, nonCascadingOptions), + ..._.omit(include, ['parent', 'association', 'as', 'originalAttributes']) + }); for (const result of results) { result.set( @@ -2152,9 +2152,7 @@ class Model { } static bulkBuild(valueSets, options) { - options = Object.assign({ - isNewRecord: true - }, options || {}); + options = { isNewRecord: true, ...options }; if (!options.includeValidated) { this._conformIncludes(options, this); @@ -2271,7 +2269,7 @@ class Model { ); } - options = Object.assign({}, options); + options = { ...options }; if (options.defaults) { const defaults = Object.keys(options.defaults); @@ -2425,11 +2423,12 @@ class Model { * @returns {Promise} Returns a boolean indicating whether the row was created or updated. For MySQL/MariaDB, it returns `true` when inserted and `false` when updated. For Postgres/MSSQL with `options.returning` true, it returns record and created boolean with signature ``. */ static async upsert(values, options) { - options = Object.assign({ + options = { hooks: true, returning: false, - validate: true - }, Utils.cloneDeep(options || {})); + validate: true, + ...Utils.cloneDeep(options) + }; options.model = this; @@ -2534,12 +2533,13 @@ class Model { const instances = records.map(values => this.build(values, { isNewRecord: true, include: options.include })); const recursiveBulkCreate = async (instances, options) => { - options = Object.assign({ + options = { validate: false, hooks: true, individualHooks: false, - ignoreDuplicates: false - }, options); + ignoreDuplicates: false, + ...options + }; if (options.returning === undefined) { if (options.association) { @@ -2661,7 +2661,7 @@ class Model { } } - const out = Object.assign({}, Utils.mapValueFieldNames(values, options.fields, model)); + const out = Utils.mapValueFieldNames(values, options.fields, model); for (const key of model._virtualAttributes) { delete out[key]; } @@ -2756,12 +2756,12 @@ class Model { const associationInstance = createdAssociationInstances[idx]; const instance = associationInstanceIndexToInstanceMap[idx]; - const values = {}; - values[include.association.foreignKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); - values[include.association.otherKey] = associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }); - - // Include values defined in the association - Object.assign(values, include.association.through.scope); + const values = { + [include.association.foreignKey]: instance.get(instance.constructor.primaryKeyAttribute, { raw: true }), + [include.association.otherKey]: associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }), + // Include values defined in the association + ...include.association.through.scope + }; if (associationInstance[include.association.through.model.name]) { for (const attr of Object.keys(include.association.through.model.rawAttributes)) { if (include.association.through.model.rawAttributes[attr]._autoGenerated || @@ -2945,10 +2945,11 @@ class Model { static async restore(options) { if (!this._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); - options = Object.assign({ + options = { hooks: true, - individualHooks: false - }, options || {}); + individualHooks: false, + ...options + }; options.type = QueryTypes.RAW; options.model = this; @@ -3060,7 +3061,7 @@ class Model { build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); if (options.sideEffects) { - values = Object.assign(values, _.pick(build.get(), build.changed())); + Object.assign(values, _.pick(build.get(), build.changed())); options.fields = _.union(options.fields, Object.keys(values)); } @@ -3194,7 +3195,7 @@ class Model { * @returns {Promise} hash of attributes and their types */ static async describe(schema, options) { - return await this.QueryInterface.describeTable(this.tableName, Object.assign({ schema: schema || this._schema || undefined }, options)); + return await this.QueryInterface.describeTable(this.tableName, { schema: schema || this._schema || undefined, ...options }); } static _getDefaultTimestamp(attr) { @@ -3291,7 +3292,7 @@ class Model { Utils.mapOptionFieldNames(options, this); - const where = Object.assign({}, options.where); + const where = { ...options.where }; // A plain object whose keys are the fields to be incremented and whose values are // the amounts to be incremented by. @@ -3556,7 +3557,7 @@ class Model { // If raw, and we're not dealing with includes or special attributes, just set it straight on the dataValues object if (options.raw && !(this._options && this._options.include) && !(options && options.attributes) && !this.constructor._hasDateAttributes && !this.constructor._hasBooleanAttributes) { if (Object.keys(this.dataValues).length) { - this.dataValues = Object.assign(this.dataValues, values); + Object.assign(this.dataValues, values); } else { this.dataValues = values; } @@ -3929,7 +3930,7 @@ class Model { if (!this.changed() && !this.isNewRecord) return this; const versionFieldName = _.get(this.constructor.rawAttributes[versionAttr], 'field') || versionAttr; - let values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor); + const values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor); let query = null; let args = []; let where; @@ -3970,9 +3971,9 @@ class Model { delete values[this.constructor.rawAttributes[attr].field]; } } - values = Object.assign(values, result.dataValues); + Object.assign(values, result.dataValues); - result.dataValues = Object.assign(result.dataValues, values); + Object.assign(result.dataValues, values); if (wasNewRecord && this._options.include && this._options.include.length) { await Promise.all( this._options.include.filter(include => !(include.association instanceof BelongsTo || @@ -3994,12 +3995,13 @@ class Model { await Promise.all(instances.map(async instance => { if (include.association instanceof BelongsToMany) { await instance.save(includeOptions); - const values0 = {}; - values0[include.association.foreignKey] = this.get(this.constructor.primaryKeyAttribute, { raw: true }); - values0[include.association.otherKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); + const values0 = { + [include.association.foreignKey]: this.get(this.constructor.primaryKeyAttribute, { raw: true }), + [include.association.otherKey]: instance.get(instance.constructor.primaryKeyAttribute, { raw: true }), + // Include values defined in the association + ...include.association.through.scope + }; - // Include values defined in the association - Object.assign(values0, include.association.through.scope); if (instance[include.association.through.model.name]) { for (const attr of Object.keys(include.association.through.model.rawAttributes)) { if (include.association.through.model.rawAttributes[attr]._autoGenerated || @@ -4140,10 +4142,11 @@ class Model { * @returns {Promise} */ async destroy(options) { - options = Object.assign({ + options = { hooks: true, - force: false - }, options); + force: false, + ...options + }; // Run before hook if (options.hooks) { @@ -4167,7 +4170,7 @@ class Model { result = await this.save(_.defaults({ hooks: false }, options)); } else { - result = await this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options)); + result = await this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, { type: QueryTypes.DELETE, limit: null, ...options }); } // Run after hook if (options.hooks) { @@ -4208,10 +4211,11 @@ class Model { async restore(options) { if (!this.constructor._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); - options = Object.assign({ + options = { hooks: true, - force: false - }, options); + force: false, + ...options + }; // Run before hook if (options.hooks) { @@ -4222,7 +4226,7 @@ class Model { const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; this.setDataValue(deletedAtCol, deletedAtDefaultValue); - const result = await this.save(Object.assign({}, options, { hooks: false, omitNull: false })); + const result = await this.save({ ...options, hooks: false, omitNull: false }); // Run after hook if (options.hooks) { await this.constructor.runHooks('afterRestore', this, options); @@ -4266,7 +4270,7 @@ class Model { const identifier = this.where(); options = Utils.cloneDeep(options); - options.where = Object.assign({}, options.where, identifier); + options.where = { ...options.where, ...identifier }; options.instance = this; await this.constructor.increment(fields, options); diff --git a/lib/query-interface.js b/lib/query-interface.js index 6976e54039ec..0c5536d4d7d7 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -109,10 +109,11 @@ class QueryInterface { * @returns {Promise} */ async showAllSchemas(options) { - options = Object.assign({}, options, { + options = { + ...options, raw: true, type: this.sequelize.QueryTypes.SELECT - }); + }; const showSchemasSql = this.QueryGenerator.showSchemasQuery(options); @@ -133,7 +134,7 @@ class QueryInterface { async databaseVersion(options) { return await this.sequelize.query( this.QueryGenerator.versionQuery(), - Object.assign({}, options, { type: QueryTypes.VERSION }) + { ...options, type: QueryTypes.VERSION } ); } @@ -266,7 +267,7 @@ class QueryInterface { if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]); options.supportsSearchPath = false; - promises.push(this.sequelize.query(sql, Object.assign({}, options, { raw: true }))); + promises.push(this.sequelize.query(sql, { ...options, raw: true })); } } } @@ -293,7 +294,7 @@ class QueryInterface { for (const tableName of tableNames) { // if tableName is not in the Array of tables names then don't drop it if (!skip.includes(tableName.tableName || tableName)) { - await this.dropTable(tableName, Object.assign({}, options, { cascade: true }) ); + await this.dropTable(tableName, { ...options, cascade: true } ); } } }; @@ -345,7 +346,7 @@ class QueryInterface { return this.sequelize.query( this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(enumName)), - Object.assign({}, options, { raw: true }) + { ...options, raw: true } ); } @@ -368,7 +369,7 @@ class QueryInterface { return await Promise.all(enums.map(result => this.sequelize.query( this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), - Object.assign({}, options, { raw: true }) + { ...options, raw: true } ))); } @@ -384,7 +385,7 @@ class QueryInterface { pgListEnums(tableName, options) { options = options || {}; const sql = this.QueryGenerator.pgListEnums(tableName); - return this.sequelize.query(sql, Object.assign({}, options, { plain: false, raw: true, type: QueryTypes.SELECT })); + return this.sequelize.query(sql, { ...options, plain: false, raw: true, type: QueryTypes.SELECT }); } /** @@ -413,10 +414,11 @@ class QueryInterface { * @private */ async showAllTables(options) { - options = Object.assign({}, options, { + options = { + ...options, raw: true, type: QueryTypes.SHOWTABLES - }); + }; const showTablesSql = this.QueryGenerator.showTablesQuery(this.sequelize.config.database); const tableNames = await this.sequelize.query(showTablesSql, options); @@ -465,7 +467,7 @@ class QueryInterface { } const sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); - options = Object.assign({}, options, { type: QueryTypes.DESCRIBE }); + options = { ...options, type: QueryTypes.DESCRIBE }; try { const data = await this.sequelize.query(sql, options); @@ -655,7 +657,7 @@ class QueryInterface { options = Utils.cloneDeep(options); options.fields = attributes; const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename); - return await this.sequelize.query(sql, Object.assign({}, options, { supportsSearchPath: false })); + return await this.sequelize.query(sql, { ...options, supportsSearchPath: false }); } /** @@ -669,7 +671,7 @@ class QueryInterface { */ async showIndex(tableName, options) { const sql = this.QueryGenerator.showIndexesQuery(tableName, options); - return await this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWINDEXES })); + return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWINDEXES }); } @@ -686,7 +688,7 @@ class QueryInterface { return {}; } - options = Object.assign({}, options || {}, { type: QueryTypes.FOREIGNKEYS }); + options = { ...options, type: QueryTypes.FOREIGNKEYS }; const results = await Promise.all(tableNames.map(tableName => this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))); @@ -722,9 +724,7 @@ class QueryInterface { * @returns {Promise} */ async getForeignKeyReferencesForTable(tableName, options) { - const queryOptions = Object.assign({}, options, { - type: QueryTypes.FOREIGNKEYS - }); + const queryOptions = { ...options, type: QueryTypes.FOREIGNKEYS }; const catalogName = this.sequelize.config.database; switch (this.sequelize.options.dialect) { case 'sqlite': @@ -853,7 +853,7 @@ class QueryInterface { async showConstraint(tableName, constraintName, options) { const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName); - return await this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS })); + return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWCONSTRAINTS }); } /** @@ -1115,7 +1115,7 @@ class QueryInterface { } async select(model, tableName, optionsArg) { - const options = Object.assign({}, optionsArg, { type: QueryTypes.SELECT, model }); + const options = { ...optionsArg, type: QueryTypes.SELECT, model }; return await this.sequelize.query( this.QueryGenerator.selectQuery(tableName, options, model), @@ -1376,9 +1376,7 @@ class QueryInterface { return; } - options = Object.assign({}, options, { - transaction: transaction.parent || transaction - }); + options = { ...options, transaction: transaction.parent || transaction }; const sql = this.QueryGenerator.setIsolationLevelQuery(value, { parent: transaction.parent @@ -1394,9 +1392,7 @@ class QueryInterface { throw new Error('Unable to start a transaction without transaction object!'); } - options = Object.assign({}, options, { - transaction: transaction.parent || transaction - }); + options = { ...options, transaction: transaction.parent || transaction }; options.transaction.name = transaction.parent ? transaction.name : undefined; const sql = this.QueryGenerator.startTransactionQuery(transaction); @@ -1404,9 +1400,7 @@ class QueryInterface { } async deferConstraints(transaction, options) { - options = Object.assign({}, options, { - transaction: transaction.parent || transaction - }); + options = { ...options, transaction: transaction.parent || transaction }; const sql = this.QueryGenerator.deferConstraintsQuery(options); @@ -1424,11 +1418,12 @@ class QueryInterface { return; } - options = Object.assign({}, options, { + options = { + ...options, transaction: transaction.parent || transaction, supportsSearchPath: false, completesTransaction: true - }); + }; const sql = this.QueryGenerator.commitTransactionQuery(transaction); const promise = this.sequelize.query(sql, options); @@ -1443,11 +1438,12 @@ class QueryInterface { throw new Error('Unable to rollback a transaction without transaction object!'); } - options = Object.assign({}, options, { + options = { + ...options, transaction: transaction.parent || transaction, supportsSearchPath: false, completesTransaction: true - }); + }; options.transaction.name = transaction.parent ? transaction.name : undefined; const sql = this.QueryGenerator.rollbackTransactionQuery(transaction); const promise = this.sequelize.query(sql, options); diff --git a/lib/sequelize.js b/lib/sequelize.js index f10e50eea059..0cd0232d91a7 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -231,7 +231,7 @@ class Sequelize { Sequelize.runHooks('beforeInit', config, options); - this.options = Object.assign({ + this.options = { dialect: null, dialectModule: null, dialectModulePath: null, @@ -264,8 +264,9 @@ class Sequelize { typeValidation: false, benchmark: false, minifyAliases: false, - logQueryParameters: false - }, options || {}); + logQueryParameters: false, + ...options + }; if (!this.options.dialect) { throw new Error('Dialect needs to be explicitly supplied as of v4.0.0'); @@ -504,7 +505,7 @@ class Sequelize { */ async query(sql, options) { - options = Object.assign({}, this.options.query, options); + options = { ...this.options.query, ...options }; if (options.instance && !options.model) { options.model = options.instance.constructor; @@ -596,7 +597,7 @@ class Sequelize { } }; - const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); + const retryOptions = { ...this.options.retry, ...options.retry }; return retry(async () => { if (options.transaction === undefined && Sequelize._cls) { @@ -636,7 +637,7 @@ class Sequelize { async set(variables, options) { // Prepare options - options = Object.assign({}, this.options.set, typeof options === 'object' && options); + options = { ...this.options.set, ...typeof options === 'object' && options }; if (this.options.dialect !== 'mysql') { throw new Error('sequelize.set is only supported for mysql'); @@ -849,11 +850,12 @@ class Sequelize { * @returns {Promise} */ async authenticate(options) { - options = Object.assign({ + options = { raw: true, plain: true, - type: QueryTypes.SELECT - }, options); + type: QueryTypes.SELECT, + ...options + }; await this.query('SELECT 1+1 AS result', options); diff --git a/lib/transaction.js b/lib/transaction.js index 147d3a65951b..63e8b54eb3b6 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -26,11 +26,12 @@ class Transaction { // get dialect specific transaction options const generateTransactionId = this.sequelize.dialect.QueryGenerator.generateTransactionId; - this.options = Object.assign({ + this.options = { type: sequelize.options.transactionType, isolationLevel: sequelize.options.isolationLevel, - readOnly: false - }, options || {}); + readOnly: false, + ...options + }; this.parent = this.options.transaction; diff --git a/lib/utils/logger.js b/lib/utils/logger.js index 13c160decfba..0b2ca26a53dd 100644 --- a/lib/utils/logger.js +++ b/lib/utils/logger.js @@ -14,10 +14,11 @@ const util = require('util'); class Logger { constructor(config) { - this.config = Object.assign({ + this.config = { context: 'sequelize', - debug: true - }, config); + debug: true, + ...config + }; } warn(message) { diff --git a/test/integration/dialects/mariadb/errors.test.js b/test/integration/dialects/mariadb/errors.test.js index 4971ea2635a7..87e0957abdf0 100644 --- a/test/integration/dialects/mariadb/errors.test.js +++ b/test/integration/dialects/mariadb/errors.test.js @@ -10,7 +10,7 @@ if (dialect !== 'mariadb') return; describe('[MariaDB Specific] Errors', () => { const validateError = (promise, errClass, errValues) => { - const wanted = Object.assign({}, errValues); + const wanted = { ...errValues }; return expect(promise).to.have.been.rejectedWith(errClass).then(() => promise.catch(err => Object.keys(wanted).forEach( diff --git a/test/integration/dialects/mysql/errors.test.js b/test/integration/dialects/mysql/errors.test.js index 787ee2f48208..b29d1a4b2179 100644 --- a/test/integration/dialects/mysql/errors.test.js +++ b/test/integration/dialects/mysql/errors.test.js @@ -11,7 +11,7 @@ if (dialect === 'mysql') { describe('[MYSQL Specific] Errors', () => { const validateError = (promise, errClass, errValues) => { - const wanted = Object.assign({}, errValues); + const wanted = { ...errValues }; return expect(promise).to.have.been.rejectedWith(errClass).then(() => promise.catch(err => Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])))); diff --git a/test/integration/dialects/postgres/connection-manager.test.js b/test/integration/dialects/postgres/connection-manager.test.js index 80f16d227c69..3578a97f7f79 100644 --- a/test/integration/dialects/postgres/connection-manager.test.js +++ b/test/integration/dialects/postgres/connection-manager.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES] Sequelize', () => { function checkTimezoneParsing(baseOptions) { - const options = Object.assign({}, baseOptions, { timezone: 'Asia/Kolkata', timestamps: true }); + const options = { ...baseOptions, timezone: 'Asia/Kolkata', timestamps: true }; const sequelize = Support.createSequelizeInstance(options); const tzTable = sequelize.define('tz_table', { foo: DataTypes.STRING }); diff --git a/test/integration/dialects/postgres/query.test.js b/test/integration/dialects/postgres/query.test.js index 4aa5fd43f05d..d54e598561a6 100644 --- a/test/integration/dialects/postgres/query.test.js +++ b/test/integration/dialects/postgres/query.test.js @@ -49,7 +49,7 @@ if (dialect.match(/^postgres/)) { }; it('should throw due to alias being truncated', function() { - const options = Object.assign({}, this.sequelize.options, { minifyAliases: false }); + const options = { ...this.sequelize.options, minifyAliases: false }; return executeTest(options, res => { expect(res[taskAlias]).to.not.exist; @@ -57,7 +57,7 @@ if (dialect.match(/^postgres/)) { }); it('should be able to retrieve include due to alias minifying', function() { - const options = Object.assign({}, this.sequelize.options, { minifyAliases: true }); + const options = { ...this.sequelize.options, minifyAliases: true }; return executeTest(options, res => { expect(res[taskAlias].title).to.be.equal('SuperTask'); diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index 7f7934c6aae4..78df4161dc34 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -62,12 +62,12 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (dialect === 'postgres') { const getConnectionUri = o => `${o.protocol}://${o.username}:${o.password}@${o.host}${o.port ? `:${o.port}` : ''}/${o.database}`; it('should work with connection strings (postgres protocol)', () => { - const connectionUri = getConnectionUri(Object.assign(config[dialect], { protocol: 'postgres' })); + const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgres' }); // postgres://... new Sequelize(connectionUri); }); it('should work with connection strings (postgresql protocol)', () => { - const connectionUri = getConnectionUri(Object.assign(config[dialect], { protocol: 'postgresql' })); + const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgresql' }); // postgresql://... new Sequelize(connectionUri); }); @@ -84,7 +84,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { describe('with an invalid connection', () => { beforeEach(function() { - const options = Object.assign({}, this.sequelize.options, { port: '99999' }); + const options = { ...this.sequelize.options, port: '99999' }; this.sequelizeWithInvalidConnection = new Sequelize('wat', 'trololo', 'wow', options); }); @@ -341,7 +341,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }, { timestamps: false }); - + return this.User.sync({ force: true }); }); it('add parameters in log sql', function() { @@ -365,7 +365,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); }); }); - + it('add parameters in log sql when use bind value', function() { let logSql; const typeCast = dialect === 'postgres' ? '::text' : ''; @@ -375,7 +375,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); }); }); - + }); it('executes select queries correctly', function() { diff --git a/test/integration/sequelize/deferrable.test.js b/test/integration/sequelize/deferrable.test.js index 54286d0b90db..994f4b7957a0 100644 --- a/test/integration/sequelize/deferrable.test.js +++ b/test/integration/sequelize/deferrable.test.js @@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { options = options || {}; const taskTableName = options.taskTableName || `tasks_${config.rand()}`; - const transactionOptions = Object.assign({}, { deferrable: Sequelize.Deferrable.SET_DEFERRED }, options); + const transactionOptions = { deferrable: Sequelize.Deferrable.SET_DEFERRED, ...options }; const userTableName = `users_${config.rand()}`; const User = this.sequelize.define( diff --git a/test/support.js b/test/support.js index 5b65b74eaf10..4d307de22dd6 100644 --- a/test/support.js +++ b/test/support.js @@ -77,7 +77,7 @@ const Support = { if (fs.existsSync(p)) { fs.unlinkSync(p); } - const options = Object.assign({}, sequelize.options, { storage: p }), + const options = { ...sequelize.options, storage: p }, _sequelize = new Sequelize(sequelize.config.database, null, null, options); return _sequelize.sync({ force: true }).then(() => _sequelize); diff --git a/test/unit/dialects/mariadb/query-generator.test.js b/test/unit/dialects/mariadb/query-generator.test.js index 190f6da45870..b320c10d5d38 100644 --- a/test/unit/dialects/mariadb/query-generator.test.js +++ b/test/unit/dialects/mariadb/query-generator.test.js @@ -834,7 +834,7 @@ if (dialect === 'mariadb') { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 7a8faaa05bf5..fb57dd7e895b 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -785,7 +785,7 @@ if (dialect === 'mysql') { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js index 40e8ea78d109..fe5b92310ed7 100644 --- a/test/unit/dialects/postgres/query-generator.test.js +++ b/test/unit/dialects/postgres/query-generator.test.js @@ -1280,7 +1280,7 @@ if (dialect.startsWith('postgres')) { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); diff --git a/test/unit/dialects/sqlite/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js index 022937efd8a2..310942464ec0 100644 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ b/test/unit/dialects/sqlite/query-generator.test.js @@ -645,7 +645,7 @@ if (dialect === 'sqlite') { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); From ce5b4ef9c1111db6907e16265633ced2ab2b60ab Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 2 May 2020 21:50:04 -0700 Subject: [PATCH 130/414] refactor: replace lodash clone with object spread (#12214) --- lib/associations/belongs-to-many.js | 4 +- lib/associations/has-many.js | 2 +- lib/dialects/postgres/query-interface.js | 10 ++-- lib/dialects/sqlite/query-interface.js | 3 +- lib/instance-validator.js | 4 +- lib/model.js | 53 +++++++++---------- lib/query-interface.js | 12 ++--- lib/sequelize.js | 2 +- lib/utils.js | 7 ++- .../abstract/connection-manager.test.js | 17 +++--- test/unit/model/destroy.test.js | 5 +- test/unit/model/update.test.js | 5 +- 12 files changed, 61 insertions(+), 63 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 8a70c1d256ee..24b724750233 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -423,7 +423,7 @@ class BelongsToMany extends Association { let throughWhere; if (this.scope) { - scopeWhere = _.clone(this.scope); + scopeWhere = { ...this.scope }; } options.where = { @@ -667,7 +667,7 @@ class BelongsToMany extends Association { // If newInstances is null or undefined, no-op if (!newInstances) return Promise.resolve(); - options = _.clone(options) || {}; + options = { ...options }; const association = this; const sourceKey = association.sourceKey; diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 814a2072b6c9..b552e95600af 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -114,7 +114,7 @@ class HasMany extends Association { _injectAttributes() { const newAttributes = {}; // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m - const constraintOptions = _.clone(this.options); + const constraintOptions = { ...this.options }; newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 646ba45818c9..1556c2ef7367 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -2,8 +2,6 @@ const DataTypes = require('../../data-types'); const QueryTypes = require('../../query-types'); -const _ = require('lodash'); - /** Returns an object that handles Postgres special needs to do certain queries. @@ -55,9 +53,11 @@ async function ensureEnums(qi, tableName, attributes, options, model) { // This little function allows us to re-use the same code that prepends or appends new value to enum array const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { - const valueOptions = _.clone(options); - valueOptions.before = null; - valueOptions.after = null; + const valueOptions = { + ...options, + before: null, + after: null + }; switch (position) { case 'after': diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 4fd889cb60ff..c56333285099 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -1,6 +1,5 @@ 'use strict'; -const _ = require('lodash'); const sequelizeErrors = require('../../errors'); const QueryTypes = require('../../query-types'); @@ -86,7 +85,7 @@ async function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, option options = options || {}; const fields = await qi.describeTable(tableName, options); - fields[attrNameAfter] = _.clone(fields[attrNameBefore]); + fields[attrNameAfter] = { ...fields[attrNameBefore] }; delete fields[attrNameBefore]; const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); diff --git a/lib/instance-validator.js b/lib/instance-validator.js index 665363ef9b66..e5969234c6a1 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -18,7 +18,7 @@ const { promisify } = require('util'); */ class InstanceValidator { constructor(modelInstance, options) { - options = _.clone(options) || {}; + options = { ...options }; if (options.fields && !options.skip) { options.skip = _.difference(Object.keys(modelInstance.constructor.rawAttributes), options.fields); @@ -405,7 +405,7 @@ class InstanceValidator { } /** * The error key for arguments as passed by custom validators - * + * * @type {string} * @private */ diff --git a/lib/model.js b/lib/model.js index f1966a97dba6..8fb3b7d33f9d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -123,7 +123,7 @@ class Model { let defaults; let key; - values = values && _.clone(values) || {}; + values = { ...values }; if (options.isNewRecord) { defaults = {}; @@ -272,23 +272,18 @@ class Model { }; } - const existingAttributes = _.clone(this.rawAttributes); - this.rawAttributes = {}; - - _.each(head, (value, attr) => { - this.rawAttributes[attr] = value; - }); - - _.each(existingAttributes, (value, attr) => { - this.rawAttributes[attr] = value; - }); - + const newRawAttributes = { + ...head, + ...this.rawAttributes + }; _.each(tail, (value, attr) => { - if (this.rawAttributes[attr] === undefined) { - this.rawAttributes[attr] = value; + if (newRawAttributes[attr] === undefined) { + newRawAttributes[attr] = value; } }); + this.rawAttributes = newRawAttributes; + if (!Object.keys(this.primaryKeys).length) { this.primaryKeys.id = this.rawAttributes.id; } @@ -1070,7 +1065,7 @@ class Model { ['get', 'set'].forEach(type => { const opt = `${type}terMethods`; - const funcs = _.clone(_.isObject(this.options[opt]) ? this.options[opt] : {}); + const funcs = { ...this.options[opt] }; const _custom = type === 'get' ? this.prototype._customGetters : this.prototype._customSetters; _.each(funcs, (method, attribute) => { @@ -2230,7 +2225,7 @@ class Model { let instance = await this.findOne(options); if (instance === null) { - values = _.clone(options.defaults) || {}; + values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2301,7 +2296,7 @@ class Model { return [found, false]; } - values = _.clone(options.defaults) || {}; + values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2379,7 +2374,7 @@ class Model { ); } - let values = _.clone(options.defaults) || {}; + let values = { ...options.defaults }; if (_.isPlainObject(options.where)) { values = Utils.defaults(values, options.where); } @@ -2580,7 +2575,7 @@ class Model { // Validate if (options.validate) { const errors = []; - const validateOptions = _.clone(options); + const validateOptions = { ...options }; validateOptions.hooks = options.individualHooks; await Promise.all(instances.map(async instance => { @@ -2598,12 +2593,14 @@ class Model { } if (options.individualHooks) { await Promise.all(instances.map(async instance => { - const individualOptions = _.clone(options); + const individualOptions = { + ...options, + validate: false, + hooks: true + }; delete individualOptions.fields; delete individualOptions.individualHooks; delete individualOptions.ignoreDuplicates; - individualOptions.validate = false; - individualOptions.hooks = true; await instance.save(individualOptions); })); @@ -3141,10 +3138,12 @@ class Model { } } else { instances = await Promise.all(instances.map(async instance => { - const individualOptions = _.clone(options); + const individualOptions = { + ...options, + hooks: false, + validate: false + }; delete individualOptions.individualHooks; - individualOptions.hooks = false; - individualOptions.validate = false; return instance.save(individualOptions); })); @@ -3562,7 +3561,7 @@ class Model { this.dataValues = values; } // If raw, .changed() shouldn't be true - this._previousDataValues = _.clone(this.dataValues); + this._previousDataValues = { ...this.dataValues }; } else { // Loop and call set if (options.attributes) { @@ -3589,7 +3588,7 @@ class Model { if (options.raw) { // If raw, .changed() shouldn't be true - this._previousDataValues = _.clone(this.dataValues); + this._previousDataValues = { ...this.dataValues }; } } return this; diff --git a/lib/query-interface.js b/lib/query-interface.js index 0c5536d4d7d7..6a0c86cf00c8 100644 --- a/lib/query-interface.js +++ b/lib/query-interface.js @@ -194,7 +194,7 @@ class QueryInterface { async createTable(tableName, attributes, options, model) { let sql = ''; - options = _.clone(options) || {}; + options = { ...options }; if (options && options.uniqueKeys) { _.forOwn(options.uniqueKeys, uniqueKey => { @@ -244,7 +244,7 @@ class QueryInterface { */ async dropTable(tableName, options) { // if we're forcing we should be cascading unless explicitly stated otherwise - options = _.clone(options) || {}; + options = { ...options }; options.cascade = options.cascade || options.force || false; let sql = this.QueryGenerator.dropTableQuery(tableName, options); @@ -913,7 +913,7 @@ class QueryInterface { let indexes = []; let indexFields; - options = _.clone(options); + options = { ...options }; if (!Utils.isWhereEmpty(where)) { wheres.push(where); @@ -994,7 +994,7 @@ class QueryInterface { * @returns {Promise} */ async bulkInsert(tableName, records, options, attributes) { - options = _.clone(options) || {}; + options = { ...options }; options.type = QueryTypes.INSERT; const results = await this.sequelize.query( @@ -1006,7 +1006,7 @@ class QueryInterface { } async update(instance, tableName, values, identifier, options) { - options = _.clone(options || {}); + options = { ...options }; options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); @@ -1053,7 +1053,7 @@ class QueryInterface { const cascades = []; const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); - options = _.clone(options) || {}; + options = { ...options }; // Check for a restrict field if (!!instance.constructor && !!instance.constructor.associations) { diff --git a/lib/sequelize.js b/lib/sequelize.js index 0cd0232d91a7..39bde8b3bb6c 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -751,7 +751,7 @@ class Sequelize { * @returns {Promise} */ async sync(options) { - options = _.clone(options) || {}; + options = { ...options }; options.hooks = options.hooks === undefined ? true : !!options.hooks; options = _.defaults(options, this.options.sync, this.options); diff --git a/lib/utils.js b/lib/utils.js index d119c62d7239..3483481cda71 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -263,8 +263,11 @@ function toDefaultValue(value, dialect) { if (value instanceof DataTypes.NOW) { return now(dialect); } - if (_.isPlainObject(value) || Array.isArray(value)) { - return _.clone(value); + if (Array.isArray(value)) { + return value.slice(); + } + if (_.isPlainObject(value)) { + return { ...value }; } return value; } diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index b70a4a071d38..8386e9e226f9 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -6,8 +6,7 @@ const chai = require('chai'), sinon = require('sinon'), Config = require('../../../config/config'), ConnectionManager = require('../../../../lib/dialects/abstract/connection-manager'), - Pool = require('sequelize-pool').Pool, - _ = require('lodash'); + Pool = require('sequelize-pool').Pool; const baseConf = Config[Support.getTestDialect()]; const poolEntry = { @@ -43,8 +42,8 @@ describe('Connection Manager', () => { it('should initialize a multiple pools with replication', () => { const options = { replication: { - write: _.clone(poolEntry), - read: [_.clone(poolEntry), _.clone(poolEntry)] + write: { ...poolEntry }, + read: [{ ...poolEntry }, { ...poolEntry }] } }; const sequelize = Support.createSequelizeInstance(options); @@ -60,14 +59,14 @@ describe('Connection Manager', () => { return; } - const slave1 = _.clone(poolEntry); - const slave2 = _.clone(poolEntry); + const slave1 = { ...poolEntry }; + const slave2 = { ...poolEntry }; slave1.host = 'slave1'; slave2.host = 'slave2'; const options = { replication: { - write: _.clone(poolEntry), + write: { ...poolEntry }, read: [slave1, slave2] } }; @@ -106,13 +105,13 @@ describe('Connection Manager', () => { }); it('should allow forced reads from the write pool', () => { - const master = _.clone(poolEntry); + const master = { ...poolEntry }; master.host = 'the-boss'; const options = { replication: { write: master, - read: [_.clone(poolEntry)] + read: [{ ...poolEntry }] } }; const sequelize = Support.createSequelizeInstance(options); diff --git a/test/unit/model/destroy.test.js b/test/unit/model/destroy.test.js index 9a98fcecbb71..8cd1233ffbc8 100644 --- a/test/unit/model/destroy.test.js +++ b/test/unit/model/destroy.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), current = Support.sequelize, sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'), - _ = require('lodash'); + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { @@ -22,7 +21,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.deloptions = { where: { secretValue: '1' } }; - this.cloneOptions = _.clone(this.deloptions); + this.cloneOptions = { ...this.deloptions }; this.stubDelete.resetHistory(); }); diff --git a/test/unit/model/update.test.js b/test/unit/model/update.test.js index 356f873fecf8..ea10f506e1ed 100644 --- a/test/unit/model/update.test.js +++ b/test/unit/model/update.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), current = Support.sequelize, sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'), - _ = require('lodash'); + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { describe('method update', () => { @@ -20,7 +19,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.stubUpdate = sinon.stub(current.getQueryInterface(), 'bulkUpdate').resolves([]); this.updates = { name: 'Batman', secretValue: '7' }; - this.cloneUpdates = _.clone(this.updates); + this.cloneUpdates = { ...this.updates }; }); afterEach(function() { From 777fbd3b0fc4f74c84de9c98e042ca7e76caa4f3 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 2 May 2020 23:14:31 -0700 Subject: [PATCH 131/414] refactor: replace lodash defaults with object spread (#12215) --- lib/associations/belongs-to-many.js | 40 +++++++++++------------- lib/associations/belongs-to.js | 13 ++++---- lib/associations/has-many.js | 30 ++++++++++-------- lib/associations/has-one.js | 13 ++++---- lib/dialects/abstract/query-generator.js | 2 +- lib/dialects/postgres/query-generator.js | 2 +- lib/dialects/sqlite/query-generator.js | 4 +-- lib/instance-validator.js | 14 +++++---- lib/model.js | 20 ++++++------ lib/sequelize.js | 9 ++++-- 10 files changed, 78 insertions(+), 69 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 24b724750233..1ea24252291e 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -284,8 +284,8 @@ class BelongsToMany extends Association { const targetKey = this.target.rawAttributes[this.targetKey]; const targetKeyType = targetKey.type; const targetKeyField = this.targetKeyField; - const sourceAttribute = _.defaults({}, this.foreignKeyAttribute, { type: sourceKeyType }); - const targetAttribute = _.defaults({}, this.otherKeyAttribute, { type: targetKeyType }); + const sourceAttribute = { type: sourceKeyType, ...this.foreignKeyAttribute }; + const targetAttribute = { type: targetKeyType, ...this.otherKeyAttribute }; if (this.primaryKeyDeleted === true) { targetAttribute.primaryKey = sourceAttribute.primaryKey = true; @@ -594,7 +594,7 @@ class BelongsToMany extends Association { throughAttributes = {}; } - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).length) { promises.push( @@ -612,28 +612,26 @@ class BelongsToMany extends Association { if (obsoleteAssociations.length > 0) { promises.push( - this.through.model.destroy(_.defaults({ + this.through.model.destroy({ + ...options, where: { [identifier]: sourceInstance.get(sourceKey), [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]), ...this.through.scope } - }, options)) + }) ); } if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { - let attributes = {}; - - attributes[identifier] = sourceInstance.get(sourceKey); - attributes[foreignIdentifier] = unassociatedObject.get(targetKey); - - attributes = _.defaults(attributes, unassociatedObject[this.through.model.name], defaultAttributes); - - Object.assign(attributes, this.through.scope); - - return attributes; + return { + ...defaultAttributes, + ...unassociatedObject[this.through.model.name], + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: unassociatedObject.get(targetKey), + ...this.through.scope + }; }); promises.push(this.through.model.bulkCreate(bulk, { validate: true, ...options })); @@ -643,7 +641,7 @@ class BelongsToMany extends Association { }; try { - const currentRows = await this.through.model.findAll(_.defaults({ where, raw: true }, options)); + const currentRows = await this.through.model.findAll({ ...options, where, raw: true }); return await updateAssociations(currentRows); } catch (error) { if (error instanceof EmptyResultError) return updateAssociations([]); @@ -695,7 +693,7 @@ class BelongsToMany extends Association { unassociatedObjects.push(obj); } else { const throughAttributes = obj[association.through.model.name]; - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).some(attribute => attributes[attribute] !== existingAssociation[attribute])) { changedAssociations.push(obj); @@ -706,7 +704,7 @@ class BelongsToMany extends Association { if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { const throughAttributes = unassociatedObject[association.through.model.name]; - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; attributes[identifier] = sourceInstance.get(sourceKey); attributes[foreignIdentifier] = unassociatedObject.get(targetKey); @@ -721,7 +719,7 @@ class BelongsToMany extends Association { for (const assoc of changedAssociations) { let throughAttributes = assoc[association.through.model.name]; - const attributes = _.defaults({}, throughAttributes, defaultAttributes); + const attributes = { ...defaultAttributes, ...throughAttributes }; // Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object) if (throughAttributes instanceof association.through.model) { throughAttributes = {}; @@ -737,7 +735,7 @@ class BelongsToMany extends Association { }; try { - const currentRows = await association.through.model.findAll(_.defaults({ where, raw: true }, options)); + const currentRows = await association.through.model.findAll({ ...options, where, raw: true }); const [associations] = await updateAssociations(currentRows); return associations; } catch (error) { @@ -767,7 +765,7 @@ class BelongsToMany extends Association { [association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.targetKey)) }; - return association.through.model.destroy(_.defaults({ where }, options)); + return association.through.model.destroy({ ...options, where }); } /** diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index a0cd340d0e7c..cf78bd5b021c 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -79,12 +79,13 @@ class BelongsTo extends Association { // the id is in the source table _injectAttributes() { - const newAttributes = {}; - - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { - type: this.options.keyType || this.target.rawAttributes[this.targetKey].type, - allowNull: true - }); + const newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.target.rawAttributes[this.targetKey].type, + allowNull: true, + ...this.foreignKeyAttribute + } + }; if (this.options.constraints !== false) { const source = this.source.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index b552e95600af..e8c184bb630b 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -112,15 +112,17 @@ class HasMany extends Association { // the id is in the target table // or in an extra table which connects two tables _injectAttributes() { - const newAttributes = {}; + const newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, + allowNull: true, + ...this.foreignKeyAttribute + } + }; + // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m const constraintOptions = { ...this.options }; - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { - type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, - allowNull: true - }); - if (this.options.constraints !== false) { const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; constraintOptions.onDelete = constraintOptions.onDelete || (target.allowNull ? 'SET NULL' : 'CASCADE'); @@ -329,7 +331,7 @@ class HasMany extends Association { targetInstances = this.toInstanceArray(targetInstances); } - const oldAssociations = await this.get(sourceInstance, _.defaults({ scope: false, raw: true }, options)); + const oldAssociations = await this.get(sourceInstance, { ...options, scope: false, raw: true }); const promises = []; const obsoleteAssociations = oldAssociations.filter(old => !targetInstances.find(obj => @@ -357,9 +359,10 @@ class HasMany extends Association { promises.push(this.target.unscoped().update( update, - _.defaults({ + { + ...options, where: updateWhere - }, options) + } )); } @@ -376,9 +379,10 @@ class HasMany extends Association { promises.push(this.target.unscoped().update( update, - _.defaults({ + { + ...options, where: updateWhere - }, options) + } )); } @@ -414,7 +418,7 @@ class HasMany extends Association { ) }; - await this.target.unscoped().update(update, _.defaults({ where }, options)); + await this.target.unscoped().update(update, { ...options, where }); return sourceInstance; } @@ -442,7 +446,7 @@ class HasMany extends Association { ) }; - await this.target.unscoped().update(update, _.defaults({ where }, options)); + await this.target.unscoped().update(update, { ...options, where }); return this; } diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index cad9f3af36e8..6d19f4263c0a 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -78,12 +78,13 @@ class HasOne extends Association { // the id is in the target table _injectAttributes() { - const newAttributes = {}; - - newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, { - type: this.options.keyType || this.source.rawAttributes[this.sourceKey].type, - allowNull: true - }); + const newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.source.rawAttributes[this.sourceKey].type, + allowNull: true, + ...this.foreignKeyAttribute + } + }; if (this.options.constraints !== false) { const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index de83ad02b242..622e2b53f3f1 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -401,7 +401,7 @@ class QueryGenerator { } } - const whereOptions = _.defaults({ bindParam }, options); + const whereOptions = { ...options, bindParam }; if (values.length === 0) { return ''; diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 7ef85ccca7a9..753aeeba0f13 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -344,7 +344,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { upsertQuery(tableName, insertValues, updateValues, where, model, options) { const primaryField = this.quoteIdentifier(model.primaryKeyField); - const upsertOptions = _.defaults({ bindParam: false, returning: ['*'] }, options); + const upsertOptions = { ...options, bindParam: false, returning: ['*'] }; const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); const returningRegex = /RETURNING \*(?![\s\S]*RETURNING \*)/; diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 9fa931bec208..36e3a042d8e9 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -181,7 +181,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { const bind = []; const bindParam = this.bindParam(bind); - const upsertOptions = _.defaults({ bindParam }, options); + const upsertOptions = { ...options, bindParam }; const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); @@ -221,7 +221,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { } let query; - const whereOptions = _.defaults({ bindParam }, options); + const whereOptions = { ...options, bindParam }; if (options.limit) { query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')} WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${this.whereQuery(where, whereOptions)} LIMIT ${this.escape(options.limit)})`; diff --git a/lib/instance-validator.js b/lib/instance-validator.js index e5969234c6a1..fa4d91debfb7 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -18,17 +18,19 @@ const { promisify } = require('util'); */ class InstanceValidator { constructor(modelInstance, options) { - options = { ...options }; + options = { + // assign defined and default options + hooks: true, + ...options + }; if (options.fields && !options.skip) { options.skip = _.difference(Object.keys(modelInstance.constructor.rawAttributes), options.fields); + } else { + options.skip = options.skip || []; } - // assign defined and default options - this.options = _.defaults(options, { - skip: [], - hooks: true - }); + this.options = options; this.modelInstance = modelInstance; diff --git a/lib/model.js b/lib/model.js index 8fb3b7d33f9d..ef7a7d3ed442 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3367,11 +3367,11 @@ class Model { * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect */ static async decrement(fields, options) { - options = _.defaults({ increment: false }, options, { - by: 1 + return this.increment(fields, { + by: 1, + ...options, + increment: false }); - - return await this.increment(fields, options); } static _optionsMustContainWhere(options) { @@ -4086,7 +4086,7 @@ class Model { * @returns {Promise} */ async validate(options) { - return await new InstanceValidator(this, options).validate(); + return new InstanceValidator(this, options).validate(); } /** @@ -4167,7 +4167,7 @@ class Model { this.setDataValue(attributeName, new Date()); } - result = await this.save(_.defaults({ hooks: false }, options)); + result = await this.save({ ...options, hooks: false }); } else { result = await this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, { type: QueryTypes.DELETE, limit: null, ...options }); } @@ -4307,11 +4307,11 @@ class Model { * @returns {Promise} */ async decrement(fields, options) { - options = _.defaults({ increment: false }, options, { - by: 1 + return this.increment(fields, { + by: 1, + ...options, + increment: false }); - - return await this.increment(fields, options); } /** diff --git a/lib/sequelize.js b/lib/sequelize.js index 39bde8b3bb6c..752b338a5629 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -751,9 +751,12 @@ class Sequelize { * @returns {Promise} */ async sync(options) { - options = { ...options }; - options.hooks = options.hooks === undefined ? true : !!options.hooks; - options = _.defaults(options, this.options.sync, this.options); + options = { + ...this.options, + ...this.options.sync, + ...options, + hooks: options ? options.hooks !== false : true + }; if (options.match) { if (!options.match.test(this.config.database)) { From c624c0e1284cee478f20f5a7243ebf73b6046585 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sun, 3 May 2020 01:08:52 -0700 Subject: [PATCH 132/414] refactor: move all dialect conditional logic into subclass (#12217) --- docs/esdoc-config.js | 6 + lib/associations/helpers.js | 2 +- .../abstract}/query-interface.js | 472 +- lib/dialects/mariadb/connection-manager.js | 3 - lib/dialects/mariadb/index.js | 4 +- lib/dialects/mssql/index.js | 4 +- lib/dialects/mssql/query-interface.js | 90 +- lib/dialects/mysql/connection-manager.js | 3 - lib/dialects/mysql/index.js | 4 +- lib/dialects/mysql/query-interface.js | 132 +- lib/dialects/postgres/index.js | 4 +- lib/dialects/postgres/query-interface.js | 334 +- lib/dialects/postgres/query.js | 6 +- lib/dialects/sqlite/index.js | 5 +- lib/dialects/sqlite/query-generator.js | 1 - lib/dialects/sqlite/query-interface.js | 312 +- lib/model.js | 56 +- lib/sequelize.js | 10 +- lib/transaction.js | 2 +- lib/utils.js | 11 - package-lock.json | 5889 ++++++++++------- package.json | 1 + .../associations/belongs-to.test.js | 4 +- .../integration/associations/has-many.test.js | 4 +- test/integration/associations/has-one.test.js | 4 +- .../dialects/mariadb/dao-factory.test.js | 18 +- .../dialects/mysql/dao-factory.test.js | 18 +- .../integration/dialects/postgres/dao.test.js | 8 +- test/integration/model.test.js | 8 +- test/integration/query-interface.test.js | 29 +- test/integration/sequelize.test.js | 12 +- test/integration/utils.test.js | 2 +- test/support.js | 38 +- test/unit/sql/add-column.test.js | 2 +- test/unit/sql/add-constraint.test.js | 2 +- test/unit/sql/create-schema.test.js | 2 +- test/unit/sql/create-table.test.js | 2 +- test/unit/sql/delete.test.js | 2 +- test/unit/sql/enum.test.js | 2 +- test/unit/sql/generateJoin.test.js | 2 +- test/unit/sql/get-constraint-snippet.test.js | 2 +- test/unit/sql/index.test.js | 2 +- test/unit/sql/insert.test.js | 6 +- test/unit/sql/json.test.js | 2 +- test/unit/sql/offset-limit.test.js | 2 +- test/unit/sql/order.test.js | 2 +- test/unit/sql/remove-column.test.js | 2 +- test/unit/sql/remove-constraint.test.js | 2 +- test/unit/sql/select.test.js | 2 +- test/unit/sql/show-constraints.test.js | 2 +- test/unit/sql/update.test.js | 2 +- test/unit/sql/where.test.js | 8 +- test/unit/utils.test.js | 26 +- types/lib/query-interface.d.ts | 25 +- types/test/query-interface.ts | 3 +- 55 files changed, 4152 insertions(+), 3446 deletions(-) rename lib/{ => dialects/abstract}/query-interface.js (71%) diff --git a/docs/esdoc-config.js b/docs/esdoc-config.js index fc2adcc9c089..3e2f3436a948 100644 --- a/docs/esdoc-config.js +++ b/docs/esdoc-config.js @@ -9,6 +9,12 @@ module.exports = { destination: './esdoc', includes: ['\\.js$'], plugins: [ + { + name: 'esdoc-ecmascript-proposal-plugin', + option: { + all: true + } + }, { name: 'esdoc-inject-style-plugin', option: { diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js index e9a6a2501fb3..c685ecec2a69 100644 --- a/lib/associations/helpers.js +++ b/lib/associations/helpers.js @@ -23,7 +23,7 @@ function addForeignKeyConstraints(newAttribute, source, target, options, key) { if (primaryKeys.length === 1 || !primaryKeys.includes(key)) { if (source._schema) { newAttribute.references = { - model: source.sequelize.getQueryInterface().QueryGenerator.addSchema({ + model: source.sequelize.getQueryInterface().queryGenerator.addSchema({ tableName: source.tableName, _schema: source._schema, _schemaDelimiter: source._schemaDelimiter diff --git a/lib/query-interface.js b/lib/dialects/abstract/query-interface.js similarity index 71% rename from lib/query-interface.js rename to lib/dialects/abstract/query-interface.js index 6a0c86cf00c8..31006ea8a52a 100644 --- a/lib/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -2,25 +2,19 @@ const _ = require('lodash'); -const Utils = require('./utils'); -const DataTypes = require('./data-types'); -const SQLiteQueryInterface = require('./dialects/sqlite/query-interface'); -const MSSQLQueryInterface = require('./dialects/mssql/query-interface'); -const MySQLQueryInterface = require('./dialects/mysql/query-interface'); -const PostgresQueryInterface = require('./dialects/postgres/query-interface'); -const Transaction = require('./transaction'); -const QueryTypes = require('./query-types'); -const Op = require('./operators'); +const Utils = require('../../utils'); +const DataTypes = require('../../data-types'); +const Transaction = require('../../transaction'); +const QueryTypes = require('../../query-types'); +const Op = require('../../operators'); /** * The interface that Sequelize uses to talk to all databases - * - * @class QueryInterface */ class QueryInterface { - constructor(sequelize) { + constructor(sequelize, queryGenerator) { this.sequelize = sequelize; - this.QueryGenerator = this.sequelize.dialect.QueryGenerator; + this.queryGenerator = queryGenerator; } /** @@ -38,7 +32,7 @@ class QueryInterface { */ async createDatabase(database, options) { options = options || {}; - const sql = this.QueryGenerator.createDatabaseQuery(database, options); + const sql = this.queryGenerator.createDatabaseQuery(database, options); return await this.sequelize.query(sql, options); } @@ -52,7 +46,7 @@ class QueryInterface { */ async dropDatabase(database, options) { options = options || {}; - const sql = this.QueryGenerator.dropDatabaseQuery(database); + const sql = this.queryGenerator.dropDatabaseQuery(database); return await this.sequelize.query(sql, options); } @@ -66,7 +60,7 @@ class QueryInterface { */ async createSchema(schema, options) { options = options || {}; - const sql = this.QueryGenerator.createSchema(schema); + const sql = this.queryGenerator.createSchema(schema); return await this.sequelize.query(sql, options); } @@ -80,7 +74,7 @@ class QueryInterface { */ async dropSchema(schema, options) { options = options || {}; - const sql = this.QueryGenerator.dropSchema(schema); + const sql = this.queryGenerator.dropSchema(schema); return await this.sequelize.query(sql, options); } @@ -94,7 +88,7 @@ class QueryInterface { async dropAllSchemas(options) { options = options || {}; - if (!this.QueryGenerator._dialect.supports.schemas) { + if (!this.queryGenerator._dialect.supports.schemas) { return this.sequelize.drop(options); } const schemas = await this.showAllSchemas(options); @@ -115,7 +109,7 @@ class QueryInterface { type: this.sequelize.QueryTypes.SELECT }; - const showSchemasSql = this.QueryGenerator.showSchemasQuery(options); + const showSchemasSql = this.queryGenerator.showSchemasQuery(options); const schemaNames = await this.sequelize.query(showSchemasSql, options); @@ -133,7 +127,7 @@ class QueryInterface { */ async databaseVersion(options) { return await this.sequelize.query( - this.QueryGenerator.versionQuery(), + this.queryGenerator.versionQuery(), { ...options, type: QueryTypes.VERSION } ); } @@ -214,22 +208,20 @@ class QueryInterface { ); // Postgres requires special SQL commands for ENUM/ENUM[] - if (this.sequelize.options.dialect === 'postgres') { - await PostgresQueryInterface.ensureEnums(this, tableName, attributes, options, model); - } + await this.ensureEnums(tableName, attributes, options, model); if ( !tableName.schema && (options.schema || !!model && model._schema) ) { - tableName = this.QueryGenerator.addSchema({ + tableName = this.queryGenerator.addSchema({ tableName, _schema: !!model && model._schema || options.schema }); } - attributes = this.QueryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' }); - sql = this.QueryGenerator.createTableQuery(tableName, attributes, options); + attributes = this.queryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' }); + sql = this.queryGenerator.createTableQuery(tableName, attributes, options); return await this.sequelize.query(sql, options); } @@ -247,35 +239,18 @@ class QueryInterface { options = { ...options }; options.cascade = options.cascade || options.force || false; - let sql = this.QueryGenerator.dropTableQuery(tableName, options); + const sql = this.queryGenerator.dropTableQuery(tableName, options); await this.sequelize.query(sql, options); - const promises = []; - - // Since postgres has a special case for enums, we should drop the related - // enum type within the table and attribute - if (this.sequelize.options.dialect === 'postgres') { - const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); - - if (instanceTable) { - const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName; - - const keys = Object.keys(instanceTable.rawAttributes); - const keyLen = keys.length; + } - for (let i = 0; i < keyLen; i++) { - if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { - sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]); - options.supportsSearchPath = false; - promises.push(this.sequelize.query(sql, { ...options, raw: true })); - } - } + async _dropAllTables(tableNames, skip, options) { + for (const tableName of tableNames) { + // if tableName is not in the Array of tables names then don't drop it + if (!skip.includes(tableName.tableName || tableName)) { + await this.dropTable(tableName, { ...options, cascade: true } ); } } - - const obj = await Promise.all(promises); - - return obj[0]; } /** @@ -290,102 +265,20 @@ class QueryInterface { options = options || {}; const skip = options.skip || []; - const dropAllTables = async tableNames => { - for (const tableName of tableNames) { - // if tableName is not in the Array of tables names then don't drop it - if (!skip.includes(tableName.tableName || tableName)) { - await this.dropTable(tableName, { ...options, cascade: true } ); - } - } - }; - const tableNames = await this.showAllTables(options); - if (this.sequelize.options.dialect === 'sqlite') { - const result = await this.sequelize.query('PRAGMA foreign_keys;', options); - const foreignKeysAreEnabled = result.foreign_keys === 1; - - if (foreignKeysAreEnabled) { - await this.sequelize.query('PRAGMA foreign_keys = OFF', options); - await dropAllTables(tableNames); - await this.sequelize.query('PRAGMA foreign_keys = ON', options); - } else { - await dropAllTables(tableNames); - } - } else { - const foreignKeys = await this.getForeignKeysForTables(tableNames, options); - - for (const tableName of tableNames) { - let normalizedTableName = tableName; - if (_.isObject(tableName)) { - normalizedTableName = `${tableName.schema}.${tableName.tableName}`; - } + const foreignKeys = await this.getForeignKeysForTables(tableNames, options); - for (const foreignKey of foreignKeys[normalizedTableName]) { - await this.sequelize.query(this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey)); - } + for (const tableName of tableNames) { + let normalizedTableName = tableName; + if (_.isObject(tableName)) { + normalizedTableName = `${tableName.schema}.${tableName.tableName}`; } - await dropAllTables(tableNames); - } - } - - /** - * Drop specified enum from database (Postgres only) - * - * @param {string} [enumName] Enum name to drop - * @param {object} options Query options - * - * @returns {Promise} - * @private - */ - async dropEnum(enumName, options) { - if (this.sequelize.getDialect() !== 'postgres') { - return; - } - - options = options || {}; - return this.sequelize.query( - this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(enumName)), - { ...options, raw: true } - ); - } - - /** - * Drop all enums from database (Postgres only) - * - * @param {object} options Query options - * - * @returns {Promise} - * @private - */ - async dropAllEnums(options) { - if (this.sequelize.getDialect() !== 'postgres') { - return; + for (const foreignKey of foreignKeys[normalizedTableName]) { + await this.sequelize.query(this.queryGenerator.dropForeignKeyQuery(tableName, foreignKey)); + } } - - options = options || {}; - - const enums = await this.pgListEnums(null, options); - - return await Promise.all(enums.map(result => this.sequelize.query( - this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), - { ...options, raw: true } - ))); - } - - /** - * List all enums (Postgres only) - * - * @param {string} [tableName] Table whose enum to list - * @param {object} [options] Query options - * - * @returns {Promise} - * @private - */ - pgListEnums(tableName, options) { - options = options || {}; - const sql = this.QueryGenerator.pgListEnums(tableName); - return this.sequelize.query(sql, { ...options, plain: false, raw: true, type: QueryTypes.SELECT }); + await this._dropAllTables(tableNames, skip, options); } /** @@ -399,7 +292,7 @@ class QueryInterface { */ async renameTable(before, after, options) { options = options || {}; - const sql = this.QueryGenerator.renameTableQuery(before, after); + const sql = this.queryGenerator.renameTableQuery(before, after); return await this.sequelize.query(sql, options); } @@ -420,7 +313,7 @@ class QueryInterface { type: QueryTypes.SHOWTABLES }; - const showTablesSql = this.QueryGenerator.showTablesQuery(this.sequelize.config.database); + const showTablesSql = this.queryGenerator.showTablesQuery(this.sequelize.config.database); const tableNames = await this.sequelize.query(showTablesSql, options); return _.flatten(tableNames); } @@ -466,7 +359,7 @@ class QueryInterface { tableName = tableName.tableName; } - const sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); + const sql = this.queryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); options = { ...options, type: QueryTypes.DESCRIBE }; try { @@ -513,7 +406,7 @@ class QueryInterface { options = options || {}; attribute = this.sequelize.normalizeAttribute(attribute); - return await this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options); + return await this.sequelize.query(this.queryGenerator.addColumnQuery(table, key, attribute), options); } /** @@ -522,25 +415,20 @@ class QueryInterface { * @param {string} tableName Table to remove column from * @param {string} attributeName Column name to remove * @param {object} [options] Query options - * - * @returns {Promise} */ async removeColumn(tableName, attributeName, options) { - options = options || {}; - switch (this.sequelize.options.dialect) { - case 'sqlite': - // sqlite needs some special treatment as it cannot drop a column - return await SQLiteQueryInterface.removeColumn(this, tableName, attributeName, options); - case 'mssql': - // mssql needs special treatment as it cannot drop a column with a default or foreign key constraint - return await MSSQLQueryInterface.removeColumn(this, tableName, attributeName, options); - case 'mysql': - case 'mariadb': - // mysql/mariadb need special treatment as it cannot drop a column with a foreign key constraint - return await MySQLQueryInterface.removeColumn(this, tableName, attributeName, options); - default: - return await this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options); + return this.sequelize.query(this.queryGenerator.removeColumnQuery(tableName, attributeName), options); + } + + normalizeAttribute(dataTypeOrOptions) { + let attribute; + if (Object.values(DataTypes).includes(dataTypeOrOptions)) { + attribute = { type: dataTypeOrOptions, allowNull: true }; + } else { + attribute = dataTypeOrOptions; } + + return this.sequelize.normalizeAttribute(attribute); } /** @@ -550,32 +438,35 @@ class QueryInterface { * @param {string} attributeName Column name * @param {object} dataTypeOrOptions Attribute definition for new column * @param {object} [options] Query options - * - * @returns {Promise} */ async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { - const attributes = {}; options = options || {}; - if (Object.values(DataTypes).includes(dataTypeOrOptions)) { - attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true }; - } else { - attributes[attributeName] = dataTypeOrOptions; - } - - attributes[attributeName] = this.sequelize.normalizeAttribute(attributes[attributeName]); - - if (this.sequelize.options.dialect === 'sqlite') { - // sqlite needs some special treatment as it cannot change a column - return SQLiteQueryInterface.changeColumn(this, tableName, attributes, options); - } - const query = this.QueryGenerator.attributesToSQL(attributes, { + const query = this.queryGenerator.attributesToSQL({ + [attributeName]: this.normalizeAttribute(dataTypeOrOptions) + }, { context: 'changeColumn', table: tableName }); - const sql = this.QueryGenerator.changeColumnQuery(tableName, query); + const sql = this.queryGenerator.changeColumnQuery(tableName, query); - return await this.sequelize.query(sql, options); + return this.sequelize.query(sql, options); + } + + /** + * Rejects if the table doesn't have the specified column, otherwise returns the column description. + * + * @param {string} tableName + * @param {string} columnName + * @param {object} options + * @private + */ + async assertTableHasColumn(tableName, columnName, options) { + const description = await this.describeTable(tableName, options); + if (description[columnName]) { + return description; + } + throw new Error(`Table ${tableName} doesn't have the column ${columnName}`); } /** @@ -590,12 +481,7 @@ class QueryInterface { */ async renameColumn(tableName, attrNameBefore, attrNameAfter, options) { options = options || {}; - let data = await this.describeTable(tableName, options); - if (!data[attrNameBefore]) { - throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`); - } - - data = data[attrNameBefore] || {}; + const data = (await this.assertTableHasColumn(tableName, attrNameBefore, options))[attrNameBefore]; const _options = {}; @@ -611,14 +497,10 @@ class QueryInterface { delete _options[attrNameAfter].defaultValue; } - if (this.sequelize.options.dialect === 'sqlite') { - // sqlite needs some special treatment as it cannot rename a column - return SQLiteQueryInterface.renameColumn(this, tableName, attrNameBefore, attrNameAfter, options); - } - const sql = this.QueryGenerator.renameColumnQuery( + const sql = this.queryGenerator.renameColumnQuery( tableName, attrNameBefore, - this.QueryGenerator.attributesToSQL(_options) + this.queryGenerator.attributesToSQL(_options) ); return await this.sequelize.query(sql, options); } @@ -656,7 +538,7 @@ class QueryInterface { options = Utils.cloneDeep(options); options.fields = attributes; - const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename); + const sql = this.queryGenerator.addIndexQuery(tableName, options, rawTablename); return await this.sequelize.query(sql, { ...options, supportsSearchPath: false }); } @@ -670,7 +552,7 @@ class QueryInterface { * @private */ async showIndex(tableName, options) { - const sql = this.QueryGenerator.showIndexesQuery(tableName, options); + const sql = this.queryGenerator.showIndexesQuery(tableName, options); return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWINDEXES }); } @@ -691,7 +573,7 @@ class QueryInterface { options = { ...options, type: QueryTypes.FOREIGNKEYS }; const results = await Promise.all(tableNames.map(tableName => - this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))); + this.sequelize.query(this.queryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))); const result = {}; @@ -720,32 +602,14 @@ class QueryInterface { * * @param {string} tableName table name * @param {object} [options] Query options - * - * @returns {Promise} */ async getForeignKeyReferencesForTable(tableName, options) { - const queryOptions = { ...options, type: QueryTypes.FOREIGNKEYS }; - const catalogName = this.sequelize.config.database; - switch (this.sequelize.options.dialect) { - case 'sqlite': - // sqlite needs some special treatment. - return await SQLiteQueryInterface.getForeignKeyReferencesForTable(this, tableName, queryOptions); - case 'postgres': - { - // postgres needs some special treatment as those field names returned are all lowercase - // in order to keep same result with other dialects. - const query = this.QueryGenerator.getForeignKeyReferencesQuery(tableName, catalogName); - const result = await this.sequelize.query(query, queryOptions); - return result.map(Utils.camelizeObjectKeys); - } - case 'mssql': - case 'mysql': - case 'mariadb': - default: { - const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName); - return await this.sequelize.query(query, queryOptions); - } - } + const queryOptions = { + ...options, + type: QueryTypes.FOREIGNKEYS + }; + const query = this.queryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database); + return this.sequelize.query(query, queryOptions); } /** @@ -759,7 +623,7 @@ class QueryInterface { */ async removeIndex(tableName, indexNameOrAttributes, options) { options = options || {}; - const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); + const sql = this.queryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); return await this.sequelize.query(sql, options); } @@ -774,13 +638,15 @@ class QueryInterface { * - PRIMARY KEY * * @example - * queryInterface.addConstraint('Users', ['email'], { + * queryInterface.addConstraint('Users', { + * fields: ['email'], * type: 'unique', * name: 'custom_unique_constraint_name' * }); * * @example - * queryInterface.addConstraint('Users', ['roles'], { + * queryInterface.addConstraint('Users', { + * fields: ['roles'], * type: 'check', * where: { * roles: ['user', 'admin', 'moderator', 'guest'] @@ -788,19 +654,22 @@ class QueryInterface { * }); * * @example - * queryInterface.addConstraint('Users', ['roles'], { + * queryInterface.addConstraint('Users', { + * fields: ['roles'], * type: 'default', * defaultValue: 'guest' * }); * * @example - * queryInterface.addConstraint('Users', ['username'], { + * queryInterface.addConstraint('Users', { + * fields: ['username'], * type: 'primary key', * name: 'custom_primary_constraint_name' * }); * * @example - * queryInterface.addConstraint('Posts', ['username'], { + * queryInterface.addConstraint('Posts', { + * fields: ['username'], * type: 'foreign key', * name: 'custom_fkey_constraint_name', * references: { //Required field @@ -812,47 +681,35 @@ class QueryInterface { * }); * * @param {string} tableName Table name where you want to add a constraint - * @param {Array} attributes Array of column names to apply the constraint over * @param {object} options An object to define the constraint name, type etc * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names * @param {string} [options.defaultValue] The value for the default constraint * @param {object} [options.where] Where clause/expression for the CHECK constraint + * @param {Array} [options.attributes] Array of column names to apply the constraint over * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint * @param {string} [options.references.table] Target table name * @param {string} [options.references.field] Target column name - * @param {string} [rawTablename] Table name, for backward compatibility * * @returns {Promise} */ - async addConstraint(tableName, attributes, options, rawTablename) { - if (!Array.isArray(attributes)) { - rawTablename = options; - options = attributes; - attributes = options.fields; + async addConstraint(tableName, options) { + if (!options.fields) { + throw new Error('Fields must be specified through options.fields'); } if (!options.type) { throw new Error('Constraint type must be specified through options.type'); } - if (!rawTablename) { - // Map for backwards compat - rawTablename = tableName; - } - options = Utils.cloneDeep(options); - options.fields = attributes; - if (this.sequelize.dialect.name === 'sqlite') { - return await SQLiteQueryInterface.addConstraint(this, tableName, options, rawTablename); - } - const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename); + const sql = this.queryGenerator.addConstraintQuery(tableName, options); return await this.sequelize.query(sql, options); } async showConstraint(tableName, constraintName, options) { - const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName); + const sql = this.queryGenerator.showConstraintsQuery(tableName, constraintName); return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWCONSTRAINTS }); } @@ -862,29 +719,15 @@ class QueryInterface { * @param {string} tableName Table name to drop constraint from * @param {string} constraintName Constraint name * @param {object} options Query options - * - * @returns {Promise} */ async removeConstraint(tableName, constraintName, options) { - options = options || {}; - - switch (this.sequelize.options.dialect) { - case 'mysql': - case 'mariadb': - //does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used - return await MySQLQueryInterface.removeConstraint(this, tableName, constraintName, options); - case 'sqlite': - return await SQLiteQueryInterface.removeConstraint(this, tableName, constraintName, options); - default: - const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName); - return await this.sequelize.query(sql, options); - } + return this.sequelize.query(this.queryGenerator.removeConstraintQuery(tableName, constraintName), options); } async insert(instance, tableName, values, options) { options = Utils.cloneDeep(options); options.hasTrigger = instance && instance.constructor.options.hasTrigger; - const sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options); + const sql = this.queryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options); options.type = QueryTypes.INSERT; options.instance = instance; @@ -952,24 +795,21 @@ class QueryInterface { options.type = QueryTypes.UPSERT; options.raw = true; - const sql = this.QueryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); + const sql = this.queryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); const result = await this.sequelize.query(sql, options); - switch (this.sequelize.options.dialect) { - case 'postgres': - return [result.created, result.primary_key]; - case 'mssql': - return [ - result.$action === 'INSERT', - result[model.primaryKeyField] - ]; - // MySQL returns 1 for inserted, 2 for updated - // http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html. - case 'mysql': - case 'mariadb': - return [result === 1, undefined]; - default: - return [result, undefined]; - } + return this._convertUpsertResult(result, model); + } + + /** + * Converts raw upsert result to API contract. + * + * @param {object} result + * @param {Model} model + * @protected + */ + // eslint-disable-next-line no-unused-vars + _convertUpsertResult(result, model) { + return [result, undefined]; } /** @@ -998,7 +838,7 @@ class QueryInterface { options.type = QueryTypes.INSERT; const results = await this.sequelize.query( - this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes), + this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes), options ); @@ -1009,7 +849,7 @@ class QueryInterface { options = { ...options }; options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); - const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); + const sql = this.queryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); options.type = QueryTypes.UPDATE; @@ -1040,7 +880,7 @@ class QueryInterface { options = Utils.cloneDeep(options); if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier); - const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes); + const sql = this.queryGenerator.updateQuery(tableName, values, identifier, options, attributes); const table = _.isObject(tableName) ? tableName : { tableName }; const model = _.find(this.sequelize.modelManager.models, { tableName: table.tableName }); @@ -1051,7 +891,7 @@ class QueryInterface { async delete(instance, tableName, identifier, options) { const cascades = []; - const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); + const sql = this.queryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); options = { ...options }; @@ -1101,7 +941,7 @@ class QueryInterface { if (options.truncate === true) { return this.sequelize.query( - this.QueryGenerator.truncateTableQuery(tableName, options), + this.queryGenerator.truncateTableQuery(tableName, options), options ); } @@ -1109,7 +949,7 @@ class QueryInterface { if (typeof identifier === 'object') where = Utils.cloneDeep(where); return await this.sequelize.query( - this.QueryGenerator.deleteQuery(tableName, where, options, model), + this.queryGenerator.deleteQuery(tableName, where, options, model), options ); } @@ -1118,7 +958,7 @@ class QueryInterface { const options = { ...optionsArg, type: QueryTypes.SELECT, model }; return await this.sequelize.query( - this.QueryGenerator.selectQuery(tableName, options, model), + this.queryGenerator.selectQuery(tableName, options, model), options ); } @@ -1126,7 +966,7 @@ class QueryInterface { async increment(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = Utils.cloneDeep(options); - const sql = this.QueryGenerator.arithmeticQuery('+', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); + const sql = this.queryGenerator.arithmeticQuery('+', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); options.type = QueryTypes.UPDATE; options.model = model; @@ -1137,7 +977,7 @@ class QueryInterface { async decrement(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { options = Utils.cloneDeep(options); - const sql = this.QueryGenerator.arithmeticQuery('-', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); + const sql = this.queryGenerator.arithmeticQuery('-', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); options.type = QueryTypes.UPDATE; options.model = model; @@ -1153,7 +993,7 @@ class QueryInterface { type: QueryTypes.SELECT }); - const sql = this.QueryGenerator.selectQuery(tableName, options, Model); + const sql = this.queryGenerator.selectQuery(tableName, options, Model); if (attributeSelector === undefined) { throw new Error('Please pass an attribute selector!'); @@ -1198,7 +1038,7 @@ class QueryInterface { optionsArray, options ) { - const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); + const sql = this.queryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); options = options || {}; if (sql) { return await this.sequelize.query(sql, options); @@ -1206,7 +1046,7 @@ class QueryInterface { } async dropTrigger(tableName, triggerName, options) { - const sql = this.QueryGenerator.dropTrigger(tableName, triggerName); + const sql = this.queryGenerator.dropTrigger(tableName, triggerName); options = options || {}; if (sql) { @@ -1215,7 +1055,7 @@ class QueryInterface { } async renameTrigger(tableName, oldTriggerName, newTriggerName, options) { - const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); + const sql = this.queryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); options = options || {}; if (sql) { @@ -1261,7 +1101,7 @@ class QueryInterface { * @returns {Promise} */ async createFunction(functionName, params, returnType, language, body, optionsArray, options) { - const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options); + const sql = this.queryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options); options = options || {}; if (sql) { @@ -1288,7 +1128,7 @@ class QueryInterface { * @returns {Promise} */ async dropFunction(functionName, params, options) { - const sql = this.QueryGenerator.dropFunction(functionName, params); + const sql = this.queryGenerator.dropFunction(functionName, params); options = options || {}; if (sql) { @@ -1317,7 +1157,7 @@ class QueryInterface { * @returns {Promise} */ async renameFunction(oldFunctionName, params, newFunctionName, options) { - const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName); + const sql = this.queryGenerator.renameFunction(oldFunctionName, params, newFunctionName); options = options || {}; if (sql) { @@ -1328,42 +1168,10 @@ class QueryInterface { // Helper methods useful for querying /** - * Escape an identifier (e.g. a table or attribute name) - * - * @param {string} identifier identifier to quote - * @param {boolean} [force] If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false. - * - * @private - */ - quoteIdentifier(identifier, force) { - return this.QueryGenerator.quoteIdentifier(identifier, force); - } - - quoteTable(identifier) { - return this.QueryGenerator.quoteTable(identifier); - } - - /** - * Quote array of identifiers at once - * - * @param {string[]} identifiers array of identifiers to quote - * @param {boolean} [force] If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false. - * - * @private - */ - quoteIdentifiers(identifiers, force) { - return this.QueryGenerator.quoteIdentifiers(identifiers, force); - } - - /** - * Escape a value (e.g. a string, number or date) - * - * @param {string} value string to escape - * * @private */ - escape(value) { - return this.QueryGenerator.escape(value); + ensureEnums() { + // noop by default } async setIsolationLevel(transaction, value, options) { @@ -1378,7 +1186,7 @@ class QueryInterface { options = { ...options, transaction: transaction.parent || transaction }; - const sql = this.QueryGenerator.setIsolationLevelQuery(value, { + const sql = this.queryGenerator.setIsolationLevelQuery(value, { parent: transaction.parent }); @@ -1394,7 +1202,7 @@ class QueryInterface { options = { ...options, transaction: transaction.parent || transaction }; options.transaction.name = transaction.parent ? transaction.name : undefined; - const sql = this.QueryGenerator.startTransactionQuery(transaction); + const sql = this.queryGenerator.startTransactionQuery(transaction); return await this.sequelize.query(sql, options); } @@ -1402,7 +1210,7 @@ class QueryInterface { async deferConstraints(transaction, options) { options = { ...options, transaction: transaction.parent || transaction }; - const sql = this.QueryGenerator.deferConstraintsQuery(options); + const sql = this.queryGenerator.deferConstraintsQuery(options); if (sql) { return await this.sequelize.query(sql, options); @@ -1425,7 +1233,7 @@ class QueryInterface { completesTransaction: true }; - const sql = this.QueryGenerator.commitTransactionQuery(transaction); + const sql = this.queryGenerator.commitTransactionQuery(transaction); const promise = this.sequelize.query(sql, options); transaction.finished = 'commit'; @@ -1445,7 +1253,7 @@ class QueryInterface { completesTransaction: true }; options.transaction.name = transaction.parent ? transaction.name : undefined; - const sql = this.QueryGenerator.rollbackTransactionQuery(transaction); + const sql = this.queryGenerator.rollbackTransactionQuery(transaction); const promise = this.sequelize.query(sql, options); transaction.finished = 'rollback'; @@ -1454,6 +1262,4 @@ class QueryInterface { } } -module.exports = QueryInterface; -module.exports.QueryInterface = QueryInterface; -module.exports.default = QueryInterface; +exports.QueryInterface = QueryInterface; diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index eba46cd3eb88..4ed1360b8443 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -15,11 +15,8 @@ const parserStore = require('../parserStore')('mariadb'); * AbstractConnectionManager pooling use it to handle MariaDB specific connections * Use https://github.com/MariaDB/mariadb-connector-nodejs to connect with MariaDB server * - * @extends AbstractConnectionManager - * @returns Class * @private */ - class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { sequelize.config.port = sequelize.config.port || 3306; diff --git a/lib/dialects/mariadb/index.js b/lib/dialects/mariadb/index.js index 3baed56342ae..f028a37c8d57 100644 --- a/lib/dialects/mariadb/index.js +++ b/lib/dialects/mariadb/index.js @@ -5,6 +5,7 @@ const AbstractDialect = require('../abstract'); const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); +const { MySQLQueryInterface } = require('../mysql/query-interface'); const DataTypes = require('../../data-types').mariadb; class MariadbDialect extends AbstractDialect { @@ -12,10 +13,11 @@ class MariadbDialect extends AbstractDialect { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new MySQLQueryInterface(sequelize, this.queryGenerator); } } diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 28c0889a2810..3d0173cec4d9 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -6,16 +6,18 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').mssql; +const { MSSqlQueryInterface } = require('./query-interface'); class MssqlDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new MSSqlQueryInterface(sequelize, this.queryGenerator); } } diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index 3ebb39fc0a90..3c5eee411ff9 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -1,53 +1,53 @@ 'use strict'; -/** - Returns an object that treats MSSQL's inabilities to do certain queries. - - @class QueryInterface - @static - @private - */ +const { QueryInterface } = require('../abstract/query-interface'); /** - A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. - - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} attributeName The name of the attribute that we want to remove. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @private + * The interface that Sequelize uses to talk with MSSQL database */ -const removeColumn = async function(qi, tableName, attributeName, options) { - options = { raw: true, ...options }; - - const findConstraintSql = qi.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); - const [results0] = await qi.sequelize.query(findConstraintSql, options); - if (results0.length) { - // No default constraint found -- we can cleanly remove the column - const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, results0[0].name); - await qi.sequelize.query(dropConstraintSql, options); +class MSSqlQueryInterface extends QueryInterface { + /** + * A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. + * + * @override + */ + async removeColumn(tableName, attributeName, options) { + options = { raw: true, ...options || {} }; + + const findConstraintSql = this.queryGenerator.getDefaultConstraintQuery(tableName, attributeName); + const [results0] = await this.sequelize.query(findConstraintSql, options); + if (results0.length) { + // No default constraint found -- we can cleanly remove the column + const dropConstraintSql = this.queryGenerator.dropConstraintQuery(tableName, results0[0].name); + await this.sequelize.query(dropConstraintSql, options); + } + const findForeignKeySql = this.queryGenerator.getForeignKeyQuery(tableName, attributeName); + const [results] = await this.sequelize.query(findForeignKeySql, options); + if (results.length) { + // No foreign key constraints found, so we can remove the column + const dropForeignKeySql = this.queryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); + await this.sequelize.query(dropForeignKeySql, options); + } + //Check if the current column is a primaryKey + const primaryKeyConstraintSql = this.queryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); + const [result] = await this.sequelize.query(primaryKeyConstraintSql, options); + if (result.length) { + const dropConstraintSql = this.queryGenerator.dropConstraintQuery(tableName, result[0].constraintName); + await this.sequelize.query(dropConstraintSql, options); + } + const removeSql = this.queryGenerator.removeColumnQuery(tableName, attributeName); + return this.sequelize.query(removeSql, options); } - const findForeignKeySql = qi.QueryGenerator.getForeignKeyQuery(tableName, attributeName); - const [results] = await qi.sequelize.query(findForeignKeySql, options); - if (results.length) { - // No foreign key constraints found, so we can remove the column - const dropForeignKeySql = qi.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); - await qi.sequelize.query(dropForeignKeySql, options); - } - //Check if the current column is a primaryKey - const primaryKeyConstraintSql = qi.QueryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); - const [result] = await qi.sequelize.query(primaryKeyConstraintSql, options); - if (result.length) { - const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, result[0].constraintName); - await qi.sequelize.query(dropConstraintSql, options); + + /** + * @override + */ + _convertUpsertResult(result, model) { + return [ + result.$action === 'INSERT', + result[model.primaryKeyField] + ]; } - const removeSql = qi.QueryGenerator.removeColumnQuery(tableName, attributeName); - return qi.sequelize.query(removeSql, options); -}; +} -module.exports = { - removeColumn -}; +exports.MSSqlQueryInterface = MSSqlQueryInterface; diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 8fdb979d463f..1eda51826726 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -16,11 +16,8 @@ const { promisify } = require('util'); * AbstractConnectionManager pooling use it to handle MySQL specific connections * Use https://github.com/sidorares/node-mysql2 to connect with MySQL server * - * @extends AbstractConnectionManager - * @returns Class * @private */ - class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { sequelize.config.port = sequelize.config.port || 3306; diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js index 4e5cadbda148..c999b64178e1 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -6,16 +6,18 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').mysql; +const { MySQLQueryInterface } = require('./query-interface'); class MysqlDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new MySQLQueryInterface(sequelize, this.queryGenerator); } } diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 3b2c1fa680d6..5e2bcc922de0 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -1,89 +1,81 @@ 'use strict'; -/** - Returns an object that treats MySQL's inabilities to do certain queries. - - @class QueryInterface - @static - @private - */ - const sequelizeErrors = require('../../errors'); +const { QueryInterface } = require('../abstract/query-interface'); /** - A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} columnName The name of the attribute that we want to remove. - @param {object} options - - @private + * The interface that Sequelize uses to talk with MySQL/MariaDB database */ -async function removeColumn(qi, tableName, columnName, options) { - options = options || {}; +class MySQLQueryInterface extends QueryInterface { + /** + * A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. + * + * @override + */ + async removeColumn(tableName, columnName, options) { + options = options || {}; - const [results] = await qi.sequelize.query( - qi.QueryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { - tableName, - schema: qi.sequelize.config.database - }, columnName), - { raw: true, ...options } - ); + const [results] = await this.sequelize.query( + this.queryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { + tableName, + schema: this.sequelize.config.database + }, columnName), + { raw: true, ...options } + ); - //Exclude primary key constraint - if (results.length && results[0].constraint_name !== 'PRIMARY') { - await Promise.all(results.map(constraint => qi.sequelize.query( - qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), + //Exclude primary key constraint + if (results.length && results[0].constraint_name !== 'PRIMARY') { + await Promise.all(results.map(constraint => this.sequelize.query( + this.queryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), + { raw: true, ...options } + ))); + } + + return await this.sequelize.query( + this.queryGenerator.removeColumnQuery(tableName, columnName), { raw: true, ...options } - ))); + ); } - return await qi.sequelize.query( - qi.QueryGenerator.removeColumnQuery(tableName, columnName), - { raw: true, ...options } - ); -} + /** + * @override + */ + async removeConstraint(tableName, constraintName, options) { + const sql = this.queryGenerator.showConstraintsQuery( + tableName.tableName ? tableName : { + tableName, + schema: this.sequelize.config.database + }, constraintName); -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {string} constraintName - * @param {object} options - * - * @private - */ -async function removeConstraint(qi, tableName, constraintName, options) { - const sql = qi.QueryGenerator.showConstraintsQuery( - tableName.tableName ? tableName : { - tableName, - schema: qi.sequelize.config.database - }, constraintName); + const constraints = await this.sequelize.query(sql, { ...options, + type: this.sequelize.QueryTypes.SHOWCONSTRAINTS }); - const constraints = await qi.sequelize.query(sql, { - ...options, - type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS - }); + const constraint = constraints[0]; + let query; + if (!constraint || !constraint.constraintType) { + throw new sequelizeErrors.UnknownConstraintError( + { + message: `Constraint ${constraintName} on table ${tableName} does not exist`, + constraint: constraintName, + table: tableName + }); + } - const constraint = constraints[0]; - let query; - if (!constraint || !constraint.constraintType) { - throw new sequelizeErrors.UnknownConstraintError( - { - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - } + if (constraint.constraintType === 'FOREIGN KEY') { + query = this.queryGenerator.dropForeignKeyQuery(tableName, constraintName); + } else { + query = this.queryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); + } - if (constraint.constraintType === 'FOREIGN KEY') { - query = qi.QueryGenerator.dropForeignKeyQuery(tableName, constraintName); - } else { - query = qi.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); + return await this.sequelize.query(query, options); } - return await qi.sequelize.query(query, options); + /** + * @override + */ + _convertUpsertResult(result) { + return [result === 1, undefined]; + } } -exports.removeConstraint = removeConstraint; -exports.removeColumn = removeColumn; +exports.MySQLQueryInterface = MySQLQueryInterface; diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index 5967d334a74d..8ff6b583442d 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -6,16 +6,18 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').postgres; +const { PostgresQueryInterface } = require('./query-interface'); class PostgresDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new PostgresQueryInterface(sequelize, this.queryGenerator); } } diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 1556c2ef7367..1beb1068bf6d 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -2,154 +2,252 @@ const DataTypes = require('../../data-types'); const QueryTypes = require('../../query-types'); +const { QueryInterface } = require('../abstract/query-interface'); +const Utils = require('../../utils'); /** - Returns an object that handles Postgres special needs to do certain queries. - - @class QueryInterface - @static - @private + * The interface that Sequelize uses to talk with Postgres database */ - -/** +class PostgresQueryInterface extends QueryInterface { + /** * Ensure enum and their values. * - * @param {QueryInterface} qi * @param {string} tableName Name of table to create * @param {object} attributes Object representing a list of normalized table attributes * @param {object} [options] * @param {Model} [model] * - * @returns {Promise} - * @private + * @protected */ -async function ensureEnums(qi, tableName, attributes, options, model) { - const keys = Object.keys(attributes); - const keyLen = keys.length; - - let sql = ''; - let promises = []; - let i = 0; - - for (i = 0; i < keyLen; i++) { - const attribute = attributes[keys[i]]; - const type = attribute.type; - - if ( - type instanceof DataTypes.ENUM || - type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM - ) { - sql = qi.QueryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); - promises.push(qi.sequelize.query( - sql, - { ...options, plain: true, raw: true, type: QueryTypes.SELECT } - )); + async ensureEnums(tableName, attributes, options, model) { + const keys = Object.keys(attributes); + const keyLen = keys.length; + + let sql = ''; + let promises = []; + let i = 0; + + for (i = 0; i < keyLen; i++) { + const attribute = attributes[keys[i]]; + const type = attribute.type; + + if ( + type instanceof DataTypes.ENUM || + type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM + ) { + sql = this.queryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); + promises.push(this.sequelize.query( + sql, + { ...options, plain: true, raw: true, type: QueryTypes.SELECT } + )); + } } - } - const results = await Promise.all(promises); - promises = []; - let enumIdx = 0; + const results = await Promise.all(promises); + promises = []; + let enumIdx = 0; + + // This little function allows us to re-use the same code that prepends or appends new value to enum array + const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { + const valueOptions = { ...options }; + valueOptions.before = null; + valueOptions.after = null; + + switch (position) { + case 'after': + valueOptions.after = relativeValue; + break; + case 'before': + default: + valueOptions.before = relativeValue; + break; + } - // This little function allows us to re-use the same code that prepends or appends new value to enum array - const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { - const valueOptions = { - ...options, - before: null, - after: null + promises.splice(spliceStart, 0, () => { + return this.sequelize.query(this.queryGenerator.pgEnumAdd( + tableName, field, value, valueOptions + ), valueOptions); + }); }; - switch (position) { - case 'after': - valueOptions.after = relativeValue; - break; - case 'before': - default: - valueOptions.before = relativeValue; - break; - } + for (i = 0; i < keyLen; i++) { + const attribute = attributes[keys[i]]; + const type = attribute.type; + const enumType = type.type || type; + const field = attribute.field || keys[i]; + + if ( + type instanceof DataTypes.ENUM || + type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM + ) { + // If the enum type doesn't exist then create it + if (!results[enumIdx]) { + promises.push(() => { + return this.sequelize.query(this.queryGenerator.pgEnum(tableName, field, enumType, options), { ...options, raw: true }); + }); + } else if (!!results[enumIdx] && !!model) { + const enumVals = this.queryGenerator.fromArray(results[enumIdx].enum_value); + const vals = enumType.values; + + // Going through already existing values allows us to make queries that depend on those values + // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values + // Then we append the rest of new values AFTER the latest already existing value + // E.g.: [1,2] -> [0,2,1] ==> [1,0,2] + // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4] + // E.g.: [1] -> [0,2,3] ==> [1,0,2,3] + let lastOldEnumValue; + let rightestPosition = -1; + for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) { + const enumVal = enumVals[oldIndex]; + const newIdx = vals.indexOf(enumVal); + lastOldEnumValue = enumVal; + + if (newIdx === -1) { + continue; + } - promises.splice(spliceStart, 0, () => { - return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd( - tableName, field, value, valueOptions - ), valueOptions); - }); - }; - - for (i = 0; i < keyLen; i++) { - const attribute = attributes[keys[i]]; - const type = attribute.type; - const enumType = type.type || type; - const field = attribute.field || keys[i]; - - if ( - type instanceof DataTypes.ENUM || - type instanceof DataTypes.ARRAY && enumType instanceof DataTypes.ENUM //ARRAY sub type is ENUM - ) { - // If the enum type doesn't exist then create it - if (!results[enumIdx]) { - promises.push(() => { - return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), { ...options, raw: true }); - }); - } else if (!!results[enumIdx] && !!model) { - const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value); - const vals = enumType.values; - - // Going through already existing values allows us to make queries that depend on those values - // We will prepend all new values between the old ones, but keep in mind - we can't change order of already existing values - // Then we append the rest of new values AFTER the latest already existing value - // E.g.: [1,2] -> [0,2,1] ==> [1,0,2] - // E.g.: [1,2,3] -> [2,1,3,4] ==> [1,2,3,4] - // E.g.: [1] -> [0,2,3] ==> [1,0,2,3] - let lastOldEnumValue; - let rightestPosition = -1; - for (let oldIndex = 0; oldIndex < enumVals.length; oldIndex++) { - const enumVal = enumVals[oldIndex]; - const newIdx = vals.indexOf(enumVal); - lastOldEnumValue = enumVal; - - if (newIdx === -1) { - continue; - } + const newValuesBefore = vals.slice(0, newIdx); + const promisesLength = promises.length; + // we go in reverse order so we could stop when we meet old value + for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { + if (~enumVals.indexOf(newValuesBefore[reverseIdx])) { + break; + } - const newValuesBefore = vals.slice(0, newIdx); - const promisesLength = promises.length; - // we go in reverse order so we could stop when we meet old value - for (let reverseIdx = newValuesBefore.length - 1; reverseIdx >= 0; reverseIdx--) { - if (~enumVals.indexOf(newValuesBefore[reverseIdx])) { - break; + addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength); } - addEnumValue(field, newValuesBefore[reverseIdx], lastOldEnumValue, 'before', promisesLength); + // we detect the most 'right' position of old value in new enum array so we can append new values to it + if (newIdx > rightestPosition) { + rightestPosition = newIdx; + } } - // we detect the most 'right' position of old value in new enum array so we can append new values to it - if (newIdx > rightestPosition) { - rightestPosition = newIdx; + if (lastOldEnumValue && rightestPosition < vals.length - 1) { + const remainingEnumValues = vals.slice(rightestPosition + 1); + for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { + addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after'); + } } - } - if (lastOldEnumValue && rightestPosition < vals.length - 1) { - const remainingEnumValues = vals.slice(rightestPosition + 1); - for (let reverseIdx = remainingEnumValues.length - 1; reverseIdx >= 0; reverseIdx--) { - addEnumValue(field, remainingEnumValues[reverseIdx], lastOldEnumValue, 'after'); - } + enumIdx++; } - - enumIdx++; } } + + const result = await promises + .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve()); + + // If ENUM processed, then refresh OIDs + if (promises.length) { + await this.sequelize.dialect.connectionManager._refreshDynamicOIDs(); + } + return result; } - const result = await promises - .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve()); + /** + * @override + */ + async getForeignKeyReferencesForTable(tableName, options) { + const queryOptions = { + ...options, + type: QueryTypes.FOREIGNKEYS + }; - // If ENUM processed, then refresh OIDs - if (promises.length) { - await qi.sequelize.dialect.connectionManager._refreshDynamicOIDs(); + // postgres needs some special treatment as those field names returned are all lowercase + // in order to keep same result with other dialects. + const query = this.queryGenerator.getForeignKeyReferencesQuery(tableName, this.sequelize.config.database); + const result = await this.sequelize.query(query, queryOptions); + return result.map(Utils.camelizeObjectKeys); + } + + /** + * Drop specified enum from database (Postgres only) + * + * @param {string} [enumName] Enum name to drop + * @param {object} options Query options + * + * @returns {Promise} + */ + async dropEnum(enumName, options) { + options = options || {}; + + return this.sequelize.query( + this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(enumName)), + { ...options, raw: true } + ); + } + + /** + * Drop all enums from database (Postgres only) + * + * @param {object} options Query options + * + * @returns {Promise} + */ + async dropAllEnums(options) { + options = options || {}; + + const enums = await this.pgListEnums(null, options); + + return await Promise.all(enums.map(result => this.sequelize.query( + this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(result.enum_name)), + { ...options, raw: true } + ))); + } + + /** + * List all enums (Postgres only) + * + * @param {string} [tableName] Table whose enum to list + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async pgListEnums(tableName, options) { + options = options || {}; + const sql = this.queryGenerator.pgListEnums(tableName); + return this.sequelize.query(sql, { ...options, plain: false, raw: true, type: QueryTypes.SELECT }); } - return result; -} + /** + * Since postgres has a special case for enums, we should drop the related + * enum type within the table and attribute + * + * @override + */ + async dropTable(tableName, options) { + await super.dropTable(tableName, options); + const promises = []; + const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); + + if (!instanceTable) { + // Do nothing when model is not available + return; + } + + const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName; + + const keys = Object.keys(instanceTable.rawAttributes); + const keyLen = keys.length; + + for (let i = 0; i < keyLen; i++) { + if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { + const sql = this.queryGenerator.pgEnumDrop(getTableName, keys[i]); + options.supportsSearchPath = false; + promises.push(this.sequelize.query(sql, { ...options, raw: true })); + } + } + + await Promise.all(promises); + } + + /** + * @override + */ + _convertUpsertResult(result) { + return [result.created, result.primary_key]; + } +} -exports.ensureEnums = ensureEnums; +exports.PostgresQueryInterface = PostgresQueryInterface; diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 3cfc388a6157..15ea21410e37 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -50,7 +50,7 @@ class Query extends AbstractQuery { const { connection } = this; if (!_.isEmpty(this.options.searchPath)) { - sql = this.sequelize.getQueryInterface().QueryGenerator.setSearchPath(this.options.searchPath) + sql; + sql = this.sequelize.getQueryInterface().queryGenerator.setSearchPath(this.options.searchPath) + sql; } if (this.sequelize.options.minifyAliases && this.options.includeAliases) { @@ -142,7 +142,7 @@ class Query extends AbstractQuery { // Map column index in table to column name const columns = _.zipObject( row.column_indexes, - this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.column_names) + this.sequelize.getQueryInterface().queryGenerator.fromArray(row.column_names) ); delete row.column_indexes; delete row.column_names; @@ -219,7 +219,7 @@ class Query extends AbstractQuery { allowNull: row.Null === 'YES', defaultValue: row.Default, comment: row.Comment, - special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [], + special: row.special ? this.sequelize.getQueryInterface().queryGenerator.fromArray(row.special) : [], primaryKey: row.Constraint === 'PRIMARY KEY' }; diff --git a/lib/dialects/sqlite/index.js b/lib/dialects/sqlite/index.js index 94c8934fe95b..1090145c645c 100644 --- a/lib/dialects/sqlite/index.js +++ b/lib/dialects/sqlite/index.js @@ -6,16 +6,19 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').sqlite; +const { SQLiteQueryInterface } = require('./query-interface'); class SqliteDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + + this.queryInterface = new SQLiteQueryInterface(sequelize, this.queryGenerator); } } diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 36e3a042d8e9..b47140a88ad5 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -257,7 +257,6 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { attributesToSQL(attributes) { const result = {}; - for (const name in attributes) { const dataType = attributes[name]; const fieldName = dataType.field || name; diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index c56333285099..8f31bd793ad2 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -2,192 +2,172 @@ const sequelizeErrors = require('../../errors'); const QueryTypes = require('../../query-types'); +const { QueryInterface } = require('../abstract/query-interface'); +const { cloneDeep } = require('../../utils'); /** - Returns an object that treats SQLite's inabilities to do certain queries. - - @class QueryInterface - @static - @private - */ - -/** - A wrapper that fixes SQLite's inability to remove columns from existing tables. - It will create a backup of the table, drop the table afterwards and create a - new table with the same name but without the obsolete column. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} attributeName The name of the attribute that we want to remove. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @since 1.6.0 - @private + * The interface that Sequelize uses to talk with SQLite database */ -async function removeColumn(qi, tableName, attributeName, options) { - options = options || {}; +class SQLiteQueryInterface extends QueryInterface { + /** + * A wrapper that fixes SQLite's inability to remove columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but without the obsolete column. + * + * @override + */ + async removeColumn(tableName, attributeName, options) { + options = options || {}; + + const fields = await this.describeTable(tableName, options); + delete fields[attributeName]; + + const sql = this.queryGenerator.removeColumnQuery(tableName, fields); + const subQueries = sql.split(';').filter(q => q !== ''); + + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } - const fields = await qi.describeTable(tableName, options); - delete fields[attributeName]; + /** + * A wrapper that fixes SQLite's inability to change columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but with a modified version of the respective column. + * + * @override + */ + async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { + options = options || {}; - const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); - const subQueries = sql.split(';').filter(q => q !== ''); + const fields = await this.describeTable(tableName, options); + fields[attributeName] = this.normalizeAttribute(dataTypeOrOptions); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); -} -exports.removeColumn = removeColumn; + const sql = this.queryGenerator.removeColumnQuery(tableName, fields); + const subQueries = sql.split(';').filter(q => q !== ''); -/** - A wrapper that fixes SQLite's inability to change columns from existing tables. - It will create a backup of the table, drop the table afterwards and create a - new table with the same name but with a modified version of the respective column. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {object} attributes An object with the attribute's name as key and its options as value object. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @since 1.6.0 - @private - */ -async function changeColumn(qi, tableName, attributes, options) { - const attributeName = Object.keys(attributes)[0]; - options = options || {}; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } - const fields = await qi.describeTable(tableName, options); - fields[attributeName] = attributes[attributeName]; + /** + * A wrapper that fixes SQLite's inability to rename columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but with a renamed version of the respective column. + * + * @override + */ + async renameColumn(tableName, attrNameBefore, attrNameAfter, options) { + options = options || {}; + const fields = await this.assertTableHasColumn(tableName, attrNameBefore, options); - const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); - const subQueries = sql.split(';').filter(q => q !== ''); + fields[attrNameAfter] = { ...fields[attrNameBefore] }; + delete fields[attrNameBefore]; - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); -} -exports.changeColumn = changeColumn; + const sql = this.queryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); + const subQueries = sql.split(';').filter(q => q !== ''); -/** - A wrapper that fixes SQLite's inability to rename columns from existing tables. - It will create a backup of the table, drop the table afterwards and create a - new table with the same name but with a renamed version of the respective column. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} attrNameBefore The name of the attribute before it was renamed. - @param {string} attrNameAfter The name of the attribute after it was renamed. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @since 1.6.0 - @private - */ -async function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options) { - options = options || {}; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } - const fields = await qi.describeTable(tableName, options); - fields[attrNameAfter] = { ...fields[attrNameBefore] }; - delete fields[attrNameBefore]; + /** + * @override + */ + async removeConstraint(tableName, constraintName, options) { + let createTableSql; + + const constraints = await this.showConstraint(tableName, constraintName); + // sqlite can't show only one constraint, so we find here the one to remove + const constraint = constraints.find(constaint => constaint.constraintName === constraintName); + + if (!constraint) { + throw new sequelizeErrors.UnknownConstraintError({ + message: `Constraint ${constraintName} on table ${tableName} does not exist`, + constraint: constraintName, + table: tableName + }); + } + createTableSql = constraint.sql; + constraint.constraintName = this.queryGenerator.quoteIdentifier(constraint.constraintName); + let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; + + if (constraint.constraintType === 'FOREIGN KEY') { + const referenceTableName = this.queryGenerator.quoteTable(constraint.referenceTableName); + constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => this.queryGenerator.quoteIdentifier(columnName)); + const referenceTableKeys = constraint.referenceTableKeys.join(', '); + constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; + constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; + constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; + } + + createTableSql = createTableSql.replace(constraintSnippet, ''); + createTableSql += ';'; + + const fields = await this.describeTable(tableName, options); + + const sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql); + const subQueries = sql.split(';').filter(q => q !== ''); + + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } - const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); - const subQueries = sql.split(';').filter(q => q !== ''); + /** + * @override + */ + async addConstraint(tableName, options) { + if (!options.fields) { + throw new Error('Fields must be specified through options.fields'); + } - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); -} -exports.renameColumn = renameColumn; + if (!options.type) { + throw new Error('Constraint type must be specified through options.type'); + } -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {string} constraintName - * @param {object} options - * - * @private - */ -async function removeConstraint(qi, tableName, constraintName, options) { - let createTableSql; - - const constraints = await qi.showConstraint(tableName, constraintName); - // sqlite can't show only one constraint, so we find here the one to remove - const constraint = constraints.find(constaint => constaint.constraintName === constraintName); - - if (!constraint) { - throw new sequelizeErrors.UnknownConstraintError({ - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - } - createTableSql = constraint.sql; - constraint.constraintName = qi.QueryGenerator.quoteIdentifier(constraint.constraintName); - let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; - - if (constraint.constraintType === 'FOREIGN KEY') { - const referenceTableName = qi.QueryGenerator.quoteTable(constraint.referenceTableName); - constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => qi.QueryGenerator.quoteIdentifier(columnName)); - const referenceTableKeys = constraint.referenceTableKeys.join(', '); - constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; - constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; - constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; - } + options = cloneDeep(options); - createTableSql = createTableSql.replace(constraintSnippet, ''); - createTableSql += ';'; + const constraintSnippet = this.queryGenerator.getConstraintSnippet(tableName, options); + const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName); - const fields = await qi.describeTable(tableName, options); + const constraints = await this.sequelize.query(describeCreateTableSql, { ...options, type: QueryTypes.SELECT, raw: true }); + let sql = constraints[0].sql; + const index = sql.length - 1; + //Replace ending ')' with constraint snippet - Simulates String.replaceAt + //http://stackoverflow.com/questions/1431094 + const createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`; - const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); + const fields = await this.describeTable(tableName, options); + sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql); + const subQueries = sql.split(';').filter(q => q !== ''); - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); -} -exports.removeConstraint = removeConstraint; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {object} options - * - * @private - */ -async function addConstraint(qi, tableName, options) { - const constraintSnippet = qi.QueryGenerator.getConstraintSnippet(tableName, options); - const describeCreateTableSql = qi.QueryGenerator.describeCreateTableQuery(tableName); - - const constraints = await qi.sequelize.query(describeCreateTableSql, { ...options, type: QueryTypes.SELECT, raw: true }); - let sql = constraints[0].sql; - const index = sql.length - 1; - //Replace ending ')' with constraint snippet - Simulates String.replaceAt - //http://stackoverflow.com/questions/1431094 - const createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`; - - const fields = await qi.describeTable(tableName, options); - sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); - - for (const subQuery of subQueries) await qi.sequelize.query(`${subQuery};`, { raw: true, ...options }); -} -exports.addConstraint = addConstraint; + /** + * @override + */ + async getForeignKeyReferencesForTable(tableName, options) { + const database = this.sequelize.config.database; + const query = this.queryGenerator.getForeignKeysQuery(tableName, database); + const result = await this.sequelize.query(query, options); + return result.map(row => ({ + tableName, + columnName: row.from, + referencedTableName: row.table, + referencedColumnName: row.to, + tableCatalog: database, + referencedTableCatalog: database + })); + } -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {object} options Query Options - * - * @private - * @returns {Promise} - */ -async function getForeignKeyReferencesForTable(qi, tableName, options) { - const database = qi.sequelize.config.database; - const query = qi.QueryGenerator.getForeignKeysQuery(tableName, database); - const result = await qi.sequelize.query(query, options); - return result.map(row => ({ - tableName, - columnName: row.from, - referencedTableName: row.table, - referencedColumnName: row.to, - tableCatalog: database, - referencedTableCatalog: database - })); + /** + * @override + */ + async dropAllTables(options) { + options = options || {}; + const skip = options.skip || []; + + const tableNames = await this.showAllTables(options); + await this.sequelize.query('PRAGMA foreign_keys = OFF', options); + await this._dropAllTables(tableNames, skip, options); + await this.sequelize.query('PRAGMA foreign_keys = ON', options); + } } -exports.getForeignKeyReferencesForTable = getForeignKeyReferencesForTable; +exports.SQLiteQueryInterface = SQLiteQueryInterface; diff --git a/lib/model.js b/lib/model.js index ef7a7d3ed442..701841ad111d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -51,12 +51,12 @@ const nonCascadingOptions = ['include', 'attributes', 'originalAttributes', 'ord * @mixes Hooks */ class Model { - static get QueryInterface() { + static get queryInterface() { return this.sequelize.getQueryInterface(); } - static get QueryGenerator() { - return this.QueryInterface.QueryGenerator; + static get queryGenerator() { + return this.queryInterface.queryGenerator; } /** @@ -1284,12 +1284,12 @@ class Model { const tableName = this.getTableName(options); - await this.QueryInterface.createTable(tableName, attributes, options, this); + await this.queryInterface.createTable(tableName, attributes, options, this); if (options.alter) { const tableInfos = await Promise.all([ - this.QueryInterface.describeTable(tableName, options), - this.QueryInterface.getForeignKeyReferencesForTable(tableName, options) + this.queryInterface.describeTable(tableName, options), + this.queryInterface.getForeignKeyReferencesForTable(tableName, options) ]); const columns = tableInfos[0]; // Use for alter foreign keys @@ -1299,7 +1299,7 @@ class Model { for (const columnName in attributes) { if (!Object.prototype.hasOwnProperty.call(attributes, columnName)) continue; if (!columns[columnName] && !columns[attributes[columnName].field]) { - await this.QueryInterface.addColumn(tableName, attributes[columnName].field || columnName, attributes[columnName], options); + await this.queryInterface.addColumn(tableName, attributes[columnName].field || columnName, attributes[columnName], options); } } @@ -1308,7 +1308,7 @@ class Model { if (!Object.prototype.hasOwnProperty.call(columns, columnName)) continue; const currentAttribute = rawAttributes[columnName]; if (!currentAttribute) { - await this.QueryInterface.removeColumn(tableName, columnName, options); + await this.queryInterface.removeColumn(tableName, columnName, options); continue; } if (currentAttribute.primaryKey) continue; @@ -1328,16 +1328,16 @@ class Model { && (schema ? foreignKeyReference.referencedTableSchema === schema : true) && !removedConstraints[constraintName]) { // Remove constraint on foreign keys. - await this.QueryInterface.removeConstraint(tableName, constraintName, options); + await this.queryInterface.removeConstraint(tableName, constraintName, options); removedConstraints[constraintName] = true; } } } - await this.QueryInterface.changeColumn(tableName, columnName, currentAttribute, options); + await this.queryInterface.changeColumn(tableName, columnName, currentAttribute, options); } } } - let indexes = await this.QueryInterface.showIndex(tableName, options); + let indexes = await this.queryInterface.showIndex(tableName, options); indexes = this._indexes.filter(item1 => !indexes.some(item2 => item1.name === item2.name) ).sort((index1, index2) => { @@ -1351,7 +1351,7 @@ class Model { }); for (const index of indexes) { - await this.QueryInterface.addIndex(tableName, { ...options, ...index }); + await this.queryInterface.addIndex(tableName, { ...options, ...index }); } if (options.hooks) { @@ -1372,11 +1372,11 @@ class Model { * @returns {Promise} */ static async drop(options) { - return await this.QueryInterface.dropTable(this.getTableName(options), options); + return await this.queryInterface.dropTable(this.getTableName(options), options); } static async dropSchema(schema) { - return await this.QueryInterface.dropSchema(schema); + return await this.queryInterface.dropSchema(schema); } /** @@ -1425,7 +1425,7 @@ class Model { * @returns {string|object} */ static getTableName() { - return this.QueryGenerator.addSchema(this); + return this.queryGenerator.addSchema(this); } /** @@ -1737,7 +1737,7 @@ class Model { await this.runHooks('beforeFindAfterOptions', options); } const selectOptions = { ...options, tableNames: Object.keys(tableNames) }; - const results = await this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); + const results = await this.queryInterface.select(this, this.getTableName(selectOptions), selectOptions); if (options.hooks) { await this.runHooks('afterFind', results, options); } @@ -1970,7 +1970,7 @@ class Model { Utils.mapOptionFieldNames(options, this); options = this._paranoidClause(this, options); - const value = await this.QueryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this); + const value = await this.queryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this); if (value === null) { return 0; } @@ -2466,7 +2466,7 @@ class Model { if (options.hooks) { await this.runHooks('beforeUpsert', values, options); } - const [created, primaryKey] = await this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); + const [created, primaryKey] = await this.queryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); let result; if (options.returning === true && primaryKey) { const record = await this.findByPk(primaryKey, options); @@ -2686,7 +2686,7 @@ class Model { options.returning = options.returning.map(attr => _.get(model.rawAttributes[attr], 'field', attr)); } - const results = await model.QueryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes); + const results = await model.queryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes); if (Array.isArray(results)) { results.forEach((result, i) => { const instance = instances[i]; @@ -2908,9 +2908,9 @@ class Model { attrValueHash[field] = Utils.now(this.sequelize.options.dialect); - result = await this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); + result = await this.queryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); } else { - result = await this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this); + result = await this.queryInterface.bulkDelete(this.getTableName(options), options.where, options, this); } // Run afterDestroy hook on each record individually if (options.individualHooks) { @@ -2973,7 +2973,7 @@ class Model { attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; options.omitNull = false; - const result = await this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); + const result = await this.queryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); // Run afterDestroy hook on each record individually if (options.individualHooks) { await Promise.all( @@ -3163,7 +3163,7 @@ class Model { options = Utils.mapOptionFieldNames(options, this); options.hasTrigger = this.options ? this.options.hasTrigger : false; - const affectedRows = await this.QueryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes); + const affectedRows = await this.queryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes); if (options.returning) { result = [affectedRows.length, affectedRows]; instances = affectedRows; @@ -3194,7 +3194,7 @@ class Model { * @returns {Promise} hash of attributes and their types */ static async describe(schema, options) { - return await this.QueryInterface.describeTable(this.tableName, { schema: schema || this._schema || undefined, ...options }); + return await this.queryInterface.describeTable(this.tableName, { schema: schema || this._schema || undefined, ...options }); } static _getDefaultTimestamp(attr) { @@ -3325,11 +3325,11 @@ class Model { const tableName = this.getTableName(options); let affectedRows; if (isSubtraction) { - affectedRows = await this.QueryInterface.decrement( + affectedRows = await this.queryInterface.decrement( this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options ); } else { - affectedRows = await this.QueryInterface.increment( + affectedRows = await this.queryInterface.increment( this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options ); } @@ -3946,7 +3946,7 @@ class Model { args = [this, this.constructor.getTableName(options), values, where, options]; } - const [result, rowsUpdated] = await this.constructor.QueryInterface[query](...args); + const [result, rowsUpdated] = await this.constructor.queryInterface[query](...args); if (versionAttr) { // Check to see that a row was updated, otherwise it's an optimistic locking error. if (rowsUpdated < 1) { @@ -4169,7 +4169,7 @@ class Model { result = await this.save({ ...options, hooks: false }); } else { - result = await this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, { type: QueryTypes.DELETE, limit: null, ...options }); + result = await this.constructor.queryInterface.delete(this, this.constructor.getTableName(options), where, { type: QueryTypes.DELETE, limit: null, ...options }); } // Run after hook if (options.hooks) { diff --git a/lib/sequelize.js b/lib/sequelize.js index 752b338a5629..27afcd41b9ef 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -10,7 +10,6 @@ const Model = require('./model'); const DataTypes = require('./data-types'); const Deferrable = require('./deferrable'); const ModelManager = require('./model-manager'); -const QueryInterface = require('./query-interface'); const Transaction = require('./transaction'); const QueryTypes = require('./query-types'); const TableHints = require('./table-hints'); @@ -329,16 +328,16 @@ class Sequelize { } this.dialect = new Dialect(this); - this.dialect.QueryGenerator.typeValidation = options.typeValidation; + this.dialect.queryGenerator.typeValidation = options.typeValidation; if (_.isPlainObject(this.options.operatorsAliases)) { deprecations.noStringOperators(); - this.dialect.QueryGenerator.setOperatorsAliases(this.options.operatorsAliases); + this.dialect.queryGenerator.setOperatorsAliases(this.options.operatorsAliases); } else if (typeof this.options.operatorsAliases === 'boolean') { deprecations.noBoolOperatorAliases(); } - this.queryInterface = new QueryInterface(this); + this.queryInterface = this.dialect.queryInterface; /** * Models are stored here under the name given to `sequelize.define` @@ -383,7 +382,6 @@ class Sequelize { * @returns {QueryInterface} An instance (singleton) of QueryInterface. */ getQueryInterface() { - this.queryInterface = this.queryInterface || new QueryInterface(this); return this.queryInterface; } @@ -667,7 +665,7 @@ class Sequelize { * @returns {string} */ escape(value) { - return this.getQueryInterface().escape(value); + return this.dialect.queryGenerator.escape(value); } /** diff --git a/lib/transaction.js b/lib/transaction.js index 63e8b54eb3b6..dc3f060f22b5 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -24,7 +24,7 @@ class Transaction { this._afterCommitHooks = []; // get dialect specific transaction options - const generateTransactionId = this.sequelize.dialect.QueryGenerator.generateTransactionId; + const generateTransactionId = this.sequelize.dialect.queryGenerator.generateTransactionId; this.options = { type: sequelize.options.transactionType, diff --git a/lib/utils.js b/lib/utils.js index 3483481cda71..285f34a5cb69 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -316,17 +316,6 @@ function removeNullValuesFromHash(hash, omitNull, options) { } exports.removeNullValuesFromHash = removeNullValuesFromHash; -function stack() { - const orig = Error.prepareStackTrace; - Error.prepareStackTrace = (_, stack) => stack; - const err = new Error(); - Error.captureStackTrace(err, stack); - const errStack = err.stack; - Error.prepareStackTrace = orig; - return errStack; -} -exports.stack = stack; - const dialects = new Set(['mariadb', 'mysql', 'postgres', 'sqlite', 'mssql']); function now(dialect) { diff --git a/package-lock.json b/package-lock.json index 1c939d8d11c3..ff5894567f2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -738,9 +738,9 @@ } }, "@octokit/types": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.12.1.tgz", - "integrity": "sha512-LRLR1tjbcCfAmUElvTmMvLEzstpx6Xt/aQVTg2xvd+kHA2Ekp1eWl5t+gU7bcwjXHYEAzh4hH4WH+kS3vh+wRw==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.12.2.tgz", + "integrity": "sha512-1GHLI/Jll3j6F0GbYyZPFTcHZMGjAiRfkTEoRUyaVVk2IWbDdwEiClAJvXzfXCDayuGSNCqAUH8lpjZtqW9GDw==", "dev": true, "requires": { "@types/node": ">= 8" @@ -1281,15 +1281,6 @@ "debug": "4" } }, - "agentkeepalive": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", - "dev": true, - "requires": { - "humanize-ms": "^1.2.1" - } - }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -1320,15 +1311,6 @@ "uri-js": "^4.2.2" } }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "^2.0.0" - } - }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -1355,7 +1337,8 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -1372,12 +1355,6 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, - "ansistyles": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", - "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", - "dev": true - }, "any-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", @@ -1486,16 +1463,11 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -1503,7 +1475,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assertion-error": { "version": "1.1.0", @@ -1725,6 +1698,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -1741,33 +1715,6 @@ "integrity": "sha512-cHUzdT+mMXd1ozht8n5ZwBlNiPO/4zCqqkyp3lF1TMPsRJLXUbQ7cKnfXRkrW475H5SOtSOP0HFeihNbpa53MQ==", "dev": true }, - "bin-links": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.7.tgz", - "integrity": "sha512-/eaLaTu7G7/o7PV04QPy1HRT65zf+1tFkPGv0sPTV0tRwufooYBQO3zrcyGgm+ja+ZtBf2GEuKjDRJ2pPG+yqA==", - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "cmd-shim": "^3.0.0", - "gentle-fs": "^2.3.0", - "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", @@ -1784,12 +1731,6 @@ "safe-buffer": "^5.1.1" } }, - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", - "dev": true - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1802,21 +1743,6 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "dev": true }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "dev": true, - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1854,12 +1780,6 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", "dev": true }, - "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", - "dev": true - }, "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -1872,58 +1792,6 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true - }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", - "dev": true - }, - "byte-size": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", - "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", - "dev": true - }, - "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -1936,12 +1804,6 @@ "write-file-atomic": "^3.0.0" } }, - "call-limit": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz", - "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==", - "dev": true - }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -1977,7 +1839,8 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true }, "camelcase-keys": { "version": "4.2.0", @@ -1990,12 +1853,6 @@ "quick-lru": "^1.0.0" } }, - "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", - "dev": true - }, "cardinal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", @@ -2117,54 +1974,12 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, - "cidr-regex": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz", - "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0" - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "dev": true - }, - "cli-columns": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz", - "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", - "dev": true, - "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -2183,26 +1998,6 @@ "colors": "1.0.3" } }, - "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - }, - "dependencies": { - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "optional": true - } - } - }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -2266,6 +2061,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, "requires": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0", @@ -2320,20 +2116,11 @@ } } }, - "cmd-shim": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz", - "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "color-convert": { "version": "1.9.3", @@ -2353,7 +2140,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "colors": { "version": "1.0.3", @@ -2361,33 +2149,6 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, - "columnify": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", - "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", - "dev": true, - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2443,88 +2204,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "dev": true, - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "dependencies": { - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, - "requires": { - "crypto-random-string": "^1.0.0" - } - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2601,31 +2280,6 @@ "safe-buffer": "~5.1.1" } }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "core-js": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", @@ -2635,7 +2289,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cosmiconfig": { "version": "5.2.1", @@ -2667,15 +2322,6 @@ } } }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, "cross-env": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", @@ -2746,12 +2392,6 @@ "array-find-index": "^1.0.1" } }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, "dargs": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", @@ -2765,6 +2405,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -2795,16 +2436,11 @@ "ms": "^2.1.1" } }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decamelize-keys": { "version": "1.1.0", @@ -2824,12 +2460,6 @@ } } }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -2874,23 +2504,6 @@ } } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - } - } - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2919,7 +2532,8 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "delegates": { "version": "1.0.0", @@ -2960,22 +2574,6 @@ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -3052,12 +2650,6 @@ "is-obj": "^1.0.0" } }, - "dotenv": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", - "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", - "dev": true - }, "dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", @@ -3197,12 +2789,6 @@ "readable-stream": "^2.0.2" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -3219,6 +2805,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -3233,12 +2820,6 @@ "safe-buffer": "^5.0.1" } }, - "editor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", - "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", - "dev": true - }, "elegant-spinner": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", @@ -3260,19 +2841,11 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "dev": true, - "requires": { - "iconv-lite": "~0.4.13" - } - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } @@ -3354,27 +2927,6 @@ } } }, - "env-paths": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", - "dev": true - }, - "err-code": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3420,19 +2972,6 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "^4.0.3" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3559,6 +3098,12 @@ "integrity": "sha1-OGmGnNf4eJH5cmJXh2laKZrs5Fw=", "dev": true }, + "esdoc-ecmascript-proposal-plugin": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esdoc-ecmascript-proposal-plugin/-/esdoc-ecmascript-proposal-plugin-1.0.0.tgz", + "integrity": "sha1-OQ3FZWuoooMOOdujVw15E43y/9k=", + "dev": true + }, "esdoc-external-ecmascript-plugin": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esdoc-external-ecmascript-plugin/-/esdoc-external-ecmascript-plugin-1.0.0.tgz", @@ -4085,6 +3630,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -4099,6 +3645,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -4110,17 +3657,20 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -4128,12 +3678,14 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -4186,7 +3738,8 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -4203,12 +3756,6 @@ "reusify": "^1.0.4" } }, - "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", - "dev": true - }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4247,16 +3794,11 @@ "pkg-dir": "^4.1.0" } }, - "find-npm-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", - "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==", - "dev": true - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, "requires": { "locate-path": "^2.0.0" } @@ -4431,40 +3973,6 @@ } } }, - "fs-vacuum": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz", - "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4481,7 +3989,8 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -4551,41 +4060,17 @@ "is-property": "^1.0.2" } }, - "genfun": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", - "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", "dev": true }, - "gentle-fs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.0.tgz", - "integrity": "sha512-3k2CgAmPxuz7S6nKK+AqFE2AdM1QuwqKLPKzIET3VRwK++3q96MsNFobScDjlCrq97ZJ8y5R725MOlm6ffUCjg==", - "dev": true, - "requires": { - "aproba": "^1.1.2", - "chownr": "^1.1.2", - "cmd-shim": "^3.0.3", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "infer-owner": "^1.0.4", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" - } - }, "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==" + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true }, "get-func-name": { "version": "2.0.0", @@ -4609,6 +4094,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -4617,6 +4103,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -4768,33 +4255,6 @@ } } }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } - } - }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -4854,6 +4314,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -4878,12 +4339,14 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true }, "has-unicode": { "version": "2.0.1", @@ -4960,12 +4423,6 @@ } } }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true - }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", @@ -5004,15 +4461,6 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, "husky": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", @@ -5209,12 +4657,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -5257,12 +4699,6 @@ "resolve-from": "^5.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5275,12 +4711,6 @@ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", "dev": true }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "inflection": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", @@ -5299,36 +4729,14 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "init-package-json": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", - "dev": true, - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true }, "inquirer": { "version": "7.1.0", @@ -5465,18 +4873,7 @@ "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "is-absolute": { @@ -5516,32 +4913,6 @@ "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - }, - "dependencies": { - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - } - } - }, - "is-cidr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.0.0.tgz", - "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", - "dev": true, - "requires": { - "cidr-regex": "^2.0.10" - } - }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", @@ -5569,7 +4940,8 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "is-glob": { "version": "4.0.1", @@ -5580,28 +4952,12 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "dev": true, - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5611,7 +4967,8 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true }, "is-observable": { "version": "1.1.0", @@ -5622,15 +4979,6 @@ "symbol-observable": "^1.1.0" } }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -5658,12 +5006,6 @@ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "dev": true }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", @@ -5688,16 +5030,11 @@ "is-unc-path": "^1.0.0" } }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-symbol": { "version": "1.0.3", @@ -5753,12 +5090,14 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "4.0.0", @@ -5937,7 +5276,8 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "jsdoctypeparser": { "version": "6.1.0", @@ -6114,21 +5454,6 @@ "graceful-fs": "^4.1.9" } }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "^4.0.0" - } - }, - "lazy-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz", - "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", - "dev": true - }, "lazystream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", @@ -6142,6 +5467,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, "requires": { "invert-kv": "^2.0.0" } @@ -6188,353 +5514,40 @@ "type-check": "~0.3.2" } }, - "libcipm": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.7.tgz", - "integrity": "sha512-fTq33otU3PNXxxCTCYCYe7V96o59v/o7bvtspmbORXpgFk+wcWrGf5x6tBgui5gCed/45/wtPomBsZBYm5KbIw==", + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", "dev": true, "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "ini": "^1.3.5", - "lock-verify": "^2.0.2", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^9.1.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "uc.micro": "^1.0.1" } }, - "libnpm": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz", - "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", - "dev": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.3", - "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.2", - "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmpublish": "^1.1.2", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "lock-verify": "^2.0.2", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", - "npmlog": "^4.1.2", - "pacote": "^9.5.3", - "read-package-json": "^2.0.13", - "stringify-package": "^1.0.0" - } - }, - "libnpmaccess": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz", - "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", + "lint-staged": { + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.7.tgz", + "integrity": "sha512-ZkK8t9Ep/AHuJQKV95izSa+DqotftGnSsNeEmCSqbQ6j4C4H0jDYhEZqVOGD1Q2Oe227igbqjMWycWyYbQtpoA==", "dev": true, "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - } - } - }, - "libnpmconfig": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz", - "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "find-up": "^3.0.0", - "ini": "^1.3.5" - }, - "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 - } - } - }, - "libnpmhook": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz", - "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - } - } - }, - "libnpmorg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz", - "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - } - } - }, - "libnpmpublish": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz", - "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "libnpmsearch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz", - "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmteam": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz", - "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - } - } - }, - "libnpx": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.2.tgz", - "integrity": "sha512-ujaYToga1SAX5r7FU5ShMFi88CWpY75meNZtr6RtEyv4l2ZK3+Wgvxq2IqlwWBiDZOqhumdeiocPS1aKrCMe3A==", - "dev": true, - "requires": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yargs": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", - "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - }, - "dependencies": { - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - } - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "lint-staged": { - "version": "10.1.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.7.tgz", - "integrity": "sha512-ZkK8t9Ep/AHuJQKV95izSa+DqotftGnSsNeEmCSqbQ6j4C4H0jDYhEZqVOGD1Q2Oe227igbqjMWycWyYbQtpoA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "commander": "^5.0.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "execa": "^4.0.0", - "listr": "^0.14.3", - "log-symbols": "^3.0.0", - "micromatch": "^4.0.2", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" + "chalk": "^4.0.0", + "commander": "^5.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "dedent": "^0.7.0", + "execa": "^4.0.0", + "listr": "^0.14.3", + "log-symbols": "^3.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", + "stringify-object": "^3.3.0" }, "dependencies": { "ansi-styles": { @@ -6854,126 +5867,39 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, - "lock-verify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz", - "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", - "dev": true, - "requires": { - "npm-package-arg": "^6.1.0", - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "lockfile": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", - "dev": true, - "requires": { - "signal-exit": "^3.0.2" - } - }, "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash._baseindexof": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", - "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._baseuniq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", - "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", - "dev": true, - "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", + "dev": true }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", "dev": true }, - "lodash._cacheindexof": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", - "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", - "dev": true - }, - "lodash._createcache": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", - "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0" - } - }, - "lodash._createset": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", - "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", - "dev": true - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", - "dev": true - }, - "lodash.capitalize": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, "lodash.defaults": { @@ -7072,12 +5998,6 @@ "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", "dev": true }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, "lodash.some": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", @@ -7109,30 +6029,12 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", "dev": true }, - "lodash.without": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz", - "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", - "dev": true - }, "log-symbols": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", @@ -7230,12 +6132,6 @@ "signal-exit": "^3.0.0" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7268,75 +6164,11 @@ } } }, - "make-fetch-happen": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", - "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", - "dev": true, - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "dev": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, "requires": { "p-defer": "^1.0.0" } @@ -7530,16 +6362,11 @@ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "meant": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz", - "integrity": "sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==", - "dev": true - }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -7586,9 +6413,9 @@ } }, "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", + "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", "dev": true }, "mime-db": { @@ -7609,7 +6436,8 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, "minimatch": { "version": "3.0.4", @@ -7623,7 +6451,8 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "minimist-options": { "version": "3.0.2", @@ -7654,36 +6483,6 @@ "minipass": "^2.9.0" } }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -7945,31 +6744,6 @@ "moment": ">= 2.9.0" } }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8090,7 +6864,8 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "nise": { "version": "4.0.3", @@ -8138,62 +6913,6 @@ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "dev": true }, - "node-fetch-npm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" - } - }, - "node-gyp": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz", - "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.1.2", - "request": "^2.88.0", - "rimraf": "^2.6.3", - "semver": "^5.7.1", - "tar": "^4.4.12", - "which": "^1.3.1" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -8423,255 +7142,2607 @@ "write-file-atomic": "^2.4.3" }, "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, "agent-base": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "bundled": true, + "dev": true, "requires": { "es6-promisify": "^5.0.0" } }, + "agentkeepalive": { + "version": "3.5.2", + "bundled": true, + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, "ajv": { "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "bundled": true, + "dev": true, "requires": { + "co": "^4.6.0", "fast-deep-equal": "^1.0.0", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.3.0" } }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "bundled": true, "dev": true }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, "requires": { - "color-name": "^1.1.1" + "color-convert": "^1.9.0" } }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" - } + "ansistyles": { + "version": "0.1.3", + "bundled": true, + "dev": true }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "aproba": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" }, "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.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" } }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asap": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true, + "dev": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "bluebird": { + "version": "3.5.5", + "bundled": true, + "dev": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "byline": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "byte-size": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "cacache": { + "version": "12.0.3", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "2.0.10", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "cmd-shim": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "colors": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "dev": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true } } }, "crypto-random-string": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + "bundled": true, + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "err-code": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "bundled": true, + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "bundled": true, + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.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" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true, + "dev": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.6.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "gentle-fs": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "dev": true, + "requires": { + "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-dirs": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.3", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "bundled": true, + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "ip": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "bundled": true, + "dev": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true, + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "libcipm": { + "version": "4.0.7", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true, + "dev": true + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.2", + "bundled": true, + "dev": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true, + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true, + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "5.0.2", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "bundled": true, + "dev": true + } + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.4", + "bundled": true, + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true, + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-audit-report": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "npm-install-checks": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "3.1.4", + "bundled": true, + "dev": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "6.1.1", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "4.0.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true, + "dev": true }, - "debug": { + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-keys": { + "version": "1.0.12", + "bundled": true, + "dev": true + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "bundled": true, + "dev": true, "requires": { - "ms": "2.0.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" }, "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "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" + } } } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true, "dev": true }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, "requires": { - "is-obj": "^1.0.0" + "p-try": "^1.0.0" } }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "9.5.12", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" }, "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } } } }, - "fast-deep-equal": { + "parallel-transform": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "bundled": true, + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } }, - "http-proxy-agent": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "performance-now": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "bundled": true, + "dev": true + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "dev": true, "requires": { - "agent-base": "4", - "debug": "3.1.0" + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + } } }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "promzard": { + "version": "0.3.0", + "bundled": true, + "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true, + "dev": true + }, + "protoduck": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "psl": { + "version": "1.1.29", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } } }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "qs": { + "version": "6.5.2", + "bundled": true, + "dev": true + }, + "query-string": { + "version": "6.8.2", + "bundled": true, + "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" } }, - "iferr": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz", - "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==", + "qw": { + "version": "1.0.1", + "bundled": true, "dev": true }, - "is-callable": { - "version": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" - }, - "is-date-object": { - "version": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } } }, - "is-regex": { - "version": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "read": { + "version": "1.0.7", + "bundled": true, + "dev": true, "requires": { - "has": "^1.0.1" + "mute-stream": "~0.0.4" } }, - "is-symbol": { - "version": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "read-cmd-shim": { + "version": "1.0.5", + "bundled": true, + "dev": true, "requires": { - "has-symbols": "^1.0.0" + "graceful-fs": "^4.1.2" } }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "read-installed": { + "version": "4.0.3", + "bundled": true, + "dev": true, "requires": { - "pify": "^3.0.0" + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" } }, - "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" - }, - "ms": { + "read-package-json": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "object-keys": { - "version": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "read-package-tree": { + "version": "5.3.1", + "bundled": true, + "dev": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } }, "readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "bundled": true, "dev": true, "requires": { "inherits": "^2.0.3", @@ -8679,59 +9750,279 @@ "util-deprecate": "^1.0.1" } }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "registry-auth-token": { "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "bundled": true, + "dev": true, "requires": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" } }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "bundled": true, + "dev": true + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true }, "rimraf": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "bundled": true, "dev": true, "requires": { "glob": "^7.1.3" } }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, "semver": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "bundled": true, + "dev": true }, "semver-diff": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "bundled": true, + "dev": true, "requires": { "semver": "^5.0.3" } }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "sha": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, "shebang-command": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "bundled": true, + "dev": true, "requires": { "shebang-regex": "^1.0.0" } }, "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "smart-buffer": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.3.3", + "bundled": true, + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "dev": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "split-on-first": { + "version": "1.1.0", + "bundled": true, + "dev": true }, "sshpk": { "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "bundled": true, + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -8744,31 +10035,188 @@ "tweetnacl": "~0.14.0" } }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + "ssri": { + "version": "6.0.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "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": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true, + "dev": true }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, + "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, "supports-color": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "bundled": true, + "dev": true, "requires": { "has-flag": "^3.0.0" } }, + "tar": { + "version": "4.4.13", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "through": { + "version": "2.3.8", + "bundled": true, + "dev": true + }, "through2": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "bundled": true, + "dev": true, "requires": { "readable-stream": "^2.1.5", "xtend": "~4.0.1" @@ -8776,8 +10224,8 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8790,51 +10238,241 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.1.0" } } } }, + "timed-out": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, "tough-cookie": { "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "bundled": true, + "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" } }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "umask": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, "unique-string": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "bundled": true, + "dev": true, "requires": { "crypto-random-string": "^1.0.0" } }, + "unpipe": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "util-promisify": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.3", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "which": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "bundled": true, + "dev": true, "requires": { "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, "wide-align": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "bundled": true, + "dev": true, "requires": { "string-width": "^1.0.2" }, "dependencies": { "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8843,10 +10481,14 @@ } } }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, "write-file-atomic": { "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -8854,15 +10496,30 @@ "signal-exit": "^3.0.2" } }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, "yallist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "bundled": true, + "dev": true }, "yargs": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", - "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", + "bundled": true, + "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.1.1", @@ -8880,31 +10537,21 @@ "dependencies": { "y18n": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "bundled": true, + "dev": true } } }, "yargs-parser": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "bundled": true, + "dev": true, "requires": { "camelcase": "^4.1.0" } } } }, - "npm-audit-report": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.2.tgz", - "integrity": "sha512-abeqS5ONyXNaZJPGAf6TOUMNdSe1Y6cpc9MLBRn+CuUoYbfdca6AxOyXVlfIv9OgKX+cacblbG5w7A6ccwoTPw==", - "dev": true, - "requires": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" - } - }, "npm-bundled": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", @@ -8914,94 +10561,12 @@ "npm-normalize-package-bin": "^1.0.1" } }, - "npm-cache-filename": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", - "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", - "dev": true - }, - "npm-install-checks": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz", - "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", - "dev": true, - "requires": { - "semver": "^2.3.0 || 3.x || 4 || 5" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "npm-lifecycle": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.4.tgz", - "integrity": "sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A==", - "dev": true, - "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.15", - "node-gyp": "^5.0.2", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "npm-logical-tree": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz", - "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==", - "dev": true - }, "npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, - "npm-package-arg": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", - "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", - "dev": true, - "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "npm-packlist": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", @@ -9009,67 +10574,15 @@ "dev": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-pick-manifest": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", - "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "npm-profile": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz", - "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", - "dev": true, - "requires": { - "aproba": "^1.1.2 || 2", - "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^4.0.0" - } - }, - "npm-registry-fetch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz", - "integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==", - "dev": true, - "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - } + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^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", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { "path-key": "^2.0.0" }, @@ -9077,16 +10590,11 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true } } }, - "npm-user-validate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.0.tgz", - "integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=", - "dev": true - }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -9111,7 +10619,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nwmatcher": { "version": "1.4.4", @@ -9270,6 +10779,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -9289,12 +10799,6 @@ "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", "dev": true }, - "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", - "dev": true - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -9328,6 +10832,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, "requires": { "execa": "^1.0.0", "lcid": "^2.0.0", @@ -9363,7 +10868,8 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true }, "p-each-series": { "version": "2.1.0", @@ -9391,17 +10897,20 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "requires": { "p-try": "^1.0.0" } @@ -9410,6 +10919,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -9471,7 +10981,8 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true }, "package-hash": { "version": "4.0.0", @@ -9485,117 +10996,12 @@ "release-zalgo": "^1.0.0" } }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - }, - "dependencies": { - "registry-auth-token": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", - "dev": true, - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", "dev": true }, - "pacote": { - "version": "9.5.12", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", - "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-normalize-package-bin": "^1.0.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^3.0.0", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.10", - "unique-filename": "^1.1.1", - "which": "^1.3.1" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9639,7 +11045,8 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -9647,12 +11054,6 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -9791,7 +11192,8 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true }, "pkg-conf": { "version": "2.1.0", @@ -9905,16 +11307,11 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "process-on-spawn": { "version": "1.0.0", @@ -9931,74 +11328,23 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "promise-retry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", - "dev": true, - "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true - } - } - }, - "promzard": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", - "dev": true, - "requires": { - "read": "1" - } - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true - }, - "protoduck": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", - "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", - "dev": true, - "requires": { - "genfun": "^5.0.0" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -10039,41 +11385,18 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, - "qrcode-terminal": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", - "dev": true - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, - "query-string": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", - "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, "quick-lru": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, - "qw": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz", - "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=", - "dev": true - }, "ramda": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", @@ -10084,6 +11407,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10094,75 +11418,11 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - } - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-cmd-shim": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz", - "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "read-installed": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", - "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "graceful-fs": "^4.1.2", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true } } }, - "read-package-json": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", - "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", - "dev": true, - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "read-package-tree": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", - "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", - "dev": true, - "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -10199,18 +11459,6 @@ "util-deprecate": "~1.0.1" } }, - "readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, "readdirp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", @@ -10266,15 +11514,6 @@ "rc": "^1.2.8" } }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "^1.0.1" - } - }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -10369,12 +11608,14 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true }, "resolve": { "version": "1.17.0", @@ -10463,15 +11704,6 @@ "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", "dev": true }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - } - }, "rxjs": { "version": "6.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", @@ -10484,12 +11716,14 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "sax": { "version": "1.2.4", @@ -10606,12 +11840,6 @@ "p-locate": "^4.1.0" } }, - "marked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", - "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", - "dev": true - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -10750,16 +11978,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "sha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz", - "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "shebang-command": { "version": "2.0.0", @@ -10785,7 +12005,8 @@ "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true }, "signale": { "version": "1.4.0", @@ -10836,133 +12057,38 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "sinon-chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", - "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", - "dev": true - }, - "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", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", - "dev": true - }, - "smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", - "dev": true - }, - "socks": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", - "dev": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", - "dev": true, - "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "dependencies": { - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "has-flag": "^4.0.0" } } } }, - "sorted-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", - "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", + "sinon-chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", + "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", "dev": true }, - "sorted-union-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz", - "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", + "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", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" - }, - "dependencies": { - "from2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz", - "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" } }, "source-map": { @@ -11032,12 +12158,6 @@ "through": "2" } }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true - }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -11099,15 +12219,6 @@ "tweetnacl": "~0.14.0" } }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, "stack-chain": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", @@ -11124,38 +12235,12 @@ "readable-stream": "^2.0.2" } }, - "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-iterate": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz", - "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" - } - }, "stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true - }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -11166,6 +12251,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -11233,16 +12319,11 @@ "is-regexp": "^1.0.0" } }, - "stringify-package": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", - "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -11256,7 +12337,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-final-newline": { "version": "2.0.0", @@ -11273,7 +12355,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "supports-color": { "version": "5.5.0", @@ -11459,89 +12542,6 @@ } } }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true, - "requires": { - "execa": "^0.7.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -11602,18 +12602,6 @@ } } }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, - "tiny-relative-date": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", - "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", - "dev": true - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11771,7 +12759,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "type-check": { "version": "0.3.2", @@ -11794,12 +12783,6 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -11831,18 +12814,6 @@ "commander": "~2.20.3" } }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", - "dev": true - }, - "umask": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", - "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", - "dev": true - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -11855,24 +12826,6 @@ "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", "dev": true }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, "unique-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", @@ -11907,53 +12860,6 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "dev": true, - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true, - "requires": { - "semver": "^5.0.3" - } - } - } - }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -11969,35 +12875,12 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util-extend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", - "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "util-promisify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", - "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -12019,15 +12902,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "requires": { - "builtins": "^1.0.3" - } - }, "validator": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", @@ -12116,15 +12990,6 @@ "vinyl": "^2.0.0" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, "webidl-conversions": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", @@ -12154,7 +13019,8 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "which-pm-runs": { "version": "1.0.0", @@ -12171,15 +13037,6 @@ "string-width": "^1.0.2 || 2" } }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", - "dev": true, - "requires": { - "string-width": "^2.1.1" - } - }, "windows-release": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz", @@ -12209,19 +13066,11 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -12230,12 +13079,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -12244,6 +13095,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -12254,6 +13106,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12263,7 +13116,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "1.0.3", @@ -12286,12 +13140,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true - }, "xml-name-validator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", @@ -12314,7 +13162,8 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "4.0.0", diff --git a/package.json b/package.json index a4ac43c89b33..4fc8c3abaa2d 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "dtslint": "^3.4.2", "env-cmd": "^10.1.0", "esdoc": "^1.1.0", + "esdoc-ecmascript-proposal-plugin": "^1.0.0", "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", "eslint": "^6.8.0", diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index d305ecd49d9d..5e75832dece8 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -715,7 +715,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { // the `UPDATE` query generated by `save()` uses `id` in the // `WHERE` clause - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); return expect( user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { @@ -748,7 +748,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { // the `UPDATE` query generated by `save()` uses `id` in the // `WHERE` clause - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) .then(() => { return Task.findAll().then(tasks => { diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index bb0f22eb97f0..9ef6229a74c6 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -1254,7 +1254,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { // the `UPDATE` query generated by `save()` uses `id` in the // `WHERE` clause - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); }).then(() => { return Task.findAll(); @@ -1311,7 +1311,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { // the `UPDATE` query generated by `save()` uses `id` in the // `WHERE` clause - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) .catch(err => { if (!(err instanceof Sequelize.ForeignKeyConstraintError)) throw err; diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index a119e1c9c9f2..f779c5d7f965 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -563,7 +563,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { // the `UPDATE` query generated by `save()` uses `id` in the // `WHERE` clause - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }).then(() => { return Task.findAll().then(tasks => { expect(tasks).to.have.length(1); @@ -619,7 +619,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { // the `UPDATE` query generated by `save()` uses `id` in the // `WHERE` clause - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); return expect( user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { diff --git a/test/integration/dialects/mariadb/dao-factory.test.js b/test/integration/dialects/mariadb/dao-factory.test.js index ac07715c6adb..f9c3100c1f43 100644 --- a/test/integration/dialects/mariadb/dao-factory.test.js +++ b/test/integration/dialects/mariadb/dao-factory.test.js @@ -16,7 +16,7 @@ describe('[MariaDB Specific] DAOFactory', () => { }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' @@ -28,7 +28,7 @@ describe('[MariaDB Specific] DAOFactory', () => { username: { type: DataTypes.STRING, defaultValue: 'foo' } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) DEFAULT \'foo\'', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' @@ -40,7 +40,7 @@ describe('[MariaDB Specific] DAOFactory', () => { username: { type: DataTypes.STRING, allowNull: false } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' @@ -52,7 +52,7 @@ describe('[MariaDB Specific] DAOFactory', () => { username: { type: DataTypes.STRING, primaryKey: true } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal( { username: 'VARCHAR(255) PRIMARY KEY' }); }); @@ -63,14 +63,14 @@ describe('[MariaDB Specific] DAOFactory', () => { { timestamps: true }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User1.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User2.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', @@ -82,7 +82,7 @@ describe('[MariaDB Specific] DAOFactory', () => { const User = this.sequelize.define(`User${config.rand()}`, {}, { paranoid: true }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', @@ -95,7 +95,7 @@ describe('[MariaDB Specific] DAOFactory', () => { const User = this.sequelize.define(`User${config.rand()}`, {}, { paranoid: true, underscored: true }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', @@ -125,7 +125,7 @@ describe('[MariaDB Specific] DAOFactory', () => { bar: DataTypes.STRING }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.primaryKeys)).to.deep.equal( { 'foo': 'VARCHAR(255) PRIMARY KEY' }); }); diff --git a/test/integration/dialects/mysql/dao-factory.test.js b/test/integration/dialects/mysql/dao-factory.test.js index 4f0c58f98b25..c5656d47cc04 100644 --- a/test/integration/dialects/mysql/dao-factory.test.js +++ b/test/integration/dialects/mysql/dao-factory.test.js @@ -15,46 +15,46 @@ if (dialect === 'mysql') { username: { type: DataTypes.STRING, unique: true } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); }); it('handles extended attributes (default)', function() { const User = this.sequelize.define(`User${config.rand()}`, { username: { type: DataTypes.STRING, defaultValue: 'foo' } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: "VARCHAR(255) DEFAULT 'foo'", id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: "VARCHAR(255) DEFAULT 'foo'", id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); }); it('handles extended attributes (null)', function() { const User = this.sequelize.define(`User${config.rand()}`, { username: { type: DataTypes.STRING, allowNull: false } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); }); it('handles extended attributes (primaryKey)', function() { const User = this.sequelize.define(`User${config.rand()}`, { username: { type: DataTypes.STRING, primaryKey: true } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) PRIMARY KEY' }); }); it('adds timestamps', function() { const User1 = this.sequelize.define(`User${config.rand()}`, {}); const User2 = this.sequelize.define(`User${config.rand()}`, {}, { timestamps: true }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User1.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User2.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User1.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User2.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); }); it('adds deletedAt if paranoid', function() { const User = this.sequelize.define(`User${config.rand()}`, {}, { paranoid: true }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); }); it('underscores timestamps if underscored', function() { const User = this.sequelize.define(`User${config.rand()}`, {}, { paranoid: true, underscored: true }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', updated_at: 'DATETIME NOT NULL', created_at: 'DATETIME NOT NULL' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', updated_at: 'DATETIME NOT NULL', created_at: 'DATETIME NOT NULL' }); }); it('omits text fields with defaultValues', function() { @@ -74,7 +74,7 @@ if (dialect === 'mysql') { foo: { type: DataTypes.STRING, primaryKey: true }, bar: DataTypes.STRING }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.primaryKeys)).to.deep.equal({ 'foo': 'VARCHAR(255) PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.primaryKeys)).to.deep.equal({ 'foo': 'VARCHAR(255) PRIMARY KEY' }); }); }); }); diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index e8254961f666..7032f7d0d043 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -1047,7 +1047,7 @@ if (dialect.match(/^postgres/)) { describe('[POSTGRES] Unquoted identifiers', () => { it('can insert and select', function() { this.sequelize.options.quoteIdentifiers = false; - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; this.User = this.sequelize.define('Userxs', { username: DataTypes.STRING, @@ -1082,7 +1082,7 @@ if (dialect.match(/^postgres/)) { }) .then(count => { this.sequelize.options.quoteIndentifiers = true; - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; this.sequelize.options.logging = false; expect(count).to.equal(1); }); @@ -1093,7 +1093,7 @@ if (dialect.match(/^postgres/)) { it('can select nested include', function() { this.sequelize.options.quoteIdentifiers = false; - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; this.Professor = this.sequelize.define('Professor', { fullName: DataTypes.STRING }, { @@ -1225,7 +1225,7 @@ if (dialect.match(/^postgres/)) { expect(professors[0].Classes[0].Students.length).to.eql(3); }) .finally(() => { - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; }); }); }); diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 6be7538e37b0..c980b1c71eca 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -1361,7 +1361,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(users.length).to.equal(1); expect(users[0].username).to.equal('Bob'); - const queryGenerator = this.sequelize.queryInterface.QueryGenerator; + const queryGenerator = this.sequelize.queryInterface.queryGenerator; const qi = queryGenerator.quoteIdentifier.bind(queryGenerator); const query = `SELECT * FROM ${qi('ParanoidUsers')} WHERE ${qi('deletedAt')} IS NOT NULL ORDER BY ${qi('id')}`; [users] = await this.sequelize.query(query); @@ -2382,7 +2382,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should not fail with an include', function() { return this.User.findAll({ - where: this.sequelize.literal(`${this.sequelize.queryInterface.QueryGenerator.quoteIdentifiers('Projects.title')} = ${this.sequelize.queryInterface.QueryGenerator.escape('republic')}`), + where: this.sequelize.literal(`${this.sequelize.queryInterface.queryGenerator.quoteIdentifiers('Projects.title')} = ${this.sequelize.queryInterface.queryGenerator.escape('republic')}`), include: [ { model: this.Project } ] @@ -2395,11 +2395,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('should not overwrite a specified deletedAt by setting paranoid: false', function() { let tableName = ''; if (this.User.name) { - tableName = `${this.sequelize.queryInterface.QueryGenerator.quoteIdentifier(this.User.name)}.`; + tableName = `${this.sequelize.queryInterface.queryGenerator.quoteIdentifier(this.User.name)}.`; } return this.User.findAll({ paranoid: false, - where: this.sequelize.literal(`${tableName + this.sequelize.queryInterface.QueryGenerator.quoteIdentifier('deletedAt')} IS NOT NULL `), + where: this.sequelize.literal(`${tableName + this.sequelize.queryInterface.queryGenerator.quoteIdentifier('deletedAt')} IS NOT NULL `), include: [ { model: this.Project } ] diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index 60e1ca8c1da3..c709f0af44c4 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -79,7 +79,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { await this.sequelize.query('CREATE DATABASE my_test_db'); await this.sequelize.query('CREATE TABLE my_test_db.my_test_table2 (id INT)'); let tableNames = await this.sequelize.query( - this.queryInterface.QueryGenerator.showTablesQuery(), + this.queryInterface.queryGenerator.showTablesQuery(), { raw: true, type: this.sequelize.QueryTypes.SHOWTABLES @@ -124,7 +124,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { expect( await showAllTablesIgnoringSpecialMSSQLTable() ).to.be.empty; - + await this.queryInterface.createTable('table', { name: DataTypes.STRING }); expect( @@ -427,7 +427,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { it('should get a list of foreign keys for the table', async function() { const foreignKeys = await this.sequelize.query( - this.queryInterface.QueryGenerator.getForeignKeysQuery( + this.queryInterface.queryGenerator.getForeignKeysQuery( 'hosts', this.sequelize.config.database ), @@ -450,7 +450,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { if (dialect === 'mysql') { const [foreignKeysViaDirectMySQLQuery] = await this.sequelize.query( - this.queryInterface.QueryGenerator.getForeignKeyQuery('hosts', 'admin') + this.queryInterface.queryGenerator.getForeignKeyQuery('hosts', 'admin') ); expect(foreignKeysViaDirectMySQLQuery[0]).to.deep.equal(foreignKeys[0]); } @@ -486,7 +486,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { describe('unique', () => { it('should add, read & remove unique constraint', async function() { - await this.queryInterface.addConstraint('users', ['email'], { type: 'unique' }); + await this.queryInterface.addConstraint('users', { type: 'unique', fields: ['email'] }); let constraints = await this.queryInterface.showConstraint('users'); constraints = constraints.map(constraint => constraint.constraintName); expect(constraints).to.include('users_email_uk'); @@ -497,8 +497,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { }); it('should add a constraint after another', async function() { - await this.queryInterface.addConstraint('users', ['username'], { type: 'unique' }); - await this.queryInterface.addConstraint('users', ['email'], { type: 'unique' }); + await this.queryInterface.addConstraint('users', { type: 'unique', fields: ['username'] }); + await this.queryInterface.addConstraint('users', { type: 'unique', fields: ['email'] }); let constraints = await this.queryInterface.showConstraint('users'); constraints = constraints.map(constraint => constraint.constraintName); expect(constraints).to.include('users_email_uk'); @@ -519,8 +519,9 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { if (current.dialect.supports.constraints.check) { describe('check', () => { it('should add, read & remove check constraint', async function() { - await this.queryInterface.addConstraint('users', ['roles'], { + await this.queryInterface.addConstraint('users', { type: 'check', + fields: ['roles'], where: { roles: ['user', 'admin', 'guest', 'moderator'] }, @@ -537,7 +538,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { it('addconstraint missing type', async function() { await expect( - this.queryInterface.addConstraint('users', ['roles'], { + this.queryInterface.addConstraint('users', { + fields: ['roles'], where: { roles: ['user', 'admin', 'guest', 'moderator'] }, name: 'check_user_roles' }) @@ -549,7 +551,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { if (current.dialect.supports.constraints.default) { describe('default', () => { it('should add, read & remove default constraint', async function() { - await this.queryInterface.addConstraint('users', ['roles'], { + await this.queryInterface.addConstraint('users', { + fields: ['roles'], type: 'default', defaultValue: 'guest' }); @@ -571,7 +574,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { type: DataTypes.STRING, allowNull: false }); - await this.queryInterface.addConstraint('users', ['username'], { + await this.queryInterface.addConstraint('users', { + fields: ['username'], type: 'PRIMARY KEY' }); let constraints = await this.queryInterface.showConstraint('users'); @@ -599,7 +603,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { type: 'PRIMARY KEY', fields: ['username'] }); - await this.queryInterface.addConstraint('posts', ['username'], { + await this.queryInterface.addConstraint('posts', { + fields: ['username'], references: { table: 'users', field: 'username' diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index 78df4161dc34..dcf9d000349d 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -37,14 +37,14 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(sequelize.config.host).to.equal('127.0.0.1'); }); - it('should set operators aliases on dialect QueryGenerator', () => { + it('should set operators aliases on dialect queryGenerator', () => { const operatorsAliases = { fake: true }; const sequelize = Support.createSequelizeInstance({ operatorsAliases }); expect(sequelize).to.have.property('dialect'); - expect(sequelize.dialect).to.have.property('QueryGenerator'); - expect(sequelize.dialect.QueryGenerator).to.have.property('OperatorsAliasMap'); - expect(sequelize.dialect.QueryGenerator.OperatorsAliasMap).to.be.eql(operatorsAliases); + expect(sequelize.dialect).to.have.property('queryGenerator'); + expect(sequelize.dialect.queryGenerator).to.have.property('OperatorsAliasMap'); + expect(sequelize.dialect.queryGenerator.OperatorsAliasMap).to.be.eql(operatorsAliases); }); if (dialect === 'sqlite') { @@ -1335,7 +1335,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); const count = transaction => { - const sql = this.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); + const sql = this.sequelizeWithTransaction.getQueryInterface().queryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); return this.sequelizeWithTransaction.query(sql, { plain: true, transaction }).then(result => { return result.cnt; @@ -1363,7 +1363,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { const aliasesMapping = new Map([['_0', 'cnt']]); const count = transaction => { - const sql = this.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); + const sql = this.sequelizeWithTransaction.getQueryInterface().queryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); return this.sequelizeWithTransaction.query(sql, { plain: true, transaction, aliasesMapping }).then(result => { return parseInt(result.cnt, 10); diff --git a/test/integration/utils.test.js b/test/integration/utils.test.js index 90d181b2e936..2e5a25318afb 100644 --- a/test/integration/utils.test.js +++ b/test/integration/utils.test.js @@ -91,7 +91,7 @@ describe(Support.getTestDialectTeaser('Utils'), () => { if (Support.getTestDialect() === 'postgres') { describe('json', () => { beforeEach(function() { - this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; + this.queryGenerator = this.sequelize.getQueryInterface().queryGenerator; }); it('successfully parses a complex nested condition hash', function() { diff --git a/test/support.js b/test/support.js index 4d307de22dd6..ec4ac250f323 100644 --- a/test/support.js +++ b/test/support.js @@ -45,6 +45,8 @@ if (global.afterEach) { }); } +let lastSqliteInstance; + const Support = { Sequelize, @@ -69,18 +71,23 @@ const Support = { return unhandledRejections = destArray; }, - prepareTransactionTest(sequelize) { + async prepareTransactionTest(sequelize) { const dialect = Support.getTestDialect(); if (dialect === 'sqlite') { const p = path.join(__dirname, 'tmp', 'db.sqlite'); + if (lastSqliteInstance) { + await lastSqliteInstance.close(); + } if (fs.existsSync(p)) { fs.unlinkSync(p); } const options = { ...sequelize.options, storage: p }, _sequelize = new Sequelize(sequelize.config.database, null, null, options); - return _sequelize.sync({ force: true }).then(() => _sequelize); + await _sequelize.sync({ force: true }); + lastSqliteInstance = _sequelize; + return _sequelize; } return Promise.resolve(sequelize); }, @@ -126,27 +133,22 @@ const Support = { return new Sequelize(db, user, pass, options); }, - clearDatabase(sequelize) { - return sequelize - .getQueryInterface() - .dropAllTables() - .then(() => { - sequelize.modelManager.models = []; - sequelize.models = {}; - - return sequelize - .getQueryInterface() - .dropAllEnums(); - }) - .then(() => { - return this.dropTestSchemas(sequelize); - }); + async clearDatabase(sequelize) { + const qi = sequelize.getQueryInterface(); + await qi.dropAllTables(); + sequelize.modelManager.models = []; + sequelize.models = {}; + + if (qi.dropAllEnums) { + await qi.dropAllEnums(); + } + await this.dropTestSchemas(sequelize); }, dropTestSchemas(sequelize) { const queryInterface = sequelize.getQueryInterface(); - if (!queryInterface.QueryGenerator._dialect.supports.schemas) { + if (!queryInterface.queryGenerator._dialect.supports.schemas) { return this.sequelize.drop({}); } diff --git a/test/unit/sql/add-column.test.js b/test/unit/sql/add-column.test.js index b27b928976aa..b4179ed427d4 100644 --- a/test/unit/sql/add-column.test.js +++ b/test/unit/sql/add-column.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; if (['mysql', 'mariadb'].includes(current.dialect.name)) { diff --git a/test/unit/sql/add-constraint.test.js b/test/unit/sql/add-constraint.test.js index 2a0875ac138f..3466062d0828 100644 --- a/test/unit/sql/add-constraint.test.js +++ b/test/unit/sql/add-constraint.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; const Op = Support.Sequelize.Op; const expect = require('chai').expect; const sinon = require('sinon'); diff --git a/test/unit/sql/create-schema.test.js b/test/unit/sql/create-schema.test.js index 7dcbc87473f4..b7e38cb622b6 100644 --- a/test/unit/sql/create-schema.test.js +++ b/test/unit/sql/create-schema.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const expectsql = Support.expectsql; const current = Support.sequelize; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; describe(Support.getTestDialectTeaser('SQL'), () => { if (current.dialect.name === 'postgres') { diff --git a/test/unit/sql/create-table.test.js b/test/unit/sql/create-table.test.js index 11db2b323d03..53fa2623482a 100644 --- a/test/unit/sql/create-table.test.js +++ b/test/unit/sql/create-table.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, _ = require('lodash'); describe(Support.getTestDialectTeaser('SQL'), () => { diff --git a/test/unit/sql/delete.test.js b/test/unit/sql/delete.test.js index 54bcbc1e5ba1..7c671eda050e 100644 --- a/test/unit/sql/delete.test.js +++ b/test/unit/sql/delete.test.js @@ -7,7 +7,7 @@ const Support = require('../support'), expectsql = Support.expectsql, current = Support.sequelize, Sequelize = Support.Sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/enum.test.js b/test/unit/sql/enum.test.js index f26105172e6d..d9edb6fa4587 100644 --- a/test/unit/sql/enum.test.js +++ b/test/unit/sql/enum.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, expect = require('chai').expect; diff --git a/test/unit/sql/generateJoin.test.js b/test/unit/sql/generateJoin.test.js index 2ad09d5e9500..ba29f63a5924 100644 --- a/test/unit/sql/generateJoin.test.js +++ b/test/unit/sql/generateJoin.test.js @@ -7,7 +7,7 @@ const Support = require('../support'), _ = require('lodash'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/get-constraint-snippet.test.js b/test/unit/sql/get-constraint-snippet.test.js index 9e757b41db94..997d45e53ba2 100644 --- a/test/unit/sql/get-constraint-snippet.test.js +++ b/test/unit/sql/get-constraint-snippet.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; const expect = require('chai').expect; const Op = Support.Sequelize.Op; diff --git a/test/unit/sql/index.test.js b/test/unit/sql/index.test.js index 026ef8f34a5d..18f6161c3ad3 100644 --- a/test/unit/sql/index.test.js +++ b/test/unit/sql/index.test.js @@ -3,7 +3,7 @@ const Support = require('../support'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Support.Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/insert.test.js b/test/unit/sql/insert.test.js index 3e9e186f0f84..34b09acdaf27 100644 --- a/test/unit/sql/insert.test.js +++ b/test/unit/sql/insert.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -52,7 +52,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { timestamps: false }); - expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20)) }, User.rawAttributes, {}), + expectsql(timezoneSequelize.dialect.queryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20)) }, User.rawAttributes, {}), { query: { postgres: 'INSERT INTO "users" ("date") VALUES ($1);', @@ -81,7 +81,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { timestamps: false }); - expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89)) }, User.rawAttributes, {}), + expectsql(timezoneSequelize.dialect.queryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89)) }, User.rawAttributes, {}), { query: { postgres: 'INSERT INTO "users" ("date") VALUES ($1);', diff --git a/test/unit/sql/json.test.js b/test/unit/sql/json.test.js index 3b01548c7ef0..ac092134f1b7 100644 --- a/test/unit/sql/json.test.js +++ b/test/unit/sql/json.test.js @@ -6,7 +6,7 @@ const Support = require('../support'), expectsql = Support.expectsql, Sequelize = Support.Sequelize, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation if (current.dialect.supports.JSON) { diff --git a/test/unit/sql/offset-limit.test.js b/test/unit/sql/offset-limit.test.js index be3bef63cdab..1a9a20a416f8 100644 --- a/test/unit/sql/offset-limit.test.js +++ b/test/unit/sql/offset-limit.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), util = require('util'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/order.test.js b/test/unit/sql/order.test.js index 89e353d9f0d6..355c5e599b5c 100644 --- a/test/unit/sql/order.test.js +++ b/test/unit/sql/order.test.js @@ -8,7 +8,7 @@ const DataTypes = require('../../../lib/data-types'); const Model = require('../../../lib/model'); const expectsql = Support.expectsql; const current = Support.sequelize; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/remove-column.test.js b/test/unit/sql/remove-column.test.js index 87be3eb84a04..9b5d186513c0 100644 --- a/test/unit/sql/remove-column.test.js +++ b/test/unit/sql/remove-column.test.js @@ -3,7 +3,7 @@ const Support = require('../support'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/remove-constraint.test.js b/test/unit/sql/remove-constraint.test.js index c16d9f339fed..76920461d7bc 100644 --- a/test/unit/sql/remove-constraint.test.js +++ b/test/unit/sql/remove-constraint.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; if (current.dialect.supports.constraints.dropConstraint) { describe(Support.getTestDialectTeaser('SQL'), () => { diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js index eeb1035541d7..a511bbb66ef3 100644 --- a/test/unit/sql/select.test.js +++ b/test/unit/sql/select.test.js @@ -8,7 +8,7 @@ const Support = require('../support'), expect = chai.expect, expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Support.Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/show-constraints.test.js b/test/unit/sql/show-constraints.test.js index 7b0d4d487552..f6cbc239fddb 100644 --- a/test/unit/sql/show-constraints.test.js +++ b/test/unit/sql/show-constraints.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; describe(Support.getTestDialectTeaser('SQL'), () => { describe('showConstraint', () => { diff --git a/test/unit/sql/update.test.js b/test/unit/sql/update.test.js index d7351536778a..572debd399a6 100644 --- a/test/unit/sql/update.test.js +++ b/test/unit/sql/update.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js index 3d96ed6e4efd..98ee3d3c8f8f 100644 --- a/test/unit/sql/where.test.js +++ b/test/unit/sql/where.test.js @@ -7,7 +7,7 @@ const Support = require('../support'), _ = require('lodash'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Support.Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -54,8 +54,8 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: 'WHERE [User].[id] = 1' }); - it("{ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {schema: 'yolo', tableName: 'User'})) }", () => { - expectsql(sql.whereQuery({ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { schema: 'yolo', tableName: 'User' })) }), { + it("{ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.queryGenerator, {schema: 'yolo', tableName: 'User'})) }", () => { + expectsql(sql.whereQuery({ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.queryGenerator, { schema: 'yolo', tableName: 'User' })) }), { default: 'WHERE [yolo.User].[id] = 1', postgres: 'WHERE "yolo"."User"."id" = 1', mariadb: 'WHERE `yolo`.`User`.`id` = 1', @@ -940,7 +940,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { field: { type: new DataTypes.JSONB() }, - prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { tableName: 'User' })) + prefix: current.literal(sql.quoteTable.call(current.dialect.queryGenerator, { tableName: 'User' })) }, { mariadb: "(json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.nested.prop')) != 'None')", mysql: "(json_unquote(json_extract(`User`.`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.\\\"nested\\\".\\\"prop\\\"')) != 'None')", diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js index 5a6ee2a77ddd..3258834234c7 100644 --- a/test/unit/utils.test.js +++ b/test/unit/utils.test.js @@ -240,33 +240,9 @@ describe(Support.getTestDialectTeaser('Utils'), () => { }); }); - describe('stack', () => { - it('stack trace starts after call to Util.stack()', function this_here_test() { // eslint-disable-line - // We need a named function to be able to capture its trace - function a() { - return b(); - } - - function b() { - return c(); - } - - function c() { - return Utils.stack(); - } - - const stack = a(); - - expect(stack[0].getFunctionName()).to.eql('c'); - expect(stack[1].getFunctionName()).to.eql('b'); - expect(stack[2].getFunctionName()).to.eql('a'); - expect(stack[3].getFunctionName()).to.eql('this_here_test'); - }); - }); - describe('Sequelize.cast', () => { const sql = Support.sequelize; - const generator = sql.queryInterface.QueryGenerator; + const generator = sql.queryInterface.queryGenerator; const run = generator.handleSequelizeMethod.bind(generator); const expectsql = Support.expectsql; diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 407b5074838d..8aee037fd52b 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -198,31 +198,31 @@ export interface IndexesOptions { export interface QueryInterfaceIndexOptions extends IndexesOptions, QueryInterfaceOptions {} -export interface AddUniqueConstraintOptions { - type: 'unique'; +export interface BaseConstraintOptions { name?: string; + fields: string[]; } -export interface AddDefaultConstraintOptions { +export interface AddUniqueConstraintOptions extends BaseConstraintOptions { + type: 'unique'; +} + +export interface AddDefaultConstraintOptions extends BaseConstraintOptions { type: 'default'; - name?: string; defaultValue?: unknown; } -export interface AddCheckConstraintOptions { +export interface AddCheckConstraintOptions extends BaseConstraintOptions { type: 'check'; - name?: string; where?: WhereOptions; } -export interface AddPrimaryKeyConstraintOptions { +export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { type: 'primary key'; - name?: string; } -export interface AddForeignKeyConstraintOptions { +export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { type: 'foreign key'; - name?: string; references?: { table: string; field: string; @@ -251,7 +251,7 @@ export interface FunctionParam { /** * The interface that Sequelize uses to talk to all databases. * -* This interface is available through sequelize.QueryInterface. It should not be commonly used, but it's +* This interface is available through sequelize.queryInterface. It should not be commonly used, but it's * referenced anyway, so it can be used. */ export class QueryInterface { @@ -326,7 +326,7 @@ export class QueryInterface { * * @param options */ - public dropAllTables(options?: QueryInterfaceDropTableOptions): Promise; + public dropAllTables(options?: QueryInterfaceDropAllTablesOptions): Promise; /** * Drops all defined enums @@ -418,7 +418,6 @@ export class QueryInterface { */ public addConstraint( tableName: string, - attributes: string[], options?: AddConstraintOptions & QueryInterfaceOptions ): Promise; diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index 2c02b8db3926..91cdba016bd0 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -160,8 +160,9 @@ queryInterface.removeIndex('Person', 'SuperDuperIndex'); queryInterface.removeIndex('Person', ['firstname', 'lastname']); -queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', ['firstname', 'lastname'], { +queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', { name: 'firstnamexlastname', + fields: ['firstname', 'lastname'], type: 'unique', transaction: trx, })) From bc4fe8a3ef45db4cdf310437d57b031386bd65af Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 3 May 2020 15:47:55 +0530 Subject: [PATCH 133/414] docs(add-constraint): options.fields support --- docs/manual/other-topics/upgrade-to-v6.md | 6 ++++++ lib/dialects/abstract/query-interface.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index b45985a4b516..5b090a82864a 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -54,6 +54,12 @@ This method now tests for equality with [`_.isEqual`](https://lodash.com/docs/4. This method now throws `Sequelize.AggregateError` instead of `Bluebird.AggregateError`. All errors are now exposed as `errors` key. +### QueryInterface + +#### `addConstraint` + +This method now only takes 2 parameters, `tableName` and `options`. Previously the second parameter could be a list of column names to apply the constraint to, this list must now be passed as `options.fields` property. + ## Changelog ### 6.0.0-beta.5 diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 31006ea8a52a..04749766c433 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -683,10 +683,10 @@ class QueryInterface { * @param {string} tableName Table name where you want to add a constraint * @param {object} options An object to define the constraint name, type etc * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) + * @param {Array} options.fields Array of column names to apply the constraint over * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names * @param {string} [options.defaultValue] The value for the default constraint * @param {object} [options.where] Where clause/expression for the CHECK constraint - * @param {Array} [options.attributes] Array of column names to apply the constraint over * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint * @param {string} [options.references.table] Target table name * @param {string} [options.references.field] Target column name From 6d116f87487f8ee8a6991be4f98ef21c040893f4 Mon Sep 17 00:00:00 2001 From: Sushant Date: Mon, 4 May 2020 11:14:41 +0530 Subject: [PATCH 134/414] fix(pool): show deprecation when engine is not supported (#12218) --- ENGINE.md | 4 +- lib/data-types.js | 2 +- lib/dialects/abstract/connection-manager.js | 8 +++- lib/dialects/mariadb/connection-manager.js | 4 +- lib/dialects/mariadb/index.js | 2 +- lib/dialects/mssql/index.js | 2 +- lib/dialects/mysql/index.js | 2 +- lib/dialects/postgres/connection-manager.js | 2 +- lib/dialects/postgres/index.js | 2 +- lib/dialects/sqlite/index.js | 2 +- lib/utils.js | 2 +- ...ssToInvokable.js => class-to-invokable.js} | 0 lib/utils/deprecations.js | 1 + .../abstract/connection-manager.test.js | 47 +++++++++++++++---- 14 files changed, 59 insertions(+), 21 deletions(-) rename lib/utils/{classToInvokable.js => class-to-invokable.js} (100%) diff --git a/ENGINE.md b/ENGINE.md index a41171724084..e13552ec0809 100644 --- a/ENGINE.md +++ b/ENGINE.md @@ -2,9 +2,9 @@ ## v6-beta | Engine | Minimum supported version | -| ------------ | :------------: | +| :------------: | :------------: | | Postgre | [9.5 ](https://www.postgresql.org/docs/9.5/ ) | | MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | | MariaDB | [10.1](https://mariadb.com/kb/en/changes-improvements-in-mariadb-101/) | -| Microsoft SQL | ? | +| Microsoft SQL | `12.0.2000` | | SQLite | [3.0](https://www.sqlite.org/version3.html) diff --git a/lib/data-types.js b/lib/data-types.js index fb8593699d56..655162fe2008 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -9,7 +9,7 @@ const momentTz = require('moment-timezone'); const moment = require('moment'); const { logger } = require('./utils/logger'); const warnings = {}; -const { classToInvokable } = require('./utils/classToInvokable'); +const { classToInvokable } = require('./utils/class-to-invokable'); const { joinSQLFragments } = require('./utils/join-sql-fragments'); class ABSTRACT { diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index c897efd8f964..8f6ee9f44f99 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -5,6 +5,7 @@ const _ = require('lodash'); const semver = require('semver'); const errors = require('../../errors'); const { logger } = require('../../utils/logger'); +const deprecations = require('../../utils/deprecations'); const debug = logger.debugContext('pool'); /** @@ -260,7 +261,12 @@ class ConnectionManager { const parsedVersion = _.get(semver.coerce(version), 'version') || version; this.sequelize.options.databaseVersion = semver.valid(parsedVersion) ? parsedVersion - : this.defaultVersion; + : this.dialect.defaultVersion; + } + + if (semver.lt(this.sequelize.options.databaseVersion, this.dialect.defaultVersion)) { + deprecations.unsupportedEngine(); + debug(`Unsupported database engine version ${this.sequelize.options.databaseVersion}`); } this.versionPromise = null; diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index 4ed1360b8443..2c163be59005 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -1,5 +1,6 @@ 'use strict'; +const semver = require('semver'); const AbstractConnectionManager = require('../abstract/connection-manager'); const SequelizeErrors = require('../../errors'); const { logger } = require('../../utils/logger'); @@ -84,7 +85,8 @@ class ConnectionManager extends AbstractConnectionManager { try { const connection = await this.lib.createConnection(connectionConfig); - this.sequelize.options.databaseVersion = connection.serverVersion(); + this.sequelize.options.databaseVersion = semver.coerce(connection.serverVersion()).version; + debug('connection acquired'); connection.on('error', error => { switch (error.code) { diff --git a/lib/dialects/mariadb/index.js b/lib/dialects/mariadb/index.js index f028a37c8d57..6b6090f9cbe8 100644 --- a/lib/dialects/mariadb/index.js +++ b/lib/dialects/mariadb/index.js @@ -52,7 +52,7 @@ MariadbDialect.prototype.supports = _.merge( REGEXP: true }); -ConnectionManager.prototype.defaultVersion = '5.5.3'; +MariadbDialect.prototype.defaultVersion = '10.1.44'; MariadbDialect.prototype.Query = Query; MariadbDialect.prototype.QueryGenerator = QueryGenerator; MariadbDialect.prototype.DataTypes = DataTypes; diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 3d0173cec4d9..5653a2dda7f5 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -54,7 +54,7 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype. tmpTableTrigger: true }); -ConnectionManager.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express +MssqlDialect.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express MssqlDialect.prototype.Query = Query; MssqlDialect.prototype.name = 'mssql'; MssqlDialect.prototype.TICK_CHAR = '"'; diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js index c999b64178e1..6b3f9cb313a7 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -50,7 +50,7 @@ MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype. REGEXP: true }); -ConnectionManager.prototype.defaultVersion = '5.6.0'; +MysqlDialect.prototype.defaultVersion = '5.7.0'; MysqlDialect.prototype.Query = Query; MysqlDialect.prototype.QueryGenerator = QueryGenerator; MysqlDialect.prototype.DataTypes = DataTypes; diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 62ac3c68e71c..d42f0d54b24f 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -132,7 +132,7 @@ class ConnectionManager extends AbstractConnectionManager { const version = semver.coerce(message.parameterValue).version; this.sequelize.options.databaseVersion = semver.valid(version) ? version - : this.defaultVersion; + : this.dialect.defaultVersion; } break; case 'standard_conforming_strings': diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index 8ff6b583442d..d813c19cae96 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -61,7 +61,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototy searchPath: true }); -ConnectionManager.prototype.defaultVersion = '9.4.0'; +PostgresDialect.prototype.defaultVersion = '9.5.0'; PostgresDialect.prototype.Query = Query; PostgresDialect.prototype.DataTypes = DataTypes; PostgresDialect.prototype.name = 'postgres'; diff --git a/lib/dialects/sqlite/index.js b/lib/dialects/sqlite/index.js index 1090145c645c..1ed11dd20ae5 100644 --- a/lib/dialects/sqlite/index.js +++ b/lib/dialects/sqlite/index.js @@ -48,7 +48,7 @@ SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype JSON: true }); -ConnectionManager.prototype.defaultVersion = '3.8.0'; +SqliteDialect.prototype.defaultVersion = '3.8.0'; SqliteDialect.prototype.Query = Query; SqliteDialect.prototype.DataTypes = DataTypes; SqliteDialect.prototype.name = 'sqlite'; diff --git a/lib/utils.js b/lib/utils.js index 285f34a5cb69..e7fd29532d76 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -10,7 +10,7 @@ const operatorsSet = new Set(Object.values(operators)); let inflection = require('inflection'); -exports.classToInvokable = require('./utils/classToInvokable').classToInvokable; +exports.classToInvokable = require('./utils/class-to-invokable').classToInvokable; exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments; function useInflection(_inflection) { diff --git a/lib/utils/classToInvokable.js b/lib/utils/class-to-invokable.js similarity index 100% rename from lib/utils/classToInvokable.js rename to lib/utils/class-to-invokable.js diff --git a/lib/utils/deprecations.js b/lib/utils/deprecations.js index 1d0b5183d250..10e231720a85 100644 --- a/lib/utils/deprecations.js +++ b/lib/utils/deprecations.js @@ -9,3 +9,4 @@ exports.noTrueLogging = deprecate(noop, 'The logging-option should be either a f exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://sequelize.org/master/manual/querying.html#operators', 'SEQUELIZE0003'); exports.noBoolOperatorAliases = deprecate(noop, 'A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.', 'SEQUELIZE0004'); exports.noDoubleNestedGroup = deprecate(noop, 'Passing a double nested nested array to `group` is unsupported and will be removed in v6.', 'SEQUELIZE0005'); +exports.unsupportedEngine = deprecate(noop, 'This database engine version is not supported, please update your database server. More information https://github.com/sequelize/sequelize/blob/master/ENGINE.md', 'SEQUELIZE0006'); diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index 8386e9e226f9..644a9933a79f 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -2,6 +2,7 @@ const chai = require('chai'), expect = chai.expect, + deprecations = require('../../../../lib/utils/deprecations'), Support = require('../../support'), sinon = require('sinon'), Config = require('../../../config/config'), @@ -15,7 +16,7 @@ const poolEntry = { pool: {} }; -describe('Connection Manager', () => { +describe(Support.getTestDialectTeaser('Connection Manager'), () => { let sandbox; beforeEach(() => { @@ -31,7 +32,7 @@ describe('Connection Manager', () => { replication: null }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); connectionManager.initPools(); expect(connectionManager.pool).to.be.instanceOf(Pool); @@ -47,7 +48,7 @@ describe('Connection Manager', () => { } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); connectionManager.initPools(); expect(connectionManager.pool.read).to.be.instanceOf(Pool); @@ -71,7 +72,7 @@ describe('Connection Manager', () => { } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); const res = { queryType: 'read' @@ -79,7 +80,7 @@ describe('Connection Manager', () => { const connectStub = sandbox.stub(connectionManager, '_connect').resolves(res); sandbox.stub(connectionManager, '_disconnect').resolves(res); - sandbox.stub(sequelize, 'databaseVersion').resolves(res); + sandbox.stub(sequelize, 'databaseVersion').resolves(sequelize.dialect.defaultVersion); connectionManager.initPools(); const queryOptions = { @@ -104,6 +105,34 @@ describe('Connection Manager', () => { }); }); + it('should trigger deprecation for non supported engine version', () => { + const deprecationStub = sandbox.stub(deprecations, 'unsupportedEngine'); + const sequelize = Support.createSequelizeInstance(); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); + + sandbox.stub(sequelize, 'databaseVersion').resolves('0.0.1'); + + const res = { + queryType: 'read' + }; + + sandbox.stub(connectionManager, '_connect').resolves(res); + sandbox.stub(connectionManager, '_disconnect').resolves(res); + connectionManager.initPools(); + + const queryOptions = { + priority: 0, + type: 'SELECT', + useMaster: true + }; + + return connectionManager.getConnection(queryOptions) + .then(() => { + chai.expect(deprecationStub).to.have.been.calledOnce; + }); + }); + + it('should allow forced reads from the write pool', () => { const master = { ...poolEntry }; master.host = 'the-boss'; @@ -115,14 +144,15 @@ describe('Connection Manager', () => { } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); const res = { queryType: 'read' }; + const connectStub = sandbox.stub(connectionManager, '_connect').resolves(res); sandbox.stub(connectionManager, '_disconnect').resolves(res); - sandbox.stub(sequelize, 'databaseVersion').resolves(res); + sandbox.stub(sequelize, 'databaseVersion').resolves(sequelize.dialect.defaultVersion); connectionManager.initPools(); const queryOptions = { @@ -144,7 +174,7 @@ describe('Connection Manager', () => { replication: null }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); connectionManager.initPools(); @@ -156,5 +186,4 @@ describe('Connection Manager', () => { expect(poolClearSpy.calledOnce).to.be.true; }); }); - }); From c14972b92e522909576c9c37953ebc04ee6d7c83 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 4 May 2020 00:47:25 -0500 Subject: [PATCH 135/414] refactor: asyncify test/unit (#12223) --- test/support.js | 24 +- .../unit/associations/belongs-to-many.test.js | 21 +- test/unit/associations/has-many.test.js | 41 ++-- test/unit/connection-manager.test.js | 47 ++-- .../dialects/mssql/connection-manager.test.js | 28 ++- test/unit/dialects/mssql/query.test.js | 12 +- test/unit/dialects/mysql/query.test.js | 9 +- test/unit/hooks.test.js | 209 ++++++++---------- test/unit/instance-validator.test.js | 55 +++-- test/unit/instance/build.test.js | 4 +- test/unit/instance/set.test.js | 48 ++-- test/unit/model/bulkcreate.test.js | 14 +- test/unit/model/count.test.js | 32 ++- test/unit/model/find-and-count-all.test.js | 15 +- test/unit/model/find-create-find.test.js | 34 +-- test/unit/model/find-or-create.test.js | 35 ++- test/unit/model/findall.test.js | 54 ++--- test/unit/model/findone.test.js | 49 ++-- test/unit/model/update.test.js | 14 +- test/unit/model/upsert.test.js | 43 ++-- test/unit/model/validation.test.js | 200 +++++++++-------- test/unit/transaction.test.js | 12 +- 22 files changed, 468 insertions(+), 532 deletions(-) diff --git a/test/support.js b/test/support.js index ec4ac250f323..2fe0f36f402e 100644 --- a/test/support.js +++ b/test/support.js @@ -89,7 +89,7 @@ const Support = { lastSqliteInstance = _sequelize; return _sequelize; } - return Promise.resolve(sequelize); + return sequelize; }, createSequelizeInstance(options) { @@ -145,24 +145,22 @@ const Support = { await this.dropTestSchemas(sequelize); }, - dropTestSchemas(sequelize) { - + async dropTestSchemas(sequelize) { const queryInterface = sequelize.getQueryInterface(); if (!queryInterface.queryGenerator._dialect.supports.schemas) { return this.sequelize.drop({}); } - return sequelize.showAllSchemas().then(schemas => { - const schemasPromise = []; - schemas.forEach(schema => { - const schemaName = schema.name ? schema.name : schema; - if (schemaName !== sequelize.config.database) { - schemasPromise.push(sequelize.dropSchema(schemaName)); - } - }); - return Promise.all(schemasPromise.map(p => p.catch(e => e))) - .then(() => {}, () => {}); + const schemas = await sequelize.showAllSchemas(); + const schemasPromise = []; + schemas.forEach(schema => { + const schemaName = schema.name ? schema.name : schema; + if (schemaName !== sequelize.config.database) { + schemasPromise.push(sequelize.dropSchema(schemaName)); + } }); + + await Promise.all(schemasPromise.map(p => p.catch(e => e))); }, getSupportedDialects() { diff --git a/test/unit/associations/belongs-to-many.test.js b/test/unit/associations/belongs-to-many.test.js index 1b621a6c4268..c3b12ab6d3e3 100644 --- a/test/unit/associations/belongs-to-many.test.js +++ b/test/unit/associations/belongs-to-many.test.js @@ -180,14 +180,13 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { this.destroy.restore(); }); - it('uses one insert into statement', function() { - return user.setTasks([task1, task2]).then(() => { - expect(this.findAll).to.have.been.calledOnce; - expect(this.bulkCreate).to.have.been.calledOnce; - }); + it('uses one insert into statement', async function() { + await user.setTasks([task1, task2]); + expect(this.findAll).to.have.been.calledOnce; + expect(this.bulkCreate).to.have.been.calledOnce; }); - it('uses one delete from statement', function() { + it('uses one delete from statement', async function() { this.findAll .onFirstCall().resolves([]) .onSecondCall().resolves([ @@ -195,12 +194,10 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { { userId: 42, taskId: 16 } ]); - return user.setTasks([task1, task2]).then(() => { - return user.setTasks(null); - }).then(() => { - expect(this.findAll).to.have.been.calledTwice; - expect(this.destroy).to.have.been.calledOnce; - }); + await user.setTasks([task1, task2]); + await user.setTasks(null); + expect(this.findAll).to.have.been.calledTwice; + expect(this.destroy).to.have.been.calledOnce; }); }); diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js index 334ae27b023b..6cadab6f330f 100644 --- a/test/unit/associations/has-many.test.js +++ b/test/unit/associations/has-many.test.js @@ -46,14 +46,13 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { this.update.restore(); }); - it('uses one update statement for addition', function() { - return user.setTasks([task1, task2]).then(() => { - expect(this.findAll).to.have.been.calledOnce; - expect(this.update).to.have.been.calledOnce; - }); + it('uses one update statement for addition', async function() { + await user.setTasks([task1, task2]); + expect(this.findAll).to.have.been.calledOnce; + expect(this.update).to.have.been.calledOnce; }); - it('uses one delete from statement', function() { + it('uses one delete from statement', async function() { this.findAll .onFirstCall().resolves([]) .onSecondCall().resolves([ @@ -61,13 +60,11 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { { userId: 42, taskId: 16 } ]); - return user.setTasks([task1, task2]).then(() => { - this.update.resetHistory(); - return user.setTasks(null); - }).then(() => { - expect(this.findAll).to.have.been.calledTwice; - expect(this.update).to.have.been.calledOnce; - }); + await user.setTasks([task1, task2]); + this.update.resetHistory(); + await user.setTasks(null); + expect(this.findAll).to.have.been.calledTwice; + expect(this.update).to.have.been.calledOnce; }); }); @@ -143,7 +140,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { idC = Math.random().toString(), foreignKey = 'user_id'; - it('should fetch associations for a single instance', () => { + it('should fetch associations for a single instance', async () => { const findAll = stub(Task, 'findAll').resolves([ Task.build({}), Task.build({}) @@ -159,15 +156,16 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { expect(findAll).to.have.been.calledOnce; expect(findAll.firstCall.args[0].where).to.deep.equal(where); - return actual.then(results => { + try { + const results = await actual; expect(results).to.be.an('array'); expect(results.length).to.equal(2); - }).finally(() => { + } finally { findAll.restore(); - }); + } }); - it('should fetch associations for multiple source instances', () => { + it('should fetch associations for multiple source instances', async () => { const findAll = stub(Task, 'findAll').returns( Promise.resolve([ Task.build({ @@ -196,16 +194,17 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { expect(findAll.firstCall.args[0].where[foreignKey]).to.have.property(Op.in); expect(findAll.firstCall.args[0].where[foreignKey][Op.in]).to.deep.equal([idA, idB, idC]); - return actual.then(result => { + try { + const result = await actual; expect(result).to.be.an('object'); expect(Object.keys(result)).to.deep.equal([idA, idB, idC]); expect(result[idA].length).to.equal(3); expect(result[idB].length).to.equal(1); expect(result[idC].length).to.equal(0); - }).finally(() => { + } finally { findAll.restore(); - }); + } }); }); describe('association hooks', () => { diff --git a/test/unit/connection-manager.test.js b/test/unit/connection-manager.test.js index a89d2d2c415a..0b55ae53a6e4 100644 --- a/test/unit/connection-manager.test.js +++ b/test/unit/connection-manager.test.js @@ -20,7 +20,7 @@ describe('connection manager', () => { this.sequelize = Support.createSequelizeInstance(); }); - it('should resolve connection on dialect connection manager', function() { + it('should resolve connection on dialect connection manager', async function() { const connection = {}; this.dialect.connectionManager.connect.resolves(connection); @@ -28,12 +28,11 @@ describe('connection manager', () => { const config = {}; - return expect(connectionManager._connect(config)).to.eventually.equal(connection).then(() => { - expect(this.dialect.connectionManager.connect).to.have.been.calledWith(config); - }); + await expect(connectionManager._connect(config)).to.eventually.equal(connection); + expect(this.dialect.connectionManager.connect).to.have.been.calledWith(config); }); - it('should let beforeConnect hook modify config', function() { + it('should let beforeConnect hook modify config', async function() { const username = Math.random().toString(), password = Math.random().toString(); @@ -45,25 +44,23 @@ describe('connection manager', () => { const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._connect({}).then(() => { - expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ - username, - password - }); + await connectionManager._connect({}); + expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ + username, + password }); }); - it('should call afterConnect', function() { + it('should call afterConnect', async function() { const spy = sinon.spy(); this.sequelize.afterConnect(spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._connect({}).then(() => { - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - expect(spy.firstCall.args[1]).to.eql({}); - }); + await connectionManager._connect({}); + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(this.connection); + expect(spy.firstCall.args[1]).to.eql({}); }); }); @@ -80,28 +77,26 @@ describe('connection manager', () => { this.sequelize = Support.createSequelizeInstance(); }); - it('should call beforeDisconnect', function() { + it('should call beforeDisconnect', async function() { const spy = sinon.spy(); this.sequelize.beforeDisconnect(spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._disconnect(this.connection).then(() => { - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - }); + await connectionManager._disconnect(this.connection); + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(this.connection); }); - it('should call afterDisconnect', function() { + it('should call afterDisconnect', async function() { const spy = sinon.spy(); this.sequelize.afterDisconnect(spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._disconnect(this.connection).then(() => { - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - }); + await connectionManager._disconnect(this.connection); + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(this.connection); }); }); }); diff --git a/test/unit/dialects/mssql/connection-manager.test.js b/test/unit/dialects/mssql/connection-manager.test.js index ccf57ecb6bca..726831e594c9 100644 --- a/test/unit/dialects/mssql/connection-manager.test.js +++ b/test/unit/dialects/mssql/connection-manager.test.js @@ -37,7 +37,7 @@ if (dialect === 'mssql') { this.connectionStub.restore(); }); - it('connectionManager._connect() does not delete `domain` from config.dialectOptions', function() { + it('connectionManager._connect() does not delete `domain` from config.dialectOptions', async function() { this.connectionStub.returns({ STATE: {}, state: '', @@ -53,12 +53,11 @@ if (dialect === 'mssql') { }); expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); - return this.instance.dialect.connectionManager._connect(this.config).then(() => { - expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); - }); + await this.instance.dialect.connectionManager._connect(this.config); + expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); }); - it('connectionManager._connect() should reject if end was called and connect was not', function() { + it('connectionManager._connect() should reject if end was called and connect was not', async function() { this.connectionStub.returns({ STATE: {}, state: '', @@ -73,14 +72,15 @@ if (dialect === 'mssql') { on: () => {} }); - return this.instance.dialect.connectionManager._connect(this.config) - .catch(err => { - expect(err.name).to.equal('SequelizeConnectionError'); - expect(err.parent.message).to.equal('Connection was closed by remote server'); - }); + try { + await this.instance.dialect.connectionManager._connect(this.config); + } catch (err) { + expect(err.name).to.equal('SequelizeConnectionError'); + expect(err.parent.message).to.equal('Connection was closed by remote server'); + } }); - it('connectionManager._connect() should call connect if state is initialized', function() { + it('connectionManager._connect() should call connect if state is initialized', async function() { const connectStub = sinon.stub(); const INITIALIZED = { name: 'INITIALIZED' }; this.connectionStub.returns({ @@ -98,10 +98,8 @@ if (dialect === 'mssql') { on: () => {} }); - return this.instance.dialect.connectionManager._connect(this.config) - .then(() => { - expect(connectStub.called).to.equal(true); - }); + await this.instance.dialect.connectionManager._connect(this.config); + expect(connectStub.called).to.equal(true); }); }); } diff --git a/test/unit/dialects/mssql/query.test.js b/test/unit/dialects/mssql/query.test.js index 90ef90479155..5487f5057458 100644 --- a/test/unit/dialects/mssql/query.test.js +++ b/test/unit/dialects/mssql/query.test.js @@ -31,13 +31,11 @@ if (dialect === 'mssql') { }); describe('beginTransaction', () => { - it('should call beginTransaction with correct arguments', () => { - return query._run(connectionStub, 'BEGIN TRANSACTION') - .then(() => { - expect(connectionStub.beginTransaction.called).to.equal(true); - expect(connectionStub.beginTransaction.args[0][1]).to.equal('transactionName'); - expect(connectionStub.beginTransaction.args[0][2]).to.equal(tediousIsolationLevel.REPEATABLE_READ); - }); + it('should call beginTransaction with correct arguments', async () => { + await query._run(connectionStub, 'BEGIN TRANSACTION'); + expect(connectionStub.beginTransaction.called).to.equal(true); + expect(connectionStub.beginTransaction.args[0][1]).to.equal('transactionName'); + expect(connectionStub.beginTransaction.args[0][2]).to.equal(tediousIsolationLevel.REPEATABLE_READ); }); }); diff --git a/test/unit/dialects/mysql/query.test.js b/test/unit/dialects/mysql/query.test.js index 12d9c247ccc2..2f0465c55541 100644 --- a/test/unit/dialects/mysql/query.test.js +++ b/test/unit/dialects/mysql/query.test.js @@ -19,7 +19,7 @@ describe('[MYSQL/MARIADB Specific] Query', () => { console.log.restore(); }); - it('check iterable', () => { + it('check iterable', async () => { const validWarning = []; const invalidWarning = {}; const warnings = [validWarning, undefined, invalidWarning]; @@ -28,10 +28,9 @@ describe('[MYSQL/MARIADB Specific] Query', () => { const stub = sinon.stub(query, 'run'); stub.onFirstCall().resolves(warnings); - return query.logWarnings('dummy-results').then(results => { - expect('dummy-results').to.equal(results); - expect(true).to.equal(console.log.calledOnce); - }); + const results = await query.logWarnings('dummy-results'); + expect('dummy-results').to.equal(results); + expect(true).to.equal(console.log.calledOnce); }); }); }); diff --git a/test/unit/hooks.test.js b/test/unit/hooks.test.js index a05d3e22d1d4..92ee7f961ee5 100644 --- a/test/unit/hooks.test.js +++ b/test/unit/hooks.test.js @@ -19,15 +19,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('arguments', () => { - it('hooks can modify passed arguments', function() { + it('hooks can modify passed arguments', async function() { this.Model.addHook('beforeCreate', options => { options.answer = 41; }); const options = {}; - return this.Model.runHooks('beforeCreate', options).then(() => { - expect(options.answer).to.equal(41); - }); + await this.Model.runHooks('beforeCreate', options); + expect(options.answer).to.equal(41); }); }); @@ -60,12 +59,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - it('calls beforeSave/afterSave', function() { - return this.Model.create({}).then(() => { - expect(this.afterCreateHook).to.have.been.calledOnce; - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); + it('calls beforeSave/afterSave', async function() { + await this.Model.create({}); + expect(this.afterCreateHook).to.have.been.calledOnce; + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; }); }); @@ -82,11 +80,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Model.addHook('afterSave', this.afterSaveHook); }); - it('calls beforeSave/afterSave', function() { - return this.Model.create({}).then(() => { - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); + it('calls beforeSave/afterSave', async function() { + await this.Model.create({}); + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; }); }); @@ -103,11 +100,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Model.addHook('afterSave', this.afterSaveHook); }); - it('calls beforeSave/afterSave', function() { - return this.Model.create({}).then(() => { - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); + it('calls beforeSave/afterSave', async function() { + await this.Model.create({}); + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; }); }); }); @@ -126,31 +122,31 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { expect(this.hook3).to.have.been.calledOnce; }); - it('using addHook', function() { + it('using addHook', async function() { this.Model.addHook('beforeCreate', this.hook1); this.Model.addHook('beforeCreate', this.hook2); this.Model.addHook('beforeCreate', this.hook3); - return this.Model.runHooks('beforeCreate'); + await this.Model.runHooks('beforeCreate'); }); - it('using function', function() { + it('using function', async function() { this.Model.beforeCreate(this.hook1); this.Model.beforeCreate(this.hook2); this.Model.beforeCreate(this.hook3); - return this.Model.runHooks('beforeCreate'); + await this.Model.runHooks('beforeCreate'); }); - it('using define', function() { - return current.define('M', {}, { + it('using define', async function() { + await current.define('M', {}, { hooks: { beforeCreate: [this.hook1, this.hook2, this.hook3] } }).runHooks('beforeCreate'); }); - it('using a mixture', function() { + it('using a mixture', async function() { const Model = current.define('M', {}, { hooks: { beforeCreate: this.hook1 @@ -159,11 +155,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { Model.beforeCreate(this.hook2); Model.addHook('beforeCreate', this.hook3); - return Model.runHooks('beforeCreate'); + await Model.runHooks('beforeCreate'); }); }); - it('stops execution when a hook throws', function() { + it('stops execution when a hook throws', async function() { this.Model.beforeCreate(() => { this.hook1(); @@ -171,41 +167,38 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); this.Model.beforeCreate(this.hook2); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejected.then(() => { - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).not.to.have.been.called; - }); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejected; + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).not.to.have.been.called; }); - it('stops execution when a hook rejects', function() { - this.Model.beforeCreate(() => { + it('stops execution when a hook rejects', async function() { + this.Model.beforeCreate(async () => { this.hook1(); - return Promise.reject(new Error('No!')); + throw new Error('No!'); }); this.Model.beforeCreate(this.hook2); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejected.then(() => { - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).not.to.have.been.called; - }); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejected; + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).not.to.have.been.called; }); }); describe('global hooks', () => { describe('using addHook', () => { - it('invokes the global hook', function() { + it('invokes the global hook', async function() { const globalHook = sinon.spy(); current.addHook('beforeUpdate', globalHook); - return this.Model.runHooks('beforeUpdate').then(() => { - expect(globalHook).to.have.been.calledOnce; - }); + await this.Model.runHooks('beforeUpdate'); + expect(globalHook).to.have.been.calledOnce; }); - it('invokes the global hook, when the model also has a hook', () => { + it('invokes the global hook, when the model also has a hook', async () => { const globalHookBefore = sinon.spy(), globalHookAfter = sinon.spy(), localHook = sinon.spy(); @@ -220,14 +213,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { current.addHook('beforeUpdate', globalHookAfter); - return Model.runHooks('beforeUpdate').then(() => { - expect(globalHookBefore).to.have.been.calledOnce; - expect(globalHookAfter).to.have.been.calledOnce; - expect(localHook).to.have.been.calledOnce; + await Model.runHooks('beforeUpdate'); + expect(globalHookBefore).to.have.been.calledOnce; + expect(globalHookAfter).to.have.been.calledOnce; + expect(localHook).to.have.been.calledOnce; - expect(localHook).to.have.been.calledBefore(globalHookBefore); - expect(localHook).to.have.been.calledBefore(globalHookAfter); - }); + expect(localHook).to.have.been.calledBefore(globalHookBefore); + expect(localHook).to.have.been.calledBefore(globalHookAfter); }); }); @@ -243,19 +235,18 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - it('runs the global hook when no hook is passed', function() { + it('runs the global hook when no hook is passed', async function() { const Model = this.sequelize.define('M', {}, { hooks: { beforeUpdate: _.noop // Just to make sure we can define other hooks without overwriting the global one } }); - return Model.runHooks('beforeCreate').then(() => { - expect(this.beforeCreate).to.have.been.calledOnce; - }); + await Model.runHooks('beforeCreate'); + expect(this.beforeCreate).to.have.been.calledOnce; }); - it('does not run the global hook when the model specifies its own hook', function() { + it('does not run the global hook when the model specifies its own hook', async function() { const localHook = sinon.spy(), Model = this.sequelize.define('M', {}, { hooks: { @@ -263,40 +254,37 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return Model.runHooks('beforeCreate').then(() => { - expect(this.beforeCreate).not.to.have.been.called; - expect(localHook).to.have.been.calledOnce; - }); + await Model.runHooks('beforeCreate'); + expect(this.beforeCreate).not.to.have.been.called; + expect(localHook).to.have.been.calledOnce; }); }); }); describe('#removeHook', () => { - it('should remove hook', function() { + it('should remove hook', async function() { const hook1 = sinon.spy(), hook2 = sinon.spy(); this.Model.addHook('beforeCreate', 'myHook', hook1); this.Model.beforeCreate('myHook2', hook2); - return this.Model.runHooks('beforeCreate').then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; - hook1.resetHistory(); - hook2.resetHistory(); + hook1.resetHistory(); + hook2.resetHistory(); - this.Model.removeHook('beforeCreate', 'myHook'); - this.Model.removeHook('beforeCreate', 'myHook2'); + this.Model.removeHook('beforeCreate', 'myHook'); + this.Model.removeHook('beforeCreate', 'myHook2'); - return this.Model.runHooks('beforeCreate'); - }).then(() => { - expect(hook1).not.to.have.been.called; - expect(hook2).not.to.have.been.called; - }); + await this.Model.runHooks('beforeCreate'); + expect(hook1).not.to.have.been.called; + expect(hook2).not.to.have.been.called; }); - it('should not remove other hooks', function() { + it('should not remove other hooks', async function() { const hook1 = sinon.spy(), hook2 = sinon.spy(), hook3 = sinon.spy(), @@ -307,31 +295,29 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Model.beforeCreate('myHook2', hook3); this.Model.beforeCreate(hook4); - return this.Model.runHooks('beforeCreate').then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; - expect(hook3).to.have.been.calledOnce; - expect(hook4).to.have.been.calledOnce; - - hook1.resetHistory(); - hook2.resetHistory(); - hook3.resetHistory(); - hook4.resetHistory(); - - this.Model.removeHook('beforeCreate', 'myHook'); - - return this.Model.runHooks('beforeCreate'); - }).then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).not.to.have.been.called; - expect(hook3).to.have.been.calledOnce; - expect(hook4).to.have.been.calledOnce; - }); + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + expect(hook3).to.have.been.calledOnce; + expect(hook4).to.have.been.calledOnce; + + hook1.resetHistory(); + hook2.resetHistory(); + hook3.resetHistory(); + hook4.resetHistory(); + + this.Model.removeHook('beforeCreate', 'myHook'); + + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).not.to.have.been.called; + expect(hook3).to.have.been.calledOnce; + expect(hook4).to.have.been.calledOnce; }); }); describe('#addHook', () => { - it('should add additional hook when previous exists', function() { + it('should add additional hook when previous exists', async function() { const hook1 = sinon.spy(), hook2 = sinon.spy(); @@ -341,44 +327,43 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { Model.addHook('beforeCreate', hook2); - return Model.runHooks('beforeCreate').then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; - }); + await Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; }); }); describe('promises', () => { - it('can return a promise', function() { - this.Model.beforeBulkCreate(() => { - return Promise.resolve(); + it('can return a promise', async function() { + this.Model.beforeBulkCreate(async () => { + // This space intentionally left blank }); - return expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; + await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; }); - it('can return undefined', function() { + it('can return undefined', async function() { this.Model.beforeBulkCreate(() => { // This space intentionally left blank }); - return expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; + await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; }); - it('can return an error by rejecting', function() { - this.Model.beforeCreate(() => { - return Promise.reject(new Error('Forbidden')); + it('can return an error by rejecting', async function() { + this.Model.beforeCreate(async () => { + throw new Error('Forbidden'); }); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); }); - it('can return an error by throwing', function() { + it('can return an error by throwing', async function() { this.Model.beforeCreate(() => { throw new Error('Forbidden'); }); - return expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); }); }); diff --git a/test/unit/instance-validator.test.js b/test/unit/instance-validator.test.js index 135c18742be5..590d3ef7dc10 100644 --- a/test/unit/instance-validator.test.js +++ b/test/unit/instance-validator.test.js @@ -51,21 +51,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(_validateAndRunHooks).to.not.have.been.called; }); - it('fulfills when validation is successful', function() { + it('fulfills when validation is successful', async function() { const instanceValidator = new InstanceValidator(this.User.build()); const result = instanceValidator.validate(); - return expect(result).to.be.fulfilled; + await expect(result).to.be.fulfilled; }); - it('rejects with a validation error when validation fails', function() { + it('rejects with a validation error when validation fails', async function() { const instanceValidator = new InstanceValidator(this.User.build({ fails: true })); const result = instanceValidator.validate(); - return expect(result).to.be.rejectedWith(SequelizeValidationError); + await expect(result).to.be.rejectedWith(SequelizeValidationError); }); - it('has a useful default error message for not null validation failures', () => { + it('has a useful default error message for not null validation failures', async () => { const User = Support.sequelize.define('user', { name: { type: Support.Sequelize.STRING, @@ -76,7 +76,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const instanceValidator = new InstanceValidator(User.build()); const result = instanceValidator.validate(); - return expect(result).to.be.rejectedWith(SequelizeValidationError, /user\.name cannot be null/); + await expect(result).to.be.rejectedWith(SequelizeValidationError, /user\.name cannot be null/); }); }); @@ -86,19 +86,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { sinon.stub(this.successfulInstanceValidator, '_validate').resolves(); }); - it('should run beforeValidate and afterValidate hooks when _validate is successful', function() { + it('should run beforeValidate and afterValidate hooks when _validate is successful', async function() { const beforeValidate = sinon.spy(); const afterValidate = sinon.spy(); this.User.beforeValidate(beforeValidate); this.User.afterValidate(afterValidate); - return expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.fulfilled.then(() => { - expect(beforeValidate).to.have.been.calledOnce; - expect(afterValidate).to.have.been.calledOnce; - }); + await expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.fulfilled; + expect(beforeValidate).to.have.been.calledOnce; + expect(afterValidate).to.have.been.calledOnce; }); - it('should run beforeValidate hook but not afterValidate hook when _validate is unsuccessful', function() { + it('should run beforeValidate hook but not afterValidate hook when _validate is unsuccessful', async function() { const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const beforeValidate = sinon.spy(); @@ -106,52 +105,48 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.User.beforeValidate(beforeValidate); this.User.afterValidate(afterValidate); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => { - expect(beforeValidate).to.have.been.calledOnce; - expect(afterValidate).to.not.have.been.called; - }); + await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(beforeValidate).to.have.been.calledOnce; + expect(afterValidate).to.not.have.been.called; }); - it('should emit an error from after hook when afterValidate fails', function() { + it('should emit an error from after hook when afterValidate fails', async function() { this.User.afterValidate(() => { throw new Error('after validation error'); }); - return expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.rejectedWith('after validation error'); + await expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.rejectedWith('after validation error'); }); describe('validatedFailed hook', () => { - it('should call validationFailed hook when validation fails', function() { + it('should call validationFailed hook when validation fails', async function() { const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const validationFailedHook = sinon.spy(); this.User.validationFailed(validationFailedHook); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => { - expect(validationFailedHook).to.have.been.calledOnce; - }); + await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(validationFailedHook).to.have.been.calledOnce; }); - it('should not replace the validation error in validationFailed hook by default', function() { + it('should not replace the validation error in validationFailed hook by default', async function() { const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().resolves(); this.User.validationFailed(validationFailedHook); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => { - expect(err.name).to.equal('SequelizeValidationError'); - }); + const err = await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(err.name).to.equal('SequelizeValidationError'); }); - it('should replace the validation error if validationFailed hook creates a new error', function() { + it('should replace the validation error if validationFailed hook creates a new error', async function() { const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().throws(new Error('validation failed hook error')); this.User.validationFailed(validationFailedHook); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => { - expect(err.message).to.equal('validation failed hook error'); - }); + const err = await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(err.message).to.equal('validation failed hook error'); }); }); }); diff --git a/test/unit/instance/build.test.js b/test/unit/instance/build.test.js index e2b89e65f4b4..f2d671c98e77 100644 --- a/test/unit/instance/build.test.js +++ b/test/unit/instance/build.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('build', () => { - it('should populate NOW default values', () => { + it('should populate NOW default values', async () => { const Model = current.define('Model', { created_time: { type: DataTypes.DATE, @@ -45,7 +45,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(instance.get('updated_time')).to.be.ok; expect(instance.get('updated_time')).to.be.an.instanceof(Date); - return instance.validate(); + await instance.validate(); }); it('should populate explicitly undefined UUID primary keys', () => { diff --git a/test/unit/instance/set.test.js b/test/unit/instance/set.test.js index 7bde622f52c1..dc2ef6056ee7 100644 --- a/test/unit/instance/set.test.js +++ b/test/unit/instance/set.test.js @@ -78,9 +78,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('custom setter', () => { before(function() { - this.stubCreate = sinon.stub(current.getQueryInterface(), 'insert').callsFake(instance => { - return Promise.resolve([instance, 1]); - }); + this.stubCreate = sinon.stub(current.getQueryInterface(), 'insert').callsFake(async instance => [instance, 1]); }); after(function() { @@ -103,52 +101,48 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - it('does not set field to changed if field is set to the same value with custom setter using primitive value', () => { + it('does not set field to changed if field is set to the same value with custom setter using primitive value', async () => { const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', '+1 (0) 234567'); // Canonical equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.false; - }); + user.set('phoneNumber', '+1 (0) 234567');// Canonical equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.false; }); - it('sets field to changed if field is set to the another value with custom setter using primitive value', () => { + it('sets field to changed if field is set to the another value with custom setter using primitive value', async () => { const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', '+1 (0) 765432'); // Canonical non-equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.true; - }); + user.set('phoneNumber', '+1 (0) 765432');// Canonical non-equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.true; }); - it('does not set field to changed if field is set to the same value with custom setter using object', () => { + it('does not set field to changed if field is set to the same value with custom setter using object', async () => { const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', { country: '1', area: '234', local: '567' }); // Canonical equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.false; - }); + user.set('phoneNumber', { country: '1', area: '234', local: '567' });// Canonical equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.false; }); - it('sets field to changed if field is set to the another value with custom setter using object', () => { + it('sets field to changed if field is set to the another value with custom setter using object', async () => { const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', { country: '1', area: '765', local: '432' }); // Canonical non-equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.true; - }); + user.set('phoneNumber', { country: '1', area: '765', local: '432' });// Canonical non-equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.true; }); }); }); diff --git a/test/unit/model/bulkcreate.test.js b/test/unit/model/bulkcreate.test.js index 870a90d070a8..81a8bb403b12 100644 --- a/test/unit/model/bulkcreate.test.js +++ b/test/unit/model/bulkcreate.test.js @@ -30,14 +30,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('validations', () => { - it('should not fail for renamed fields', function() { - return this.Model.bulkCreate([ + it('should not fail for renamed fields', async function() { + await this.Model.bulkCreate([ { accountId: 42 } - ], { validate: true }).then(() => { - expect(this.stub.getCall(0).args[1]).to.deep.equal([ - { account_id: 42, id: null } - ]); - }); + ], { validate: true }); + + expect(this.stub.getCall(0).args[1]).to.deep.equal([ + { account_id: 42, id: null } + ]); }); }); }); diff --git a/test/unit/model/count.test.js b/test/unit/model/count.test.js index 6ffe587ed50e..08838b351057 100644 --- a/test/unit/model/count.test.js +++ b/test/unit/model/count.test.js @@ -38,32 +38,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('should pass the same options to model.aggregate as findAndCountAll', () => { - it('with includes', function() { + it('with includes', async function() { const queryObject = { include: [this.Project] }; - return this.User.count(queryObject) - .then(() => this.User.findAndCountAll(queryObject)) - .then(() => { - const count = this.stub.getCall(0).args; - const findAndCountAll = this.stub.getCall(1).args; - expect(count).to.eql(findAndCountAll); - }); + await this.User.count(queryObject); + await this.User.findAndCountAll(queryObject); + const count = this.stub.getCall(0).args; + const findAndCountAll = this.stub.getCall(1).args; + expect(count).to.eql(findAndCountAll); }); - it('attributes should be stripped in case of findAndCountAll', function() { + it('attributes should be stripped in case of findAndCountAll', async function() { const queryObject = { attributes: ['username'] }; - return this.User.count(queryObject) - .then(() => this.User.findAndCountAll(queryObject)) - .then(() => { - const count = this.stub.getCall(0).args; - const findAndCountAll = this.stub.getCall(1).args; - expect(count).not.to.eql(findAndCountAll); - count[2].attributes = undefined; - expect(count).to.eql(findAndCountAll); - }); + await this.User.count(queryObject); + await this.User.findAndCountAll(queryObject); + const count = this.stub.getCall(0).args; + const findAndCountAll = this.stub.getCall(1).args; + expect(count).not.to.eql(findAndCountAll); + count[2].attributes = undefined; + expect(count).to.eql(findAndCountAll); }); }); diff --git a/test/unit/model/find-and-count-all.test.js b/test/unit/model/find-and-count-all.test.js index e89203af5730..0cf1b2e8c17e 100644 --- a/test/unit/model/find-and-count-all.test.js +++ b/test/unit/model/find-and-count-all.test.js @@ -30,14 +30,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.count.resetBehavior(); }); - it('with errors in count and findAll both', function() { - return this.User.findAndCountAll({}) - .then(() => { - throw new Error(); - }) - .catch(() => { - expect(this.stub.callCount).to.eql(0); - }); + it('with errors in count and findAll both', async function() { + try { + await this.User.findAndCountAll({}); + throw new Error(); + } catch (err) { + expect(this.stub.callCount).to.eql(0); + } }); }); }); diff --git a/test/unit/model/find-create-find.test.js b/test/unit/model/find-create-find.test.js index 4099601f6828..bc1df8cda81f 100644 --- a/test/unit/model/find-create-find.test.js +++ b/test/unit/model/find-create-find.test.js @@ -19,34 +19,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sinon.restore(); }); - it('should return the result of the first find call if not empty', function() { + it('should return the result of the first find call if not empty', async function() { const result = {}, where = { prop: Math.random().toString() }, findSpy = this.sinon.stub(Model, 'findOne').resolves(result); - return expect(Model.findCreateFind({ + await expect(Model.findCreateFind({ where - })).to.eventually.eql([result, false]).then(() => { - expect(findSpy).to.have.been.calledOnce; - expect(findSpy.getCall(0).args[0].where).to.equal(where); - }); + })).to.eventually.eql([result, false]); + + expect(findSpy).to.have.been.calledOnce; + expect(findSpy.getCall(0).args[0].where).to.equal(where); }); - it('should create if first find call is empty', function() { + it('should create if first find call is empty', async function() { const result = {}, where = { prop: Math.random().toString() }, createSpy = this.sinon.stub(Model, 'create').resolves(result); this.sinon.stub(Model, 'findOne').resolves(null); - return expect(Model.findCreateFind({ + await expect(Model.findCreateFind({ where - })).to.eventually.eql([result, true]).then(() => { - expect(createSpy).to.have.been.calledWith(where); - }); + })).to.eventually.eql([result, true]); + + expect(createSpy).to.have.been.calledWith(where); }); - it('should do a second find if create failed do to unique constraint', function() { + it('should do a second find if create failed do to unique constraint', async function() { const result = {}, where = { prop: Math.random().toString() }, findSpy = this.sinon.stub(Model, 'findOne'); @@ -56,12 +56,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { findSpy.onFirstCall().resolves(null); findSpy.onSecondCall().resolves(result); - return expect(Model.findCreateFind({ + await expect(Model.findCreateFind({ where - })).to.eventually.eql([result, false]).then(() => { - expect(findSpy).to.have.been.calledTwice; - expect(findSpy.getCall(1).args[0].where).to.equal(where); - }); + })).to.eventually.eql([result, false]); + + expect(findSpy).to.have.been.calledTwice; + expect(findSpy.getCall(1).args[0].where).to.equal(where); }); }); }); diff --git a/test/unit/model/find-or-create.test.js b/test/unit/model/find-or-create.test.js index 19e1da7c214a..087273a11a3d 100644 --- a/test/unit/model/find-or-create.test.js +++ b/test/unit/model/find-or-create.test.js @@ -34,25 +34,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clsStub.restore(); }); - it('should use transaction from cls if available', function() { + it('should use transaction from cls if available', async function() { const options = { where: { name: 'John' } }; - return this.User.findOrCreate(options) - .then(() => { - expect.fail('expected to fail'); - }) - .catch(err => { - if (!/abort/.test(err.message)) throw err; - expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); - }); - + try { + await this.User.findOrCreate(options); + expect.fail('expected to fail'); + } catch (err) { + if (!/abort/.test(err.message)) throw err; + expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); + } }); - it('should not use transaction from cls if provided as argument', function() { + it('should not use transaction from cls if provided as argument', async function() { const options = { where: { name: 'John' @@ -60,14 +58,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { transaction: { id: 123 } }; - return this.User.findOrCreate(options) - .then(() => { - expect.fail('expected to fail'); - }) - .catch(err => { - if (!/abort/.test(err.message)) throw err; - expect(this.clsStub.called).to.equal(false); - }); + try { + await this.User.findOrCreate(options); + expect.fail('expected to fail'); + } catch (err) { + if (!/abort/.test(err.message)) throw err; + expect(this.clsStub.called).to.equal(false); + } }); }); }); diff --git a/test/unit/model/findall.test.js b/test/unit/model/findall.test.js index 0000b1911728..ed01ea620e04 100644 --- a/test/unit/model/findall.test.js +++ b/test/unit/model/findall.test.js @@ -71,63 +71,63 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('attributes include / exclude', () => { - it('allows me to include additional attributes', function() { - return Model.findAll({ + it('allows me to include additional attributes', async function() { + await Model.findAll({ attributes: { include: ['foobar'] } - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id', - 'name', - 'foobar' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'id', + 'name', + 'foobar' + ]); }); - it('allows me to exclude attributes', function() { - return Model.findAll({ + it('allows me to exclude attributes', async function() { + await Model.findAll({ attributes: { exclude: ['name'] } - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'id' + ]); }); - it('include takes precendence over exclude', function() { - return Model.findAll({ + it('include takes precendence over exclude', async function() { + await Model.findAll({ attributes: { exclude: ['name'], include: ['name'] } - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id', - 'name' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'id', + 'name' + ]); }); - it('works for models without PK #4607', function() { + it('works for models without PK #4607', async function() { const Model = current.define('model', {}, { timestamps: false }); const Foo = current.define('foo'); Model.hasOne(Foo); Model.removeAttribute('id'); - return Model.findAll({ + await Model.findAll({ attributes: { include: ['name'] }, include: [Foo] - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'name' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'name' + ]); }); }); diff --git a/test/unit/model/findone.test.js b/test/unit/model/findone.test.js index b898255d5ce5..93ebe7d21080 100644 --- a/test/unit/model/findone.test.js +++ b/test/unit/model/findone.test.js @@ -23,15 +23,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('should not add limit when querying on a primary key', () => { - it('with id primary key', function() { + it('with id primary key', async function() { const Model = current.define('model'); - return Model.findOne({ where: { id: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { id: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); - it('with custom primary key', function() { + it('with custom primary key', async function() { const Model = current.define('model', { uid: { type: DataTypes.INTEGER, @@ -40,12 +39,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { uid: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { uid: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); - it('with blob primary key', function() { + it('with blob primary key', async function() { const Model = current.define('model', { id: { type: DataTypes.BLOB, @@ -54,22 +52,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { id: Buffer.from('foo') } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { id: Buffer.from('foo') } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); }); - it('should add limit when using { $ gt on the primary key', function() { + it('should add limit when using { $ gt on the primary key', async function() { const Model = current.define('model'); - return Model.findOne({ where: { id: { [Op.gt]: 42 } } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); - }); + await Model.findOne({ where: { id: { [Op.gt]: 42 } } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); }); describe('should not add limit when querying on an unique key', () => { - it('with custom unique key', function() { + it('with custom unique key', async function() { const Model = current.define('model', { unique: { type: DataTypes.INTEGER, @@ -77,12 +73,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { unique: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { unique: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); - it('with blob unique key', function() { + it('with blob unique key', async function() { const Model = current.define('model', { unique: { type: DataTypes.BLOB, @@ -90,13 +85,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { unique: Buffer.from('foo') } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { unique: Buffer.from('foo') } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); }); - it('should add limit when using multi-column unique key', function() { + it('should add limit when using multi-column unique key', async function() { const Model = current.define('model', { unique1: { type: DataTypes.INTEGER, @@ -108,9 +102,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { unique1: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); - }); + await Model.findOne({ where: { unique1: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); }); }); }); diff --git a/test/unit/model/update.test.js b/test/unit/model/update.test.js index ea10f506e1ed..042b53faaa7e 100644 --- a/test/unit/model/update.test.js +++ b/test/unit/model/update.test.js @@ -32,16 +32,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('properly clones input values', () => { - it('with default options', function() { - return this.User.update(this.updates, { where: { secretValue: '1' } }).then(() => { - expect(this.updates).to.be.deep.eql(this.cloneUpdates); - }); + it('with default options', async function() { + await this.User.update(this.updates, { where: { secretValue: '1' } }); + expect(this.updates).to.be.deep.eql(this.cloneUpdates); }); - it('when using fields option', function() { - return this.User.update(this.updates, { where: { secretValue: '1' }, fields: ['name'] }).then(() => { - expect(this.updates).to.be.deep.eql(this.cloneUpdates); - }); + it('when using fields option', async function() { + await this.User.update(this.updates, { where: { secretValue: '1' }, fields: ['name'] }); + expect(this.updates).to.be.deep.eql(this.cloneUpdates); }); }); diff --git a/test/unit/model/upsert.test.js b/test/unit/model/upsert.test.js index e59b9851665b..63db06413b4b 100644 --- a/test/unit/model/upsert.test.js +++ b/test/unit/model/upsert.test.js @@ -51,48 +51,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.stub.restore(); }); - it('skip validations for missing fields', function() { - return expect(this.User.upsert({ + it('skip validations for missing fields', async function() { + await expect(this.User.upsert({ name: 'Grumpy Cat' })).not.to.be.rejectedWith(Sequelize.ValidationError); }); - it('creates new record with correct field names', function() { - return this.User + it('creates new record with correct field names', async function() { + await this.User .upsert({ name: 'Young Cat', virtualValue: 999 - }) - .then(() => { - expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ - 'name', 'value', 'created_at', 'updatedAt' - ]); }); + + expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ + 'name', 'value', 'created_at', 'updatedAt' + ]); }); - it('creates new record with timestamps disabled', function() { - return this.UserNoTime + it('creates new record with timestamps disabled', async function() { + await this.UserNoTime .upsert({ name: 'Young Cat' - }) - .then(() => { - expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ - 'name' - ]); }); + + expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ + 'name' + ]); }); - it('updates all changed fields by default', function() { - return this.User + it('updates all changed fields by default', async function() { + await this.User .upsert({ name: 'Old Cat', virtualValue: 111 - }) - .then(() => { - expect(Object.keys(this.stub.getCall(0).args[2])).to.deep.equal([ - 'name', 'value', 'updatedAt' - ]); }); + + expect(Object.keys(this.stub.getCall(0).args[2])).to.deep.equal([ + 'name', 'value', 'updatedAt' + ]); }); }); } diff --git a/test/unit/model/validation.test.js b/test/unit/model/validation.test.js index 5caf5f778c87..1c70ea1880f5 100644 --- a/test/unit/model/validation.test.js +++ b/test/unit/model/validation.test.js @@ -181,7 +181,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const applyFailTest = function applyFailTest(validatorDetails, i, validator) { const failingValue = validatorDetails.fail[i]; - it(`correctly specifies an instance as invalid using a value of "${failingValue}" for the validation "${validator}"`, function() { + it(`correctly specifies an instance as invalid using a value of "${failingValue}" for the validation "${validator}"`, async function() { const validations = {}, message = `${validator}(${failingValue})`; @@ -197,15 +197,14 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingUser = UserFail.build({ name: failingValue }); - return expect(failingUser.validate()).to.be.rejected.then(_errors => { - expect(_errors.get('name')[0].message).to.equal(message); - expect(_errors.get('name')[0].value).to.equal(failingValue); - }); + const _errors = await expect(failingUser.validate()).to.be.rejected; + expect(_errors.get('name')[0].message).to.equal(message); + expect(_errors.get('name')[0].value).to.equal(failingValue); }); }, applyPassTest = function applyPassTest(validatorDetails, j, validator, type) { const succeedingValue = validatorDetails.pass[j]; - it(`correctly specifies an instance as valid using a value of "${succeedingValue}" for the validation "${validator}"`, function() { + it(`correctly specifies an instance as valid using a value of "${succeedingValue}" for the validation "${validator}"`, async function() { const validations = {}, message = `${validator}(${succeedingValue})`; @@ -227,7 +226,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); const successfulUser = UserSuccess.build({ name: succeedingValue }); - return expect(successfulUser.validate()).not.to.be.rejected; + await expect(successfulUser.validate()).not.to.be.rejected; }); }; @@ -272,7 +271,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').callsFake(() => Promise.resolve([User.build({}), 1])); + this.stub = sinon.stub(current, 'query').callsFake(async () => Promise.resolve([User.build({}), 1])); }); after(function() { @@ -281,34 +280,34 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should not throw', () => { describe('create', () => { - it('should allow number as a string', () => { - return expect(User.create({ + it('should allow number as a string', async () => { + await expect(User.create({ age: '12' })).not.to.be.rejected; }); - it('should allow decimal as a string', () => { - return expect(User.create({ + it('should allow decimal as a string', async () => { + await expect(User.create({ number: '12.6' })).not.to.be.rejected; }); - it('should allow dates as a string', () => { - return expect(User.findOne({ + it('should allow dates as a string', async () => { + await expect(User.findOne({ where: { date: '2000-12-16' } })).not.to.be.rejected; }); - it('should allow decimal big numbers as a string', () => { - return expect(User.create({ + it('should allow decimal big numbers as a string', async () => { + await expect(User.create({ number: '2321312301230128391820831289123012' })).not.to.be.rejected; }); - it('should allow decimal as scientific notation', () => { - return Promise.all([expect(User.create({ + it('should allow decimal as scientific notation', async () => { + await Promise.all([expect(User.create({ number: '2321312301230128391820e219' })).not.to.be.rejected, expect(User.create({ number: '2321312301230128391820e+219' @@ -317,34 +316,34 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { })).to.be.rejected]); }); - it('should allow string as a number', () => { - return expect(User.create({ + it('should allow string as a number', async () => { + await expect(User.create({ name: 12 })).not.to.be.rejected; }); - it('should allow 0/1 as a boolean', () => { - return expect(User.create({ + it('should allow 0/1 as a boolean', async () => { + await expect(User.create({ awesome: 1 })).not.to.be.rejected; }); - it('should allow 0/1 string as a boolean', () => { - return expect(User.create({ + it('should allow 0/1 string as a boolean', async () => { + await expect(User.create({ awesome: '1' })).not.to.be.rejected; }); - it('should allow true/false string as a boolean', () => { - return expect(User.create({ + it('should allow true/false string as a boolean', async () => { + await expect(User.create({ awesome: 'true' })).not.to.be.rejected; }); }); describe('findAll', () => { - it('should allow $in', () => { - return expect(User.findAll({ + it('should allow $in', async () => { + await expect(User.findAll({ where: { name: { [Op.like]: { @@ -355,8 +354,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { })).not.to.be.rejected; }); - it('should allow $like for uuid', () => { - return expect(User.findAll({ + it('should allow $like for uuid', async () => { + await expect(User.findAll({ where: { uid: { [Op.like]: '12345678%' @@ -370,8 +369,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should throw validationerror', () => { describe('create', () => { - it('should throw when passing string', () => { - return expect(User.create({ + it('should throw when passing string', async () => { + await expect(User.create({ age: 'jan' })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -388,8 +387,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('should throw when passing decimal', () => { - return expect(User.create({ + it('should throw when passing decimal', async () => { + await expect(User.create({ age: 4.5 })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -408,8 +407,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); describe('update', () => { - it('should throw when passing string', () => { - return expect(User.update({ + it('should throw when passing string', async () => { + await expect(User.update({ age: 'jan' }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -426,8 +425,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('should throw when passing decimal', () => { - return expect(User.update({ + it('should throw when passing decimal', async () => { + await expect(User.update({ age: 4.5 }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -484,8 +483,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should not throw', () => { describe('create', () => { - it('custom validation functions are successful', () => { - return expect(User.create({ + it('custom validation functions are successful', async () => { + await expect(User.create({ age: 1, name: 'noerror' })).not.to.be.rejected; @@ -493,8 +492,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); describe('update', () => { - it('custom validation functions are successful', () => { - return expect(User.update({ + it('custom validation functions are successful', async () => { + await expect(User.update({ age: 1, name: 'noerror' }, { where: {} })).not.to.be.rejected; @@ -505,28 +504,28 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should throw validationerror', () => { describe('create', () => { - it('custom attribute validation function fails', () => { - return expect(User.create({ + it('custom attribute validation function fails', async () => { + await expect(User.create({ age: -1 })).to.be.rejectedWith(Sequelize.ValidationError); }); - it('custom model validation function fails', () => { - return expect(User.create({ + it('custom model validation function fails', async () => { + await expect(User.create({ name: 'error' })).to.be.rejectedWith(Sequelize.ValidationError); }); }); describe('update', () => { - it('custom attribute validation function fails', () => { - return expect(User.update({ + it('custom attribute validation function fails', async () => { + await expect(User.update({ age: -1 }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); }); - it('when custom model validation function fails', () => { - return expect(User.update({ + it('when custom model validation function fails', async () => { + await expect(User.update({ name: 'error' }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -540,11 +539,10 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { name: Sequelize.STRING }, { validate: { - customFn() { + async customFn() { if (this.get('name') === 'error') { - return Promise.reject(new Error('Error from model validation promise')); + throw new Error('Error from model validation promise'); } - return Promise.resolve(); } } }); @@ -559,16 +557,16 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should not throw', () => { describe('create', () => { - it('custom model validation functions are successful', () => { - return expect(User.create({ + it('custom model validation functions are successful', async () => { + await expect(User.create({ name: 'noerror' })).not.to.be.rejected; }); }); describe('update', () => { - it('custom model validation functions are successful', () => { - return expect(User.update({ + it('custom model validation functions are successful', async () => { + await expect(User.update({ name: 'noerror' }, { where: {} })).not.to.be.rejected; }); @@ -578,16 +576,16 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should throw validationerror', () => { describe('create', () => { - it('custom model validation function fails', () => { - return expect(User.create({ + it('custom model validation function fails', async () => { + await expect(User.create({ name: 'error' })).to.be.rejectedWith(Sequelize.ValidationError); }); }); describe('update', () => { - it('when custom model validation function fails', () => { - return expect(User.update({ + it('when custom model validation function fails', async () => { + await expect(User.update({ name: 'error' }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -631,21 +629,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 10, name: null - })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 10, name: null - }, { where: {} })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + }, { where: {} })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); }); @@ -654,21 +652,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 11, name: null - })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.been.calledOnce; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 11, name: null - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.been.calledOnce; }); }); @@ -700,21 +698,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 99, name: null - })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.not.been.called; - }); + })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.not.been.called; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 99, name: null - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.not.been.called; - }); + }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.not.been.called; }); }); @@ -723,21 +721,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 99, name: 'foo' - })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 99, name: 'foo' - }, { where: {} })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + }, { where: {} })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); }); diff --git a/test/unit/transaction.test.js b/test/unit/transaction.test.js index ab23a3c9296d..2ab1d1d877f3 100644 --- a/test/unit/transaction.test.js +++ b/test/unit/transaction.test.js @@ -33,7 +33,7 @@ describe('Transaction', () => { this.stubConnection.restore(); }); - it('should run auto commit query only when needed', function() { + it('should run auto commit query only when needed', async function() { const expectations = { all: [ 'START TRANSACTION;' @@ -45,13 +45,13 @@ describe('Transaction', () => { 'BEGIN TRANSACTION;' ] }; - return current.transaction(() => { + + await current.transaction(async () => { expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - return Promise.resolve(); }); }); - it('should set isolation level correctly', function() { + it('should set isolation level correctly', async function() { const expectations = { all: [ 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', @@ -69,9 +69,9 @@ describe('Transaction', () => { 'BEGIN TRANSACTION;' ] }; - return current.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED }, () => { + + await current.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED }, async () => { expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - return Promise.resolve(); }); }); }); From c013246bdc9ea388af6a393fd1f3f5646a4143b3 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Mon, 4 May 2020 23:18:54 -0500 Subject: [PATCH 136/414] test: asyncify integration/associations (#12227) --- test/integration/associations/alias.test.js | 106 +- .../associations/belongs-to-many.test.js | 3106 ++++++++--------- .../associations/belongs-to.test.js | 996 +++--- .../integration/associations/has-many.test.js | 1925 +++++----- test/integration/associations/has-one.test.js | 879 ++--- .../multiple-level-filters.test.js | 338 +- test/integration/associations/scope.test.js | 651 ++-- test/integration/associations/self.test.js | 145 +- 8 files changed, 3762 insertions(+), 4384 deletions(-) diff --git a/test/integration/associations/alias.test.js b/test/integration/associations/alias.test.js index b6be24065700..b53067d25958 100644 --- a/test/integration/associations/alias.test.js +++ b/test/integration/associations/alias.test.js @@ -5,78 +5,68 @@ const chai = require('chai'), Support = require('../support'); describe(Support.getTestDialectTeaser('Alias'), () => { - it('should uppercase the first letter in alias getter, but not in eager loading', function() { + it('should uppercase the first letter in alias getter, but not in eager loading', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: 'assignments', foreignKey: 'userId' }); Task.belongsTo(User, { as: 'owner', foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getAssignments).to.be.ok; - - return Task.create({ id: 1, userId: 1 }); - }).then(task => { - expect(task.getOwner).to.be.ok; - - return Promise.all([ - User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'assignments' }] }), - Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'owner' }] }) - ]); - }).then(([user, task]) => { - expect(user.assignments).to.be.ok; - expect(task.owner).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getAssignments).to.be.ok; + + const task0 = await Task.create({ id: 1, userId: 1 }); + expect(task0.getOwner).to.be.ok; + + const [user, task] = await Promise.all([ + User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'assignments' }] }), + Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'owner' }] }) + ]); + + expect(user.assignments).to.be.ok; + expect(task.owner).to.be.ok; }); - it('shouldnt touch the passed alias', function() { + it('shouldnt touch the passed alias', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: 'ASSIGNMENTS', foreignKey: 'userId' }); Task.belongsTo(User, { as: 'OWNER', foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getASSIGNMENTS).to.be.ok; - - return Task.create({ id: 1, userId: 1 }); - }).then(task => { - expect(task.getOWNER).to.be.ok; - - return Promise.all([ - User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'ASSIGNMENTS' }] }), - Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'OWNER' }] }) - ]); - }).then(([user, task]) => { - expect(user.ASSIGNMENTS).to.be.ok; - expect(task.OWNER).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getASSIGNMENTS).to.be.ok; + + const task0 = await Task.create({ id: 1, userId: 1 }); + expect(task0.getOWNER).to.be.ok; + + const [user, task] = await Promise.all([ + User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'ASSIGNMENTS' }] }), + Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'OWNER' }] }) + ]); + + expect(user.ASSIGNMENTS).to.be.ok; + expect(task.OWNER).to.be.ok; }); - it('should allow me to pass my own plural and singular forms to hasMany', function() { + it('should allow me to pass my own plural and singular forms to hasMany', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: { singular: 'task', plural: 'taskz' } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getTaskz).to.be.ok; - expect(user.addTask).to.be.ok; - expect(user.addTaskz).to.be.ok; - }).then(() => { - return User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'taskz' }] }); - }).then(user => { - expect(user.taskz).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getTaskz).to.be.ok; + expect(user0.addTask).to.be.ok; + expect(user0.addTaskz).to.be.ok; + const user = await User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'taskz' }] }); + expect(user.taskz).to.be.ok; }); - it('should allow me to define plural and singular forms on the model', function() { + it('should allow me to define plural and singular forms on the model', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}, { name: { @@ -87,16 +77,12 @@ describe(Support.getTestDialectTeaser('Alias'), () => { User.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getAssignments).to.be.ok; - expect(user.addAssignment).to.be.ok; - expect(user.addAssignments).to.be.ok; - }).then(() => { - return User.findOne({ where: { id: 1 }, include: [Task] }); - }).then(user => { - expect(user.assignments).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getAssignments).to.be.ok; + expect(user0.addAssignment).to.be.ok; + expect(user0.addAssignments).to.be.ok; + const user = await User.findOne({ where: { id: 1 }, include: [Task] }); + expect(user.assignments).to.be.ok; }); }); diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 9edec9f92173..82f95d5725c8 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -13,144 +13,129 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('BelongsToMany'), () => { describe('getAssociations', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); this.User.belongsToMany(this.Task, { through: 'UserTasks' }); this.Task.belongsToMany(this.User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - this.tasks = [task1, task2]; - this.user = john; - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + this.tasks = [task1, task2]; + this.user = john; + + return john.setTasks([task1, task2]); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.belongsToMany(ctx.Label, { through: 'ArticleLabels' }); - ctx.Label.belongsToMany(ctx.Article, { through: 'ArticleLabels' }); - - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }), - ctx.sequelize.transaction() - ]); - }).then(([article, label, t]) => { - ctx.t = t; - return article.setLabels([label], { transaction: t }); - }).then(() => { - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return articles[0].getLabels(); - }).then(labels => { - expect(labels).to.have.length(0); - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return articles[0].getLabels({ transaction: ctx.t }); - }).then(labels => { - expect(labels).to.have.length(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.belongsToMany(Label, { through: 'ArticleLabels' }); + Label.belongsToMany(Article, { through: 'ArticleLabels' }); + + await sequelize.sync({ force: true }); + + const [article, label, t] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }), + sequelize.transaction() + ]); + + await article.setLabels([label], { transaction: t }); + const articles0 = await Article.findAll({ transaction: t }); + const labels0 = await articles0[0].getLabels(); + expect(labels0).to.have.length(0); + const articles = await Article.findAll({ transaction: t }); + const labels = await articles[0].getLabels({ transaction: t }); + expect(labels).to.have.length(1); + await t.rollback(); }); } - it('gets all associated objects with all fields', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks(); - }).then(tasks => { - Object.keys(tasks[0].rawAttributes).forEach(attr => { - expect(tasks[0]).to.have.property(attr); - }); + it('gets all associated objects with all fields', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + Object.keys(tasks[0].rawAttributes).forEach(attr => { + expect(tasks[0]).to.have.property(attr); }); }); - it('gets all associated objects when no options are passed', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - }); + it('gets all associated objects when no options are passed', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + expect(tasks).to.have.length(2); }); - it('only get objects that fulfill the options', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks({ - where: { - active: true - } - }); - }).then(tasks => { - expect(tasks).to.have.length(1); + it('only get objects that fulfill the options', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + + const tasks = await john.getTasks({ + where: { + active: true + } }); + + expect(tasks).to.have.length(1); }); - it('supports a where not in', function() { - return this.User.findOne({ + it('supports a where not in', async function() { + const john = await this.User.findOne({ where: { username: 'John' } - }).then(john => { - return john.getTasks({ - where: { - title: { - [Op.not]: ['Get rich'] - } + }); + + const tasks = await john.getTasks({ + where: { + title: { + [Op.not]: ['Get rich'] } - }); - }).then(tasks => { - expect(tasks).to.have.length(1); + } }); + + expect(tasks).to.have.length(1); }); - it('supports a where not in on the primary key', function() { - return this.User.findOne({ + it('supports a where not in on the primary key', async function() { + const john = await this.User.findOne({ where: { username: 'John' } - }).then(john => { - return john.getTasks({ - where: { - id: { - [Op.not]: [this.tasks[0].get('id')] - } + }); + + const tasks = await john.getTasks({ + where: { + id: { + [Op.not]: [this.tasks[0].get('id')] } - }); - }).then(tasks => { - expect(tasks).to.have.length(1); + } }); + + expect(tasks).to.have.length(1); }); - it('only gets objects that fulfill options with a formatted value', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks({ where: { active: true } }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + it('only gets objects that fulfill options with a formatted value', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks({ where: { active: true } }); + expect(tasks).to.have.length(1); }); - it('get associated objects with an eager load', function() { - return this.User.findOne({ where: { username: 'John' }, include: [this.Task] }).then(john => { - expect(john.Tasks).to.have.length(2); - }); + it('get associated objects with an eager load', async function() { + const john = await this.User.findOne({ where: { username: 'John' }, include: [this.Task] }); + expect(john.Tasks).to.have.length(2); }); - it('get associated objects with an eager load with conditions but not required', function() { + it('get associated objects with an eager load with conditions but not required', async function() { const Label = this.sequelize.define('Label', { 'title': DataTypes.STRING, 'isActive': DataTypes.BOOLEAN }), Task = this.Task, User = this.User; @@ -158,21 +143,21 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.hasMany(Label); Label.belongsTo(Task); - return Label.sync({ force: true }).then(() => { - return User.findOne({ - where: { username: 'John' }, - include: [ - { model: Task, required: false, include: [ - { model: Label, required: false, where: { isActive: true } } - ] } - ] - }); - }).then(john => { - expect(john.Tasks).to.have.length(2); + await Label.sync({ force: true }); + + const john = await User.findOne({ + where: { username: 'John' }, + include: [ + { model: Task, required: false, include: [ + { model: Label, required: false, where: { isActive: true } } + ] } + ] }); + + expect(john.Tasks).to.have.length(2); }); - it('should support schemas', function() { + it('should support schemas', async function() { const AcmeUser = this.sequelize.define('User', { username: DataTypes.STRING }).schema('acme', '_'), @@ -188,42 +173,32 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { AcmeUser.belongsToMany(AcmeProject, { through: AcmeProjectUsers }); AcmeProject.belongsToMany(AcmeUser, { through: AcmeProjectUsers }); - const ctx = {}; - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('acme'); - }).then(() => { - return Promise.all([ - AcmeUser.sync({ force: true }), - AcmeProject.sync({ force: true }) - ]); - }).then(() => { - return AcmeProjectUsers.sync({ force: true }); - }).then(() => { - return AcmeUser.create(); - }).then(u => { - ctx.u = u; - return AcmeProject.create(); - }).then(p => { - return ctx.u.addProject(p, { through: { status: 'active', data: 42 } }); - }).then(() => { - return ctx.u.getProjects(); - }).then(projects => { - expect(projects).to.have.length(1); - const project = projects[0]; - expect(project.ProjectUsers).to.be.ok; - expect(project.status).not.to.exist; - expect(project.ProjectUsers.status).to.equal('active'); - return this.sequelize.dropSchema('acme').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('acme'); - } - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('acme'); + + await Promise.all([ + AcmeUser.sync({ force: true }), + AcmeProject.sync({ force: true }) + ]); + + await AcmeProjectUsers.sync({ force: true }); + const u = await AcmeUser.create(); + const p = await AcmeProject.create(); + await u.addProject(p, { through: { status: 'active', data: 42 } }); + const projects = await u.getProjects(); + expect(projects).to.have.length(1); + const project = projects[0]; + expect(project.ProjectUsers).to.be.ok; + expect(project.status).not.to.exist; + expect(project.ProjectUsers.status).to.equal('active'); + await this.sequelize.dropSchema('acme'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('acme'); + } }); - it('supports custom primary keys and foreign keys', function() { + it('supports custom primary keys and foreign keys', async function() { const User = this.sequelize.define('User', { 'id_user': { type: DataTypes.UUID, @@ -255,20 +230,18 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { as: 'groups', through: User_has_Group, foreignKey: 'id_user' }); Group.belongsToMany(User, { as: 'users', through: User_has_Group, foreignKey: 'id_group' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), Group.create()]).then(([user, group]) => { - return user.addGroup(group); - }).then(() => { - return User.findOne({ - where: {} - }).then(user => { - return user.getGroups(); - }); - }); + await this.sequelize.sync({ force: true }); + const [user0, group] = await Promise.all([User.create(), Group.create()]); + await user0.addGroup(group); + + const user = await User.findOne({ + where: {} }); + + await user.getGroups(); }); - it('supports primary key attributes with different field and attribute names', function() { + it('supports primary key attributes with different field and attribute names', async function() { const User = this.sequelize.define('User', { userSecondId: { type: DataTypes.UUID, @@ -302,29 +275,27 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: User_has_Group }); Group.belongsToMany(User, { through: User_has_Group }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), Group.create()]).then(([user, group]) => { - return user.addGroup(group); - }).then(() => { - return Promise.all([User.findOne({ - where: {}, - include: [Group] - }), User.findAll({ - include: [Group] - })]); - }).then(([user, users]) => { - expect(user.Groups.length).to.be.equal(1); - expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.equal(user.userSecondId); - expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(user.Groups[0].groupSecondId); - expect(users.length).to.be.equal(1); - expect(users[0].toJSON()).to.be.eql(user.toJSON()); - }); - }); + await this.sequelize.sync({ force: true }); + const [user0, group] = await Promise.all([User.create(), Group.create()]); + await user0.addGroup(group); + + const [user, users] = await Promise.all([User.findOne({ + where: {}, + include: [Group] + }), User.findAll({ + include: [Group] + })]); + + expect(user.Groups.length).to.be.equal(1); + expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.equal(user.userSecondId); + expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(user.Groups[0].groupSecondId); + expect(users.length).to.be.equal(1); + expect(users[0].toJSON()).to.be.eql(user.toJSON()); }); - it('supports non primary key attributes for joins (sourceKey only)', function() { + it('supports non primary key attributes for joins (sourceKey only)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -376,45 +347,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); - }).then(() => { - return Promise.all([User.findAll({ - where: {}, - include: [Group] - }), Group.findAll({ - include: [User] - })]); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (targetKey only)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (targetKey only)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -460,45 +429,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: 'usergroups', targetKey: 'userSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); - }).then(() => { - return Promise.all([User.findAll({ - where: {}, - include: [Group] - }), Group.findAll({ - include: [User] - })]); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.GroupId).to.be.ok; - expect(users[0].Groups[0].usergroups.GroupId).to.be.equal(users[0].Groups[0].id); - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.GroupId).to.be.ok; - expect(users[1].Groups[0].usergroups.GroupId).to.be.equal(users[1].Groups[0].id); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.GroupId).to.be.ok; - expect(groups[0].Users[0].usergroups.GroupId).to.be.equal(groups[0].id); - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.GroupId).to.be.ok; - expect(groups[1].Users[0].usergroups.GroupId).to.be.equal(groups[1].id); - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (sourceKey and targetKey)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.GroupId).to.be.ok; + expect(users[0].Groups[0].usergroups.GroupId).to.be.equal(users[0].Groups[0].id); + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.GroupId).to.be.ok; + expect(users[1].Groups[0].usergroups.GroupId).to.be.equal(users[1].Groups[0].id); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.GroupId).to.be.ok; + expect(groups[0].Users[0].usergroups.GroupId).to.be.equal(groups[0].id); + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.GroupId).to.be.ok; + expect(groups[1].Users[0].usergroups.GroupId).to.be.equal(groups[1].id); + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (sourceKey and targetKey)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -550,45 +517,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId', targetKey: 'groupSecondId' }); Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); - }).then(() => { - return Promise.all([User.findAll({ - where: {}, - include: [Group] - }), Group.findAll({ - include: [User] - })]); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (custom through model)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (custom through model)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -651,45 +616,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: User_has_Group, sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: User_has_Group, sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); - }).then(() => { - return Promise.all([User.findAll({ - where: {}, - include: [Group] - }), Group.findAll({ - include: [User] - })]); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins for getting associations (sourceKey/targetKey)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins for getting associations (sourceKey/targetKey)', async function() { const User = this.sequelize.define('User', { userId: { type: DataTypes.UUID, @@ -739,28 +702,25 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId', targetKey: 'groupSecondId' }); Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]) - .then(() => { - return Promise.all( - [user1.getGroups(), user2.getGroups(), group1.getUsers(), group2.getUsers()] - ); - }).then(([groups1, groups2, users1, users2]) => { - expect(groups1.length).to.be.equal(1); - expect(groups1[0].id).to.be.equal(group1.id); - expect(groups2.length).to.be.equal(1); - expect(groups2[0].id).to.be.equal(group2.id); - expect(users1.length).to.be.equal(1); - expect(users1[0].id).to.be.equal(user1.id); - expect(users2.length).to.be.equal(1); - expect(users2[0].id).to.be.equal(user2.id); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [groups1, groups2, users1, users2] = await Promise.all( + [user1.getGroups(), user2.getGroups(), group1.getUsers(), group2.getUsers()] + ); + + expect(groups1.length).to.be.equal(1); + expect(groups1[0].id).to.be.equal(group1.id); + expect(groups2.length).to.be.equal(1); + expect(groups2[0].id).to.be.equal(group2.id); + expect(users1.length).to.be.equal(1); + expect(users1[0].id).to.be.equal(user1.id); + expect(users2.length).to.be.equal(1); + expect(users2[0].id).to.be.equal(user2.id); }); - it('supports non primary key attributes for joins (custom foreignKey)', function() { + it('supports non primary key attributes for joins (custom foreignKey)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -812,45 +772,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', foreignKey: 'userId2', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: 'usergroups', foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); - }).then(() => { - return Promise.all([User.findAll({ - where: {}, - include: [Group] - }), Group.findAll({ - include: [User] - })]); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.userId2).to.be.ok; - expect(users[0].Groups[0].usergroups.userId2).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.groupId2).to.be.ok; - expect(users[0].Groups[0].usergroups.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].usergroups.userId2).to.be.ok; - expect(users[1].Groups[0].usergroups.userId2).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.groupId2).to.be.ok; - expect(users[1].Groups[0].usergroups.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.groupId2).to.be.ok; - expect(groups[0].Users[0].usergroups.groupId2).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].usergroups.userId2).to.be.ok; - expect(groups[0].Users[0].usergroups.userId2).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.groupId2).to.be.ok; - expect(groups[1].Users[0].usergroups.groupId2).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].usergroups.userId2).to.be.ok; - expect(groups[1].Users[0].usergroups.userId2).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (custom foreignKey, custom through model)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.userId2).to.be.ok; + expect(users[0].Groups[0].usergroups.userId2).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.groupId2).to.be.ok; + expect(users[0].Groups[0].usergroups.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].usergroups.userId2).to.be.ok; + expect(users[1].Groups[0].usergroups.userId2).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.groupId2).to.be.ok; + expect(users[1].Groups[0].usergroups.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.groupId2).to.be.ok; + expect(groups[0].Users[0].usergroups.groupId2).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].usergroups.userId2).to.be.ok; + expect(groups[0].Users[0].usergroups.userId2).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.groupId2).to.be.ok; + expect(groups[1].Users[0].usergroups.groupId2).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].usergroups.userId2).to.be.ok; + expect(groups[1].Users[0].usergroups.userId2).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (custom foreignKey, custom through model)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -923,45 +881,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: User_has_Group, foreignKey: 'userId2', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: User_has_Group, foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), User.create(), Group.create(), Group.create()]).then(([user1, user2, group1, group2]) => { - return Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); - }).then(() => { - return Promise.all([User.findAll({ - where: {}, - include: [Group] - }), Group.findAll({ - include: [User] - })]); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].User_has_Group.userId2).to.be.ok; - expect(users[0].Groups[0].User_has_Group.userId2).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].User_has_Group.groupId2).to.be.ok; - expect(users[0].Groups[0].User_has_Group.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].User_has_Group.userId2).to.be.ok; - expect(users[1].Groups[0].User_has_Group.userId2).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].User_has_Group.groupId2).to.be.ok; - expect(users[1].Groups[0].User_has_Group.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].User_has_Group.groupId2).to.be.ok; - expect(groups[0].Users[0].User_has_Group.groupId2).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].User_has_Group.userId2).to.be.ok; - expect(groups[0].Users[0].User_has_Group.userId2).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].User_has_Group.groupId2).to.be.ok; - expect(groups[1].Users[0].User_has_Group.groupId2).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].User_has_Group.userId2).to.be.ok; - expect(groups[1].Users[0].User_has_Group.userId2).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports primary key attributes with different field names where parent include is required', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].User_has_Group.userId2).to.be.ok; + expect(users[0].Groups[0].User_has_Group.userId2).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].User_has_Group.groupId2).to.be.ok; + expect(users[0].Groups[0].User_has_Group.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].User_has_Group.userId2).to.be.ok; + expect(users[1].Groups[0].User_has_Group.userId2).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].User_has_Group.groupId2).to.be.ok; + expect(users[1].Groups[0].User_has_Group.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].User_has_Group.groupId2).to.be.ok; + expect(groups[0].Users[0].User_has_Group.groupId2).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].User_has_Group.userId2).to.be.ok; + expect(groups[0].Users[0].User_has_Group.userId2).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].User_has_Group.groupId2).to.be.ok; + expect(groups[1].Users[0].User_has_Group.groupId2).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].User_has_Group.userId2).to.be.ok; + expect(groups[1].Users[0].User_has_Group.userId2).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports primary key attributes with different field names where parent include is required', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -1009,31 +965,29 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Company.belongsToMany(Group, { through: Company_has_Group }); Group.belongsToMany(Company, { through: Company_has_Group }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create(), Group.create(), Company.create()]).then(([user, group, company]) => { - return Promise.all([user.setCompany(company), company.addGroup(group)]); - }).then(() => { - return Promise.all([User.findOne({ - where: {}, - include: [ - { model: Company, include: [Group] } - ] - }), User.findAll({ - include: [ - { model: Company, include: [Group] } - ] - }), User.findOne({ - where: {}, - include: [ - { model: Company, required: true, include: [Group] } - ] - }), User.findAll({ - include: [ - { model: Company, required: true, include: [Group] } - ] - })]); - }); - }); + await this.sequelize.sync({ force: true }); + const [user, group, company] = await Promise.all([User.create(), Group.create(), Company.create()]); + await Promise.all([user.setCompany(company), company.addGroup(group)]); + + await Promise.all([User.findOne({ + where: {}, + include: [ + { model: Company, include: [Group] } + ] + }), User.findAll({ + include: [ + { model: Company, include: [Group] } + ] + }), User.findOne({ + where: {}, + include: [ + { model: Company, required: true, include: [Group] } + ] + }), User.findAll({ + include: [ + { model: Company, required: true, include: [Group] } + ] + })]); }); }); @@ -1064,123 +1018,114 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = ctx.sequelize.define('Article', { - pk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - title: DataTypes.STRING - }); - ctx.Label = ctx.sequelize.define('Label', { - sk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - text: DataTypes.STRING - }); - ctx.ArticleLabel = ctx.sequelize.define('ArticleLabel'); - - ctx.Article.belongsToMany(ctx.Label, { through: ctx.ArticleLabel }); - ctx.Label.belongsToMany(ctx.Article, { through: ctx.ArticleLabel }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); - return ctx.sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - ctx.label = label; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.setLabels([ctx.label], { transaction: t }); - }).then(() => { - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return Promise.all([ - articles[0].hasLabels([ctx.label]), - articles[0].hasLabels([ctx.label], { transaction: ctx.t }) - ]); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.true; + const Article = sequelize.define('Article', { + pk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + title: DataTypes.STRING + }); - return ctx.t.rollback(); + const Label = sequelize.define('Label', { + sk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + text: DataTypes.STRING }); + + const ArticleLabel = sequelize.define('ArticleLabel'); + + Article.belongsToMany(Label, { through: ArticleLabel }); + Label.belongsToMany(Article, { through: ArticleLabel }); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.setLabels([label], { transaction: t }); + const articles = await Article.findAll({ transaction: t }); + + const [hasLabel1, hasLabel2] = await Promise.all([ + articles[0].hasLabels([label]), + articles[0].hasLabels([label], { transaction: t }) + ]); + + expect(hasLabel1).to.be.false; + expect(hasLabel2).to.be.true; + + await t.rollback(); }); } - it('answers false if only some labels have been assigned', function() { - return Promise.all([ + it('answers false if only some labels have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return article.hasLabels([label1, label2]); - }); - }).then(result => { - expect(result).to.be.false; - }); + ]); + + await article.addLabel(label1); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.false; }); - it('answers false if only some labels have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers false if only some labels have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabels([label1]).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.false; - }); - }); - }); + ]); + + await article.addLabels([label1]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.false; }); - it('answers true if all label have been assigned', function() { - return Promise.all([ + it('answers true if all label have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([label1, label2]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.true; }); - it('answers true if all label have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers true if all label have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.true; }); - it('answers true for labels that have been assigned multitple times', function() { + it('answers true for labels that have been assigned multitple times', async function() { this.ArticleLabel = this.sequelize.define('ArticleLabel', { id: { primaryKey: true, @@ -1198,31 +1143,35 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.Article.belongsToMany(this.Label, { through: { model: this.ArticleLabel, unique: false } }); this.Label.belongsToMany(this.Article, { through: { model: this.ArticleLabel, unique: false } }); - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ])) - .then(([article, label1, label2]) => Promise.all([ - article, - label1, - label2, - article.addLabel(label1, { - through: { relevance: 1 } - }), - article.addLabel(label2, { - through: { relevance: .54 } - }), - article.addLabel(label2, { - through: { relevance: .99 } - }) - ])) - .then(([article, label1, label2]) => article.hasLabels([label1, label2])) - .then(result => expect(result).to.be.true); + await this.sequelize.sync({ force: true }); + + const [article0, label10, label20] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }) + ]); + + const [article, label1, label2] = await Promise.all([ + article0, + label10, + label20, + article0.addLabel(label10, { + through: { relevance: 1 } + }), + article0.addLabel(label20, { + through: { relevance: .54 } + }), + article0.addLabel(label20, { + through: { relevance: .99 } + }) + ]); + + const result = await article.hasLabels([label1, label2]); + + await expect(result).to.be.true; }); - it('answers true for labels that have been assigned multitple times when passing a primary key instead of an object', function() { + it('answers true for labels that have been assigned multitple times when passing a primary key instead of an object', async function() { this.ArticleLabel = this.sequelize.define('ArticleLabel', { id: { primaryKey: true, @@ -1240,31 +1189,35 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.Article.belongsToMany(this.Label, { through: { model: this.ArticleLabel, unique: false } }); this.Label.belongsToMany(this.Article, { through: { model: this.ArticleLabel, unique: false } }); - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ])) - .then(([article, label1, label2]) => Promise.all([ - article, - label1, - label2, - article.addLabel(label1, { - through: { relevance: 1 } - }), - article.addLabel(label2, { - through: { relevance: .54 } - }), - article.addLabel(label2, { - through: { relevance: .99 } - }) - ])) - .then(([article, label1, label2]) => article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ])) - .then(result => expect(result).to.be.true); + await this.sequelize.sync({ force: true }); + + const [article0, label10, label20] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }) + ]); + + const [article, label1, label2] = await Promise.all([ + article0, + label10, + label20, + article0.addLabel(label10, { + through: { relevance: 1 } + }), + article0.addLabel(label20, { + through: { relevance: .54 } + }), + article0.addLabel(label20, { + through: { relevance: .99 } + }) + ]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + await expect(result).to.be.true; }); }); @@ -1291,39 +1244,45 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('answers true for labels that have been assigned', function() { - return Promise.all([ + it('answers true for labels that have been assigned', async function() { + const [article0, label0] = await Promise.all([ this.Article.create({ id: Buffer.alloc(255) }), this.Label.create({ id: Buffer.alloc(255) }) - ]).then(([article, label]) => Promise.all([ - article, - label, - article.addLabel(label, { + ]); + + const [article, label] = await Promise.all([ + article0, + label0, + article0.addLabel(label0, { through: 'ArticleLabel' }) - ])).then(([article, label]) => article.hasLabels([label])) - .then(result => expect(result).to.be.true); + ]); + + const result = await article.hasLabels([label]); + await expect(result).to.be.true; }); - it('answer false for labels that have not been assigned', function() { - return Promise.all([ + it('answer false for labels that have not been assigned', async function() { + const [article, label] = await Promise.all([ this.Article.create({ id: Buffer.alloc(255) }), this.Label.create({ id: Buffer.alloc(255) }) - ]).then(([article, label]) => article.hasLabels([label])) - .then(result => expect(result).to.be.false); + ]); + + const result = await article.hasLabels([label]); + await expect(result).to.be.false; }); }); describe('countAssociations', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); @@ -1346,17 +1305,18 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.User.belongsToMany(this.Task, { through: this.UserTask }); this.Task.belongsToMany(this.User, { through: this.UserTask }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - this.tasks = [task1, task2]; - this.user = john; - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + this.tasks = [task1, task2]; + this.user = john; + + return john.setTasks([task1, task2]); }); it('should count all associations', async function() { @@ -1401,65 +1361,52 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('setAssociations', () => { - it('clears associations when passing null to the set-method', function() { + it('clears associations when passing null to the set-method', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.task = task; - return task.setUsers([user]); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); + await this.sequelize.sync({ force: true }); - return ctx.task.setUsers(null); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(0); - }); + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await task.setUsers([user]); + const _users0 = await task.getUsers(); + expect(_users0).to.have.length(1); + + await task.setUsers(null); + const _users = await task.getUsers(); + expect(_users).to.have.length(0); }); - it('should be able to set twice with custom primary keys', function() { + it('should be able to set twice with custom primary keys', async function() { const User = this.sequelize.define('User', { uid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: DataTypes.STRING }), Task = this.sequelize.define('Task', { tid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - User.create({ username: 'bar' }), - Task.create({ title: 'task' }) - ]); - }).then(([user1, user2, task]) => { - ctx.task = task; - ctx.user1 = user1; - ctx.user2 = user2; - return task.setUsers([user1]); - }).then(() => { - ctx.user2.user_has_task = { usertitle: 'Something' }; - return ctx.task.setUsers([ctx.user1, ctx.user2]); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(2); - }); - }); - - it('joins an association with custom primary keys', function() { + await this.sequelize.sync({ force: true }); + + const [user1, user2, task] = await Promise.all([ + User.create({ username: 'foo' }), + User.create({ username: 'bar' }), + Task.create({ title: 'task' }) + ]); + + await task.setUsers([user1]); + user2.user_has_task = { usertitle: 'Something' }; + await task.setUsers([user1, user2]); + const _users = await task.getUsers(); + expect(_users).to.have.length(2); + }); + + it('joins an association with custom primary keys', async function() { const Group = this.sequelize.define('group', { group_id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING(64) @@ -1472,52 +1419,45 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(Member, { through: 'group_members', foreignKey: 'group_id', otherKey: 'member_id' }); Member.belongsToMany(Group, { through: 'group_members', foreignKey: 'member_id', otherKey: 'group_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.create({ group_id: 1, name: 'Group1' }), - Member.create({ member_id: 10, email: 'team@sequelizejs.com' }) - ]); - }).then(([group, member]) => { - return group.addMember(member).then(() => group); - }).then(group => { - return group.getMembers(); - }).then(members => { - expect(members).to.be.instanceof(Array); - expect(members).to.have.length(1); - expect(members[0].member_id).to.equal(10); - expect(members[0].email).to.equal('team@sequelizejs.com'); - }); + await this.sequelize.sync({ force: true }); + + const [group0, member] = await Promise.all([ + Group.create({ group_id: 1, name: 'Group1' }), + Member.create({ member_id: 10, email: 'team@sequelizejs.com' }) + ]); + + await group0.addMember(member); + const group = group0; + const members = await group.getMembers(); + expect(members).to.be.instanceof(Array); + expect(members).to.have.length(1); + expect(members[0].member_id).to.equal(10); + expect(members[0].email).to.equal('team@sequelizejs.com'); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 5, title: 'wat' }) - ]); - }).then(([user, task1, task2]) => { - ctx.user = user; - ctx.task2 = task2; - return user.addTask(task1.id); - }).then(() => { - return ctx.user.setTasks([ctx.task2.id]); - }).then(() => { - return ctx.user.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].title).to.equal('wat'); - }); + await this.sequelize.sync({ force: true }); + + const [user, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 5, title: 'wat' }) + ]); + + await user.addTask(task1.id); + await user.setTasks([task2.id]); + const tasks = await user.getTasks(); + expect(tasks).to.have.length(1); + expect(tasks[0].title).to.equal('wat'); }); - it('using scope to set associations', function() { + it('using scope to set associations', async function() { const ItemTag = this.sequelize.define('ItemTag', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, tag_id: { type: DataTypes.INTEGER, unique: false }, @@ -1547,31 +1487,30 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { foreignKey: 'taggable_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Post.create({ name: 'post1' }), - Comment.create({ name: 'comment1' }), - Tag.create({ name: 'tag1' }) - ]); - }).then(([post, comment, tag]) => { - this.post = post; - this.comment = comment; - this.tag = tag; - return this.post.setTags([this.tag]); - }).then(() => { - return this.comment.setTags([this.tag]); - }).then(() => { - return Promise.all([ - this.post.getTags(), - this.comment.getTags() - ]); - }).then(([postTags, commentTags]) => { - expect(postTags).to.have.length(1); - expect(commentTags).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + + const [post, comment, tag] = await Promise.all([ + Post.create({ name: 'post1' }), + Comment.create({ name: 'comment1' }), + Tag.create({ name: 'tag1' }) + ]); + + this.post = post; + this.comment = comment; + this.tag = tag; + await this.post.setTags([this.tag]); + await this.comment.setTags([this.tag]); + + const [postTags, commentTags] = await Promise.all([ + this.post.getTags(), + this.comment.getTags() + ]); + + expect(postTags).to.have.length(1); + expect(commentTags).to.have.length(1); }); - it('updating association via set associations with scope', function() { + it('updating association via set associations with scope', async function() { const ItemTag = this.sequelize.define('ItemTag', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, tag_id: { type: DataTypes.INTEGER, unique: false }, @@ -1601,35 +1540,33 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { foreignKey: 'taggable_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Post.create({ name: 'post1' }), - Comment.create({ name: 'comment1' }), - Tag.create({ name: 'tag1' }), - Tag.create({ name: 'tag2' }) - ]); - }).then(([post, comment, tag, secondTag]) => { - this.post = post; - this.comment = comment; - this.tag = tag; - this.secondTag = secondTag; - return this.post.setTags([this.tag, this.secondTag]); - }).then(() => { - return this.comment.setTags([this.tag, this.secondTag]); - }).then(() => { - return this.post.setTags([this.tag]); - }).then(() => { - return Promise.all([ - this.post.getTags(), - this.comment.getTags() - ]); - }).then(([postTags, commentTags]) => { - expect(postTags).to.have.length(1); - expect(commentTags).to.have.length(2); - }); + await this.sequelize.sync({ force: true }); + + const [post, comment, tag, secondTag] = await Promise.all([ + Post.create({ name: 'post1' }), + Comment.create({ name: 'comment1' }), + Tag.create({ name: 'tag1' }), + Tag.create({ name: 'tag2' }) + ]); + + this.post = post; + this.comment = comment; + this.tag = tag; + this.secondTag = secondTag; + await this.post.setTags([this.tag, this.secondTag]); + await this.comment.setTags([this.tag, this.secondTag]); + await this.post.setTags([this.tag]); + + const [postTags, commentTags] = await Promise.all([ + this.post.getTags(), + this.comment.getTags() + ]); + + expect(postTags).to.have.length(1); + expect(commentTags).to.have.length(2); }); - it('should catch EmptyResultError when rejectOnEmpty is set', function() { + it('should catch EmptyResultError when rejectOnEmpty is set', async function() { const User = this.sequelize.define( 'User', { username: DataTypes.STRING }, @@ -1643,81 +1580,66 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 51, title: 'following up' }) - ]); - }).then(([user, task1, task2]) => { - return user.setTasks([task1, task2]).then(() => user); - }).then(user => { - return user.getTasks(); - }).then(userTasks => { - expect(userTasks).to.be.an('array').that.has.a.lengthOf(2); - expect(userTasks[0]).to.be.an.instanceOf(Task); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 51, title: 'following up' }) + ]); + + await user0.setTasks([task1, task2]); + const user = user0; + const userTasks = await user.getTasks(); + expect(userTasks).to.be.an('array').that.has.a.lengthOf(2); + expect(userTasks[0]).to.be.an.instanceOf(Task); }); }); describe('createAssociations', () => { - it('creates a new associated object', function() { + it('creates a new associated object', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return task.createUser({ username: 'foo' }); - }).then(createdUser => { - expect(createdUser).to.be.instanceof(User); - expect(createdUser.username).to.equal('foo'); - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + const task = await Task.create({ title: 'task' }); + const createdUser = await task.createUser({ username: 'foo' }); + expect(createdUser).to.be.instanceof(User); + expect(createdUser.username).to.equal('foo'); + const _users = await task.getUsers(); + expect(_users).to.have.length(1); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.User = sequelize.define('User', { username: DataTypes.STRING }); - ctx.Task = sequelize.define('Task', { title: DataTypes.STRING }); - - ctx.User.belongsToMany(ctx.Task, { through: 'UserTasks' }); - ctx.Task.belongsToMany(ctx.User, { through: 'UserTasks' }); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Task.create({ title: 'task' }), - ctx.sequelize.transaction() - ]); - }).then(([task, t]) => { - ctx.task = task; - ctx.t = t; - return task.createUser({ username: 'foo' }, { transaction: t }); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(0); - - return ctx.task.getUsers({ transaction: ctx.t }); - }).then(users => { - expect(users).to.have.length(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + await sequelize.sync({ force: true }); + + const [task, t] = await Promise.all([ + Task.create({ title: 'task' }), + sequelize.transaction() + ]); + + await task.createUser({ username: 'foo' }, { transaction: t }); + const users0 = await task.getUsers(); + expect(users0).to.have.length(0); + + const users = await task.getUsers({ transaction: t }); + expect(users).to.have.length(1); + await t.rollback(); }); } - it('supports setting through table attributes', function() { + it('supports setting through table attributes', async function() { const User = this.sequelize.define('user', {}), Group = this.sequelize.define('group', {}), UserGroups = this.sequelize.define('user_groups', { @@ -1727,175 +1649,150 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: UserGroups }); Group.belongsToMany(User, { through: UserGroups }); - return this.sequelize.sync({ force: true }).then(() => { - return Group.create({}); - }).then(group => { - return Promise.all([ - group.createUser({ id: 1 }, { through: { isAdmin: true } }), - group.createUser({ id: 2 }, { through: { isAdmin: false } }) - ]).then(() => { - return UserGroups.findAll(); - }); - }).then(userGroups => { - userGroups.sort((a, b) => { - return a.userId < b.userId ? - 1 : 1; - }); - expect(userGroups[0].userId).to.equal(1); - expect(userGroups[0].isAdmin).to.be.ok; - expect(userGroups[1].userId).to.equal(2); - expect(userGroups[1].isAdmin).not.to.be.ok; + await this.sequelize.sync({ force: true }); + const group = await Group.create({}); + + await Promise.all([ + group.createUser({ id: 1 }, { through: { isAdmin: true } }), + group.createUser({ id: 2 }, { through: { isAdmin: false } }) + ]); + + const userGroups = await UserGroups.findAll(); + userGroups.sort((a, b) => { + return a.userId < b.userId ? - 1 : 1; }); + expect(userGroups[0].userId).to.equal(1); + expect(userGroups[0].isAdmin).to.be.ok; + expect(userGroups[1].userId).to.equal(2); + expect(userGroups[1].isAdmin).not.to.be.ok; }); - it('supports using the field parameter', function() { + it('supports using the field parameter', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return task.createUser({ username: 'foo' }, { fields: ['username'] }); - }).then(createdUser => { - expect(createdUser).to.be.instanceof(User); - expect(createdUser.username).to.equal('foo'); - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + const task = await Task.create({ title: 'task' }); + const createdUser = await task.createUser({ username: 'foo' }, { fields: ['username'] }); + expect(createdUser).to.be.instanceof(User); + expect(createdUser.username).to.equal('foo'); + const _users = await task.getUsers(); + expect(_users).to.have.length(1); }); }); describe('addAssociations', () => { - it('supports both single instance and array', function() { + it('supports both single instance and array', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 52, title: 'get done' }) - ]); - }).then(([user, task1, task2]) => { - return Promise.all([ - user.addTask(task1), - user.addTask([task2]) - ]).then(() => user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - expect(tasks.find(item => item.title === 'get started')).to.be.ok; - expect(tasks.find(item => item.title === 'get done')).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 52, title: 'get done' }) + ]); + + await Promise.all([ + user0.addTask(task1), + user0.addTask([task2]) + ]); + + const user = user0; + const tasks = await user.getTasks(); + expect(tasks).to.have.length(2); + expect(tasks.find(item => item.title === 'get started')).to.be.ok; + expect(tasks.find(item => item.title === 'get done')).to.be.ok; }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.User = sequelize.define('User', { username: DataTypes.STRING }); - ctx.Task = sequelize.define('Task', { title: DataTypes.STRING }); - - ctx.User.belongsToMany(ctx.Task, { through: 'UserTasks' }); - ctx.Task.belongsToMany(ctx.User, { through: 'UserTasks' }); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.User.create({ username: 'foo' }), - ctx.Task.create({ title: 'task' }), - ctx.sequelize.transaction() - ]); - }).then(([user, task, t]) => { - ctx.task = task; - ctx.user = user; - ctx.t = t; - return task.addUser(user, { transaction: t }); - }).then(() => { - return ctx.task.hasUser(ctx.user); - }).then(hasUser => { - expect(hasUser).to.be.false; - return ctx.task.hasUser(ctx.user, { transaction: ctx.t }); - }).then(hasUser => { - expect(hasUser).to.be.true; - return ctx.t.rollback(); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); - it('supports transactions when updating a through model', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.User = sequelize.define('User', { username: DataTypes.STRING }); - ctx.Task = sequelize.define('Task', { title: DataTypes.STRING }); + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); - ctx.UserTask = sequelize.define('UserTask', { - status: Sequelize.STRING - }); + await sequelize.sync({ force: true }); - ctx.User.belongsToMany(ctx.Task, { through: ctx.UserTask }); - ctx.Task.belongsToMany(ctx.User, { through: ctx.UserTask }); - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.User.create({ username: 'foo' }), - ctx.Task.create({ title: 'task' }), - ctx.sequelize.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED }) - ]); - }).then(([user, task, t]) => { - ctx.task = task; - ctx.user = user; - ctx.t = t; - return task.addUser(user, { through: { status: 'pending' } }); // Create without transaction, so the old value is accesible from outside the transaction - }).then(() => { - return ctx.task.addUser(ctx.user, { transaction: ctx.t, through: { status: 'completed' } }); // Add an already exisiting user in a transaction, updating a value in the join table - }).then(() => { - return Promise.all([ - ctx.user.getTasks(), - ctx.user.getTasks({ transaction: ctx.t }) - ]); - }).then(([tasks, transactionTasks]) => { - expect(tasks[0].UserTask.status).to.equal('pending'); - expect(transactionTasks[0].UserTask.status).to.equal('completed'); + const [user, task, t] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }), + sequelize.transaction() + ]); + + await task.addUser(user, { transaction: t }); + const hasUser0 = await task.hasUser(user); + expect(hasUser0).to.be.false; + const hasUser = await task.hasUser(user, { transaction: t }); + expect(hasUser).to.be.true; + await t.rollback(); + }); + + it('supports transactions when updating a through model', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); - return ctx.t.rollback(); + const UserTask = sequelize.define('UserTask', { + status: Sequelize.STRING }); + + User.belongsToMany(Task, { through: UserTask }); + Task.belongsToMany(User, { through: UserTask }); + await sequelize.sync({ force: true }); + + const [user, task, t] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }), + sequelize.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED }) + ]); + + await task.addUser(user, { through: { status: 'pending' } }); // Create without transaction, so the old value is accesible from outside the transaction + await task.addUser(user, { transaction: t, through: { status: 'completed' } }); // Add an already exisiting user in a transaction, updating a value in the join table + + const [tasks, transactionTasks] = await Promise.all([ + user.getTasks(), + user.getTasks({ transaction: t }) + ]); + + expect(tasks[0].UserTask.status).to.equal('pending'); + expect(transactionTasks[0].UserTask.status).to.equal('completed'); + + await t.rollback(); }); } - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }) - ]); - }).then(([user, task]) => { - return user.addTask(task.id).then(() => user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks[0].title).to.equal('get started'); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }) + ]); + + await user0.addTask(task.id); + const user = user0; + const tasks = await user.getTasks(); + expect(tasks[0].title).to.equal('get started'); }); - it('should not pass indexes to the join table', function() { + it('should not pass indexes to the join table', async function() { const User = this.sequelize.define( 'User', { username: DataTypes.STRING }, @@ -1924,10 +1821,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { //create associations User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should catch EmptyResultError when rejectOnEmpty is set', function() { + it('should catch EmptyResultError when rejectOnEmpty is set', async function() { const User = this.sequelize.define( 'User', { username: DataTypes.STRING }, @@ -1941,21 +1838,20 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }) - ]); - }).then(([user, task]) => { - return user.addTask(task).then(() => user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks[0].title).to.equal('get started'); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }) + ]); + + await user0.addTask(task); + const user = user0; + const tasks = await user.getTasks(); + expect(tasks[0].title).to.equal('get started'); }); - it('should returns array of intermediate table', function() { + it('should returns array of intermediate table', async function() { const User = this.sequelize.define('User'); const Task = this.sequelize.define('Task'); const UserTask = this.sequelize.define('UserTask'); @@ -1963,94 +1859,87 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Task, { through: UserTask }); Task.belongsToMany(User, { through: UserTask }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create(), - Task.create() - ]).then(([user, task]) => { - return user.addTask(task); - }).then(userTasks => { - expect(userTasks).to.be.an('array').that.has.a.lengthOf(1); - expect(userTasks[0]).to.be.an.instanceOf(UserTask); - }); - }); + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create(), + Task.create() + ]); + + const userTasks = await user.addTask(task); + expect(userTasks).to.be.an('array').that.has.a.lengthOf(1); + expect(userTasks[0]).to.be.an.instanceOf(UserTask); }); }); describe('addMultipleAssociations', () => { - it('supports both single instance and array', function() { + it('supports both single instance and array', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 52, title: 'get done' }) - ]); - }).then(([user, task1, task2]) => { - return Promise.all([ - user.addTasks(task1), - user.addTasks([task2]) - ]).then(() => user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - expect(tasks.some(item => { return item.title === 'get started'; })).to.be.ok; - expect(tasks.some(item => { return item.title === 'get done'; })).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 52, title: 'get done' }) + ]); + + await Promise.all([ + user0.addTasks(task1), + user0.addTasks([task2]) + ]); + + const user = user0; + const tasks = await user.getTasks(); + expect(tasks).to.have.length(2); + expect(tasks.some(item => { return item.title === 'get started'; })).to.be.ok; + expect(tasks.some(item => { return item.title === 'get done'; })).to.be.ok; }); - it('adds associations without removing the current ones', function() { + it('adds associations without removing the current ones', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'foo ' }, - { username: 'bar ' }, - { username: 'baz ' } - ]).then(() => { - return Promise.all([ - Task.create({ title: 'task' }), - User.findAll() - ]); - }).then(([task, users]) => { - ctx.task = task; - ctx.users = users; - return task.setUsers([users[0]]); - }).then(() => { - return ctx.task.addUsers([ctx.users[1], ctx.users[2]]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(3); - - // Re-add user 0's object, this should be harmless - // Re-add user 0's id, this should be harmless - return Promise.all([ - expect(ctx.task.addUsers([ctx.users[0]])).not.to.be.rejected, - expect(ctx.task.addUsers([ctx.users[0].id])).not.to.be.rejected - ]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(3); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([ + { username: 'foo ' }, + { username: 'bar ' }, + { username: 'baz ' } + ]); + + const [task, users1] = await Promise.all([ + Task.create({ title: 'task' }), + User.findAll() + ]); + + const users = users1; + await task.setUsers([users1[0]]); + await task.addUsers([users[1], users[2]]); + const users0 = await task.getUsers(); + expect(users0).to.have.length(3); + + // Re-add user 0's object, this should be harmless + // Re-add user 0's id, this should be harmless + + await Promise.all([ + expect(task.addUsers([users[0]])).not.to.be.rejected, + expect(task.addUsers([users[0].id])).not.to.be.rejected + ]); + + expect(await task.getUsers()).to.have.length(3); }); }); describe('through model validations', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: Sequelize.STRING }); @@ -2075,27 +1964,27 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Project.belongsToMany(Employee, { as: 'Participants', through: Participation }); Employee.belongsToMany(Project, { as: 'Participations', through: Participation }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Project.create({ name: 'project 1' }), - Employee.create({ name: 'employee 1' }) - ]).then(([project, employee]) => { - this.project = project; - this.employee = employee; - }); - }); + await this.sequelize.sync({ force: true }); + + const [project, employee] = await Promise.all([ + Project.create({ name: 'project 1' }), + Employee.create({ name: 'employee 1' }) + ]); + + this.project = project; + this.employee = employee; }); - it('runs on add', function() { - return expect(this.project.addParticipant(this.employee, { through: { role: '' } })).to.be.rejected; + it('runs on add', async function() { + await expect(this.project.addParticipant(this.employee, { through: { role: '' } })).to.be.rejected; }); - it('runs on set', function() { - return expect(this.project.setParticipants([this.employee], { through: { role: '' } })).to.be.rejected; + it('runs on set', async function() { + await expect(this.project.setParticipants([this.employee], { through: { role: '' } })).to.be.rejected; }); - it('runs on create', function() { - return expect(this.project.createParticipant({ name: 'employee 2' }, { through: { role: '' } })).to.be.rejected; + it('runs on create', async function() { + await expect(this.project.createParticipant({ name: 'employee 2' }, { through: { role: '' } })).to.be.rejected; }); }); @@ -2110,38 +1999,39 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('uses one insert into statement', function() { + it('uses one insert into statement', async function() { const spy = sinon.spy(); - return Promise.all([ + const [user, task1, task2] = await Promise.all([ this.User.create({ username: 'foo' }), this.Task.create({ id: 12, title: 'task1' }), this.Task.create({ id: 15, title: 'task2' }) - ]).then(([user, task1, task2]) => { - return user.setTasks([task1, task2], { - logging: spy - }); - }).then(() => { - expect(spy.calledTwice).to.be.ok; + ]); + + await user.setTasks([task1, task2], { + logging: spy }); + + expect(spy.calledTwice).to.be.ok; }); - it('uses one delete from statement', function() { + it('uses one delete from statement', async function() { const spy = sinon.spy(); - return Promise.all([ + const [user0, task1, task2] = await Promise.all([ this.User.create({ username: 'foo' }), this.Task.create({ title: 'task1' }), this.Task.create({ title: 'task2' }) - ]).then(([user, task1, task2]) => { - return user.setTasks([task1, task2]).then(() => user); - }).then(user => { - return user.setTasks(null, { - logging: spy - }); - }).then(() => { - expect(spy.calledTwice).to.be.ok; + ]); + + await user0.setTasks([task1, task2]); + const user = user0; + + await user.setTasks(null, { + logging: spy }); + + expect(spy.calledTwice).to.be.ok; }); }); // end optimization using bulk create, destroy and update @@ -2162,7 +2052,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('should work with non integer primary keys', function() { + it('should work with non integer primary keys', async function() { const Beacons = this.sequelize.define('Beacon', { id: { primaryKey: true, @@ -2184,7 +2074,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Beacons.belongsToMany(Users, { through: 'UserBeacons' }); Users.belongsToMany(Beacons, { through: 'UserBeacons' }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); it('makes join table non-paranoid by default', () => { @@ -2330,75 +2220,81 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); }); - it('should correctly get associations even after a child instance is deleted', function() { + it('should correctly get associations even after a child instance is deleted', async function() { const spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ name: 'Matt' }), - this.Project.create({ name: 'Good Will Hunting' }), - this.Project.create({ name: 'The Departed' }) - ]); - }).then(([user, project1, project2]) => { - return user.addProjects([project1, project2], { - logging: spy - }).then(() => user); - }).then(user => { - expect(spy).to.have.been.calledTwice; - spy.resetHistory(); - return Promise.all([user, user.getProjects({ - logging: spy - })]); - }).then(([user, projects]) => { - expect(spy.calledOnce).to.be.ok; - const project = projects[0]; - expect(project).to.be.ok; - return project.destroy().then(() => user); - }).then(user => { - return this.User.findOne({ - where: { id: user.id }, - include: [{ model: this.Project, as: 'Projects' }] - }); - }).then(user => { - const projects = user.Projects, - project = projects[0]; + await this.sequelize.sync({ force: true }); + + const [user3, project1, project2] = await Promise.all([ + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }), + this.Project.create({ name: 'The Departed' }) + ]); + + await user3.addProjects([project1, project2], { + logging: spy + }); + + const user2 = user3; + expect(spy).to.have.been.calledTwice; + spy.resetHistory(); - expect(project).to.be.ok; + const [user1, projects0] = await Promise.all([user2, user2.getProjects({ + logging: spy + })]); + + expect(spy.calledOnce).to.be.ok; + const project0 = projects0[0]; + expect(project0).to.be.ok; + await project0.destroy(); + const user0 = user1; + + const user = await this.User.findOne({ + where: { id: user0.id }, + include: [{ model: this.Project, as: 'Projects' }] }); + + const projects = user.Projects, + project = projects[0]; + + expect(project).to.be.ok; }); - it('should correctly get associations when doubly linked', function() { + it('should correctly get associations when doubly linked', async function() { const spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ name: 'Matt' }), - this.Project.create({ name: 'Good Will Hunting' }) - ]); - }).then(([user, project]) => { - this.user = user; - this.project = project; - return user.addProject(project, { logging: spy }).then(() => user); - }).then(user => { - expect(spy.calledTwice).to.be.ok; // Once for SELECT, once for INSERT - spy.resetHistory(); - return user.getProjects({ - logging: spy - }); - }).then(projects => { - const project = projects[0]; - expect(spy.calledOnce).to.be.ok; - spy.resetHistory(); + await this.sequelize.sync({ force: true }); + + const [user0, project0] = await Promise.all([ + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }) + ]); - expect(project).to.be.ok; - return this.user.removeProject(project, { - logging: spy - }).then(() => project); - }).then(() => { - expect(spy).to.have.been.calledOnce; + this.user = user0; + this.project = project0; + await user0.addProject(project0, { logging: spy }); + const user = user0; + expect(spy.calledTwice).to.be.ok; // Once for SELECT, once for INSERT + spy.resetHistory(); + + const projects = await user.getProjects({ + logging: spy + }); + + const project = projects[0]; + expect(spy.calledOnce).to.be.ok; + spy.resetHistory(); + + expect(project).to.be.ok; + + await this.user.removeProject(project, { + logging: spy }); + + await project; + expect(spy).to.have.been.calledOnce; }); - it('should be able to handle nested includes properly', function() { + it('should be able to handle nested includes properly', async function() { this.Group = this.sequelize.define('Group', { groupName: DataTypes.STRING }); this.Group.belongsToMany(this.User, { @@ -2426,41 +2322,41 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Group.create({ groupName: 'The Illuminati' }), - this.User.create({ name: 'Matt' }), - this.Project.create({ name: 'Good Will Hunting' }) - ]); - }).then(([group, user, project]) => { - return user.addProject(project).then(() => { - return group.addUser(user).then(() => group); - }); - }).then(group => { - // get the group and include both the users in the group and their project's - return this.Group.findAll({ - where: { id: group.id }, - include: [ - { - model: this.User, - as: 'Users', - include: [ - { model: this.Project, as: 'Projects' } - ] - } - ] - }); - }).then(groups => { - const group = groups[0]; - expect(group).to.be.ok; + await this.sequelize.sync({ force: true }); - const user = group.Users[0]; - expect(user).to.be.ok; + const [group1, user0, project0] = await Promise.all([ + this.Group.create({ groupName: 'The Illuminati' }), + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }) + ]); - const project = user.Projects[0]; - expect(project).to.be.ok; - expect(project.name).to.equal('Good Will Hunting'); + await user0.addProject(project0); + await group1.addUser(user0); + const group0 = group1; + + // get the group and include both the users in the group and their project's + const groups = await this.Group.findAll({ + where: { id: group0.id }, + include: [ + { + model: this.User, + as: 'Users', + include: [ + { model: this.Project, as: 'Projects' } + ] + } + ] }); + + const group = groups[0]; + expect(group).to.be.ok; + + const user = group.Users[0]; + expect(user).to.be.ok; + + const project = user.Projects[0]; + expect(project).to.be.ok; + expect(project.name).to.equal('Good Will Hunting'); }); }); @@ -2519,15 +2415,16 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('without sync', () => { - beforeEach(function() { - return this.sequelize.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }).then(() => { - return this.sequelize.queryInterface.createTable('tasks', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); - }).then(() => { - return this.sequelize.queryInterface.createTable('users_tasks', { TaskId: DataTypes.INTEGER, UserId: DataTypes.INTEGER, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); - }); + beforeEach(async function() { + await this.sequelize.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); + await this.sequelize.queryInterface.createTable('tasks', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); + return this.sequelize.queryInterface.createTable( + 'users_tasks', + { TaskId: DataTypes.INTEGER, UserId: DataTypes.INTEGER, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE } + ); }); - it('removes all associations', function() { + it('removes all associations', async function() { this.UsersTasks = this.sequelize.define('UsersTasks', {}, { tableName: 'users_tasks' }); this.User.belongsToMany(this.Task, { through: this.UsersTasks }); @@ -2535,16 +2432,15 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { expect(Object.keys(this.UsersTasks.primaryKeys).sort()).to.deep.equal(['TaskId', 'UserId']); - return Promise.all([ + const [user0, task] = await Promise.all([ this.User.create({ username: 'foo' }), this.Task.create({ title: 'foo' }) - ]).then(([user, task]) => { - return user.addTask(task).then(() => user); - }).then(user => { - return user.setTasks(null); - }).then(result => { - expect(result).to.be.ok; - }); + ]); + + await user0.addTask(task); + const user = user0; + const result = await user.setTasks(null); + expect(result).to.be.ok; }); }); }); @@ -2565,111 +2461,94 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('fetching from join table', () => { - it('should contain the data from the join table on .UserProjects a DAO', function() { - return Promise.all([ + it('should contain the data from the join table on .UserProjects a DAO', async function() { + const [user0, project0] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).then(() => user); - }).then(user => { - return user.getProjects(); - }).then(projects => { - const project = projects[0]; - - expect(project.UserProjects).to.be.ok; - expect(project.status).not.to.exist; - expect(project.UserProjects.status).to.equal('active'); - expect(project.UserProjects.data).to.equal(42); - }); + ]); + + await user0.addProject(project0, { through: { status: 'active', data: 42 } }); + const user = user0; + const projects = await user.getProjects(); + const project = projects[0]; + + expect(project.UserProjects).to.be.ok; + expect(project.status).not.to.exist; + expect(project.UserProjects.status).to.equal('active'); + expect(project.UserProjects.data).to.equal(42); }); - it('should be able to alias the default name of the join table', function() { - return Promise.all([ + it('should be able to alias the default name of the join table', async function() { + const [user, project0] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).then(() => user); - }).then(() => { - return this.User.findAll({ - include: [{ - model: this.Project, - through: { - as: 'myProject' - } - }] - }); - }).then(users => { - const project = users[0].Projects[0]; - - expect(project.UserProjects).not.to.exist; - expect(project.status).not.to.exist; - expect(project.myProject).to.be.ok; - expect(project.myProject.status).to.equal('active'); - expect(project.myProject.data).to.equal(42); + ]); + + await user.addProject(project0, { through: { status: 'active', data: 42 } }); + + const users = await this.User.findAll({ + include: [{ + model: this.Project, + through: { + as: 'myProject' + } + }] }); + + const project = users[0].Projects[0]; + + expect(project.UserProjects).not.to.exist; + expect(project.status).not.to.exist; + expect(project.myProject).to.be.ok; + expect(project.myProject.status).to.equal('active'); + expect(project.myProject.data).to.equal(42); }); - it('should be able to limit the join table attributes returned', function() { - return Promise.all([ + it('should be able to limit the join table attributes returned', async function() { + const [user0, project0] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).then(() => user); - }).then(user => { - return user.getProjects({ joinTableAttributes: ['status'] }); - }).then(projects => { - const project = projects[0]; - - expect(project.UserProjects).to.be.ok; - expect(project.status).not.to.exist; - expect(project.UserProjects.status).to.equal('active'); - expect(project.UserProjects.data).not.to.exist; - }); + ]); + + await user0.addProject(project0, { through: { status: 'active', data: 42 } }); + const user = user0; + const projects = await user.getProjects({ joinTableAttributes: ['status'] }); + const project = projects[0]; + + expect(project.UserProjects).to.be.ok; + expect(project.status).not.to.exist; + expect(project.UserProjects.status).to.equal('active'); + expect(project.UserProjects.data).not.to.exist; }); }); describe('inserting in join table', () => { describe('add', () => { - it('should insert data provided on the object into the join table', function() { - const ctx = { - UserProjects: this.UserProjects - }; - return Promise.all([ + it('should insert data provided on the object into the join table', async function() { + const [u, p] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([u, p]) => { - ctx.u = u; - ctx.p = p; - p.UserProjects = { status: 'active' }; - - return u.addProject(p); - }).then(() => { - return ctx.UserProjects.findOne({ where: { UserId: ctx.u.id, ProjectId: ctx.p.id } }); - }).then(up => { - expect(up.status).to.equal('active'); - }); + ]); + + p.UserProjects = { status: 'active' }; + + await u.addProject(p); + const up = await this.UserProjects.findOne({ where: { UserId: u.id, ProjectId: p.id } }); + expect(up.status).to.equal('active'); }); - it('should insert data provided as a second argument into the join table', function() { - const ctx = { - UserProjects: this.UserProjects - }; - return Promise.all([ + it('should insert data provided as a second argument into the join table', async function() { + const [u, p] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([u, p]) => { - ctx.u = u; - ctx.p = p; - - return u.addProject(p, { through: { status: 'active' } }); - }).then(() => { - return ctx.UserProjects.findOne({ where: { UserId: ctx.u.id, ProjectId: ctx.p.id } }); - }).then(up => { - expect(up.status).to.equal('active'); - }); + ]); + + await u.addProject(p, { through: { status: 'active' } }); + const up = await this.UserProjects.findOne({ where: { UserId: u.id, ProjectId: p.id } }); + expect(up.status).to.equal('active'); }); - it('should be able to add twice (second call result in UPDATE call) without any attributes (and timestamps off) on the through model', function() { + it('should be able to add twice (second call result in UPDATE call) without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2677,20 +2556,14 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Worker.belongsToMany(Task, { through: WorkerTasks }); Task.belongsToMany(Worker, { through: WorkerTasks }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Worker.create({ id: 1337 }); - }).then(worker => { - ctx.worker = worker; - return Task.create({ id: 7331 }); - }).then(() => { - return ctx.worker.addTask(ctx.task); - }).then(() => { - return ctx.worker.addTask(ctx.task); - }); + await this.sequelize.sync({ force: true }); + const worker = await Worker.create({ id: 1337 }); + const task = await Task.create({ id: 7331 }); + await worker.addTask(task); + await worker.addTask(task); }); - it('should be able to add twice (second call result in UPDATE call) with custom primary keys and without any attributes (and timestamps off) on the through model', function() { + it('should be able to add twice (second call result in UPDATE call) with custom primary keys and without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', { id: { type: DataTypes.INTEGER, @@ -2719,87 +2592,77 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Worker.belongsToMany(Task, { through: WorkerTasks }); Task.belongsToMany(Worker, { through: WorkerTasks }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Worker.create({ id: 1337 }); - }).then(worker => { - ctx.worker = worker; - return Task.create({ id: 7331 }); - }).then(task => { - ctx.task = task; - return ctx.worker.addTask(ctx.task); - }).then(() => { - return ctx.worker.addTask(ctx.task); - }); + await this.sequelize.sync({ force: true }); + const worker = await Worker.create({ id: 1337 }); + const task = await Task.create({ id: 7331 }); + await worker.addTask(task); + await worker.addTask(task); }); - it('should be able to create an instance along with its many-to-many association which has an extra column in the junction table', function() { + it('should be able to create an instance along with its many-to-many association which has an extra column in the junction table', async function() { const Foo = this.sequelize.define('foo', { name: Sequelize.STRING }); const Bar = this.sequelize.define('bar', { name: Sequelize.STRING }); const FooBar = this.sequelize.define('foobar', { baz: Sequelize.STRING }); Foo.belongsToMany(Bar, { through: FooBar }); Bar.belongsToMany(Foo, { through: FooBar }); - return this.sequelize.sync({ force: true }).then(() => { - return Foo.create({ - name: 'foo...', - bars: [ - { - name: 'bar...', - foobar: { - baz: 'baz...' - } + await this.sequelize.sync({ force: true }); + + const foo0 = await Foo.create({ + name: 'foo...', + bars: [ + { + name: 'bar...', + foobar: { + baz: 'baz...' } - ] - }, { - include: Bar - }); - }).then(foo => { - expect(foo.name).to.equal('foo...'); - expect(foo.bars).to.have.length(1); - expect(foo.bars[0].name).to.equal('bar...'); - expect(foo.bars[0].foobar).to.not.equal(null); - expect(foo.bars[0].foobar.baz).to.equal('baz...'); - - return Foo.findOne({ include: Bar }); - }).then(foo => { - expect(foo.name).to.equal('foo...'); - expect(foo.bars).to.have.length(1); - expect(foo.bars[0].name).to.equal('bar...'); - expect(foo.bars[0].foobar).to.not.equal(null); - expect(foo.bars[0].foobar.baz).to.equal('baz...'); + } + ] + }, { + include: Bar }); + + expect(foo0.name).to.equal('foo...'); + expect(foo0.bars).to.have.length(1); + expect(foo0.bars[0].name).to.equal('bar...'); + expect(foo0.bars[0].foobar).to.not.equal(null); + expect(foo0.bars[0].foobar.baz).to.equal('baz...'); + + const foo = await Foo.findOne({ include: Bar }); + expect(foo.name).to.equal('foo...'); + expect(foo.bars).to.have.length(1); + expect(foo.bars[0].name).to.equal('bar...'); + expect(foo.bars[0].foobar).to.not.equal(null); + expect(foo.bars[0].foobar.baz).to.equal('baz...'); }); }); describe('set', () => { - it('should be able to combine properties on the associated objects, and default values', function() { - const ctx = {}; - return Promise.all([ + it('should be able to combine properties on the associated objects, and default values', async function() { + await this.Project.bulkCreate([{}, {}]); + + const [user, projects] = await Promise.all([ this.User.create(), - this.Project.bulkCreate([{}, {}]).then(() => { - return this.Project.findAll(); - }) - ]).then(([user, projects]) => { - ctx.user = user; - ctx.p1 = projects[0]; - ctx.p2 = projects[1]; - - ctx.p1.UserProjects = { status: 'inactive' }; - - return user.setProjects([ctx.p1, ctx.p2], { through: { status: 'active' } }); - }).then(() => { - return Promise.all([ - this.UserProjects.findOne({ where: { UserId: ctx.user.id, ProjectId: ctx.p1.id } }), - this.UserProjects.findOne({ where: { UserId: ctx.user.id, ProjectId: ctx.p2.id } }) - ]); - }).then(([up1, up2]) => { - expect(up1.status).to.equal('inactive'); - expect(up2.status).to.equal('active'); - }); + await this.Project.findAll() + ]); + + const p1 = projects[0]; + const p2 = projects[1]; + + p1.UserProjects = { status: 'inactive' }; + + await user.setProjects([p1, p2], { through: { status: 'active' } }); + + const [up1, up2] = await Promise.all([ + this.UserProjects.findOne({ where: { UserId: user.id, ProjectId: p1.id } }), + this.UserProjects.findOne({ where: { UserId: user.id, ProjectId: p2.id } }) + ]); + + expect(up1.status).to.equal('inactive'); + expect(up2.status).to.equal('active'); }); - it('should be able to set twice (second call result in UPDATE calls) without any attributes (and timestamps off) on the through model', function() { + it('should be able to set twice (second call result in UPDATE calls) without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2807,44 +2670,45 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Worker.belongsToMany(Task, { through: WorkerTasks }); Task.belongsToMany(Worker, { through: WorkerTasks }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Worker.create(), - Task.bulkCreate([{}, {}]).then(() => { - return Task.findAll(); - }) - ]); - }).then(([worker, tasks]) => { - return worker.setTasks(tasks).then(() => [worker, tasks]); - }).then(([worker, tasks]) => { - return worker.setTasks(tasks); - }); + await this.sequelize.sync({ force: true }); + + const [worker0, tasks0] = await Promise.all([ + Worker.create(), + Task.bulkCreate([{}, {}]).then(() => { + return Task.findAll(); + }) + ]); + + await worker0.setTasks(tasks0); + const [worker, tasks] = [worker0, tasks0]; + + await worker.setTasks(tasks); }); }); describe('query with through.where', () => { - it('should support query the through model', function() { - return this.User.create().then(user => { - return Promise.all([ - user.createProject({}, { through: { status: 'active', data: 1 } }), - user.createProject({}, { through: { status: 'inactive', data: 2 } }), - user.createProject({}, { through: { status: 'inactive', data: 3 } }) - ]).then(() => { - return Promise.all([ - user.getProjects({ through: { where: { status: 'active' } } }), - user.countProjects({ through: { where: { status: 'inactive' } } }) - ]); - }); - }).then(([activeProjects, inactiveProjectCount]) => { - expect(activeProjects).to.have.lengthOf(1); - expect(inactiveProjectCount).to.eql(2); - }); + it('should support query the through model', async function() { + const user = await this.User.create(); + + await Promise.all([ + user.createProject({}, { through: { status: 'active', data: 1 } }), + user.createProject({}, { through: { status: 'inactive', data: 2 } }), + user.createProject({}, { through: { status: 'inactive', data: 3 } }) + ]); + + const [activeProjects, inactiveProjectCount] = await Promise.all([ + user.getProjects({ through: { where: { status: 'active' } } }), + user.countProjects({ through: { where: { status: 'inactive' } } }) + ]); + + expect(activeProjects).to.have.lengthOf(1); + expect(inactiveProjectCount).to.eql(2); }); }); }); describe('removing from the join table', () => { - it('should remove a single entry without any attributes (and timestamps off) on the through model', function() { + it('should remove a single entry without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2853,28 +2717,25 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.belongsToMany(Worker, { through: WorkerTasks }); // Test setup - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Worker.create({}), - Task.bulkCreate([{}, {}, {}]).then(() => { - return Task.findAll(); - }) - ]); - }).then(([worker, tasks]) => { - // Set all tasks, then remove one task by instance, then remove one task by id, then return all tasks - return worker.setTasks(tasks).then(() => { - return worker.removeTask(tasks[0]); - }).then(() => { - return worker.removeTask(tasks[1].id); - }).then(() => { - return worker.getTasks(); - }); - }).then(tasks => { - expect(tasks.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + + const [worker, tasks0] = await Promise.all([ + Worker.create({}), + Task.bulkCreate([{}, {}, {}]).then(() => { + return Task.findAll(); + }) + ]); + + // Set all tasks, then remove one task by instance, then remove one task by id, then return all tasks + await worker.setTasks(tasks0); + + await worker.removeTask(tasks0[0]); + await worker.removeTask(tasks0[1].id); + const tasks = await worker.getTasks(); + expect(tasks.length).to.equal(1); }); - it('should remove multiple entries without any attributes (and timestamps off) on the through model', function() { + it('should remove multiple entries without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2883,25 +2744,22 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.belongsToMany(Worker, { through: WorkerTasks }); // Test setup - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Worker.create({}), - Task.bulkCreate([{}, {}, {}, {}, {}]).then(() => { - return Task.findAll(); - }) - ]); - }).then(([worker, tasks]) => { - // Set all tasks, then remove two tasks by instance, then remove two tasks by id, then return all tasks - return worker.setTasks(tasks).then(() => { - return worker.removeTasks([tasks[0], tasks[1]]); - }).then(() => { - return worker.removeTasks([tasks[2].id, tasks[3].id]); - }).then(() => { - return worker.getTasks(); - }); - }).then(tasks => { - expect(tasks.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + + const [worker, tasks0] = await Promise.all([ + Worker.create({}), + Task.bulkCreate([{}, {}, {}, {}, {}]).then(() => { + return Task.findAll(); + }) + ]); + + // Set all tasks, then remove two tasks by instance, then remove two tasks by id, then return all tasks + await worker.setTasks(tasks0); + + await worker.removeTasks([tasks0[0], tasks0[1]]); + await worker.removeTasks([tasks0[2].id, tasks0[3].id]); + const tasks = await worker.getTasks(); + expect(tasks.length).to.equal(1); }); }); }); @@ -2921,18 +2779,17 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('correctly uses bId in A', function() { + it('correctly uses bId in A', async function() { const a1 = this.A.build({ name: 'a1' }), b1 = this.B.build({ name: 'b1' }); - return a1 - .save() - .then(() => { return b1.save(); }) - .then(() => { return a1.setRelation1(b1); }) - .then(() => { return this.A.findOne({ where: { name: 'a1' } }); }) - .then(a => { - expect(a.relation1Id).to.be.eq(b1.id); - }); + await a1 + .save(); + + await b1.save(); + await a1.setRelation1(b1); + const a = await this.A.findOne({ where: { name: 'a1' } }); + expect(a.relation1Id).to.be.eq(b1.id); }); }); @@ -2945,42 +2802,39 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('correctly uses bId in A', function() { + it('correctly uses bId in A', async function() { const a1 = this.A.build({ name: 'a1' }), b1 = this.B.build({ name: 'b1' }); - return a1 - .save() - .then(() => { return b1.save(); }) - .then(() => { return b1.setRelation1(a1); }) - .then(() => { return this.B.findOne({ where: { name: 'b1' } }); }) - .then(b => { - expect(b.relation1Id).to.be.eq(a1.id); - }); + await a1 + .save(); + + await b1.save(); + await b1.setRelation1(a1); + const b = await this.B.findOne({ where: { name: 'b1' } }); + expect(b.relation1Id).to.be.eq(a1.id); }); }); }); describe('alias', () => { - it('creates the join table when through is a string', function() { + it('creates the join table when through is a string', async function() { const User = this.sequelize.define('User', {}); const Group = this.sequelize.define('Group', {}); User.belongsToMany(Group, { as: 'MyGroups', through: 'group_user' }); Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user' }); - return this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showAllTables(); - }).then(result => { - if (dialect === 'mssql' || dialect === 'mariadb') { - result = result.map(v => v.tableName); - } + await this.sequelize.sync({ force: true }); + let result = await this.sequelize.getQueryInterface().showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + result = result.map(v => v.tableName); + } - expect(result.includes('group_user')).to.be.true; - }); + expect(result.includes('group_user')).to.be.true; }); - it('creates the join table when through is a model', function() { + it('creates the join table when through is a model', async function() { const User = this.sequelize.define('User', {}); const Group = this.sequelize.define('Group', {}); const UserGroup = this.sequelize.define('GroupUser', {}, { tableName: 'user_groups' }); @@ -2988,15 +2842,13 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup }); Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup }); - return this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showAllTables(); - }).then(result => { - if (dialect === 'mssql' || dialect === 'mariadb') { - result = result.map(v => v.tableName); - } + await this.sequelize.sync({ force: true }); + let result = await this.sequelize.getQueryInterface().showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + result = result.map(v => v.tableName); + } - expect(result).to.include('user_groups'); - }); + expect(result).to.include('user_groups'); }); it('correctly identifies its counterpart when through is a string', function() { @@ -3043,17 +2895,19 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('correctly sets user and owner', function() { + it('correctly sets user and owner', async function() { const p1 = this.Project.build({ projectName: 'p1' }), u1 = this.User.build({ name: 'u1' }), u2 = this.User.build({ name: 'u2' }); - return p1 - .save() - .then(() => { return u1.save(); }) - .then(() => { return u2.save(); }) - .then(() => { return p1.setUsers([u1]); }) - .then(() => { return p1.setOwners([u2]); }); + await p1 + .save(); + + await u1.save(); + await u2.save(); + await p1.setUsers([u1]); + + await p1.setOwners([u2]); }); }); }); @@ -3065,159 +2919,138 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.UserTasks = this.sequelize.define('tasksusers', { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER }); }); - it('can cascade deletes both ways by default', function() { + it('can cascade deletes both ways by default', async function() { this.User.belongsToMany(this.Task, { through: 'tasksusers' }); this.Task.belongsToMany(this.User, { through: 'tasksusers' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ + await this.sequelize.sync({ force: true }); + + const [user1, task1, user2, task2] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }), + this.User.create({ id: 89, username: 'bar' }), + this.Task.create({ id: 42, title: 'kast' }) + ]); + + await Promise.all([ + user1.setTasks([task1]), + task2.setUsers([user2]) + ]); + + await Promise.all([ + user1.destroy(), + task2.destroy() + ]); + + const [tu1, tu2] = await Promise.all([ + this.sequelize.model('tasksusers').findAll({ where: { userId: user1.id } }), + this.sequelize.model('tasksusers').findAll({ where: { taskId: task2.id } }), + this.User.findOne({ + where: this.sequelize.or({ username: 'Franz Joseph' }), + include: [{ + model: this.Task, + where: { + title: { + [Op.ne]: 'task' + } + } + }] + }) + ]); + + expect(tu1).to.have.length(0); + expect(tu2).to.have.length(0); + }); + + if (current.dialect.supports.constraints.restrict) { + + it('can restrict deletes both ways', async function() { + this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); + this.Task.belongsToMany(this.User, { onDelete: 'RESTRICT', through: 'tasksusers' }); + + await this.sequelize.sync({ force: true }); + + const [user1, task1, user2, task2] = await Promise.all([ this.User.create({ id: 67, username: 'foo' }), this.Task.create({ id: 52, title: 'task' }), this.User.create({ id: 89, username: 'bar' }), this.Task.create({ id: 42, title: 'kast' }) ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ + + await Promise.all([ user1.setTasks([task1]), task2.setUsers([user2]) ]); - }).then(() => { - return Promise.all([ - ctx.user1.destroy(), - ctx.task2.destroy() - ]); - }).then(() => { - return Promise.all([ - this.sequelize.model('tasksusers').findAll({ where: { userId: ctx.user1.id } }), - this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }), - this.User.findOne({ - where: this.sequelize.or({ username: 'Franz Joseph' }), - include: [{ - model: this.Task, - where: { - title: { - [Op.ne]: 'task' - } - } - }] - }) - ]); - }).then(([tu1, tu2]) => { - expect(tu1).to.have.length(0); - expect(tu2).to.have.length(0); - }); - }); - - if (current.dialect.supports.constraints.restrict) { - - it('can restrict deletes both ways', function() { - this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { onDelete: 'RESTRICT', through: 'tasksusers' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }), - this.User.create({ id: 89, username: 'bar' }), - this.Task.create({ id: 42, title: 'kast' }) - ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ - user1.setTasks([task1]), - task2.setUsers([user2]) - ]); - }).then(() => { - return Promise.all([ - expect(ctx.user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint - expect(ctx.task2.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError) - ]); - }); + await Promise.all([ + expect(user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint + expect(task2.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError) + ]); }); - it('can cascade and restrict deletes', function() { + it('can cascade and restrict deletes', async function() { this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); this.Task.belongsToMany(this.User, { onDelete: 'CASCADE', through: 'tasksusers' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }), - this.User.create({ id: 89, username: 'bar' }), - this.Task.create({ id: 42, title: 'kast' }) - ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ - user1.setTasks([task1]), - task2.setUsers([user2]) - ]); - }).then(() => { - return Promise.all([ - expect(ctx.user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint - ctx.task2.destroy() - ]); - }).then(() => { - return this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }); - }).then(usertasks => { - // This should not exist because deletes cascade - expect(usertasks).to.have.length(0); - }); - }); - - } - - it('should be possible to remove all constraints', function() { - this.User.belongsToMany(this.Task, { constraints: false, through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { constraints: false, through: 'tasksusers' }); + await this.sequelize.sync({ force: true }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ + const [user1, task1, user2, task2] = await Promise.all([ this.User.create({ id: 67, username: 'foo' }), this.Task.create({ id: 52, title: 'task' }), this.User.create({ id: 89, username: 'bar' }), this.Task.create({ id: 42, title: 'kast' }) ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ + + await Promise.all([ user1.setTasks([task1]), task2.setUsers([user2]) ]); - }).then(() => { - return Promise.all([ - ctx.user1.destroy(), - ctx.task2.destroy() - ]); - }).then(() => { - return Promise.all([ - this.sequelize.model('tasksusers').findAll({ where: { userId: ctx.user1.id } }), - this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }) + + await Promise.all([ + expect(user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint + task2.destroy() ]); - }).then(([ut1, ut2]) => { - expect(ut1).to.have.length(1); - expect(ut2).to.have.length(1); + + const usertasks = await this.sequelize.model('tasksusers').findAll({ where: { taskId: task2.id } }); + // This should not exist because deletes cascade + expect(usertasks).to.have.length(0); }); + + } + + it('should be possible to remove all constraints', async function() { + this.User.belongsToMany(this.Task, { constraints: false, through: 'tasksusers' }); + this.Task.belongsToMany(this.User, { constraints: false, through: 'tasksusers' }); + + await this.sequelize.sync({ force: true }); + + const [user1, task1, user2, task2] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }), + this.User.create({ id: 89, username: 'bar' }), + this.Task.create({ id: 42, title: 'kast' }) + ]); + + await Promise.all([ + user1.setTasks([task1]), + task2.setUsers([user2]) + ]); + + await Promise.all([ + user1.destroy(), + task2.destroy() + ]); + + const [ut1, ut2] = await Promise.all([ + this.sequelize.model('tasksusers').findAll({ where: { userId: user1.id } }), + this.sequelize.model('tasksusers').findAll({ where: { taskId: task2.id } }) + ]); + + expect(ut1).to.have.length(1); + expect(ut2).to.have.length(1); }); - it('create custom unique identifier', function() { + it('create custom unique identifier', async function() { this.UserTasksLong = this.sequelize.define('table_user_task_with_very_long_name', { id_user_very_long_field: { type: DataTypes.INTEGER(1) @@ -3240,10 +3073,9 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { uniqueKey: 'custom_user_group_unique' }); - return this.sequelize.sync({ force: true }).then(() => { - expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique'); - expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_task_very_long_field.unique).to.equal('custom_user_group_unique'); - }); + await this.sequelize.sync({ force: true }); + expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique'); + expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_task_very_long_field.unique).to.equal('custom_user_group_unique'); }); }); @@ -3277,7 +3109,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('thisAssociations', () => { - it('should work with this reference', function() { + it('should work with this reference', async function() { const User = this.sequelize.define('User', { name: Sequelize.STRING(100) }), @@ -3286,24 +3118,22 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(User, { through: Follow, as: 'User' }); User.belongsToMany(User, { through: Follow, as: 'Fan' }); - return this.sequelize.sync({ force: true }) - .then(() => { - return Promise.all([ - User.create({ name: 'Khsama' }), - User.create({ name: 'Vivek' }), - User.create({ name: 'Satya' }) - ]); - }) - .then(users => { - return Promise.all([ - users[0].addFan(users[1]), - users[1].addUser(users[2]), - users[2].addFan(users[0]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([ + User.create({ name: 'Khsama' }), + User.create({ name: 'Vivek' }), + User.create({ name: 'Satya' }) + ]); + + await Promise.all([ + users[0].addFan(users[1]), + users[1].addUser(users[2]), + users[2].addFan(users[0]) + ]); }); - it('should work with custom this reference', function() { + it('should work with custom this reference', async function() { const User = this.sequelize.define('User', { name: Sequelize.STRING(100) }), @@ -3326,21 +3156,19 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { through: 'Invites' }); - return this.sequelize.sync({ force: true }) - .then(() => { - return Promise.all([ - User.create({ name: 'Jalrangi' }), - User.create({ name: 'Sargrahi' }) - ]); - }) - .then(users => { - return Promise.all([ - users[0].addFollower(users[1]), - users[1].addFollower(users[0]), - users[0].addInvitee(users[1]), - users[1].addInvitee(users[0]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([ + User.create({ name: 'Jalrangi' }), + User.create({ name: 'Sargrahi' }) + ]); + + await Promise.all([ + users[0].addFollower(users[1]), + users[1].addFollower(users[0]), + users[0].addInvitee(users[1]), + users[1].addInvitee(users[0]) + ]); }); it('should setup correct foreign keys', function() { @@ -3395,62 +3223,62 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghats' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); - }).then(() => { - return this.Hat.findOne({ - where: { name: 'Baz' }, - include: [{ model: this.Individual, as: 'hatwornbys' }] - }); - }).then(hat => { - expect(hat.name).to.equal('Baz'); - expect(hat.hatwornbys.length).to.equal(1); - expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat0] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat0); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghats' }] + }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); + + const hat = await this.Hat.findOne({ + where: { name: 'Baz' }, + include: [{ model: this.Individual, as: 'hatwornbys' }] }); + + expect(hat.name).to.equal('Baz'); + expect(hat.hatwornbys.length).to.equal(1); + expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); - }).then(() => { - return this.Hat.findOne({ - where: { name: 'Baz' }, - include: [{ all: true }] - }); - }).then(hat => { - expect(hat.name).to.equal('Baz'); - expect(hat.hatwornbys.length).to.equal(1); - expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat0] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat0); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); + + const hat = await this.Hat.findOne({ + where: { name: 'Baz' }, + include: [{ all: true }] + }); + + expect(hat.name).to.equal('Baz'); + expect(hat.hatwornbys.length).to.equal(1); + expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); }); }); }); diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index 5e75832dece8..e9cbaeaacb31 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -27,129 +27,108 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { describe('get', () => { describe('multiple', () => { - it('should fetch associations for multiple instances', function() { + it('should fetch associations for multiple instances', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', {}); Task.User = Task.belongsTo(User, { as: 'user' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Task.create({ - id: 1, - user: { id: 1 } - }, { - include: [Task.User] - }), Task.create({ - id: 2, - user: { id: 2 } - }, { - include: [Task.User] - }), Task.create({ - id: 3 - })]); - }).then(tasks => { - return Task.User.get(tasks).then(result => { - expect(result[tasks[0].id].id).to.equal(tasks[0].user.id); - expect(result[tasks[1].id].id).to.equal(tasks[1].user.id); - expect(result[tasks[2].id]).to.be.undefined; - }); - }); + await this.sequelize.sync({ force: true }); + + const tasks = await Promise.all([Task.create({ + id: 1, + user: { id: 1 } + }, { + include: [Task.User] + }), Task.create({ + id: 2, + user: { id: 2 } + }, { + include: [Task.User] + }), Task.create({ + id: 3 + })]); + + const result = await Task.User.get(tasks); + expect(result[tasks[0].id].id).to.equal(tasks[0].user.id); + expect(result[tasks[1].id].id).to.equal(tasks[1].user.id); + expect(result[tasks[2].id]).to.be.undefined; }); }); }); describe('getAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.belongsTo(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return Group.findAll({ transaction: t }).then(groups => { - return groups[0].getUser({ transaction: t }).then(associatedUser => { - expect(associatedUser).to.be.not.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + const groups0 = await Group.findAll({ transaction: t }); + const associatedUser0 = await groups0[0].getUser({ transaction: t }); + expect(associatedUser0).to.be.not.null; + await t.rollback(); }); } - it('should be able to handle a where object that\'s a first class citizen.', function() { + it('should be able to handle a where object that\'s a first class citizen.', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }); Task.belongsTo(User); - return User.sync({ force: true }).then(() => { - // Can't use Promise.all cause of foreign key references - return Task.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - User.create({ username: 'bar', gender: 'female' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - }).then(([userA, , task]) => { - return task.setUserXYZ(userA).then(() => { - return task.getUserXYZ({ where: { gender: 'female' } }); - }); - }).then(user => { - expect(user).to.be.null; - }); + await User.sync({ force: true }); + // Can't use Promise.all cause of foreign key references + await Task.sync({ force: true }); + + const [userA, , task] = await Promise.all([ + User.create({ username: 'foo', gender: 'male' }), + User.create({ username: 'bar', gender: 'female' }), + Task.create({ title: 'task', status: 'inactive' }) + ]); + + await task.setUserXYZ(userA); + const user = await task.getUserXYZ({ where: { gender: 'female' } }); + expect(user).to.be.null; }); - it('supports schemas', function() { + it('supports schemas', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }).schema('archive'), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }).schema('archive'); Task.belongsTo(User); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('archive'); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - }).then(([user, task]) => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ(); - }); - }).then(user => { - expect(user).to.be.ok; - return this.sequelize.dropSchema('archive').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('archive'); - } - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('archive'); + await User.sync({ force: true }); + await Task.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ username: 'foo', gender: 'male' }), + Task.create({ title: 'task', status: 'inactive' }) + ]); + + await task.setUserXYZ(user0); + const user = await task.getUserXYZ(); + expect(user).to.be.ok; + await this.sequelize.dropSchema('archive'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('archive'); + } }); - it('supports schemas when defining custom foreign key attribute #9029', function() { + it('supports schemas when defining custom foreign key attribute #9029', async function() { const User = this.sequelize.define('UserXYZ', { uid: { type: Sequelize.INTEGER, @@ -167,160 +146,120 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.belongsTo(User, { foreignKey: 'user_id' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('archive'); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return User.create({}); - }).then(user => { - return Task.create({}).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ(); - }); - }); - }).then(user => { - expect(user).to.be.ok; - return this.sequelize.dropSchema('archive'); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('archive'); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user0 = await User.create({}); + const task = await Task.create({}); + await task.setUserXYZ(user0); + const user = await task.getUserXYZ(); + expect(user).to.be.ok; + + await this.sequelize.dropSchema('archive'); }); }); describe('setAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.belongsTo(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + await t.rollback(); }); } - it('can set the association with declared primary keys...', function() { + it('can set the association with declared primary keys...', async function() { const User = this.sequelize.define('UserXYZ', { user_id: { type: DataTypes.INTEGER, primaryKey: true }, username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { task_id: { type: DataTypes.INTEGER, primaryKey: true }, title: DataTypes.STRING }); Task.belongsTo(User, { foreignKey: 'user_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ user_id: 1, username: 'foo' }).then(user => { - return Task.create({ task_id: 1, title: 'task' }).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ().then(user => { - expect(user).not.to.be.null; - - return task.setUserXYZ(null).then(() => { - return task.getUserXYZ().then(user => { - expect(user).to.be.null; - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ user_id: 1, username: 'foo' }); + const task = await Task.create({ task_id: 1, title: 'task' }); + await task.setUserXYZ(user); + const user1 = await task.getUserXYZ(); + expect(user1).not.to.be.null; + + await task.setUserXYZ(null); + const user0 = await task.getUserXYZ(); + expect(user0).to.be.null; }); - it('clears the association if null is passed', function() { + it('clears the association if null is passed', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ().then(user => { - expect(user).not.to.be.null; - - return task.setUserXYZ(null).then(() => { - return task.getUserXYZ().then(user => { - expect(user).to.be.null; - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUserXYZ(user); + const user1 = await task.getUserXYZ(); + expect(user1).not.to.be.null; + + await task.setUserXYZ(null); + const user0 = await task.getUserXYZ(); + expect(user0).to.be.null; }); - it('should throw a ForeignKeyConstraintError if the associated record does not exist', function() { + it('should throw a ForeignKeyConstraintError if the associated record does not exist', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.create({ title: 'task' }).then(task => { - return expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const task = await Task.create({ title: 'task' }); + + await expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 15, username: 'jansemand' }).then(user => { - return Task.create({}).then(task => { - return task.setUserXYZ(user.id).then(() => { - return task.getUserXYZ().then(user => { - expect(user.username).to.equal('jansemand'); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ id: 15, username: 'jansemand' }); + const task = await Task.create({}); + await task.setUserXYZ(user.id); + const user0 = await task.getUserXYZ(); + expect(user0.username).to.equal('jansemand'); }); - it('should support logging', function() { + it('should support logging', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }), spy = sinon.spy(); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return Task.create({}).then(task => { - return task.setUserXYZ(user, { logging: spy }).then(() => { - expect(spy.called).to.be.ok; - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create(); + const task = await Task.create({}); + await task.setUserXYZ(user, { logging: spy }); + expect(spy.called).to.be.ok; }); - it('should not clobber atributes', function() { + it('should not clobber atributes', async function() { const Comment = this.sequelize.define('comment', { text: DataTypes.STRING }); @@ -332,23 +271,22 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Post.hasOne(Comment); Comment.belongsTo(Post); - return this.sequelize.sync().then(() => { - return Post.create({ - title: 'Post title' - }).then(post => { - return Comment.create({ - text: 'OLD VALUE' - }).then(comment => { - comment.text = 'UPDATED VALUE'; - return comment.setPost(post).then(() => { - expect(comment.text).to.equal('UPDATED VALUE'); - }); - }); - }); + await this.sequelize.sync(); + + const post = await Post.create({ + title: 'Post title' + }); + + const comment = await Comment.create({ + text: 'OLD VALUE' }); + + comment.text = 'UPDATED VALUE'; + await comment.setPost(post); + expect(comment.text).to.equal('UPDATED VALUE'); }); - it('should set the foreign key value without saving when using save: false', function() { + it('should set the foreign key value without saving when using save: false', async function() { const Comment = this.sequelize.define('comment', { text: DataTypes.STRING }); @@ -360,17 +298,15 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Post.hasMany(Comment, { foreignKey: 'post_id' }); Comment.belongsTo(Post, { foreignKey: 'post_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Post.create(), Comment.create()]).then(async ([post, comment]) => { - expect(comment.get('post_id')).not.to.be.ok; + await this.sequelize.sync({ force: true }); + const [post, comment] = await Promise.all([Post.create(), Comment.create()]); + expect(comment.get('post_id')).not.to.be.ok; - const setter = await comment.setPost(post, { save: false }); + const setter = await comment.setPost(post, { save: false }); - expect(setter).to.be.undefined; - expect(comment.get('post_id')).to.equal(post.get('id')); - expect(comment.changed('post_id')).to.be.true; - }); - }); + expect(setter).to.be.undefined; + expect(comment.get('post_id')).to.equal(post.get('id')); + expect(comment.changed('post_id')).to.be.true; }); it('supports setting same association twice', async function() { @@ -390,48 +326,38 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { }); describe('createAssociation', () => { - it('creates an associated model instance', function() { + it('creates an associated model instance', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ title: 'task' }).then(task => { - return task.createUser({ username: 'bob' }).then(user => { - expect(user).not.to.be.null; - expect(user.username).to.equal('bob'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const task = await Task.create({ title: 'task' }); + const user = await task.createUser({ username: 'bob' }); + expect(user).not.to.be.null; + expect(user.username).to.equal('bob'); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.belongsTo(User); - - return sequelize.sync({ force: true }).then(() => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.createUser({ username: 'foo' }, { transaction: t }).then(() => { - return group.getUser().then(user => { - expect(user).to.be.null; - - return group.getUser({ transaction: t }).then(user => { - expect(user).not.to.be.null; - - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.createUser({ username: 'foo' }, { transaction: t }); + const user = await group.getUser(); + expect(user).to.be.null; + + const user0 = await group.getUser({ transaction: t }); + expect(user0).not.to.be.null; + + await t.rollback(); }); } }); @@ -457,7 +383,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { expect(User.rawAttributes.AccountId.field).to.equal('AccountId'); }); - it('should support specifying the field of a foreign key', function() { + it('should support specifying the field of a foreign key', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }), Account = this.sequelize.define('Account', { title: Sequelize.STRING }, { underscored: false }); @@ -471,31 +397,29 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { expect(User.rawAttributes.AccountId).to.exist; expect(User.rawAttributes.AccountId.field).to.equal('account_id'); - return Account.sync({ force: true }).then(() => { - // Can't use Promise.all cause of foreign key references - return User.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Account.create({ title: 'pepsico' }) - ]); - }).then(([user, account]) => { - return user.setAccount(account).then(() => { - return user.getAccount(); - }); - }).then(user => { - expect(user).to.not.be.null; - return User.findOne({ - where: { username: 'foo' }, - include: [Account] - }); - }).then(user => { - // the sql query should correctly look at account_id instead of AccountId - expect(user.Account).to.exist; + await Account.sync({ force: true }); + // Can't use Promise.all cause of foreign key references + await User.sync({ force: true }); + + const [user1, account] = await Promise.all([ + User.create({ username: 'foo' }), + Account.create({ title: 'pepsico' }) + ]); + + await user1.setAccount(account); + const user0 = await user1.getAccount(); + expect(user0).to.not.be.null; + + const user = await User.findOne({ + where: { username: 'foo' }, + include: [Account] }); + + // the sql query should correctly look at account_id instead of AccountId + expect(user.Account).to.exist; }); - it('should set foreignKey on foreign table', function() { + it('should set foreignKey on foreign table', async function() { const Mail = this.sequelize.define('mail', {}, { timestamps: false }); const Entry = this.sequelize.define('entry', {}, { timestamps: false }); const User = this.sequelize.define('user', {}, { timestamps: false }); @@ -542,230 +466,192 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { } }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({})) - .then(() => Mail.create({})) - .then(mail => - Entry.create({ mailId: mail.id, ownerId: 1 }) - .then(() => Entry.create({ mailId: mail.id, ownerId: 1 })) - // set recipients - .then(() => mail.setRecipients([1])) - ) - .then(() => Entry.findAndCountAll({ - offset: 0, - limit: 10, - order: [['id', 'DESC']], - include: [ - { - association: Entry.associations.mail, - include: [ - { - association: Mail.associations.recipients, - through: { - where: { - recipientId: 1 - } - }, - required: true - } - ], - required: true - } - ] - })).then(result => { - expect(result.count).to.equal(2); - expect(result.rows[0].get({ plain: true })).to.deep.equal( - { - id: 2, - ownerId: 1, - mailId: 1, - mail: { - id: 1, - recipients: [{ - id: 1, - MailRecipients: { - mailId: 1, + await this.sequelize.sync({ force: true }); + await User.create({}); + const mail = await Mail.create({}); + await Entry.create({ mailId: mail.id, ownerId: 1 }); + await Entry.create({ mailId: mail.id, ownerId: 1 }); + // set recipients + await mail.setRecipients([1]); + + const result = await Entry.findAndCountAll({ + offset: 0, + limit: 10, + order: [['id', 'DESC']], + include: [ + { + association: Entry.associations.mail, + include: [ + { + association: Mail.associations.recipients, + through: { + where: { recipientId: 1 } - }] + }, + required: true } - } - ); - }); + ], + required: true + } + ] + }); + + expect(result.count).to.equal(2); + expect(result.rows[0].get({ plain: true })).to.deep.equal( + { + id: 2, + ownerId: 1, + mailId: 1, + mail: { + id: 1, + recipients: [{ + id: 1, + MailRecipients: { + mailId: 1, + recipientId: 1 + } + }] + } + } + ); }); }); describe('foreign key constraints', () => { - it('are enabled by default', function() { + it('are enabled by default', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User); // defaults to SET NULL - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(null); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(null); }); - it('sets to NO ACTION if allowNull: false', function() { + it('sets to NO ACTION if allowNull: false', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { foreignKey: { allowNull: false } }); // defaults to NO ACTION - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', UserId: user.id }).then(() => { - return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + await Task.create({ title: 'task', UserId: user.id }); + await expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); }); - it('should be possible to disable them', function() { + it('should be possible to disable them', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); Task.belongsTo(User, { constraints: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(user.id); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(user.id); }); - it('can cascade deletes', function() { + it('can cascade deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onDelete: 'cascade' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return user.destroy().then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(0); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(0); }); if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes', function() { + it('can restrict deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onDelete: 'restrict' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); }); - it('can restrict updates', function() { + it('can restrict updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onUpdate: 'restrict' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); - return expect( - user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - // Should fail due to FK restriction - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + + await expect( + user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) + ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + + // Should fail due to FK restriction + const tasks = await Task.findAll(); + + expect(tasks).to.have.length(1); }); } // NOTE: mssql does not support changing an autoincrement primary key if (Support.getTestDialect() !== 'mssql') { - it('can cascade updates', function() { + it('can cascade updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onUpdate: 'cascade' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - .then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].UserId).to.equal(999); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); + expect(tasks[0].UserId).to.equal(999); }); } }); describe('association column', () => { - it('has correct type and name for non-id primary keys with non-integer type', function() { + it('has correct type and name for non-id primary keys with non-integer type', async function() { const User = this.sequelize.define('UserPKBT', { username: { type: DataTypes.STRING @@ -781,36 +667,33 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING); - }); + await this.sequelize.sync({ force: true }); + expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING); }); - it('should support a non-primary key as the association column on a target without a primary key', function() { + it('should support a non-primary key as the association column on a target without a primary key', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, unique: true } }); const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.removeAttribute('id'); Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'username', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'username', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); - it('should support a non-primary unique key as the association column', function() { + it('should support a non-primary unique key as the association column', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -824,24 +707,22 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'user_name', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'user_name', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); - it('should support a non-primary key as the association column with a field option', function() { + it('should support a non-primary key as the association column with a field option', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -854,24 +735,22 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { User.removeAttribute('id'); Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'the_user_name_field', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'the_user_name_field', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); - it('should support a non-primary key as the association column in a table with a composite primary key', function() { + it('should support a non-primary key as the association column in a table with a composite primary key', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -893,26 +772,24 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob', age: 18, weight: 40 })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'the_user_name_field', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob', age: 18, weight: 40 }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'the_user_name_field', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); }); describe('association options', () => { - it('can specify data type for auto-generated relational keys', function() { + it('can specify data type for auto-generated relational keys', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), dataTypes = [DataTypes.INTEGER, DataTypes.BIGINT, DataTypes.STRING], Tasks = {}; @@ -923,10 +800,9 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Tasks[dataType].belongsTo(User, { foreignKey: 'userId', keyType: dataType, constraints: false }); }); - return this.sequelize.sync({ force: true }).then(() => { - dataTypes.forEach(dataType => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); + await this.sequelize.sync({ force: true }); + dataTypes.forEach(dataType => { + expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); }); }); @@ -1019,53 +895,53 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghat' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ - model: this.Hat, - as: { singular: 'personwearinghat' } - }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual1, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual1.setPersonwearinghat(hat); + + const individual0 = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghat' }] }); + + expect(individual0.name).to.equal('Foo Bar'); + expect(individual0.personwearinghat.name).to.equal('Baz'); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ + model: this.Hat, + as: { singular: 'personwearinghat' } + }] + }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.setPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); }); }); diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index 9ef6229a74c6..c470a1183a9a 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -27,82 +27,83 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); describe('count', () => { - it('should not fail due to ambiguous field', function() { + it('should not fail due to ambiguous field', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); User.hasMany(Task); const subtasks = Task.hasMany(Task, { as: 'subtasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'John', - Tasks: [{ - title: 'Get rich', active: true - }] - }, { - include: [Task] - }); - }).then(user => { - return Promise.all([ - user.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }), - user.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }) - ]).then(() => user); - }).then(user => { - return expect(user.countTasks({ - attributes: [Task.primaryKeyField, 'title'], - include: [{ - attributes: [], - association: subtasks, - where: { - active: true - } - }], - group: this.sequelize.col(Task.name.concat('.', Task.primaryKeyField)) - })).to.eventually.equal(1); + await this.sequelize.sync({ force: true }); + + const user0 = await User.create({ + username: 'John', + Tasks: [{ + title: 'Get rich', active: true + }] + }, { + include: [Task] }); + + await Promise.all([ + user0.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }), + user0.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }) + ]); + + const user = user0; + + await expect(user.countTasks({ + attributes: [Task.primaryKeyField, 'title'], + include: [{ + attributes: [], + association: subtasks, + where: { + active: true + } + }], + group: this.sequelize.col(Task.name.concat('.', Task.primaryKeyField)) + })).to.eventually.equal(1); }); }); describe('get', () => { if (current.dialect.supports.groupedLimit) { describe('multiple', () => { - it('should fetch associations for multiple instances', function() { + it('should fetch associations for multiple instances', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', {}); User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), User.create({ - id: 2, - tasks: [ - {} - ] - }, { - include: [User.Tasks] - }), User.create({ - id: 3 - })]); - }).then(users => { - return User.Tasks.get(users).then(result => { - expect(result[users[0].id].length).to.equal(3); - expect(result[users[1].id].length).to.equal(1); - expect(result[users[2].id].length).to.equal(0); - }); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 3 + })]); + + const result = await User.Tasks.get(users); + expect(result[users[0].id].length).to.equal(3); + expect(result[users[1].id].length).to.equal(1); + expect(result[users[2].id].length).to.equal(0); }); - it('should fetch associations for multiple instances with limit and order', function() { + it('should fetch associations for multiple instances with limit and order', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { title: DataTypes.STRING @@ -110,44 +111,44 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - tasks: [ - { title: 'b' }, - { title: 'd' }, - { title: 'c' }, - { title: 'a' } - ] - }, { - include: [User.Tasks] - }), User.create({ - tasks: [ - { title: 'a' }, - { title: 'c' }, - { title: 'b' } - ] - }, { - include: [User.Tasks] - })]); - }).then(users => { - return User.Tasks.get(users, { - limit: 2, - order: [ - ['title', 'ASC'] - ] - }).then(result => { - expect(result[users[0].id].length).to.equal(2); - expect(result[users[0].id][0].title).to.equal('a'); - expect(result[users[0].id][1].title).to.equal('b'); - - expect(result[users[1].id].length).to.equal(2); - expect(result[users[1].id][0].title).to.equal('a'); - expect(result[users[1].id][1].title).to.equal('b'); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([User.create({ + tasks: [ + { title: 'b' }, + { title: 'd' }, + { title: 'c' }, + { title: 'a' } + ] + }, { + include: [User.Tasks] + }), User.create({ + tasks: [ + { title: 'a' }, + { title: 'c' }, + { title: 'b' } + ] + }, { + include: [User.Tasks] + })]); + + const result = await User.Tasks.get(users, { + limit: 2, + order: [ + ['title', 'ASC'] + ] }); + + expect(result[users[0].id].length).to.equal(2); + expect(result[users[0].id][0].title).to.equal('a'); + expect(result[users[0].id][1].title).to.equal('b'); + + expect(result[users[1].id].length).to.equal(2); + expect(result[users[1].id][0].title).to.equal('a'); + expect(result[users[1].id][1].title).to.equal('b'); }); - it('should fetch multiple layers of associations with limit and order with separate=true', function() { + it('should fetch multiple layers of associations with limit and order with separate=true', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { title: DataTypes.STRING @@ -159,97 +160,97 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - tasks: [ - { title: 'b', subtasks: [ - { title: 'c' }, - { title: 'a' } - ] }, - { title: 'd' }, - { title: 'c', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'a', subtasks: [ - { title: 'c' }, - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }), User.create({ - id: 2, - tasks: [ - { title: 'a', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'c', subtasks: [ - { title: 'a' } - ] }, - { title: 'b', subtasks: [ - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - })]); - }).then(() => { - return User.findAll({ - include: [{ - association: User.Tasks, - limit: 2, - order: [['title', 'ASC']], - separate: true, - as: 'tasks', - include: [ - { - association: Task.SubTasks, - order: [['title', 'DESC']], - separate: true, - as: 'subtasks' - } - ] - }], - order: [ - ['id', 'ASC'] + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + { title: 'b', subtasks: [ + { title: 'c' }, + { title: 'a' } + ] }, + { title: 'd' }, + { title: 'c', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'a', subtasks: [ + { title: 'c' }, + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + }), User.create({ + id: 2, + tasks: [ + { title: 'a', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'c', subtasks: [ + { title: 'a' } + ] }, + { title: 'b', subtasks: [ + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + })]); + + const users = await User.findAll({ + include: [{ + association: User.Tasks, + limit: 2, + order: [['title', 'ASC']], + separate: true, + as: 'tasks', + include: [ + { + association: Task.SubTasks, + order: [['title', 'DESC']], + separate: true, + as: 'subtasks' + } ] - }).then(users => { - expect(users[0].tasks.length).to.equal(2); - - expect(users[0].tasks[0].title).to.equal('a'); - expect(users[0].tasks[0].subtasks.length).to.equal(3); - expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[0].tasks[1].title).to.equal('b'); - expect(users[0].tasks[1].subtasks.length).to.equal(2); - expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); - - expect(users[1].tasks.length).to.equal(2); - expect(users[1].tasks[0].title).to.equal('a'); - expect(users[1].tasks[0].subtasks.length).to.equal(3); - expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[1].tasks[1].title).to.equal('b'); - expect(users[1].tasks[1].subtasks.length).to.equal(2); - expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); - expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); - }); + }], + order: [ + ['id', 'ASC'] + ] }); + + expect(users[0].tasks.length).to.equal(2); + + expect(users[0].tasks[0].title).to.equal('a'); + expect(users[0].tasks[0].subtasks.length).to.equal(3); + expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[0].tasks[1].title).to.equal('b'); + expect(users[0].tasks[1].subtasks.length).to.equal(2); + expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); + + expect(users[1].tasks.length).to.equal(2); + expect(users[1].tasks[0].title).to.equal('a'); + expect(users[1].tasks[0].subtasks.length).to.equal(3); + expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[1].tasks[1].title).to.equal('b'); + expect(users[1].tasks[1].subtasks.length).to.equal(2); + expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); + expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); }); - it('should fetch associations for multiple instances with limit and order and a belongsTo relation', function() { + it('should fetch associations for multiple instances with limit and order and a belongsTo relation', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { title: DataTypes.STRING, @@ -263,49 +264,49 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.Category = Task.belongsTo(Category, { as: 'category', foreignKey: 'categoryId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - tasks: [ - { title: 'b', category: {} }, - { title: 'd', category: {} }, - { title: 'c', category: {} }, - { title: 'a', category: {} } - ] - }, { - include: [{ association: User.Tasks, include: [Task.Category] }] - }), User.create({ - tasks: [ - { title: 'a', category: {} }, - { title: 'c', category: {} }, - { title: 'b', category: {} } - ] - }, { - include: [{ association: User.Tasks, include: [Task.Category] }] - })]); - }).then(users => { - return User.Tasks.get(users, { - limit: 2, - order: [ - ['title', 'ASC'] - ], - include: [Task.Category] - }).then(result => { - expect(result[users[0].id].length).to.equal(2); - expect(result[users[0].id][0].title).to.equal('a'); - expect(result[users[0].id][0].category).to.be.ok; - expect(result[users[0].id][1].title).to.equal('b'); - expect(result[users[0].id][1].category).to.be.ok; - - expect(result[users[1].id].length).to.equal(2); - expect(result[users[1].id][0].title).to.equal('a'); - expect(result[users[1].id][0].category).to.be.ok; - expect(result[users[1].id][1].title).to.equal('b'); - expect(result[users[1].id][1].category).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([User.create({ + tasks: [ + { title: 'b', category: {} }, + { title: 'd', category: {} }, + { title: 'c', category: {} }, + { title: 'a', category: {} } + ] + }, { + include: [{ association: User.Tasks, include: [Task.Category] }] + }), User.create({ + tasks: [ + { title: 'a', category: {} }, + { title: 'c', category: {} }, + { title: 'b', category: {} } + ] + }, { + include: [{ association: User.Tasks, include: [Task.Category] }] + })]); + + const result = await User.Tasks.get(users, { + limit: 2, + order: [ + ['title', 'ASC'] + ], + include: [Task.Category] }); + + expect(result[users[0].id].length).to.equal(2); + expect(result[users[0].id][0].title).to.equal('a'); + expect(result[users[0].id][0].category).to.be.ok; + expect(result[users[0].id][1].title).to.equal('b'); + expect(result[users[0].id][1].category).to.be.ok; + + expect(result[users[1].id].length).to.equal(2); + expect(result[users[1].id][0].title).to.equal('a'); + expect(result[users[1].id][0].category).to.be.ok; + expect(result[users[1].id][1].title).to.equal('b'); + expect(result[users[1].id][1].category).to.be.ok; }); - it('supports schemas', function() { + it('supports schemas', async function() { const User = this.sequelize.define('User', {}).schema('work'), Task = this.sequelize.define('Task', { title: DataTypes.STRING @@ -317,109 +318,103 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('work'); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return SubTask.sync({ force: true }); - }).then(() => { - return Promise.all([User.create({ - id: 1, - tasks: [ - { title: 'b', subtasks: [ - { title: 'c' }, - { title: 'a' } - ] }, - { title: 'd' }, - { title: 'c', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'a', subtasks: [ - { title: 'c' }, - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }), User.create({ - id: 2, - tasks: [ - { title: 'a', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'c', subtasks: [ - { title: 'a' } - ] }, - { title: 'b', subtasks: [ - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - })]); - }).then(() => { - return User.findAll({ - include: [{ - association: User.Tasks, - limit: 2, - order: [['title', 'ASC']], - separate: true, - as: 'tasks', - include: [ - { - association: Task.SubTasks, - order: [['title', 'DESC']], - separate: true, - as: 'subtasks' - } - ] - }], - order: [ - ['id', 'ASC'] + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('work'); + await User.sync({ force: true }); + await Task.sync({ force: true }); + await SubTask.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + { title: 'b', subtasks: [ + { title: 'c' }, + { title: 'a' } + ] }, + { title: 'd' }, + { title: 'c', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'a', subtasks: [ + { title: 'c' }, + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + }), User.create({ + id: 2, + tasks: [ + { title: 'a', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'c', subtasks: [ + { title: 'a' } + ] }, + { title: 'b', subtasks: [ + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + })]); + + const users = await User.findAll({ + include: [{ + association: User.Tasks, + limit: 2, + order: [['title', 'ASC']], + separate: true, + as: 'tasks', + include: [ + { + association: Task.SubTasks, + order: [['title', 'DESC']], + separate: true, + as: 'subtasks' + } ] - }).then(users => { - expect(users[0].tasks.length).to.equal(2); - - expect(users[0].tasks[0].title).to.equal('a'); - expect(users[0].tasks[0].subtasks.length).to.equal(3); - expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[0].tasks[1].title).to.equal('b'); - expect(users[0].tasks[1].subtasks.length).to.equal(2); - expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); - - expect(users[1].tasks.length).to.equal(2); - expect(users[1].tasks[0].title).to.equal('a'); - expect(users[1].tasks[0].subtasks.length).to.equal(3); - expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[1].tasks[1].title).to.equal('b'); - expect(users[1].tasks[1].subtasks.length).to.equal(2); - expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); - expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); - return this.sequelize.dropSchema('work').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || schemas === 'mariadb') { - expect(schemas).to.be.empty; - } - }); - }); - }); + }], + order: [ + ['id', 'ASC'] + ] }); + + expect(users[0].tasks.length).to.equal(2); + + expect(users[0].tasks[0].title).to.equal('a'); + expect(users[0].tasks[0].subtasks.length).to.equal(3); + expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[0].tasks[1].title).to.equal('b'); + expect(users[0].tasks[1].subtasks.length).to.equal(2); + expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); + + expect(users[1].tasks.length).to.equal(2); + expect(users[1].tasks[0].title).to.equal('a'); + expect(users[1].tasks[0].subtasks.length).to.equal(3); + expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[1].tasks[1].title).to.equal('b'); + expect(users[1].tasks[1].subtasks.length).to.equal(2); + expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); + expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); + await this.sequelize.dropSchema('work'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || schemas === 'mariadb') { + expect(schemas).to.be.empty; + } }); }); } @@ -463,95 +458,82 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - let Article, Label, sequelize, article, label, t; - return Support.prepareTransactionTest(this.sequelize).then(_sequelize => { - sequelize = _sequelize; - Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - Article.hasMany(Label); - - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - Article.create({ title: 'foo' }), - Label.create({ text: 'bar' }) - ]); - }).then(([_article, _label]) => { - article = _article; - label = _label; - return sequelize.transaction(); - }).then(_t => { - t = _t; - return article.setLabels([label], { transaction: t }); - }).then(() => { - return Article.findAll({ transaction: t }); - }).then(articles => { - return articles[0].hasLabel(label).then(hasLabel => { - expect(hasLabel).to.be.false; - }); - }).then(() => { - return Article.findAll({ transaction: t }); - }).then(articles => { - return articles[0].hasLabel(label, { transaction: t }).then(hasLabel => { - expect(hasLabel).to.be.true; - return t.rollback(); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.setLabels([label], { transaction: t }); + const articles0 = await Article.findAll({ transaction: t }); + const hasLabel0 = await articles0[0].hasLabel(label); + expect(hasLabel0).to.be.false; + const articles = await Article.findAll({ transaction: t }); + const hasLabel = await articles[0].hasLabel(label, { transaction: t }); + expect(hasLabel).to.be.true; + await t.rollback(); }); } - it('does not have any labels assigned to it initially', function() { - return Promise.all([ + it('does not have any labels assigned to it initially', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return Promise.all([ - article.hasLabel(label1), - article.hasLabel(label2) - ]); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.false; - }); + ]); + + const [hasLabel1, hasLabel2] = await Promise.all([ + article.hasLabel(label1), + article.hasLabel(label2) + ]); + + expect(hasLabel1).to.be.false; + expect(hasLabel2).to.be.false; }); - it('answers true if the label has been assigned', function() { - return Promise.all([ + it('answers true if the label has been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return Promise.all([ - article.hasLabel(label1), - article.hasLabel(label2) - ]); - }); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.true; - expect(hasLabel2).to.be.false; - }); + ]); + + await article.addLabel(label1); + + const [hasLabel1, hasLabel2] = await Promise.all([ + article.hasLabel(label1), + article.hasLabel(label2) + ]); + + expect(hasLabel1).to.be.true; + expect(hasLabel2).to.be.false; }); - it('answers correctly if the label has been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers correctly if the label has been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return Promise.all([ - article.hasLabel(label1[this.Label.primaryKeyAttribute]), - article.hasLabel(label2[this.Label.primaryKeyAttribute]) - ]); - }); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.true; - expect(hasLabel2).to.be.false; - }); + ]); + + await article.addLabel(label1); + + const [hasLabel1, hasLabel2] = await Promise.all([ + article.hasLabel(label1[this.Label.primaryKeyAttribute]), + article.hasLabel(label2[this.Label.primaryKeyAttribute]) + ]); + + expect(hasLabel1).to.be.true; + expect(hasLabel2).to.be.false; }); }); @@ -581,310 +563,249 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.hasMany(ctx.Label); - - return ctx.sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - ctx.label = label; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.setLabels([ctx.label], { transaction: t }); - }).then(() => { - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return Promise.all([ - articles[0].hasLabels([ctx.label]), - articles[0].hasLabels([ctx.label], { transaction: ctx.t }) - ]); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.true; - - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.setLabels([label], { transaction: t }); + const articles = await Article.findAll({ transaction: t }); + + const [hasLabel1, hasLabel2] = await Promise.all([ + articles[0].hasLabels([label]), + articles[0].hasLabels([label], { transaction: t }) + ]); + + expect(hasLabel1).to.be.false; + expect(hasLabel2).to.be.true; + + await t.rollback(); }); } - it('answers false if only some labels have been assigned', function() { - return Promise.all([ + it('answers false if only some labels have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return article.hasLabels([label1, label2]); - }); - }).then(result => { - expect(result).to.be.false; - }); + ]); + + await article.addLabel(label1); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.false; }); - it('answers false if only some labels have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers false if only some labels have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.false; - }); - }); - }); + ]); + + await article.addLabel(label1); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.false; }); - it('answers true if all label have been assigned', function() { - return Promise.all([ + it('answers true if all label have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([label1, label2]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.true; }); - it('answers true if all label have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers true if all label have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.true; }); }); describe('setAssociations', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.hasMany(ctx.Label); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }), - ctx.sequelize.transaction() - ]); - }).then(([article, label, t]) => { - ctx.article = article; - ctx. t = t; - return article.setLabels([label], { transaction: t }); - }).then(() => { - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: undefined }); - }).then(labels => { - expect(labels.length).to.equal(0); - - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: ctx.t }); - }).then(labels => { - expect(labels.length).to.equal(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label, t] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }), + sequelize.transaction() + ]); + + await article.setLabels([label], { transaction: t }); + const labels0 = await Label.findAll({ where: { ArticleId: article.id }, transaction: undefined }); + expect(labels0.length).to.equal(0); + + const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t }); + expect(labels.length).to.equal(1); + await t.rollback(); }); } - it('clears associations when passing null to the set-method', function() { + it('clears associations when passing null to the set-method', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.task = task; - return task.setUsers([user]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(1); - - return ctx.task.setUsers(null); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(0); - }); + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await task.setUsers([user]); + const users0 = await task.getUsers(); + expect(users0).to.have.length(1); + + await task.setUsers(null); + const users = await task.getUsers(); + expect(users).to.have.length(0); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const Article = this.sequelize.define('Article', { title: DataTypes.STRING }), Label = this.sequelize.define('Label', { text: DataTypes.STRING }); Article.hasMany(Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Article.create({}), - Label.create({ text: 'label one' }), - Label.create({ text: 'label two' }) - ]); - }).then(([article, label1, label2]) => { - ctx.article = article; - ctx.label1 = label1; - ctx.label2 = label2; - return article.addLabel(label1.id); - }).then(() => { - return ctx.article.setLabels([ctx.label2.id]); - }).then(() => { - return ctx.article.getLabels(); - }).then(labels => { - expect(labels).to.have.length(1); - expect(labels[0].text).to.equal('label two'); - }); + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + Article.create({}), + Label.create({ text: 'label one' }), + Label.create({ text: 'label two' }) + ]); + + await article.addLabel(label1.id); + await article.setLabels([label2.id]); + const labels = await article.getLabels(); + expect(labels).to.have.length(1); + expect(labels[0].text).to.equal('label two'); }); }); describe('addAssociations', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - ctx.Article.hasMany(ctx.Label); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - ctx.label = label; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.addLabel(ctx.label, { transaction: ctx.t }); - }).then(() => { - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: undefined }); - }).then(labels => { - expect(labels.length).to.equal(0); - - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: ctx.t }); - }).then(labels => { - expect(labels.length).to.equal(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.addLabel(label, { transaction: t }); + const labels0 = await Label.findAll({ where: { ArticleId: article.id }, transaction: undefined }); + expect(labels0.length).to.equal(0); + + const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t }); + expect(labels.length).to.equal(1); + await t.rollback(); }); } - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), Label = this.sequelize.define('Label', { 'text': DataTypes.STRING }); Article.hasMany(Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Article.create({}), - Label.create({ text: 'label one' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - return article.addLabel(label.id); - }).then(() => { - return ctx.article.getLabels(); - }).then(labels => { - expect(labels[0].text).to.equal('label one'); // Make sure that we didn't modify one of the other attributes while building / saving a new instance - }); + await this.sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({}), + Label.create({ text: 'label one' }) + ]); + + await article.addLabel(label.id); + const labels = await article.getLabels(); + expect(labels[0].text).to.equal('label one'); // Make sure that we didn't modify one of the other attributes while building / saving a new instance }); }); describe('addMultipleAssociations', () => { - it('adds associations without removing the current ones', function() { + it('adds associations without removing the current ones', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'foo ' }, - { username: 'bar ' }, - { username: 'baz ' } - ]); - }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return User.findAll(); - }).then(users => { - ctx.users = users; - return ctx.task.setUsers([users[0]]); - }).then(() => { - return ctx.task.addUsers([ctx.users[1], ctx.users[2]]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(3); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([ + { username: 'foo ' }, + { username: 'bar ' }, + { username: 'baz ' } + ]); + + const task = await Task.create({ title: 'task' }); + const users0 = await User.findAll(); + const users = users0; + await task.setUsers([users0[0]]); + await task.addUsers([users[1], users[2]]); + expect(await task.getUsers()).to.have.length(3); }); - it('handles decent sized bulk creates', function() { + it('handles decent sized bulk creates', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING, num: DataTypes.INTEGER, status: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - const users = _.range(1000).map(i => ({ username: `user${i}`, num: i, status: 'live' })); - return User.bulkCreate(users); - }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return User.findAll(); - }).then(users=> { - expect(users).to.have.length(1000); - }); + await this.sequelize.sync({ force: true }); + const users0 = _.range(1000).map(i => ({ username: `user${i}`, num: i, status: 'live' })); + await User.bulkCreate(users0); + await Task.create({ title: 'task' }); + const users = await User.findAll(); + expect(users).to.have.length(1000); }); }); - it('clears associations when passing null to the set-method with omitNull set to true', function() { + it('clears associations when passing null to the set-method with omitNull set to true', async function() { this.sequelize.options.omitNull = true; const User = this.sequelize.define('User', { username: DataTypes.STRING }), @@ -892,49 +813,38 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }); - }).then(user => { - ctx.user = user; - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return task.setUsers([ctx.user]); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); - - return ctx.task.setUsers(null); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { + try { + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUsers([user]); + const _users0 = await task.getUsers(); + expect(_users0).to.have.length(1); + + await task.setUsers(null); + const _users = await task.getUsers(); expect(_users).to.have.length(0); - }).finally(() => { + } finally { this.sequelize.options.omitNull = false; - }); + } }); describe('createAssociations', () => { - it('creates a new associated object', function() { + it('creates a new associated object', async function() { const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), Label = this.sequelize.define('Label', { 'text': DataTypes.STRING }); Article.hasMany(Label); - return this.sequelize.sync({ force: true }).then(() => { - return Article.create({ title: 'foo' }); - }).then(article => { - return article.createLabel({ text: 'bar' }).then(() => article); - }).then(article => { - return Label.findAll({ where: { ArticleId: article.id } }); - }).then(labels => { - expect(labels.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + const article0 = await Article.create({ title: 'foo' }); + await article0.createLabel({ text: 'bar' }); + const article = article0; + const labels = await Label.findAll({ where: { ArticleId: article.id } }); + expect(labels.length).to.equal(1); }); - it('creates the object with the association directly', function() { + it('creates the object with the association directly', async function() { const spy = sinon.spy(); const Article = this.sequelize.define('Article', { @@ -947,53 +857,36 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Article.hasMany(Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Article.create({ title: 'foo' }); - }).then(article => { - ctx.article = article; - return article.createLabel({ text: 'bar' }, { logging: spy }); - }).then(label => { - expect(spy.calledOnce).to.be.true; - expect(label.ArticleId).to.equal(ctx.article.id); - }); + await this.sequelize.sync({ force: true }); + const article = await Article.create({ title: 'foo' }); + const label = await article.createLabel({ text: 'bar' }, { logging: spy }); + expect(spy.calledOnce).to.be.true; + expect(label.ArticleId).to.equal(article.id); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.hasMany(ctx.Label); - - return sequelize.sync({ force: true }); - }).then(() => { - return ctx.Article.create({ title: 'foo' }); - }).then(article => { - ctx.article = article; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.createLabel({ text: 'bar' }, { transaction: ctx.t }); - }).then(() => { - return ctx.Label.findAll(); - }).then(labels => { - expect(labels.length).to.equal(0); - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id } }); - }).then(labels => { - expect(labels.length).to.equal(0); - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: ctx.t }); - }).then(labels => { - expect(labels.length).to.equal(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + const article = await Article.create({ title: 'foo' }); + const t = await sequelize.transaction(); + await article.createLabel({ text: 'bar' }, { transaction: t }); + const labels1 = await Label.findAll(); + expect(labels1.length).to.equal(0); + const labels0 = await Label.findAll({ where: { ArticleId: article.id } }); + expect(labels0.length).to.equal(0); + const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t }); + expect(labels.length).to.equal(1); + await t.rollback(); }); } - it('supports passing the field option', function() { + it('supports passing the field option', async function() { const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), @@ -1003,41 +896,40 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Article.hasMany(Label); - return this.sequelize.sync({ force: true }).then(() => { - return Article.create(); - }).then(article => { - return article.createLabel({ - text: 'yolo' - }, { - fields: ['text'] - }).then(() => article); - }).then(article => { - return article.getLabels(); - }).then(labels => { - expect(labels.length).to.be.ok; + await this.sequelize.sync({ force: true }); + const article0 = await Article.create(); + + await article0.createLabel({ + text: 'yolo' + }, { + fields: ['text'] }); + + const article = article0; + const labels = await article.getLabels(); + expect(labels.length).to.be.ok; }); }); describe('getting assocations with options', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); this.User.hasMany(this.Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + return john.setTasks([task1, task2]); }); - it('should treat the where object of associations as a first class citizen', function() { + it('should treat the where object of associations as a first class citizen', async function() { this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }); @@ -1048,44 +940,36 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { this.Article.hasMany(this.Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }), - this.Label.create({ text: 'Epicness', until: '2014-01-03 01:00:00' }) - ]); - }).then(([article, label1, label2]) => { - ctx.article = article; - return article.setLabels([label1, label2]); - }).then(() => { - return ctx.article.getLabels({ where: { until: { [Op.gt]: moment('2014-01-02').toDate() } } }); - }).then(labels => { - expect(labels).to.be.instanceof(Array); - expect(labels).to.have.length(1); - expect(labels[0].text).to.equal('Epicness'); - }); + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }), + this.Label.create({ text: 'Epicness', until: '2014-01-03 01:00:00' }) + ]); + + await article.setLabels([label1, label2]); + const labels = await article.getLabels({ where: { until: { [Op.gt]: moment('2014-01-02').toDate() } } }); + expect(labels).to.be.instanceof(Array); + expect(labels).to.have.length(1); + expect(labels[0].text).to.equal('Epicness'); }); - it('gets all associated objects when no options are passed', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - }); + it('gets all associated objects when no options are passed', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + expect(tasks).to.have.length(2); }); - it('only get objects that fulfill the options', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks({ where: { active: true }, limit: 10, order: [['id', 'DESC']] }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + it('only get objects that fulfill the options', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks({ where: { active: true }, limit: 10, order: [['id', 'DESC']] }); + expect(tasks).to.have.length(1); }); }); describe('countAssociations', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); @@ -1093,31 +977,32 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - this.user = john; - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + this.user = john; + + return john.setTasks([task1, task2]); }); - it('should count all associations', function() { - return expect(this.user.countTasks({})).to.eventually.equal(2); + it('should count all associations', async function() { + await expect(this.user.countTasks({})).to.eventually.equal(2); }); - it('should count filtered associations', function() { - return expect(this.user.countTasks({ + it('should count filtered associations', async function() { + await expect(this.user.countTasks({ where: { active: true } })).to.eventually.equal(1); }); - it('should count scoped associations', function() { + it('should count scoped associations', async function() { this.User.hasMany(this.Task, { foreignKey: 'userId', as: 'activeTasks', @@ -1126,201 +1011,188 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { } }); - return expect(this.user.countActiveTasks({})).to.eventually.equal(1); + await expect(this.user.countActiveTasks({})).to.eventually.equal(1); }); }); describe('thisAssociations', () => { - it('should work with alias', function() { + it('should work with alias', async function() { const Person = this.sequelize.define('Group', {}); Person.hasMany(Person, { as: 'Children' }); - return this.sequelize.sync(); + await this.sequelize.sync(); }); }); }); describe('foreign key constraints', () => { describe('1:m', () => { - it('sets null by default', function() { + it('sets null by default', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - return user.setTasks([task]).then(() => { - return user.destroy().then(() => { - return task.reload(); - }); - }); - }).then(task => { - expect(task.UserId).to.equal(null); - }); + await this.sequelize.sync({ force: true }); + + const [user, task0] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user.setTasks([task0]); + await user.destroy(); + const task = await task0.reload(); + expect(task.UserId).to.equal(null); }); - it('sets to CASCADE if allowNull: false', function() { + it('sets to CASCADE if allowNull: false', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { foreignKey: { allowNull: false } }); // defaults to CASCADE - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', UserId: user.id }).then(() => { - return user.destroy().then(() => { - return Task.findAll(); - }); - }); - }).then(tasks => { - expect(tasks).to.be.empty; - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + await Task.create({ title: 'task', UserId: user.id }); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.be.empty; }); - it('should be possible to remove all constraints', function() { + it('should be possible to remove all constraints', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { constraints: false }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.user = user; - ctx.task = task; - return user.setTasks([task]); - }).then(() => { - return ctx.user.destroy(); - }).then(() => { - return ctx.task.reload(); - }).then(task => { - expect(task.UserId).to.equal(ctx.user.id); - }); + await this.sequelize.sync({ force: true }); + + const [user, task0] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + const task = task0; + await user.setTasks([task0]); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(user.id); }); - it('can cascade deletes', function() { + it('can cascade deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onDelete: 'cascade' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.user = user; - ctx.task = task; - return user.setTasks([task]); - }).then(() => { - return ctx.user.destroy(); - }).then(() => { - return Task.findAll(); - }).then(tasks => { - expect(tasks).to.have.length(0); - }); + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user.setTasks([task]); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(0); }); // NOTE: mssql does not support changing an autoincrement primary key if (dialect !== 'mssql') { - it('can cascade updates', function() { + it('can cascade updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onUpdate: 'cascade' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - return user.setTasks([task]).then(() => user); - }).then(user => { - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); - }).then(() => { - return Task.findAll(); - }).then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].UserId).to.equal(999); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user0.setTasks([task]); + const user = user0; + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); + expect(tasks[0].UserId).to.equal(999); }); } if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes', function() { + it('can restrict deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onDelete: 'restrict' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.user = user; - ctx.task = task; - return user.setTasks([task]); - }).then(() => { - return ctx.user.destroy().catch(err => { - if (!(err instanceof Sequelize.ForeignKeyConstraintError)) throw err; - // Should fail due to FK violation - return Task.findAll(); - }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + let tasks; + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user.setTasks([task]); + + try { + tasks = await user.destroy(); + } catch (err) { + if (!(err instanceof Sequelize.ForeignKeyConstraintError)) + throw err; + + // Should fail due to FK violation + tasks = await Task.findAll(); + } + + expect(tasks).to.have.length(1); }); - it('can restrict updates', function() { + it('can restrict updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onUpdate: 'restrict' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - return user.setTasks([task]).then(() => user); - }).then(user => { - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - .catch(err => { - if (!(err instanceof Sequelize.ForeignKeyConstraintError)) throw err; - // Should fail due to FK violation - return Task.findAll(); - }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + let tasks; + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user0.setTasks([task]); + const user = user0; + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + + try { + tasks = await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + } catch (err) { + if (!(err instanceof Sequelize.ForeignKeyConstraintError)) + throw err; + + // Should fail due to FK violation + tasks = await Task.findAll(); + } + + expect(tasks).to.have.length(1); }); } }); @@ -1363,7 +1235,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { } }); - it('infers the keyType if none provided', function() { + it('infers the keyType if none provided', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.STRING, primaryKey: true }, username: DataTypes.STRING @@ -1374,9 +1246,8 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - expect(Task.rawAttributes.UserId.type instanceof DataTypes.STRING).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + expect(Task.rawAttributes.UserId.type instanceof DataTypes.STRING).to.be.ok; }); describe('allows the user to provide an attribute definition object as foreignKey', () => { @@ -1445,7 +1316,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { .throw('Naming collision between attribute \'user\' and association \'user\' on model user. To remedy this, change either foreignKey or as in your association definition'); }); - it('should ignore group from ancestor on deep separated query', function() { + it('should ignore group from ancestor on deep separated query', async function() { const User = this.sequelize.define('user', { userId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, username: Sequelize.STRING @@ -1462,29 +1333,26 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.hasMany(Job, { foreignKey: 'taskId' }); User.hasMany(Task, { foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }) - .then(() => { - return User.create({ - username: 'John Doe', - tasks: [ - { title: 'Task #1', jobs: [{ title: 'Job #1' }, { title: 'Job #2' }] }, - { title: 'Task #2', jobs: [{ title: 'Job #3' }, { title: 'Job #4' }] } - ] - }, { include: [{ model: Task, include: [Job] }] }); - }) - .then(() => { - return User.findAndCountAll({ - attributes: ['userId'], - include: [ - { model: Task, separate: true, include: [{ model: Job, separate: true }] } - ], - group: [['userId']] - }); - }) - .then(({ count, rows }) => { - expect(count.length).to.equal(1); - expect(rows[0].tasks[0].jobs.length).to.equal(2); - }); + await this.sequelize.sync({ force: true }); + + await User.create({ + username: 'John Doe', + tasks: [ + { title: 'Task #1', jobs: [{ title: 'Job #1' }, { title: 'Job #2' }] }, + { title: 'Task #2', jobs: [{ title: 'Job #3' }, { title: 'Job #4' }] } + ] + }, { include: [{ model: Task, include: [Job] }] }); + + const { count, rows } = await User.findAndCountAll({ + attributes: ['userId'], + include: [ + { model: Task, separate: true, include: [{ model: Job, separate: true }] } + ], + group: [['userId']] + }); + + expect(count.length).to.equal(1); + expect(rows[0].tasks[0].jobs.length).to.equal(2); }); }); @@ -1505,49 +1373,39 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { return this.sequelize.sync({ force: true }); }); - it('should use sourceKey', function() { + it('should use sourceKey', async function() { const User = this.User, Task = this.Task; - return User.create({ username: 'John', email: 'john@example.com' }).then(user => { - return Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }).then(() => { - return user.getTasks().then(tasks => { - expect(tasks.length).to.equal(1); - expect(tasks[0].title).to.equal('Fix PR'); - }); - }); - }); + const user = await User.create({ username: 'John', email: 'john@example.com' }); + await Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }); + const tasks = await user.getTasks(); + expect(tasks.length).to.equal(1); + expect(tasks[0].title).to.equal('Fix PR'); }); - it('should count related records', function() { + it('should count related records', async function() { const User = this.User, Task = this.Task; - return User.create({ username: 'John', email: 'john@example.com' }).then(user => { - return Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }).then(() => { - return user.countTasks().then(tasksCount => { - expect(tasksCount).to.equal(1); - }); - }); - }); + const user = await User.create({ username: 'John', email: 'john@example.com' }); + await Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }); + const tasksCount = await user.countTasks(); + expect(tasksCount).to.equal(1); }); - it('should set right field when add relative', function() { + it('should set right field when add relative', async function() { const User = this.User, Task = this.Task; - return User.create({ username: 'John', email: 'john@example.com' }).then(user => { - return Task.create({ title: 'Fix PR' }).then(task => { - return user.addTask(task).then(() => { - return user.hasTask(task.id).then(hasTask => { - expect(hasTask).to.be.true; - }); - }); - }); - }); + const user = await User.create({ username: 'John', email: 'john@example.com' }); + const task = await Task.create({ title: 'Fix PR' }); + await user.addTask(task); + const hasTask = await user.hasTask(task.id); + expect(hasTask).to.be.true; }); - it('should create with nested associated models', function() { + it('should create with nested associated models', async function() { const User = this.User, values = { username: 'John', @@ -1555,27 +1413,22 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { tasks: [{ title: 'Fix new PR' }] }; - return User.create(values, { include: ['tasks'] }) - .then(user => { - // Make sure tasks are defined for created user - expect(user).to.have.property('tasks'); - expect(user.tasks).to.be.an('array'); - expect(user.tasks).to.lengthOf(1); - expect(user.tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); - - return User.findOne({ where: { email: values.email } }); - }) - .then(user => - user.getTasks() - .then(tasks => { - // Make sure tasks relationship is successful - expect(tasks).to.be.an('array'); - expect(tasks).to.lengthOf(1); - expect(tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); - })); + const user0 = await User.create(values, { include: ['tasks'] }); + // Make sure tasks are defined for created user + expect(user0).to.have.property('tasks'); + expect(user0.tasks).to.be.an('array'); + expect(user0.tasks).to.lengthOf(1); + expect(user0.tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); + + const user = await User.findOne({ where: { email: values.email } }); + const tasks = await user.getTasks(); + // Make sure tasks relationship is successful + expect(tasks).to.be.an('array'); + expect(tasks).to.lengthOf(1); + expect(tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); }); - it('should create nested associations with symmetric getters/setters on FK', function() { + it('should create nested associations with symmetric getters/setters on FK', async function() { // Dummy getter/setter to test they are symmetric function toCustomFormat(string) { return string && `FORMAT-${string}`; @@ -1626,20 +1479,18 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { id: 'sJn369d8Em', children: [{ id: 'dgeQAQaW7A' }] }; - return this.sequelize.sync({ force: true }) - .then(() => Parent.create(values, { include: { model: Child, as: 'children' } })) - .then(father => { - // Make sure tasks are defined for created user - expect(father.id).to.be.equal('sJn369d8Em'); - expect(father.get('id', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); - - expect(father).to.have.property('children'); - expect(father.children).to.be.an('array'); - expect(father.children).to.lengthOf(1); - - expect(father.children[0].parent).to.be.equal('sJn369d8Em'); - expect(father.children[0].get('parent', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); - }); + await this.sequelize.sync({ force: true }); + const father = await Parent.create(values, { include: { model: Child, as: 'children' } }); + // Make sure tasks are defined for created user + expect(father.id).to.be.equal('sJn369d8Em'); + expect(father.get('id', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); + + expect(father).to.have.property('children'); + expect(father.children).to.be.an('array'); + expect(father.children).to.lengthOf(1); + + expect(father.children[0].parent).to.be.equal('sJn369d8Em'); + expect(father.children[0].get('parent', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); }); }); @@ -1660,27 +1511,27 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { return this.sequelize.sync({ force: true }); }); - it('should use the specified sourceKey instead of the primary key', function() { - return this.User.create({ username: 'John', email: 'john@example.com' }).then(() => - this.Task.bulkCreate([ - { title: 'Active Task', userEmail: 'john@example.com', taskStatus: 'Active' }, - { title: 'Inactive Task', userEmail: 'john@example.com', taskStatus: 'Inactive' } - ]) - ).then(() => - this.User.findOne({ - include: [ - { - model: this.Task, - where: { taskStatus: 'Active' } - } - ], - where: { username: 'John' } - }) - ).then(user => { - expect(user).to.be.ok; - expect(user.Tasks.length).to.equal(1); - expect(user.Tasks[0].title).to.equal('Active Task'); + it('should use the specified sourceKey instead of the primary key', async function() { + await this.User.create({ username: 'John', email: 'john@example.com' }); + + await this.Task.bulkCreate([ + { title: 'Active Task', userEmail: 'john@example.com', taskStatus: 'Active' }, + { title: 'Inactive Task', userEmail: 'john@example.com', taskStatus: 'Inactive' } + ]); + + const user = await this.User.findOne({ + include: [ + { + model: this.Task, + where: { taskStatus: 'Active' } + } + ], + where: { username: 'John' } }); + + expect(user).to.be.ok; + expect(user.Tasks.length).to.equal(1); + expect(user.Tasks[0].title).to.equal('Active Task'); }); }); @@ -1700,44 +1551,44 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghats' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghats' }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); }); }); }); diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index f779c5d7f965..81f7b97f5665 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -25,358 +25,276 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { describe('get', () => { describe('multiple', () => { - it('should fetch associations for multiple instances', function() { + it('should fetch associations for multiple instances', async function() { const User = this.sequelize.define('User', {}), Player = this.sequelize.define('Player', {}); Player.User = Player.hasOne(User, { as: 'user' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Player.create({ - id: 1, - user: {} - }, { - include: [Player.User] - }), Player.create({ - id: 2, - user: {} - }, { - include: [Player.User] - }), Player.create({ - id: 3 - })]); - }).then(players => { - return Player.User.get(players).then(result => { - expect(result[players[0].id].id).to.equal(players[0].user.id); - expect(result[players[1].id].id).to.equal(players[1].user.id); - expect(result[players[2].id]).to.equal(null); - }); - }); + await this.sequelize.sync({ force: true }); + + const players = await Promise.all([Player.create({ + id: 1, + user: {} + }, { + include: [Player.User] + }), Player.create({ + id: 2, + user: {} + }, { + include: [Player.User] + }), Player.create({ + id: 3 + })]); + + const result = await Player.User.get(players); + expect(result[players[0].id].id).to.equal(players[0].user.id); + expect(result[players[1].id].id).to.equal(players[1].user.id); + expect(result[players[2].id]).to.equal(null); }); }); }); describe('getAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.hasOne(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(fakeUser => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return Group.findAll({ transaction: t }).then(groups => { - return groups[0].getUser({ transaction: t }).then(associatedUser => { - expect(associatedUser).not.to.be.null; - expect(associatedUser.id).to.equal(user.id); - expect(associatedUser.id).not.to.equal(fakeUser.id); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.hasOne(User); + + await sequelize.sync({ force: true }); + const fakeUser = await User.create({ username: 'foo' }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + const groups0 = await Group.findAll({ transaction: t }); + const associatedUser0 = await groups0[0].getUser({ transaction: t }); + expect(associatedUser0).not.to.be.null; + expect(associatedUser0.id).to.equal(user.id); + expect(associatedUser0.id).not.to.equal(fakeUser.id); + await t.rollback(); }); } - it('should be able to handle a where object that\'s a first class citizen.', function() { + it('should be able to handle a where object that\'s a first class citizen.', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }); User.hasOne(Task); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', status: 'inactive' }).then(task => { - return user.setTaskXYZ(task).then(() => { - return user.getTaskXYZ({ where: { status: 'active' } }).then(task => { - expect(task).to.be.null; - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task', status: 'inactive' }); + await user.setTaskXYZ(task); + const task0 = await user.getTaskXYZ({ where: { status: 'active' } }); + expect(task0).to.be.null; }); - it('supports schemas', function() { + it('supports schemas', async function() { const User = this.sequelize.define('User', { username: Support.Sequelize.STRING }).schema('admin'), Group = this.sequelize.define('Group', { name: Support.Sequelize.STRING }).schema('admin'); Group.hasOne(User); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('admin'); - }).then(() => { - return Group.sync({ force: true }); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - User.create({ username: 'foo' }), - Group.create({ name: 'bar' }) - ]); - }).then(([fakeUser, user, group]) => { - return group.setUser(user).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).not.to.be.null; - expect(associatedUser.id).to.equal(user.id); - expect(associatedUser.id).not.to.equal(fakeUser.id); - }); - }); - }); - }).then(() => { - return this.sequelize.dropSchema('admin').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('admin'); - } - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('admin'); + await Group.sync({ force: true }); + await User.sync({ force: true }); + + const [fakeUser, user, group] = await Promise.all([ + User.create({ username: 'foo' }), + User.create({ username: 'foo' }), + Group.create({ name: 'bar' }) + ]); + + await group.setUser(user); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).not.to.be.null; + expect(associatedUser.id).to.equal(user.id); + expect(associatedUser.id).not.to.equal(fakeUser.id); + await this.sequelize.dropSchema('admin'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('admin'); + } }); }); describe('setAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.hasOne(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.hasOne(User); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + await t.rollback(); }); } - it('can set an association with predefined primary keys', function() { + it('can set an association with predefined primary keys', async function() { const User = this.sequelize.define('UserXYZZ', { userCoolIdTag: { type: Sequelize.INTEGER, primaryKey: true }, username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZZ', { taskOrSomething: { type: Sequelize.INTEGER, primaryKey: true }, title: Sequelize.STRING }); User.hasOne(Task, { foreignKey: 'userCoolIdTag' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ userCoolIdTag: 1, username: 'foo' }).then(user => { - return Task.create({ taskOrSomething: 1, title: 'bar' }).then(task => { - return user.setTaskXYZZ(task).then(() => { - return user.getTaskXYZZ().then(task => { - expect(task).not.to.be.null; - - return user.setTaskXYZZ(null).then(() => { - return user.getTaskXYZZ().then(_task => { - expect(_task).to.be.null; - }); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ userCoolIdTag: 1, username: 'foo' }); + const task = await Task.create({ taskOrSomething: 1, title: 'bar' }); + await user.setTaskXYZZ(task); + const task0 = await user.getTaskXYZZ(); + expect(task0).not.to.be.null; + + await user.setTaskXYZZ(null); + const _task = await user.getTaskXYZZ(); + expect(_task).to.be.null; }); - it('clears the association if null is passed', function() { + it('clears the association if null is passed', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTaskXYZ(task).then(() => { - return user.getTaskXYZ().then(task => { - expect(task).not.to.equal(null); - - return user.setTaskXYZ(null).then(() => { - return user.getTaskXYZ().then(task => { - expect(task).to.equal(null); - }); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTaskXYZ(task); + const task1 = await user.getTaskXYZ(); + expect(task1).not.to.equal(null); + + await user.setTaskXYZ(null); + const task0 = await user.getTaskXYZ(); + expect(task0).to.equal(null); }); - it('should throw a ForeignKeyConstraintError if the associated record does not exist', function() { + it('should throw a ForeignKeyConstraintError if the associated record does not exist', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.create({ title: 'task' }).then(task => { - return expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + await expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const task = await Task.create({ title: 'task' }); + + await expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - return Task.create({ id: 19, title: 'task it!' }).then(task => { - return user.setTaskXYZ(task.id).then(() => { - return user.getTaskXYZ().then(task => { - expect(task.title).to.equal('task it!'); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + const task = await Task.create({ id: 19, title: 'task it!' }); + await user.setTaskXYZ(task.id); + const task0 = await user.getTaskXYZ(); + expect(task0.title).to.equal('task it!'); }); - it('supports updating with a primary key instead of an object', function() { + it('supports updating with a primary key instead of an object', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 1, username: 'foo' }), - Task.create({ id: 20, title: 'bar' }) - ]); - }) - .then(([user, task]) => { - return user.setTaskXYZ(task.id) - .then(() => user.getTaskXYZ()) - .then(task => { - expect(task).not.to.be.null; - return Promise.all([ - user, - Task.create({ id: 2, title: 'bar2' }) - ]); - }); - }) - .then(([user, task2]) => { - return user.setTaskXYZ(task2.id) - .then(() => user.getTaskXYZ()) - .then(task => { - expect(task).not.to.be.null; - }); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1] = await Promise.all([ + User.create({ id: 1, username: 'foo' }), + Task.create({ id: 20, title: 'bar' }) + ]); + + await user0.setTaskXYZ(task1.id); + const task0 = await user0.getTaskXYZ(); + expect(task0).not.to.be.null; + + const [user, task2] = await Promise.all([ + user0, + Task.create({ id: 2, title: 'bar2' }) + ]); + + await user.setTaskXYZ(task2.id); + const task = await user.getTaskXYZ(); + expect(task).not.to.be.null; }); - it('supports setting same association twice', function() { + it('supports setting same association twice', async function() { const Home = this.sequelize.define('home', {}), User = this.sequelize.define('user'); User.hasOne(Home); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Home.create(), - User.create() - ]); - }).then(([home, user]) => { - ctx.home = home; - ctx.user = user; - return user.setHome(home); - }).then(() => { - return ctx.user.setHome(ctx.home); - }).then(() => { - return expect(ctx.user.getHome()).to.eventually.have.property('id', ctx.home.get('id')); - }); + await this.sequelize.sync({ force: true }); + + const [home, user] = await Promise.all([ + Home.create(), + User.create() + ]); + + await user.setHome(home); + await user.setHome(home); + await expect(user.getHome()).to.eventually.have.property('id', home.get('id')); }); }); describe('createAssociation', () => { - it('creates an associated model instance', function() { + it('creates an associated model instance', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }), Task = this.sequelize.define('Task', { title: Sequelize.STRING }); User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - return user.createTask({ title: 'task' }).then(() => { - return user.getTask().then(task => { - expect(task).not.to.be.null; - expect(task.title).to.equal('task'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + await user.createTask({ title: 'task' }); + const task = await user.getTask(); + expect(task).not.to.be.null; + expect(task.title).to.equal('task'); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }), - Group = sequelize.define('Group', { name: Sequelize.STRING }); - - User.hasOne(Group); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - return sequelize.transaction().then(t => { - return user.createGroup({ name: 'testgroup' }, { transaction: t }).then(() => { - return User.findAll().then(users => { - return users[0].getGroup().then(group => { - expect(group).to.be.null; - return User.findAll({ transaction: t }).then(users => { - return users[0].getGroup({ transaction: t }).then(group => { - expect(group).to.be.not.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }), + Group = sequelize.define('Group', { name: Sequelize.STRING }); + + User.hasOne(Group); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + const t = await sequelize.transaction(); + await user.createGroup({ name: 'testgroup' }, { transaction: t }); + const users = await User.findAll(); + const group = await users[0].getGroup(); + expect(group).to.be.null; + const users0 = await User.findAll({ transaction: t }); + const group0 = await users0[0].getGroup({ transaction: t }); + expect(group0).to.be.not.null; + await t.rollback(); }); } @@ -403,7 +321,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { expect(User.rawAttributes.AccountId.field).to.equal('AccountId'); }); - it('should support specifying the field of a foreign key', function() { + it('should support specifying the field of a foreign key', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }); @@ -416,223 +334,176 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { expect(User.rawAttributes.taskId).to.exist; expect(User.rawAttributes.taskId.field).to.equal('task_id'); - return Task.sync({ force: true }).then(() => { - // Can't use Promise.all cause of foreign key references - return User.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - }).then(([user, task]) => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ(); - }); - }).then(user => { - // the sql query should correctly look at task_id instead of taskId - expect(user).to.not.be.null; - return Task.findOne({ - where: { title: 'task' }, - include: [User] - }); - }).then(task => { - expect(task.UserXYZ).to.exist; + await Task.sync({ force: true }); + await User.sync({ force: true }); + + const [user0, task0] = await Promise.all([ + User.create({ username: 'foo', gender: 'male' }), + Task.create({ title: 'task', status: 'inactive' }) + ]); + + await task0.setUserXYZ(user0); + const user = await task0.getUserXYZ(); + // the sql query should correctly look at task_id instead of taskId + expect(user).to.not.be.null; + + const task = await Task.findOne({ + where: { title: 'task' }, + include: [User] }); + + expect(task.UserXYZ).to.exist; }); }); describe('foreign key constraints', () => { - it('are enabled by default', function() { + it('are enabled by default', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task); // defaults to set NULL - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(null); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(null); }); - it('sets to CASCADE if allowNull: false', function() { + it('sets to CASCADE if allowNull: false', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { foreignKey: { allowNull: false } }); // defaults to CASCADE - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', UserId: user.id }).then(() => { - return user.destroy().then(() => { - return Task.findAll(); - }); - }); - }).then(tasks => { - expect(tasks).to.be.empty; - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + await Task.create({ title: 'task', UserId: user.id }); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.be.empty; }); - it('should be possible to disable them', function() { + it('should be possible to disable them', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { constraints: false }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(user.id); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(user.id); }); - it('can cascade deletes', function() { + it('can cascade deletes', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onDelete: 'cascade' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return user.destroy().then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(0); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(0); }); - it('works when cascading a delete with hooks but there is no associate (i.e. "has zero")', function() { + it('works when cascading a delete with hooks but there is no associate (i.e. "has zero")', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onDelete: 'cascade', hooks: true }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return user.destroy(); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + + await user.destroy(); }); // NOTE: mssql does not support changing an autoincrement primary key if (Support.getTestDialect() !== 'mssql') { - it('can cascade updates', function() { + it('can cascade updates', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onUpdate: 'cascade' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].UserId).to.equal(999); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); + expect(tasks[0].UserId).to.equal(999); }); } if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes', function() { + it('can restrict deletes', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onDelete: 'restrict' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); }); - it('can restrict updates', function() { + it('can restrict updates', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onUpdate: 'restrict' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); - return expect( - user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - // Should fail due to FK restriction - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + + await expect( + user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) + ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + + // Should fail due to FK restriction + const tasks = await Task.findAll(); + + expect(tasks).to.have.length(1); }); } @@ -640,7 +511,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { }); describe('association column', () => { - it('has correct type for non-id primary keys with non-integer type', function() { + it('has correct type for non-id primary keys with non-integer type', async function() { const User = this.sequelize.define('UserPKBT', { username: { type: Sequelize.STRING @@ -656,12 +527,11 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { Group.hasOne(User); - return this.sequelize.sync({ force: true }).then(() => { - expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(Sequelize.STRING); - }); + await this.sequelize.sync({ force: true }); + expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(Sequelize.STRING); }); - it('should support a non-primary key as the association column on a target with custom primary key', function() { + it('should support a non-primary key as the association column on a target with custom primary key', async function() { const User = this.sequelize.define('User', { user_name: { unique: true, @@ -676,22 +546,16 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { User.hasOne(Task, { foreignKey: 'username', sourceKey: 'user_name' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ user_name: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newUser.setTask(newTask).then(() => { - return User.findOne({ where: { user_name: 'bob' } }).then(foundUser => { - return foundUser.getTask().then(foundTask => { - expect(foundTask.title).to.equal('some task'); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ user_name: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newUser.setTask(newTask); + const foundUser = await User.findOne({ where: { user_name: 'bob' } }); + const foundTask = await foundUser.getTask(); + expect(foundTask.title).to.equal('some task'); }); - it('should support a non-primary unique key as the association column', function() { + it('should support a non-primary unique key as the association column', async function() { const User = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -706,22 +570,16 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newUser.setTask(newTask).then(() => { - return User.findOne({ where: { username: 'bob' } }).then(foundUser => { - return foundUser.getTask().then(foundTask => { - expect(foundTask.title).to.equal('some task'); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newUser.setTask(newTask); + const foundUser = await User.findOne({ where: { username: 'bob' } }); + const foundTask = await foundUser.getTask(); + expect(foundTask.title).to.equal('some task'); }); - it('should support a non-primary unique key as the association column with a field option', function() { + it('should support a non-primary unique key as the association column with a field option', async function() { const User = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -737,37 +595,30 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newUser.setTask(newTask).then(() => { - return User.findOne({ where: { username: 'bob' } }).then(foundUser => { - return foundUser.getTask().then(foundTask => { - expect(foundTask.title).to.equal('some task'); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newUser.setTask(newTask); + const foundUser = await User.findOne({ where: { username: 'bob' } }); + const foundTask = await foundUser.getTask(); + expect(foundTask.title).to.equal('some task'); }); }); describe('Association options', () => { - it('can specify data type for autogenerated relational keys', function() { + it('can specify data type for autogenerated relational keys', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING], Tasks = {}; - return Promise.all(dataTypes.map(dataType => { + await Promise.all(dataTypes.map(async dataType => { const tableName = `TaskXYZ_${dataType.key}`; Tasks[dataType] = this.sequelize.define(tableName, { title: Sequelize.STRING }); User.hasOne(Tasks[dataType], { foreignKey: 'userId', keyType: dataType, constraints: false }); - return Tasks[dataType].sync({ force: true }).then(() => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); + await Tasks[dataType].sync({ force: true }); + expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); })); }); @@ -893,53 +744,53 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghat' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ - model: this.Hat, - as: { singular: 'personwearinghat' } - }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual1, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual1.setPersonwearinghat(hat); + + const individual0 = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghat' }] + }); + + expect(individual0.name).to.equal('Foo Bar'); + expect(individual0.personwearinghat.name).to.equal('Baz'); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ + model: this.Hat, + as: { singular: 'personwearinghat' } + }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' }) - ]); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.setPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); }); }); diff --git a/test/integration/associations/multiple-level-filters.test.js b/test/integration/associations/multiple-level-filters.test.js index 17baadd9eebd..267cbbe6249a 100644 --- a/test/integration/associations/multiple-level-filters.test.js +++ b/test/integration/associations/multiple-level-filters.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { - it('can filter through belongsTo', function() { + it('can filter through belongsTo', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -17,55 +17,54 @@ describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { Task.belongsTo(Project); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - UserId: 1, - title: 'republic' - }, { - UserId: 2, - title: 'empire' - }]).then(() => { - return Task.bulkCreate([{ - ProjectId: 1, - title: 'fight empire' - }, { - ProjectId: 1, - title: 'stablish republic' - }, { - ProjectId: 2, - title: 'destroy rebel alliance' - }, { - ProjectId: 2, - title: 'rule everything' - }]).then(() => { - return Task.findAll({ - include: [ - { - model: Project, - include: [ - { model: User, where: { username: 'leia' } } - ], - required: true - } - ] - }).then(tasks => { - - expect(tasks.length).to.be.equal(2); - expect(tasks[0].title).to.be.equal('fight empire'); - expect(tasks[1].title).to.be.equal('stablish republic'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + UserId: 1, + title: 'republic' + }, { + UserId: 2, + title: 'empire' + }]); + + await Task.bulkCreate([{ + ProjectId: 1, + title: 'fight empire' + }, { + ProjectId: 1, + title: 'stablish republic' + }, { + ProjectId: 2, + title: 'destroy rebel alliance' + }, { + ProjectId: 2, + title: 'rule everything' + }]); + + const tasks = await Task.findAll({ + include: [ + { + model: Project, + include: [ + { model: User, where: { username: 'leia' } } + ], + required: true + } + ] }); + + expect(tasks.length).to.be.equal(2); + expect(tasks[0].title).to.be.equal('fight empire'); + expect(tasks[1].title).to.be.equal('stablish republic'); }); - it('avoids duplicated tables in query', function() { + it('avoids duplicated tables in query', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -76,57 +75,57 @@ describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { Task.belongsTo(Project); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - UserId: 1, - title: 'republic' - }, { - UserId: 2, - title: 'empire' - }]).then(() => { - return Task.bulkCreate([{ - ProjectId: 1, - title: 'fight empire' - }, { - ProjectId: 1, - title: 'stablish republic' - }, { - ProjectId: 2, - title: 'destroy rebel alliance' - }, { - ProjectId: 2, - title: 'rule everything' - }]).then(() => { - return Task.findAll({ - include: [ - { - model: Project, - include: [ - { model: User, where: { - username: 'leia', - id: 1 - } } - ], - required: true - } - ] - }).then(tasks => { - expect(tasks.length).to.be.equal(2); - expect(tasks[0].title).to.be.equal('fight empire'); - expect(tasks[1].title).to.be.equal('stablish republic'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + UserId: 1, + title: 'republic' + }, { + UserId: 2, + title: 'empire' + }]); + + await Task.bulkCreate([{ + ProjectId: 1, + title: 'fight empire' + }, { + ProjectId: 1, + title: 'stablish republic' + }, { + ProjectId: 2, + title: 'destroy rebel alliance' + }, { + ProjectId: 2, + title: 'rule everything' + }]); + + const tasks = await Task.findAll({ + include: [ + { + model: Project, + include: [ + { model: User, where: { + username: 'leia', + id: 1 + } } + ], + required: true + } + ] }); + + expect(tasks.length).to.be.equal(2); + expect(tasks[0].title).to.be.equal('fight empire'); + expect(tasks[1].title).to.be.equal('stablish republic'); }); - it('can filter through hasMany', function() { + it('can filter through hasMany', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -137,92 +136,87 @@ describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { Task.belongsTo(Project); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - UserId: 1, - title: 'republic' - }, { - UserId: 2, - title: 'empire' - }]).then(() => { - return Task.bulkCreate([{ - ProjectId: 1, - title: 'fight empire' - }, { - ProjectId: 1, - title: 'stablish republic' - }, { - ProjectId: 2, - title: 'destroy rebel alliance' - }, { - ProjectId: 2, - title: 'rule everything' - }]).then(() => { - return User.findAll({ - include: [ - { - model: Project, - include: [ - { model: Task, where: { title: 'fight empire' } } - ], - required: true - } - ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('leia'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + UserId: 1, + title: 'republic' + }, { + UserId: 2, + title: 'empire' + }]); + + await Task.bulkCreate([{ + ProjectId: 1, + title: 'fight empire' + }, { + ProjectId: 1, + title: 'stablish republic' + }, { + ProjectId: 2, + title: 'destroy rebel alliance' + }, { + ProjectId: 2, + title: 'rule everything' + }]); + + const users = await User.findAll({ + include: [ + { + model: Project, + include: [ + { model: Task, where: { title: 'fight empire' } } + ], + required: true + } + ] }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('leia'); }); - it('can filter through hasMany connector', function() { + it('can filter through hasMany connector', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); Project.belongsToMany(User, { through: 'user_project' }); User.belongsToMany(Project, { through: 'user_project' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - title: 'republic' - }, { - title: 'empire' - }]).then(() => { - return User.findByPk(1).then(user => { - return Project.findByPk(1).then(project => { - return user.setProjects([project]).then(() => { - return User.findByPk(2).then(user => { - return Project.findByPk(2).then(project => { - return user.setProjects([project]).then(() => { - return User.findAll({ - include: [ - { model: Project, where: { title: 'republic' } } - ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('leia'); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + title: 'republic' + }, { + title: 'empire' + }]); + + const user = await User.findByPk(1); + const project = await Project.findByPk(1); + await user.setProjects([project]); + const user0 = await User.findByPk(2); + const project0 = await Project.findByPk(2); + await user0.setProjects([project0]); + + const users = await User.findAll({ + include: [ + { model: Project, where: { title: 'republic' } } + ] }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('leia'); }); }); diff --git a/test/integration/associations/scope.test.js b/test/integration/associations/scope.test.js index 536559660670..bd2b0efa1df8 100644 --- a/test/integration/associations/scope.test.js +++ b/test/integration/associations/scope.test.js @@ -96,164 +96,158 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); describe('1:1', () => { - it('should create, find and include associations with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([this.Post.create(), this.Comment.create({ - title: 'I am a comment' - }), this.Comment.create({ - title: 'I am a main comment', - isMain: true - })]); - }).then(([post]) => { - this.post = post; - return post.createComment({ - title: 'I am a post comment' - }); - }).then(comment => { - expect(comment.get('commentable')).to.equal('post'); - expect(comment.get('isMain')).to.be.false; - return this.Post.scope('withMainComment').findByPk(this.post.get('id')); - }).then(post => { - expect(post.mainComment).to.be.null; - return post.createMainComment({ - title: 'I am a main post comment' - }); - }).then(mainComment => { - this.mainComment = mainComment; - expect(mainComment.get('commentable')).to.equal('post'); - expect(mainComment.get('isMain')).to.be.true; - return this.Post.scope('withMainComment').findByPk(this.post.id); - }).then(post => { - expect(post.mainComment.get('id')).to.equal(this.mainComment.get('id')); - return post.getMainComment(); - }).then(mainComment => { - expect(mainComment.get('commentable')).to.equal('post'); - expect(mainComment.get('isMain')).to.be.true; - return this.Comment.create({ - title: 'I am a future main comment' - }); - }).then(comment => { - return this.post.setMainComment(comment); - }).then(() => { - return this.post.getMainComment(); - }).then(mainComment => { - expect(mainComment.get('commentable')).to.equal('post'); - expect(mainComment.get('isMain')).to.be.true; - expect(mainComment.get('title')).to.equal('I am a future main comment'); + it('should create, find and include associations with scope values', async function() { + await this.sequelize.sync({ force: true }); + + const [post1] = await Promise.all([this.Post.create(), this.Comment.create({ + title: 'I am a comment' + }), this.Comment.create({ + title: 'I am a main comment', + isMain: true + })]); + + this.post = post1; + + const comment0 = await post1.createComment({ + title: 'I am a post comment' + }); + + expect(comment0.get('commentable')).to.equal('post'); + expect(comment0.get('isMain')).to.be.false; + const post0 = await this.Post.scope('withMainComment').findByPk(this.post.get('id')); + expect(post0.mainComment).to.be.null; + + const mainComment1 = await post0.createMainComment({ + title: 'I am a main post comment' + }); + + this.mainComment = mainComment1; + expect(mainComment1.get('commentable')).to.equal('post'); + expect(mainComment1.get('isMain')).to.be.true; + const post = await this.Post.scope('withMainComment').findByPk(this.post.id); + expect(post.mainComment.get('id')).to.equal(this.mainComment.get('id')); + const mainComment0 = await post.getMainComment(); + expect(mainComment0.get('commentable')).to.equal('post'); + expect(mainComment0.get('isMain')).to.be.true; + + const comment = await this.Comment.create({ + title: 'I am a future main comment' }); + + await this.post.setMainComment(comment); + const mainComment = await this.post.getMainComment(); + expect(mainComment.get('commentable')).to.equal('post'); + expect(mainComment.get('isMain')).to.be.true; + expect(mainComment.get('title')).to.equal('I am a future main comment'); }); - it('should create included association with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return this.Post.create({ - mainComment: { - title: 'I am a main comment created with a post' - } - }, { - include: [{ model: this.Comment, as: 'mainComment' }] - }); - }).then(post => { - expect(post.mainComment.get('commentable')).to.equal('post'); - expect(post.mainComment.get('isMain')).to.be.true; - return this.Post.scope('withMainComment').findByPk(post.id); - }).then(post => { - expect(post.mainComment.get('commentable')).to.equal('post'); - expect(post.mainComment.get('isMain')).to.be.true; + it('should create included association with scope values', async function() { + await this.sequelize.sync({ force: true }); + + const post0 = await this.Post.create({ + mainComment: { + title: 'I am a main comment created with a post' + } + }, { + include: [{ model: this.Comment, as: 'mainComment' }] }); + + expect(post0.mainComment.get('commentable')).to.equal('post'); + expect(post0.mainComment.get('isMain')).to.be.true; + const post = await this.Post.scope('withMainComment').findByPk(post0.id); + expect(post.mainComment.get('commentable')).to.equal('post'); + expect(post.mainComment.get('isMain')).to.be.true; }); }); describe('1:M', () => { - it('should create, find and include associations with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Post.create(), - this.Image.create(), - this.Question.create(), - this.Comment.create({ - title: 'I am a image comment' - }), - this.Comment.create({ - title: 'I am a question comment' - }) - ]); - }).then(([post, image, question, commentA, commentB]) => { - this.post = post; - this.image = image; - this.question = question; - return Promise.all([post.createComment({ - title: 'I am a post comment' - }), image.addComment(commentA), question.setComments([commentB])]); - }).then(() => { - return this.Comment.findAll(); - }).then(comments => { - comments.forEach(comment => { - expect(comment.get('commentable')).to.be.ok; - }); - expect(comments.map(comment => { - return comment.get('commentable'); - }).sort()).to.deep.equal(['image', 'post', 'question']); - }).then(() => { - return Promise.all([ - this.post.getComments(), - this.image.getComments(), - this.question.getComments() - ]); - }).then(([postComments, imageComments, questionComments]) => { - expect(postComments.length).to.equal(1); - expect(postComments[0].get('title')).to.equal('I am a post comment'); - expect(imageComments.length).to.equal(1); - expect(imageComments[0].get('title')).to.equal('I am a image comment'); - expect(questionComments.length).to.equal(1); - expect(questionComments[0].get('title')).to.equal('I am a question comment'); - - return [postComments[0], imageComments[0], questionComments[0]]; - }).then(([postComment, imageComment, questionComment]) => { - return Promise.all([postComment.getItem(), imageComment.getItem(), questionComment.getItem()]); - }).then(([post, image, question]) => { - expect(post).to.be.instanceof(this.Post); - expect(image).to.be.instanceof(this.Image); - expect(question).to.be.instanceof(this.Question); - }).then(() => { - return Promise.all([this.Post.findOne({ - include: [this.Comment] - }), this.Image.findOne({ - include: [this.Comment] - }), this.Question.findOne({ - include: [this.Comment] - })]); - }).then(([post, image, question]) => { - expect(post.comments.length).to.equal(1); - expect(post.comments[0].get('title')).to.equal('I am a post comment'); - expect(image.comments.length).to.equal(1); - expect(image.comments[0].get('title')).to.equal('I am a image comment'); - expect(question.comments.length).to.equal(1); - expect(question.comments[0].get('title')).to.equal('I am a question comment'); + it('should create, find and include associations with scope values', async function() { + await this.sequelize.sync({ force: true }); + + const [post1, image1, question1, commentA, commentB] = await Promise.all([ + this.Post.create(), + this.Image.create(), + this.Question.create(), + this.Comment.create({ + title: 'I am a image comment' + }), + this.Comment.create({ + title: 'I am a question comment' + }) + ]); + + this.post = post1; + this.image = image1; + this.question = question1; + + await Promise.all([post1.createComment({ + title: 'I am a post comment' + }), image1.addComment(commentA), question1.setComments([commentB])]); + + const comments = await this.Comment.findAll(); + comments.forEach(comment => { + expect(comment.get('commentable')).to.be.ok; }); + expect(comments.map(comment => { + return comment.get('commentable'); + }).sort()).to.deep.equal(['image', 'post', 'question']); + + const [postComments, imageComments, questionComments] = await Promise.all([ + this.post.getComments(), + this.image.getComments(), + this.question.getComments() + ]); + + expect(postComments.length).to.equal(1); + expect(postComments[0].get('title')).to.equal('I am a post comment'); + expect(imageComments.length).to.equal(1); + expect(imageComments[0].get('title')).to.equal('I am a image comment'); + expect(questionComments.length).to.equal(1); + expect(questionComments[0].get('title')).to.equal('I am a question comment'); + + const [postComment, imageComment, questionComment] = [postComments[0], imageComments[0], questionComments[0]]; + const [post0, image0, question0] = await Promise.all([postComment.getItem(), imageComment.getItem(), questionComment.getItem()]); + expect(post0).to.be.instanceof(this.Post); + expect(image0).to.be.instanceof(this.Image); + expect(question0).to.be.instanceof(this.Question); + + const [post, image, question] = await Promise.all([this.Post.findOne({ + include: [this.Comment] + }), this.Image.findOne({ + include: [this.Comment] + }), this.Question.findOne({ + include: [this.Comment] + })]); + + expect(post.comments.length).to.equal(1); + expect(post.comments[0].get('title')).to.equal('I am a post comment'); + expect(image.comments.length).to.equal(1); + expect(image.comments[0].get('title')).to.equal('I am a image comment'); + expect(question.comments.length).to.equal(1); + expect(question.comments[0].get('title')).to.equal('I am a question comment'); }); - it('should make the same query if called multiple time (#4470)', function() { + it('should make the same query if called multiple time (#4470)', async function() { const logs = []; const logging = function(log) { //removing 'executing( || 'default'}) :' from logs logs.push(log.substring(log.indexOf(':') + 1)); }; - return this.sequelize.sync({ force: true }).then(() => { - return this.Post.create(); - }).then(post => { - return post.createComment({ - title: 'I am a post comment' - }); - }).then(() => { - return this.Post.scope('withComments').findAll({ - logging - }); - }).then(() => { - return this.Post.scope('withComments').findAll({ - logging - }); - }).then(() => { - expect(logs[0]).to.equal(logs[1]); + await this.sequelize.sync({ force: true }); + const post = await this.Post.create(); + + await post.createComment({ + title: 'I am a post comment' + }); + + await this.Post.scope('withComments').findAll({ + logging + }); + + await this.Post.scope('withComments').findAll({ + logging }); + + expect(logs[0]).to.equal(logs[1]); }); it('should created included association with scope values', async function() { await this.sequelize.sync({ force: true }); @@ -275,34 +269,34 @@ describe(Support.getTestDialectTeaser('associations'), () => { expect(comment.get('commentable')).to.equal('post'); } }); - it('should include associations with operator scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([this.Post.create(), this.Comment.create({ - title: 'I am a blue comment', - type: 'blue' - }), this.Comment.create({ - title: 'I am a red comment', - type: 'red' - }), this.Comment.create({ - title: 'I am a green comment', - type: 'green' - })]); - }).then(([post, commentA, commentB, commentC]) => { - this.post = post; - return post.addComments([commentA, commentB, commentC]); - }).then(() => { - return this.Post.findByPk(this.post.id, { - include: [{ - model: this.Comment, - as: 'coloredComments' - }] - }); - }).then(post => { - expect(post.coloredComments.length).to.equal(2); - for (const comment of post.coloredComments) { - expect(comment.type).to.match(/blue|green/); - } + it('should include associations with operator scope values', async function() { + await this.sequelize.sync({ force: true }); + + const [post0, commentA, commentB, commentC] = await Promise.all([this.Post.create(), this.Comment.create({ + title: 'I am a blue comment', + type: 'blue' + }), this.Comment.create({ + title: 'I am a red comment', + type: 'red' + }), this.Comment.create({ + title: 'I am a green comment', + type: 'green' + })]); + + this.post = post0; + await post0.addComments([commentA, commentB, commentC]); + + const post = await this.Post.findByPk(this.post.id, { + include: [{ + model: this.Comment, + as: 'coloredComments' + }] }); + + expect(post.coloredComments.length).to.equal(2); + for (const comment of post.coloredComments) { + expect(comment.type).to.match(/blue|green/); + } }); }); @@ -321,96 +315,95 @@ describe(Support.getTestDialectTeaser('associations'), () => { this.Post.belongsToMany(this.Tag, { as: 'tags', through: this.PostTag, scope: { type: 'tag' } }); }); - it('should create, find and include associations with scope values', function() { - return Promise.all([this.Post.sync({ force: true }), this.Tag.sync({ force: true })]).then(() => { - return this.PostTag.sync({ force: true }); - }).then(() => { - return Promise.all([ - this.Post.create(), - this.Post.create(), - this.Post.create(), - this.Tag.create({ type: 'category' }), - this.Tag.create({ type: 'category' }), - this.Tag.create({ type: 'tag' }), - this.Tag.create({ type: 'tag' }) - ]); - }).then(([postA, postB, postC, categoryA, categoryB, tagA, tagB]) => { - this.postA = postA; - this.postB = postB; - this.postC = postC; - - return Promise.all([ - postA.addCategory(categoryA), - postB.setCategories([categoryB]), - postC.createCategory(), - postA.createTag(), - postB.addTag(tagA), - postC.setTags([tagB]) - ]); - }).then(() => { - return Promise.all([ - this.postA.getCategories(), - this.postA.getTags(), - this.postB.getCategories(), - this.postB.getTags(), - this.postC.getCategories(), - this.postC.getTags() - ]); - }).then(([postACategories, postATags, postBCategories, postBTags, postCCategories, postCTags]) => { - expect(postACategories.length).to.equal(1); - expect(postATags.length).to.equal(1); - expect(postBCategories.length).to.equal(1); - expect(postBTags.length).to.equal(1); - expect(postCCategories.length).to.equal(1); - expect(postCTags.length).to.equal(1); - - expect(postACategories[0].get('type')).to.equal('category'); - expect(postATags[0].get('type')).to.equal('tag'); - expect(postBCategories[0].get('type')).to.equal('category'); - expect(postBTags[0].get('type')).to.equal('tag'); - expect(postCCategories[0].get('type')).to.equal('category'); - expect(postCTags[0].get('type')).to.equal('tag'); - }).then(() => { - return Promise.all([this.Post.findOne({ - where: { - id: this.postA.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }), this.Post.findOne({ - where: { - id: this.postB.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }), this.Post.findOne({ - where: { - id: this.postC.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - })]); - }).then(([postA, postB, postC]) => { - expect(postA.get('categories').length).to.equal(1); - expect(postA.get('tags').length).to.equal(1); - expect(postB.get('categories').length).to.equal(1); - expect(postB.get('tags').length).to.equal(1); - expect(postC.get('categories').length).to.equal(1); - expect(postC.get('tags').length).to.equal(1); - - expect(postA.get('categories')[0].get('type')).to.equal('category'); - expect(postA.get('tags')[0].get('type')).to.equal('tag'); - expect(postB.get('categories')[0].get('type')).to.equal('category'); - expect(postB.get('tags')[0].get('type')).to.equal('tag'); - expect(postC.get('categories')[0].get('type')).to.equal('category'); - expect(postC.get('tags')[0].get('type')).to.equal('tag'); - }); + it('should create, find and include associations with scope values', async function() { + await Promise.all([this.Post.sync({ force: true }), this.Tag.sync({ force: true })]); + await this.PostTag.sync({ force: true }); + + const [postA0, postB0, postC0, categoryA, categoryB, tagA, tagB] = await Promise.all([ + this.Post.create(), + this.Post.create(), + this.Post.create(), + this.Tag.create({ type: 'category' }), + this.Tag.create({ type: 'category' }), + this.Tag.create({ type: 'tag' }), + this.Tag.create({ type: 'tag' }) + ]); + + this.postA = postA0; + this.postB = postB0; + this.postC = postC0; + + await Promise.all([ + postA0.addCategory(categoryA), + postB0.setCategories([categoryB]), + postC0.createCategory(), + postA0.createTag(), + postB0.addTag(tagA), + postC0.setTags([tagB]) + ]); + + const [postACategories, postATags, postBCategories, postBTags, postCCategories, postCTags] = await Promise.all([ + this.postA.getCategories(), + this.postA.getTags(), + this.postB.getCategories(), + this.postB.getTags(), + this.postC.getCategories(), + this.postC.getTags() + ]); + + expect(postACategories.length).to.equal(1); + expect(postATags.length).to.equal(1); + expect(postBCategories.length).to.equal(1); + expect(postBTags.length).to.equal(1); + expect(postCCategories.length).to.equal(1); + expect(postCTags.length).to.equal(1); + + expect(postACategories[0].get('type')).to.equal('category'); + expect(postATags[0].get('type')).to.equal('tag'); + expect(postBCategories[0].get('type')).to.equal('category'); + expect(postBTags[0].get('type')).to.equal('tag'); + expect(postCCategories[0].get('type')).to.equal('category'); + expect(postCTags[0].get('type')).to.equal('tag'); + + const [postA, postB, postC] = await Promise.all([this.Post.findOne({ + where: { + id: this.postA.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + }), this.Post.findOne({ + where: { + id: this.postB.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + }), this.Post.findOne({ + where: { + id: this.postC.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + })]); + + expect(postA.get('categories').length).to.equal(1); + expect(postA.get('tags').length).to.equal(1); + expect(postB.get('categories').length).to.equal(1); + expect(postB.get('tags').length).to.equal(1); + expect(postC.get('categories').length).to.equal(1); + expect(postC.get('tags').length).to.equal(1); + + expect(postA.get('categories')[0].get('type')).to.equal('category'); + expect(postA.get('tags')[0].get('type')).to.equal('tag'); + expect(postB.get('categories')[0].get('type')).to.equal('category'); + expect(postB.get('tags')[0].get('type')).to.equal('tag'); + expect(postC.get('categories')[0].get('type')).to.equal('category'); + expect(postC.get('tags')[0].get('type')).to.equal('tag'); }); }); @@ -502,80 +495,80 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); }); - it('should create, find and include associations with scope values', function() { - return Promise.all([ + it('should create, find and include associations with scope values', async function() { + await Promise.all([ this.Post.sync({ force: true }), this.Image.sync({ force: true }), this.Question.sync({ force: true }), this.Tag.sync({ force: true }) - ]).then(() => { - return this.ItemTag.sync({ force: true }); - }).then(() => { - return Promise.all([ - this.Post.create(), - this.Image.create(), - this.Question.create(), - this.Tag.create({ name: 'tagA' }), - this.Tag.create({ name: 'tagB' }), - this.Tag.create({ name: 'tagC' }) - ]); - }).then(([post, image, question, tagA, tagB, tagC]) => { - this.post = post; - this.image = image; - this.question = question; - return Promise.all([post.setTags([tagA]).then(() => { - return Promise.all([post.createTag({ name: 'postTag' }), post.addTag(tagB)]); - }), image.setTags([tagB]).then(() => { - return Promise.all([image.createTag({ name: 'imageTag' }), image.addTag(tagC)]); - }), question.setTags([tagC]).then(() => { - return Promise.all([question.createTag({ name: 'questionTag' }), question.addTag(tagA)]); - })]); - }).then(() => { - return Promise.all([this.post.getTags(), this.image.getTags(), this.question.getTags()]).then(([postTags, imageTags, questionTags]) => { - expect(postTags.length).to.equal(3); - expect(imageTags.length).to.equal(3); - expect(questionTags.length).to.equal(3); - - expect(postTags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); - - expect(imageTags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); - - expect(questionTags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); - }).then(() => { - return Promise.all([this.Post.findOne({ - where: {}, - include: [this.Tag] - }), this.Image.findOne({ - where: {}, - include: [this.Tag] - }), this.Question.findOne({ - where: {}, - include: [this.Tag] - })]).then(([post, image, question]) => { - expect(post.tags.length).to.equal(3); - expect(image.tags.length).to.equal(3); - expect(question.tags.length).to.equal(3); - - expect(post.tags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); - - expect(image.tags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); - - expect(question.tags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); - }); - }); - }); + ]); + + await this.ItemTag.sync({ force: true }); + + const [post0, image0, question0, tagA, tagB, tagC] = await Promise.all([ + this.Post.create(), + this.Image.create(), + this.Question.create(), + this.Tag.create({ name: 'tagA' }), + this.Tag.create({ name: 'tagB' }), + this.Tag.create({ name: 'tagC' }) + ]); + + this.post = post0; + this.image = image0; + this.question = question0; + + await Promise.all([post0.setTags([tagA]).then(async () => { + return Promise.all([post0.createTag({ name: 'postTag' }), post0.addTag(tagB)]); + }), image0.setTags([tagB]).then(async () => { + return Promise.all([image0.createTag({ name: 'imageTag' }), image0.addTag(tagC)]); + }), question0.setTags([tagC]).then(async () => { + return Promise.all([question0.createTag({ name: 'questionTag' }), question0.addTag(tagA)]); + })]); + + const [postTags, imageTags, questionTags] = await Promise.all([this.post.getTags(), this.image.getTags(), this.question.getTags()]); + expect(postTags.length).to.equal(3); + expect(imageTags.length).to.equal(3); + expect(questionTags.length).to.equal(3); + + expect(postTags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); + + expect(imageTags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); + + expect(questionTags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); + + const [post, image, question] = await Promise.all([this.Post.findOne({ + where: {}, + include: [this.Tag] + }), this.Image.findOne({ + where: {}, + include: [this.Tag] + }), this.Question.findOne({ + where: {}, + include: [this.Tag] + })]); + + expect(post.tags.length).to.equal(3); + expect(image.tags.length).to.equal(3); + expect(question.tags.length).to.equal(3); + + expect(post.tags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); + + expect(image.tags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); + + expect(question.tags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); }); }); }); diff --git a/test/integration/associations/self.test.js b/test/integration/associations/self.test.js index 8ee2d9489d72..dc6a094039d3 100644 --- a/test/integration/associations/self.test.js +++ b/test/integration/associations/self.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Self'), () => { - it('supports freezeTableName', function() { + it('supports freezeTableName', async function() { const Group = this.sequelize.define('Group', {}, { tableName: 'user_group', timestamps: false, @@ -15,35 +15,35 @@ describe(Support.getTestDialectTeaser('Self'), () => { }); Group.belongsTo(Group, { as: 'Parent', foreignKey: 'parent_id' }); - return Group.sync({ force: true }).then(() => { - return Group.findAll({ - include: [{ - model: Group, - as: 'Parent' - }] - }); + await Group.sync({ force: true }); + + await Group.findAll({ + include: [{ + model: Group, + as: 'Parent' + }] }); }); - it('can handle 1:m associations', function() { + it('can handle 1:m associations', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); Person.hasMany(Person, { as: 'Children', foreignKey: 'parent_id' }); expect(Person.rawAttributes.parent_id).to.be.ok; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Person.create({ name: 'Mary' }), - Person.create({ name: 'John' }), - Person.create({ name: 'Chris' }) - ]); - }).then(([mary, john, chris]) => { - return mary.setChildren([john, chris]); - }); + await this.sequelize.sync({ force: true }); + + const [mary, john, chris] = await Promise.all([ + Person.create({ name: 'Mary' }), + Person.create({ name: 'John' }), + Person.create({ name: 'Chris' }) + ]); + + await mary.setChildren([john, chris]); }); - it('can handle n:m associations', function() { + it('can handle n:m associations', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); Person.belongsToMany(Person, { as: 'Parents', through: 'Family', foreignKey: 'ChildId', otherKey: 'PersonId' }); @@ -58,24 +58,21 @@ describe(Support.getTestDialectTeaser('Self'), () => { expect(foreignIdentifiers).to.have.members(['PersonId', 'ChildId']); expect(rawAttributes).to.have.members(['createdAt', 'updatedAt', 'PersonId', 'ChildId']); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Person.create({ name: 'Mary' }), - Person.create({ name: 'John' }), - Person.create({ name: 'Chris' }) - ]).then(([mary, john, chris]) => { - return mary.setParents([john]).then(() => { - return chris.addParent(john); - }).then(() => { - return john.getChilds(); - }).then(children => { - expect(children.map(v => v.id)).to.have.members([mary.id, chris.id]); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [mary, john, chris] = await Promise.all([ + Person.create({ name: 'Mary' }), + Person.create({ name: 'John' }), + Person.create({ name: 'Chris' }) + ]); + + await mary.setParents([john]); + await chris.addParent(john); + const children = await john.getChilds(); + expect(children.map(v => v.id)).to.have.members([mary.id, chris.id]); }); - it('can handle n:m associations with pre-defined through table', function() { + it('can handle n:m associations with pre-defined through table', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); const Family = this.sequelize.define('Family', { preexisting_child: { @@ -101,47 +98,49 @@ describe(Support.getTestDialectTeaser('Self'), () => { expect(rawAttributes).to.have.members(['preexisting_parent', 'preexisting_child']); let count = 0; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Person.create({ name: 'Mary' }), - Person.create({ name: 'John' }), - Person.create({ name: 'Chris' }) - ]); - }).then(([mary, john, chris]) => { - this.mary = mary; - this.chris = chris; - this.john = john; - return mary.setParents([john], { - logging(sql) { - if (sql.match(/INSERT/)) { - count++; - expect(sql).to.have.string('preexisting_child'); - expect(sql).to.have.string('preexisting_parent'); - } - } - }); - }).then(() => { - return this.mary.addParent(this.chris, { - logging(sql) { - if (sql.match(/INSERT/)) { - count++; - expect(sql).to.have.string('preexisting_child'); - expect(sql).to.have.string('preexisting_parent'); - } + await this.sequelize.sync({ force: true }); + + const [mary, john, chris] = await Promise.all([ + Person.create({ name: 'Mary' }), + Person.create({ name: 'John' }), + Person.create({ name: 'Chris' }) + ]); + + this.mary = mary; + this.chris = chris; + this.john = john; + + await mary.setParents([john], { + logging(sql) { + if (sql.match(/INSERT/)) { + count++; + expect(sql).to.have.string('preexisting_child'); + expect(sql).to.have.string('preexisting_parent'); } - }); - }).then(() => { - return this.john.getChildren({ - logging(sql) { + } + }); + + await this.mary.addParent(this.chris, { + logging(sql) { + if (sql.match(/INSERT/)) { count++; - const whereClause = sql.split('FROM')[1]; // look only in the whereClause - expect(whereClause).to.have.string('preexisting_child'); - expect(whereClause).to.have.string('preexisting_parent'); + expect(sql).to.have.string('preexisting_child'); + expect(sql).to.have.string('preexisting_parent'); } - }); - }).then(children => { - expect(count).to.be.equal(3); - expect(children.map(v => v.id)).to.have.members([this.mary.id]); + } }); + + const children = await this.john.getChildren({ + logging(sql) { + count++; + const whereClause = sql.split('FROM')[1]; + // look only in the whereClause + expect(whereClause).to.have.string('preexisting_child'); + expect(whereClause).to.have.string('preexisting_parent'); + } + }); + + expect(count).to.be.equal(3); + expect(children.map(v => v.id)).to.have.members([this.mary.id]); }); }); From 2fa578ea63739bd8ea47de696bd2f6ab086d37b8 Mon Sep 17 00:00:00 2001 From: Pankaj Vaghela Date: Thu, 7 May 2020 11:22:43 +0530 Subject: [PATCH 137/414] docs(resources): add sequelize-guard library (#12235) --- docs/manual/other-topics/resources.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/other-topics/resources.md b/docs/manual/other-topics/resources.md index 5c6d8860cb62..291f25712526 100644 --- a/docs/manual/other-topics/resources.md +++ b/docs/manual/other-topics/resources.md @@ -6,6 +6,7 @@ * [ssacl](https://github.com/pumpupapp/ssacl) * [ssacl-attribute-roles](https://github.com/mickhansen/ssacl-attribute-roles) +* [SequelizeGuard](https://github.com/lotivo/sequelize-acl) - Role, Permission based Authorization for Sequelize. ### Auto Code Generation & Scaffolding From 839f1a97a1a26a0b457e963a803c176829e2f5bd Mon Sep 17 00:00:00 2001 From: SuHun Han Date: Fri, 8 May 2020 14:38:59 +0900 Subject: [PATCH 138/414] fix(query-interface): allow sequelize.fn and sequelize.literal in fields of IndexesOptions (#12224) --- types/lib/query-interface.d.ts | 3 ++- types/test/query-interface.ts | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 8aee037fd52b..79801f75f832 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -4,6 +4,7 @@ import QueryTypes = require('./query-types'); import { Sequelize, RetryOptions } from './sequelize'; import { Transaction } from './transaction'; import { SetRequired } from './../type-helpers/set-required'; +import { Fn, Literal } from './utils'; type BindOrReplacements = { [key: string]: unknown } | unknown[]; type FieldMap = { [key: string]: string }; @@ -172,7 +173,7 @@ export interface IndexesOptions { * (field name), `length` (create a prefix index of length chars), `order` (the direction the column * should be sorted in), `collate` (the collation (sort order) for the column), `operator` (likes IndexesOptions['operator']) */ - fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string; operator?: string })[]; + fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string; operator?: string } | Fn | Literal)[]; /** * The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index 91cdba016bd0..515976410346 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -1,4 +1,4 @@ -import { DataTypes, Model } from 'sequelize'; +import { DataTypes, Model, fn, literal, col } from 'sequelize'; // tslint:disable-next-line:no-submodule-imports import { QueryInterface } from 'sequelize/lib/query-interface'; @@ -154,6 +154,20 @@ queryInterface.addIndex('Foo', { ], }); +queryInterface.addIndex('Foo', { + name: 'foo_b_lower', + fields: [ + fn('lower', col('foo_b')) + ], +}); + +queryInterface.addIndex('Foo', { + name: 'foo_c_lower', + fields: [ + literal('LOWER(foo_c)') + ] +}) + queryInterface.removeIndex('Person', 'SuperDuperIndex'); // or From 4c1ce2b006be16c9ba47f684f25bd484ebe23a62 Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Fri, 8 May 2020 16:26:39 -0300 Subject: [PATCH 139/414] docs(typescript): fix confusing comments (#12226) Co-authored-by: Pedro Augusto de Paula Barbosa --- docs/manual/other-topics/typescript.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index 7a40468f9729..ef557aecd483 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -68,7 +68,7 @@ class Address extends Model { Project.init({ id: { - type: DataTypes.INTEGER.UNSIGNED, // you can omit the `new` but this is discouraged + type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, }, @@ -101,7 +101,7 @@ User.init({ } }, { tableName: 'users', - sequelize: sequelize, // this bit is important + sequelize: sequelize, // passing the `sequelize` instance is required }); Address.init({ @@ -114,7 +114,7 @@ Address.init({ } }, { tableName: 'address', - sequelize: sequelize, // this bit is important + sequelize: sequelize, // passing the `sequelize` instance is required }); // Here we associate which actually populates out pre-declared `association` static and other methods. From a35ec013714a264db301b5d0e9931525fc40d0b4 Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Sat, 9 May 2020 17:25:43 +0300 Subject: [PATCH 140/414] fix(sqlite): multiple primary keys results in syntax error (#12237) --- lib/dialects/sqlite/query-generator.js | 6 +++++- test/unit/dialects/sqlite/query-generator.test.js | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index b47140a88ad5..0a78f9807034 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -44,7 +44,11 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { if (needsMultiplePrimaryKeys) { primaryKeys.push(attr); - dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL'); + if (dataType.includes('NOT NULL')) { + dataTypeString = dataType.replace(' PRIMARY KEY', ''); + } else { + dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL'); + } } } attrArray.push(`${this.quoteIdentifier(attr)} ${dataTypeString}`); diff --git a/test/unit/dialects/sqlite/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js index 310942464ec0..062352d78b08 100644 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ b/test/unit/dialects/sqlite/query-generator.test.js @@ -152,6 +152,10 @@ if (dialect === 'sqlite') { { arguments: ['myTable', { id: 'INTEGER PRIMARY KEY AUTOINCREMENT', name: 'VARCHAR(255)', surname: 'VARCHAR(255)' }, { uniqueKeys: { uniqueConstraint: { fields: ['name', 'surname'], customIndex: true } } }], expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255), `surname` VARCHAR(255), UNIQUE (`name`, `surname`));' + }, + { + arguments: ['myTable', { foo1: 'INTEGER PRIMARY KEY NOT NULL', foo2: 'INTEGER PRIMARY KEY NOT NULL' }], + expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`foo1` INTEGER NOT NULL, `foo2` INTEGER NOT NULL, PRIMARY KEY (`foo1`, `foo2`));' } ], From 5336424a2276425d7abad64d852f7a304883e18d Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sun, 10 May 2020 23:46:25 -0500 Subject: [PATCH 141/414] test(integration/dialects): asyncify (#12239) --- .../abstract/connection-manager.test.js | 53 +- .../dialects/mariadb/associations.test.js | 28 +- .../mariadb/connector-manager.test.js | 42 +- test/integration/dialects/mariadb/dao.test.js | 117 +- .../dialects/mariadb/errors.test.js | 91 +- .../dialects/mariadb/query-interface.test.js | 20 +- .../dialects/mssql/connection-manager.test.js | 16 +- .../dialects/mssql/query-queue.test.js | 12 +- .../dialects/mssql/regressions.test.js | 134 +- .../dialects/mysql/associations.test.js | 91 +- .../dialects/mysql/connector-manager.test.js | 37 +- .../integration/dialects/mysql/errors.test.js | 79 +- .../dialects/mysql/warning.test.js | 24 +- .../dialects/postgres/associations.test.js | 88 +- .../postgres/connection-manager.test.js | 129 +- .../integration/dialects/postgres/dao.test.js | 1289 ++++++++--------- .../dialects/postgres/data-types.test.js | 224 ++- .../dialects/postgres/error.test.js | 35 +- .../dialects/postgres/query-interface.test.js | 229 ++- .../dialects/postgres/query.test.js | 74 +- .../dialects/postgres/range.test.js | 16 +- .../dialects/postgres/regressions.test.js | 32 +- .../sqlite/connection-manager.test.js | 64 +- .../dialects/sqlite/dao-factory.test.js | 125 +- test/integration/dialects/sqlite/dao.test.js | 98 +- .../dialects/sqlite/sqlite-master.test.js | 64 +- 26 files changed, 1475 insertions(+), 1736 deletions(-) diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index 644a9933a79f..1c4ce5faf95c 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -55,7 +55,7 @@ describe(Support.getTestDialectTeaser('Connection Manager'), () => { expect(connectionManager.pool.write).to.be.instanceOf(Pool); }); - it('should round robin calls to the read pool', () => { + it('should round robin calls to the read pool', async () => { if (Support.getTestDialect() === 'sqlite') { return; } @@ -91,21 +91,19 @@ describe(Support.getTestDialectTeaser('Connection Manager'), () => { const _getConnection = connectionManager.getConnection.bind(connectionManager, queryOptions); - return _getConnection() - .then(_getConnection) - .then(_getConnection) - .then(() => { - chai.expect(connectStub.callCount).to.equal(4); - - // First call is the get connection for DB versions - ignore - const calls = connectStub.getCalls(); - chai.expect(calls[1].args[0].host).to.eql('slave1'); - chai.expect(calls[2].args[0].host).to.eql('slave2'); - chai.expect(calls[3].args[0].host).to.eql('slave1'); - }); + await _getConnection(); + await _getConnection(); + await _getConnection(); + chai.expect(connectStub.callCount).to.equal(4); + + // First call is the get connection for DB versions - ignore + const calls = connectStub.getCalls(); + chai.expect(calls[1].args[0].host).to.eql('slave1'); + chai.expect(calls[2].args[0].host).to.eql('slave2'); + chai.expect(calls[3].args[0].host).to.eql('slave1'); }); - it('should trigger deprecation for non supported engine version', () => { + it('should trigger deprecation for non supported engine version', async () => { const deprecationStub = sandbox.stub(deprecations, 'unsupportedEngine'); const sequelize = Support.createSequelizeInstance(); const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); @@ -126,14 +124,12 @@ describe(Support.getTestDialectTeaser('Connection Manager'), () => { useMaster: true }; - return connectionManager.getConnection(queryOptions) - .then(() => { - chai.expect(deprecationStub).to.have.been.calledOnce; - }); + await connectionManager.getConnection(queryOptions); + chai.expect(deprecationStub).to.have.been.calledOnce; }); - it('should allow forced reads from the write pool', () => { + it('should allow forced reads from the write pool', async () => { const master = { ...poolEntry }; master.host = 'the-boss'; @@ -161,15 +157,13 @@ describe(Support.getTestDialectTeaser('Connection Manager'), () => { useMaster: true }; - return connectionManager.getConnection(queryOptions) - .then(() => { - chai.expect(connectStub).to.have.been.calledTwice; // Once to get DB version, and once to actually get the connection. - const calls = connectStub.getCalls(); - chai.expect(calls[1].args[0].host).to.eql('the-boss'); - }); + await connectionManager.getConnection(queryOptions); + chai.expect(connectStub).to.have.been.calledTwice; // Once to get DB version, and once to actually get the connection. + const calls = connectStub.getCalls(); + chai.expect(calls[1].args[0].host).to.eql('the-boss'); }); - it('should clear the pool after draining it', () => { + it('should clear the pool after draining it', async () => { const options = { replication: null }; @@ -181,9 +175,8 @@ describe(Support.getTestDialectTeaser('Connection Manager'), () => { const poolDrainSpy = sandbox.spy(connectionManager.pool, 'drain'); const poolClearSpy = sandbox.spy(connectionManager.pool, 'destroyAllNow'); - return connectionManager.close().then(() => { - expect(poolDrainSpy.calledOnce).to.be.true; - expect(poolClearSpy.calledOnce).to.be.true; - }); + await connectionManager.close(); + expect(poolDrainSpy.calledOnce).to.be.true; + expect(poolClearSpy.calledOnce).to.be.true; }); }); diff --git a/test/integration/dialects/mariadb/associations.test.js b/test/integration/dialects/mariadb/associations.test.js index 1466f6a75997..0256e4387f83 100644 --- a/test/integration/dialects/mariadb/associations.test.js +++ b/test/integration/dialects/mariadb/associations.test.js @@ -11,33 +11,30 @@ if (dialect !== 'mariadb') return; describe('[MariaDB Specific] Associations', () => { describe('many-to-many', () => { describe('where tables have the same prefix', () => { - it('should create a table wp_table1wp_table2s', function() { + it('should create a table wp_table1wp_table2s', async function() { const Table2 = this.sequelize.define('wp_table2', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('wp_table1', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'wp_table1swp_table2s' }); Table2.belongsToMany(Table1, { through: 'wp_table1swp_table2s' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }).then(() => { - expect(this.sequelize.modelManager.getModel( - 'wp_table1swp_table2s')).to.exist; - }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); + expect(this.sequelize.modelManager.getModel( + 'wp_table1swp_table2s')).to.exist; }); }); describe('when join table name is specified', () => { - beforeEach(function() { + beforeEach(async function() { const Table2 = this.sequelize.define('ms_table1', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('ms_table2', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'table1_to_table2' }); Table2.belongsToMany(Table1, { through: 'table1_to_table2' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); }); it('should not use only a specified name', function() { @@ -50,7 +47,7 @@ describe('[MariaDB Specific] Associations', () => { }); describe('HasMany', () => { - beforeEach(function() { + beforeEach(async function() { //prevent periods from occurring in the table name since they are used to delimit (table.column) this.User = this.sequelize.define(`User${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); this.Task = this.sequelize.define(`Task${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); @@ -68,10 +65,9 @@ describe('[MariaDB Specific] Associations', () => { tasks[i] = { name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }) - .then(() => this.User.bulkCreate(users)) - .then(() => this.Task.bulkCreate(tasks)); - + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); }); }); diff --git a/test/integration/dialects/mariadb/connector-manager.test.js b/test/integration/dialects/mariadb/connector-manager.test.js index 5357a597cbca..391f4573cb16 100644 --- a/test/integration/dialects/mariadb/connector-manager.test.js +++ b/test/integration/dialects/mariadb/connector-manager.test.js @@ -13,17 +13,15 @@ if (dialect !== 'mariadb') { describe('[MARIADB Specific] Connection Manager', () => { - it('has existing init SQL', () => { + it('has existing init SQL', async () => { const sequelize = Support.createSequelizeInstance( { dialectOptions: { initSql: 'SET @myUserVariable=\'myValue\'' } }); - return sequelize.query('SELECT @myUserVariable') - .then(res => { - expect(res[0]).to.deep.equal([{ '@myUserVariable': 'myValue' }]); - sequelize.close(); - }); + const res = await sequelize.query('SELECT @myUserVariable'); + expect(res[0]).to.deep.equal([{ '@myUserVariable': 'myValue' }]); + sequelize.close(); }); - it('has existing init SQL array', () => { + it('has existing init SQL array', async () => { const sequelize = Support.createSequelizeInstance( { dialectOptions: { @@ -31,12 +29,10 @@ describe('[MARIADB Specific] Connection Manager', () => { 'SET @myUserVariable2=\'myValue\''] } }); - return sequelize.query('SELECT @myUserVariable1, @myUserVariable2') - .then(res => { - expect(res[0]).to.deep.equal( - [{ '@myUserVariable1': 'myValue', '@myUserVariable2': 'myValue' }]); - sequelize.close(); - }); + const res = await sequelize.query('SELECT @myUserVariable1, @myUserVariable2'); + expect(res[0]).to.deep.equal( + [{ '@myUserVariable1': 'myValue', '@myUserVariable2': 'myValue' }]); + sequelize.close(); }); @@ -44,29 +40,29 @@ describe('[MARIADB Specific] Connection Manager', () => { describe('Errors', () => { const testHost = env.MARIADB_PORT_3306_TCP_ADDR || env.SEQ_MARIADB_HOST || env.SEQ_HOST || '127.0.0.1'; - it('Connection timeout', () => { + it('Connection timeout', async () => { const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535, dialectOptions: { connectTimeout: 500 } }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.SequelizeConnectionError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.SequelizeConnectionError); }); - it('ECONNREFUSED', () => { + it('ECONNREFUSED', async () => { const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535 }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); }); - it('ENOTFOUND', () => { + it('ENOTFOUND', async () => { const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); }); - it('EHOSTUNREACH', () => { + it('EHOSTUNREACH', async () => { const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); }); - it('ER_ACCESS_DENIED_ERROR | ELOGIN', () => { + it('ER_ACCESS_DENIED_ERROR | ELOGIN', async () => { const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); }); }); diff --git a/test/integration/dialects/mariadb/dao.test.js b/test/integration/dialects/mariadb/dao.test.js index e07a1d493440..1bfc543aab2f 100644 --- a/test/integration/dialects/mariadb/dao.test.js +++ b/test/integration/dialects/mariadb/dao.test.js @@ -8,14 +8,14 @@ const chai = require('chai'), if (dialect !== 'mariadb') return; describe('[MariaDB Specific] DAO', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize.options.quoteIdentifiers = true; this.User = this.sequelize.define('User', { username: DataTypes.STRING, email: DataTypes.STRING, location: DataTypes.GEOMETRY() }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); afterEach(function() { @@ -24,112 +24,99 @@ describe('[MariaDB Specific] DAO', () => { describe('integers', () => { describe('integer', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.INTEGER }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: 2147483647 }).then(user => { - expect(user.aNumber).to.equal(2147483647); - return User.findOne({ where: { aNumber: 2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(2147483647); - }); - }); + const user = await User.create({ aNumber: 2147483647 }); + expect(user.aNumber).to.equal(2147483647); + const _user = await User.findOne({ where: { aNumber: 2147483647 } }); + expect(_user.aNumber).to.equal(2147483647); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: -2147483647 }).then(user => { - expect(user.aNumber).to.equal(-2147483647); - return User.findOne({ where: { aNumber: -2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(-2147483647); - }); - }); + const user = await User.create({ aNumber: -2147483647 }); + expect(user.aNumber).to.equal(-2147483647); + const _user = await User.findOne({ where: { aNumber: -2147483647 } }); + expect(_user.aNumber).to.equal(-2147483647); }); }); describe('bigint', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.BIGINT }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: '9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('9223372036854775807'); - return User.findOne({ where: { aNumber: '9223372036854775807' } }).then( - _user => { - return expect(_user.aNumber.toString()).to.equal( - '9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '9223372036854775807' }); + expect(user.aNumber).to.equal('9223372036854775807'); + const _user = await User.findOne({ where: { aNumber: '9223372036854775807' } }); + + await expect(_user.aNumber.toString()).to.equal('9223372036854775807'); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: '-9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('-9223372036854775807'); - return User.findOne( - { where: { aNumber: '-9223372036854775807' } }).then(_user => { - return expect(_user.aNumber.toString()).to.equal( - '-9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '-9223372036854775807' }); + expect(user.aNumber).to.equal('-9223372036854775807'); + + const _user = await User.findOne( + { where: { aNumber: '-9223372036854775807' } }); + + await expect(_user.aNumber.toString()).to.equal('-9223372036854775807'); }); }); }); - it('should save geometry correctly', function() { + it('should save geometry correctly', async function() { const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return this.User.create( - { username: 'user', email: 'foo@bar.com', location: point }).then( - newUser => { - expect(newUser.location).to.deep.eql(point); - }); + + const newUser = await this.User.create( + { username: 'user', email: 'foo@bar.com', location: point }); + + expect(newUser.location).to.deep.eql(point); }); - it('should update geometry correctly', function() { + it('should update geometry correctly', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }; const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] }; - return User.create( - { username: 'user', email: 'foo@bar.com', location: point1 }) - .then(oldUser => { - return User.update({ location: point2 }, - { where: { username: oldUser.username } }) - .then(() => { - return User.findOne({ where: { username: oldUser.username } }); - }) - .then(updatedUser => { - expect(updatedUser.location).to.deep.eql(point2); - }); - }); + + const oldUser = await User.create( + { username: 'user', email: 'foo@bar.com', location: point1 }); + + await User.update({ location: point2 }, + { where: { username: oldUser.username } }); + + const updatedUser = await User.findOne({ where: { username: oldUser.username } }); + expect(updatedUser.location).to.deep.eql(point2); }); - it('should read geometry correctly', function() { + it('should read geometry correctly', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create( - { username: 'user', email: 'foo@bar.com', location: point }).then( - user => { - return User.findOne({ where: { username: user.username } }); - }).then(user => { - expect(user.location).to.deep.eql(point); - }); + const user0 = await User.create( + { username: 'user', email: 'foo@bar.com', location: point }); + + const user = await User.findOne({ where: { username: user0.username } }); + expect(user.location).to.deep.eql(point); }); }); diff --git a/test/integration/dialects/mariadb/errors.test.js b/test/integration/dialects/mariadb/errors.test.js index 87e0957abdf0..33b2b62042da 100644 --- a/test/integration/dialects/mariadb/errors.test.js +++ b/test/integration/dialects/mariadb/errors.test.js @@ -9,12 +9,16 @@ const chai = require('chai'), if (dialect !== 'mariadb') return; describe('[MariaDB Specific] Errors', () => { - const validateError = (promise, errClass, errValues) => { + const validateError = async (promise, errClass, errValues) => { const wanted = { ...errValues }; - return expect(promise).to.have.been.rejectedWith(errClass).then(() => - promise.catch(err => Object.keys(wanted).forEach( - k => expect(err[k]).to.eql(wanted[k])))); + await expect(promise).to.have.been.rejectedWith(errClass); + + try { + return await promise; + } catch (err) { + return Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])); + } }; describe('ForeignKeyConstraintError', () => { @@ -33,50 +37,51 @@ describe('[MariaDB Specific] Errors', () => { { foreignKey: 'primaryUserId', as: 'primaryUsers' }); }); - it('in context of DELETE restriction', function() { + it('in context of DELETE restriction', async function() { const ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }) - ]); - }).then(([user1, task1]) => { - ctx.user1 = user1; - ctx.task1 = task1; - return user1.setTasks([task1]); - }).then(() => { - return Promise.all([ - validateError(ctx.user1.destroy(), ForeignKeyConstraintError, { - fields: ['userId'], - table: 'users', - value: undefined, - index: 'tasksusers_ibfk_1', - reltype: 'parent' - }), - validateError(ctx.task1.destroy(), ForeignKeyConstraintError, { - fields: ['taskId'], - table: 'tasks', - value: undefined, - index: 'tasksusers_ibfk_2', - reltype: 'parent' - }) - ]); - }); + await this.sequelize.sync({ force: true }); + + const [user1, task1] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }) + ]); + + await user1.setTasks([task1]); + + await Promise.all([ + validateError(user1.destroy(), ForeignKeyConstraintError, { + fields: ['userId'], + table: 'users', + value: undefined, + index: 'tasksusers_ibfk_1', + reltype: 'parent' + }), + validateError(task1.destroy(), ForeignKeyConstraintError, { + fields: ['taskId'], + table: 'tasks', + value: undefined, + index: 'tasksusers_ibfk_2', + reltype: 'parent' + }) + ]); }); - it('in context of missing relation', function() { + it('in context of missing relation', async function() { const ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; - return this.sequelize.sync({ force: true }).then(() => - validateError(this.Task.create({ title: 'task', primaryUserId: 5 }), - ForeignKeyConstraintError, { - fields: ['primaryUserId'], - table: 'users', - value: 5, - index: 'tasks_ibfk_1', - reltype: 'child' - })); + await this.sequelize.sync({ force: true }); + + await validateError( + this.Task.create({ title: 'task', primaryUserId: 5 }), + ForeignKeyConstraintError, + { + fields: ['primaryUserId'], + table: 'users', + value: 5, + index: 'tasks_ibfk_1', + reltype: 'child' + } + ); }); }); diff --git a/test/integration/dialects/mariadb/query-interface.test.js b/test/integration/dialects/mariadb/query-interface.test.js index ce1c492f1577..7386047cb281 100644 --- a/test/integration/dialects/mariadb/query-interface.test.js +++ b/test/integration/dialects/mariadb/query-interface.test.js @@ -9,19 +9,13 @@ if (dialect.match(/^mariadb/)) { describe('QueryInterface', () => { describe('databases', () => { - it('should create and drop database', function() { - return this.sequelize.query('SHOW DATABASES') - .then(res => { - const databaseNumber = res[0].length; - return this.sequelize.getQueryInterface().createDatabase('myDB') - .then(() => { - return this.sequelize.query('SHOW DATABASES'); - }) - .then(databases => { - expect(databases[0]).to.have.length(databaseNumber + 1); - return this.sequelize.getQueryInterface().dropDatabase('myDB'); - }); - }); + it('should create and drop database', async function() { + const res = await this.sequelize.query('SHOW DATABASES'); + const databaseNumber = res[0].length; + await this.sequelize.getQueryInterface().createDatabase('myDB'); + const databases = await this.sequelize.query('SHOW DATABASES'); + expect(databases[0]).to.have.length(databaseNumber + 1); + await this.sequelize.getQueryInterface().dropDatabase('myDB'); }); }); }); diff --git a/test/integration/dialects/mssql/connection-manager.test.js b/test/integration/dialects/mssql/connection-manager.test.js index f71e6f77447f..7410fef0cfc9 100644 --- a/test/integration/dialects/mssql/connection-manager.test.js +++ b/test/integration/dialects/mssql/connection-manager.test.js @@ -9,24 +9,24 @@ const dialect = Support.getTestDialect(); if (dialect.match(/^mssql/)) { describe('[MSSQL Specific] Connection Manager', () => { describe('Errors', () => { - it('ECONNREFUSED', () => { + it('ECONNREFUSED', async () => { const sequelize = Support.createSequelizeInstance({ host: '127.0.0.1', port: 34237 }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); }); - it('ENOTFOUND', () => { + it('ENOTFOUND', async () => { const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); }); - it('EHOSTUNREACH', () => { + it('EHOSTUNREACH', async () => { const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); }); - it('ER_ACCESS_DENIED_ERROR | ELOGIN', () => { + it('ER_ACCESS_DENIED_ERROR | ELOGIN', async () => { const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); }); }); }); diff --git a/test/integration/dialects/mssql/query-queue.test.js b/test/integration/dialects/mssql/query-queue.test.js index 2b0ccfec8d60..d757dedfe201 100644 --- a/test/integration/dialects/mssql/query-queue.test.js +++ b/test/integration/dialects/mssql/query-queue.test.js @@ -11,20 +11,20 @@ const chai = require('chai'), if (dialect.match(/^mssql/)) { describe('[MSSQL Specific] Query Queue', () => { - beforeEach(function() { + beforeEach(async function() { const User = this.User = this.sequelize.define('User', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'John' }); - }); + await this.sequelize.sync({ force: true }); + + await User.create({ username: 'John' }); }); - it('should queue concurrent requests to a connection', function() { + it('should queue concurrent requests to a connection', async function() { const User = this.User; - return expect(this.sequelize.transaction(t => { + await expect(this.sequelize.transaction(async t => { return Promise.all([ User.findOne({ transaction: t diff --git a/test/integration/dialects/mssql/regressions.test.js b/test/integration/dialects/mssql/regressions.test.js index cfdbd0782fbc..c0893b049d3e 100644 --- a/test/integration/dialects/mssql/regressions.test.js +++ b/test/integration/dialects/mssql/regressions.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), if (dialect.match(/^mssql/)) { describe('[MSSQL Specific] Regressions', () => { - it('does not duplicate columns in ORDER BY statement, #9008', function() { + it('does not duplicate columns in ORDER BY statement, #9008', async function() { const LoginLog = this.sequelize.define('LoginLog', { ID: { field: 'id', @@ -45,71 +45,67 @@ if (dialect.match(/^mssql/)) { foreignKey: 'UserID' }); - return this.sequelize.sync({ force: true }) - .then(() => User.bulkCreate([ - { UserName: 'Vayom' }, - { UserName: 'Shaktimaan' }, - { UserName: 'Nikita' }, - { UserName: 'Aryamaan' } - ], { returning: true })) - .then(([vyom, shakti, nikita, arya]) => { - return Promise.all([ - vyom.createLoginLog(), - shakti.createLoginLog(), - nikita.createLoginLog(), - arya.createLoginLog() - ]); - }).then(() => { - return LoginLog.findAll({ - include: [ - { - model: User, - where: { - UserName: { - [Op.like]: '%maan%' - } - } + await this.sequelize.sync({ force: true }); + + const [vyom, shakti, nikita, arya] = await User.bulkCreate([ + { UserName: 'Vayom' }, + { UserName: 'Shaktimaan' }, + { UserName: 'Nikita' }, + { UserName: 'Aryamaan' } + ], { returning: true }); + + await Promise.all([ + vyom.createLoginLog(), + shakti.createLoginLog(), + nikita.createLoginLog(), + arya.createLoginLog() + ]); + + const logs = await LoginLog.findAll({ + include: [ + { + model: User, + where: { + UserName: { + [Op.like]: '%maan%' } - ], - order: [[User, 'UserName', 'DESC']], - offset: 0, - limit: 10 - }); - }).then(logs => { - expect(logs).to.have.length(2); - expect(logs[0].User.get('UserName')).to.equal('Shaktimaan'); - expect(logs[1].User.get('UserName')).to.equal('Aryamaan'); - }); + } + } + ], + order: [[User, 'UserName', 'DESC']], + offset: 0, + limit: 10 + }); + + expect(logs).to.have.length(2); + expect(logs[0].User.get('UserName')).to.equal('Shaktimaan'); + expect(logs[1].User.get('UserName')).to.equal('Aryamaan'); }); }); - it('sets the varchar(max) length correctly on describeTable', function() { + it('sets the varchar(max) length correctly on describeTable', async function() { const Users = this.sequelize.define('_Users', { username: Sequelize.STRING('MAX') }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().describeTable('_Users').then(metadata => { - const username = metadata.username; - expect(username.type).to.include('(MAX)'); - }); - }); + await Users.sync({ force: true }); + const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); + const username = metadata.username; + expect(username.type).to.include('(MAX)'); }); - it('sets the char(10) length correctly on describeTable', function() { + it('sets the char(10) length correctly on describeTable', async function() { const Users = this.sequelize.define('_Users', { username: Sequelize.CHAR(10) }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().describeTable('_Users').then(metadata => { - const username = metadata.username; - expect(username.type).to.include('(10)'); - }); - }); + await Users.sync({ force: true }); + const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); + const username = metadata.username; + expect(username.type).to.include('(10)'); }); - it('saves value bigger than 2147483647, #11245', function() { + it('saves value bigger than 2147483647, #11245', async function() { const BigIntTable = this.sequelize.define('BigIntTable', { business_id: { type: Sequelize.BIGINT, @@ -121,19 +117,17 @@ if (dialect.match(/^mssql/)) { const bigIntValue = 2147483648; - return BigIntTable.sync({ force: true }) - .then(() => { - return BigIntTable.create({ - business_id: bigIntValue - }); - }) - .then(() => BigIntTable.findOne()) - .then(record => { - expect(Number(record.business_id)).to.equals(bigIntValue); - }); + await BigIntTable.sync({ force: true }); + + await BigIntTable.create({ + business_id: bigIntValue + }); + + const record = await BigIntTable.findOne(); + expect(Number(record.business_id)).to.equals(bigIntValue); }); - it('saves boolean is true, #12090', function() { + it('saves boolean is true, #12090', async function() { const BooleanTable = this.sequelize.define('BooleanTable', { status: { type: Sequelize.BOOLEAN, @@ -145,15 +139,13 @@ if (dialect.match(/^mssql/)) { const value = true; - return BooleanTable.sync({ force: true }) - .then(() => { - return BooleanTable.create({ - status: value - }); - }) - .then(() => BooleanTable.findOne()) - .then(record => { - expect(record.status).to.equals(value); - }); + await BooleanTable.sync({ force: true }); + + await BooleanTable.create({ + status: value + }); + + const record = await BooleanTable.findOne(); + expect(record.status).to.equals(value); }); } diff --git a/test/integration/dialects/mysql/associations.test.js b/test/integration/dialects/mysql/associations.test.js index bc6971161bdd..0c16f6219fa5 100644 --- a/test/integration/dialects/mysql/associations.test.js +++ b/test/integration/dialects/mysql/associations.test.js @@ -10,30 +10,27 @@ if (dialect === 'mysql') { describe('[MYSQL Specific] Associations', () => { describe('many-to-many', () => { describe('where tables have the same prefix', () => { - it('should create a table wp_table1wp_table2s', function() { + it('should create a table wp_table1wp_table2s', async function() { const Table2 = this.sequelize.define('wp_table2', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('wp_table1', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'wp_table1swp_table2s' }); Table2.belongsToMany(Table1, { through: 'wp_table1swp_table2s' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }).then(() => { - expect(this.sequelize.modelManager.getModel('wp_table1swp_table2s')).to.exist; - }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); + expect(this.sequelize.modelManager.getModel('wp_table1swp_table2s')).to.exist; }); }); describe('when join table name is specified', () => { - beforeEach(function() { + beforeEach(async function() { const Table2 = this.sequelize.define('ms_table1', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('ms_table2', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'table1_to_table2' }); Table2.belongsToMany(Table1, { through: 'table1_to_table2' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); }); it('should not use only a specified name', function() { @@ -44,7 +41,7 @@ if (dialect === 'mysql') { }); describe('HasMany', () => { - beforeEach(function() { + beforeEach(async function() { //prevent periods from occurring in the table name since they are used to delimit (table.column) this.User = this.sequelize.define(`User${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); this.Task = this.sequelize.define(`Task${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); @@ -62,70 +59,48 @@ if (dialect === 'mysql') { tasks[i] = { name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate(users).then(() => { - return this.Task.bulkCreate(tasks); - }); - }); + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); }); describe('addDAO / getModel', () => { - beforeEach(function() { + beforeEach(async function() { this.user = null; this.task = null; - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.task = _tasks[0]; - }); - }); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.task = _tasks[0]; }); - it('should correctly add an association to the dao', function() { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(0); - return this.user.addTask(this.task).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(1); - }); - }); - }); + it('should correctly add an association to the dao', async function() { + expect(await this.user.getTasks()).to.have.length(0); + await this.user.addTask(this.task); + expect(await this.user.getTasks()).to.have.length(1); }); }); describe('removeDAO', () => { - beforeEach(function() { + beforeEach(async function() { this.user = null; this.tasks = null; - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.tasks = _tasks; - }); - }); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.tasks = _tasks; }); - it('should correctly remove associated objects', function() { - return this.user.getTasks().then(__tasks => { - expect(__tasks.length).to.equal(0); - return this.user.setTasks(this.tasks).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(this.tasks.length); - return this.user.removeTask(this.tasks[0]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(this.tasks.length - 1); - return this.user.removeTasks([this.tasks[1], this.tasks[2]]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length - 3); - }); - }); - }); - }); - }); - }); - }); + it('should correctly remove associated objects', async function() { + expect(await this.user.getTasks()).to.have.length(0); + await this.user.setTasks(this.tasks); + expect(await this.user.getTasks()).to.have.length(this.tasks.length); + await this.user.removeTask(this.tasks[0]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 1); + await this.user.removeTasks([this.tasks[1], this.tasks[2]]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 3); }); }); }); diff --git a/test/integration/dialects/mysql/connector-manager.test.js b/test/integration/dialects/mysql/connector-manager.test.js index a73823de1894..ec0d1549ca24 100644 --- a/test/integration/dialects/mysql/connector-manager.test.js +++ b/test/integration/dialects/mysql/connector-manager.test.js @@ -8,31 +8,32 @@ const DataTypes = require('../../../../lib/data-types'); if (dialect === 'mysql') { describe('[MYSQL Specific] Connection Manager', () => { - it('-FOUND_ROWS can be suppressed to get back legacy behavior', () => { + it('-FOUND_ROWS can be suppressed to get back legacy behavior', async () => { const sequelize = Support.createSequelizeInstance({ dialectOptions: { flags: '' } }); const User = sequelize.define('User', { username: DataTypes.STRING }); - return User.sync({ force: true }) - .then(() => User.create({ id: 1, username: 'jozef' })) - .then(() => User.update({ username: 'jozef' }, { - where: { - id: 1 - } - })) - // https://github.com/sequelize/sequelize/issues/7184 - .then(([affectedCount]) => affectedCount.should.equal(1)); + await User.sync({ force: true }); + await User.create({ id: 1, username: 'jozef' }); + + const [affectedCount] = await User.update({ username: 'jozef' }, { + where: { + id: 1 + } + }); + + // https://github.com/sequelize/sequelize/issues/7184 + await affectedCount.should.equal(1); }); - it('should acquire a valid connection when keepDefaultTimezone is true', () => { + it('should acquire a valid connection when keepDefaultTimezone is true', async () => { const sequelize = Support.createSequelizeInstance({ keepDefaultTimezone: true, pool: { min: 1, max: 1, handleDisconnects: true, idle: 5000 } }); const cm = sequelize.connectionManager; - return sequelize - .sync() - .then(() => cm.getConnection()) - .then(connection => { - expect(cm.validate(connection)).to.be.ok; - return cm.releaseConnection(connection); - }); + + await sequelize.sync(); + + const connection = await cm.getConnection(); + expect(cm.validate(connection)).to.be.ok; + await cm.releaseConnection(connection); }); }); } diff --git a/test/integration/dialects/mysql/errors.test.js b/test/integration/dialects/mysql/errors.test.js index b29d1a4b2179..13b7ff376488 100644 --- a/test/integration/dialects/mysql/errors.test.js +++ b/test/integration/dialects/mysql/errors.test.js @@ -10,11 +10,16 @@ const DataTypes = require('../../../../lib/data-types'); if (dialect === 'mysql') { describe('[MYSQL Specific] Errors', () => { - const validateError = (promise, errClass, errValues) => { + const validateError = async (promise, errClass, errValues) => { const wanted = { ...errValues }; - return expect(promise).to.have.been.rejectedWith(errClass).then(() => - promise.catch(err => Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])))); + await expect(promise).to.have.been.rejectedWith(errClass); + + try { + return await promise; + } catch (err) { + return Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])); + } }; describe('ForeignKeyConstraintError', () => { @@ -29,46 +34,48 @@ if (dialect === 'mysql') { this.Task.belongsTo(this.User, { foreignKey: 'primaryUserId', as: 'primaryUsers' }); }); - it('in context of DELETE restriction', function() { - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }) - ]); - }).then(([user1, task1]) => { - ctx.user1 = user1; - ctx.task1 = task1; - return user1.setTasks([task1]); - }).then(() => { - return Promise.all([ - validateError(ctx.user1.destroy(), Sequelize.ForeignKeyConstraintError, { - fields: ['userId'], - table: 'users', - value: undefined, - index: 'tasksusers_ibfk_1', - reltype: 'parent' - }), - validateError(ctx.task1.destroy(), Sequelize.ForeignKeyConstraintError, { - fields: ['taskId'], - table: 'tasks', - value: undefined, - index: 'tasksusers_ibfk_2', - reltype: 'parent' - }) - ]); - }); + it('in context of DELETE restriction', async function() { + await this.sequelize.sync({ force: true }); + + const [user1, task1] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }) + ]); + + await user1.setTasks([task1]); + + await Promise.all([ + validateError(user1.destroy(), Sequelize.ForeignKeyConstraintError, { + fields: ['userId'], + table: 'users', + value: undefined, + index: 'tasksusers_ibfk_1', + reltype: 'parent' + }), + validateError(task1.destroy(), Sequelize.ForeignKeyConstraintError, { + fields: ['taskId'], + table: 'tasks', + value: undefined, + index: 'tasksusers_ibfk_2', + reltype: 'parent' + }) + ]); }); - it('in context of missing relation', function() { - return this.sequelize.sync({ force: true }).then(() => - validateError(this.Task.create({ title: 'task', primaryUserId: 5 }), Sequelize.ForeignKeyConstraintError, { + it('in context of missing relation', async function() { + await this.sequelize.sync({ force: true }); + + await validateError( + this.Task.create({ title: 'task', primaryUserId: 5 }), + Sequelize.ForeignKeyConstraintError, + { fields: ['primaryUserId'], table: 'users', value: 5, index: 'tasks_ibfk_1', reltype: 'child' - })); + } + ); }); }); }); diff --git a/test/integration/dialects/mysql/warning.test.js b/test/integration/dialects/mysql/warning.test.js index e9b0a54bc4cc..ef0f6b85e4d9 100644 --- a/test/integration/dialects/mysql/warning.test.js +++ b/test/integration/dialects/mysql/warning.test.js @@ -11,7 +11,7 @@ describe(Support.getTestDialectTeaser('Warning'), () => { // We can only test MySQL warnings when using MySQL. if (dialect === 'mysql') { describe('logging', () => { - it('logs warnings when there are warnings', () => { + it('logs warnings when there are warnings', async () => { const logger = sinon.spy(console, 'log'); const sequelize = Support.createSequelizeInstance({ logging: logger, @@ -23,20 +23,16 @@ describe(Support.getTestDialectTeaser('Warning'), () => { name: Sequelize.DataTypes.STRING(1, true) }); - return sequelize.sync({ force: true }).then(() => { - return sequelize.authenticate(); - }).then(() => { - return sequelize.query("SET SESSION sql_mode='';"); - }).then(() => { - return Model.create({ - name: 'very-long-long-name' - }); - }).then(() => { - // last log is warning message - expect(logger.args[logger.args.length - 1][0]).to.be.match(/^MySQL Warnings \(default\):.*/m); - }, () => { - expect.fail(); + await sequelize.sync({ force: true }); + await sequelize.authenticate(); + await sequelize.query("SET SESSION sql_mode='';"); + + await Model.create({ + name: 'very-long-long-name' }); + + // last log is warning message + expect(logger.args[logger.args.length - 1][0]).to.be.match(/^MySQL Warnings \(default\):.*/m); }); }); } diff --git a/test/integration/dialects/postgres/associations.test.js b/test/integration/dialects/postgres/associations.test.js index 6bf71cdb3824..eb4427c37449 100644 --- a/test/integration/dialects/postgres/associations.test.js +++ b/test/integration/dialects/postgres/associations.test.js @@ -43,7 +43,7 @@ if (dialect.match(/^postgres/)) { describe('HasMany', () => { describe('addDAO / getModel', () => { - beforeEach(function() { + beforeEach(async function() { //prevent periods from occurring in the table name since they are used to delimit (table.column) this.User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING }); this.Task = this.sequelize.define(`Task${config.rand()}`, { name: DataTypes.STRING }); @@ -61,35 +61,25 @@ if (dialect.match(/^postgres/)) { tasks[i] = { name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate(users).then(() => { - return this.Task.bulkCreate(tasks).then(() => { - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.task = _tasks[0]; - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.task = _tasks[0]; }); - it('should correctly add an association to the dao', function() { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(0); - return this.user.addTask(this.task).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(1); - }); - }); - }); + it('should correctly add an association to the dao', async function() { + expect(await this.user.getTasks()).to.have.length(0); + await this.user.addTask(this.task); + expect(await this.user.getTasks()).to.have.length(1); }); }); describe('removeDAO', () => { - it('should correctly remove associated objects', function() { - const users = [], + it('should correctly remove associated objects', async function() { + const users = [], tasks = []; //prevent periods from occurring in the table name since they are used to delimit (table.column) @@ -106,39 +96,23 @@ if (dialect.match(/^postgres/)) { tasks[i] = { id: i + 1, name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate(users).then(() => { - return this.Task.bulkCreate(tasks).then(() => { - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.task = _tasks[0]; - this.users = _users; - this.tasks = _tasks; - - return this.user.getTasks().then(__tasks => { - expect(__tasks).to.have.length(0); - return this.user.setTasks(this.tasks).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length); - return this.user.removeTask(this.tasks[0]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length - 1); - return this.user.removeTasks([this.tasks[1], this.tasks[2]]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length - 3); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.task = _tasks[0]; + this.users = _users; + this.tasks = _tasks; + + expect(await this.user.getTasks()).to.have.length(0); + await this.user.setTasks(this.tasks); + expect(await this.user.getTasks()).to.have.length(this.tasks.length); + await this.user.removeTask(this.tasks[0]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 1); + await this.user.removeTasks([this.tasks[1], this.tasks[2]]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 3); }); }); }); diff --git a/test/integration/dialects/postgres/connection-manager.test.js b/test/integration/dialects/postgres/connection-manager.test.js index 3578a97f7f79..0dbd9641980a 100644 --- a/test/integration/dialects/postgres/connection-manager.test.js +++ b/test/integration/dialects/postgres/connection-manager.test.js @@ -8,44 +8,36 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES] Sequelize', () => { - function checkTimezoneParsing(baseOptions) { + async function checkTimezoneParsing(baseOptions) { const options = { ...baseOptions, timezone: 'Asia/Kolkata', timestamps: true }; const sequelize = Support.createSequelizeInstance(options); const tzTable = sequelize.define('tz_table', { foo: DataTypes.STRING }); - return tzTable.sync({ force: true }).then(() => { - return tzTable.create({ foo: 'test' }).then(row => { - expect(row).to.be.not.null; - }); - }); + await tzTable.sync({ force: true }); + const row = await tzTable.create({ foo: 'test' }); + expect(row).to.be.not.null; } - it('should correctly parse the moment based timezone while fetching hstore oids', function() { - return checkTimezoneParsing(this.sequelize.options); + it('should correctly parse the moment based timezone while fetching hstore oids', async function() { + await checkTimezoneParsing(this.sequelize.options); }); - it('should set client_min_messages to warning by default', () => { - return Support.sequelize.query('SHOW client_min_messages') - .then(result => { - expect(result[0].client_min_messages).to.equal('warning'); - }); + it('should set client_min_messages to warning by default', async () => { + const result = await Support.sequelize.query('SHOW client_min_messages'); + expect(result[0].client_min_messages).to.equal('warning'); }); - it('should allow overriding client_min_messages', () => { + it('should allow overriding client_min_messages', async () => { const sequelize = Support.createSequelizeInstance({ clientMinMessages: 'ERROR' }); - return sequelize.query('SHOW client_min_messages') - .then(result => { - expect(result[0].client_min_messages).to.equal('error'); - }); + const result = await sequelize.query('SHOW client_min_messages'); + expect(result[0].client_min_messages).to.equal('error'); }); - it('should not set client_min_messages if clientMinMessages is false', () => { + it('should not set client_min_messages if clientMinMessages is false', async () => { const sequelize = Support.createSequelizeInstance({ clientMinMessages: false }); - return sequelize.query('SHOW client_min_messages') - .then(result => { - // `notice` is Postgres's default - expect(result[0].client_min_messages).to.equal('notice'); - }); + const result = await sequelize.query('SHOW client_min_messages'); + // `notice` is Postgres's default + expect(result[0].client_min_messages).to.equal('notice'); }); }); @@ -79,66 +71,63 @@ if (dialect.match(/^postgres/)) { return User.sync({ force: true }); } - it('should fetch regular dynamic oids and create parsers', () => { + it('should fetch regular dynamic oids and create parsers', async () => { const sequelize = Support.sequelize; - return reloadDynamicOIDs(sequelize).then(() => { - dynamicTypesToCheck.forEach(type => { - expect(type.types.postgres, - `DataType.${type.key}.types.postgres`).to.not.be.empty; + await reloadDynamicOIDs(sequelize); + dynamicTypesToCheck.forEach(type => { + expect(type.types.postgres, + `DataType.${type.key}.types.postgres`).to.not.be.empty; - for (const name of type.types.postgres) { - const entry = sequelize.connectionManager.nameOidMap[name]; - const oidParserMap = sequelize.connectionManager.oidParserMap; + for (const name of type.types.postgres) { + const entry = sequelize.connectionManager.nameOidMap[name]; + const oidParserMap = sequelize.connectionManager.oidParserMap; - expect(entry.oid, `nameOidMap[${name}].oid`).to.be.a('number'); - expect(entry.arrayOid, `nameOidMap[${name}].arrayOid`).to.be.a('number'); + expect(entry.oid, `nameOidMap[${name}].oid`).to.be.a('number'); + expect(entry.arrayOid, `nameOidMap[${name}].arrayOid`).to.be.a('number'); - expect(oidParserMap.get(entry.oid), - `oidParserMap.get(nameOidMap[${name}].oid)`).to.be.a('function'); - expect(oidParserMap.get(entry.arrayOid), - `oidParserMap.get(nameOidMap[${name}].arrayOid)`).to.be.a('function'); - } + expect(oidParserMap.get(entry.oid), + `oidParserMap.get(nameOidMap[${name}].oid)`).to.be.a('function'); + expect(oidParserMap.get(entry.arrayOid), + `oidParserMap.get(nameOidMap[${name}].arrayOid)`).to.be.a('function'); + } - }); }); }); - it('should fetch enum dynamic oids and create parsers', () => { + it('should fetch enum dynamic oids and create parsers', async () => { const sequelize = Support.sequelize; - return reloadDynamicOIDs(sequelize).then(() => { - const enumOids = sequelize.connectionManager.enumOids; - const oidParserMap = sequelize.connectionManager.oidParserMap; - - expect(enumOids.oids, 'enumOids.oids').to.not.be.empty; - expect(enumOids.arrayOids, 'enumOids.arrayOids').to.not.be.empty; - - for (const oid of enumOids.oids) { - expect(oidParserMap.get(oid), 'oidParserMap.get(enumOids.oids)').to.be.a('function'); - } - for (const arrayOid of enumOids.arrayOids) { - expect(oidParserMap.get(arrayOid), 'oidParserMap.get(enumOids.arrayOids)').to.be.a('function'); - } - }); + await reloadDynamicOIDs(sequelize); + const enumOids = sequelize.connectionManager.enumOids; + const oidParserMap = sequelize.connectionManager.oidParserMap; + + expect(enumOids.oids, 'enumOids.oids').to.not.be.empty; + expect(enumOids.arrayOids, 'enumOids.arrayOids').to.not.be.empty; + + for (const oid of enumOids.oids) { + expect(oidParserMap.get(oid), 'oidParserMap.get(enumOids.oids)').to.be.a('function'); + } + for (const arrayOid of enumOids.arrayOids) { + expect(oidParserMap.get(arrayOid), 'oidParserMap.get(enumOids.arrayOids)').to.be.a('function'); + } }); - it('should fetch range dynamic oids and create parsers', () => { + it('should fetch range dynamic oids and create parsers', async () => { const sequelize = Support.sequelize; - return reloadDynamicOIDs(sequelize).then(() => { - for (const baseKey in expCastTypes) { - const name = expCastTypes[baseKey]; - const entry = sequelize.connectionManager.nameOidMap[name]; - const oidParserMap = sequelize.connectionManager.oidParserMap; - - for (const key of ['rangeOid', 'arrayRangeOid']) { - expect(entry[key], `nameOidMap[${name}][${key}]`).to.be.a('number'); - } + await reloadDynamicOIDs(sequelize); + for (const baseKey in expCastTypes) { + const name = expCastTypes[baseKey]; + const entry = sequelize.connectionManager.nameOidMap[name]; + const oidParserMap = sequelize.connectionManager.oidParserMap; - expect(oidParserMap.get(entry.rangeOid), - `oidParserMap.get(nameOidMap[${name}].rangeOid)`).to.be.a('function'); - expect(oidParserMap.get(entry.arrayRangeOid), - `oidParserMap.get(nameOidMap[${name}].arrayRangeOid)`).to.be.a('function'); + for (const key of ['rangeOid', 'arrayRangeOid']) { + expect(entry[key], `nameOidMap[${name}][${key}]`).to.be.a('number'); } - }); + + expect(oidParserMap.get(entry.rangeOid), + `oidParserMap.get(nameOidMap[${name}].rangeOid)`).to.be.a('function'); + expect(oidParserMap.get(entry.arrayRangeOid), + `oidParserMap.get(nameOidMap[${name}].arrayRangeOid)`).to.be.a('function'); + } }); }); diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index 7032f7d0d043..d85d084012c6 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -11,7 +11,7 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] DAO', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize.options.quoteIdentifiers = true; this.User = this.sequelize.define('User', { username: DataTypes.STRING, @@ -35,15 +35,15 @@ if (dialect.match(/^postgres/)) { holidays: DataTypes.ARRAY(DataTypes.RANGE(DataTypes.DATE)), location: DataTypes.GEOMETRY() }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); afterEach(function() { this.sequelize.options.quoteIdentifiers = true; }); - it('should be able to search within an array', function() { - return this.User.findAll({ + it('should be able to search within an array', async function() { + await this.User.findAll({ where: { email: ['hello', 'world'] }, @@ -54,144 +54,116 @@ if (dialect.match(/^postgres/)) { }); }); - it('should be able to update a field with type ARRAY(JSON)', function() { - return this.User.create({ + it('should be able to update a field with type ARRAY(JSON)', async function() { + const userInstance = await this.User.create({ username: 'bob', email: ['myemail@email.com'], friends: [{ name: 'John Smith' }] - }).then(userInstance => { - expect(userInstance.friends).to.have.length(1); - expect(userInstance.friends[0].name).to.equal('John Smith'); - - return userInstance.update({ - friends: [{ - name: 'John Smythe' - }] - }); - }).then(obj => obj['friends']) - .then(friends => { - expect(friends).to.have.length(1); - expect(friends[0].name).to.equal('John Smythe'); - return friends; - }); + }); + + expect(userInstance.friends).to.have.length(1); + expect(userInstance.friends[0].name).to.equal('John Smith'); + + const obj = await userInstance.update({ + friends: [{ + name: 'John Smythe' + }] + }); + + const friends = await obj['friends']; + expect(friends).to.have.length(1); + expect(friends[0].name).to.equal('John Smythe'); + await friends; }); - it('should be able to find a record while searching in an array', function() { - return this.User.bulkCreate([ + it('should be able to find a record while searching in an array', async function() { + await this.User.bulkCreate([ { username: 'bob', email: ['myemail@email.com'] }, { username: 'tony', email: ['wrongemail@email.com'] } - ]).then(() => { - return this.User.findAll({ where: { email: ['myemail@email.com'] } }).then(user => { - expect(user).to.be.instanceof(Array); - expect(user).to.have.length(1); - expect(user[0].username).to.equal('bob'); - }); - }); + ]); + + const user = await this.User.findAll({ where: { email: ['myemail@email.com'] } }); + expect(user).to.be.instanceof(Array); + expect(user).to.have.length(1); + expect(user[0].username).to.equal('bob'); }); describe('json', () => { - it('should be able to retrieve a row with ->> operator', function() { - return Promise.all([ + it('should be able to retrieve a row with ->> operator', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ where: sequelize.json("emergency_contact->>'name'", 'kate'), attributes: ['username', 'emergency_contact'] }); - }) - .then(user => { - expect(user.emergency_contact.name).to.equal('kate'); - }); + this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]); + + const user = await this.User.findOne({ where: sequelize.json("emergency_contact->>'name'", 'kate'), attributes: ['username', 'emergency_contact'] }); + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using the nested query language', function() { - return Promise.all([ + it('should be able to query using the nested query language', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ - where: sequelize.json({ emergency_contact: { name: 'kate' } }) - }); - }) - .then(user => { - expect(user.emergency_contact.name).to.equal('kate'); - }); + this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]); + + const user = await this.User.findOne({ + where: sequelize.json({ emergency_contact: { name: 'kate' } }) + }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using dot syntax', function() { - return Promise.all([ + it('should be able to query using dot syntax', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ where: sequelize.json('emergency_contact.name', 'joe') }); - }) - .then(user => { - expect(user.emergency_contact.name).to.equal('joe'); - }); + this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]); + + const user = await this.User.findOne({ where: sequelize.json('emergency_contact.name', 'joe') }); + expect(user.emergency_contact.name).to.equal('joe'); }); - it('should be able to query using dot syntax with uppercase name', function() { - return Promise.all([ + it('should be able to query using dot syntax with uppercase name', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ - attributes: [[sequelize.json('emergencyContact.name'), 'contactName']], - where: sequelize.json('emergencyContact.name', 'joe') - }); - }) - .then(user => { - expect(user.get('contactName')).to.equal('joe'); - }); + this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })]); + + const user = await this.User.findOne({ + attributes: [[sequelize.json('emergencyContact.name'), 'contactName']], + where: sequelize.json('emergencyContact.name', 'joe') + }); + + expect(user.get('contactName')).to.equal('joe'); }); - it('should be able to store values that require JSON escaping', function() { + it('should be able to store values that require JSON escaping', async function() { const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values"; - return this.User.create({ username: 'swen', emergency_contact: { value: text } }) - .then(user => { - expect(user.isNewRecord).to.equal(false); - }) - .then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }) - .then(() => { - return this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); - }) - .then(user => { - expect(user.username).to.equal('swen'); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: { value: text } }); + expect(user0.isNewRecord).to.equal(false); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); - it('should be able to findOrCreate with values that require JSON escaping', function() { + it('should be able to findOrCreate with values that require JSON escaping', async function() { const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values"; - return this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } }) - .then(user => { - expect(!user.isNewRecord).to.equal(true); - }) - .then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }) - .then(() => { - return this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); - }) - .then(user => { - expect(user.username).to.equal('swen'); - }); + const user0 = await this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } }); + expect(!user0.isNewRecord).to.equal(true); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); }); describe('hstore', () => { - it('should tell me that a column is hstore and not USER-DEFINED', function() { - return this.sequelize.queryInterface.describeTable('Users').then(table => { - expect(table.settings.type).to.equal('HSTORE'); - expect(table.document.type).to.equal('HSTORE'); - }); + it('should tell me that a column is hstore and not USER-DEFINED', async function() { + const table = await this.sequelize.queryInterface.describeTable('Users'); + expect(table.settings.type).to.equal('HSTORE'); + expect(table.document.type).to.equal('HSTORE'); }); - it('should NOT stringify hstore with insert', function() { - return this.User.create({ + it('should NOT stringify hstore with insert', async function() { + await this.User.create({ username: 'bob', email: ['myemail@email.com'], settings: { mailing: false, push: 'facebook', frequency: 3 } @@ -203,7 +175,7 @@ if (dialect.match(/^postgres/)) { }); }); - it('should not rename hstore fields', function() { + it('should not rename hstore fields', async function() { const Equipment = this.sequelize.define('Equipment', { grapplingHook: { type: DataTypes.STRING, @@ -214,21 +186,21 @@ if (dialect.match(/^postgres/)) { } }); - return Equipment.sync({ force: true }).then(() => { - return Equipment.findAll({ - where: { - utilityBelt: { - grapplingHook: true - } - }, - logging(sql) { - expect(sql).to.contains(' WHERE "Equipment"."utilityBelt" = \'"grapplingHook"=>"true"\';'); + await Equipment.sync({ force: true }); + + await Equipment.findAll({ + where: { + utilityBelt: { + grapplingHook: true } - }); + }, + logging(sql) { + expect(sql).to.contains(' WHERE "Equipment"."utilityBelt" = \'"grapplingHook"=>"true"\';'); + } }); }); - it('should not rename json fields', function() { + it('should not rename json fields', async function() { const Equipment = this.sequelize.define('Equipment', { grapplingHook: { type: DataTypes.STRING, @@ -239,62 +211,61 @@ if (dialect.match(/^postgres/)) { } }); - return Equipment.sync({ force: true }).then(() => { - return Equipment.findAll({ - where: { - utilityBelt: { - grapplingHook: true - } - }, - logging(sql) { - expect(sql).to.contains(' WHERE CAST(("Equipment"."utilityBelt"#>>\'{grapplingHook}\') AS BOOLEAN) = true;'); + await Equipment.sync({ force: true }); + + await Equipment.findAll({ + where: { + utilityBelt: { + grapplingHook: true } - }); + }, + logging(sql) { + expect(sql).to.contains(' WHERE CAST(("Equipment"."utilityBelt"#>>\'{grapplingHook}\') AS BOOLEAN) = true;'); + } }); }); }); describe('range', () => { - it('should tell me that a column is range and not USER-DEFINED', function() { - return this.sequelize.queryInterface.describeTable('Users').then(table => { - expect(table.course_period.type).to.equal('TSTZRANGE'); - expect(table.available_amount.type).to.equal('INT4RANGE'); - }); + it('should tell me that a column is range and not USER-DEFINED', async function() { + const table = await this.sequelize.queryInterface.describeTable('Users'); + expect(table.course_period.type).to.equal('TSTZRANGE'); + expect(table.available_amount.type).to.equal('INT4RANGE'); }); }); describe('enums', () => { - it('should be able to create enums with escape values', function() { + it('should be able to create enums with escape values', async function() { const User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', '1970\'s') }); - return User.sync({ force: true }); + await User.sync({ force: true }); }); - it('should be able to ignore enum types that already exist', function() { + it('should be able to ignore enum types that already exist', async function() { const User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', 'meh') }); - return User.sync({ force: true }).then(() => { - return User.sync(); - }); + await User.sync({ force: true }); + + await User.sync(); }); - it('should be able to create/drop enums multiple times', function() { + it('should be able to create/drop enums multiple times', async function() { const User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', 'meh') }); - return User.sync({ force: true }).then(() => { - return User.sync({ force: true }); - }); + await User.sync({ force: true }); + + await User.sync({ force: true }); }); - it('should be able to create/drop multiple enums multiple times', function() { + it('should be able to create/drop multiple enums multiple times', async function() { const DummyModel = this.sequelize.define('Dummy-pg', { username: DataTypes.STRING, theEnumOne: { @@ -315,16 +286,14 @@ if (dialect.match(/^postgres/)) { } }); - return DummyModel.sync({ force: true }).then(() => { - // now sync one more time: - return DummyModel.sync({ force: true }).then(() => { - // sync without dropping - return DummyModel.sync(); - }); - }); + await DummyModel.sync({ force: true }); + // now sync one more time: + await DummyModel.sync({ force: true }); + // sync without dropping + await DummyModel.sync(); }); - it('should be able to create/drop multiple enums multiple times with field name (#7812)', function() { + it('should be able to create/drop multiple enums multiple times with field name (#7812)', async function() { const DummyModel = this.sequelize.define('Dummy-pg', { username: DataTypes.STRING, theEnumOne: { @@ -347,55 +316,47 @@ if (dialect.match(/^postgres/)) { } }); - return DummyModel.sync({ force: true }).then(() => { - // now sync one more time: - return DummyModel.sync({ force: true }).then(() => { - // sync without dropping - return DummyModel.sync(); - }); - }); + await DummyModel.sync({ force: true }); + // now sync one more time: + await DummyModel.sync({ force: true }); + // sync without dropping + await DummyModel.sync(); }); - it('should be able to add values to enum types', function() { + it('should be able to add values to enum types', async function() { let User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', 'meh') }); - return User.sync({ force: true }).then(() => { - User = this.sequelize.define('UserEnums', { - mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful') - }); - - return User.sync(); - }).then(() => { - return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); - }).then(enums => { - expect(enums).to.have.length(1); - expect(enums[0].enum_value).to.equal('{neutral,happy,sad,ecstatic,meh,joyful}'); + await User.sync({ force: true }); + User = this.sequelize.define('UserEnums', { + mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful') }); + + await User.sync(); + const enums = await this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); + expect(enums).to.have.length(1); + expect(enums[0].enum_value).to.equal('{neutral,happy,sad,ecstatic,meh,joyful}'); }); - it('should be able to add multiple values with different order', function() { + it('should be able to add multiple values with different order', async function() { let User = this.sequelize.define('UserEnums', { priority: DataTypes.ENUM('1', '2', '6') }); - return User.sync({ force: true }).then(() => { - User = this.sequelize.define('UserEnums', { - priority: DataTypes.ENUM('0', '1', '2', '3', '4', '5', '6', '7') - }); - - return User.sync(); - }).then(() => { - return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); - }).then(enums => { - expect(enums).to.have.length(1); - expect(enums[0].enum_value).to.equal('{0,1,2,3,4,5,6,7}'); + await User.sync({ force: true }); + User = this.sequelize.define('UserEnums', { + priority: DataTypes.ENUM('0', '1', '2', '3', '4', '5', '6', '7') }); + + await User.sync(); + const enums = await this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); + expect(enums).to.have.length(1); + expect(enums[0].enum_value).to.equal('{0,1,2,3,4,5,6,7}'); }); describe('ARRAY(ENUM)', () => { - it('should be able to ignore enum types that already exist', function() { + it('should be able to ignore enum types that already exist', async function() { const User = this.sequelize.define('UserEnums', { permissions: DataTypes.ARRAY(DataTypes.ENUM([ 'access', @@ -405,10 +366,12 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }).then(() => User.sync()); + await User.sync({ force: true }); + + await User.sync(); }); - it('should be able to create/drop enums multiple times', function() { + it('should be able to create/drop enums multiple times', async function() { const User = this.sequelize.define('UserEnums', { permissions: DataTypes.ARRAY(DataTypes.ENUM([ 'access', @@ -418,10 +381,12 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }).then(() => User.sync({ force: true })); + await User.sync({ force: true }); + + await User.sync({ force: true }); }); - it('should be able to add values to enum types', function() { + it('should be able to add values to enum types', async function() { let User = this.sequelize.define('UserEnums', { permissions: DataTypes.ARRAY(DataTypes.ENUM([ 'access', @@ -431,23 +396,20 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }).then(() => { - User = this.sequelize.define('UserEnums', { - permissions: DataTypes.ARRAY( - DataTypes.ENUM('view', 'access', 'edit', 'write', 'check', 'delete') - ) - }); - - return User.sync(); - }).then(() => { - return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); - }).then(enums => { - expect(enums).to.have.length(1); - expect(enums[0].enum_value).to.equal('{view,access,edit,write,check,delete}'); + await User.sync({ force: true }); + User = this.sequelize.define('UserEnums', { + permissions: DataTypes.ARRAY( + DataTypes.ENUM('view', 'access', 'edit', 'write', 'check', 'delete') + ) }); + + await User.sync(); + const enums = await this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); + expect(enums).to.have.length(1); + expect(enums[0].enum_value).to.equal('{view,access,edit,write,check,delete}'); }); - it('should be able to insert new record', function() { + it('should be able to insert new record', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, type: DataTypes.ENUM('A', 'B', 'C'), @@ -460,24 +422,22 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }) - .then(() => { - return User.create({ - name: 'file.exe', - type: 'C', - owners: ['userA', 'userB'], - permissions: ['access', 'write'] - }); - }) - .then(user => { - expect(user.name).to.equal('file.exe'); - expect(user.type).to.equal('C'); - expect(user.owners).to.deep.equal(['userA', 'userB']); - expect(user.permissions).to.deep.equal(['access', 'write']); - }); + await User.sync({ force: true }); + + const user = await User.create({ + name: 'file.exe', + type: 'C', + owners: ['userA', 'userB'], + permissions: ['access', 'write'] + }); + + expect(user.name).to.equal('file.exe'); + expect(user.type).to.equal('C'); + expect(user.owners).to.deep.equal(['userA', 'userB']); + expect(user.permissions).to.deep.equal(['access', 'write']); }); - it('should fail when trying to insert foreign element on ARRAY(ENUM)', function() { + it('should fail when trying to insert foreign element on ARRAY(ENUM)', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, type: DataTypes.ENUM('A', 'B', 'C'), @@ -490,7 +450,7 @@ if (dialect.match(/^postgres/)) { ])) }); - return expect(User.sync({ force: true }).then(() => { + await expect(User.sync({ force: true }).then(() => { return User.create({ name: 'file.exe', type: 'C', @@ -500,7 +460,7 @@ if (dialect.match(/^postgres/)) { })).to.be.rejectedWith(/invalid input value for enum "enum_UserEnums_permissions": "cosmic_ray_disk_access"/); }); - it('should be able to find records', function() { + it('should be able to find records', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, type: DataTypes.ENUM('A', 'B', 'C'), @@ -512,120 +472,109 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }) - .then(() => { - return User.bulkCreate([{ - name: 'file1.exe', - type: 'C', - permissions: ['access', 'write'] - }, { - name: 'file2.exe', - type: 'A', - permissions: ['access', 'check'] - }, { - name: 'file3.exe', - type: 'B', - permissions: ['access', 'write', 'delete'] - }]); - }) - .then(() => { - return User.findAll({ - where: { - type: { - [Op.in]: ['A', 'C'] - }, - permissions: { - [Op.contains]: ['write'] - } - } - }); - }) - .then(users => { - expect(users.length).to.equal(1); - expect(users[0].name).to.equal('file1.exe'); - expect(users[0].type).to.equal('C'); - expect(users[0].permissions).to.deep.equal(['access', 'write']); - }); + await User.sync({ force: true }); + + await User.bulkCreate([{ + name: 'file1.exe', + type: 'C', + permissions: ['access', 'write'] + }, { + name: 'file2.exe', + type: 'A', + permissions: ['access', 'check'] + }, { + name: 'file3.exe', + type: 'B', + permissions: ['access', 'write', 'delete'] + }]); + + const users = await User.findAll({ + where: { + type: { + [Op.in]: ['A', 'C'] + }, + permissions: { + [Op.contains]: ['write'] + } + } + }); + + expect(users.length).to.equal(1); + expect(users[0].name).to.equal('file1.exe'); + expect(users[0].type).to.equal('C'); + expect(users[0].permissions).to.deep.equal(['access', 'write']); }); }); }); describe('integers', () => { describe('integer', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.INTEGER }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: 2147483647 }).then(user => { - expect(user.aNumber).to.equal(2147483647); - return User.findOne({ where: { aNumber: 2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(2147483647); - }); - }); + const user = await User.create({ aNumber: 2147483647 }); + expect(user.aNumber).to.equal(2147483647); + const _user = await User.findOne({ where: { aNumber: 2147483647 } }); + expect(_user.aNumber).to.equal(2147483647); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: -2147483647 }).then(user => { - expect(user.aNumber).to.equal(-2147483647); - return User.findOne({ where: { aNumber: -2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(-2147483647); - }); - }); + const user = await User.create({ aNumber: -2147483647 }); + expect(user.aNumber).to.equal(-2147483647); + const _user = await User.findOne({ where: { aNumber: -2147483647 } }); + expect(_user.aNumber).to.equal(-2147483647); }); }); describe('bigint', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.BIGINT }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: '9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('9223372036854775807'); - return User.findOne({ where: { aNumber: '9223372036854775807' } }).then(_user => { - expect(_user.aNumber).to.equal('9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '9223372036854775807' }); + expect(user.aNumber).to.equal('9223372036854775807'); + const _user = await User.findOne({ where: { aNumber: '9223372036854775807' } }); + expect(_user.aNumber).to.equal('9223372036854775807'); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: '-9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('-9223372036854775807'); - return User.findOne({ where: { aNumber: '-9223372036854775807' } }).then(_user => { - expect(_user.aNumber).to.equal('-9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '-9223372036854775807' }); + expect(user.aNumber).to.equal('-9223372036854775807'); + const _user = await User.findOne({ where: { aNumber: '-9223372036854775807' } }); + expect(_user.aNumber).to.equal('-9223372036854775807'); }); }); }); describe('timestamps', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { dates: DataTypes.ARRAY(DataTypes.DATE) }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should use bind params instead of "TIMESTAMP WITH TIME ZONE"', function() { - return this.User.create({ + it('should use bind params instead of "TIMESTAMP WITH TIME ZONE"', async function() { + await this.User.create({ dates: [] }, { logging(sql) { @@ -637,127 +586,105 @@ if (dialect.match(/^postgres/)) { }); describe('model', () => { - it('create handles array correctly', function() { - return this.User - .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }) - .then(oldUser => { - expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com']); - }); + it('create handles array correctly', async function() { + const oldUser = await this.User + .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }); + + expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com']); }); - it('should save hstore correctly', function() { - return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' } }).then(newUser => { - // Check to see if the default value for an hstore field works - expect(newUser.document).to.deep.equal({ default: "'value'" }); - expect(newUser.settings).to.deep.equal({ created: '"value"' }); + it('should save hstore correctly', async function() { + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' } }); + // Check to see if the default value for an hstore field works + expect(newUser.document).to.deep.equal({ default: "'value'" }); + expect(newUser.settings).to.deep.equal({ created: '"value"' }); - // Check to see if updating an hstore field works - return newUser.update({ settings: { should: 'update', to: 'this', first: 'place' } }).then(oldUser => { - // Postgres always returns keys in alphabetical order (ascending) - expect(oldUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); - }); - }); + // Check to see if updating an hstore field works + const oldUser = await newUser.update({ settings: { should: 'update', to: 'this', first: 'place' } }); + // Postgres always returns keys in alphabetical order (ascending) + expect(oldUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); }); - it('should save hstore array correctly', function() { + it('should save hstore array correctly', async function() { const User = this.User; - return this.User.create({ + await this.User.create({ username: 'bob', email: ['myemail@email.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }, { number: '8675309', type: "Jenny's" }, { number: '5555554321', type: '"home\n"' }] - }).then(() => { - return User.findByPk(1).then(user => { - expect(user.phones.length).to.equal(4); - expect(user.phones[1].number).to.equal('987654321'); - expect(user.phones[2].type).to.equal("Jenny's"); - expect(user.phones[3].type).to.equal('"home\n"'); - }); }); + + const user = await User.findByPk(1); + expect(user.phones.length).to.equal(4); + expect(user.phones[1].number).to.equal('987654321'); + expect(user.phones[2].type).to.equal("Jenny's"); + expect(user.phones[3].type).to.equal('"home\n"'); }); - it('should bulkCreate with hstore property', function() { + it('should bulkCreate with hstore property', async function() { const User = this.User; - return this.User.bulkCreate([{ + await this.User.bulkCreate([{ username: 'bob', email: ['myemail@email.com'], settings: { mailing: true, push: 'facebook', frequency: 3 } - }]).then(() => { - return User.findByPk(1).then(user => { - expect(user.settings.mailing).to.equal('true'); - }); - }); + }]); + + const user = await User.findByPk(1); + expect(user.settings.mailing).to.equal('true'); }); - it('should update hstore correctly', function() { - return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }).then(newUser => { - // Check to see if the default value for an hstore field works - expect(newUser.document).to.deep.equal({ default: "'value'" }); - expect(newUser.settings).to.deep.equal({ test: '"value"' }); - - // Check to see if updating an hstore field works - return this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: newUser.where() }).then(() => { - return newUser.reload().then(() => { - // Postgres always returns keys in alphabetical order (ascending) - expect(newUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); - }); - }); - }); + it('should update hstore correctly', async function() { + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }); + // Check to see if the default value for an hstore field works + expect(newUser.document).to.deep.equal({ default: "'value'" }); + expect(newUser.settings).to.deep.equal({ test: '"value"' }); + + // Check to see if updating an hstore field works + await this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: newUser.where() }); + await newUser.reload(); + // Postgres always returns keys in alphabetical order (ascending) + expect(newUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); }); - it('should update hstore correctly and return the affected rows', function() { - return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }).then(oldUser => { - // Update the user and check that the returned object's fields have been parsed by the hstore library - return this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: oldUser.where(), returning: true }).then(([count, users]) => { - expect(count).to.equal(1); - expect(users[0].settings).to.deep.equal({ should: 'update', to: 'this', first: 'place' }); - }); - }); + it('should update hstore correctly and return the affected rows', async function() { + const oldUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }); + // Update the user and check that the returned object's fields have been parsed by the hstore library + const [count, users] = await this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: oldUser.where(), returning: true }); + expect(count).to.equal(1); + expect(users[0].settings).to.deep.equal({ should: 'update', to: 'this', first: 'place' }); }); - it('should read hstore correctly', function() { + it('should read hstore correctly', async function() { const data = { username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }; - return this.User.create(data) - .then(() => { - return this.User.findOne({ where: { username: 'user' } }); - }) - .then(user => { - // Check that the hstore fields are the same when retrieving the user - expect(user.settings).to.deep.equal(data.settings); - }); + await this.User.create(data); + const user = await this.User.findOne({ where: { username: 'user' } }); + // Check that the hstore fields are the same when retrieving the user + expect(user.settings).to.deep.equal(data.settings); }); - it('should read an hstore array correctly', function() { + it('should read an hstore array correctly', async function() { const data = { username: 'user', email: ['foo@bar.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }] }; - return this.User.create(data) - .then(() => { - // Check that the hstore fields are the same when retrieving the user - return this.User.findOne({ where: { username: 'user' } }); - }).then(user => { - expect(user.phones).to.deep.equal(data.phones); - }); + await this.User.create(data); + // Check that the hstore fields are the same when retrieving the user + const user = await this.User.findOne({ where: { username: 'user' } }); + expect(user.phones).to.deep.equal(data.phones); }); - it('should read hstore correctly from multiple rows', function() { - return this.User - .create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' } }) - .then(() => { - return this.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another: '"example"' } }); - }) - .then(() => { - // Check that the hstore fields are the same when retrieving the user - return this.User.findAll({ order: ['username'] }); - }) - .then(users => { - expect(users[0].settings).to.deep.equal({ test: '"value"' }); - expect(users[1].settings).to.deep.equal({ another: '"example"' }); - }); + it('should read hstore correctly from multiple rows', async function() { + await this.User + .create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' } }); + + await this.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another: '"example"' } }); + // Check that the hstore fields are the same when retrieving the user + const users = await this.User.findAll({ order: ['username'] }); + expect(users[0].settings).to.deep.equal({ test: '"value"' }); + expect(users[1].settings).to.deep.equal({ another: '"example"' }); }); - it('should read hstore correctly from included models as well', function() { + it('should read hstore correctly from included models as well', async function() { const HstoreSubmodel = this.sequelize.define('hstoreSubmodel', { someValue: DataTypes.HSTORE }); @@ -765,179 +692,156 @@ if (dialect.match(/^postgres/)) { this.User.hasMany(HstoreSubmodel); - return this.sequelize - .sync({ force: true }) - .then(() => { - return this.User.create({ username: 'user1' }) - .then(user => { - return HstoreSubmodel.create({ someValue: submodelValue }) - .then(submodel => { - return user.setHstoreSubmodels([submodel]); - }); - }); - }) - .then(() => { - return this.User.findOne({ where: { username: 'user1' }, include: [HstoreSubmodel] }); - }) - .then(user => { - expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok; - expect(user.hstoreSubmodels.length).to.equal(1); - expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue); - }); + await this.sequelize + .sync({ force: true }); + + const user0 = await this.User.create({ username: 'user1' }); + const submodel = await HstoreSubmodel.create({ someValue: submodelValue }); + await user0.setHstoreSubmodels([submodel]); + const user = await this.User.findOne({ where: { username: 'user1' }, include: [HstoreSubmodel] }); + expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok; + expect(user.hstoreSubmodels.length).to.equal(1); + expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue); }); - it('should save range correctly', function() { + it('should save range correctly', async function() { const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(newUser => { - // Check to see if the default value for a range field works - - expect(newUser.acceptable_marks.length).to.equal(2); - expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound - expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound - expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive - expect(newUser.course_period[0].value instanceof Date).to.be.ok; // lower bound - expect(newUser.course_period[1].value instanceof Date).to.be.ok; // upper bound - expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound - expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound - expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive - - // Check to see if updating a range field works - return newUser.update({ acceptable_marks: [0.8, 0.9] }) - .then(() => newUser.reload()) // Ensure the acceptable_marks array is loaded with the complete range definition - .then(() => { - expect(newUser.acceptable_marks.length).to.equal(2); - expect(newUser.acceptable_marks[0].value).to.equal('0.8'); // lower bound - expect(newUser.acceptable_marks[1].value).to.equal('0.9'); // upper bound - }); - }); + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }); + // Check to see if the default value for a range field works + + expect(newUser.acceptable_marks.length).to.equal(2); + expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound + expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound + expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive + expect(newUser.course_period[0].value instanceof Date).to.be.ok; // lower bound + expect(newUser.course_period[1].value instanceof Date).to.be.ok; // upper bound + expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound + expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound + expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive + + // Check to see if updating a range field works + await newUser.update({ acceptable_marks: [0.8, 0.9] }); + await newUser.reload(); // Ensure the acceptable_marks array is loaded with the complete range definition + expect(newUser.acceptable_marks.length).to.equal(2); + expect(newUser.acceptable_marks[0].value).to.equal('0.8'); // lower bound + expect(newUser.acceptable_marks[1].value).to.equal('0.9'); // upper bound }); - it('should save range array correctly', function() { + it('should save range array correctly', async function() { const User = this.User; const holidays = [ [new Date(2015, 3, 1), new Date(2015, 3, 15)], [new Date(2015, 8, 1), new Date(2015, 9, 15)] ]; - return User.create({ + await User.create({ username: 'bob', email: ['myemail@email.com'], holidays - }).then(() => { - return User.findByPk(1).then(user => { - expect(user.holidays.length).to.equal(2); - expect(user.holidays[0].length).to.equal(2); - expect(user.holidays[0][0].value instanceof Date).to.be.ok; - expect(user.holidays[0][1].value instanceof Date).to.be.ok; - expect(user.holidays[0][0].value).to.equalTime(holidays[0][0]); - expect(user.holidays[0][1].value).to.equalTime(holidays[0][1]); - expect(user.holidays[1].length).to.equal(2); - expect(user.holidays[1][0].value instanceof Date).to.be.ok; - expect(user.holidays[1][1].value instanceof Date).to.be.ok; - expect(user.holidays[1][0].value).to.equalTime(holidays[1][0]); - expect(user.holidays[1][1].value).to.equalTime(holidays[1][1]); - }); }); + + const user = await User.findByPk(1); + expect(user.holidays.length).to.equal(2); + expect(user.holidays[0].length).to.equal(2); + expect(user.holidays[0][0].value instanceof Date).to.be.ok; + expect(user.holidays[0][1].value instanceof Date).to.be.ok; + expect(user.holidays[0][0].value).to.equalTime(holidays[0][0]); + expect(user.holidays[0][1].value).to.equalTime(holidays[0][1]); + expect(user.holidays[1].length).to.equal(2); + expect(user.holidays[1][0].value instanceof Date).to.be.ok; + expect(user.holidays[1][1].value instanceof Date).to.be.ok; + expect(user.holidays[1][0].value).to.equalTime(holidays[1][0]); + expect(user.holidays[1][1].value).to.equalTime(holidays[1][1]); }); - it('should bulkCreate with range property', function() { + it('should bulkCreate with range property', async function() { const User = this.User; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - return User.bulkCreate([{ + await User.bulkCreate([{ username: 'bob', email: ['myemail@email.com'], course_period: period - }]).then(() => { - return User.findByPk(1).then(user => { - expect(user.course_period[0].value instanceof Date).to.be.ok; - expect(user.course_period[1].value instanceof Date).to.be.ok; - expect(user.course_period[0].value).to.equalTime(period[0]); // lower bound - expect(user.course_period[1].value).to.equalTime(period[1]); // upper bound - expect(user.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(user.course_period[1].inclusive).to.deep.equal(false); // exclusive - }); - }); + }]); + + const user = await User.findByPk(1); + expect(user.course_period[0].value instanceof Date).to.be.ok; + expect(user.course_period[1].value instanceof Date).to.be.ok; + expect(user.course_period[0].value).to.equalTime(period[0]); // lower bound + expect(user.course_period[1].value).to.equalTime(period[1]); // upper bound + expect(user.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(user.course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should update range correctly', function() { + it('should update range correctly', async function() { const User = this.User; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(newUser => { - // Check to see if the default value for a range field works - expect(newUser.acceptable_marks.length).to.equal(2); - expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound - expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound - expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive - expect(newUser.course_period[0].value instanceof Date).to.be.ok; - expect(newUser.course_period[1].value instanceof Date).to.be.ok; - expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound - expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound - expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive - - - const period2 = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; - - // Check to see if updating a range field works - return User.update({ course_period: period2 }, { where: newUser.where() }).then(() => { - return newUser.reload().then(() => { - expect(newUser.course_period[0].value instanceof Date).to.be.ok; - expect(newUser.course_period[1].value instanceof Date).to.be.ok; - expect(newUser.course_period[0].value).to.equalTime(period2[0]); // lower bound - expect(newUser.course_period[1].value).to.equalTime(period2[1]); // upper bound - expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive - }); - }); - }); + const newUser = await User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }); + // Check to see if the default value for a range field works + expect(newUser.acceptable_marks.length).to.equal(2); + expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound + expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound + expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive + expect(newUser.course_period[0].value instanceof Date).to.be.ok; + expect(newUser.course_period[1].value instanceof Date).to.be.ok; + expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound + expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound + expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive + + + const period2 = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; + + // Check to see if updating a range field works + await User.update({ course_period: period2 }, { where: newUser.where() }); + await newUser.reload(); + expect(newUser.course_period[0].value instanceof Date).to.be.ok; + expect(newUser.course_period[1].value instanceof Date).to.be.ok; + expect(newUser.course_period[0].value).to.equalTime(period2[0]); // lower bound + expect(newUser.course_period[1].value).to.equalTime(period2[1]); // upper bound + expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should update range correctly and return the affected rows', function() { + it('should update range correctly and return the affected rows', async function() { const User = this.User; const period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; - return User.create({ + const oldUser = await User.create({ username: 'user', email: ['foo@bar.com'], course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)] - }).then(oldUser => { - // Update the user and check that the returned object's fields have been parsed by the range parser - return User.update({ course_period: period }, { where: oldUser.where(), returning: true }) - .then(([count, users]) => { - expect(count).to.equal(1); - expect(users[0].course_period[0].value instanceof Date).to.be.ok; - expect(users[0].course_period[1].value instanceof Date).to.be.ok; - expect(users[0].course_period[0].value).to.equalTime(period[0]); // lower bound - expect(users[0].course_period[1].value).to.equalTime(period[1]); // upper bound - expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive - }); }); + + // Update the user and check that the returned object's fields have been parsed by the range parser + const [count, users] = await User.update({ course_period: period }, { where: oldUser.where(), returning: true }); + expect(count).to.equal(1); + expect(users[0].course_period[0].value instanceof Date).to.be.ok; + expect(users[0].course_period[1].value instanceof Date).to.be.ok; + expect(users[0].course_period[0].value).to.equalTime(period[0]); // lower bound + expect(users[0].course_period[1].value).to.equalTime(period[1]); // upper bound + expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should read range correctly', function() { + it('should read range correctly', async function() { const User = this.User; const course_period = [{ value: new Date(2015, 1, 1), inclusive: false }, { value: new Date(2015, 10, 30), inclusive: false }]; const data = { username: 'user', email: ['foo@bar.com'], course_period }; - return User.create(data) - .then(() => { - return User.findOne({ where: { username: 'user' } }); - }) - .then(user => { - // Check that the range fields are the same when retrieving the user - expect(user.course_period).to.deep.equal(data.course_period); - }); + await User.create(data); + const user = await User.findOne({ where: { username: 'user' } }); + // Check that the range fields are the same when retrieving the user + expect(user.course_period).to.deep.equal(data.course_period); }); - it('should read range array correctly', function() { + it('should read range array correctly', async function() { const User = this.User; const holidays = [ [{ value: new Date(2015, 3, 1, 10), inclusive: true }, { value: new Date(2015, 3, 15), inclusive: true }], @@ -945,44 +849,36 @@ if (dialect.match(/^postgres/)) { ]; const data = { username: 'user', email: ['foo@bar.com'], holidays }; - return User.create(data) - .then(() => { - // Check that the range fields are the same when retrieving the user - return User.findOne({ where: { username: 'user' } }); - }).then(user => { - expect(user.holidays).to.deep.equal(data.holidays); - }); + await User.create(data); + // Check that the range fields are the same when retrieving the user + const user = await User.findOne({ where: { username: 'user' } }); + expect(user.holidays).to.deep.equal(data.holidays); }); - it('should read range correctly from multiple rows', function() { + it('should read range correctly from multiple rows', async function() { const User = this.User; const periods = [ [new Date(2015, 0, 1), new Date(2015, 11, 31)], [new Date(2016, 0, 1), new Date(2016, 11, 31)] ]; - return User - .create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0] }) - .then(() => { - return User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1] }); - }) - .then(() => { - // Check that the range fields are the same when retrieving the user - return User.findAll({ order: ['username'] }); - }) - .then(users => { - expect(users[0].course_period[0].value).to.equalTime(periods[0][0]); // lower bound - expect(users[0].course_period[1].value).to.equalTime(periods[0][1]); // upper bound - expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive - expect(users[1].course_period[0].value).to.equalTime(periods[1][0]); // lower bound - expect(users[1].course_period[1].value).to.equalTime(periods[1][1]); // upper bound - expect(users[1].course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(users[1].course_period[1].inclusive).to.deep.equal(false); // exclusive - }); + await User + .create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0] }); + + await User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1] }); + // Check that the range fields are the same when retrieving the user + const users = await User.findAll({ order: ['username'] }); + expect(users[0].course_period[0].value).to.equalTime(periods[0][0]); // lower bound + expect(users[0].course_period[1].value).to.equalTime(periods[0][1]); // upper bound + expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive + expect(users[1].course_period[0].value).to.equalTime(periods[1][0]); // lower bound + expect(users[1].course_period[1].value).to.equalTime(periods[1][1]); // upper bound + expect(users[1].course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(users[1].course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should read range correctly from included models as well', function() { + it('should read range correctly from included models as well', async function() { const period = [new Date(2016, 0, 1), new Date(2016, 11, 31)]; const HolidayDate = this.sequelize.define('holidayDate', { period: DataTypes.RANGE(DataTypes.DATE) @@ -990,62 +886,49 @@ if (dialect.match(/^postgres/)) { this.User.hasMany(HolidayDate); - return this.sequelize - .sync({ force: true }) - .then(() => { - return this.User - .create({ username: 'user', email: ['foo@bar.com'] }) - .then(user => { - return HolidayDate.create({ period }) - .then(holidayDate => { - return user.setHolidayDates([holidayDate]); - }); - }); - }) - .then(() => { - return this.User.findOne({ where: { username: 'user' }, include: [HolidayDate] }); - }) - .then(user => { - expect(user.hasOwnProperty('holidayDates')).to.be.ok; - expect(user.holidayDates.length).to.equal(1); - expect(user.holidayDates[0].period.length).to.equal(2); - expect(user.holidayDates[0].period[0].value).to.equalTime(period[0]); - expect(user.holidayDates[0].period[1].value).to.equalTime(period[1]); - }); + await this.sequelize + .sync({ force: true }); + + const user0 = await this.User + .create({ username: 'user', email: ['foo@bar.com'] }); + + const holidayDate = await HolidayDate.create({ period }); + await user0.setHolidayDates([holidayDate]); + const user = await this.User.findOne({ where: { username: 'user' }, include: [HolidayDate] }); + expect(user.hasOwnProperty('holidayDates')).to.be.ok; + expect(user.holidayDates.length).to.equal(1); + expect(user.holidayDates[0].period.length).to.equal(2); + expect(user.holidayDates[0].period[0].value).to.equalTime(period[0]); + expect(user.holidayDates[0].period[1].value).to.equalTime(period[1]); }); }); - it('should save geometry correctly', function() { + it('should save geometry correctly', async function() { const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return this.User.create({ username: 'user', email: ['foo@bar.com'], location: point }).then(newUser => { - expect(newUser.location).to.deep.eql(point); - }); + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], location: point }); + expect(newUser.location).to.deep.eql(point); }); - it('should update geometry correctly', function() { + it('should update geometry correctly', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }; const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] }; - return User.create({ username: 'user', email: ['foo@bar.com'], location: point1 }).then(oldUser => { - return User.update({ location: point2 }, { where: { username: oldUser.username }, returning: true }).then(([, updatedUsers]) => { - expect(updatedUsers[0].location).to.deep.eql(point2); - }); - }); + const oldUser = await User.create({ username: 'user', email: ['foo@bar.com'], location: point1 }); + const [, updatedUsers] = await User.update({ location: point2 }, { where: { username: oldUser.username }, returning: true }); + expect(updatedUsers[0].location).to.deep.eql(point2); }); - it('should read geometry correctly', function() { + it('should read geometry correctly', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create({ username: 'user', email: ['foo@bar.com'], location: point }).then(user => { - return User.findOne({ where: { username: user.username } }); - }).then(user => { - expect(user.location).to.deep.eql(point); - }); + const user0 = await User.create({ username: 'user', email: ['foo@bar.com'], location: point }); + const user = await User.findOne({ where: { username: user0.username } }); + expect(user.location).to.deep.eql(point); }); describe('[POSTGRES] Unquoted identifiers', () => { - it('can insert and select', function() { + it('can insert and select', async function() { this.sequelize.options.quoteIdentifiers = false; this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; @@ -1056,42 +939,40 @@ if (dialect.match(/^postgres/)) { quoteIdentifiers: false }); - return this.User.sync({ force: true }).then(() => { - return this.User - .create({ username: 'user', fullName: 'John Smith' }) - .then(user => { - // We can insert into a table with non-quoted identifiers - expect(user.id).to.exist; - expect(user.id).not.to.be.null; - expect(user.username).to.equal('user'); - expect(user.fullName).to.equal('John Smith'); - - // We can query by non-quoted identifiers - return this.User.findOne({ - where: { fullName: 'John Smith' } - }).then(user2 => { - // We can map values back to non-quoted identifiers - expect(user2.id).to.equal(user.id); - expect(user2.username).to.equal('user'); - expect(user2.fullName).to.equal('John Smith'); - - // We can query and aggregate by non-quoted identifiers - return this.User - .count({ - where: { fullName: 'John Smith' } - }) - .then(count => { - this.sequelize.options.quoteIndentifiers = true; - this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; - this.sequelize.options.logging = false; - expect(count).to.equal(1); - }); - }); - }); + await this.User.sync({ force: true }); + + const user = await this.User + .create({ username: 'user', fullName: 'John Smith' }); + + // We can insert into a table with non-quoted identifiers + expect(user.id).to.exist; + expect(user.id).not.to.be.null; + expect(user.username).to.equal('user'); + expect(user.fullName).to.equal('John Smith'); + + // We can query by non-quoted identifiers + const user2 = await this.User.findOne({ + where: { fullName: 'John Smith' } }); + + // We can map values back to non-quoted identifiers + expect(user2.id).to.equal(user.id); + expect(user2.username).to.equal('user'); + expect(user2.fullName).to.equal('John Smith'); + + // We can query and aggregate by non-quoted identifiers + const count = await this.User + .count({ + where: { fullName: 'John Smith' } + }); + + this.sequelize.options.quoteIndentifiers = true; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; + this.sequelize.options.logging = false; + expect(count).to.equal(1); }); - it('can select nested include', function() { + it('can select nested include', async function() { this.sequelize.options.quoteIdentifiers = false; this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; this.Professor = this.sequelize.define('Professor', { @@ -1118,115 +999,105 @@ if (dialect.match(/^postgres/)) { this.Class.belongsTo(this.Professor); this.Class.belongsToMany(this.Student, { through: this.ClassStudent }); this.Student.belongsToMany(this.Class, { through: this.ClassStudent }); - return this.Professor.sync({ force: true }) - .then(() => { - return this.Student.sync({ force: true }); - }) - .then(() => { - return this.Class.sync({ force: true }); - }) - .then(() => { - return this.ClassStudent.sync({ force: true }); - }) - .then(() => { - return this.Professor.bulkCreate([ - { - id: 1, - fullName: 'Albus Dumbledore' - }, - { - id: 2, - fullName: 'Severus Snape' - } - ]); - }) - .then(() => { - return this.Class.bulkCreate([ - { - id: 1, - name: 'Transfiguration', - ProfessorId: 1 - }, - { - id: 2, - name: 'Potions', - ProfessorId: 2 - }, - { - id: 3, - name: 'Defence Against the Dark Arts', - ProfessorId: 2 - } - ]); - }) - .then(() => { - return this.Student.bulkCreate([ - { - id: 1, - fullName: 'Harry Potter' - }, - { - id: 2, - fullName: 'Ron Weasley' - }, - { - id: 3, - fullName: 'Ginny Weasley' - }, + + try { + await this.Professor.sync({ force: true }); + await this.Student.sync({ force: true }); + await this.Class.sync({ force: true }); + await this.ClassStudent.sync({ force: true }); + + await this.Professor.bulkCreate([ + { + id: 1, + fullName: 'Albus Dumbledore' + }, + { + id: 2, + fullName: 'Severus Snape' + } + ]); + + await this.Class.bulkCreate([ + { + id: 1, + name: 'Transfiguration', + ProfessorId: 1 + }, + { + id: 2, + name: 'Potions', + ProfessorId: 2 + }, + { + id: 3, + name: 'Defence Against the Dark Arts', + ProfessorId: 2 + } + ]); + + await this.Student.bulkCreate([ + { + id: 1, + fullName: 'Harry Potter' + }, + { + id: 2, + fullName: 'Ron Weasley' + }, + { + id: 3, + fullName: 'Ginny Weasley' + }, + { + id: 4, + fullName: 'Hermione Granger' + } + ]); + + await Promise.all([ + this.Student.findByPk(1) + .then(Harry => { + return Harry.setClasses([1, 2, 3]); + }), + this.Student.findByPk(2) + .then(Ron => { + return Ron.setClasses([1, 2]); + }), + this.Student.findByPk(3) + .then(Ginny => { + return Ginny.setClasses([2, 3]); + }), + this.Student.findByPk(4) + .then(Hermione => { + return Hermione.setClasses([1, 2, 3]); + }) + ]); + + const professors = await this.Professor.findAll({ + include: [ { - id: 4, - fullName: 'Hermione Granger' + model: this.Class, + include: [ + { + model: this.Student + } + ] } - ]); - }) - .then(() => { - return Promise.all([ - this.Student.findByPk(1) - .then(Harry => { - return Harry.setClasses([1, 2, 3]); - }), - this.Student.findByPk(2) - .then(Ron => { - return Ron.setClasses([1, 2]); - }), - this.Student.findByPk(3) - .then(Ginny => { - return Ginny.setClasses([2, 3]); - }), - this.Student.findByPk(4) - .then(Hermione => { - return Hermione.setClasses([1, 2, 3]); - }) - ]); - }) - .then(() => { - return this.Professor.findAll({ - include: [ - { - model: this.Class, - include: [ - { - model: this.Student - } - ] - } - ], - order: [ - ['id'], - [this.Class, 'id'], - [this.Class, this.Student, 'id'] - ] - }); - }) - .then(professors => { - expect(professors.length).to.eql(2); - expect(professors[0].fullName).to.eql('Albus Dumbledore'); - expect(professors[0].Classes.length).to.eql(1); - expect(professors[0].Classes[0].Students.length).to.eql(3); - }) - .finally(() => { - this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; + ], + order: [ + ['id'], + [this.Class, 'id'], + [this.Class, this.Student, 'id'] + ] }); + + expect(professors.length).to.eql(2); + expect(professors[0].fullName).to.eql('Albus Dumbledore'); + expect(professors[0].Classes.length).to.eql(1); + expect(professors[0].Classes[0].Students.length).to.eql(3); + } finally { + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; + } }); }); }); diff --git a/test/integration/dialects/postgres/data-types.test.js b/test/integration/dialects/postgres/data-types.test.js index 7407abc2de21..17de0200af52 100644 --- a/test/integration/dialects/postgres/data-types.test.js +++ b/test/integration/dialects/postgres/data-types.test.js @@ -72,7 +72,7 @@ if (dialect === 'postgres') { describe('DATE SQL', () => { // create dummy user - it('should be able to create and update records with Infinity/-Infinity', function() { + it('should be able to create and update records with Infinity/-Infinity', async function() { this.sequelize.options.typeValidation = true; const date = new Date(); @@ -97,69 +97,68 @@ if (dialect === 'postgres') { timestamps: true }); - return User.sync({ + await User.sync({ force: true - }).then(() => { - return User.create({ - username: 'bob', - anotherTime: Infinity - }, { - validate: true - }); - }).then(user => { - expect(user.username).to.equal('bob'); - expect(user.beforeTime).to.equal(-Infinity); - expect(user.sometime).to.be.withinTime(date, new Date()); - expect(user.anotherTime).to.equal(Infinity); - expect(user.afterTime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: this.sequelize.fn('NOW') - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.be.withinTime(date, new Date()); - - // find - return User.findAll(); - }).then(users => { - expect(users[0].beforeTime).to.equal(-Infinity); - expect(users[0].sometime).to.not.equal(Infinity); - expect(users[0].afterTime).to.equal(Infinity); - - return users[0].update({ - sometime: date - }); - }).then(user => { - expect(user.sometime).to.equalTime(date); - - return user.update({ - sometime: date - }); - }).then(user => { - expect(user.sometime).to.equalTime(date); }); + + const user4 = await User.create({ + username: 'bob', + anotherTime: Infinity + }, { + validate: true + }); + + expect(user4.username).to.equal('bob'); + expect(user4.beforeTime).to.equal(-Infinity); + expect(user4.sometime).to.be.withinTime(date, new Date()); + expect(user4.anotherTime).to.equal(Infinity); + expect(user4.afterTime).to.equal(Infinity); + + const user3 = await user4.update({ + sometime: Infinity + }, { + returning: true + }); + + expect(user3.sometime).to.equal(Infinity); + + const user2 = await user3.update({ + sometime: Infinity + }); + + expect(user2.sometime).to.equal(Infinity); + + const user1 = await user2.update({ + sometime: this.sequelize.fn('NOW') + }, { + returning: true + }); + + expect(user1.sometime).to.be.withinTime(date, new Date()); + + // find + const users = await User.findAll(); + expect(users[0].beforeTime).to.equal(-Infinity); + expect(users[0].sometime).to.not.equal(Infinity); + expect(users[0].afterTime).to.equal(Infinity); + + const user0 = await users[0].update({ + sometime: date + }); + + expect(user0.sometime).to.equalTime(date); + + const user = await user0.update({ + sometime: date + }); + + expect(user.sometime).to.equalTime(date); }); }); describe('DATEONLY SQL', () => { // create dummy user - it('should be able to create and update records with Infinity/-Infinity', function() { + it('should be able to create and update records with Infinity/-Infinity', async function() { this.sequelize.options.typeValidation = true; const date = new Date(); @@ -184,64 +183,63 @@ if (dialect === 'postgres') { timestamps: true }); - return User.sync({ + await User.sync({ force: true - }).then(() => { - return User.create({ - username: 'bob', - anotherTime: Infinity - }, { - validate: true - }); - }).then(user => { - expect(user.username).to.equal('bob'); - expect(user.beforeTime).to.equal(-Infinity); - expect(new Date(user.sometime)).to.be.withinDate(date, new Date()); - expect(user.anotherTime).to.equal(Infinity); - expect(user.afterTime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: this.sequelize.fn('NOW') - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.not.equal(Infinity); - expect(new Date(user.sometime)).to.be.withinDate(date, new Date()); - - // find - return User.findAll(); - }).then(users => { - expect(users[0].beforeTime).to.equal(-Infinity); - expect(users[0].sometime).to.not.equal(Infinity); - expect(users[0].afterTime).to.equal(Infinity); - - return users[0].update({ - sometime: '1969-07-20' - }); - }).then(user => { - expect(user.sometime).to.equal('1969-07-20'); - - return user.update({ - sometime: '1969-07-20' - }); - }).then(user => { - expect(user.sometime).to.equal('1969-07-20'); }); + + const user4 = await User.create({ + username: 'bob', + anotherTime: Infinity + }, { + validate: true + }); + + expect(user4.username).to.equal('bob'); + expect(user4.beforeTime).to.equal(-Infinity); + expect(new Date(user4.sometime)).to.be.withinDate(date, new Date()); + expect(user4.anotherTime).to.equal(Infinity); + expect(user4.afterTime).to.equal(Infinity); + + const user3 = await user4.update({ + sometime: Infinity + }, { + returning: true + }); + + expect(user3.sometime).to.equal(Infinity); + + const user2 = await user3.update({ + sometime: Infinity + }); + + expect(user2.sometime).to.equal(Infinity); + + const user1 = await user2.update({ + sometime: this.sequelize.fn('NOW') + }, { + returning: true + }); + + expect(user1.sometime).to.not.equal(Infinity); + expect(new Date(user1.sometime)).to.be.withinDate(date, new Date()); + + // find + const users = await User.findAll(); + expect(users[0].beforeTime).to.equal(-Infinity); + expect(users[0].sometime).to.not.equal(Infinity); + expect(users[0].afterTime).to.equal(Infinity); + + const user0 = await users[0].update({ + sometime: '1969-07-20' + }); + + expect(user0.sometime).to.equal('1969-07-20'); + + const user = await user0.update({ + sometime: '1969-07-20' + }); + + expect(user.sometime).to.equal('1969-07-20'); }); }); diff --git a/test/integration/dialects/postgres/error.test.js b/test/integration/dialects/postgres/error.test.js index 8c850a1c8e27..425a599cbb2d 100644 --- a/test/integration/dialects/postgres/error.test.js +++ b/test/integration/dialects/postgres/error.test.js @@ -11,18 +11,18 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] ExclusionConstraintError', () => { const constraintName = 'overlap_period'; - beforeEach(function() { + beforeEach(async function() { this.Booking = this.sequelize.define('Booking', { roomNo: DataTypes.INTEGER, period: DataTypes.RANGE(DataTypes.DATE) }); - return this.Booking - .sync({ force: true }) - .then(() => { - return this.sequelize.query( - `ALTER TABLE "${this.Booking.tableName}" ADD CONSTRAINT ${constraintName} EXCLUDE USING gist ("roomNo" WITH =, period WITH &&)` - ); - }); + + await this.Booking + .sync({ force: true }); + + await this.sequelize.query( + `ALTER TABLE "${this.Booking.tableName}" ADD CONSTRAINT ${constraintName} EXCLUDE USING gist ("roomNo" WITH =, period WITH &&)` + ); }); it('should contain error specific properties', () => { @@ -40,23 +40,22 @@ if (dialect.match(/^postgres/)) { }); }); - it('should throw ExclusionConstraintError when "period" value overlaps existing', function() { + it('should throw ExclusionConstraintError when "period" value overlaps existing', async function() { const Booking = this.Booking; - return Booking + await Booking .create({ roomNo: 1, guestName: 'Incognito Visitor', period: [new Date(2015, 0, 1), new Date(2015, 0, 3)] - }) - .then(() => { - return expect(Booking - .create({ - roomNo: 1, - guestName: 'Frequent Visitor', - period: [new Date(2015, 0, 2), new Date(2015, 0, 5)] - })).to.eventually.be.rejectedWith(Sequelize.ExclusionConstraintError); }); + + await expect(Booking + .create({ + roomNo: 1, + guestName: 'Frequent Visitor', + period: [new Date(2015, 0, 2), new Date(2015, 0, 5)] + })).to.eventually.be.rejectedWith(Sequelize.ExclusionConstraintError); }); }); diff --git a/test/integration/dialects/postgres/query-interface.test.js b/test/integration/dialects/postgres/query-interface.test.js index 98c4f7fab770..066f684f03d8 100644 --- a/test/integration/dialects/postgres/query-interface.test.js +++ b/test/integration/dialects/postgres/query-interface.test.js @@ -16,117 +16,111 @@ if (dialect.match(/^postgres/)) { }); describe('createSchema', () => { - beforeEach(function() { + beforeEach(async function() { // make sure we don't have a pre-existing schema called testSchema. - return this.queryInterface.dropSchema('testschema').catch(() => {}); + await this.queryInterface.dropSchema('testschema').catch(() => {}); }); - it('creates a schema', function() { - return this.queryInterface.createSchema('testschema') - .then(() => this.sequelize.query(` + it('creates a schema', async function() { + await this.queryInterface.createSchema('testschema'); + + const res = await this.sequelize.query(` SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'testschema'; - `, { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res, 'query results').to.not.be.empty; - expect(res[0].schema_name).to.be.equal('testschema'); - }); + `, { type: this.sequelize.QueryTypes.SELECT }); + + expect(res, 'query results').to.not.be.empty; + expect(res[0].schema_name).to.be.equal('testschema'); }); - it('works even when schema exists', function() { - return this.queryInterface.createSchema('testschema') - .then(() => this.queryInterface.createSchema('testschema')) - .then(() => this.sequelize.query(` + it('works even when schema exists', async function() { + await this.queryInterface.createSchema('testschema'); + await this.queryInterface.createSchema('testschema'); + + const res = await this.sequelize.query(` SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'testschema'; - `, { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res, 'query results').to.not.be.empty; - expect(res[0].schema_name).to.be.equal('testschema'); - }); + `, { type: this.sequelize.QueryTypes.SELECT }); + + expect(res, 'query results').to.not.be.empty; + expect(res[0].schema_name).to.be.equal('testschema'); }); }); describe('databaseVersion', () => { - it('reports version', function() { - return this.queryInterface.databaseVersion() - .then(res => { - // check that result matches expected version number format. example 9.5.4 - expect(res).to.match(/\d\.\d/); - }); + it('reports version', async function() { + const res = await this.queryInterface.databaseVersion(); + // check that result matches expected version number format. example 9.5.4 + expect(res).to.match(/\d\.\d/); }); }); describe('renameFunction', () => { - beforeEach(function() { + beforeEach(async function() { // ensure the function names we'll use don't exist before we start. // then setup our function to rename - return this.queryInterface.dropFunction('rftest1', []) - .catch(() => {}) - .then(() => this.queryInterface.dropFunction('rftest2', [])) - .catch(() => {}) - .then(() => this.queryInterface.createFunction('rftest1', [], 'varchar', 'plpgsql', 'return \'testreturn\';', {})); + await this.queryInterface.dropFunction('rftest1', []) + .catch(() => {}); + + await this.queryInterface.dropFunction('rftest2', []) + .catch(() => {}); + + await this.queryInterface.createFunction('rftest1', [], 'varchar', 'plpgsql', 'return \'testreturn\';', {}); }); - it('renames a function', function() { - return this.queryInterface.renameFunction('rftest1', [], 'rftest2') - .then(() => this.sequelize.query('select rftest2();', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].rftest2).to.be.eql('testreturn'); - }); + it('renames a function', async function() { + await this.queryInterface.renameFunction('rftest1', [], 'rftest2'); + const res = await this.sequelize.query('select rftest2();', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].rftest2).to.be.eql('testreturn'); }); }); describe('createFunction', () => { - beforeEach(function() { + beforeEach(async function() { // make sure we don't have a pre-existing function called create_job // this is needed to cover the edge case of afterEach not getting called because of an unexpected issue or stopage with the // test suite causing a failure of afterEach's cleanup to be called. - return this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) + await this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. .catch(() => {}); }); - after(function() { + after(async function() { // cleanup - return this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) + await this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. .catch(() => {}); }); - it('creates a stored procedure', function() { + it('creates a stored procedure', async function() { const body = 'return test;'; const options = {}; // make our call to create a function - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options) - // validate - .then(() => this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].create_job).to.be.eql('test'); - }); + await this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options); + // validate + const res = await this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].create_job).to.be.eql('test'); }); - it('treats options as optional', function() { + it('treats options as optional', async function() { const body = 'return test;'; // run with null options parameter - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, null) - // validate - .then(() => this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].create_job).to.be.eql('test'); - }); + await this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, null); + // validate + const res = await this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].create_job).to.be.eql('test'); }); - it('produces an error when missing expected parameters', function() { + it('produces an error when missing expected parameters', async function() { const body = 'return 1;'; const options = {}; - return Promise.all([ + await Promise.all([ // requires functionName expect(this.queryInterface.createFunction(null, [{ name: 'test' }], 'integer', 'plpgsql', body, options)) .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), @@ -153,19 +147,25 @@ if (dialect.match(/^postgres/)) { ]); }); - it('overrides a function', function() { + it('overrides a function', async function() { const first_body = 'return \'first\';'; const second_body = 'return \'second\';'; // create function - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', first_body, null) - // override - .then(() => this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', second_body, null, { force: true })) - // validate - .then(() => this.sequelize.query("select create_job('abc');", { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].create_job).to.be.eql('second'); - }); + await this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', first_body, null); + // override + await this.queryInterface.createFunction( + 'create_job', + [{ type: 'varchar', name: 'test' }], + 'varchar', + 'plpgsql', + second_body, + null, + { force: true } + ); + // validate + const res = await this.sequelize.query("select create_job('abc');", { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].create_job).to.be.eql('second'); }); it('produces an error when options.variables is missing expected parameters', function() { @@ -180,43 +180,45 @@ if (dialect.match(/^postgres/)) { .to.be.rejectedWith(/function variable must have a name and type/); }); - it('uses declared variables', function() { + it('uses declared variables', async function() { const body = 'RETURN myVar + 1;'; const options = { variables: [{ type: 'integer', name: 'myVar', default: 100 }] }; - return this.queryInterface.createFunction('add_one', [], 'integer', 'plpgsql', body, [], options) - .then(() => this.sequelize.query('select add_one();', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].add_one).to.be.eql(101); - }); + await this.queryInterface.createFunction('add_one', [], 'integer', 'plpgsql', body, [], options); + const res = await this.sequelize.query('select add_one();', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].add_one).to.be.eql(101); }); }); describe('dropFunction', () => { - beforeEach(function() { + beforeEach(async function() { const body = 'return test;'; const options = {}; // make sure we have a droptest function in place. - return this.queryInterface.createFunction('droptest', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options) + await this.queryInterface.createFunction( + 'droptest', + [{ type: 'varchar', name: 'test' }], + 'varchar', + 'plpgsql', + body, + options + ) // suppress errors.. this could fail if the function is already there.. thats ok. .catch(() => {}); }); - it('can drop a function', function() { - return expect( - // call drop function - this.queryInterface.dropFunction('droptest', [{ type: 'varchar', name: 'test' }]) - // now call the function we attempted to drop.. if dropFunction worked as expect it should produce an error. - .then(() => { - // call the function we attempted to drop.. if it is still there then throw an error informing that the expected behavior is not met. - return this.sequelize.query('select droptest(\'test\');', { type: this.sequelize.QueryTypes.SELECT }); - }) + it('can drop a function', async function() { + // call drop function + await this.queryInterface.dropFunction('droptest', [{ type: 'varchar', name: 'test' }]); + await expect( + // now call the function we attempted to drop.. if dropFunction worked as expect it should produce an error. + this.sequelize.query('select droptest(\'test\');', { type: this.sequelize.QueryTypes.SELECT }) // test that we did get the expected error indicating that droptest was properly removed. ).to.be.rejectedWith(/.*function droptest.* does not exist/); }); - it('produces an error when missing expected parameters', function() { - return Promise.all([ + it('produces an error when missing expected parameters', async function() { + await Promise.all([ expect(this.queryInterface.dropFunction()) .to.be.rejectedWith(/.*requires functionName/), @@ -230,46 +232,43 @@ if (dialect.match(/^postgres/)) { }); describe('indexes', () => { - beforeEach(function() { - return this.queryInterface.dropTable('Group') - .then(() => this.queryInterface.createTable('Group', { - username: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN, - from: DataTypes.STRING - })); + beforeEach(async function() { + await this.queryInterface.dropTable('Group'); + + await this.queryInterface.createTable('Group', { + username: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN, + from: DataTypes.STRING + }); }); - it('supports newlines', function() { - return this.queryInterface.addIndex('Group', [this.sequelize.literal(`( + it('supports newlines', async function() { + await this.queryInterface.addIndex('Group', [this.sequelize.literal(`( CASE "username" WHEN 'foo' THEN 'bar' ELSE 'baz' END - )`)], { name: 'group_username_case' }) - .then(() => this.queryInterface.showIndex('Group')) - .then(indexes => { - const indexColumns = _.uniq(indexes.map(index => index.name)); + )`)], { name: 'group_username_case' }); + + const indexes = await this.queryInterface.showIndex('Group'); + const indexColumns = _.uniq(indexes.map(index => index.name)); - expect(indexColumns).to.include('group_username_case'); - }); + expect(indexColumns).to.include('group_username_case'); }); - it('adds, reads and removes a named functional index to the table', function() { - return this.queryInterface.addIndex('Group', [this.sequelize.fn('lower', this.sequelize.col('username'))], { + it('adds, reads and removes a named functional index to the table', async function() { + await this.queryInterface.addIndex('Group', [this.sequelize.fn('lower', this.sequelize.col('username'))], { name: 'group_username_lower' - }) - .then(() => this.queryInterface.showIndex('Group')) - .then(indexes => { - const indexColumns = _.uniq(indexes.map(index => index.name)); - - expect(indexColumns).to.include('group_username_lower'); - }) - .then(() => this.queryInterface.removeIndex('Group', 'group_username_lower')) - .then(() => this.queryInterface.showIndex('Group')) - .then(indexes => { - const indexColumns = _.uniq(indexes.map(index => index.name)); - expect(indexColumns).to.be.empty; - }); + }); + + const indexes0 = await this.queryInterface.showIndex('Group'); + const indexColumns0 = _.uniq(indexes0.map(index => index.name)); + + expect(indexColumns0).to.include('group_username_lower'); + await this.queryInterface.removeIndex('Group', 'group_username_lower'); + const indexes = await this.queryInterface.showIndex('Group'); + const indexColumns = _.uniq(indexes.map(index => index.name)); + expect(indexColumns).to.be.empty; }); }); }); diff --git a/test/integration/dialects/postgres/query.test.js b/test/integration/dialects/postgres/query.test.js index d54e598561a6..5f4265653a6e 100644 --- a/test/integration/dialects/postgres/query.test.js +++ b/test/integration/dialects/postgres/query.test.js @@ -12,7 +12,7 @@ if (dialect.match(/^postgres/)) { const taskAlias = 'AnActualVeryLongAliasThatShouldBreakthePostgresLimitOfSixtyFourCharacters'; const teamAlias = 'Toto'; - const executeTest = (options, test) => { + const executeTest = async (options, test) => { const sequelize = Support.createSequelizeInstance(options); const User = sequelize.define('User', { name: DataTypes.STRING, updatedAt: DataTypes.DATE }, { underscored: true }); @@ -23,48 +23,43 @@ if (dialect.match(/^postgres/)) { User.belongsToMany(Team, { as: teamAlias, foreignKey: 'teamId', through: 'UserTeam' }); Team.belongsToMany(User, { foreignKey: 'userId', through: 'UserTeam' }); - return sequelize.sync({ force: true }).then(() => { - return Team.create({ name: 'rocket' }).then(team => { - return Task.create({ title: 'SuperTask' }).then(task => { - return User.create({ name: 'test', task_id: task.id, updatedAt: new Date() }).then(user => { - return user[`add${teamAlias}`](team).then(() => { - return User.findOne({ - include: [ - { - model: Task, - as: taskAlias - }, - { - model: Team, - as: teamAlias - } - ] - }).then(test); - }); - }); - }); - }); - }); + await sequelize.sync({ force: true }); + const team = await Team.create({ name: 'rocket' }); + const task = await Task.create({ title: 'SuperTask' }); + const user = await User.create({ name: 'test', task_id: task.id, updatedAt: new Date() }); + await user[`add${teamAlias}`](team); + return test(await User.findOne({ + include: [ + { + model: Task, + as: taskAlias + }, + { + model: Team, + as: teamAlias + } + ] + })); }; - it('should throw due to alias being truncated', function() { + it('should throw due to alias being truncated', async function() { const options = { ...this.sequelize.options, minifyAliases: false }; - return executeTest(options, res => { + await executeTest(options, res => { expect(res[taskAlias]).to.not.exist; }); }); - it('should be able to retrieve include due to alias minifying', function() { + it('should be able to retrieve include due to alias minifying', async function() { const options = { ...this.sequelize.options, minifyAliases: true }; - return executeTest(options, res => { + await executeTest(options, res => { expect(res[taskAlias].title).to.be.equal('SuperTask'); }); }); - it('should throw due to table name being truncated', () => { + it('should throw due to table name being truncated', async () => { const sequelize = Support.createSequelizeInstance({ minifyAliases: true }); const User = sequelize.define('user_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', @@ -95,19 +90,16 @@ if (dialect.match(/^postgres/)) { User.hasMany(Project, { foreignKey: 'userId' }); Project.belongsTo(Company, { foreignKey: 'companyId' }); - return sequelize.sync({ force: true }).then(() => { - return Company.create({ name: 'Sequelize' }).then(comp => { - return User.create({ name: 'standard user' }).then(user => { - return Project.create({ name: 'Manhattan', companyId: comp.id, userId: user.id }).then(() => { - return User.findAll({ - include: { - model: Project, - include: Company - } - }); - }); - }); - }); + await sequelize.sync({ force: true }); + const comp = await Company.create({ name: 'Sequelize' }); + const user = await User.create({ name: 'standard user' }); + await Project.create({ name: 'Manhattan', companyId: comp.id, userId: user.id }); + + await User.findAll({ + include: { + model: Project, + include: Company + } }); }); }); diff --git a/test/integration/dialects/postgres/range.test.js b/test/integration/dialects/postgres/range.test.js index 9e33dca9373f..c7e7268f1ec2 100644 --- a/test/integration/dialects/postgres/range.test.js +++ b/test/integration/dialects/postgres/range.test.js @@ -184,16 +184,16 @@ if (dialect.match(/^postgres/)) { expect(range.parse('some_non_array')).to.deep.equal('some_non_array'); }); - it('should handle native postgres timestamp format', () => { + it('should handle native postgres timestamp format', async () => { // Make sure nameOidMap is loaded - return Support.sequelize.connectionManager.getConnection().then(connection => { - Support.sequelize.connectionManager.releaseConnection(connection); + const connection = await Support.sequelize.connectionManager.getConnection(); - const tsName = DataTypes.postgres.DATE.types.postgres[0], - tsOid = Support.sequelize.connectionManager.nameOidMap[tsName].oid, - parser = pg.types.getTypeParser(tsOid); - expect(range.parse('(2016-01-01 08:00:00-04,)', parser)[0].value.toISOString()).to.equal('2016-01-01T12:00:00.000Z'); - }); + Support.sequelize.connectionManager.releaseConnection(connection); + + const tsName = DataTypes.postgres.DATE.types.postgres[0], + tsOid = Support.sequelize.connectionManager.nameOidMap[tsName].oid, + parser = pg.types.getTypeParser(tsOid); + expect(range.parse('(2016-01-01 08:00:00-04,)', parser)[0].value.toISOString()).to.equal('2016-01-01T12:00:00.000Z'); }); }); diff --git a/test/integration/dialects/postgres/regressions.test.js b/test/integration/dialects/postgres/regressions.test.js index b3ad1b0961a3..963e9110fe75 100644 --- a/test/integration/dialects/postgres/regressions.test.js +++ b/test/integration/dialects/postgres/regressions.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] Regressions', () => { - it('properly fetch OIDs after sync, #8749', function() { + it('properly fetch OIDs after sync, #8749', async function() { const User = this.sequelize.define('User', { active: Sequelize.BOOLEAN }); @@ -27,24 +27,18 @@ if (dialect.match(/^postgres/)) { User.hasMany(Media); Media.belongsTo(User); - return this.sequelize - .sync({ force: true }) - .then(() => User.create({ active: true })) - .then(user => { - expect(user.active).to.be.true; - expect(user.get('active')).to.be.true; - - return User.findOne(); - }) - .then(user => { - expect(user.active).to.be.true; - expect(user.get('active')).to.be.true; - - return User.findOne({ raw: true }); - }) - .then(user => { - expect(user.active).to.be.true; - }); + await this.sequelize.sync({ force: true }); + + const user1 = await User.create({ active: true }); + expect(user1.active).to.be.true; + expect(user1.get('active')).to.be.true; + + const user0 = await User.findOne(); + expect(user0.active).to.be.true; + expect(user0.get('active')).to.be.true; + + const user = await User.findOne({ raw: true }); + expect(user.active).to.be.true; }); }); } diff --git a/test/integration/dialects/sqlite/connection-manager.test.js b/test/integration/dialects/sqlite/connection-manager.test.js index 60f132ed3e42..064fa12a3921 100644 --- a/test/integration/dialects/sqlite/connection-manager.test.js +++ b/test/integration/dialects/sqlite/connection-manager.test.js @@ -18,52 +18,48 @@ if (dialect === 'sqlite') { jetpack.remove(directoryName); }); - it('close connection and remove journal and wal files', function() { + it('close connection and remove journal and wal files', async function() { const sequelize = Support.createSequelizeInstance({ storage: jetpack.path(fileName) }); const User = sequelize.define('User', { username: DataTypes.STRING }); - return User - .sync({ force: true }) - .then(() => sequelize.query('PRAGMA journal_mode = WAL')) - .then(() => User.create({ username: 'user1' })) - .then(() => { - return sequelize.transaction(transaction => { - return User.create({ username: 'user2' }, { transaction }); - }); - }) - .then(async () => { - expect(jetpack.exists(fileName)).to.be.equal('file'); - expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); - expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + await User.sync({ force: true }); - // move wal file content to main database - // so those files can be removed on connection close - // https://www.sqlite.org/wal.html#ckpt - await sequelize.query('PRAGMA wal_checkpoint'); + await sequelize.query('PRAGMA journal_mode = WAL'); + await User.create({ username: 'user1' }); - // wal, shm files exist after checkpoint - expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); - expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + await sequelize.transaction(transaction => { + return User.create({ username: 'user2' }, { transaction }); + }); + + expect(jetpack.exists(fileName)).to.be.equal('file'); + expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); + expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + + // move wal file content to main database + // so those files can be removed on connection close + // https://www.sqlite.org/wal.html#ckpt + await sequelize.query('PRAGMA wal_checkpoint'); - return sequelize.close(); - }) - .then(() => { - expect(jetpack.exists(fileName)).to.be.equal('file'); - expect(jetpack.exists(`${fileName}-shm`), 'shm file exists').to.be.false; - expect(jetpack.exists(`${fileName}-wal`), 'wal file exists').to.be.false; + // wal, shm files exist after checkpoint + expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); + expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); - return this.sequelize.query('PRAGMA journal_mode = DELETE'); - }); + await sequelize.close(); + expect(jetpack.exists(fileName)).to.be.equal('file'); + expect(jetpack.exists(`${fileName}-shm`), 'shm file exists').to.be.false; + expect(jetpack.exists(`${fileName}-wal`), 'wal file exists').to.be.false; + + await this.sequelize.query('PRAGMA journal_mode = DELETE'); }); - it('automatic path provision for `options.storage`', () => { - return Support.createSequelizeInstance({ storage: nestedFileName }) + it('automatic path provision for `options.storage`', async () => { + await Support.createSequelizeInstance({ storage: nestedFileName }) .define('User', { username: DataTypes.STRING }) - .sync({ force: true }).then(() => { - expect(jetpack.exists(nestedFileName)).to.be.equal('file'); - }); + .sync({ force: true }); + + expect(jetpack.exists(nestedFileName)).to.be.equal('file'); }); }); } diff --git a/test/integration/dialects/sqlite/dao-factory.test.js b/test/integration/dialects/sqlite/dao-factory.test.js index c55e1c995217..c73030137f0d 100644 --- a/test/integration/dialects/sqlite/dao-factory.test.js +++ b/test/integration/dialects/sqlite/dao-factory.test.js @@ -14,14 +14,14 @@ if (dialect === 'sqlite') { this.sequelize.options.storage = ':memory:'; }); - beforeEach(function() { + beforeEach(async function() { this.sequelize.options.storage = dbFile; this.User = this.sequelize.define('User', { age: DataTypes.INTEGER, name: DataTypes.STRING, bio: DataTypes.TEXT }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); storages.forEach(storage => { @@ -33,137 +33,128 @@ if (dialect === 'sqlite') { }); describe('create', () => { - it('creates a table entry', function() { - return this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }).then(user => { - expect(user.age).to.equal(21); - expect(user.name).to.equal('John Wayne'); - expect(user.bio).to.equal('noot noot'); - - return this.User.findAll().then(users => { - const usernames = users.map(user => { - return user.name; - }); - expect(usernames).to.contain('John Wayne'); - }); + it('creates a table entry', async function() { + const user = await this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }); + expect(user.age).to.equal(21); + expect(user.name).to.equal('John Wayne'); + expect(user.bio).to.equal('noot noot'); + + const users = await this.User.findAll(); + const usernames = users.map(user => { + return user.name; }); + expect(usernames).to.contain('John Wayne'); }); - it('should allow the creation of an object with options as attribute', function() { + it('should allow the creation of an object with options as attribute', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING, options: DataTypes.TEXT }); - return Person.sync({ force: true }).then(() => { - const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); + await Person.sync({ force: true }); + const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); - return Person.create({ - name: 'John Doe', - options - }).then(people => { - expect(people.options).to.deep.equal(options); - }); + const people = await Person.create({ + name: 'John Doe', + options }); + + expect(people.options).to.deep.equal(options); }); - it('should allow the creation of an object with a boolean (true) as attribute', function() { + it('should allow the creation of an object with a boolean (true) as attribute', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING, has_swag: DataTypes.BOOLEAN }); - return Person.sync({ force: true }).then(() => { - return Person.create({ - name: 'John Doe', - has_swag: true - }).then(people => { - expect(people.has_swag).to.be.ok; - }); + await Person.sync({ force: true }); + + const people = await Person.create({ + name: 'John Doe', + has_swag: true }); + + expect(people.has_swag).to.be.ok; }); - it('should allow the creation of an object with a boolean (false) as attribute', function() { + it('should allow the creation of an object with a boolean (false) as attribute', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING, has_swag: DataTypes.BOOLEAN }); - return Person.sync({ force: true }).then(() => { - return Person.create({ - name: 'John Doe', - has_swag: false - }).then(people => { - expect(people.has_swag).to.not.be.ok; - }); + await Person.sync({ force: true }); + + const people = await Person.create({ + name: 'John Doe', + has_swag: false }); + + expect(people.has_swag).to.not.be.ok; }); }); describe('.findOne', () => { - beforeEach(function() { - return this.User.create({ name: 'user', bio: 'footbar' }); + beforeEach(async function() { + await this.User.create({ name: 'user', bio: 'footbar' }); }); - it('finds normal lookups', function() { - return this.User.findOne({ where: { name: 'user' } }).then(user => { - expect(user.name).to.equal('user'); - }); + it('finds normal lookups', async function() { + const user = await this.User.findOne({ where: { name: 'user' } }); + expect(user.name).to.equal('user'); }); - it.skip('should make aliased attributes available', function() { - return this.User.findOne({ + it.skip('should make aliased attributes available', async function() { // eslint-disable-line mocha/no-skipped-tests + const user = await this.User.findOne({ where: { name: 'user' }, attributes: ['id', ['name', 'username']] - }).then(user => { - expect(user.username).to.equal('user'); }); + + expect(user.username).to.equal('user'); }); }); describe('.all', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { name: 'user', bio: 'foobar' }, { name: 'user', bio: 'foobar' } ]); }); - it('should return all users', function() { - return this.User.findAll().then(users => { - expect(users).to.have.length(2); - }); + it('should return all users', async function() { + const users = await this.User.findAll(); + expect(users).to.have.length(2); }); }); describe('.min', () => { - it('should return the min value', function() { + it('should return the min value', async function() { const users = []; for (let i = 2; i < 5; i++) { users[users.length] = { age: i }; } - return this.User.bulkCreate(users).then(() => { - return this.User.min('age').then(min => { - expect(min).to.equal(2); - }); - }); + await this.User.bulkCreate(users); + const min = await this.User.min('age'); + expect(min).to.equal(2); }); }); describe('.max', () => { - it('should return the max value', function() { + it('should return the max value', async function() { const users = []; for (let i = 2; i <= 5; i++) { users[users.length] = { age: i }; } - return this.User.bulkCreate(users).then(() => { - return this.User.max('age').then(min => { - expect(min).to.equal(5); - }); - }); + await this.User.bulkCreate(users); + const min = await this.User.max('age'); + expect(min).to.equal(5); }); }); }); diff --git a/test/integration/dialects/sqlite/dao.test.js b/test/integration/dialects/sqlite/dao.test.js index 354a343aff87..47eb3d286ead 100644 --- a/test/integration/dialects/sqlite/dao.test.js +++ b/test/integration/dialects/sqlite/dao.test.js @@ -10,7 +10,7 @@ const chai = require('chai'), if (dialect === 'sqlite') { describe('[SQLITE Specific] DAO', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, emergency_contact: DataTypes.JSON, @@ -28,90 +28,90 @@ if (dialect === 'sqlite') { }); this.User.hasMany(this.Project); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('findAll', () => { - it('handles dates correctly', function() { + it('handles dates correctly', async function() { const user = this.User.build({ username: 'user' }); user.dataValues.createdAt = new Date(2011, 4, 4); - return user.save().then(() => { - return this.User.create({ username: 'new user' }).then(() => { - return this.User.findAll({ - where: { createdAt: { [Op.gt]: new Date(2012, 1, 1) } } - }).then(users => { - expect(users).to.have.length(1); - }); - }); + await user.save(); + await this.User.create({ username: 'new user' }); + + const users = await this.User.findAll({ + where: { createdAt: { [Op.gt]: new Date(2012, 1, 1) } } }); + + expect(users).to.have.length(1); }); - it('handles dates with aliasses correctly #3611', function() { - return this.User.create({ + it('handles dates with aliasses correctly #3611', async function() { + await this.User.create({ dateField: new Date(2010, 10, 10) - }).then(() => { - return this.User.findAll().then(obj => obj[0]); - }).then(user => { - expect(user.get('dateField')).to.be.an.instanceof(Date); - expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10)); }); + + const obj = await this.User.findAll(); + const user = await obj[0]; + expect(user.get('dateField')).to.be.an.instanceof(Date); + expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10)); }); - it('handles dates in includes correctly #2644', function() { - return this.User.create({ + it('handles dates in includes correctly #2644', async function() { + await this.User.create({ projects: [ { dateField: new Date(1990, 5, 5) } ] - }, { include: [this.Project] }).then(() => { - return this.User.findAll({ - include: [this.Project] - }).then(obj => obj[0]); - }).then(user => { - expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date); - expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5)); + }, { include: [this.Project] }); + + const obj = await this.User.findAll({ + include: [this.Project] }); + + const user = await obj[0]; + expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date); + expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5)); }); }); describe('json', () => { - it('should be able to retrieve a row with json_extract function', function() { - return Promise.all([ + it('should be able to retrieve a row with json_extract function', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json('json_extract(emergency_contact, \'$.name\')', 'kate'), - attributes: ['username', 'emergency_contact'] - }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('kate'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json('json_extract(emergency_contact, \'$.name\')', 'kate'), + attributes: ['username', 'emergency_contact'] }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to retrieve a row by json_type function', function() { - return Promise.all([ + it('should be able to retrieve a row by json_type function', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: ['kate', 'joe'] }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json('json_type(emergency_contact)', 'array'), - attributes: ['username', 'emergency_contact'] - }); - }).then(user => { - expect(user.username).to.equal('anna'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json('json_type(emergency_contact)', 'array'), + attributes: ['username', 'emergency_contact'] }); + + expect(user.username).to.equal('anna'); }); }); describe('regression tests', () => { - it('do not crash while parsing unique constraint errors', function() { + it('do not crash while parsing unique constraint errors', async function() { const Payments = this.sequelize.define('payments', {}); - return Payments.sync({ force: true }).then(() => { - return expect(Payments.bulkCreate([{ id: 1 }, { id: 1 }], { ignoreDuplicates: false })).to.eventually.be.rejected; - }); + await Payments.sync({ force: true }); + + await expect(Payments.bulkCreate([{ id: 1 }, { id: 1 }], { ignoreDuplicates: false })).to.eventually.be.rejected; }); }); }); diff --git a/test/integration/dialects/sqlite/sqlite-master.test.js b/test/integration/dialects/sqlite/sqlite-master.test.js index 7afa54a8a25c..ced16581138a 100644 --- a/test/integration/dialects/sqlite/sqlite-master.test.js +++ b/test/integration/dialects/sqlite/sqlite-master.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), if (dialect === 'sqlite') { describe('[SQLITE Specific] sqlite_master raw queries', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize.define('SomeTable', { someColumn: DataTypes.INTEGER }, { @@ -16,46 +16,40 @@ if (dialect === 'sqlite') { timestamps: false }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should be able to select with tbl_name filter', function() { - return this.sequelize.query('SELECT * FROM sqlite_master WHERE tbl_name=\'SomeTable\'') - .then(result => { - const rows = result[0]; - expect(rows).to.have.length(1); - const row = rows[0]; - expect(row).to.have.property('type', 'table'); - expect(row).to.have.property('name', 'SomeTable'); - expect(row).to.have.property('tbl_name', 'SomeTable'); - expect(row).to.have.property('sql'); - }); + it('should be able to select with tbl_name filter', async function() { + const result = await this.sequelize.query('SELECT * FROM sqlite_master WHERE tbl_name=\'SomeTable\''); + const rows = result[0]; + expect(rows).to.have.length(1); + const row = rows[0]; + expect(row).to.have.property('type', 'table'); + expect(row).to.have.property('name', 'SomeTable'); + expect(row).to.have.property('tbl_name', 'SomeTable'); + expect(row).to.have.property('sql'); }); - it('should be able to select *', function() { - return this.sequelize.query('SELECT * FROM sqlite_master') - .then(result => { - const rows = result[0]; - expect(rows).to.have.length(2); - rows.forEach(row => { - expect(row).to.have.property('type'); - expect(row).to.have.property('name'); - expect(row).to.have.property('tbl_name'); - expect(row).to.have.property('rootpage'); - expect(row).to.have.property('sql'); - }); - }); + it('should be able to select *', async function() { + const result = await this.sequelize.query('SELECT * FROM sqlite_master'); + const rows = result[0]; + expect(rows).to.have.length(2); + rows.forEach(row => { + expect(row).to.have.property('type'); + expect(row).to.have.property('name'); + expect(row).to.have.property('tbl_name'); + expect(row).to.have.property('rootpage'); + expect(row).to.have.property('sql'); + }); }); - it('should be able to select just "sql" column and get rows back', function() { - return this.sequelize.query('SELECT sql FROM sqlite_master WHERE tbl_name=\'SomeTable\'') - .then(result => { - const rows = result[0]; - expect(rows).to.have.length(1); - const row = rows[0]; - expect(row).to.have.property('sql', - 'CREATE TABLE `SomeTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `someColumn` INTEGER)'); - }); + it('should be able to select just "sql" column and get rows back', async function() { + const result = await this.sequelize.query('SELECT sql FROM sqlite_master WHERE tbl_name=\'SomeTable\''); + const rows = result[0]; + expect(rows).to.have.length(1); + const row = rows[0]; + expect(row).to.have.property('sql', + 'CREATE TABLE `SomeTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `someColumn` INTEGER)'); }); }); } From 784712e868a7f77fb23d87361b2238922ceec0f5 Mon Sep 17 00:00:00 2001 From: Yufan Lou <2263580+louy2@users.noreply.github.com> Date: Mon, 11 May 2020 23:21:08 -0400 Subject: [PATCH 142/414] docs(manuals): avoid duplicate header ids (#12201) Co-authored-by: papb --- docs/transforms/fix-ids.js | 46 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 47 insertions(+) create mode 100644 docs/transforms/fix-ids.js diff --git a/docs/transforms/fix-ids.js b/docs/transforms/fix-ids.js new file mode 100644 index 000000000000..127b4cc71ee9 --- /dev/null +++ b/docs/transforms/fix-ids.js @@ -0,0 +1,46 @@ +'use strict'; + +const _ = require('lodash'); +const assert = require('assert'); + +module.exports = function transform($, filePath) { + // The rest of this script assumes forward slashes, so let's ensure this works on windows + filePath = filePath.replace(/\\/g, '/'); + + // Detect every heading with an ID + const headingsWithId = $('h1,h2,h3,h4,h5').filter('[id]'); + + // Find duplicate IDs among them + const headingsWithDuplicateId = _.chain(headingsWithId) + .groupBy(h => $(h).attr('id')) + .filter(g => g.length > 1) + .value(); + + // Replace their IDs according to the following rule + // #original-header --> #original-header + // #original-header --> #original-header-2 + // #original-header --> #original-header-3 + for (const headingGroup of headingsWithDuplicateId) { + const id = $(headingGroup[0]).attr('id'); + + // Find the corresponding nav links + const urlPath = filePath.replace('esdoc/', ''); + const navLinks = $(`li[data-ice="manualNav"] > a[href="${urlPath}#${id}"]`); + + // make sure there are same number of headings and links + assert(headingGroup.length === navLinks.length, + `not every heading is linked to in nav: + ${headingGroup.length} headings but ${navLinks.length} links + heading id is ${id} in file ${filePath}. NavLinks is ${require('util').inspect(navLinks, { compact: false, depth: 5 })}`); + + // Fix the headings and nav links beyond the first + for (let i = 1; i < headingGroup.length; i++) { + const heading = headingGroup[i]; + const navLink = navLinks[i]; + const newId = `${id}-${i + 1}`; + $(heading).attr('id', newId); + $(navLink).attr('href', `${urlPath}#${newId}`); + } + } + +}; diff --git a/package.json b/package.json index 4fc8c3abaa2d..d2d30cf851ea 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "chai": "^4.x", "chai-as-promised": "^7.x", "chai-datetime": "^1.x", + "cheerio": "^1.0.0-rc.2", "cls-hooked": "^4.2.2", "cross-env": "^7.0.2", "delay": "^4.3.0", From 4dbfb5d100633948d12c25e39049bf38547aa193 Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Thu, 14 May 2020 02:54:00 -0300 Subject: [PATCH 143/414] fix(query): do not bind $ used within a whole-word (#12250) --- lib/dialects/abstract/query.js | 3 +-- test/integration/sequelize.test.js | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 221266911ef2..f5a755ce0fd9 100644 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -84,8 +84,7 @@ class AbstractQuery { const timeZone = null; const list = Array.isArray(values); - - sql = sql.replace(/\$(\$|\w+)/g, (match, key) => { + sql = sql.replace(/\B\$(\$|\w+)/g, (match, key) => { if ('$' === key) { return options.skipUnescape ? match : key; } diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index dcf9d000349d..c66f56d6113b 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -716,6 +716,13 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); }); + it('escape where has $ on the middle of characters', function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + return this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }).then(result => { + expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); + }); + }); + if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { it('does not improperly escape arrays of strings bound to named parameters', function() { return this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }).then(result => { From b49a24d486407749229599b57932041db43ac481 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Thu, 14 May 2020 00:57:17 -0500 Subject: [PATCH 144/414] fix(test/integration/hooks): asyncify (#12251) --- test/integration/hooks/associations.test.js | 643 ++++++++---------- test/integration/hooks/bulkOperation.test.js | 329 +++++---- test/integration/hooks/count.test.js | 25 +- test/integration/hooks/create.test.js | 122 ++-- test/integration/hooks/destroy.test.js | 68 +- test/integration/hooks/find.test.js | 87 +-- test/integration/hooks/hooks.test.js | 218 +++--- test/integration/hooks/restore.test.js | 49 +- .../hooks/updateAttributes.test.js | 108 ++- test/integration/hooks/upsert.test.js | 40 +- test/integration/hooks/validate.test.js | 127 ++-- 11 files changed, 816 insertions(+), 1000 deletions(-) diff --git a/test/integration/hooks/associations.test.js b/test/integration/hooks/associations.test.js index f175eefde0a9..9140abb762d4 100644 --- a/test/integration/hooks/associations.test.js +++ b/test/integration/hooks/associations.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), dialect = Support.getTestDialect(); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -30,14 +30,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('associations', () => { describe('1:1', () => { describe('cascade onUpdate', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -49,54 +49,49 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasOne(this.Tasks, { onUpdate: 'cascade', hooks: true }); this.Tasks.belongsTo(this.Projects); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); - it('on success', function() { + it('on success', async function() { let beforeHook = false, afterHook = false; - this.Tasks.beforeUpdate(() => { + this.Tasks.beforeUpdate(async () => { beforeHook = true; - return Promise.resolve(); }); - this.Tasks.afterUpdate(() => { + this.Tasks.afterUpdate(async () => { afterHook = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return project.update({ id: 2 }).then(() => { - expect(beforeHook).to.be.true; - expect(afterHook).to.be.true; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await project.update({ id: 2 }); + expect(beforeHook).to.be.true; + expect(afterHook).to.be.true; }); - it('on error', function() { - this.Tasks.afterUpdate(() => { - return Promise.reject(new Error('Whoops!')); + it('on error', async function() { + this.Tasks.afterUpdate(async () => { + throw new Error('Whoops!'); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).catch(err => { - expect(err).to.be.instanceOf(Error); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + try { + await project.setTask(task); + } catch (err) { + expect(err).to.be.instanceOf(Error); + } }); }); describe('cascade onDelete', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -108,11 +103,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasOne(this.Tasks, { onDelete: 'CASCADE', hooks: true }); this.Tasks.belongsTo(this.Projects); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -123,65 +118,54 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Tasks.beforeDestroy(beforeTask); this.Tasks.afterDestroy(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).to.have.been.calledOnce; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await project.destroy(); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).to.have.been.calledOnce; + expect(afterTask).to.have.been.calledOnce; }); - it('with errors', function() { + it('with errors', async function() { const CustomErrorText = 'Whoops!'; let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.reject(new Error(CustomErrorText)); + throw new Error(CustomErrorText); }); - this.Tasks.afterDestroy(() => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; }); }); }); describe('no cascade update', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -193,45 +177,40 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasOne(this.Tasks); this.Tasks.belongsTo(this.Projects); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); - it('on success', function() { + it('on success', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.Tasks.beforeUpdate(beforeHook); this.Tasks.afterUpdate(afterHook); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return project.update({ id: 2 }).then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await project.update({ id: 2 }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); - it('on error', function() { + it('on error', async function() { this.Tasks.afterUpdate(() => { throw new Error('Whoops!'); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return expect(project.setTask(task)).to.be.rejected; - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + await expect(project.setTask(task)).to.be.rejected; }); }); describe('no cascade delete', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -243,13 +222,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasMany(this.Tasks); this.Tasks.belongsTo(this.Projects); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -260,21 +239,17 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Tasks.beforeUpdate(beforeTask); this.Tasks.afterUpdate(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.removeTask(task).then(() => { - expect(beforeProject).to.have.been.called; - expect(afterProject).to.have.been.called; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.removeTask(task); + expect(beforeProject).to.have.been.called; + expect(afterProject).to.have.been.called; + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -288,17 +263,18 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); this.Tasks.afterUpdate(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).not.to.have.been.called; - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + try { + await project.addTask(task); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).to.have.been.calledOnce; + expect(afterTask).not.to.have.been.called; + } }); }); }); @@ -306,7 +282,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('1:M', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -318,13 +294,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasMany(this.Tasks, { onDelete: 'cascade', hooks: true }); this.Tasks.belongsTo(this.Projects, { hooks: true }); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -335,65 +311,58 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Tasks.beforeDestroy(beforeTask); this.Tasks.afterDestroy(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).to.have.been.calledOnce; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.destroy(); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).to.have.been.calledOnce; + expect(afterTask).to.have.been.calledOnce; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.afterDestroy(() => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + + try { + await project.destroy(); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; + } }); }); }); describe('no cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -405,11 +374,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasMany(this.Tasks); this.Tasks.belongsTo(this.Projects); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -420,57 +389,51 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Tasks.beforeUpdate(beforeTask); this.Tasks.afterUpdate(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.removeTask(task).then(() => { - expect(beforeProject).to.have.been.called; - expect(afterProject).to.have.been.called; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.removeTask(task); + expect(beforeProject).to.have.been.called; + expect(afterProject).to.have.been.called; + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeUpdate(() => { + this.Tasks.beforeUpdate(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.afterUpdate(() => { + this.Tasks.afterUpdate(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + try { + await project.addTask(task); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; + } }); }); }); @@ -478,7 +441,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('M:M', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -490,11 +453,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.belongsToMany(this.Tasks, { cascade: 'onDelete', through: 'projects_and_tasks', hooks: true }); this.Tasks.belongsToMany(this.Projects, { cascade: 'onDelete', through: 'projects_and_tasks', hooks: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -505,65 +468,54 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Tasks.beforeDestroy(beforeTask); this.Tasks.afterDestroy(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - // Since Sequelize does not cascade M:M, these should be false - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.destroy(); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + // Since Sequelize does not cascade M:M, these should be false + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.afterDestroy(() => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.false; - expect(afterTask).to.be.false; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.destroy(); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.false; + expect(afterTask).to.be.false; }); }); }); describe('no cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -575,11 +527,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.belongsToMany(this.Tasks, { hooks: true, through: 'project_tasks' }); this.Tasks.belongsToMany(this.Projects, { hooks: true, through: 'project_tasks' }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), @@ -590,56 +542,46 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Tasks.beforeUpdate(beforeTask); this.Tasks.afterUpdate(afterTask); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.removeTask(task).then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.removeTask(task); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeUpdate(() => { + this.Tasks.beforeUpdate(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.afterUpdate(() => { + this.Tasks.afterUpdate(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.false; - expect(afterTask).to.be.false; - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.false; + expect(afterTask).to.be.false; }); }); }); @@ -650,7 +592,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('multiple 1:M', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -672,11 +614,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.MiniTasks.belongsTo(this.Projects, { hooks: true }); this.MiniTasks.belongsTo(this.Tasks, { hooks: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -684,55 +626,46 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.resolve(); }); - this.Tasks.afterDestroy(() => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.beforeDestroy(async () => { beforeMiniTask = true; - return Promise.resolve(); }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.afterDestroy(async () => { afterMiniTask = true; - return Promise.resolve(); }); - return Promise.all([ + const [project0, minitask] = await Promise.all([ this.Projects.create({ title: 'New Project' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, minitask]) => { - return project.addMiniTask(minitask); - }).then(project => { - return project.destroy(); - }).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.false; - expect(afterTask).to.be.false; - expect(beforeMiniTask).to.be.true; - expect(afterMiniTask).to.be.true; - }); + ]); + const project = await project0.addMiniTask(minitask); + await project.destroy(); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.false; + expect(afterTask).to.be.false; + expect(beforeMiniTask).to.be.true; + expect(afterMiniTask).to.be.true; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -740,51 +673,47 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.resolve(); }); - this.Tasks.afterDestroy(() => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.beforeDestroy(async () => { beforeMiniTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.afterDestroy(async () => { afterMiniTask = true; - return Promise.resolve(); }); - return Promise.all([ - this.Projects.create({ title: 'New Project' }), - this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, minitask]) => { - return project.addMiniTask(minitask); - }).then(project => { - return project.destroy(); - }).catch(() => { + try { + const [project0, minitask] = await Promise.all([ + this.Projects.create({ title: 'New Project' }), + this.MiniTasks.create({ mini_title: 'New MiniTask' }) + ]); + + const project = await project0.addMiniTask(minitask); + await project.destroy(); + } catch (err) { expect(beforeProject).to.be.true; expect(afterProject).to.be.true; expect(beforeTask).to.be.false; expect(afterTask).to.be.false; expect(beforeMiniTask).to.be.true; expect(afterMiniTask).to.be.false; - }); + } }); }); }); @@ -792,7 +721,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('multiple 1:M sequential hooks', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -814,11 +743,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.MiniTasks.belongsTo(this.Projects, { hooks: true }); this.MiniTasks.belongsTo(this.Tasks, { hooks: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -826,58 +755,52 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.beforeCreate(() => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.afterCreate(() => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.beforeDestroy(() => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.resolve(); }); - this.Tasks.afterDestroy(() => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - this.MiniTasks.beforeDestroy(() => { + this.MiniTasks.beforeDestroy(async () => { beforeMiniTask = true; - return Promise.resolve(); }); - this.MiniTasks.afterDestroy(() => { + this.MiniTasks.afterDestroy(async () => { afterMiniTask = true; - return Promise.resolve(); }); - return Promise.all([ + const [project0, task, minitask] = await Promise.all([ this.Projects.create({ title: 'New Project' }), this.Tasks.create({ title: 'New Task' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, task, minitask]) => { - return Promise.all([ - task.addMiniTask(minitask), - project.addTask(task) - ]).then(() => project); - }).then(project => { - return project.destroy(); - }).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.true; - expect(beforeMiniTask).to.be.true; - expect(afterMiniTask).to.be.true; - }); + ]); + + await Promise.all([ + task.addMiniTask(minitask), + project0.addTask(task) + ]); + + const project = project0; + await project.destroy(); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.true; + expect(beforeMiniTask).to.be.true; + expect(afterMiniTask).to.be.true; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -911,25 +834,25 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterMiniTask = true; }); - return Promise.all([ + const [project0, task, minitask] = await Promise.all([ this.Projects.create({ title: 'New Project' }), this.Tasks.create({ title: 'New Task' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, task, minitask]) => { - return Promise.all([ - task.addMiniTask(minitask), - project.addTask(task) - ]).then(() => project); - }).then(project => { - return expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - expect(beforeMiniTask).to.be.false; - expect(afterMiniTask).to.be.false; - }); - }); + ]); + + await Promise.all([ + task.addMiniTask(minitask), + project0.addTask(task) + ]); + + const project = project0; + await expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; + expect(beforeMiniTask).to.be.false; + expect(afterMiniTask).to.be.false; }); }); }); diff --git a/test/integration/hooks/bulkOperation.test.js b/test/integration/hooks/bulkOperation.test.js index 4a34d6078802..040955024d80 100644 --- a/test/integration/hooks/bulkOperation.test.js +++ b/test/integration/hooks/bulkOperation.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -29,12 +29,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#bulkCreate', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); @@ -42,34 +42,34 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.afterBulkCreate(afterBulk); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + ]); + + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { this.User.beforeBulkCreate(() => { throw new Error('Whoops!'); }); - return expect(this.User.bulkCreate([ + await expect(this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } ])).to.be.rejected; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { this.User.afterBulkCreate(() => { throw new Error('Whoops!'); }); - return expect(this.User.bulkCreate([ + await expect(this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } ])).to.be.rejected; @@ -77,7 +77,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -93,126 +93,119 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should run the afterCreate/beforeCreate functions for each item created successfully', function() { + it('should run the afterCreate/beforeCreate functions for each item created successfully', async function() { let beforeBulkCreate = false, afterBulkCreate = false; - this.User.beforeBulkCreate(() => { + this.User.beforeBulkCreate(async () => { beforeBulkCreate = true; - return Promise.resolve(); }); - this.User.afterBulkCreate(() => { + this.User.afterBulkCreate(async () => { afterBulkCreate = true; - return Promise.resolve(); }); - this.User.beforeCreate(user => { + this.User.beforeCreate(async user => { user.beforeHookTest = true; - return Promise.resolve(); }); - this.User.afterCreate(user => { + this.User.afterCreate(async user => { user.username = `User${user.id}`; - return Promise.resolve(); }); - return this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }).then(records => { - records.forEach(record => { - expect(record.username).to.equal(`User${record.id}`); - expect(record.beforeHookTest).to.be.true; - }); - expect(beforeBulkCreate).to.be.true; - expect(afterBulkCreate).to.be.true; + const records = await this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }); + records.forEach(record => { + expect(record.username).to.equal(`User${record.id}`); + expect(record.beforeHookTest).to.be.true; }); + expect(beforeBulkCreate).to.be.true; + expect(afterBulkCreate).to.be.true; }); - it('should run the afterCreate/beforeCreate functions for each item created with an error', function() { + it('should run the afterCreate/beforeCreate functions for each item created with an error', async function() { let beforeBulkCreate = false, afterBulkCreate = false; - this.User.beforeBulkCreate(() => { + this.User.beforeBulkCreate(async () => { beforeBulkCreate = true; - return Promise.resolve(); }); - this.User.afterBulkCreate(() => { + this.User.afterBulkCreate(async () => { afterBulkCreate = true; - return Promise.resolve(); }); - this.User.beforeCreate(() => { - return Promise.reject(new Error('You shall not pass!')); + this.User.beforeCreate(async () => { + throw new Error('You shall not pass!'); }); - this.User.afterCreate(user => { + this.User.afterCreate(async user => { user.username = `User${user.id}`; - return Promise.resolve(); }); - return this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }).catch(err => { + try { + await this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }); + } catch (err) { expect(err).to.be.instanceOf(Error); expect(beforeBulkCreate).to.be.true; expect(afterBulkCreate).to.be.false; - }); + } }); }); }); describe('#bulkUpdate', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); this.User.beforeBulkUpdate(beforeBulk); this.User.afterBulkUpdate(afterBulk); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - return this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); - }); + ]); + + await this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { this.User.beforeBulkUpdate(() => { throw new Error('Whoops!'); }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - return expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; - }); + ]); + + await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { this.User.afterBulkUpdate(() => { throw new Error('Whoops!'); }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - return expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; - }); + ]); + + await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -228,10 +221,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should run the after/before functions for each item created successfully', function() { + it('should run the after/before functions for each item created successfully', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); @@ -248,21 +241,20 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { user.username = `User${user.id}`; }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }).then(([, records]) => { - records.forEach(record => { - expect(record.username).to.equal(`User${record.id}`); - expect(record.beforeHookTest).to.be.true; - }); - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + ]); + + const [, records] = await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); + records.forEach(record => { + expect(record.username).to.equal(`User${record.id}`); + expect(record.beforeHookTest).to.be.true; }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); - it('should run the after/before functions for each item created successfully changing some data before updating', function() { + it('should run the after/before functions for each item created successfully changing some data before updating', async function() { this.User.beforeUpdate(user => { expect(user.changed()).to.not.be.empty; if (user.get('id') === 1) { @@ -270,18 +262,17 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }).then(([, records]) => { - records.forEach(record => { - expect(record.aNumber).to.equal(10 + (record.id === 1 ? 3 : 0)); - }); - }); + ]); + + const [, records] = await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); + records.forEach(record => { + expect(record.aNumber).to.equal(10 + (record.id === 1 ? 3 : 0)); }); }); - it('should run the after/before functions for each item created with an error', function() { + it('should run the after/before functions for each item created with an error', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); @@ -297,54 +288,55 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { user.username = `User${user.id}`; }); - return this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { - return this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal('You shall not pass!'); - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).not.to.have.been.called; - }); - }); + await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }); + + try { + await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal('You shall not pass!'); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).not.to.have.been.called; + } }); }); }); describe('#bulkDestroy', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); this.User.beforeBulkDestroy(beforeBulk); this.User.afterBulkDestroy(afterBulk); - return this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + await this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { this.User.beforeBulkDestroy(() => { throw new Error('Whoops!'); }); - return expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; + await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { this.User.afterBulkDestroy(() => { throw new Error('Whoops!'); }); - return expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; + await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -360,131 +352,124 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should run the after/before functions for each item created successfully', function() { + it('should run the after/before functions for each item created successfully', async function() { let beforeBulk = false, afterBulk = false, beforeHook = false, afterHook = false; - this.User.beforeBulkDestroy(() => { + this.User.beforeBulkDestroy(async () => { beforeBulk = true; - return Promise.resolve(); }); - this.User.afterBulkDestroy(() => { + this.User.afterBulkDestroy(async () => { afterBulk = true; - return Promise.resolve(); }); - this.User.beforeDestroy(() => { + this.User.beforeDestroy(async () => { beforeHook = true; - return Promise.resolve(); }); - this.User.afterDestroy(() => { + this.User.afterDestroy(async () => { afterHook = true; - return Promise.resolve(); }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }).then(() => { - expect(beforeBulk).to.be.true; - expect(afterBulk).to.be.true; - expect(beforeHook).to.be.true; - expect(afterHook).to.be.true; - }); - }); + ]); + + await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }); + expect(beforeBulk).to.be.true; + expect(afterBulk).to.be.true; + expect(beforeHook).to.be.true; + expect(afterHook).to.be.true; }); - it('should run the after/before functions for each item created with an error', function() { + it('should run the after/before functions for each item created with an error', async function() { let beforeBulk = false, afterBulk = false, beforeHook = false, afterHook = false; - this.User.beforeBulkDestroy(() => { + this.User.beforeBulkDestroy(async () => { beforeBulk = true; - return Promise.resolve(); }); - this.User.afterBulkDestroy(() => { + this.User.afterBulkDestroy(async () => { afterBulk = true; - return Promise.resolve(); }); - this.User.beforeDestroy(() => { + this.User.beforeDestroy(async () => { beforeHook = true; - return Promise.reject(new Error('You shall not pass!')); + throw new Error('You shall not pass!'); }); - this.User.afterDestroy(() => { + this.User.afterDestroy(async () => { afterHook = true; - return Promise.resolve(); }); - return this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { - return this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeBulk).to.be.true; - expect(beforeHook).to.be.true; - expect(afterBulk).to.be.false; - expect(afterHook).to.be.false; - }); - }); + await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }); + + try { + await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeBulk).to.be.true; + expect(beforeHook).to.be.true; + expect(afterBulk).to.be.false; + expect(afterHook).to.be.false; + } }); }); }); describe('#bulkRestore', () => { - beforeEach(function() { - return this.ParanoidUser.bulkCreate([ + beforeEach(async function() { + await this.ParanoidUser.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' } - ]).then(() => { - return this.ParanoidUser.destroy({ truncate: true }); - }); + ]); + + await this.ParanoidUser.destroy({ truncate: true }); }); describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); this.ParanoidUser.beforeBulkRestore(beforeBulk); this.ParanoidUser.afterBulkRestore(afterBulk); - return this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + await this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { this.ParanoidUser.beforeBulkRestore(() => { throw new Error('Whoops!'); }); - return expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; + await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { this.ParanoidUser.afterBulkRestore(() => { throw new Error('Whoops!'); }); - return expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; + await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.ParanoidUser = this.sequelize.define('ParanoidUser', { aNumber: { type: DataTypes.INTEGER, @@ -494,10 +479,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.ParanoidUser.sync({ force: true }); + await this.ParanoidUser.sync({ force: true }); }); - it('should run the after/before functions for each item restored successfully', function() { + it('should run the after/before functions for each item restored successfully', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(), beforeHook = sinon.spy(), @@ -508,21 +493,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.ParanoidUser.beforeRestore(beforeHook); this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.bulkCreate([ + await this.ParanoidUser.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.ParanoidUser.destroy({ where: { aNumber: 1 } }); - }).then(() => { - return this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); - }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - expect(beforeHook).to.have.been.calledThrice; - expect(afterHook).to.have.been.calledThrice; - }); + ]); + + await this.ParanoidUser.destroy({ where: { aNumber: 1 } }); + await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; + expect(beforeHook).to.have.been.calledThrice; + expect(afterHook).to.have.been.calledThrice; }); - it('should run the after/before functions for each item restored with an error', function() { + it('should run the after/before functions for each item restored with an error', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(), beforeHook = sinon.spy(), @@ -530,24 +513,24 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.ParanoidUser.beforeBulkRestore(beforeBulk); this.ParanoidUser.afterBulkRestore(afterBulk); - this.ParanoidUser.beforeRestore(() => { + this.ParanoidUser.beforeRestore(async () => { beforeHook(); - return Promise.reject(new Error('You shall not pass!')); + throw new Error('You shall not pass!'); }); this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { - return this.ParanoidUser.destroy({ where: { aNumber: 1 } }); - }).then(() => { - return this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); - }).catch(err => { + try { + await this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }); + await this.ParanoidUser.destroy({ where: { aNumber: 1 } }); + await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); + } catch (err) { expect(err).to.be.instanceOf(Error); expect(beforeBulk).to.have.been.calledOnce; expect(beforeHook).to.have.been.calledThrice; expect(afterBulk).not.to.have.been.called; expect(afterHook).not.to.have.been.called; - }); + } }); }); }); diff --git a/test/integration/hooks/count.test.js b/test/integration/hooks/count.test.js index ff865a2a52e7..a97a2084c0fc 100644 --- a/test/integration/hooks/count.test.js +++ b/test/integration/hooks/count.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -17,12 +17,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#count', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' }, { username: 'joe', mood: 'happy' } @@ -30,35 +30,34 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('on success', () => { - it('hook runs', function() { + it('hook runs', async function() { let beforeHook = false; this.User.beforeCount(() => { beforeHook = true; }); - return this.User.count().then(count => { - expect(count).to.equal(3); - expect(beforeHook).to.be.true; - }); + const count = await this.User.count(); + expect(count).to.equal(3); + expect(beforeHook).to.be.true; }); - it('beforeCount hook can change options', function() { + it('beforeCount hook can change options', async function() { this.User.beforeCount(options => { options.where.username = 'adam'; }); - return expect(this.User.count({ where: { username: 'joe' } })).to.eventually.equal(1); + await expect(this.User.count({ where: { username: 'joe' } })).to.eventually.equal(1); }); }); describe('on error', () => { - it('in beforeCount hook returns error', function() { + it('in beforeCount hook returns error', async function() { this.User.beforeCount(() => { throw new Error('Oops!'); }); - return expect(this.User.count({ where: { username: 'adam' } })).to.be.rejectedWith('Oops!'); + await expect(this.User.count({ where: { username: 'adam' } })).to.be.rejectedWith('Oops!'); }); }); }); diff --git a/test/integration/hooks/create.test.js b/test/integration/hooks/create.test.js index f6645f3d9437..da684deff9f6 100644 --- a/test/integration/hooks/create.test.js +++ b/test/integration/hooks/create.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -19,12 +19,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#create', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), @@ -35,17 +35,16 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.beforeSave(beforeSave); this.User.afterSave(afterSave); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterSave).to.have.been.calledOnce; - }); + await this.User.create({ username: 'Toni', mood: 'happy' }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledOnce; + expect(afterSave).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), beforeSave = sinon.spy(), afterHook = sinon.spy(), @@ -59,15 +58,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.beforeSave(beforeSave); this.User.afterSave(afterSave); - return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - expect(beforeSave).not.to.have.been.called; - expect(afterSave).not.to.have.been.called; - }); + await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; + expect(beforeSave).not.to.have.been.called; + expect(afterSave).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), beforeSave = sinon.spy(), afterHook = sinon.spy(), @@ -82,16 +80,15 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.beforeSave(beforeSave); this.User.afterSave(afterSave); - return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterSave).not.to.have.been.called; - }); + await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledOnce; + expect(afterSave).not.to.have.been.called; }); }); - it('should not trigger hooks on parent when using N:M association setters', function() { + it('should not trigger hooks on parent when using N:M association setters', async function() { const A = this.sequelize.define('A', { name: Sequelize.STRING }); @@ -101,28 +98,26 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { let hookCalled = 0; - A.addHook('afterCreate', () => { + A.addHook('afterCreate', async () => { hookCalled++; - return Promise.resolve(); }); B.belongsToMany(A, { through: 'a_b' }); A.belongsToMany(B, { through: 'a_b' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - A.create({ name: 'a' }), - B.create({ name: 'b' }) - ]).then(([a, b]) => { - return a.addB(b).then(() => { - expect(hookCalled).to.equal(1); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [a, b] = await Promise.all([ + A.create({ name: 'a' }), + B.create({ name: 'b' }) + ]); + + await a.addB(b); + expect(hookCalled).to.equal(1); }); describe('preserves changes to instance', () => { - it('beforeValidate', function() { + it('beforeValidate', async function() { let hookCalled = 0; this.User.beforeValidate(user => { @@ -130,14 +125,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ mood: 'sad', username: 'leafninja' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('leafninja'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ mood: 'sad', username: 'leafninja' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('leafninja'); + expect(hookCalled).to.equal(1); }); - it('afterValidate', function() { + it('afterValidate', async function() { let hookCalled = 0; this.User.afterValidate(user => { @@ -145,14 +139,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ mood: 'sad', username: 'fireninja' }).then(user => { - expect(user.mood).to.equal('neutral'); - expect(user.username).to.equal('fireninja'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ mood: 'sad', username: 'fireninja' }); + expect(user.mood).to.equal('neutral'); + expect(user.username).to.equal('fireninja'); + expect(hookCalled).to.equal(1); }); - it('beforeCreate', function() { + it('beforeCreate', async function() { let hookCalled = 0; this.User.beforeCreate(user => { @@ -160,14 +153,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('akira'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ username: 'akira' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('akira'); + expect(hookCalled).to.equal(1); }); - it('beforeSave', function() { + it('beforeSave', async function() { let hookCalled = 0; this.User.beforeSave(user => { @@ -175,14 +167,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('akira'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ username: 'akira' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('akira'); + expect(hookCalled).to.equal(1); }); - it('beforeSave with beforeCreate', function() { + it('beforeSave with beforeCreate', async function() { let hookCalled = 0; this.User.beforeCreate(user => { @@ -195,11 +186,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('akira'); - expect(hookCalled).to.equal(2); - }); + const user = await this.User.create({ username: 'akira' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('akira'); + expect(hookCalled).to.equal(2); }); }); }); diff --git a/test/integration/hooks/destroy.test.js b/test/integration/hooks/destroy.test.js index 441c08498a6d..6dfa3e7f61c3 100644 --- a/test/integration/hooks/destroy.test.js +++ b/test/integration/hooks/destroy.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,29 +18,27 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#destroy', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.User.beforeDestroy(beforeHook); this.User.afterDestroy(afterHook); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -50,15 +48,13 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); this.User.afterDestroy(afterHook); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.destroy()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.destroy()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -68,12 +64,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { throw new Error('Whoops!'); }); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.destroy()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.destroy()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); @@ -96,26 +90,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - it('sets other changed values when soft deleting and a beforeDestroy hooks kicks in', function() { - return this.ParanoidUser.sync({ force: true }) - .then(() => this.ParanoidUser.create({ username: 'user1' })) - .then(user => user.destroy()) - .then(() => this.ParanoidUser.findOne({ paranoid: false })) - .then(user => { - expect(user.updatedBy).to.equal(1); - }); + it('sets other changed values when soft deleting and a beforeDestroy hooks kicks in', async function() { + await this.ParanoidUser.sync({ force: true }); + const user0 = await this.ParanoidUser.create({ username: 'user1' }); + await user0.destroy(); + const user = await this.ParanoidUser.findOne({ paranoid: false }); + expect(user.updatedBy).to.equal(1); }); - it('should not throw error when a beforeDestroy hook changes a virtual column', function() { + it('should not throw error when a beforeDestroy hook changes a virtual column', async function() { this.ParanoidUser.beforeDestroy(instance => instance.virtualField = 2); - return this.ParanoidUser.sync({ force: true }) - .then(() => this.ParanoidUser.create({ username: 'user1' })) - .then(user => user.destroy()) - .then(() => this.ParanoidUser.findOne({ paranoid: false })) - .then(user => { - expect(user.virtualField).to.equal(0); - }); + await this.ParanoidUser.sync({ force: true }); + const user0 = await this.ParanoidUser.create({ username: 'user1' }); + await user0.destroy(); + const user = await this.ParanoidUser.findOne({ paranoid: false }); + expect(user.virtualField).to.equal(0); }); }); }); diff --git a/test/integration/hooks/find.test.js b/test/integration/hooks/find.test.js index 8a3f625a8253..e0e87a68dfc3 100644 --- a/test/integration/hooks/find.test.js +++ b/test/integration/hooks/find.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,28 +18,28 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#find', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' } ]); }); - it('allow changing attributes via beforeFind #5675', function() { + it('allow changing attributes via beforeFind #5675', async function() { this.User.beforeFind(options => { options.attributes = { include: [['id', 'my_id']] }; }); - return this.User.findAll({}); + await this.User.findAll({}); }); describe('on success', () => { - it('all hooks run', function() { + it('all hooks run', async function() { let beforeHook = false, beforeHook2 = false, beforeHook3 = false, @@ -61,95 +61,98 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterHook = true; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('happy'); - expect(beforeHook).to.be.true; - expect(beforeHook2).to.be.true; - expect(beforeHook3).to.be.true; - expect(afterHook).to.be.true; - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('happy'); + expect(beforeHook).to.be.true; + expect(beforeHook2).to.be.true; + expect(beforeHook3).to.be.true; + expect(afterHook).to.be.true; }); - it('beforeFind hook can change options', function() { + it('beforeFind hook can change options', async function() { this.User.beforeFind(options => { options.where.username = 'joe'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); - it('beforeFindAfterExpandIncludeAll hook can change options', function() { + it('beforeFindAfterExpandIncludeAll hook can change options', async function() { this.User.beforeFindAfterExpandIncludeAll(options => { options.where.username = 'joe'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); - it('beforeFindAfterOptions hook can change options', function() { + it('beforeFindAfterOptions hook can change options', async function() { this.User.beforeFindAfterOptions(options => { options.where.username = 'joe'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); - it('afterFind hook can change results', function() { + it('afterFind hook can change results', async function() { this.User.afterFind(user => { user.mood = 'sad'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); }); describe('on error', () => { - it('in beforeFind hook returns error', function() { + it('in beforeFind hook returns error', async function() { this.User.beforeFind(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); - it('in beforeFindAfterExpandIncludeAll hook returns error', function() { + it('in beforeFindAfterExpandIncludeAll hook returns error', async function() { this.User.beforeFindAfterExpandIncludeAll(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); - it('in beforeFindAfterOptions hook returns error', function() { + it('in beforeFindAfterOptions hook returns error', async function() { this.User.beforeFindAfterOptions(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); - it('in afterFind hook returns error', function() { + it('in afterFind hook returns error', async function() { this.User.afterFind(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); }); }); diff --git a/test/integration/hooks/hooks.test.js b/test/integration/hooks/hooks.test.js index db456d413d5e..5314bcc5687a 100644 --- a/test/integration/hooks/hooks.test.js +++ b/test/integration/hooks/hooks.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -31,7 +31,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#define', () => { @@ -104,163 +104,143 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('passing DAO instances', () => { describe('beforeValidate / afterValidate', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeValidate(user) { + async beforeValidate(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterValidate(user) { + async afterValidate(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); + await User.sync({ force: true }); + await User.create({ username: 'bob' }); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); describe('beforeCreate / afterCreate', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeCreate(user) { + async beforeCreate(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterCreate(user) { + async afterCreate(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); + await User.sync({ force: true }); + await User.create({ username: 'bob' }); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); describe('beforeDestroy / afterDestroy', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeDestroy(user) { + async beforeDestroy(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterDestroy(user) { + async afterDestroy(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - return user.destroy().then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + await user.destroy(); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); describe('beforeUpdate / afterUpdate', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeUpdate(user) { + async beforeUpdate(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterUpdate(user) { + async afterUpdate(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - user.username = 'bawb'; - return user.save({ fields: ['username'] }).then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + user.username = 'bawb'; + await user.save({ fields: ['username'] }); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); }); describe('Model#sync', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.User.beforeSync(beforeHook); this.User.afterSync(afterHook); - return this.User.sync().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await this.User.sync(); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); - it('should not run hooks when "hooks = false" option passed', function() { + it('should not run hooks when "hooks = false" option passed', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.User.beforeSync(beforeHook); this.User.afterSync(afterHook); - return this.User.sync({ hooks: false }).then(() => { - expect(beforeHook).to.not.have.been.called; - expect(afterHook).to.not.have.been.called; - }); + await this.User.sync({ hooks: false }); + expect(beforeHook).to.not.have.been.called; + expect(afterHook).to.not.have.been.called; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -270,13 +250,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); this.User.afterSync(afterHook); - return expect(this.User.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); + await expect(this.User.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -286,17 +265,16 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { throw new Error('Whoops!'); }); - return expect(this.User.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await expect(this.User.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); }); describe('sequelize#sync', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), modelBeforeHook = sinon.spy(), @@ -307,15 +285,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.afterSync(modelAfterHook); this.sequelize.afterBulkSync(afterHook); - return this.sequelize.sync().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(modelBeforeHook).to.have.been.calledOnce; - expect(modelAfterHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await this.sequelize.sync(); + expect(beforeHook).to.have.been.calledOnce; + expect(modelBeforeHook).to.have.been.calledOnce; + expect(modelAfterHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); - it('should not run hooks if "hooks = false" option passed', function() { + it('should not run hooks if "hooks = false" option passed', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), modelBeforeHook = sinon.spy(), @@ -326,12 +303,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.afterSync(modelAfterHook); this.sequelize.afterBulkSync(afterHook); - return this.sequelize.sync({ hooks: false }).then(() => { - expect(beforeHook).to.not.have.been.called; - expect(modelBeforeHook).to.not.have.been.called; - expect(modelAfterHook).to.not.have.been.called; - expect(afterHook).to.not.have.been.called; - }); + await this.sequelize.sync({ hooks: false }); + expect(beforeHook).to.not.have.been.called; + expect(modelBeforeHook).to.not.have.been.called; + expect(modelAfterHook).to.not.have.been.called; + expect(afterHook).to.not.have.been.called; }); afterEach(function() { @@ -342,7 +318,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.sequelize.beforeBulkSync(() => { @@ -351,13 +327,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); this.sequelize.afterBulkSync(afterHook); - return expect(this.sequelize.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); + await expect(this.sequelize.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -367,10 +342,9 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { throw new Error('Whoops!'); }); - return expect(this.sequelize.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await expect(this.sequelize.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); afterEach(function() { @@ -381,58 +355,52 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('#removal', () => { - it('should be able to remove by name', function() { + it('should be able to remove by name', async function() { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); this.User.addHook('beforeCreate', 'sasuke', sasukeHook); this.User.addHook('beforeCreate', 'naruto', narutoHook); - return this.User.create({ username: 'makunouchi' }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeCreate', 'sasuke'); - return this.User.create({ username: 'sendo' }); - }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); + await this.User.create({ username: 'makunouchi' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledOnce; + this.User.removeHook('beforeCreate', 'sasuke'); + await this.User.create({ username: 'sendo' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledTwice; }); - it('should be able to remove by reference', function() { + it('should be able to remove by reference', async function() { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); this.User.addHook('beforeCreate', sasukeHook); this.User.addHook('beforeCreate', narutoHook); - return this.User.create({ username: 'makunouchi' }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeCreate', sasukeHook); - return this.User.create({ username: 'sendo' }); - }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); + await this.User.create({ username: 'makunouchi' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledOnce; + this.User.removeHook('beforeCreate', sasukeHook); + await this.User.create({ username: 'sendo' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledTwice; }); - it('should be able to remove proxies', function() { + it('should be able to remove proxies', async function() { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); this.User.addHook('beforeSave', sasukeHook); this.User.addHook('beforeSave', narutoHook); - return this.User.create({ username: 'makunouchi' }).then(user => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.removeHook('beforeSave', sasukeHook); - return user.update({ username: 'sendo' }); - }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); + const user = await this.User.create({ username: 'makunouchi' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledOnce; + this.User.removeHook('beforeSave', sasukeHook); + await user.update({ username: 'sendo' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledTwice; }); }); }); diff --git a/test/integration/hooks/restore.test.js b/test/integration/hooks/restore.test.js index e309755b5ee9..c1665d774f59 100644 --- a/test/integration/hooks/restore.test.js +++ b/test/integration/hooks/restore.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -29,31 +29,28 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#restore', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.ParanoidUser.beforeRestore(beforeHook); this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - return user.restore().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); - }); + const user = await this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + await user.restore(); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -63,17 +60,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - return expect(user.restore()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); - }); - }); + const user = await this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + await expect(user.restore()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -83,14 +77,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { throw new Error('Whoops!'); }); - return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - return expect(user.restore()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); - }); + const user = await this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + await expect(user.restore()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); }); diff --git a/test/integration/hooks/updateAttributes.test.js b/test/integration/hooks/updateAttributes.test.js index 7d7c91c813ac..eec8ec895fac 100644 --- a/test/integration/hooks/updateAttributes.test.js +++ b/test/integration/hooks/updateAttributes.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,12 +18,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#update', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), @@ -34,20 +34,18 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.beforeSave(beforeSave); this.User.afterSave(afterSave); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.update({ username: 'Chong' }).then(user => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledTwice; - expect(afterSave).to.have.been.calledTwice; - expect(user.username).to.equal('Chong'); - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + const user0 = await user.update({ username: 'Chong' }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledTwice; + expect(afterSave).to.have.been.calledTwice; + expect(user0.username).to.equal('Chong'); }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), @@ -61,17 +59,15 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.beforeSave(beforeSave); this.User.afterSave(afterSave); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - expect(afterSave).to.have.been.calledOnce; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.update({ username: 'Chong' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; + expect(afterSave).to.have.been.calledOnce; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), @@ -85,47 +81,39 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.User.beforeSave(beforeSave); this.User.afterSave(afterSave); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledTwice; - expect(afterSave).to.have.been.calledOnce; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.update({ username: 'Chong' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledTwice; + expect(afterSave).to.have.been.calledOnce; }); }); describe('preserves changes to instance', () => { - it('beforeValidate', function() { - + it('beforeValidate', async function() { this.User.beforeValidate(user => { user.mood = 'happy'; }); - return this.User.create({ username: 'fireninja', mood: 'invalid' }).then(user => { - return user.update({ username: 'hero' }); - }).then(user => { - expect(user.username).to.equal('hero'); - expect(user.mood).to.equal('happy'); - }); + const user0 = await this.User.create({ username: 'fireninja', mood: 'invalid' }); + const user = await user0.update({ username: 'hero' }); + expect(user.username).to.equal('hero'); + expect(user.mood).to.equal('happy'); }); - it('afterValidate', function() { - + it('afterValidate', async function() { this.User.afterValidate(user => { user.mood = 'sad'; }); - return this.User.create({ username: 'fireninja', mood: 'nuetral' }).then(user => { - return user.update({ username: 'spider' }); - }).then(user => { - expect(user.username).to.equal('spider'); - expect(user.mood).to.equal('sad'); - }); + const user0 = await this.User.create({ username: 'fireninja', mood: 'nuetral' }); + const user = await user0.update({ username: 'spider' }); + expect(user.username).to.equal('spider'); + expect(user.mood).to.equal('sad'); }); - it('beforeSave', function() { + it('beforeSave', async function() { let hookCalled = 0; this.User.beforeSave(user => { @@ -133,16 +121,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ username: 'fireninja', mood: 'nuetral' }).then(user => { - return user.update({ username: 'spider', mood: 'sad' }); - }).then(user => { - expect(user.username).to.equal('spider'); - expect(user.mood).to.equal('happy'); - expect(hookCalled).to.equal(2); - }); + const user0 = await this.User.create({ username: 'fireninja', mood: 'nuetral' }); + const user = await user0.update({ username: 'spider', mood: 'sad' }); + expect(user.username).to.equal('spider'); + expect(user.mood).to.equal('happy'); + expect(hookCalled).to.equal(2); }); - it('beforeSave with beforeUpdate', function() { + it('beforeSave with beforeUpdate', async function() { let hookCalled = 0; this.User.beforeUpdate(user => { @@ -155,13 +141,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - return user.update({ username: 'spider', mood: 'sad' }); - }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('spider'); - expect(hookCalled).to.equal(3); - }); + const user0 = await this.User.create({ username: 'akira' }); + const user = await user0.update({ username: 'spider', mood: 'sad' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('spider'); + expect(hookCalled).to.equal(3); }); }); }); diff --git a/test/integration/hooks/upsert.test.js b/test/integration/hooks/upsert.test.js index 289a67bfd5ea..dd7b2cb0f51c 100644 --- a/test/integration/hooks/upsert.test.js +++ b/test/integration/hooks/upsert.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), if (Support.sequelize.dialect.supports.upserts) { describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -20,27 +20,26 @@ if (Support.sequelize.dialect.supports.upserts) { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#upsert', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); this.User.beforeUpsert(beforeHook); this.User.afterUpsert(afterHook); - return this.User.upsert({ username: 'Toni', mood: 'happy' }).then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await this.User.upsert({ username: 'Toni', mood: 'happy' }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -50,13 +49,12 @@ if (Support.sequelize.dialect.supports.upserts) { }); this.User.afterUpsert(afterHook); - return expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); + await expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); @@ -66,15 +64,14 @@ if (Support.sequelize.dialect.supports.upserts) { throw new Error('Whoops!'); }); - return expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('preserves changes to values', () => { - it('beforeUpsert', function() { + it('beforeUpsert', async function() { let hookCalled = 0; const valuesOriginal = { mood: 'sad', username: 'leafninja' }; @@ -83,10 +80,9 @@ if (Support.sequelize.dialect.supports.upserts) { hookCalled++; }); - return this.User.upsert(valuesOriginal).then(() => { - expect(valuesOriginal.mood).to.equal('happy'); - expect(hookCalled).to.equal(1); - }); + await this.User.upsert(valuesOriginal); + expect(valuesOriginal.mood).to.equal('happy'); + expect(hookCalled).to.equal(1); }); }); }); diff --git a/test/integration/hooks/validate.test.js b/test/integration/hooks/validate.test.js index 16c7c6b2cf00..d3a6d301eb80 100644 --- a/test/integration/hooks/validate.test.js +++ b/test/integration/hooks/validate.test.js @@ -7,7 +7,7 @@ const Support = require('../support'); const DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,12 +18,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#validate', () => { describe('#create', () => { - it('should return the user', function() { + it('should return the user', async function() { this.User.beforeValidate(user => { user.username = 'Bob'; user.mood = 'happy'; @@ -33,15 +33,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { user.username = 'Toni'; }); - return this.User.create({ mood: 'ecstatic' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('Toni'); - }); + const user = await this.User.create({ mood: 'ecstatic' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('Toni'); }); }); describe('#3534, hooks modifications', () => { - it('fields modified in hooks are saved', function() { + it('fields modified in hooks are saved', async function() { this.User.afterValidate(user => { //if username is defined and has more than 5 char user.username = user.username @@ -56,94 +55,84 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); - return this.User.create({ username: 'T', mood: 'neutral' }).then(user => { - expect(user.mood).to.equal('neutral'); - expect(user.username).to.equal('Samorost 3'); - - //change attributes - user.mood = 'sad'; - user.username = 'Samorost Good One'; - - return user.save(); - }).then(uSaved => { - expect(uSaved.mood).to.equal('sad'); - expect(uSaved.username).to.equal('Samorost Good One'); - - //change attributes, expect to be replaced by hooks - uSaved.username = 'One'; - - return uSaved.save(); - }).then(uSaved => { - //attributes were replaced by hooks ? - expect(uSaved.mood).to.equal('sad'); - expect(uSaved.username).to.equal('Samorost 3'); - return this.User.findByPk(uSaved.id); - }).then(uFetched => { - expect(uFetched.mood).to.equal('sad'); - expect(uFetched.username).to.equal('Samorost 3'); - - uFetched.mood = null; - uFetched.username = 'New Game is Needed'; - - return uFetched.save(); - }).then(uFetchedSaved => { - expect(uFetchedSaved.mood).to.equal('neutral'); - expect(uFetchedSaved.username).to.equal('New Game is Needed'); - - return this.User.findByPk(uFetchedSaved.id); - }).then(uFetched => { - expect(uFetched.mood).to.equal('neutral'); - expect(uFetched.username).to.equal('New Game is Needed'); - - //expect to be replaced by hooks - uFetched.username = 'New'; - uFetched.mood = 'happy'; - return uFetched.save(); - }).then(uFetchedSaved => { - expect(uFetchedSaved.mood).to.equal('happy'); - expect(uFetchedSaved.username).to.equal('Samorost 3'); - }); + const user = await this.User.create({ username: 'T', mood: 'neutral' }); + expect(user.mood).to.equal('neutral'); + expect(user.username).to.equal('Samorost 3'); + + //change attributes + user.mood = 'sad'; + user.username = 'Samorost Good One'; + + const uSaved0 = await user.save(); + expect(uSaved0.mood).to.equal('sad'); + expect(uSaved0.username).to.equal('Samorost Good One'); + + //change attributes, expect to be replaced by hooks + uSaved0.username = 'One'; + + const uSaved = await uSaved0.save(); + //attributes were replaced by hooks ? + expect(uSaved.mood).to.equal('sad'); + expect(uSaved.username).to.equal('Samorost 3'); + const uFetched0 = await this.User.findByPk(uSaved.id); + expect(uFetched0.mood).to.equal('sad'); + expect(uFetched0.username).to.equal('Samorost 3'); + + uFetched0.mood = null; + uFetched0.username = 'New Game is Needed'; + + const uFetchedSaved0 = await uFetched0.save(); + expect(uFetchedSaved0.mood).to.equal('neutral'); + expect(uFetchedSaved0.username).to.equal('New Game is Needed'); + + const uFetched = await this.User.findByPk(uFetchedSaved0.id); + expect(uFetched.mood).to.equal('neutral'); + expect(uFetched.username).to.equal('New Game is Needed'); + + //expect to be replaced by hooks + uFetched.username = 'New'; + uFetched.mood = 'happy'; + const uFetchedSaved = await uFetched.save(); + expect(uFetchedSaved.mood).to.equal('happy'); + expect(uFetchedSaved.username).to.equal('Samorost 3'); }); }); describe('on error', () => { - it('should emit an error from after hook', function() { + it('should emit an error from after hook', async function() { this.User.afterValidate(user => { user.mood = 'ecstatic'; throw new Error('Whoops! Changed user.mood!'); }); - return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejectedWith('Whoops! Changed user.mood!'); + await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejectedWith('Whoops! Changed user.mood!'); }); - it('should call validationFailed hook', function() { + it('should call validationFailed hook', async function() { const validationFailedHook = sinon.spy(); this.User.validationFailed(validationFailedHook); - return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(() => { - expect(validationFailedHook).to.have.been.calledOnce; - }); + await expect(this.User.create({ mood: 'happy' })).to.be.rejected; + expect(validationFailedHook).to.have.been.calledOnce; }); - it('should not replace the validation error in validationFailed hook by default', function() { + it('should not replace the validation error in validationFailed hook by default', async function() { const validationFailedHook = sinon.stub(); this.User.validationFailed(validationFailedHook); - return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => { - expect(err.name).to.equal('SequelizeValidationError'); - }); + const err = await expect(this.User.create({ mood: 'happy' })).to.be.rejected; + expect(err.name).to.equal('SequelizeValidationError'); }); - it('should replace the validation error if validationFailed hook creates a new error', function() { + it('should replace the validation error if validationFailed hook creates a new error', async function() { const validationFailedHook = sinon.stub().throws(new Error('Whoops!')); this.User.validationFailed(validationFailedHook); - return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => { - expect(err.message).to.equal('Whoops!'); - }); + const err = await expect(this.User.create({ mood: 'happy' })).to.be.rejected; + expect(err.message).to.equal('Whoops!'); }); }); }); From 9a95e727cec29625404475bfed990ad5a3350f87 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 16 May 2020 10:30:54 +0530 Subject: [PATCH 145/414] feat(belongs-to-many): get/has/count for paranoid join table (#12256) --- lib/associations/belongs-to-many.js | 2 + .../associations/belongs-to-many.test.js | 110 ++++++++++++++++-- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 1ea24252291e..14ed1015eb76 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -412,6 +412,7 @@ class BelongsToMany extends Association { * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model * @param {object} [options.through.where] An optional where clause applied to through model (join table) + * @param {boolean} [options.through.paranoid=true] If true, only non-deleted records will be returned from the join table. If false, both deleted and non-deleted records will be returned. Only applies if through model is paranoid * * @returns {Promise>} */ @@ -453,6 +454,7 @@ class BelongsToMany extends Association { association: this.oneFromTarget, attributes: options.joinTableAttributes, required: true, + paranoid: _.get(options.through, 'paranoid', true), where: throughWhere }); } diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 82f95d5725c8..03725fea1528 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -2446,21 +2446,99 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('through', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', {}); - this.Project = this.sequelize.define('Project', {}); - this.UserProjects = this.sequelize.define('UserProjects', { - status: DataTypes.STRING, - data: DataTypes.INTEGER + describe('paranoid', () => { + beforeEach(async function() { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', {}, { + paranoid: true + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + await this.sequelize.sync(); + + this.users = await Promise.all([ + this.User.create(), + this.User.create(), + this.User.create() + ]); + + this.projects = await Promise.all([ + this.Project.create(), + this.Project.create(), + this.Project.create() + ]); + }); + + it('gets only non-deleted records by default', async function() { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + ProjectId: this.projects[0].id + } + }); + + const result = await this.users[0].getProjects(); + + expect(result.length).to.equal(2); }); - this.User.belongsToMany(this.Project, { through: this.UserProjects }); - this.Project.belongsToMany(this.User, { through: this.UserProjects }); + it('returns both deleted and non-deleted records with paranoid=false', async function() { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + ProjectId: this.projects[0].id + } + }); + + const result = await this.users[0].getProjects({ through: { paranoid: false } }); + + expect(result.length).to.equal(3); + }); - return this.sequelize.sync(); + it('hasAssociation also respects paranoid option', async function() { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + ProjectId: this.projects[0].id + } + }); + + expect( + await this.users[0].hasProjects(this.projects[0], { through: { paranoid: false } }) + ).to.equal(true); + + expect( + await this.users[0].hasProjects(this.projects[0]) + ).to.equal(false); + + expect( + await this.users[0].hasProjects(this.projects[1]) + ).to.equal(true); + + expect( + await this.users[0].hasProjects(this.projects) + ).to.equal(false); + }); }); describe('fetching from join table', () => { + beforeEach(function() { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', { + status: DataTypes.STRING, + data: DataTypes.INTEGER + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + return this.sequelize.sync(); + }); + it('should contain the data from the join table on .UserProjects a DAO', async function() { const [user0, project0] = await Promise.all([ this.User.create(), @@ -2523,6 +2601,20 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('inserting in join table', () => { + beforeEach(function() { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', { + status: DataTypes.STRING, + data: DataTypes.INTEGER + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + return this.sequelize.sync(); + }); + describe('add', () => { it('should insert data provided on the object into the join table', async function() { const [u, p] = await Promise.all([ From 4d75caf44090ed93d94e7e9bce76b014e0d82d89 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 16 May 2020 13:00:48 +0530 Subject: [PATCH 146/414] fix(mssql): duplicate order in FETCH/NEXT queries (#12257) --- lib/dialects/mssql/query-generator.js | 14 +- .../dialects/mssql/regressions.test.js | 152 ++++++++++++------ 2 files changed, 112 insertions(+), 54 deletions(-) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index b8ea6cde99a2..25572053d3a0 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -917,8 +917,18 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { if (options.limit || options.offset) { if (!options.order || options.include && !orders.subQueryOrder.length) { - fragment += options.order && !isSubQuery ? ', ' : ' ORDER BY '; - fragment += `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`; + const tablePkFragment = `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`; + if (!options.order) { + fragment += ` ORDER BY ${tablePkFragment}`; + } else { + const orderFieldNames = _.map(options.order, order => order[0]); + const primaryKeyFieldAlreadyPresent = _.includes(orderFieldNames, model.primaryKeyField); + + if (!primaryKeyFieldAlreadyPresent) { + fragment += options.order && !isSubQuery ? ', ' : ' ORDER BY '; + fragment += tablePkFragment; + } + } } if (options.offset || options.limit) { diff --git a/test/integration/dialects/mssql/regressions.test.js b/test/integration/dialects/mssql/regressions.test.js index c0893b049d3e..8e8e80b2b4cb 100644 --- a/test/integration/dialects/mssql/regressions.test.js +++ b/test/integration/dialects/mssql/regressions.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), dialect = Support.getTestDialect(); if (dialect.match(/^mssql/)) { - describe('[MSSQL Specific] Regressions', () => { + describe(Support.getTestDialectTeaser('Regressions'), () => { it('does not duplicate columns in ORDER BY statement, #9008', async function() { const LoginLog = this.sequelize.define('LoginLog', { ID: { @@ -80,72 +80,120 @@ if (dialect.match(/^mssql/)) { expect(logs).to.have.length(2); expect(logs[0].User.get('UserName')).to.equal('Shaktimaan'); expect(logs[1].User.get('UserName')).to.equal('Aryamaan'); - }); - }); - - it('sets the varchar(max) length correctly on describeTable', async function() { - const Users = this.sequelize.define('_Users', { - username: Sequelize.STRING('MAX') - }, { freezeTableName: true }); - await Users.sync({ force: true }); - const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); - const username = metadata.username; - expect(username.type).to.include('(MAX)'); - }); + // #11258 and similar + const otherLogs = await LoginLog.findAll({ + include: [ + { + model: User, + where: { + UserName: { + [Op.like]: '%maan%' + } + } + } + ], + order: [['id', 'DESC']], + offset: 0, + limit: 10 + }); - it('sets the char(10) length correctly on describeTable', async function() { - const Users = this.sequelize.define('_Users', { - username: Sequelize.CHAR(10) - }, { freezeTableName: true }); + expect(otherLogs).to.have.length(2); + expect(otherLogs[0].User.get('UserName')).to.equal('Aryamaan'); + expect(otherLogs[1].User.get('UserName')).to.equal('Shaktimaan'); - await Users.sync({ force: true }); - const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); - const username = metadata.username; - expect(username.type).to.include('(10)'); - }); + // Separate queries can apply order freely + const separateUsers = await User.findAll({ + include: [ + { + model: LoginLog, + separate: true, + order: [ + 'id' + ] + } + ], + where: { + UserName: { + [Op.like]: '%maan%' + } + }, + order: ['UserName', ['UserID', 'DESC']], + offset: 0, + limit: 10 + }); - it('saves value bigger than 2147483647, #11245', async function() { - const BigIntTable = this.sequelize.define('BigIntTable', { - business_id: { - type: Sequelize.BIGINT, - allowNull: false - } - }, { - freezeTableName: true + expect(separateUsers).to.have.length(2); + expect(separateUsers[0].get('UserName')).to.equal('Aryamaan'); + expect(separateUsers[0].get('LoginLogs')).to.have.length(1); + expect(separateUsers[1].get('UserName')).to.equal('Shaktimaan'); + expect(separateUsers[1].get('LoginLogs')).to.have.length(1); }); - const bigIntValue = 2147483648; - - await BigIntTable.sync({ force: true }); + it('sets the varchar(max) length correctly on describeTable', async function() { + const Users = this.sequelize.define('_Users', { + username: Sequelize.STRING('MAX') + }, { freezeTableName: true }); - await BigIntTable.create({ - business_id: bigIntValue + await Users.sync({ force: true }); + const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); + const username = metadata.username; + expect(username.type).to.include('(MAX)'); }); - const record = await BigIntTable.findOne(); - expect(Number(record.business_id)).to.equals(bigIntValue); - }); + it('sets the char(10) length correctly on describeTable', async function() { + const Users = this.sequelize.define('_Users', { + username: Sequelize.CHAR(10) + }, { freezeTableName: true }); - it('saves boolean is true, #12090', async function() { - const BooleanTable = this.sequelize.define('BooleanTable', { - status: { - type: Sequelize.BOOLEAN, - allowNull: false - } - }, { - freezeTableName: true + await Users.sync({ force: true }); + const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); + const username = metadata.username; + expect(username.type).to.include('(10)'); }); - const value = true; + it('saves value bigger than 2147483647, #11245', async function() { + const BigIntTable = this.sequelize.define('BigIntTable', { + business_id: { + type: Sequelize.BIGINT, + allowNull: false + } + }, { + freezeTableName: true + }); + + const bigIntValue = 2147483648; - await BooleanTable.sync({ force: true }); + await BigIntTable.sync({ force: true }); - await BooleanTable.create({ - status: value + await BigIntTable.create({ + business_id: bigIntValue + }); + + const record = await BigIntTable.findOne(); + expect(Number(record.business_id)).to.equals(bigIntValue); }); - const record = await BooleanTable.findOne(); - expect(record.status).to.equals(value); + it('saves boolean is true, #12090', async function() { + const BooleanTable = this.sequelize.define('BooleanTable', { + status: { + type: Sequelize.BOOLEAN, + allowNull: false + } + }, { + freezeTableName: true + }); + + const value = true; + + await BooleanTable.sync({ force: true }); + + await BooleanTable.create({ + status: value + }); + + const record = await BooleanTable.findOne(); + expect(record.status).to.equals(value); + }); }); } From 461c924241298c763b01589f3a6d9996e1f0b05a Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 16 May 2020 17:59:19 +0530 Subject: [PATCH 147/414] fix(associations): ensure correct schema on all generated attributes (#12258) --- lib/associations/helpers.js | 16 ++---- lib/dialects/mssql/query-generator.js | 1 - lib/dialects/mysql/query-generator.js | 1 - lib/utils.js | 5 +- .../dialects/mssql/regressions.test.js | 52 +++++++++++++++++++ 5 files changed, 60 insertions(+), 15 deletions(-) diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js index c685ecec2a69..41e2f27a3e08 100644 --- a/lib/associations/helpers.js +++ b/lib/associations/helpers.js @@ -21,19 +21,11 @@ function addForeignKeyConstraints(newAttribute, source, target, options, key) { .map(primaryKeyAttribute => source.rawAttributes[primaryKeyAttribute].field || primaryKeyAttribute); if (primaryKeys.length === 1 || !primaryKeys.includes(key)) { - if (source._schema) { - newAttribute.references = { - model: source.sequelize.getQueryInterface().queryGenerator.addSchema({ - tableName: source.tableName, - _schema: source._schema, - _schemaDelimiter: source._schemaDelimiter - }) - }; - } else { - newAttribute.references = { model: source.tableName }; - } + newAttribute.references = { + model: source.getTableName(), + key: key || primaryKeys[0] + }; - newAttribute.references.key = key || primaryKeys[0]; newAttribute.onDelete = options.onDelete; newAttribute.onUpdate = options.onUpdate; } diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 25572053d3a0..4c1b20bc4bf8 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -648,7 +648,6 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { attribute = attributes[key]; if (attribute.references) { - if (existingConstraints.includes(attribute.references.model.toString())) { // no cascading constraints to a table more than once attribute.onDelete = ''; diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index ab2de8bf09cc..fe8f02cb5703 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -411,7 +411,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } if (attribute.references) { - if (options && options.context === 'addColumn' && options.foreignKey) { const attrName = this.quoteIdentifier(options.foreignKey); const fkName = this.quoteIdentifier(`${options.tableName}_${attrName}_foreign_idx`); diff --git a/lib/utils.js b/lib/utils.js index e7fd29532d76..538cec2791a9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -48,9 +48,12 @@ exports.isPrimitive = isPrimitive; // Same concept as _.merge, but don't overwrite properties that have already been assigned function mergeDefaults(a, b) { - return _.mergeWith(a, b, objectValue => { + return _.mergeWith(a, b, (objectValue, sourceValue) => { // If it's an object, let _ handle it this time, we will be called again for each property if (!_.isPlainObject(objectValue) && objectValue !== undefined) { + if (_.isFunction(objectValue) && _.isNative(objectValue)) { + return sourceValue || objectValue; + } return objectValue; } }); diff --git a/test/integration/dialects/mssql/regressions.test.js b/test/integration/dialects/mssql/regressions.test.js index 8e8e80b2b4cb..9ff1b4a07691 100644 --- a/test/integration/dialects/mssql/regressions.test.js +++ b/test/integration/dialects/mssql/regressions.test.js @@ -2,6 +2,7 @@ const chai = require('chai'), expect = chai.expect, + sinon = require('sinon'), Support = require('../../support'), Sequelize = Support.Sequelize, Op = Sequelize.Op, @@ -130,6 +131,57 @@ if (dialect.match(/^mssql/)) { expect(separateUsers[1].get('LoginLogs')).to.have.length(1); }); + it('allow referencing FK to different tables in a schema with onDelete, #10125', async function() { + const Child = this.sequelize.define( + 'Child', + {}, + { + timestamps: false, + freezeTableName: true, + schema: 'a' + } + ); + const Toys = this.sequelize.define( + 'Toys', + {}, + { + timestamps: false, + freezeTableName: true, + schema: 'a' + } + ); + const Parent = this.sequelize.define( + 'Parent', + {}, + { + timestamps: false, + freezeTableName: true, + schema: 'a' + } + ); + + Child.hasOne(Toys, { + onDelete: 'CASCADE' + }); + + Parent.hasOne(Toys, { + onDelete: 'CASCADE' + }); + + const spy = sinon.spy(); + + await this.sequelize.queryInterface.createSchema('a'); + await this.sequelize.sync({ + force: true, + logging: spy + }); + + expect(spy).to.have.been.called; + const log = spy.args.find(arg => arg[0].includes('IF OBJECT_ID(\'[a].[Toys]\', \'U\') IS NULL CREATE TABLE'))[0]; + + expect(log.match(/ON DELETE CASCADE/g).length).to.equal(2); + }); + it('sets the varchar(max) length correctly on describeTable', async function() { const Users = this.sequelize.define('_Users', { username: Sequelize.STRING('MAX') From 18dcc7ee3c00dbb1337684495dc9f03980301fbe Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 16 May 2020 18:39:56 +0530 Subject: [PATCH 148/414] fix(postgres): addColumn support ARRAY(ENUM) (#12259) --- lib/dialects/postgres/query-generator.js | 10 ++++++---- test/integration/query-interface.test.js | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 753aeeba0f13..18678c740344 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -244,17 +244,19 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return super.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend); } - addColumnQuery(table, key, dataType) { - - const dbDataType = this.attributeToSQL(dataType, { context: 'addColumn', table, key }); + addColumnQuery(table, key, attribute) { + const dbDataType = this.attributeToSQL(attribute, { context: 'addColumn', table, key }); + const dataType = attribute.type || attribute; const definition = this.dataTypeMapping(table, key, dbDataType); const quotedKey = this.quoteIdentifier(key); const quotedTable = this.quoteTable(this.extractTableDetails(table)); let query = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedKey} ${definition};`; - if (dataType.type && dataType.type instanceof DataTypes.ENUM || dataType instanceof DataTypes.ENUM) { + if (dataType instanceof DataTypes.ENUM) { query = this.pgEnum(table, key, dataType) + query; + } else if (dataType.type && dataType.type instanceof DataTypes.ENUM) { + query = this.pgEnum(table, key, dataType.type) + query; } return query; diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index c709f0af44c4..16ab9f55b5de 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -380,6 +380,23 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { values: ['value1', 'value2', 'value3'] }); }); + + if (dialect === 'postgres') { + it('should be able to add a column of type of array of enums', async function() { + await this.queryInterface.addColumn('users', 'tags', { + allowNull: false, + type: Sequelize.ARRAY(Sequelize.ENUM( + 'Value1', + 'Value2', + 'Value3' + )) + }); + const result = await this.queryInterface.describeTable('users'); + expect(result).to.have.property('tags'); + expect(result.tags.type).to.equal('ARRAY'); + expect(result.tags.allowNull).to.be.false; + }); + } }); describe('describeForeignKeys', () => { From daeb0f7b447a40c872eedd76871e5771a6d6a643 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 17 May 2020 13:35:22 +0530 Subject: [PATCH 149/414] fix(mssql): dont use OUTPUT INSERTED.* for update without returning (#12260) --- lib/dialects/abstract/query-generator.js | 2 +- lib/dialects/abstract/query-interface.js | 2 +- lib/dialects/mssql/query.js | 2 +- lib/instance-validator.js | 2 +- lib/model.js | 3 +-- test/unit/sql/update.test.js | 29 +++++++++++++++++++++--- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 622e2b53f3f1..9cb0f285aa36 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -362,7 +362,7 @@ class QueryGenerator { } } - if (this._dialect.supports.returnValues && (this._dialect.supports.returnValues.output || options.returning)) { + if (this._dialect.supports.returnValues && options.returning) { const returnValues = this.generateReturnValues(attributes, options); suffix += returnValues.returningFragment; diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 04749766c433..0124662c490f 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -847,7 +847,7 @@ class QueryInterface { async update(instance, tableName, values, identifier, options) { options = { ...options }; - options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); + options.hasTrigger = instance && instance.constructor.options.hasTrigger; const sql = this.queryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 7c6e9350bfca..7ee46fdbab12 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -211,7 +211,7 @@ class Query extends AbstractQuery { return data[0]; } if (this.isBulkUpdateQuery()) { - return data.length; + return rowCount; } if (this.isBulkDeleteQuery()) { return data[0] && data[0].AFFECTEDROWS; diff --git a/lib/instance-validator.js b/lib/instance-validator.js index fa4d91debfb7..1e4f343a4160 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -160,7 +160,7 @@ class InstanceValidator { */ async _customValidators() { const validators = []; - _.each(this.modelInstance._modelOptions.validate, (validator, validatorType) => { + _.each(this.modelInstance.constructor.options.validate, (validator, validatorType) => { if (this.options.skip.includes(validatorType)) { return; } diff --git a/lib/model.js b/lib/model.js index 701841ad111d..92446c789e89 100644 --- a/lib/model.js +++ b/lib/model.js @@ -105,7 +105,6 @@ class Model { this.dataValues = {}; this._previousDataValues = {}; this._changed = new Set(); - this._modelOptions = this.constructor.options; this._options = options || {}; /** @@ -3394,7 +3393,7 @@ class Model { }, {}); if (_.size(where) === 0) { - return this._modelOptions.whereCollection; + return this.constructor.options.whereCollection; } const versionAttr = this.constructor._versionAttribute; if (checkVersion && versionAttr) { diff --git a/test/unit/sql/update.test.js b/test/unit/sql/update.test.js index 572debd399a6..71f99050b380 100644 --- a/test/unit/sql/update.test.js +++ b/test/unit/sql/update.test.js @@ -10,6 +10,30 @@ const Support = require('../support'), describe(Support.getTestDialectTeaser('SQL'), () => { describe('update', () => { + it('supports returning false', () => { + const User = Support.sequelize.define('user', { + username: { + type: DataTypes.STRING, + field: 'user_name' + } + }, { + timestamps: false + }); + + const options = { + returning: false + }; + expectsql(sql.updateQuery(User.tableName, { user_name: 'triggertest' }, { id: 2 }, options, User.rawAttributes), + { + query: { + default: 'UPDATE [users] SET [user_name]=$1 WHERE [id] = $2' + }, + bind: { + default: ['triggertest', 2] + } + }); + }); + it('with temp table for trigger', () => { const User = Support.sequelize.define('user', { username: { @@ -38,8 +62,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); - - it('Works with limit', () => { + it('works with limit', () => { const User = Support.sequelize.define('User', { username: { type: DataTypes.STRING @@ -53,7 +76,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { expectsql(sql.updateQuery(User.tableName, { username: 'new.username' }, { username: 'username' }, { limit: 1 }), { query: { - mssql: 'UPDATE TOP(1) [Users] SET [username]=$1 OUTPUT INSERTED.* WHERE [username] = $2', + mssql: 'UPDATE TOP(1) [Users] SET [username]=$1 WHERE [username] = $2', mariadb: 'UPDATE `Users` SET `username`=$1 WHERE `username` = $2 LIMIT 1', mysql: 'UPDATE `Users` SET `username`=$1 WHERE `username` = $2 LIMIT 1', sqlite: 'UPDATE `Users` SET `username`=$1 WHERE rowid IN (SELECT rowid FROM `Users` WHERE `username` = $2 LIMIT 1)', From b74fec90e64536f0ebed1b07592025718b569ed6 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 17 May 2020 14:53:45 +0530 Subject: [PATCH 150/414] docs: 6.0.0-beta.6 --- docs/manual/other-topics/upgrade-to-v6.md | 61 ++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index 5b090a82864a..0321088f0246 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -20,9 +20,13 @@ You should now use [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) packag Sequelize.useCLS(namespace); ``` +### Database Engine Support + +We have updated our minimum supported database engine versions. Using older database engine will show `SEQUELIZE0006` deprecation warning. Please check [ENGINE.md](https://github.com/sequelize/sequelize/blob/master/ENGINE.md) for version table. + ### Sequelize -- Bluebird has been removed. Public API now returns native promises. +- Bluebird has been removed. Internally all methods are now using async/await. Public API now returns native promises. Thanks to [Andy Edwards](https://github.com/jedwards1211) for this refactor work. - `Sequelize.Promise` is no longer available. - `sequelize.import` method has been removed. @@ -62,6 +66,61 @@ This method now only takes 2 parameters, `tableName` and `options`. Previously t ## Changelog +### 6.0.0-beta.6 + +- docs(add-constraint): options.fields support +- docs(association): document uniqueKey for belongs to many [#12166](https://github.com/sequelize/sequelize/pull/12166) +- docs(association): options.through.where support +- docs(association): use and instead of 'a nd' [#12191](https://github.com/sequelize/sequelize/pull/12191) +- docs(association): use correct scope name [#12204](https://github.com/sequelize/sequelize/pull/12204) +- docs(manuals): avoid duplicate header ids [#12201](https://github.com/sequelize/sequelize/pull/12201) +- docs(model): correct syntax error in example code [#12137](https://github.com/sequelize/sequelize/pull/12137) +- docs(query-interface): removeIndex indexNameOrAttributes [#11947](https://github.com/sequelize/sequelize/pull/11947) +- docs(resources): add sequelize-guard library [#12235](https://github.com/sequelize/sequelize/pull/12235) +- docs(typescript): fix confusing comments [#12226](https://github.com/sequelize/sequelize/pull/12226) +- docs(v6-guide): bluebird removal API changes +- docs: database version support info [#12168](https://github.com/sequelize/sequelize/pull/12168) +- docs: remove remaining bluebird references [#12167](https://github.com/sequelize/sequelize/pull/12167) +- feat(belongs-to-many): allow creation of paranoid join tables [#12088](https://github.com/sequelize/sequelize/pull/12088) +- feat(belongs-to-many): get/has/count for paranoid join table [#12256](https://github.com/sequelize/sequelize/pull/12256) +- feat(pool): expose maxUses pool config option [#12101](https://github.com/sequelize/sequelize/pull/12101) +- feat(postgres): minify include aliases over limit [#11940](https://github.com/sequelize/sequelize/pull/11940) +- feat(sequelize): handle query string host value [#12041](https://github.com/sequelize/sequelize/pull/12041) +- fix(associations): ensure correct schema on all generated attributes [#12258](https://github.com/sequelize/sequelize/pull/12258) +- fix(docs/instances): use correct variable for increment [#12087](https://github.com/sequelize/sequelize/pull/12087) +- fix(include): separate queries are not sub-queries [#12144](https://github.com/sequelize/sequelize/pull/12144) +- fix(model): fix unchained promise in association logic in bulkCreate [#12163](https://github.com/sequelize/sequelize/pull/12163) +- fix(model): updateOnDuplicate handles composite keys [#11984](https://github.com/sequelize/sequelize/pull/11984) +- fix(model.count): distinct without any column generates invalid SQL [#11946](https://github.com/sequelize/sequelize/pull/11946) +- fix(model.reload): ignore options.where and always use this.where() [#12211](https://github.com/sequelize/sequelize/pull/12211) +- fix(mssql) insert record failure because of BOOLEAN column type [#12090](https://github.com/sequelize/sequelize/pull/12090) +- fix(mssql): cast sql_variant in query generator [#11994](https://github.com/sequelize/sequelize/pull/11994) +- fix(mssql): dont use OUTPUT INSERTED for update without returning [#12260](https://github.com/sequelize/sequelize/pull/12260) +- fix(mssql): duplicate order in FETCH/NEXT queries [#12257](https://github.com/sequelize/sequelize/pull/12257) +- fix(mssql): set correct scale for float [#11962](https://github.com/sequelize/sequelize/pull/11962) +- fix(mssql): tedious v9 requires connect call [#12182](https://github.com/sequelize/sequelize/pull/12182) +- fix(mssql): use uppercase for engine table and columns [#12212](https://github.com/sequelize/sequelize/pull/12212) +- fix(pool): show deprecation when engine is not supported [#12218](https://github.com/sequelize/sequelize/pull/12218) +- fix(postgres): addColumn support ARRAY(ENUM) [#12259](https://github.com/sequelize/sequelize/pull/12259) +- fix(query): do not bind $ used within a whole-word [#12250](https://github.com/sequelize/sequelize/pull/12250) +- fix(query-generator): handle literal for substring based operators [#12210](https://github.com/sequelize/sequelize/pull/12210) +- fix(query-interface): allow passing null for query interface insert [#11931](https://github.com/sequelize/sequelize/pull/11931) +- fix(query-interface): allow sequelize.fn and sequelize.literal in fields of IndexesOptions [#12224](https://github.com/sequelize/sequelize/pull/12224) +- fix(scope): don't modify original scope definition [#12207](https://github.com/sequelize/sequelize/pull/12207) +- fix(sqlite): multiple primary keys results in syntax error [#12237](https://github.com/sequelize/sequelize/pull/12237) +- fix(sync): pass options to all query methods [#12208](https://github.com/sequelize/sequelize/pull/12208) +- fix(typings): add type_helpers to file list [#12000](https://github.com/sequelize/sequelize/pull/12000) +- fix(typings): correct Model.init return type [#12148](https://github.com/sequelize/sequelize/pull/12148) +- fix(typings): fn is assignable to where [#12040](https://github.com/sequelize/sequelize/pull/12040) +- fix(typings): getForeignKeysForTables argument definition [#12084](https://github.com/sequelize/sequelize/pull/12084) +- fix(typings): make between operator accept date ranges [#12162](https://github.com/sequelize/sequelize/pull/12162) +- refactor(ci): improve database wait script [#12132](https://github.com/sequelize/sequelize/pull/12132) +- refactor(tsd-test-setup): add & setup dtslint [#11879](https://github.com/sequelize/sequelize/pull/11879) +- refactor: move all dialect conditional logic into subclass [#12217](https://github.com/sequelize/sequelize/pull/12217) +- refactor: remove sequelize.import helper [#12175](https://github.com/sequelize/sequelize/pull/12175) +- refactor: use native versions [#12159](https://github.com/sequelize/sequelize/pull/12159) +- refactor: use object spread instead of Object.assign [#12213](https://github.com/sequelize/sequelize/pull/12213) + ### 6.0.0-beta.5 - fix(find-all): throw on empty attributes [#11867](https://github.com/sequelize/sequelize/pull/11867) From 8e51d2ac701e67dc5710df0772527d7bd2f78f64 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 17 May 2020 14:53:59 +0530 Subject: [PATCH 151/414] 6.0.0-beta.6 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff5894567f2e..a4aa41c0c7ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sequelize", - "version": "6.0.0-beta.5", + "version": "6.0.0-beta.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d2d30cf851ea..8e1623fe518f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sequelize", "description": "Multi dialect ORM for Node.JS", - "version": "6.0.0-beta.5", + "version": "6.0.0-beta.6", "maintainers": [ "Sascha Depold ", "Jan Aagaard Meier ", From 8a1429d6044123bba3a3f312b2e0c74b0c829990 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 17 May 2020 15:20:34 +0530 Subject: [PATCH 152/414] fix: remove custom inspect (#12262) --- lib/associations/base.js | 4 ---- lib/model.js | 4 ---- 2 files changed, 8 deletions(-) diff --git a/lib/associations/base.js b/lib/associations/base.js index fe769bd65eaf..40ce304445c4 100644 --- a/lib/associations/base.js +++ b/lib/associations/base.js @@ -136,10 +136,6 @@ class Association { [Symbol.for('nodejs.util.inspect.custom')]() { return this.as; } - - inspect() { - return this.as; - } } module.exports = Association; diff --git a/lib/model.js b/lib/model.js index 92446c789e89..3efd0ffc10e1 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3230,10 +3230,6 @@ class Model { return this.name; } - static inspect() { - return this.name; - } - static hasAlias(alias) { return Object.prototype.hasOwnProperty.call(this.associations, alias); } From 3e4b5ed7dabedf5a6f93b5f93ec9ef586c7212c1 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 19 May 2020 01:47:35 -0300 Subject: [PATCH 153/414] docs(query-interface): fix broken links (#12272) --- docs/manual/other-topics/query-interface.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/manual/other-topics/query-interface.md b/docs/manual/other-topics/query-interface.md index be7f8b2e96c0..5225d0adf1d3 100644 --- a/docs/manual/other-topics/query-interface.md +++ b/docs/manual/other-topics/query-interface.md @@ -4,11 +4,11 @@ An instance of Sequelize uses something called **Query Interface** to communicat The methods from the query interface are therefore lower-level methods; you should use them only if you do not find another way to do it with higher-level APIs from Sequelize. They are, of course, still higher-level than running raw queries directly (i.e., writing SQL by hand). -This guide shows a few examples, but for the full list of what it can do, and for detailed usage of each method, check the [QueryInterface API](../class/lib/query-interface.js~QueryInterface.html). +This guide shows a few examples, but for the full list of what it can do, and for detailed usage of each method, check the [QueryInterface API](../class/lib/dialects/abstract/query-interface.js~QueryInterface.html). ## Obtaining the query interface -From now on, we will call `queryInterface` the singleton instance of the [QueryInterface](../class/lib/query-interface.js~QueryInterface.html) class, which is available on your Sequelize instance: +From now on, we will call `queryInterface` the singleton instance of the [QueryInterface](../class/lib/dialects/abstract/query-interface.js~QueryInterface.html) class, which is available on your Sequelize instance: ```js const { Sequelize, DataTypes } = require('sequelize'); @@ -149,4 +149,4 @@ DROP TABLE `Person_backup`; ## Other -As mentioned in the beginning of this guide, there is a lot more to the Query Interface available in Sequelize! Check the [QueryInterface API](../class/lib/query-interface.js~QueryInterface.html) for a full list of what can be done. \ No newline at end of file +As mentioned in the beginning of this guide, there is a lot more to the Query Interface available in Sequelize! Check the [QueryInterface API](../class/lib/dialects/abstract/query-interface.js~QueryInterface.html) for a full list of what can be done. \ No newline at end of file From 796b6b7ddb6f3f5a9245bd2dd5f53ff2d6b57e68 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Wed, 20 May 2020 23:45:41 -0500 Subject: [PATCH 154/414] test(integration/instance): asyncify (#12285) --- test/integration/instance/decrement.test.js | 225 +++--- test/integration/instance/destroy.test.js | 514 +++++++------- test/integration/instance/increment.test.js | 213 +++--- test/integration/instance/reload.test.js | 370 +++++----- test/integration/instance/save.test.js | 734 +++++++++----------- test/integration/instance/to-json.test.js | 204 +++--- test/integration/instance/update.test.js | 480 ++++++------- test/integration/instance/values.test.js | 138 ++-- 8 files changed, 1309 insertions(+), 1569 deletions(-) diff --git a/test/integration/instance/decrement.test.js b/test/integration/instance/decrement.test.js index 438fc8fafe06..a38397066c35 100644 --- a/test/integration/instance/decrement.test.js +++ b/test/integration/instance/decrement.test.js @@ -20,7 +20,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -52,170 +52,137 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('decrement', () => { - beforeEach(function() { - return this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); + beforeEach(async function() { + await this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); - - return User.sync({ force: true }).then(() => { - return User.create({ number: 3 }).then(user => { - return sequelize.transaction().then(t => { - return user.decrement('number', { by: 2, transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].number).to.equal(3); - expect(users2[0].number).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); + + await User.sync({ force: true }); + const user = await User.create({ number: 3 }); + const t = await sequelize.transaction(); + await user.decrement('number', { by: 2, transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].number).to.equal(3); + expect(users2[0].number).to.equal(1); + await t.rollback(); }); } if (current.dialect.supports.returnValues.returning) { - it('supports returning', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement('aNumber', { by: 2 }).then(() => { - expect(user1.aNumber).to.be.equal(-2); - return user1.decrement('bNumber', { by: 2, returning: false }).then(user3 => { - expect(user3.bNumber).to.be.equal(0); - }); - }); - }); + it('supports returning', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement('aNumber', { by: 2 }); + expect(user1.aNumber).to.be.equal(-2); + const user3 = await user1.decrement('bNumber', { by: 2, returning: false }); + expect(user3.bNumber).to.be.equal(0); }); } - it('with array', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(-2); - }); - }); - }); + it('with array', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement(['aNumber'], { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(-2); }); - it('with single field', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement('aNumber', { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(-2); - }); - }); - }); + it('with single field', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement('aNumber', { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(-2); }); - it('with single field and no value', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement('aNumber').then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.be.equal(-1); - }); - }); - }); + it('with single field and no value', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement('aNumber'); + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.be.equal(-1); }); - it('should still work right with other concurrent updates', function() { - return this.User.findByPk(1).then(user1 => { - // Select the user again (simulating a concurrent query) - return this.User.findByPk(1).then(user2 => { - return user2.update({ - aNumber: user2.aNumber + 1 - }).then(() => { - return user1.decrement(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user5 => { - expect(user5.aNumber).to.be.equal(-1); - }); - }); - }); - }); + it('should still work right with other concurrent updates', async function() { + const user1 = await this.User.findByPk(1); + // Select the user again (simulating a concurrent query) + const user2 = await this.User.findByPk(1); + + await user2.update({ + aNumber: user2.aNumber + 1 }); + + await user1.decrement(['aNumber'], { by: 2 }); + const user5 = await this.User.findByPk(1); + expect(user5.aNumber).to.be.equal(-1); }); - it('should still work right with other concurrent increments', function() { - return this.User.findByPk(1).then(user1 => { - return Promise.all([ - user1.decrement(['aNumber'], { by: 2 }), - user1.decrement(['aNumber'], { by: 2 }), - user1.decrement(['aNumber'], { by: 2 }) - ]).then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.equal(-6); - }); - }); - }); + it('should still work right with other concurrent increments', async function() { + const user1 = await this.User.findByPk(1); + + await Promise.all([ + user1.decrement(['aNumber'], { by: 2 }), + user1.decrement(['aNumber'], { by: 2 }), + user1.decrement(['aNumber'], { by: 2 }) + ]); + + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.equal(-6); }); - it('with key value pair', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement({ 'aNumber': 1, 'bNumber': 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(-1); - expect(user3.bNumber).to.be.equal(-2); - }); - }); - }); + it('with key value pair', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement({ 'aNumber': 1, 'bNumber': 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(-1); + expect(user3.bNumber).to.be.equal(-2); }); - it('with negative value', function() { - return this.User.findByPk(1).then(user1 => { - return Promise.all([ - user1.decrement('aNumber', { by: -2 }), - user1.decrement(['aNumber', 'bNumber'], { by: -2 }), - user1.decrement({ 'aNumber': -1, 'bNumber': -2 }) - ]).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(+5); - expect(user3.bNumber).to.be.equal(+4); - }); - }); - }); + it('with negative value', async function() { + const user1 = await this.User.findByPk(1); + + await Promise.all([ + user1.decrement('aNumber', { by: -2 }), + user1.decrement(['aNumber', 'bNumber'], { by: -2 }), + user1.decrement({ 'aNumber': -1, 'bNumber': -2 }) + ]); + + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(+5); + expect(user3.bNumber).to.be.equal(+4); }); - it('with timestamps set to true', function() { + it('with timestamps set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return user.decrement('aNumber', { by: 1 }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await user.decrement('aNumber', { by: 1 }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); }); - it('with timestamps set to true and options.silent set to true', function() { + it('with timestamps set to true and options.silent set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return user.decrement('aNumber', { by: 1, silent: true }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await user.decrement('aNumber', { by: 1, silent: true }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); }); }); }); diff --git a/test/integration/instance/destroy.test.js b/test/integration/instance/destroy.test.js index 58713466e2d4..c912fb24aeae 100644 --- a/test/integration/instance/destroy.test.js +++ b/test/integration/instance/destroy.test.js @@ -11,272 +11,241 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('destroy', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return sequelize.transaction().then(t => { - return user.destroy({ transaction: t }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(1); - expect(count2).to.equal(0); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + + await User.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await user.destroy({ transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(1); + expect(count2).to.equal(0); + await t.rollback(); }); } - it('does not set the deletedAt date in subsequent destroys if dao is paranoid', function() { + it('does not set the deletedAt date in subsequent destroys if dao is paranoid', async function() { const UserDestroy = this.sequelize.define('UserDestroy', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT }, { paranoid: true }); - return UserDestroy.sync({ force: true }).then(() => { - return UserDestroy.create({ name: 'hallo', bio: 'welt' }).then(user => { - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - const deletedAt = user.deletedAt; - - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAt).to.eql(deletedAt); - }); - }); - }); - }); - }); - }); + await UserDestroy.sync({ force: true }); + const user = await UserDestroy.create({ name: 'hallo', bio: 'welt' }); + await user.destroy(); + await user.reload({ paranoid: false }); + const deletedAt = user.deletedAt; + + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAt).to.eql(deletedAt); }); - it('does not update deletedAt with custom default in subsequent destroys', function() { + it('does not update deletedAt with custom default in subsequent destroys', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, deletedAt: { type: Support.Sequelize.DATE, defaultValue: new Date(0) } }, { paranoid: true }); - let deletedAt; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - deletedAt = user.deletedAt; - expect(deletedAt).to.be.ok; - expect(deletedAt.getTime()).to.be.ok; - - return user.destroy(); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.be.ok; - expect(user.deletedAt.toISOString()).to.equal(deletedAt.toISOString()); + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' }); + + const user0 = await user1.destroy(); + const deletedAt = user0.deletedAt; + expect(deletedAt).to.be.ok; + expect(deletedAt.getTime()).to.be.ok; + + const user = await user0.destroy(); + expect(user).to.be.ok; + expect(user.deletedAt).to.be.ok; + expect(user.deletedAt.toISOString()).to.equal(deletedAt.toISOString()); }); - it('deletes a record from the database if dao is not paranoid', function() { + it('deletes a record from the database if dao is not paranoid', async function() { const UserDestroy = this.sequelize.define('UserDestroy', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT }); - return UserDestroy.sync({ force: true }).then(() => { - return UserDestroy.create({ name: 'hallo', bio: 'welt' }).then(u => { - return UserDestroy.findAll().then(users => { - expect(users.length).to.equal(1); - return u.destroy().then(() => { - return UserDestroy.findAll().then(users => { - expect(users.length).to.equal(0); - }); - }); - }); - }); - }); + await UserDestroy.sync({ force: true }); + const u = await UserDestroy.create({ name: 'hallo', bio: 'welt' }); + const users = await UserDestroy.findAll(); + expect(users.length).to.equal(1); + await u.destroy(); + const users0 = await UserDestroy.findAll(); + expect(users0.length).to.equal(0); }); - it('allows updating soft deleted instance', function() { + it('allows updating soft deleted instance', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING }, { paranoid: true }); - let deletedAt; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.deletedAt).to.be.ok; - deletedAt = user.deletedAt; - user.username = 'foo'; - return user.save(); - }).then(user => { - expect(user.username).to.equal('foo'); - expect(user.deletedAt).to.equal(deletedAt, 'should not update deletedAt'); - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' + }); + + const user1 = await user2.destroy(); + expect(user1.deletedAt).to.be.ok; + const deletedAt = user1.deletedAt; + user1.username = 'foo'; + const user0 = await user1.save(); + expect(user0.username).to.equal('foo'); + expect(user0.deletedAt).to.equal(deletedAt, 'should not update deletedAt'); + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { + username: 'foo' + } }); + + expect(user).to.be.ok; + expect(user.deletedAt).to.be.ok; }); - it('supports custom deletedAt field', function() { + it('supports custom deletedAt field', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, destroyTime: Support.Sequelize.DATE }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' + }); + + const user0 = await user1.destroy(); + expect(user0.destroyTime).to.be.ok; + expect(user0.deletedAt).to.not.be.ok; + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'username' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; + } }); + + expect(user).to.be.ok; + expect(user.destroyTime).to.be.ok; + expect(user.deletedAt).to.not.be.ok; }); - it('supports custom deletedAt database column', function() { + it('supports custom deletedAt database column', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, deletedAt: { type: Support.Sequelize.DATE, field: 'deleted_at' } }, { paranoid: true }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' + }); + + const user0 = await user1.destroy(); + expect(user0.dataValues.deletedAt).to.be.ok; + expect(user0.dataValues.deleted_at).to.not.be.ok; + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.deletedAt).to.be.ok; - expect(user.dataValues.deleted_at).to.not.be.ok; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'username' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.be.ok; - expect(user.deleted_at).to.not.be.ok; + } }); + + expect(user).to.be.ok; + expect(user.deletedAt).to.be.ok; + expect(user.deleted_at).to.not.be.ok; }); - it('supports custom deletedAt field and database column', function() { + it('supports custom deletedAt field and database column', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, destroyTime: { type: Support.Sequelize.DATE, field: 'destroy_time' } }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' + }); + + const user0 = await user1.destroy(); + expect(user0.dataValues.destroyTime).to.be.ok; + expect(user0.dataValues.destroy_time).to.not.be.ok; + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.destroyTime).to.be.ok; - expect(user.dataValues.destroy_time).to.not.be.ok; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'username' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.be.ok; - expect(user.destroy_time).to.not.be.ok; + } }); + + expect(user).to.be.ok; + expect(user.destroyTime).to.be.ok; + expect(user.destroy_time).to.not.be.ok; }); - it('persists other model changes when soft deleting', function() { + it('persists other model changes when soft deleting', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING }, { paranoid: true }); - let deletedAt; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - user.username = 'foo'; - return user.destroy(); - }).then(user => { - expect(user.username).to.equal('foo'); - expect(user.deletedAt).to.be.ok; - deletedAt = user.deletedAt; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(moment.utc(user.deletedAt).startOf('second').toISOString()) - .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); - expect(user.username).to.equal('foo'); - return user; - }).then(user => { - // update model and delete again - user.username = 'bar'; - return user.destroy(); - }).then(user => { - expect(moment.utc(user.deletedAt).startOf('second').toISOString()) - .to.equal(moment.utc(deletedAt).startOf('second').toISOString(), - 'should not updated deletedAt when destroying multiple times'); - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'bar' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(moment.utc(user.deletedAt).startOf('second').toISOString()) - .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); - expect(user.username).to.equal('bar'); + await ParanoidUser.sync({ force: true }); + + const user4 = await ParanoidUser.create({ + username: 'username' + }); + + user4.username = 'foo'; + const user3 = await user4.destroy(); + expect(user3.username).to.equal('foo'); + expect(user3.deletedAt).to.be.ok; + const deletedAt = user3.deletedAt; + + const user2 = await ParanoidUser.findOne({ + paranoid: false, + where: { + username: 'foo' + } + }); + + expect(user2).to.be.ok; + expect(moment.utc(user2.deletedAt).startOf('second').toISOString()) + .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); + expect(user2.username).to.equal('foo'); + const user1 = user2; + // update model and delete again + user1.username = 'bar'; + const user0 = await user1.destroy(); + expect(moment.utc(user0.deletedAt).startOf('second').toISOString()) + .to.equal(moment.utc(deletedAt).startOf('second').toISOString(), + 'should not updated deletedAt when destroying multiple times'); + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { + username: 'bar' + } }); + + expect(user).to.be.ok; + expect(moment.utc(user.deletedAt).startOf('second').toISOString()) + .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); + expect(user.username).to.equal('bar'); }); - it('allows sql logging of delete statements', function() { + it('allows sql logging of delete statements', async function() { const UserDelete = this.sequelize.define('UserDelete', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT @@ -284,22 +253,18 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const logging = sinon.spy(); - return UserDelete.sync({ force: true }).then(() => { - return UserDelete.create({ name: 'hallo', bio: 'welt' }).then(u => { - return UserDelete.findAll().then(users => { - expect(users.length).to.equal(1); - return u.destroy({ logging }); - }); - }); - }).then(() => { - expect(logging.callCount).to.equal(1, 'should call logging'); - const sql = logging.firstCall.args[0]; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('DELETE'); - }); + await UserDelete.sync({ force: true }); + const u = await UserDelete.create({ name: 'hallo', bio: 'welt' }); + const users = await UserDelete.findAll(); + expect(users.length).to.equal(1); + await u.destroy({ logging }); + expect(logging.callCount).to.equal(1, 'should call logging'); + const sql = logging.firstCall.args[0]; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('DELETE'); }); - it('allows sql logging of update statements', function() { + it('allows sql logging of update statements', async function() { const UserDelete = this.sequelize.define('UserDelete', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT @@ -307,22 +272,18 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const logging = sinon.spy(); - return UserDelete.sync({ force: true }).then(() => { - return UserDelete.create({ name: 'hallo', bio: 'welt' }).then(u => { - return UserDelete.findAll().then(users => { - expect(users.length).to.equal(1); - return u.destroy({ logging }); - }); - }); - }).then(() => { - expect(logging.callCount).to.equal(1, 'should call logging'); - const sql = logging.firstCall.args[0]; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('UPDATE'); - }); + await UserDelete.sync({ force: true }); + const u = await UserDelete.create({ name: 'hallo', bio: 'welt' }); + const users = await UserDelete.findAll(); + expect(users.length).to.equal(1); + await u.destroy({ logging }); + expect(logging.callCount).to.equal(1, 'should call logging'); + const sql = logging.firstCall.args[0]; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('UPDATE'); }); - it('should not call save hooks when soft deleting', function() { + it('should not call save hooks when soft deleting', async function() { const beforeSave = sinon.spy(); const afterSave = sinon.spy(); @@ -336,31 +297,28 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - // clear out calls from .create - beforeSave.resetHistory(); - afterSave.resetHistory(); - - return user.destroy(); - }).then(result => { - expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave'); - expect(afterSave.callCount).to.equal(0, 'should not call afterSave'); - return result; - }).then(user => { - // now try with `hooks: true` - return user.destroy({ hooks: true }); - }).then(result => { - expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave even if `hooks: true`'); - expect(afterSave.callCount).to.equal(0, 'should not call afterSave even if `hooks: true`'); - return result; + await ParanoidUser.sync({ force: true }); + + const user0 = await ParanoidUser.create({ + username: 'username' }); + + // clear out calls from .create + beforeSave.resetHistory(); + afterSave.resetHistory(); + + const result0 = await user0.destroy(); + expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave'); + expect(afterSave.callCount).to.equal(0, 'should not call afterSave'); + const user = result0; + const result = await user.destroy({ hooks: true }); + expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave even if `hooks: true`'); + expect(afterSave.callCount).to.equal(0, 'should not call afterSave even if `hooks: true`'); + + await result; }); - it('delete a record of multiple primary keys table', function() { + it('delete a record of multiple primary keys table', async function() { const MultiPrimary = this.sequelize.define('MultiPrimary', { bilibili: { type: Support.Sequelize.CHAR(2), @@ -373,33 +331,29 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return MultiPrimary.sync({ force: true }).then(() => { - return MultiPrimary.create({ bilibili: 'bl', guruguru: 'gu' }).then(() => { - return MultiPrimary.create({ bilibili: 'bl', guruguru: 'ru' }).then(m2 => { - return MultiPrimary.findAll().then(ms => { - expect(ms.length).to.equal(2); - return m2.destroy({ - logging(sql) { - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('DELETE'); - expect(sql).to.include('ru'); - expect(sql).to.include('bl'); - } - }).then(() => { - return MultiPrimary.findAll().then(ms => { - expect(ms.length).to.equal(1); - expect(ms[0].bilibili).to.equal('bl'); - expect(ms[0].guruguru).to.equal('gu'); - }); - }); - }); - }); - }); + await MultiPrimary.sync({ force: true }); + await MultiPrimary.create({ bilibili: 'bl', guruguru: 'gu' }); + const m2 = await MultiPrimary.create({ bilibili: 'bl', guruguru: 'ru' }); + const ms = await MultiPrimary.findAll(); + expect(ms.length).to.equal(2); + + await m2.destroy({ + logging(sql) { + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('DELETE'); + expect(sql).to.include('ru'); + expect(sql).to.include('bl'); + } }); + + const ms0 = await MultiPrimary.findAll(); + expect(ms0.length).to.equal(1); + expect(ms0[0].bilibili).to.equal('bl'); + expect(ms0[0].guruguru).to.equal('gu'); }); if (dialect.match(/^postgres/)) { - it('converts Infinity in where clause to a timestamp', function() { + it('converts Infinity in where clause to a timestamp', async function() { const Date = this.sequelize.define('Date', { date: { @@ -413,14 +367,12 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { paranoid: true }); - return this.sequelize.sync({ force: true }) - .then(() => { - return Date.build({ date: Infinity }) - .save() - .then(date => { - return date.destroy(); - }); - }); + await this.sequelize.sync({ force: true }); + + const date = await Date.build({ date: Infinity }) + .save(); + + await date.destroy(); }); } }); diff --git a/test/integration/instance/increment.test.js b/test/integration/instance/increment.test.js index 18dd46a29978..a50aba8fe9f6 100644 --- a/test/integration/instance/increment.test.js +++ b/test/integration/instance/increment.test.js @@ -20,7 +20,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -52,169 +52,132 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('increment', () => { - beforeEach(function() { - return this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); + beforeEach(async function() { + await this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); - - return User.sync({ force: true }).then(() => { - return User.create({ number: 1 }).then(user => { - return sequelize.transaction().then(t => { - return user.increment('number', { by: 2, transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].number).to.equal(1); - expect(users2[0].number).to.equal(3); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); + + await User.sync({ force: true }); + const user = await User.create({ number: 1 }); + const t = await sequelize.transaction(); + await user.increment('number', { by: 2, transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].number).to.equal(1); + expect(users2[0].number).to.equal(3); + await t.rollback(); }); } if (current.dialect.supports.returnValues.returning) { - it('supports returning', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment('aNumber', { by: 2 }).then(() => { - expect(user1.aNumber).to.be.equal(2); - return user1.increment('bNumber', { by: 2, returning: false }).then(user3 => { - expect(user3.bNumber).to.be.equal(0); - }); - }); - }); + it('supports returning', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment('aNumber', { by: 2 }); + expect(user1.aNumber).to.be.equal(2); + const user3 = await user1.increment('bNumber', { by: 2, returning: false }); + expect(user3.bNumber).to.be.equal(0); }); } - it('supports where conditions', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment(['aNumber'], { by: 2, where: { bNumber: 1 } }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(0); - }); - }); - }); + it('supports where conditions', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment(['aNumber'], { by: 2, where: { bNumber: 1 } }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(0); }); - it('with array', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(2); - }); - }); - }); + it('with array', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment(['aNumber'], { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(2); }); - it('with single field', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment('aNumber', { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(2); - }); - }); - }); + it('with single field', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment('aNumber', { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(2); }); - it('with single field and no value', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment('aNumber').then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.be.equal(1); - }); - }); - }); + it('with single field and no value', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment('aNumber'); + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.be.equal(1); }); - it('should still work right with other concurrent updates', function() { - return this.User.findByPk(1).then(user1 => { - // Select the user again (simulating a concurrent query) - return this.User.findByPk(1).then(user2 => { - return user2.update({ - aNumber: user2.aNumber + 1 - }).then(() => { - return user1.increment(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user5 => { - expect(user5.aNumber).to.be.equal(3); - }); - }); - }); - }); + it('should still work right with other concurrent updates', async function() { + const user1 = await this.User.findByPk(1); + // Select the user again (simulating a concurrent query) + const user2 = await this.User.findByPk(1); + + await user2.update({ + aNumber: user2.aNumber + 1 }); + + await user1.increment(['aNumber'], { by: 2 }); + const user5 = await this.User.findByPk(1); + expect(user5.aNumber).to.be.equal(3); }); - it('should still work right with other concurrent increments', function() { - return this.User.findByPk(1).then(user1 => { - return Promise.all([ - user1.increment(['aNumber'], { by: 2 }), - user1.increment(['aNumber'], { by: 2 }), - user1.increment(['aNumber'], { by: 2 }) - ]).then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.equal(6); - }); - }); - }); + it('should still work right with other concurrent increments', async function() { + const user1 = await this.User.findByPk(1); + + await Promise.all([ + user1.increment(['aNumber'], { by: 2 }), + user1.increment(['aNumber'], { by: 2 }), + user1.increment(['aNumber'], { by: 2 }) + ]); + + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.equal(6); }); - it('with key value pair', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment({ 'aNumber': 1, 'bNumber': 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(1); - expect(user3.bNumber).to.be.equal(2); - }); - }); - }); + it('with key value pair', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment({ 'aNumber': 1, 'bNumber': 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(1); + expect(user3.bNumber).to.be.equal(2); }); - it('with timestamps set to true', function() { + it('with timestamps set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; + await User.sync({ force: true }); + const user1 = await User.create({ aNumber: 1 }); + const oldDate = user1.get('updatedAt'); - return User.sync({ force: true }) - .then(() => User.create({ aNumber: 1 })) - .then(user => { - oldDate = user.get('updatedAt'); + this.clock.tick(1000); + const user0 = await user1.increment('aNumber', { by: 1 }); + const user = await user0.reload(); - this.clock.tick(1000); - return user.increment('aNumber', { by: 1 }); - }) - .then(user => user.reload()) - .then(user => { - return expect(user).to.have.property('updatedAt').afterTime(oldDate); - }); + await expect(user).to.have.property('updatedAt').afterTime(oldDate); }); - it('with timestamps set to true and options.silent set to true', function() { + it('with timestamps set to true and options.silent set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return user.increment('aNumber', { by: 1, silent: true }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await user.increment('aNumber', { by: 1, silent: true }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); }); }); }); diff --git a/test/integration/instance/reload.test.js b/test/integration/instance/reload.test.js index b7ff5cb233f2..89ed2b666e1c 100644 --- a/test/integration/instance/reload.test.js +++ b/test/integration/instance/reload.test.js @@ -21,7 +21,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -53,42 +53,32 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('reload', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return sequelize.transaction().then(t => { - return User.update({ username: 'bar' }, { where: { username: 'foo' }, transaction: t }).then(() => { - return user.reload().then(user => { - expect(user.username).to.equal('foo'); - return user.reload({ transaction: t }).then(user => { - expect(user.username).to.equal('bar'); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + + await User.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await User.update({ username: 'bar' }, { where: { username: 'foo' }, transaction: t }); + const user1 = await user.reload(); + expect(user1.username).to.equal('foo'); + const user0 = await user1.reload({ transaction: t }); + expect(user0.username).to.equal('bar'); + await t.rollback(); }); } - it('should return a reference to the same DAO instead of creating a new one', function() { - return this.User.create({ username: 'John Doe' }).then(originalUser => { - return originalUser.update({ username: 'Doe John' }).then(() => { - return originalUser.reload().then(updatedUser => { - expect(originalUser === updatedUser).to.be.true; - }); - }); - }); + it('should return a reference to the same DAO instead of creating a new one', async function() { + const originalUser = await this.User.create({ username: 'John Doe' }); + await originalUser.update({ username: 'Doe John' }); + const updatedUser = await originalUser.reload(); + expect(originalUser === updatedUser).to.be.true; }); it('should use default internal where', async function() { @@ -105,202 +95,183 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(user.get('id')).to.equal(primaryKey).and.not.equal(anotherUser.get('id')); }); - it('should update the values on all references to the DAO', function() { - return this.User.create({ username: 'John Doe' }).then(originalUser => { - return this.User.findByPk(originalUser.id).then(updater => { - return updater.update({ username: 'Doe John' }).then(() => { - // We used a different reference when calling update, so originalUser is now out of sync - expect(originalUser.username).to.equal('John Doe'); - return originalUser.reload().then(updatedUser => { - expect(originalUser.username).to.equal('Doe John'); - expect(updatedUser.username).to.equal('Doe John'); - }); - }); - }); - }); + it('should update the values on all references to the DAO', async function() { + const originalUser = await this.User.create({ username: 'John Doe' }); + const updater = await this.User.findByPk(originalUser.id); + await updater.update({ username: 'Doe John' }); + // We used a different reference when calling update, so originalUser is now out of sync + expect(originalUser.username).to.equal('John Doe'); + const updatedUser = await originalUser.reload(); + expect(originalUser.username).to.equal('Doe John'); + expect(updatedUser.username).to.equal('Doe John'); }); - it('should support updating a subset of attributes', function() { - return this.User.create({ + it('should support updating a subset of attributes', async function() { + const user1 = await this.User.create({ aNumber: 1, bNumber: 1 - }).then(user => { - return Promise.resolve(this.User.update({ - bNumber: 2 - }, { - where: { - id: user.get('id') - } - })).then(() => user); - }).then(user => { - return user.reload({ - attributes: ['bNumber'] - }); - }).then(user => { - expect(user.get('aNumber')).to.equal(1); - expect(user.get('bNumber')).to.equal(2); }); - }); - it('should update read only attributes as well (updatedAt)', function() { - return this.User.create({ username: 'John Doe' }).then(originalUser => { - this.originallyUpdatedAt = originalUser.updatedAt; - this.originalUser = originalUser; - - // Wait for a second, so updatedAt will actually be different - this.clock.tick(1000); - return this.User.findByPk(originalUser.id); - }).then(updater => { - return updater.update({ username: 'Doe John' }); - }).then(updatedUser => { - this.updatedUser = updatedUser; - return this.originalUser.reload(); - }).then(() => { - expect(this.originalUser.updatedAt).to.be.above(this.originallyUpdatedAt); - expect(this.updatedUser.updatedAt).to.be.above(this.originallyUpdatedAt); + await this.User.update({ + bNumber: 2 + }, { + where: { + id: user1.get('id') + } }); + + const user0 = user1; + + const user = await user0.reload({ + attributes: ['bNumber'] + }); + + expect(user.get('aNumber')).to.equal(1); + expect(user.get('bNumber')).to.equal(2); }); - it('should update the associations as well', function() { + it('should update read only attributes as well (updatedAt)', async function() { + const originalUser = await this.User.create({ username: 'John Doe' }); + this.originallyUpdatedAt = originalUser.updatedAt; + this.originalUser = originalUser; + + // Wait for a second, so updatedAt will actually be different + this.clock.tick(1000); + const updater = await this.User.findByPk(originalUser.id); + const updatedUser = await updater.update({ username: 'Doe John' }); + this.updatedUser = updatedUser; + await this.originalUser.reload(); + expect(this.originalUser.updatedAt).to.be.above(this.originallyUpdatedAt); + expect(this.updatedUser.updatedAt).to.be.above(this.originallyUpdatedAt); + }); + + it('should update the associations as well', async function() { const Book = this.sequelize.define('Book', { title: DataTypes.STRING }), Page = this.sequelize.define('Page', { content: DataTypes.TEXT }); Book.hasMany(Page); Page.belongsTo(Book); - return Book.sync({ force: true }).then(() => { - return Page.sync({ force: true }).then(() => { - return Book.create({ title: 'A very old book' }).then(book => { - return Page.create({ content: 'om nom nom' }).then(page => { - return book.setPages([page]).then(() => { - return Book.findOne({ - where: { id: book.id }, - include: [Page] - }).then(leBook => { - return page.update({ content: 'something totally different' }).then(page => { - expect(leBook.Pages.length).to.equal(1); - expect(leBook.Pages[0].content).to.equal('om nom nom'); - expect(page.content).to.equal('something totally different'); - return leBook.reload().then(leBook => { - expect(leBook.Pages.length).to.equal(1); - expect(leBook.Pages[0].content).to.equal('something totally different'); - expect(page.content).to.equal('something totally different'); - }); - }); - }); - }); - }); - }); - }); + await Book.sync({ force: true }); + await Page.sync({ force: true }); + const book = await Book.create({ title: 'A very old book' }); + const page = await Page.create({ content: 'om nom nom' }); + await book.setPages([page]); + + const leBook = await Book.findOne({ + where: { id: book.id }, + include: [Page] }); + + const page0 = await page.update({ content: 'something totally different' }); + expect(leBook.Pages.length).to.equal(1); + expect(leBook.Pages[0].content).to.equal('om nom nom'); + expect(page0.content).to.equal('something totally different'); + const leBook0 = await leBook.reload(); + expect(leBook0.Pages.length).to.equal(1); + expect(leBook0.Pages[0].content).to.equal('something totally different'); + expect(page0.content).to.equal('something totally different'); }); - it('should update internal options of the instance', function() { + it('should update internal options of the instance', async function() { const Book = this.sequelize.define('Book', { title: DataTypes.STRING }), Page = this.sequelize.define('Page', { content: DataTypes.TEXT }); Book.hasMany(Page); Page.belongsTo(Book); - return Book.sync({ force: true }).then(() => { - return Page.sync({ force: true }).then(() => { - return Book.create({ title: 'A very old book' }).then(book => { - return Page.create().then(page => { - return book.setPages([page]).then(() => { - return Book.findOne({ - where: { id: book.id } - }).then(leBook => { - const oldOptions = leBook._options; - return leBook.reload({ - include: [Page] - }).then(leBook => { - expect(oldOptions).not.to.equal(leBook._options); - expect(leBook._options.include.length).to.equal(1); - expect(leBook.Pages.length).to.equal(1); - expect(leBook.get({ plain: true }).Pages.length).to.equal(1); - }); - }); - }); - }); - }); - }); + await Book.sync({ force: true }); + await Page.sync({ force: true }); + const book = await Book.create({ title: 'A very old book' }); + const page = await Page.create(); + await book.setPages([page]); + + const leBook = await Book.findOne({ + where: { id: book.id } }); - }); - it('should return an error when reload fails', function() { - return this.User.create({ username: 'John Doe' }).then(user => { - return user.destroy().then(() => { - return expect(user.reload()).to.be.rejectedWith( - Sequelize.InstanceError, - 'Instance could not be reloaded because it does not exist anymore (find call returned null)' - ); - }); + const oldOptions = leBook._options; + + const leBook0 = await leBook.reload({ + include: [Page] }); + + expect(oldOptions).not.to.equal(leBook0._options); + expect(leBook0._options.include.length).to.equal(1); + expect(leBook0.Pages.length).to.equal(1); + expect(leBook0.get({ plain: true }).Pages.length).to.equal(1); + }); + + it('should return an error when reload fails', async function() { + const user = await this.User.create({ username: 'John Doe' }); + await user.destroy(); + + await expect(user.reload()).to.be.rejectedWith( + Sequelize.InstanceError, + 'Instance could not be reloaded because it does not exist anymore (find call returned null)' + ); }); - it('should set an association to null after deletion, 1-1', function() { + it('should set an association to null after deletion, 1-1', async function() { const Shoe = this.sequelize.define('Shoe', { brand: DataTypes.STRING }), Player = this.sequelize.define('Player', { name: DataTypes.STRING }); Player.hasOne(Shoe); Shoe.belongsTo(Player); - return this.sequelize.sync({ force: true }).then(() => { - return Shoe.create({ - brand: 'the brand', - Player: { - name: 'the player' - } - }, { include: [Player] }); - }).then(shoe => { - return Player.findOne({ - where: { id: shoe.Player.id }, - include: [Shoe] - }).then(lePlayer => { - expect(lePlayer.Shoe).not.to.be.null; - return lePlayer.Shoe.destroy().then(() => lePlayer); - }).then(lePlayer => { - return lePlayer.reload(); - }).then(lePlayer => { - expect(lePlayer.Shoe).to.be.null; - }); + await this.sequelize.sync({ force: true }); + + const shoe = await Shoe.create({ + brand: 'the brand', + Player: { + name: 'the player' + } + }, { include: [Player] }); + + const lePlayer1 = await Player.findOne({ + where: { id: shoe.Player.id }, + include: [Shoe] }); + + expect(lePlayer1.Shoe).not.to.be.null; + await lePlayer1.Shoe.destroy(); + const lePlayer0 = lePlayer1; + const lePlayer = await lePlayer0.reload(); + expect(lePlayer.Shoe).to.be.null; }); - it('should set an association to empty after all deletion, 1-N', function() { + it('should set an association to empty after all deletion, 1-N', async function() { const Team = this.sequelize.define('Team', { name: DataTypes.STRING }), Player = this.sequelize.define('Player', { name: DataTypes.STRING }); Team.hasMany(Player); Player.belongsTo(Team); - return this.sequelize.sync({ force: true }).then(() => { - return Team.create({ - name: 'the team', - Players: [{ - name: 'the player1' - }, { - name: 'the player2' - }] - }, { include: [Player] }); - }).then(team => { - return Team.findOne({ - where: { id: team.id }, - include: [Player] - }).then(leTeam => { - expect(leTeam.Players).not.to.be.empty; - return leTeam.Players[1].destroy().then(() => { - return leTeam.Players[0].destroy(); - }).then(() => leTeam); - }).then(leTeam => { - return leTeam.reload(); - }).then(leTeam => { - expect(leTeam.Players).to.be.empty; - }); + await this.sequelize.sync({ force: true }); + + const team = await Team.create({ + name: 'the team', + Players: [{ + name: 'the player1' + }, { + name: 'the player2' + }] + }, { include: [Player] }); + + const leTeam1 = await Team.findOne({ + where: { id: team.id }, + include: [Player] }); + + expect(leTeam1.Players).not.to.be.empty; + await leTeam1.Players[1].destroy(); + await leTeam1.Players[0].destroy(); + const leTeam0 = leTeam1; + const leTeam = await leTeam0.reload(); + expect(leTeam.Players).to.be.empty; }); - it('should update the associations after one element deleted', function() { + it('should update the associations after one element deleted', async function() { const Team = this.sequelize.define('Team', { name: DataTypes.STRING }), Player = this.sequelize.define('Player', { name: DataTypes.STRING }); @@ -308,28 +279,27 @@ describe(Support.getTestDialectTeaser('Instance'), () => { Player.belongsTo(Team); - return this.sequelize.sync({ force: true }).then(() => { - return Team.create({ - name: 'the team', - Players: [{ - name: 'the player1' - }, { - name: 'the player2' - }] - }, { include: [Player] }); - }).then(team => { - return Team.findOne({ - where: { id: team.id }, - include: [Player] - }).then(leTeam => { - expect(leTeam.Players).to.have.length(2); - return leTeam.Players[0].destroy().then(() => leTeam); - }).then(leTeam => { - return leTeam.reload(); - }).then(leTeam => { - expect(leTeam.Players).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + + const team = await Team.create({ + name: 'the team', + Players: [{ + name: 'the player1' + }, { + name: 'the player2' + }] + }, { include: [Player] }); + + const leTeam1 = await Team.findOne({ + where: { id: team.id }, + include: [Player] }); + + expect(leTeam1.Players).to.have.length(2); + await leTeam1.Players[0].destroy(); + const leTeam0 = leTeam1; + const leTeam = await leTeam0.reload(); + expect(leTeam.Players).to.have.length(1); }); }); }); diff --git a/test/integration/instance/save.test.js b/test/integration/instance/save.test.js index 02a0ea623b29..6c5bc93b0f68 100644 --- a/test/integration/instance/save.test.js +++ b/test/integration/instance/save.test.js @@ -22,7 +22,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -54,83 +54,74 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('save', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.build({ username: 'foo' }).save({ transaction: t }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(0); - expect(count2).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.build({ username: 'foo' }).save({ transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(0); + expect(count2).to.equal(1); + await t.rollback(); }); } - it('only updates fields in passed array', function() { + it('only updates fields in passed array', async function() { const date = new Date(1990, 1, 1); - return this.User.create({ + const user = await this.User.create({ username: 'foo', touchedAt: new Date() - }).then(user => { - user.username = 'fizz'; - user.touchedAt = date; - - return user.save({ fields: ['username'] }).then(() => { - // re-select user - return this.User.findByPk(user.id).then(user2 => { - // name should have changed - expect(user2.username).to.equal('fizz'); - // bio should be unchanged - expect(user2.birthDate).not.to.equal(date); - }); - }); }); + + user.username = 'fizz'; + user.touchedAt = date; + + await user.save({ fields: ['username'] }); + // re-select user + const user2 = await this.User.findByPk(user.id); + // name should have changed + expect(user2.username).to.equal('fizz'); + // bio should be unchanged + expect(user2.birthDate).not.to.equal(date); }); - it('should work on a model with an attribute named length', function() { + it('should work on a model with an attribute named length', async function() { const Box = this.sequelize.define('box', { length: DataTypes.INTEGER, width: DataTypes.INTEGER, height: DataTypes.INTEGER }); - return Box.sync({ force: true }).then(() => { - return Box.create({ - length: 1, - width: 2, - height: 3 - }).then(box => { - return box.update({ - length: 4, - width: 5, - height: 6 - }); - }).then(() => { - return Box.findOne({}).then(box => { - expect(box.get('length')).to.equal(4); - expect(box.get('width')).to.equal(5); - expect(box.get('height')).to.equal(6); - }); - }); + await Box.sync({ force: true }); + + const box0 = await Box.create({ + length: 1, + width: 2, + height: 3 + }); + + await box0.update({ + length: 4, + width: 5, + height: 6 }); + + const box = await Box.findOne({}); + expect(box.get('length')).to.equal(4); + expect(box.get('width')).to.equal(5); + expect(box.get('height')).to.equal(6); }); - it('only validates fields in passed array', function() { - return this.User.build({ + it('only validates fields in passed array', async function() { + await this.User.build({ validateTest: 'cake', // invalid, but not saved validateCustom: '1' }).save({ @@ -139,7 +130,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('hooks', () => { - it('should update attributes added in hooks when default fields are used', function() { + it('should update attributes added in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -150,27 +141,26 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.set({ - name: 'B', - bio: 'B' - }).save(); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('B'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' }); + + await user0.set({ + name: 'B', + bio: 'B' + }).save(); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('B'); }); - it('should update attributes changed in hooks when default fields are used', function() { + it('should update attributes changed in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -181,28 +171,27 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'C'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.set({ - name: 'B', - bio: 'B', - email: 'B' - }).save(); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('C'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' }); + + await user0.set({ + name: 'B', + bio: 'B', + email: 'B' + }).save(); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('C'); }); - it('should validate attributes added in hooks when default fields are used', function() { + it('should validate attributes added in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -218,24 +207,23 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.set({ - name: 'B' - }).save()).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.set({ + name: 'B' + }).save()).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); - it('should validate attributes changed in hooks when default fields are used', function() { + it('should validate attributes changed in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -251,26 +239,25 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.set({ - name: 'B', - email: 'still.valid.email@gmail.com' - }).save()).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.set({ + name: 'B', + email: 'still.valid.email@gmail.com' + }).save()).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); }); - it('stores an entry in the database', function() { + it('stores an entry in the database', async function() { const username = 'user', User = this.User, user = this.User.build({ @@ -278,20 +265,17 @@ describe(Support.getTestDialectTeaser('Instance'), () => { touchedAt: new Date(1984, 8, 23) }); - return User.findAll().then(users => { - expect(users).to.have.length(0); - return user.save().then(() => { - return User.findAll().then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal(username); - expect(users[0].touchedAt).to.be.instanceof(Date); - expect(users[0].touchedAt).to.equalDate(new Date(1984, 8, 23)); - }); - }); - }); + const users = await User.findAll(); + expect(users).to.have.length(0); + await user.save(); + const users0 = await User.findAll(); + expect(users0).to.have.length(1); + expect(users0[0].username).to.equal(username); + expect(users0[0].touchedAt).to.be.instanceof(Date); + expect(users0[0].touchedAt).to.equalDate(new Date(1984, 8, 23)); }); - it('handles an entry with primaryKey of zero', function() { + it('handles an entry with primaryKey of zero', async function() { const username = 'user', newUsername = 'newUser', User2 = this.sequelize.define('User2', @@ -304,100 +288,84 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: { type: DataTypes.STRING } }); - return User2.sync().then(() => { - return User2.create({ id: 0, username }).then(user => { - expect(user).to.be.ok; - expect(user.id).to.equal(0); - expect(user.username).to.equal(username); - return User2.findByPk(0).then(user => { - expect(user).to.be.ok; - expect(user.id).to.equal(0); - expect(user.username).to.equal(username); - return user.update({ username: newUsername }).then(user => { - expect(user).to.be.ok; - expect(user.id).to.equal(0); - expect(user.username).to.equal(newUsername); - }); - }); - }); - }); + await User2.sync(); + const user = await User2.create({ id: 0, username }); + expect(user).to.be.ok; + expect(user.id).to.equal(0); + expect(user.username).to.equal(username); + const user1 = await User2.findByPk(0); + expect(user1).to.be.ok; + expect(user1.id).to.equal(0); + expect(user1.username).to.equal(username); + const user0 = await user1.update({ username: newUsername }); + expect(user0).to.be.ok; + expect(user0.id).to.equal(0); + expect(user0.username).to.equal(newUsername); }); - it('updates the timestamps', function() { + it('updates the timestamps', async function() { const now = new Date(); now.setMilliseconds(0); const user = this.User.build({ username: 'user' }); this.clock.tick(1000); - return user.save().then(savedUser => { - expect(savedUser).have.property('updatedAt').afterTime(now); + const savedUser = await user.save(); + expect(savedUser).have.property('updatedAt').afterTime(now); - this.clock.tick(1000); - return savedUser.save(); - }).then(updatedUser => { - expect(updatedUser).have.property('updatedAt').afterTime(now); - }); + this.clock.tick(1000); + const updatedUser = await savedUser.save(); + expect(updatedUser).have.property('updatedAt').afterTime(now); }); - it('does not update timestamps when passing silent=true', function() { - return this.User.create({ username: 'user' }).then(user => { - const updatedAt = user.updatedAt; + it('does not update timestamps when passing silent=true', async function() { + const user = await this.User.create({ username: 'user' }); + const updatedAt = user.updatedAt; - this.clock.tick(1000); - return expect(user.update({ - username: 'userman' - }, { - silent: true - })).to.eventually.have.property('updatedAt').equalTime(updatedAt); - }); + this.clock.tick(1000); + + await expect(user.update({ + username: 'userman' + }, { + silent: true + })).to.eventually.have.property('updatedAt').equalTime(updatedAt); }); - it('does not update timestamps when passing silent=true in a bulk update', function() { + it('does not update timestamps when passing silent=true in a bulk update', async function() { const data = [ { username: 'Paul' }, { username: 'Peter' } ]; - let updatedAtPeter, - updatedAtPaul; - - return this.User.bulkCreate(data).then(() => { - return this.User.findAll(); - }).then(users => { - updatedAtPaul = users[0].updatedAt; - updatedAtPeter = users[1].updatedAt; - }) - .then(() => { - this.clock.tick(150); - return this.User.update( - { aNumber: 1 }, - { where: {}, silent: true } - ); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users[0].updatedAt).to.equalTime(updatedAtPeter); - expect(users[1].updatedAt).to.equalTime(updatedAtPaul); - }); + + await this.User.bulkCreate(data); + const users0 = await this.User.findAll(); + const updatedAtPaul = users0[0].updatedAt; + const updatedAtPeter = users0[1].updatedAt; + this.clock.tick(150); + + await this.User.update( + { aNumber: 1 }, + { where: {}, silent: true } + ); + + const users = await this.User.findAll(); + expect(users[0].updatedAt).to.equalTime(updatedAtPeter); + expect(users[1].updatedAt).to.equalTime(updatedAtPaul); }); describe('when nothing changed', () => { - it('does not update timestamps', function() { - return this.User.create({ username: 'John' }).then(() => { - return this.User.findOne({ where: { username: 'John' } }).then(user => { - const updatedAt = user.updatedAt; - this.clock.tick(2000); - return user.save().then(newlySavedUser => { - expect(newlySavedUser.updatedAt).to.equalTime(updatedAt); - return this.User.findOne({ where: { username: 'John' } }).then(newlySavedUser => { - expect(newlySavedUser.updatedAt).to.equalTime(updatedAt); - }); - }); - }); - }); + it('does not update timestamps', async function() { + await this.User.create({ username: 'John' }); + const user = await this.User.findOne({ where: { username: 'John' } }); + const updatedAt = user.updatedAt; + this.clock.tick(2000); + const newlySavedUser = await user.save(); + expect(newlySavedUser.updatedAt).to.equalTime(updatedAt); + const newlySavedUser0 = await this.User.findOne({ where: { username: 'John' } }); + expect(newlySavedUser0.updatedAt).to.equalTime(updatedAt); }); - it('should not throw ER_EMPTY_QUERY if changed only virtual fields', function() { + it('should not throw ER_EMPTY_QUERY if changed only virtual fields', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING, bio: { @@ -407,55 +375,48 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { timestamps: false }); - return User.sync({ force: true }).then(() => - User.create({ name: 'John', bio: 'swag 1' }).then(user => user.update({ bio: 'swag 2' }).should.be.fulfilled) - ); + await User.sync({ force: true }); + const user = await User.create({ name: 'John', bio: 'swag 1' }); + await user.update({ bio: 'swag 2' }).should.be.fulfilled; }); }); - it('updates with function and column value', function() { - return this.User.create({ + it('updates with function and column value', async function() { + const user = await this.User.create({ aNumber: 42 - }).then(user => { - user.bNumber = this.sequelize.col('aNumber'); - user.username = this.sequelize.fn('upper', 'sequelize'); - return user.save().then(() => { - return this.User.findByPk(user.id).then(user2 => { - expect(user2.username).to.equal('SEQUELIZE'); - expect(user2.bNumber).to.equal(42); - }); - }); }); + + user.bNumber = this.sequelize.col('aNumber'); + user.username = this.sequelize.fn('upper', 'sequelize'); + await user.save(); + const user2 = await this.User.findByPk(user.id); + expect(user2.username).to.equal('SEQUELIZE'); + expect(user2.bNumber).to.equal(42); }); - it('updates with function that contains escaped dollar symbol', function() { - return this.User.create({}).then(user => { - user.username = this.sequelize.fn('upper', '$sequelize'); - return user.save().then(() => { - return this.User.findByPk(user.id).then(userAfterUpdate => { - expect(userAfterUpdate.username).to.equal('$SEQUELIZE'); - }); - }); - }); + it('updates with function that contains escaped dollar symbol', async function() { + const user = await this.User.create({}); + user.username = this.sequelize.fn('upper', '$sequelize'); + await user.save(); + const userAfterUpdate = await this.User.findByPk(user.id); + expect(userAfterUpdate.username).to.equal('$SEQUELIZE'); }); describe('without timestamps option', () => { - it("doesn't update the updatedAt column", function() { + it("doesn't update the updatedAt column", async function() { const User2 = this.sequelize.define('User2', { username: DataTypes.STRING, updatedAt: DataTypes.DATE }, { timestamps: false }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - // sqlite and mysql return undefined, whereas postgres returns null - expect([undefined, null]).to.include(johnDoe.updatedAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + // sqlite and mysql return undefined, whereas postgres returns null + expect([undefined, null]).to.include(johnDoe.updatedAt); }); }); describe('with custom timestamp options', () => { - it('updates the createdAt column if updatedAt is disabled', function() { + it('updates the createdAt column if updatedAt is disabled', async function() { const now = new Date(); this.clock.tick(1000); @@ -463,15 +424,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: DataTypes.STRING }, { updatedAt: false }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - expect(johnDoe.updatedAt).to.be.undefined; - expect(now).to.be.beforeTime(johnDoe.createdAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + expect(johnDoe.updatedAt).to.be.undefined; + expect(now).to.be.beforeTime(johnDoe.createdAt); }); - it('updates the updatedAt column if createdAt is disabled', function() { + it('updates the updatedAt column if createdAt is disabled', async function() { const now = new Date(); this.clock.tick(1000); @@ -479,15 +438,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: DataTypes.STRING }, { createdAt: false }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - expect(johnDoe.createdAt).to.be.undefined; - expect(now).to.be.beforeTime(johnDoe.updatedAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + expect(johnDoe.createdAt).to.be.undefined; + expect(now).to.be.beforeTime(johnDoe.updatedAt); }); - it('works with `allowNull: false` on createdAt and updatedAt columns', function() { + it('works with `allowNull: false` on createdAt and updatedAt columns', async function() { const User2 = this.sequelize.define('User2', { username: DataTypes.STRING, createdAt: { @@ -500,86 +457,88 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }, { timestamps: true }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - expect(johnDoe.createdAt).to.be.an.instanceof(Date); - expect( ! isNaN(johnDoe.createdAt.valueOf()) ).to.be.ok; - expect(johnDoe.createdAt).to.equalTime(johnDoe.updatedAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + expect(johnDoe.createdAt).to.be.an.instanceof(Date); + expect( ! isNaN(johnDoe.createdAt.valueOf()) ).to.be.ok; + expect(johnDoe.createdAt).to.equalTime(johnDoe.updatedAt); }); }); - it('should fail a validation upon creating', function() { - return this.User.create({ aNumber: 0, validateTest: 'hello' }).catch(err => { + it('should fail a validation upon creating', async function() { + try { + await this.User.create({ aNumber: 0, validateTest: 'hello' }); + } catch (err) { expect(err).to.exist; expect(err).to.be.instanceof(Object); expect(err.get('validateTest')).to.be.instanceof(Array); expect(err.get('validateTest')[0]).to.exist; expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); - }); + } }); - it('should fail a validation upon creating with hooks false', function() { - return this.User.create({ aNumber: 0, validateTest: 'hello' }, { hooks: false }).catch(err => { + it('should fail a validation upon creating with hooks false', async function() { + try { + await this.User.create({ aNumber: 0, validateTest: 'hello' }, { hooks: false }); + } catch (err) { expect(err).to.exist; expect(err).to.be.instanceof(Object); expect(err.get('validateTest')).to.be.instanceof(Array); expect(err.get('validateTest')[0]).to.exist; expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); - }); + } }); - it('should fail a validation upon building', function() { - return this.User.build({ aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' }).save() - .catch(err => { - expect(err).to.exist; - expect(err).to.be.instanceof(Object); - expect(err.get('validateCustom')).to.exist; - expect(err.get('validateCustom')).to.be.instanceof(Array); - expect(err.get('validateCustom')[0]).to.exist; - expect(err.get('validateCustom')[0].message).to.equal('Length failed.'); - }); + it('should fail a validation upon building', async function() { + try { + await this.User.build({ aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' }).save(); + } catch (err) { + expect(err).to.exist; + expect(err).to.be.instanceof(Object); + expect(err.get('validateCustom')).to.exist; + expect(err.get('validateCustom')).to.be.instanceof(Array); + expect(err.get('validateCustom')[0]).to.exist; + expect(err.get('validateCustom')[0].message).to.equal('Length failed.'); + } }); - it('should fail a validation when updating', function() { - return this.User.create({ aNumber: 0 }).then(user => { - return user.update({ validateTest: 'hello' }).catch(err => { - expect(err).to.exist; - expect(err).to.be.instanceof(Object); - expect(err.get('validateTest')).to.exist; - expect(err.get('validateTest')).to.be.instanceof(Array); - expect(err.get('validateTest')[0]).to.exist; - expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); - }); - }); + it('should fail a validation when updating', async function() { + const user = await this.User.create({ aNumber: 0 }); + + try { + await user.update({ validateTest: 'hello' }); + } catch (err) { + expect(err).to.exist; + expect(err).to.be.instanceof(Object); + expect(err.get('validateTest')).to.exist; + expect(err.get('validateTest')).to.be.instanceof(Array); + expect(err.get('validateTest')[0]).to.exist; + expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); + } }); - it('takes zero into account', function() { - return this.User.build({ aNumber: 0 }).save({ + it('takes zero into account', async function() { + const user = await this.User.build({ aNumber: 0 }).save({ fields: ['aNumber'] - }).then(user => { - expect(user.aNumber).to.equal(0); }); + + expect(user.aNumber).to.equal(0); }); - it('saves a record with no primary key', function() { + it('saves a record with no primary key', async function() { const HistoryLog = this.sequelize.define('HistoryLog', { someText: { type: DataTypes.STRING }, aNumber: { type: DataTypes.INTEGER }, aRandomId: { type: DataTypes.INTEGER } }); - return HistoryLog.sync().then(() => { - return HistoryLog.create({ someText: 'Some random text', aNumber: 3, aRandomId: 5 }).then(log => { - return log.update({ aNumber: 5 }).then(newLog => { - expect(newLog.aNumber).to.equal(5); - }); - }); - }); + await HistoryLog.sync(); + const log = await HistoryLog.create({ someText: 'Some random text', aNumber: 3, aRandomId: 5 }); + const newLog = await log.update({ aNumber: 5 }); + expect(newLog.aNumber).to.equal(5); }); describe('eagerly loaded objects', () => { - beforeEach(function() { + beforeEach(async function() { this.UserEager = this.sequelize.define('UserEagerLoadingSaves', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -593,113 +552,88 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.UserEager.hasMany(this.ProjectEager, { as: 'Projects', foreignKey: 'PoobahId' }); this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah', foreignKey: 'PoobahId' }); - return this.UserEager.sync({ force: true }).then(() => { - return this.ProjectEager.sync({ force: true }); - }); + await this.UserEager.sync({ force: true }); + + await this.ProjectEager.sync({ force: true }); }); - it('saves one object that has a collection of eagerly loaded objects', function() { - return this.UserEager.create({ username: 'joe', age: 1 }).then(user => { - return this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }).then(project1 => { - return this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }).then(project2 => { - return user.setProjects([project1, project2]).then(() => { - return this.UserEager.findOne({ where: { age: 1 }, include: [{ model: this.ProjectEager, as: 'Projects' }] }).then(user => { - expect(user.username).to.equal('joe'); - expect(user.age).to.equal(1); - expect(user.Projects).to.exist; - expect(user.Projects.length).to.equal(2); - - user.age = user.age + 1; // happy birthday joe - return user.save().then(user => { - expect(user.username).to.equal('joe'); - expect(user.age).to.equal(2); - expect(user.Projects).to.exist; - expect(user.Projects.length).to.equal(2); - }); - }); - }); - }); - }); - }); + it('saves one object that has a collection of eagerly loaded objects', async function() { + const user = await this.UserEager.create({ username: 'joe', age: 1 }); + const project1 = await this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }); + const project2 = await this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }); + await user.setProjects([project1, project2]); + const user1 = await this.UserEager.findOne({ where: { age: 1 }, include: [{ model: this.ProjectEager, as: 'Projects' }] }); + expect(user1.username).to.equal('joe'); + expect(user1.age).to.equal(1); + expect(user1.Projects).to.exist; + expect(user1.Projects.length).to.equal(2); + + user1.age = user1.age + 1; // happy birthday joe + const user0 = await user1.save(); + expect(user0.username).to.equal('joe'); + expect(user0.age).to.equal(2); + expect(user0.Projects).to.exist; + expect(user0.Projects.length).to.equal(2); }); - it('saves many objects that each a have collection of eagerly loaded objects', function() { - return this.UserEager.create({ username: 'bart', age: 20 }).then(bart => { - return this.UserEager.create({ username: 'lisa', age: 20 }).then(lisa => { - return this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }).then(detention1 => { - return this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }).then(detention2 => { - return this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }).then(exam1 => { - return this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }).then(exam2 => { - return bart.setProjects([detention1, detention2]).then(() => { - return lisa.setProjects([exam1, exam2]).then(() => { - return this.UserEager.findAll({ where: { age: 20 }, order: [['username', 'ASC']], include: [{ model: this.ProjectEager, as: 'Projects' }] }).then(simpsons => { - expect(simpsons.length).to.equal(2); - - const _bart = simpsons[0]; - const _lisa = simpsons[1]; - - expect(_bart.Projects).to.exist; - expect(_lisa.Projects).to.exist; - expect(_bart.Projects.length).to.equal(2); - expect(_lisa.Projects.length).to.equal(2); - - _bart.age = _bart.age + 1; // happy birthday bart - off to Moe's - - return _bart.save().then(savedbart => { - expect(savedbart.username).to.equal('bart'); - expect(savedbart.age).to.equal(21); - - _lisa.username = 'lsimpson'; - - return _lisa.save().then(savedlisa => { - expect(savedlisa.username).to.equal('lsimpson'); - expect(savedlisa.age).to.equal(20); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('saves many objects that each a have collection of eagerly loaded objects', async function() { + const bart = await this.UserEager.create({ username: 'bart', age: 20 }); + const lisa = await this.UserEager.create({ username: 'lisa', age: 20 }); + const detention1 = await this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }); + const detention2 = await this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }); + const exam1 = await this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }); + const exam2 = await this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }); + await bart.setProjects([detention1, detention2]); + await lisa.setProjects([exam1, exam2]); + const simpsons = await this.UserEager.findAll({ where: { age: 20 }, order: [['username', 'ASC']], include: [{ model: this.ProjectEager, as: 'Projects' }] }); + expect(simpsons.length).to.equal(2); + + const _bart = simpsons[0]; + const _lisa = simpsons[1]; + + expect(_bart.Projects).to.exist; + expect(_lisa.Projects).to.exist; + expect(_bart.Projects.length).to.equal(2); + expect(_lisa.Projects.length).to.equal(2); + + _bart.age = _bart.age + 1; // happy birthday bart - off to Moe's + + const savedbart = await _bart.save(); + expect(savedbart.username).to.equal('bart'); + expect(savedbart.age).to.equal(21); + + _lisa.username = 'lsimpson'; + + const savedlisa = await _lisa.save(); + expect(savedlisa.username).to.equal('lsimpson'); + expect(savedlisa.age).to.equal(20); }); - it('saves many objects that each has one eagerly loaded object (to which they belong)', function() { - return this.UserEager.create({ username: 'poobah', age: 18 }).then(user => { - return this.ProjectEager.create({ title: 'homework', overdue_days: 10 }).then(homework => { - return this.ProjectEager.create({ title: 'party', overdue_days: 2 }).then(party => { - return user.setProjects([homework, party]).then(() => { - return this.ProjectEager.findAll({ include: [{ model: this.UserEager, as: 'Poobah' }] }).then(projects => { - expect(projects.length).to.equal(2); - expect(projects[0].Poobah).to.exist; - expect(projects[1].Poobah).to.exist; - expect(projects[0].Poobah.username).to.equal('poobah'); - expect(projects[1].Poobah.username).to.equal('poobah'); - - projects[0].title = 'partymore'; - projects[1].title = 'partymore'; - projects[0].overdue_days = 0; - projects[1].overdue_days = 0; - - return projects[0].save().then(() => { - return projects[1].save().then(() => { - return this.ProjectEager.findAll({ where: { title: 'partymore', overdue_days: 0 }, include: [{ model: this.UserEager, as: 'Poobah' }] }).then(savedprojects => { - expect(savedprojects.length).to.equal(2); - expect(savedprojects[0].Poobah).to.exist; - expect(savedprojects[1].Poobah).to.exist; - expect(savedprojects[0].Poobah.username).to.equal('poobah'); - expect(savedprojects[1].Poobah.username).to.equal('poobah'); - }); - }); - }); - }); - }); - }); - }); - }); + it('saves many objects that each has one eagerly loaded object (to which they belong)', async function() { + const user = await this.UserEager.create({ username: 'poobah', age: 18 }); + const homework = await this.ProjectEager.create({ title: 'homework', overdue_days: 10 }); + const party = await this.ProjectEager.create({ title: 'party', overdue_days: 2 }); + await user.setProjects([homework, party]); + const projects = await this.ProjectEager.findAll({ include: [{ model: this.UserEager, as: 'Poobah' }] }); + expect(projects.length).to.equal(2); + expect(projects[0].Poobah).to.exist; + expect(projects[1].Poobah).to.exist; + expect(projects[0].Poobah.username).to.equal('poobah'); + expect(projects[1].Poobah.username).to.equal('poobah'); + + projects[0].title = 'partymore'; + projects[1].title = 'partymore'; + projects[0].overdue_days = 0; + projects[1].overdue_days = 0; + + await projects[0].save(); + await projects[1].save(); + const savedprojects = await this.ProjectEager.findAll({ where: { title: 'partymore', overdue_days: 0 }, include: [{ model: this.UserEager, as: 'Poobah' }] }); + expect(savedprojects.length).to.equal(2); + expect(savedprojects[0].Poobah).to.exist; + expect(savedprojects[1].Poobah).to.exist; + expect(savedprojects[0].Poobah.username).to.equal('poobah'); + expect(savedprojects[1].Poobah.username).to.equal('poobah'); }); }); }); diff --git a/test/integration/instance/to-json.test.js b/test/integration/instance/to-json.test.js index cc4b4715ac54..cd904747facd 100644 --- a/test/integration/instance/to-json.test.js +++ b/test/integration/instance/to-json.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('toJSON', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, age: DataTypes.INTEGER, @@ -26,45 +26,41 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.User.hasMany(this.Project, { as: 'Projects', foreignKey: 'lovelyUserId' }); this.Project.belongsTo(this.User, { as: 'LovelyUser', foreignKey: 'lovelyUserId' }); - return this.User.sync({ force: true }).then(() => { - return this.Project.sync({ force: true }); - }); + await this.User.sync({ force: true }); + + await this.Project.sync({ force: true }); }); - it("doesn't return instance that isn't defined", function() { - return this.Project.create({ lovelyUserId: null }) - .then(project => { - return this.Project.findOne({ - where: { - id: project.id - }, - include: [ - { model: this.User, as: 'LovelyUser' } - ] - }); - }) - .then(project => { - const json = project.toJSON(); - expect(json.LovelyUser).to.be.equal(null); - }); + it("doesn't return instance that isn't defined", async function() { + const project0 = await this.Project.create({ lovelyUserId: null }); + + const project = await this.Project.findOne({ + where: { + id: project0.id + }, + include: [ + { model: this.User, as: 'LovelyUser' } + ] + }); + + const json = project.toJSON(); + expect(json.LovelyUser).to.be.equal(null); }); - it("doesn't return instances that aren't defined", function() { - return this.User.create({ username: 'cuss' }) - .then(user => { - return this.User.findOne({ - where: { - id: user.id - }, - include: [ - { model: this.Project, as: 'Projects' } - ] - }); - }) - .then(user => { - expect(user.Projects).to.be.instanceof(Array); - expect(user.Projects).to.be.length(0); - }); + it("doesn't return instances that aren't defined", async function() { + const user0 = await this.User.create({ username: 'cuss' }); + + const user = await this.User.findOne({ + where: { + id: user0.id + }, + include: [ + { model: this.Project, as: 'Projects' } + ] + }); + + expect(user.Projects).to.be.instanceof(Array); + expect(user.Projects).to.be.length(0); }); describe('build', () => { @@ -104,125 +100,123 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('create', () => { - it('returns an object containing all values', function() { - return this.User.create({ + it('returns an object containing all values', async function() { + const user = await this.User.create({ username: 'Adam', age: 22, level: -1, isUser: false, isAdmin: true - }).then(user => { - expect(user.toJSON()).to.deep.equal({ - id: user.get('id'), - username: 'Adam', - age: 22, - isUser: false, - isAdmin: true, - level: -1 - }); + }); + + expect(user.toJSON()).to.deep.equal({ + id: user.get('id'), + username: 'Adam', + age: 22, + isUser: false, + isAdmin: true, + level: -1 }); }); - it('returns a response that can be stringified', function() { - return this.User.create({ + it('returns a response that can be stringified', async function() { + const user = await this.User.create({ username: 'test.user', age: 99, isAdmin: true, isUser: false, level: null - }).then(user => { - expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"isAdmin":true,"isUser":false,"level":null}`); }); + + expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"isAdmin":true,"isUser":false,"level":null}`); }); - it('returns a response that can be stringified and then parsed', function() { - return this.User.create({ + it('returns a response that can be stringified and then parsed', async function() { + const user = await this.User.create({ username: 'test.user', age: 99, isAdmin: true, level: null - }).then(user => { - expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ - age: 99, - id: user.get('id'), - isAdmin: true, - isUser: false, - level: null, - username: 'test.user' - }); + }); + + expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ + age: 99, + id: user.get('id'), + isAdmin: true, + isUser: false, + level: null, + username: 'test.user' }); }); }); describe('find', () => { - it('returns an object containing all values', function() { - return this.User.create({ + it('returns an object containing all values', async function() { + const user0 = await this.User.create({ + username: 'Adam', + age: 22, + level: -1, + isUser: false, + isAdmin: true + }); + + const user = await this.User.findByPk(user0.get('id')); + expect(user.toJSON()).to.deep.equal({ + id: user.get('id'), username: 'Adam', age: 22, level: -1, isUser: false, isAdmin: true - }).then(user => this.User.findByPk(user.get('id'))).then(user => { - expect(user.toJSON()).to.deep.equal({ - id: user.get('id'), - username: 'Adam', - age: 22, - level: -1, - isUser: false, - isAdmin: true - }); }); }); - it('returns a response that can be stringified', function() { - return this.User.create({ + it('returns a response that can be stringified', async function() { + const user0 = await this.User.create({ username: 'test.user', age: 99, isAdmin: true, isUser: false - }).then(user => this.User.findByPk(user.get('id'))).then(user => { - expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"level":null,"isUser":false,"isAdmin":true}`); }); + + const user = await this.User.findByPk(user0.get('id')); + expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"level":null,"isUser":false,"isAdmin":true}`); }); - it('returns a response that can be stringified and then parsed', function() { - return this.User.create({ + it('returns a response that can be stringified and then parsed', async function() { + const user0 = await this.User.create({ username: 'test.user', age: 99, isAdmin: true - }).then(user => this.User.findByPk(user.get('id'))).then(user => { - expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ - id: user.get('id'), - username: 'test.user', - age: 99, - isAdmin: true, - isUser: false, - level: null - }); + }); + + const user = await this.User.findByPk(user0.get('id')); + expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ + id: user.get('id'), + username: 'test.user', + age: 99, + isAdmin: true, + isUser: false, + level: null }); }); }); - it('includes the eagerly loaded associations', function() { - return this.User.create({ username: 'fnord', age: 1, isAdmin: true }).then(user => { - return this.Project.create({ title: 'fnord' }).then(project => { - return user.setProjects([project]).then(() => { - return this.User.findAll({ include: [{ model: this.Project, as: 'Projects' }] }).then(users => { - const _user = users[0]; + it('includes the eagerly loaded associations', async function() { + const user = await this.User.create({ username: 'fnord', age: 1, isAdmin: true }); + const project = await this.Project.create({ title: 'fnord' }); + await user.setProjects([project]); + const users = await this.User.findAll({ include: [{ model: this.Project, as: 'Projects' }] }); + const _user = users[0]; - expect(_user.Projects).to.exist; - expect(JSON.parse(JSON.stringify(_user)).Projects).to.exist; + expect(_user.Projects).to.exist; + expect(JSON.parse(JSON.stringify(_user)).Projects).to.exist; - return this.Project.findAll({ include: [{ model: this.User, as: 'LovelyUser' }] }).then(projects => { - const _project = projects[0]; + const projects = await this.Project.findAll({ include: [{ model: this.User, as: 'LovelyUser' }] }); + const _project = projects[0]; - expect(_project.LovelyUser).to.exist; - expect(JSON.parse(JSON.stringify(_project)).LovelyUser).to.exist; - }); - }); - }); - }); - }); + expect(_project.LovelyUser).to.exist; + expect(JSON.parse(JSON.stringify(_project)).LovelyUser).to.exist; }); }); }); diff --git a/test/integration/instance/update.test.js b/test/integration/instance/update.test.js index c9b1328ca702..497365f17667 100644 --- a/test/integration/instance/update.test.js +++ b/test/integration/instance/update.test.js @@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('update', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -58,59 +58,50 @@ describe(Support.getTestDialectTeaser('Instance'), () => { allowNull: true } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return sequelize.transaction().then(t => { - return user.update({ username: 'bar' }, { transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].username).to.equal('foo'); - expect(users2[0].username).to.equal('bar'); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + + await User.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await user.update({ username: 'bar' }, { transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].username).to.equal('foo'); + expect(users2[0].username).to.equal('bar'); + await t.rollback(); }); } - it('should update fields that are not specified on create', function() { + it('should update fields that are not specified on create', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }, { - fields: ['name', 'email'] - }).then(user => { - return user.update({ bio: 'swag' }); - }).then(user => { - return user.reload(); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - expect(user.get('bio')).to.equal('swag'); - }); + await User.sync({ force: true }); + + const user1 = await User.create({ + name: 'snafu', + email: 'email' + }, { + fields: ['name', 'email'] }); + + const user0 = await user1.update({ bio: 'swag' }); + const user = await user0.reload(); + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); + expect(user.get('bio')).to.equal('swag'); }); - it('should succeed in updating when values are unchanged (without timestamps)', function() { + it('should succeed in updating when values are unchanged (without timestamps)', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -119,27 +110,26 @@ describe(Support.getTestDialectTeaser('Instance'), () => { timestamps: false }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }, { - fields: ['name', 'email'] - }).then(user => { - return user.update({ - name: 'snafu', - email: 'email' - }); - }).then(user => { - return user.reload(); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - }); + await User.sync({ force: true }); + + const user1 = await User.create({ + name: 'snafu', + email: 'email' + }, { + fields: ['name', 'email'] }); + + const user0 = await user1.update({ + name: 'snafu', + email: 'email' + }); + + const user = await user0.reload(); + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); }); - it('should update timestamps with milliseconds', function() { + it('should update timestamps with milliseconds', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -152,54 +142,48 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.tick(2100); //move the clock forward 2100 ms. - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }).then(user => { - return user.reload(); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - const testDate = new Date(); - testDate.setTime(2100); - expect(user.get('createdAt')).to.equalTime(testDate); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'snafu', + email: 'email' }); + + const user = await user0.reload(); + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); + const testDate = new Date(); + testDate.setTime(2100); + expect(user.get('createdAt')).to.equalTime(testDate); }); - it('should only save passed attributes', function() { + it('should only save passed attributes', async function() { const user = this.User.build(); - return user.save().then(() => { - user.set('validateTest', 5); - expect(user.changed('validateTest')).to.be.ok; - return user.update({ - validateCustom: '1' - }); - }).then(() => { - expect(user.changed('validateTest')).to.be.ok; - expect(user.validateTest).to.be.equal(5); - }).then(() => { - return user.reload(); - }).then(() => { - expect(user.validateTest).to.not.be.equal(5); + await user.save(); + user.set('validateTest', 5); + expect(user.changed('validateTest')).to.be.ok; + + await user.update({ + validateCustom: '1' }); + + expect(user.changed('validateTest')).to.be.ok; + expect(user.validateTest).to.be.equal(5); + await user.reload(); + expect(user.validateTest).to.not.be.equal(5); }); - it('should save attributes affected by setters', function() { + it('should save attributes affected by setters', async function() { const user = this.User.build(); - return user.update({ validateSideEffect: 5 }).then(() => { - expect(user.validateSideEffect).to.be.equal(5); - }).then(() => { - return user.reload(); - }).then(() => { - expect(user.validateSideAffected).to.be.equal(10); - expect(user.validateSideEffect).not.to.be.ok; - }); + await user.update({ validateSideEffect: 5 }); + expect(user.validateSideEffect).to.be.equal(5); + await user.reload(); + expect(user.validateSideAffected).to.be.equal(10); + expect(user.validateSideEffect).not.to.be.ok; }); describe('hooks', () => { - it('should update attributes added in hooks when default fields are used', function() { + it('should update attributes added in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -210,27 +194,26 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.update({ - name: 'B', - bio: 'B' - }); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('B'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' + }); + + await user0.update({ + name: 'B', + bio: 'B' }); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('B'); }); - it('should update attributes changed in hooks when default fields are used', function() { + it('should update attributes changed in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -241,28 +224,27 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'C'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.update({ - name: 'B', - bio: 'B', - email: 'B' - }); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('C'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' + }); + + await user0.update({ + name: 'B', + bio: 'B', + email: 'B' }); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('C'); }); - it('should validate attributes added in hooks when default fields are used', function() { + it('should validate attributes added in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -278,24 +260,23 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.update({ - name: 'B' - })).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.update({ + name: 'B' + })).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); - it('should validate attributes changed in hooks when default fields are used', function() { + it('should validate attributes changed in hooks when default fields are used', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, @@ -311,153 +292,144 @@ describe(Support.getTestDialectTeaser('Instance'), () => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.update({ - name: 'B', - email: 'still.valid.email@gmail.com' - })).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.update({ + name: 'B', + email: 'still.valid.email@gmail.com' + })).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); }); - it('should not set attributes that are not specified by fields', function() { + it('should not set attributes that are not specified by fields', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }).then(user => { - return user.update({ - bio: 'heyo', - email: 'heho' - }, { - fields: ['bio'] - }); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - expect(user.get('bio')).to.equal('heyo'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'snafu', + email: 'email' }); - }); - it('updates attributes in the database', function() { - return this.User.create({ username: 'user' }).then(user => { - expect(user.username).to.equal('user'); - return user.update({ username: 'person' }).then(user => { - expect(user.username).to.equal('person'); - }); + const user = await user0.update({ + bio: 'heyo', + email: 'heho' + }, { + fields: ['bio'] }); + + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); + expect(user.get('bio')).to.equal('heyo'); }); - it('ignores unknown attributes', function() { - return this.User.create({ username: 'user' }).then(user => { - return user.update({ username: 'person', foo: 'bar' }).then(user => { - expect(user.username).to.equal('person'); - expect(user.foo).not.to.exist; - }); - }); + it('updates attributes in the database', async function() { + const user = await this.User.create({ username: 'user' }); + expect(user.username).to.equal('user'); + const user0 = await user.update({ username: 'person' }); + expect(user0.username).to.equal('person'); }); - it('ignores undefined attributes', function() { - return this.User.sync({ force: true }).then(() => { - return this.User.create({ username: 'user' }).then(user => { - return user.update({ username: undefined }).then(user => { - expect(user.username).to.equal('user'); - }); - }); - }); + it('ignores unknown attributes', async function() { + const user = await this.User.create({ username: 'user' }); + const user0 = await user.update({ username: 'person', foo: 'bar' }); + expect(user0.username).to.equal('person'); + expect(user0.foo).not.to.exist; + }); + + it('ignores undefined attributes', async function() { + await this.User.sync({ force: true }); + const user = await this.User.create({ username: 'user' }); + const user0 = await user.update({ username: undefined }); + expect(user0.username).to.equal('user'); }); - it('doesn\'t update primary keys or timestamps', function() { + it('doesn\'t update primary keys or timestamps', async function() { const User = this.sequelize.define(`User${ config.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, identifier: { type: DataTypes.STRING, primaryKey: true } }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - identifier: 'identifier' - }); - }).then(user => { - const oldCreatedAt = user.createdAt, - oldUpdatedAt = user.updatedAt, - oldIdentifier = user.identifier; - - this.clock.tick(1000); - return user.update({ - name: 'foobar', - createdAt: new Date(2000, 1, 1), - identifier: 'another identifier' - }).then(user => { - expect(new Date(user.createdAt)).to.equalDate(new Date(oldCreatedAt)); - expect(new Date(user.updatedAt)).to.not.equalTime(new Date(oldUpdatedAt)); - expect(user.identifier).to.equal(oldIdentifier); - }); + await User.sync({ force: true }); + + const user = await User.create({ + name: 'snafu', + identifier: 'identifier' + }); + + const oldCreatedAt = user.createdAt, + oldUpdatedAt = user.updatedAt, + oldIdentifier = user.identifier; + + this.clock.tick(1000); + + const user0 = await user.update({ + name: 'foobar', + createdAt: new Date(2000, 1, 1), + identifier: 'another identifier' }); + + expect(new Date(user0.createdAt)).to.equalDate(new Date(oldCreatedAt)); + expect(new Date(user0.updatedAt)).to.not.equalTime(new Date(oldUpdatedAt)); + expect(user0.identifier).to.equal(oldIdentifier); }); - it('stores and restores null values', function() { + it('stores and restores null values', async function() { const Download = this.sequelize.define('download', { startedAt: DataTypes.DATE, canceledAt: DataTypes.DATE, finishedAt: DataTypes.DATE }); - return Download.sync().then(() => { - return Download.create({ - startedAt: new Date() - }).then(download => { - expect(download.startedAt instanceof Date).to.be.true; - expect(download.canceledAt).to.not.be.ok; - expect(download.finishedAt).to.not.be.ok; - - return download.update({ - canceledAt: new Date() - }).then(download => { - expect(download.startedAt instanceof Date).to.be.true; - expect(download.canceledAt instanceof Date).to.be.true; - expect(download.finishedAt).to.not.be.ok; - - return Download.findAll({ - where: { finishedAt: null } - }).then(downloads => { - downloads.forEach(download => { - expect(download.startedAt instanceof Date).to.be.true; - expect(download.canceledAt instanceof Date).to.be.true; - expect(download.finishedAt).to.not.be.ok; - }); - }); - }); - }); + await Download.sync(); + + const download = await Download.create({ + startedAt: new Date() + }); + + expect(download.startedAt instanceof Date).to.be.true; + expect(download.canceledAt).to.not.be.ok; + expect(download.finishedAt).to.not.be.ok; + + const download0 = await download.update({ + canceledAt: new Date() + }); + + expect(download0.startedAt instanceof Date).to.be.true; + expect(download0.canceledAt instanceof Date).to.be.true; + expect(download0.finishedAt).to.not.be.ok; + + const downloads = await Download.findAll({ + where: { finishedAt: null } + }); + + downloads.forEach(download => { + expect(download.startedAt instanceof Date).to.be.true; + expect(download.canceledAt instanceof Date).to.be.true; + expect(download.finishedAt).to.not.be.ok; }); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.create({}).then(user => { - return user.update({ username: 'yolo' }, { logging: spy }).then(() => { - expect(spy.called).to.be.ok; - }); - }); + const user = await this.User.create({}); + await user.update({ username: 'yolo' }, { logging: spy }); + expect(spy.called).to.be.ok; }); }); }); diff --git a/test/integration/instance/values.test.js b/test/integration/instance/values.test.js index 1fac0f5b2017..6995eff1cebd 100644 --- a/test/integration/instance/values.test.js +++ b/test/integration/instance/values.test.js @@ -105,7 +105,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { expect(user.dataValues.email).not.to.be.ok; }); - it('allows use of sequelize.fn and sequelize.col in date and bool fields', function() { + it('allows use of sequelize.fn and sequelize.col in date and bool fields', async function() { const User = this.sequelize.define('User', { d: DataTypes.DATE, b: DataTypes.BOOLEAN, @@ -115,30 +115,26 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }, { timestamps: false }); - return User.sync({ force: true }).then(() => { - return User.create({}).then(user => { - // Create the user first to set the proper default values. PG does not support column references in insert, - // so we must create a record with the right value for always_false, then reference it in an update - let now = dialect === 'sqlite' ? this.sequelize.fn('', this.sequelize.fn('datetime', 'now')) : this.sequelize.fn('NOW'); - if (dialect === 'mssql') { - now = this.sequelize.fn('', this.sequelize.fn('getdate')); - } - user.set({ - d: now, - b: this.sequelize.col('always_false') - }); - - expect(user.get('d')).to.be.instanceof(Sequelize.Utils.Fn); - expect(user.get('b')).to.be.instanceof(Sequelize.Utils.Col); - - return user.save().then(() => { - return user.reload().then(() => { - expect(user.d).to.equalDate(new Date()); - expect(user.b).to.equal(false); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}); + // Create the user first to set the proper default values. PG does not support column references in insert, + // so we must create a record with the right value for always_false, then reference it in an update + let now = dialect === 'sqlite' ? this.sequelize.fn('', this.sequelize.fn('datetime', 'now')) : this.sequelize.fn('NOW'); + if (dialect === 'mssql') { + now = this.sequelize.fn('', this.sequelize.fn('getdate')); + } + user.set({ + d: now, + b: this.sequelize.col('always_false') }); + + expect(user.get('d')).to.be.instanceof(Sequelize.Utils.Fn); + expect(user.get('b')).to.be.instanceof(Sequelize.Utils.Col); + + await user.save(); + await user.reload(); + expect(user.d).to.equalDate(new Date()); + expect(user.b).to.equal(false); }); describe('includes', () => { @@ -288,7 +284,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { expect(product.toJSON()).to.deep.equal({ withTaxes: 1250, price: 1000, id: null }); }); - it('should work with save', function() { + it('should work with save', async function() { const Contact = this.sequelize.define('Contact', { first: { type: Sequelize.STRING }, last: { type: Sequelize.STRING }, @@ -304,18 +300,16 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - return this.sequelize.sync().then(() => { - const contact = Contact.build({ - first: 'My', - last: 'Name', - tags: ['yes', 'no'] - }); - expect(contact.get('tags')).to.deep.equal(['yes', 'no']); - - return contact.save().then(me => { - expect(me.get('tags')).to.deep.equal(['yes', 'no']); - }); + await this.sequelize.sync(); + const contact = Contact.build({ + first: 'My', + last: 'Name', + tags: ['yes', 'no'] }); + expect(contact.get('tags')).to.deep.equal(['yes', 'no']); + + const me = await contact.save(); + expect(me.get('tags')).to.deep.equal(['yes', 'no']); }); describe('plain', () => { @@ -432,22 +426,18 @@ describe(Support.getTestDialectTeaser('DAO'), () => { }); describe('changed', () => { - it('should return false if object was built from database', function() { + it('should return false if object was built from database', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING } }); - return User.sync().then(() => { - return User.create({ name: 'Jan Meier' }).then(user => { - expect(user.changed('name')).to.be.false; - expect(user.changed()).not.to.be.ok; - }); - }).then(() => { - return User.bulkCreate([{ name: 'Jan Meier' }]).then(([user]) => { - expect(user.changed('name')).to.be.false; - expect(user.changed()).not.to.be.ok; - }); - }); + await User.sync(); + const user0 = await User.create({ name: 'Jan Meier' }); + expect(user0.changed('name')).to.be.false; + expect(user0.changed()).not.to.be.ok; + const [user] = await User.bulkCreate([{ name: 'Jan Meier' }]); + expect(user.changed('name')).to.be.false; + expect(user.changed()).not.to.be.ok; }); it('should return true if previous value is different', function() { @@ -463,27 +453,25 @@ describe(Support.getTestDialectTeaser('DAO'), () => { expect(user.changed()).to.be.ok; }); - it('should return false immediately after saving', function() { + it('should return false immediately after saving', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING } }); - return User.sync().then(() => { - const user = User.build({ - name: 'Jan Meier' - }); - user.set('name', 'Mick Hansen'); - expect(user.changed('name')).to.be.true; - expect(user.changed()).to.be.ok; - - return user.save().then(() => { - expect(user.changed('name')).to.be.false; - expect(user.changed()).not.to.be.ok; - }); + await User.sync(); + const user = User.build({ + name: 'Jan Meier' }); + user.set('name', 'Mick Hansen'); + expect(user.changed('name')).to.be.true; + expect(user.changed()).to.be.ok; + + await user.save(); + expect(user.changed('name')).to.be.false; + expect(user.changed()).not.to.be.ok; }); - it('should be available to a afterUpdate hook', function() { + it('should be available to a afterUpdate hook', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING } }); @@ -494,20 +482,20 @@ describe(Support.getTestDialectTeaser('DAO'), () => { return; }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'Ford Prefect' - }); - }).then(user => { - return user.update({ - name: 'Arthur Dent' - }); - }).then(user => { - expect(changed).to.be.ok; - expect(changed.length).to.be.ok; - expect(changed).to.include('name'); - expect(user.changed()).not.to.be.ok; + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'Ford Prefect' }); + + const user = await user0.update({ + name: 'Arthur Dent' + }); + + expect(changed).to.be.ok; + expect(changed.length).to.be.ok; + expect(changed).to.include('name'); + expect(user.changed()).not.to.be.ok; }); }); From 35af68389393d36edec48a96cdba31f98f30705d Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Thu, 21 May 2020 00:42:45 -0500 Subject: [PATCH 155/414] test(integration/model): asyncify (#12287) --- test/integration/model/attributes.test.js | 132 +- .../model/attributes/field.test.js | 550 +++-- .../model/attributes/types.test.js | 151 +- test/integration/model/bulk-create.test.js | 1134 +++++----- .../model/bulk-create/include.test.js | 775 +++---- test/integration/model/count.test.js | 229 +- test/integration/model/create.test.js | 1429 ++++++------ test/integration/model/create/include.test.js | 462 ++-- test/integration/model/findAll.test.js | 1929 ++++++++--------- test/integration/model/findAll/group.test.js | 119 +- .../model/findAll/groupedLimit.test.js | 250 +-- test/integration/model/findAll/order.test.js | 68 +- .../model/findAll/separate.test.js | 92 +- test/integration/model/findOne.test.js | 1070 +++++---- test/integration/model/findOrBuild.test.js | 60 +- test/integration/model/geography.test.js | 150 +- test/integration/model/geometry.test.js | 170 +- test/integration/model/increment.test.js | 310 ++- test/integration/model/json.test.js | 842 +++---- .../model/optimistic_locking.test.js | 87 +- test/integration/model/paranoid.test.js | 91 +- test/integration/model/schema.test.js | 672 +++--- test/integration/model/scope.test.js | 94 +- .../integration/model/scope/aggregate.test.js | 94 +- .../model/scope/associations.test.js | 384 ++-- test/integration/model/scope/count.test.js | 80 +- test/integration/model/scope/destroy.test.js | 95 +- test/integration/model/scope/find.test.js | 126 +- .../model/scope/findAndCountAll.test.js | 75 +- test/integration/model/scope/merge.test.js | 78 +- test/integration/model/scope/update.test.js | 99 +- test/integration/model/searchPath.test.js | 625 +++--- test/integration/model/sum.test.js | 30 +- test/integration/model/sync.test.js | 430 ++-- test/integration/model/update.test.js | 202 +- test/integration/model/upsert.test.js | 684 +++--- 36 files changed, 6618 insertions(+), 7250 deletions(-) diff --git a/test/integration/model/attributes.test.js b/test/integration/model/attributes.test.js index e5aa22ca8fb0..99dd66e07ebc 100644 --- a/test/integration/model/attributes.test.js +++ b/test/integration/model/attributes.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('attributes', () => { describe('set', () => { - it('should only be called once when used on a join model called with an association getter', function() { + it('should only be called once when used on a join model called with an association getter', async function() { let callCount = 0; this.Student = this.sequelize.define('student', { @@ -44,33 +44,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Student.belongsToMany(this.Course, { through: this.Score, foreignKey: 'StudentId' }); this.Course.belongsToMany(this.Student, { through: this.Score, foreignKey: 'CourseId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Student.create({ no: 1, name: 'ryan' }), - this.Course.create({ no: 100, name: 'history' }) - ]).then(([student, course]) => { - return student.addCourse(course, { through: { score: 98, test_value: 1000 } }); - }).then(() => { - expect(callCount).to.equal(1); - return this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }).then(score => { - expect(score.test_value).to.equal(1001); - }); - }) - .then(() => { - return Promise.all([ - this.Student.build({ no: 1 }).getCourses({ where: { no: 100 } }), - this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }) - ]); - }) - .then(([courses, score]) => { - expect(score.test_value).to.equal(1001); - expect(courses[0].score.toJSON().test_value).to.equal(1001); - expect(callCount).to.equal(1); - }); - }); + await this.sequelize.sync({ force: true }); + + const [student, course] = await Promise.all([ + this.Student.create({ no: 1, name: 'ryan' }), + this.Course.create({ no: 100, name: 'history' }) + ]); + + await student.addCourse(course, { through: { score: 98, test_value: 1000 } }); + expect(callCount).to.equal(1); + const score0 = await this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }); + expect(score0.test_value).to.equal(1001); + + const [courses, score] = await Promise.all([ + this.Student.build({ no: 1 }).getCourses({ where: { no: 100 } }), + this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }) + ]); + + expect(score.test_value).to.equal(1001); + expect(courses[0].score.toJSON().test_value).to.equal(1001); + expect(callCount).to.equal(1); }); - it('allows for an attribute to be called "toString"', function() { + it('allows for an attribute to be called "toString"', async function() { const Person = this.sequelize.define('person', { name: Sequelize.STRING, nick: Sequelize.STRING @@ -78,24 +74,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { timestamps: false }); - return this.sequelize.sync({ force: true }) - .then(() => Person.create({ name: 'Jozef', nick: 'Joe' })) - .then(() => Person.findOne({ - attributes: [ - 'nick', - ['name', 'toString'] - ], - where: { - name: 'Jozef' - } - })) - .then(person => { - expect(person.dataValues['toString']).to.equal('Jozef'); - expect(person.get('toString')).to.equal('Jozef'); - }); + await this.sequelize.sync({ force: true }); + await Person.create({ name: 'Jozef', nick: 'Joe' }); + + const person = await Person.findOne({ + attributes: [ + 'nick', + ['name', 'toString'] + ], + where: { + name: 'Jozef' + } + }); + + expect(person.dataValues['toString']).to.equal('Jozef'); + expect(person.get('toString')).to.equal('Jozef'); }); - it('allows for an attribute to be called "toString" with associations', function() { + it('allows for an attribute to be called "toString" with associations', async function() { const Person = this.sequelize.define('person', { name: Sequelize.STRING, nick: Sequelize.STRING @@ -107,41 +103,39 @@ describe(Support.getTestDialectTeaser('Model'), () => { Person.hasMany(Computer); - return this.sequelize.sync({ force: true }) - .then(() => Person.create({ name: 'Jozef', nick: 'Joe' })) - .then(person => person.createComputer({ hostname: 'laptop' })) - .then(() => Person.findAll({ - attributes: [ - 'nick', - ['name', 'toString'] - ], - include: { - model: Computer - }, - where: { - name: 'Jozef' - } - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].dataValues['toString']).to.equal('Jozef'); - expect(result[0].get('toString')).to.equal('Jozef'); - expect(result[0].get('computers')[0].hostname).to.equal('laptop'); - }); + await this.sequelize.sync({ force: true }); + const person = await Person.create({ name: 'Jozef', nick: 'Joe' }); + await person.createComputer({ hostname: 'laptop' }); + + const result = await Person.findAll({ + attributes: [ + 'nick', + ['name', 'toString'] + ], + include: { + model: Computer + }, + where: { + name: 'Jozef' + } + }); + + expect(result.length).to.equal(1); + expect(result[0].dataValues['toString']).to.equal('Jozef'); + expect(result[0].get('toString')).to.equal('Jozef'); + expect(result[0].get('computers')[0].hostname).to.equal('laptop'); }); }); describe('quote', () => { - it('allows for an attribute with dots', function() { + it('allows for an attribute with dots', async function() { const User = this.sequelize.define('user', { 'foo.bar.baz': Sequelize.TEXT }); - return this.sequelize.sync({ force: true }) - .then(() => User.findAll()) - .then(result => { - expect(result.length).to.equal(0); - }); + await this.sequelize.sync({ force: true }); + const result = await User.findAll(); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/integration/model/attributes/field.test.js b/test/integration/model/attributes/field.test.js index 8292d823a56c..e03aebfe4ee2 100644 --- a/test/integration/model/attributes/field.test.js +++ b/test/integration/model/attributes/field.test.js @@ -20,7 +20,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('attributes', () => { describe('field', () => { - beforeEach(function() { + beforeEach(async function() { const queryInterface = this.sequelize.getQueryInterface(); this.User = this.sequelize.define('user', { @@ -100,7 +100,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { through: 'userComments' }); - return Promise.all([ + await Promise.all([ queryInterface.createTable('users', { userId: { type: DataTypes.INTEGER, @@ -171,7 +171,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('primaryKey', () => { describe('in combination with allowNull', () => { - beforeEach(function() { + beforeEach(async function() { this.ModelUnderTest = this.sequelize.define('ModelUnderTest', { identifier: { primaryKey: true, @@ -180,151 +180,137 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.ModelUnderTest.sync({ force: true }); + await this.ModelUnderTest.sync({ force: true }); }); - it('sets the column to not allow null', function() { - return this + it('sets the column to not allow null', async function() { + const fields = await this .ModelUnderTest - .describe() - .then(fields => { - expect(fields.identifier).to.include({ allowNull: false }); - }); + .describe(); + + expect(fields.identifier).to.include({ allowNull: false }); }); }); - it('should support instance.destroy()', function() { - return this.User.create().then(user => { - return user.destroy(); - }); + it('should support instance.destroy()', async function() { + const user = await this.User.create(); + await user.destroy(); }); - it('should support Model.destroy()', function() { - return this.User.create().then(user => { - return this.User.destroy({ - where: { - id: user.get('id') - } - }); + it('should support Model.destroy()', async function() { + const user = await this.User.create(); + + await this.User.destroy({ + where: { + id: user.get('id') + } }); }); }); describe('field and attribute name is the same', () => { - beforeEach(function() { - return this.Comment.bulkCreate([ + beforeEach(async function() { + await this.Comment.bulkCreate([ { notes: 'Number one' }, { notes: 'Number two' } ]); }); - it('bulkCreate should work', function() { - return this.Comment.findAll().then(comments => { - expect(comments[0].notes).to.equal('Number one'); - expect(comments[1].notes).to.equal('Number two'); - }); + it('bulkCreate should work', async function() { + const comments = await this.Comment.findAll(); + expect(comments[0].notes).to.equal('Number one'); + expect(comments[1].notes).to.equal('Number two'); }); - it('find with where should work', function() { - return this.Comment.findAll({ where: { notes: 'Number one' } }).then(comments => { - expect(comments).to.have.length(1); - expect(comments[0].notes).to.equal('Number one'); - }); + it('find with where should work', async function() { + const comments = await this.Comment.findAll({ where: { notes: 'Number one' } }); + expect(comments).to.have.length(1); + expect(comments[0].notes).to.equal('Number one'); }); - it('reload should work', function() { - return this.Comment.findByPk(1).then(comment => { - return comment.reload(); - }); + it('reload should work', async function() { + const comment = await this.Comment.findByPk(1); + await comment.reload(); }); - it('save should work', function() { - return this.Comment.create({ notes: 'my note' }).then(comment => { - comment.notes = 'new note'; - return comment.save(); - }).then(comment => { - return comment.reload(); - }).then(comment => { - expect(comment.notes).to.equal('new note'); - }); + it('save should work', async function() { + const comment1 = await this.Comment.create({ notes: 'my note' }); + comment1.notes = 'new note'; + const comment0 = await comment1.save(); + const comment = await comment0.reload(); + expect(comment.notes).to.equal('new note'); }); }); - it('increment should work', function() { - return this.Comment.destroy({ truncate: true }) - .then(() => this.Comment.create({ note: 'oh boy, here I go again', likes: 23 })) - .then(comment => comment.increment('likes')) - .then(comment => comment.reload()) - .then(comment => { - expect(comment.likes).to.be.equal(24); - }); + it('increment should work', async function() { + await this.Comment.destroy({ truncate: true }); + const comment1 = await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 }); + const comment0 = await comment1.increment('likes'); + const comment = await comment0.reload(); + expect(comment.likes).to.be.equal(24); }); - it('decrement should work', function() { - return this.Comment.destroy({ truncate: true }) - .then(() => this.Comment.create({ note: 'oh boy, here I go again', likes: 23 })) - .then(comment => comment.decrement('likes')) - .then(comment => comment.reload()) - .then(comment => { - expect(comment.likes).to.be.equal(22); - }); + it('decrement should work', async function() { + await this.Comment.destroy({ truncate: true }); + const comment1 = await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 }); + const comment0 = await comment1.decrement('likes'); + const comment = await comment0.reload(); + expect(comment.likes).to.be.equal(22); }); - it('sum should work', function() { - return this.Comment.destroy({ truncate: true }) - .then(() => this.Comment.create({ note: 'oh boy, here I go again', likes: 23 })) - .then(() => this.Comment.sum('likes')) - .then(likes => { - expect(likes).to.be.equal(23); - }); + it('sum should work', async function() { + await this.Comment.destroy({ truncate: true }); + await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 }); + const likes = await this.Comment.sum('likes'); + expect(likes).to.be.equal(23); }); - it('should create, fetch and update with alternative field names from a simple model', function() { - return this.User.create({ + it('should create, fetch and update with alternative field names from a simple model', async function() { + await this.User.create({ name: 'Foobar' - }).then(() => { - return this.User.findOne({ - limit: 1 - }); - }).then(user => { - expect(user.get('name')).to.equal('Foobar'); - return user.update({ - name: 'Barfoo' - }); - }).then(() => { - return this.User.findOne({ - limit: 1 - }); - }).then(user => { - expect(user.get('name')).to.equal('Barfoo'); }); + + const user0 = await this.User.findOne({ + limit: 1 + }); + + expect(user0.get('name')).to.equal('Foobar'); + + await user0.update({ + name: 'Barfoo' + }); + + const user = await this.User.findOne({ + limit: 1 + }); + + expect(user.get('name')).to.equal('Barfoo'); }); - it('should bulk update', function() { + it('should bulk update', async function() { const Entity = this.sequelize.define('Entity', { strField: { type: Sequelize.STRING, field: 'str_field' } }); - return this.sequelize.sync({ force: true }).then(() => { - return Entity.create({ strField: 'foo' }); - }).then(() => { - return Entity.update( - { strField: 'bar' }, - { where: { strField: 'foo' } } - ); - }).then(() => { - return Entity.findOne({ - where: { - strField: 'bar' - } - }).then(entity => { - expect(entity).to.be.ok; - expect(entity.get('strField')).to.equal('bar'); - }); + await this.sequelize.sync({ force: true }); + await Entity.create({ strField: 'foo' }); + + await Entity.update( + { strField: 'bar' }, + { where: { strField: 'foo' } } + ); + + const entity = await Entity.findOne({ + where: { + strField: 'bar' + } }); + + expect(entity).to.be.ok; + expect(entity.get('strField')).to.equal('bar'); }); - it('should not contain the field properties after create', function() { + it('should not contain the field properties after create', async function() { const Model = this.sequelize.define('test', { id: { type: Sequelize.INTEGER, @@ -346,215 +332,203 @@ describe(Support.getTestDialectTeaser('Model'), () => { freezeTableName: true }); - return Model.sync({ force: true }).then(() => { - return Model.create({ title: 'test' }).then(data => { - expect(data.get('test_title')).to.be.an('undefined'); - expect(data.get('test_id')).to.be.an('undefined'); - }); - }); + await Model.sync({ force: true }); + const data = await Model.create({ title: 'test' }); + expect(data.get('test_title')).to.be.an('undefined'); + expect(data.get('test_id')).to.be.an('undefined'); }); - it('should make the aliased auto incremented primary key available after create', function() { - return this.User.create({ + it('should make the aliased auto incremented primary key available after create', async function() { + const user = await this.User.create({ name: 'Barfoo' - }).then(user => { - expect(user.get('id')).to.be.ok; }); + + expect(user.get('id')).to.be.ok; }); - it('should work with where on includes for find', function() { - return this.User.create({ + it('should work with where on includes for find', async function() { + const user = await this.User.create({ name: 'Barfoo' - }).then(user => { - return user.createTask({ - title: 'DatDo' - }); - }).then(task => { - return task.createComment({ - text: 'Comment' - }); - }).then(() => { - return this.Task.findOne({ - include: [ - { model: this.Comment }, - { model: this.User } - ], - where: { title: 'DatDo' } - }); - }).then(task => { - expect(task.get('title')).to.equal('DatDo'); - expect(task.get('comments')[0].get('text')).to.equal('Comment'); - expect(task.get('user')).to.be.ok; }); + + const task0 = await user.createTask({ + title: 'DatDo' + }); + + await task0.createComment({ + text: 'Comment' + }); + + const task = await this.Task.findOne({ + include: [ + { model: this.Comment }, + { model: this.User } + ], + where: { title: 'DatDo' } + }); + + expect(task.get('title')).to.equal('DatDo'); + expect(task.get('comments')[0].get('text')).to.equal('Comment'); + expect(task.get('user')).to.be.ok; }); - it('should work with where on includes for findAll', function() { - return this.User.create({ + it('should work with where on includes for findAll', async function() { + const user = await this.User.create({ name: 'Foobar' - }).then(user => { - return user.createTask({ - title: 'DoDat' - }); - }).then(task => { - return task.createComment({ - text: 'Comment' - }); - }).then(() => { - return this.User.findAll({ - include: [ - { model: this.Task, where: { title: 'DoDat' }, include: [ - { model: this.Comment } - ] } - ] - }); - }).then(users => { - users.forEach(user => { - expect(user.get('name')).to.be.ok; - expect(user.get('tasks')[0].get('title')).to.equal('DoDat'); - expect(user.get('tasks')[0].get('comments')).to.be.ok; - }); }); - }); - it('should work with increment', function() { - return this.User.create().then(user => { - return user.increment('taskCount'); + const task = await user.createTask({ + title: 'DoDat' + }); + + await task.createComment({ + text: 'Comment' + }); + + const users = await this.User.findAll({ + include: [ + { model: this.Task, where: { title: 'DoDat' }, include: [ + { model: this.Comment } + ] } + ] + }); + + users.forEach(user => { + expect(user.get('name')).to.be.ok; + expect(user.get('tasks')[0].get('title')).to.equal('DoDat'); + expect(user.get('tasks')[0].get('comments')).to.be.ok; }); }); - it('should work with a simple where', function() { - return this.User.create({ + it('should work with increment', async function() { + const user = await this.User.create(); + await user.increment('taskCount'); + }); + + it('should work with a simple where', async function() { + await this.User.create({ name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: 'Foobar' - } - }); - }).then(user => { - expect(user).to.be.ok; }); + + const user = await this.User.findOne({ + where: { + name: 'Foobar' + } + }); + + expect(user).to.be.ok; }); - it('should work with a where or', function() { - return this.User.create({ + it('should work with a where or', async function() { + await this.User.create({ name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: this.sequelize.or({ - name: 'Foobar' - }, { - name: 'Lollerskates' - }) - }); - }).then(user => { - expect(user).to.be.ok; }); + + const user = await this.User.findOne({ + where: this.sequelize.or({ + name: 'Foobar' + }, { + name: 'Lollerskates' + }) + }); + + expect(user).to.be.ok; }); - it('should work with bulkCreate and findAll', function() { - return this.User.bulkCreate([{ + it('should work with bulkCreate and findAll', async function() { + await this.User.bulkCreate([{ name: 'Abc' }, { name: 'Bcd' }, { name: 'Cde' - }]).then(() => { - return this.User.findAll(); - }).then(users => { - users.forEach(user => { - expect(['Abc', 'Bcd', 'Cde'].includes(user.get('name'))).to.be.true; - }); + }]); + + const users = await this.User.findAll(); + users.forEach(user => { + expect(['Abc', 'Bcd', 'Cde'].includes(user.get('name'))).to.be.true; }); }); - it('should support renaming of sequelize method fields', function() { + it('should support renaming of sequelize method fields', async function() { const Test = this.sequelize.define('test', { someProperty: Sequelize.VIRTUAL // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field }); - return this.sequelize.sync({ force: true }).then(() => { - return Test.create({}); - }).then(() => { - let findAttributes; - if (dialect === 'mssql') { - findAttributes = [ - Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someProperty"'), - [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] - ]; - } else { - findAttributes = [ - Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'), - [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] - ]; - } - - return Test.findAll({ - attributes: findAttributes - }); + await this.sequelize.sync({ force: true }); + await Test.create({}); + let findAttributes; + if (dialect === 'mssql') { + findAttributes = [ + Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someProperty"'), + [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] + ]; + } else { + findAttributes = [ + Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'), + [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] + ]; + } - }).then(tests => { - expect(tests[0].get('someProperty')).to.be.ok; - expect(tests[0].get('someProperty2')).to.be.ok; + const tests = await Test.findAll({ + attributes: findAttributes }); + + expect(tests[0].get('someProperty')).to.be.ok; + expect(tests[0].get('someProperty2')).to.be.ok; }); - it('should sync foreign keys with custom field names', function() { - return this.sequelize.sync({ force: true }) - .then(() => { - const attrs = this.Task.tableAttributes; - expect(attrs.user_id.references.model).to.equal('users'); - expect(attrs.user_id.references.key).to.equal('userId'); - }); + it('should sync foreign keys with custom field names', async function() { + await this.sequelize.sync({ force: true }); + const attrs = this.Task.tableAttributes; + expect(attrs.user_id.references.model).to.equal('users'); + expect(attrs.user_id.references.key).to.equal('userId'); }); - it('should find the value of an attribute with a custom field name', function() { - return this.User.create({ name: 'test user' }) - .then(() => { - return this.User.findOne({ where: { name: 'test user' } }); - }) - .then(user => { - expect(user.name).to.equal('test user'); - }); + it('should find the value of an attribute with a custom field name', async function() { + await this.User.create({ name: 'test user' }); + const user = await this.User.findOne({ where: { name: 'test user' } }); + expect(user.name).to.equal('test user'); }); - it('field names that are the same as property names should create, update, and read correctly', function() { - return this.Comment.create({ + it('field names that are the same as property names should create, update, and read correctly', async function() { + await this.Comment.create({ notes: 'Foobar' - }).then(() => { - return this.Comment.findOne({ - limit: 1 - }); - }).then(comment => { - expect(comment.get('notes')).to.equal('Foobar'); - return comment.update({ - notes: 'Barfoo' - }); - }).then(() => { - return this.Comment.findOne({ - limit: 1 - }); - }).then(comment => { - expect(comment.get('notes')).to.equal('Barfoo'); }); + + const comment0 = await this.Comment.findOne({ + limit: 1 + }); + + expect(comment0.get('notes')).to.equal('Foobar'); + + await comment0.update({ + notes: 'Barfoo' + }); + + const comment = await this.Comment.findOne({ + limit: 1 + }); + + expect(comment.get('notes')).to.equal('Barfoo'); }); - it('should work with a belongsTo association getter', function() { + it('should work with a belongsTo association getter', async function() { const userId = Math.floor(Math.random() * 100000); - return Promise.all([this.User.create({ + + const [user, task] = await Promise.all([this.User.create({ id: userId }), this.Task.create({ user_id: userId - })]).then(([user, task]) => { - return Promise.all([user, task.getUser()]); - }).then(([userA, userB]) => { - expect(userA.get('id')).to.equal(userB.get('id')); - expect(userA.get('id')).to.equal(userId); - expect(userB.get('id')).to.equal(userId); - }); + })]); + + const [userA, userB] = await Promise.all([user, task.getUser()]); + expect(userA.get('id')).to.equal(userB.get('id')); + expect(userA.get('id')).to.equal(userId); + expect(userB.get('id')).to.equal(userId); }); - it('should work with paranoid instance.destroy()', function() { + it('should work with paranoid instance.destroy()', async function() { const User = this.sequelize.define('User', { deletedAt: { type: DataTypes.DATE, @@ -565,23 +539,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return User.sync({ force: true }) - .then(() => { - return User.create(); - }) - .then(user => { - return user.destroy(); - }) - .then(() => { - this.clock.tick(1000); - return User.findAll(); - }) - .then(users => { - expect(users.length).to.equal(0); - }); + await User.sync({ force: true }); + const user = await User.create(); + await user.destroy(); + this.clock.tick(1000); + const users = await User.findAll(); + expect(users.length).to.equal(0); }); - it('should work with paranoid Model.destroy()', function() { + it('should work with paranoid Model.destroy()', async function() { const User = this.sequelize.define('User', { deletedAt: { type: DataTypes.DATE, @@ -592,31 +558,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.create().then(user => { - return User.destroy({ where: { id: user.get('id') } }); - }).then(() => { - return User.findAll().then(users => { - expect(users.length).to.equal(0); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create(); + await User.destroy({ where: { id: user.get('id') } }); + const users = await User.findAll(); + expect(users.length).to.equal(0); }); - it('should work with `belongsToMany` association `count`', function() { - return this.User.create({ + it('should work with `belongsToMany` association `count`', async function() { + const user = await this.User.create({ name: 'John' - }) - .then(user => user.countComments()) - .then(commentCount => expect(commentCount).to.equal(0)); + }); + + const commentCount = await user.countComments(); + await expect(commentCount).to.equal(0); }); - it('should work with `hasMany` association `count`', function() { - return this.User.create({ + it('should work with `hasMany` association `count`', async function() { + const user = await this.User.create({ name: 'John' - }) - .then(user => user.countTasks()) - .then(taskCount => expect(taskCount).to.equal(0)); + }); + + const taskCount = await user.countTasks(); + await expect(taskCount).to.equal(0); }); }); }); diff --git a/test/integration/model/attributes/types.test.js b/test/integration/model/attributes/types.test.js index 94541cbb3a4a..8da3d1235ddb 100644 --- a/test/integration/model/attributes/types.test.js +++ b/test/integration/model/attributes/types.test.js @@ -10,7 +10,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('attributes', () => { describe('types', () => { describe('VIRTUAL', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { storage: Sequelize.STRING, field1: { @@ -48,7 +48,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(sql).to.not.include('field2'); }; - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); it('should not be ignored in dataValues get', function() { @@ -60,14 +60,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.get()).to.deep.equal({ storage: 'field1_value', field1: 'field1_value', virtualWithDefault: 'cake', field2: 42, id: null }); }); - it('should be ignored in table creation', function() { - return this.sequelize.getQueryInterface().describeTable(this.User.tableName).then(fields => { - expect(Object.keys(fields).length).to.equal(2); - }); + it('should be ignored in table creation', async function() { + const fields = await this.sequelize.getQueryInterface().describeTable(this.User.tableName); + expect(Object.keys(fields).length).to.equal(2); }); - it('should be ignored in find, findAll and includes', function() { - return Promise.all([ + it('should be ignored in find, findAll and includes', async function() { + await Promise.all([ this.User.findOne({ logging: this.sqlAssert }), @@ -89,7 +88,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ]); }); - it('should allow me to store selected values', function() { + it('should allow me to store selected values', async function() { const Post = this.sequelize.define('Post', { text: Sequelize.TEXT, someBoolean: { @@ -97,98 +96,94 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.bulkCreate([{ text: 'text1' }, { text: 'text2' }]); - }).then(() => { - let boolQuery = 'EXISTS(SELECT 1) AS "someBoolean"'; - if (dialect === 'mssql') { - boolQuery = 'CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someBoolean"'; - } + await this.sequelize.sync({ force: true }); + await Post.bulkCreate([{ text: 'text1' }, { text: 'text2' }]); + let boolQuery = 'EXISTS(SELECT 1) AS "someBoolean"'; + if (dialect === 'mssql') { + boolQuery = 'CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someBoolean"'; + } - return Post.findOne({ attributes: ['id', 'text', Sequelize.literal(boolQuery)] }); - }).then(post => { - expect(post.get('someBoolean')).to.be.ok; - expect(post.get().someBoolean).to.be.ok; - }); + const post = await Post.findOne({ attributes: ['id', 'text', Sequelize.literal(boolQuery)] }); + expect(post.get('someBoolean')).to.be.ok; + expect(post.get().someBoolean).to.be.ok; }); - it('should be ignored in create and update', function() { - return this.User.create({ + it('should be ignored in create and update', async function() { + const user0 = await this.User.create({ field1: 'something' - }).then(user => { - // We already verified that the virtual is not added to the table definition, - // so if this succeeds, were good - - expect(user.virtualWithDefault).to.equal('cake'); - expect(user.storage).to.equal('something'); - return user.update({ - field1: 'something else' - }, { - fields: ['storage'] - }); - }).then(user => { - expect(user.virtualWithDefault).to.equal('cake'); - expect(user.storage).to.equal('something else'); }); + + // We already verified that the virtual is not added to the table definition, + // so if this succeeds, were good + + expect(user0.virtualWithDefault).to.equal('cake'); + expect(user0.storage).to.equal('something'); + + const user = await user0.update({ + field1: 'something else' + }, { + fields: ['storage'] + }); + + expect(user.virtualWithDefault).to.equal('cake'); + expect(user.storage).to.equal('something else'); }); - it('should be ignored in bulkCreate and and bulkUpdate', function() { - return this.User.bulkCreate([{ + it('should be ignored in bulkCreate and and bulkUpdate', async function() { + await this.User.bulkCreate([{ field1: 'something' }], { logging: this.sqlAssert - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users[0].storage).to.equal('something'); }); + + const users = await this.User.findAll(); + expect(users[0].storage).to.equal('something'); }); - it('should be able to exclude with attributes', function() { - return this.User.bulkCreate([{ + it('should be able to exclude with attributes', async function() { + await this.User.bulkCreate([{ field1: 'something' }], { logging: this.sqlAssert - }).then(() => { - return this.User.findAll({ - logging: this.sqlAssert - }); - }).then(users => { - const user = users[0].get(); + }); - expect(user.storage).to.equal('something'); - expect(user).to.include.all.keys(['field1', 'field2']); + const users0 = await this.User.findAll({ + logging: this.sqlAssert + }); - return this.User.findAll({ - attributes: { - exclude: ['field1'] - }, - logging: this.sqlAssert - }); - }).then(users => { - const user = users[0].get(); + const user0 = users0[0].get(); - expect(user.storage).to.equal('something'); - expect(user).not.to.include.all.keys(['field1']); - expect(user).to.include.all.keys(['field2']); + expect(user0.storage).to.equal('something'); + expect(user0).to.include.all.keys(['field1', 'field2']); + + const users = await this.User.findAll({ + attributes: { + exclude: ['field1'] + }, + logging: this.sqlAssert }); + + const user = users[0].get(); + + expect(user.storage).to.equal('something'); + expect(user).not.to.include.all.keys(['field1']); + expect(user).to.include.all.keys(['field2']); }); - it('should be able to include model with virtual attributes', function() { - return this.User.create({}).then(user => { - return user.createTask(); - }).then(() => { - return this.Task.findAll({ - include: [{ - attributes: ['field2', 'id'], - model: this.User - }] - }); - }).then(tasks => { - const user = tasks[0].user.get(); - - expect(user.field2).to.equal(42); + it('should be able to include model with virtual attributes', async function() { + const user0 = await this.User.create({}); + await user0.createTask(); + + const tasks = await this.Task.findAll({ + include: [{ + attributes: ['field2', 'id'], + model: this.User + }] }); + + const user = tasks[0].user.get(); + + expect(user.field2).to.equal(42); }); }); }); diff --git a/test/integration/model/bulk-create.test.js b/test/integration/model/bulk-create.test.js index faabff6ffbdf..7c92fe1eef74 100644 --- a/test/integration/model/bulk-create.test.js +++ b/test/integration/model/bulk-create.test.js @@ -11,71 +11,61 @@ const chai = require('chai'), current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: { - type: DataTypes.STRING, - field: 'secret_value' - }, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN, - uniqueName: { type: DataTypes.STRING, unique: true } - }); - this.Account = this.sequelize.define('Account', { - accountName: DataTypes.STRING - }); - this.Student = this.sequelize.define('Student', { - no: { type: DataTypes.INTEGER, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false } - }); - this.Car = this.sequelize.define('Car', { - plateNumber: { - type: DataTypes.STRING, - primaryKey: true, - field: 'plate_number' - }, - color: { - type: DataTypes.TEXT - } - }); - - return this.sequelize.sync({ force: true }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.User = this.sequelize.define('User', { + username: DataTypes.STRING, + secretValue: { + type: DataTypes.STRING, + field: 'secret_value' + }, + data: DataTypes.STRING, + intVal: DataTypes.INTEGER, + theDate: DataTypes.DATE, + aBool: DataTypes.BOOLEAN, + uniqueName: { type: DataTypes.STRING, unique: true } + }); + this.Account = this.sequelize.define('Account', { + accountName: DataTypes.STRING }); + this.Student = this.sequelize.define('Student', { + no: { type: DataTypes.INTEGER, primaryKey: true }, + name: { type: DataTypes.STRING, allowNull: false } + }); + this.Car = this.sequelize.define('Car', { + plateNumber: { + type: DataTypes.STRING, + primaryKey: true, + field: 'plate_number' + }, + color: { + type: DataTypes.TEXT + } + }); + + await this.sequelize.sync({ force: true }); }); describe('bulkCreate', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { + it('supports transactions', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - let transaction, count1; - return User.sync({ force: true }) - .then(() => this.sequelize.transaction()) - .then(t => { - transaction = t; - return User.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction }); - }) - .then(() => User.count()) - .then(count => { - count1 = count; - return User.count({ transaction }); - }) - .then(count2 => { - expect(count1).to.equal(0); - expect(count2).to.equal(2); - return transaction.rollback(); - }); + await User.sync({ force: true }); + const transaction = await this.sequelize.transaction(); + await User.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction }); + const count1 = await User.count(); + const count2 = await User.count({ transaction }); + expect(count1).to.equal(0); + expect(count2).to.equal(2); + await transaction.rollback(); }); } - it('should be able to set createdAt and updatedAt if using silent: true', function() { + it('should be able to set createdAt and updatedAt if using silent: true', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }, { @@ -89,39 +79,39 @@ describe(Support.getTestDialectTeaser('Model'), () => { updatedAt }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate(values, { - silent: true - }).then(() => { - return User.findAll({ - where: { - updatedAt: { - [Op.ne]: null - } - } - }).then(users => { - users.forEach(user => { - expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); - expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); - }); - }); - }); + await User.sync({ force: true }); + + await User.bulkCreate(values, { + silent: true + }); + + const users = await User.findAll({ + where: { + updatedAt: { + [Op.ne]: null + } + } + }); + + users.forEach(user => { + expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); + expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); }); }); - it('should not fail on validate: true and individualHooks: true', function() { + it('should not fail on validate: true and individualHooks: true', async function() { const User = this.sequelize.define('user', { name: Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { name: 'James' } - ], { validate: true, individualHooks: true }); - }); + await User.sync({ force: true }); + + await User.bulkCreate([ + { name: 'James' } + ], { validate: true, individualHooks: true }); }); - it('should not map instance dataValues to fields with individualHooks: true', function() { + it('should not map instance dataValues to fields with individualHooks: true', async function() { const User = this.sequelize.define('user', { name: Sequelize.STRING, type: { @@ -140,169 +130,153 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { name: 'James', type: 'A' }, - { name: 'Alan', type: 'Z' } - ], { individualHooks: true }); - }); + await User.sync({ force: true }); + + await User.bulkCreate([ + { name: 'James', type: 'A' }, + { name: 'Alan', type: 'Z' } + ], { individualHooks: true }); }); - it('should not insert NULL for unused fields', function() { + it('should not insert NULL for unused fields', async function() { const Beer = this.sequelize.define('Beer', { style: Sequelize.STRING, size: Sequelize.INTEGER }); - return Beer.sync({ force: true }).then(() => { - return Beer.bulkCreate([{ - style: 'ipa' - }], { - logging(sql) { - if (dialect === 'postgres') { - expect(sql).to.include('INSERT INTO "Beers" ("id","style","createdAt","updatedAt") VALUES (DEFAULT'); - } else if (dialect === 'mssql') { - expect(sql).to.include('INSERT INTO [Beers] ([style],[createdAt],[updatedAt]) '); - } else { // mysql, sqlite - expect(sql).to.include('INSERT INTO `Beers` (`id`,`style`,`createdAt`,`updatedAt`) VALUES (NULL'); - } + await Beer.sync({ force: true }); + + await Beer.bulkCreate([{ + style: 'ipa' + }], { + logging(sql) { + if (dialect === 'postgres') { + expect(sql).to.include('INSERT INTO "Beers" ("id","style","createdAt","updatedAt") VALUES (DEFAULT'); + } else if (dialect === 'mssql') { + expect(sql).to.include('INSERT INTO [Beers] ([style],[createdAt],[updatedAt]) '); + } else { // mysql, sqlite + expect(sql).to.include('INSERT INTO `Beers` (`id`,`style`,`createdAt`,`updatedAt`) VALUES (NULL'); } - }); + } }); }); - it('properly handles disparate field lists', function() { + it('properly handles disparate field lists', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', uniqueName: '2' }, { username: 'Steve', uniqueName: '3' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ where: { username: 'Paul' } }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Paul'); - expect(users[0].secretValue).to.be.null; - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ where: { username: 'Paul' } }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Paul'); + expect(users[0].secretValue).to.be.null; }); - it('inserts multiple values respecting the white list', function() { + it('inserts multiple values respecting the white list', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - return this.User.bulkCreate(data, { fields: ['username', 'uniqueName'] }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].secretValue).to.be.null; - expect(users[1].username).to.equal('Paul'); - expect(users[1].secretValue).to.be.null; - }); - }); + await this.User.bulkCreate(data, { fields: ['username', 'uniqueName'] }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].secretValue).to.be.null; + expect(users[1].username).to.equal('Paul'); + expect(users[1].secretValue).to.be.null; }); - it('should store all values if no whitelist is specified', function() { + it('should store all values if no whitelist is specified', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].secretValue).to.equal('42'); - expect(users[1].username).to.equal('Paul'); - expect(users[1].secretValue).to.equal('23'); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].secretValue).to.equal('42'); + expect(users[1].username).to.equal('Paul'); + expect(users[1].secretValue).to.equal('23'); }); - it('should set isNewRecord = false', function() { + it('should set isNewRecord = false', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - users.forEach(user => { - expect(user.isNewRecord).to.equal(false); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + users.forEach(user => { + expect(user.isNewRecord).to.equal(false); }); }); - it('saves data with single quote', function() { + it('saves data with single quote', async function() { const quote = "Single'Quote", data = [{ username: 'Peter', data: quote, uniqueName: '1' }, { username: 'Paul', data: quote, uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].data).to.equal(quote); - expect(users[1].username).to.equal('Paul'); - expect(users[1].data).to.equal(quote); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].data).to.equal(quote); + expect(users[1].username).to.equal('Paul'); + expect(users[1].data).to.equal(quote); }); - it('saves data with double quote', function() { + it('saves data with double quote', async function() { const quote = 'Double"Quote', data = [{ username: 'Peter', data: quote, uniqueName: '1' }, { username: 'Paul', data: quote, uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].data).to.equal(quote); - expect(users[1].username).to.equal('Paul'); - expect(users[1].data).to.equal(quote); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].data).to.equal(quote); + expect(users[1].username).to.equal('Paul'); + expect(users[1].data).to.equal(quote); }); - it('saves stringified JSON data', function() { + it('saves stringified JSON data', async function() { const json = JSON.stringify({ key: 'value' }), data = [{ username: 'Peter', data: json, uniqueName: '1' }, { username: 'Paul', data: json, uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].data).to.equal(json); - expect(users[1].username).to.equal('Paul'); - expect(users[1].data).to.equal(json); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].data).to.equal(json); + expect(users[1].username).to.equal('Paul'); + expect(users[1].data).to.equal(json); }); - it('properly handles a model with a length column', function() { + it('properly handles a model with a length column', async function() { const UserWithLength = this.sequelize.define('UserWithLength', { length: Sequelize.INTEGER }); - return UserWithLength.sync({ force: true }).then(() => { - return UserWithLength.bulkCreate([{ length: 42 }, { length: 11 }]); - }); + await UserWithLength.sync({ force: true }); + + await UserWithLength.bulkCreate([{ length: 42 }, { length: 11 }]); }); - it('stores the current date in createdAt', function() { + it('stores the current date in createdAt', async function() { const data = [{ username: 'Peter', uniqueName: '1' }, { username: 'Paul', uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(parseInt(+users[0].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); - expect(users[1].username).to.equal('Paul'); - expect(parseInt(+users[1].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(parseInt(+users[0].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); + expect(users[1].username).to.equal('Paul'); + expect(parseInt(+users[1].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); }); - it('emits an error when validate is set to true', function() { + it('emits an error when validate is set to true', async function() { const Tasks = this.sequelize.define('Task', { name: { type: Sequelize.STRING, @@ -316,34 +290,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Tasks.sync({ force: true }).then(() => { - return Tasks.bulkCreate([ + await Tasks.sync({ force: true }); + + try { + await Tasks.bulkCreate([ { name: 'foo', code: '123' }, { code: '1234' }, { name: 'bar', code: '1' } - ], { validate: true }).catch(error => { - const expectedValidationError = 'Validation len on code failed'; - const expectedNotNullError = 'notNull Violation: Task.name cannot be null'; - - expect(error).to.be.instanceof(AggregateError); - expect(error.toString()).to.include(expectedValidationError) - .and.to.include(expectedNotNullError); - const { errors } = error; - expect(errors).to.have.length(2); - - const e0name0 = errors[0].errors.get('name')[0]; - - expect(errors[0].record.code).to.equal('1234'); - expect(e0name0.type || e0name0.origin).to.equal('notNull Violation'); - - expect(errors[1].record.name).to.equal('bar'); - expect(errors[1].record.code).to.equal('1'); - expect(errors[1].errors.get('code')[0].message).to.equal(expectedValidationError); - }); - }); + ], { validate: true }); + } catch (error) { + const expectedValidationError = 'Validation len on code failed'; + const expectedNotNullError = 'notNull Violation: Task.name cannot be null'; + + expect(error).to.be.instanceof(AggregateError); + expect(error.toString()).to.include(expectedValidationError) + .and.to.include(expectedNotNullError); + const { errors } = error; + expect(errors).to.have.length(2); + + const e0name0 = errors[0].errors.get('name')[0]; + + expect(errors[0].record.code).to.equal('1234'); + expect(e0name0.type || e0name0.origin).to.equal('notNull Violation'); + + expect(errors[1].record.name).to.equal('bar'); + expect(errors[1].record.code).to.equal('1'); + expect(errors[1].errors.get('code')[0].message).to.equal(expectedValidationError); + } }); - it("doesn't emit an error when validate is set to true but our selectedValues are fine", function() { + it("doesn't emit an error when validate is set to true but our selectedValues are fine", async function() { const Tasks = this.sequelize.define('Task', { name: { type: Sequelize.STRING, @@ -359,49 +335,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Tasks.sync({ force: true }).then(() => { - return Tasks.bulkCreate([ - { name: 'foo', code: '123' }, - { code: '1234' } - ], { fields: ['code'], validate: true }); - }); + await Tasks.sync({ force: true }); + + await Tasks.bulkCreate([ + { name: 'foo', code: '123' }, + { code: '1234' } + ], { fields: ['code'], validate: true }); }); - it('should allow blank arrays (return immediately)', function() { + it('should allow blank arrays (return immediately)', async function() { const Worker = this.sequelize.define('Worker', {}); - return Worker.sync().then(() => { - return Worker.bulkCreate([]).then(workers => { - expect(workers).to.be.ok; - expect(workers.length).to.equal(0); - }); - }); + await Worker.sync(); + const workers = await Worker.bulkCreate([]); + expect(workers).to.be.ok; + expect(workers.length).to.equal(0); }); - it('should allow blank creates (with timestamps: false)', function() { + it('should allow blank creates (with timestamps: false)', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.bulkCreate([{}, {}]).then(workers => { - expect(workers).to.be.ok; - }); - }); + await Worker.sync(); + const workers = await Worker.bulkCreate([{}, {}]); + expect(workers).to.be.ok; }); - it('should allow autoincremented attributes to be set', function() { + it('should allow autoincremented attributes to be set', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.bulkCreate([ - { id: 5 }, - { id: 10 } - ]).then(() => { - return Worker.findAll({ order: [['id', 'ASC']] }).then(workers => { - expect(workers[0].id).to.equal(5); - expect(workers[1].id).to.equal(10); - }); - }); - }); + await Worker.sync(); + + await Worker.bulkCreate([ + { id: 5 }, + { id: 10 } + ]); + + const workers = await Worker.findAll({ order: [['id', 'ASC']] }); + expect(workers[0].id).to.equal(5); + expect(workers[1].id).to.equal(10); }); - it('should support schemas', function() { + it('should support schemas', async function() { const Dummy = this.sequelize.define('Dummy', { foo: DataTypes.STRING, bar: DataTypes.STRING @@ -410,141 +381,128 @@ describe(Support.getTestDialectTeaser('Model'), () => { tableName: 'Dummy' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('space1'); - }).then(() => { - return Dummy.sync({ force: true }); - }).then(() => { - return Dummy.bulkCreate([ - { foo: 'a', bar: 'b' }, - { foo: 'c', bar: 'd' } - ]); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('space1'); + await Dummy.sync({ force: true }); + + await Dummy.bulkCreate([ + { foo: 'a', bar: 'b' }, + { foo: 'c', bar: 'd' } + ]); }); if (current.dialect.supports.inserts.ignoreDuplicates || current.dialect.supports.inserts.onConflictDoNothing) { - it('should support the ignoreDuplicates option', function() { + it('should support the ignoreDuplicates option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).then(() => { - data.push({ uniqueName: 'Michael', secretValue: '26' }); - - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(3); - expect(users[0].uniqueName).to.equal('Peter'); - expect(users[0].secretValue).to.equal('42'); - expect(users[1].uniqueName).to.equal('Paul'); - expect(users[1].secretValue).to.equal('23'); - expect(users[2].uniqueName).to.equal('Michael'); - expect(users[2].secretValue).to.equal('26'); - }); - }); - }); + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }); + data.push({ uniqueName: 'Michael', secretValue: '26' }); + + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(3); + expect(users[0].uniqueName).to.equal('Peter'); + expect(users[0].secretValue).to.equal('42'); + expect(users[1].uniqueName).to.equal('Paul'); + expect(users[1].secretValue).to.equal('23'); + expect(users[2].uniqueName).to.equal('Michael'); + expect(users[2].secretValue).to.equal('26'); }); } else { - it('should throw an error when the ignoreDuplicates option is passed', function() { + it('should throw an error when the ignoreDuplicates option is passed', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).then(() => { - data.push({ uniqueName: 'Michael', secretValue: '26' }); + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }); + data.push({ uniqueName: 'Michael', secretValue: '26' }); - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).catch(err => { - expect(err.message).to.equal(`${dialect} does not support the ignoreDuplicates option.`); - }); - }); + try { + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }); + } catch (err) { + expect(err.message).to.equal(`${dialect} does not support the ignoreDuplicates option.`); + } }); } if (current.dialect.supports.inserts.updateOnDuplicate) { describe('updateOnDuplicate', () => { - it('should support the updateOnDuplicate option', function() { + it('should support the updateOnDuplicate option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }).then(() => { - const new_data = [ - { uniqueName: 'Peter', secretValue: '43' }, - { uniqueName: 'Paul', secretValue: '24' }, - { uniqueName: 'Michael', secretValue: '26' } - ]; - return this.User.bulkCreate(new_data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(3); - expect(users[0].uniqueName).to.equal('Peter'); - expect(users[0].secretValue).to.equal('43'); - expect(users[1].uniqueName).to.equal('Paul'); - expect(users[1].secretValue).to.equal('24'); - expect(users[2].uniqueName).to.equal('Michael'); - expect(users[2].secretValue).to.equal('26'); - }); - }); - }); + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }); + const new_data = [ + { uniqueName: 'Peter', secretValue: '43' }, + { uniqueName: 'Paul', secretValue: '24' }, + { uniqueName: 'Michael', secretValue: '26' } + ]; + await this.User.bulkCreate(new_data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(3); + expect(users[0].uniqueName).to.equal('Peter'); + expect(users[0].secretValue).to.equal('43'); + expect(users[1].uniqueName).to.equal('Paul'); + expect(users[1].secretValue).to.equal('24'); + expect(users[2].uniqueName).to.equal('Michael'); + expect(users[2].secretValue).to.equal('26'); }); describe('should support the updateOnDuplicate option with primary keys', () => { - it('when the primary key column names and model field names are the same', function() { + it('when the primary key column names and model field names are the same', async function() { const data = [ { no: 1, name: 'Peter' }, { no: 2, name: 'Paul' } ]; - return this.Student.bulkCreate(data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }).then(() => { - const new_data = [ - { no: 1, name: 'Peterson' }, - { no: 2, name: 'Paulson' }, - { no: 3, name: 'Michael' } - ]; - return this.Student.bulkCreate(new_data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }).then(() => { - return this.Student.findAll({ order: ['no'] }).then(students => { - expect(students.length).to.equal(3); - expect(students[0].name).to.equal('Peterson'); - expect(students[0].no).to.equal(1); - expect(students[1].name).to.equal('Paulson'); - expect(students[1].no).to.equal(2); - expect(students[2].name).to.equal('Michael'); - expect(students[2].no).to.equal(3); - }); - }); - }); + await this.Student.bulkCreate(data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }); + const new_data = [ + { no: 1, name: 'Peterson' }, + { no: 2, name: 'Paulson' }, + { no: 3, name: 'Michael' } + ]; + await this.Student.bulkCreate(new_data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }); + const students = await this.Student.findAll({ order: ['no'] }); + expect(students.length).to.equal(3); + expect(students[0].name).to.equal('Peterson'); + expect(students[0].no).to.equal(1); + expect(students[1].name).to.equal('Paulson'); + expect(students[1].no).to.equal(2); + expect(students[2].name).to.equal('Michael'); + expect(students[2].no).to.equal(3); }); - it('when the primary key column names and model field names are different', function() { + it('when the primary key column names and model field names are different', async function() { const data = [ { plateNumber: 'abc', color: 'Grey' }, { plateNumber: 'def', color: 'White' } ]; - return this.Car.bulkCreate(data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }).then(() => { - const new_data = [ - { plateNumber: 'abc', color: 'Red' }, - { plateNumber: 'def', color: 'Green' }, - { plateNumber: 'ghi', color: 'Blue' } - ]; - return this.Car.bulkCreate(new_data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }).then(() => { - return this.Car.findAll({ order: ['plateNumber'] }).then(cars => { - expect(cars.length).to.equal(3); - expect(cars[0].plateNumber).to.equal('abc'); - expect(cars[0].color).to.equal('Red'); - expect(cars[1].plateNumber).to.equal('def'); - expect(cars[1].color).to.equal('Green'); - expect(cars[2].plateNumber).to.equal('ghi'); - expect(cars[2].color).to.equal('Blue'); - }); - }); - }); + await this.Car.bulkCreate(data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }); + const new_data = [ + { plateNumber: 'abc', color: 'Red' }, + { plateNumber: 'def', color: 'Green' }, + { plateNumber: 'ghi', color: 'Blue' } + ]; + await this.Car.bulkCreate(new_data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }); + const cars = await this.Car.findAll({ order: ['plateNumber'] }); + expect(cars.length).to.equal(3); + expect(cars[0].plateNumber).to.equal('abc'); + expect(cars[0].color).to.equal('Red'); + expect(cars[1].plateNumber).to.equal('def'); + expect(cars[1].color).to.equal('Green'); + expect(cars[2].plateNumber).to.equal('ghi'); + expect(cars[2].color).to.equal('Blue'); }); - it('when the primary key column names and model field names are different and have unique constraints', function() { + it('when the primary key column names and model field names are different and have unique constraints', async function() { const Person = this.sequelize.define('Person', { emailAddress: { type: DataTypes.STRING, @@ -560,35 +518,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, {}); - return Person.sync({ force: true }) - .then(() => { - const inserts = [ - { emailAddress: 'a@example.com', name: 'Alice' } - ]; - return Person.bulkCreate(inserts); - }) - .then(people => { - expect(people.length).to.equal(1); - expect(people[0].emailAddress).to.equal('a@example.com'); - expect(people[0].name).to.equal('Alice'); - - const updates = [ - { emailAddress: 'a@example.com', name: 'CHANGED NAME' }, - { emailAddress: 'b@example.com', name: 'Bob' } - ]; - - return Person.bulkCreate(updates, { updateOnDuplicate: ['emailAddress', 'name'] }); - }) - .then(people => { - expect(people.length).to.equal(2); - expect(people[0].emailAddress).to.equal('a@example.com'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].emailAddress).to.equal('b@example.com'); - expect(people[1].name).to.equal('Bob'); - }); + await Person.sync({ force: true }); + const inserts = [ + { emailAddress: 'a@example.com', name: 'Alice' } + ]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].emailAddress).to.equal('a@example.com'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { emailAddress: 'a@example.com', name: 'CHANGED NAME' }, + { emailAddress: 'b@example.com', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['emailAddress', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].emailAddress).to.equal('a@example.com'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].emailAddress).to.equal('b@example.com'); + expect(people[1].name).to.equal('Bob'); }); - it('when the composite primary key column names and model field names are different', function() { + it('when the composite primary key column names and model field names are different', async function() { const Person = this.sequelize.define('Person', { systemId: { type: DataTypes.INTEGER, @@ -609,38 +561,32 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, {}); - return Person.sync({ force: true }) - .then(() => { - const inserts = [ - { systemId: 1, system: 'system1', name: 'Alice' } - ]; - return Person.bulkCreate(inserts); - }) - .then(people => { - expect(people.length).to.equal(1); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('Alice'); - - const updates = [ - { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, - { systemId: 1, system: 'system2', name: 'Bob' } - ]; - - return Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); - }) - .then(people => { - expect(people.length).to.equal(2); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].systemId).to.equal(1); - expect(people[1].system).to.equal('system2'); - expect(people[1].name).to.equal('Bob'); - }); + await Person.sync({ force: true }); + const inserts = [ + { systemId: 1, system: 'system1', name: 'Alice' } + ]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].systemId).to.equal(1); + expect(people0[0].system).to.equal('system1'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { systemId: 1, system: 'system2', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); }); - it('when the primary key column names and model field names are different and have composite unique constraints', function() { + it('when the primary key column names and model field names are different and have composite unique constraints', async function() { const Person = this.sequelize.define('Person', { id: { type: DataTypes.INTEGER, @@ -667,58 +613,52 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, {}); - return Person.sync({ force: true }) - .then(() => { - const inserts = [ - { id: 1, systemId: 1, system: 'system1', name: 'Alice' } - ]; - return Person.bulkCreate(inserts); - }) - .then(people => { - expect(people.length).to.equal(1); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('Alice'); - - const updates = [ - { id: 1, systemId: 1, system: 'system1', name: 'CHANGED NAME' }, - { id: 2, systemId: 1, system: 'system2', name: 'Bob' } - ]; - - return Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); - }) - .then(people => { - expect(people.length).to.equal(2); - expect(people[0].systemId).to.equal(1); - expect(people[0].system).to.equal('system1'); - expect(people[0].name).to.equal('CHANGED NAME'); - expect(people[1].systemId).to.equal(1); - expect(people[1].system).to.equal('system2'); - expect(people[1].name).to.equal('Bob'); - }); + await Person.sync({ force: true }); + const inserts = [ + { id: 1, systemId: 1, system: 'system1', name: 'Alice' } + ]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].systemId).to.equal(1); + expect(people0[0].system).to.equal('system1'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { id: 1, systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { id: 2, systemId: 1, system: 'system2', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); }); }); - it('should reject for non array updateOnDuplicate option', function() { + it('should reject for non array updateOnDuplicate option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return expect( + await expect( this.User.bulkCreate(data, { updateOnDuplicate: true }) ).to.be.rejectedWith('updateOnDuplicate option only supports non-empty array.'); }); - it('should reject for empty array updateOnDuplicate option', function() { + it('should reject for empty array updateOnDuplicate option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return expect( + await expect( this.User.bulkCreate(data, { updateOnDuplicate: [] }) ).to.be.rejectedWith('updateOnDuplicate option only supports non-empty array.'); }); @@ -727,33 +667,31 @@ describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.returnValues) { describe('return values', () => { - it('should make the auto incremented values available on the returned instances', function() { + it('should make the auto incremented values available on the returned instances', async function() { const User = this.sequelize.define('user', {}); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - })) - .then(users => - User.findAll({ order: ['id'] }) - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - users.forEach((user, i) => { - expect(user.get('id')).to.be.ok; - expect(user.get('id')).to.equal(actualUsers[i].get('id')) - .and.to.equal(i + 1); - }); - }); + await User + .sync({ force: true }); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: true + }); + + const actualUsers0 = await User.findAll({ order: ['id'] }); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach((user, i) => { + expect(user.get('id')).to.be.ok; + expect(user.get('id')).to.equal(actualUsers[i].get('id')) + .and.to.equal(i + 1); + }); }); - it('should make the auto incremented values available on the returned instances with custom fields', function() { + it('should make the auto incremented values available on the returned instances with custom fields', async function() { const User = this.sequelize.define('user', { maId: { type: DataTypes.INTEGER, @@ -763,99 +701,92 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - })) - .then(users => - User.findAll({ order: ['maId'] }) - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - users.forEach((user, i) => { - expect(user.get('maId')).to.be.ok; - expect(user.get('maId')).to.equal(actualUsers[i].get('maId')) - .and.to.equal(i + 1); - }); - }); + await User + .sync({ force: true }); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: true + }); + + const actualUsers0 = await User.findAll({ order: ['maId'] }); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach((user, i) => { + expect(user.get('maId')).to.be.ok; + expect(user.get('maId')).to.equal(actualUsers[i].get('maId')) + .and.to.equal(i + 1); + }); }); - it('should only return fields that are not defined in the model (with returning: true)', function() { + it('should only return fields that are not defined in the model (with returning: true)', async function() { const User = this.sequelize.define('user'); - return User - .sync({ force: true }) - .then(() => this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING)) - .then(() => User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - })) - .then(users => - User.findAll() - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - users.forEach(user => { - expect(user.get()).not.to.have.property('not_on_model'); - }); - }); + await User + .sync({ force: true }); + + await this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: true + }); + + const actualUsers0 = await User.findAll(); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach(user => { + expect(user.get()).not.to.have.property('not_on_model'); + }); }); - it('should return fields that are not defined in the model (with returning: ["*"])', function() { + it('should return fields that are not defined in the model (with returning: ["*"])', async function() { const User = this.sequelize.define('user'); - return User - .sync({ force: true }) - .then(() => this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING)) - .then(() => User.bulkCreate([ - {}, - {}, - {} - ], { - returning: ['*'] - })) - .then(users => - User.findAll() - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - users.forEach(user => { - expect(user.get()).to.have.property('not_on_model'); - }); - }); + await User + .sync({ force: true }); + + await this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: ['*'] + }); + + const actualUsers0 = await User.findAll(); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach(user => { + expect(user.get()).to.have.property('not_on_model'); + }); }); }); } describe('enums', () => { - it('correctly restores enum values', function() { + it('correctly restores enum values', async function() { const Item = this.sequelize.define('Item', { state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }, name: Sequelize.STRING }); - return Item.sync({ force: true }).then(() => { - return Item.bulkCreate([{ state: 'in_cart', name: 'A' }, { state: 'available', name: 'B' }]).then(() => { - return Item.findOne({ where: { state: 'available' } }).then(item => { - expect(item.name).to.equal('B'); - }); - }); - }); + await Item.sync({ force: true }); + await Item.bulkCreate([{ state: 'in_cart', name: 'A' }, { state: 'available', name: 'B' }]); + const item = await Item.findOne({ where: { state: 'available' } }); + expect(item.name).to.equal('B'); }); }); - it('should properly map field names to attribute names', function() { + it('should properly map field names to attribute names', async function() { const Maya = this.sequelize.define('Maya', { name: Sequelize.STRING, secret: { @@ -875,89 +806,84 @@ describe(Support.getTestDialectTeaser('Model'), () => { const M1 = { id: 1, name: 'Prathma Maya', secret: 'You are on list #1' }; const M2 = { id: 2, name: 'Dwitiya Maya', secret: 'You are on list #2' }; - return Maya.sync({ force: true }).then(() => Maya.create(M1)) - .then(m => { - expect(m.createdAt).to.be.ok; - expect(m.id).to.be.eql(M1.id); - expect(m.name).to.be.eql(M1.name); - expect(m.secret).to.be.eql(M1.secret); - - return Maya.bulkCreate([M2]); - }).then(([m]) => { - - // only attributes are returned, no fields are mixed - expect(m.createdAt).to.be.ok; - expect(m.created_at).to.not.exist; - expect(m.secret_given).to.not.exist; - expect(m.get('secret_given')).to.be.undefined; - expect(m.get('created_at')).to.be.undefined; - - // values look fine - expect(m.id).to.be.eql(M2.id); - expect(m.name).to.be.eql(M2.name); - expect(m.secret).to.be.eql(M2.secret); - }); + await Maya.sync({ force: true }); + const m0 = await Maya.create(M1); + expect(m0.createdAt).to.be.ok; + expect(m0.id).to.be.eql(M1.id); + expect(m0.name).to.be.eql(M1.name); + expect(m0.secret).to.be.eql(M1.secret); + + const [m] = await Maya.bulkCreate([M2]); + + // only attributes are returned, no fields are mixed + expect(m.createdAt).to.be.ok; + expect(m.created_at).to.not.exist; + expect(m.secret_given).to.not.exist; + expect(m.get('secret_given')).to.be.undefined; + expect(m.get('created_at')).to.be.undefined; + + // values look fine + expect(m.id).to.be.eql(M2.id); + expect(m.name).to.be.eql(M2.name); + expect(m.secret).to.be.eql(M2.secret); }); describe('handles auto increment values', () => { - it('should return auto increment primary key values', function() { + it('should return auto increment primary key values', async function() { const Maya = this.sequelize.define('Maya', {}); const M1 = {}; const M2 = {}; - return Maya.sync({ force: true }) - .then(() => Maya.bulkCreate([M1, M2], { returning: true })) - .then(ms => { - expect(ms[0].id).to.be.eql(1); - expect(ms[1].id).to.be.eql(2); - }); + await Maya.sync({ force: true }); + const ms = await Maya.bulkCreate([M1, M2], { returning: true }); + expect(ms[0].id).to.be.eql(1); + expect(ms[1].id).to.be.eql(2); }); - it('should return supplied values on primary keys', function() { + it('should return supplied values on primary keys', async function() { const User = this.sequelize.define('user', {}); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - { id: 1 }, - { id: 2 }, - { id: 3 } - ], { returning: true })) - .then(users => - User.findAll({ order: [['id', 'ASC']] }) - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - - expect(users[0].get('id')).to.equal(1).and.to.equal(actualUsers[0].get('id')); - expect(users[1].get('id')).to.equal(2).and.to.equal(actualUsers[1].get('id')); - expect(users[2].get('id')).to.equal(3).and.to.equal(actualUsers[2].get('id')); - }); + await User + .sync({ force: true }); + + const users0 = await User.bulkCreate([ + { id: 1 }, + { id: 2 }, + { id: 3 } + ], { returning: true }); + + const actualUsers0 = await User.findAll({ order: [['id', 'ASC']] }); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + + expect(users[0].get('id')).to.equal(1).and.to.equal(actualUsers[0].get('id')); + expect(users[1].get('id')).to.equal(2).and.to.equal(actualUsers[1].get('id')); + expect(users[2].get('id')).to.equal(3).and.to.equal(actualUsers[2].get('id')); }); - it('should return supplied values on primary keys when some instances already exists', function() { + it('should return supplied values on primary keys when some instances already exists', async function() { const User = this.sequelize.define('user', {}); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - { id: 1 }, - { id: 3 } - ])) - .then(() => User.bulkCreate([ - { id: 2 }, - { id: 4 }, - { id: 5 } - ], { returning: true })) - .then(users => { - expect(users.length).to.eql(3); - - expect(users[0].get('id')).to.equal(2); - expect(users[1].get('id')).to.equal(4); - expect(users[2].get('id')).to.equal(5); - }); + await User + .sync({ force: true }); + + await User.bulkCreate([ + { id: 1 }, + { id: 3 } + ]); + + const users = await User.bulkCreate([ + { id: 2 }, + { id: 4 }, + { id: 5 } + ], { returning: true }); + + expect(users.length).to.eql(3); + + expect(users[0].get('id')).to.equal(2); + expect(users[1].get('id')).to.equal(4); + expect(users[2].get('id')).to.equal(5); }); }); @@ -975,35 +901,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should validate', function() { - return this.User - .sync({ force: true }) - .then(() => this.User.bulkCreate([ + it('should validate', async function() { + try { + await this.User + .sync({ force: true }); + + await this.User.bulkCreate([ { password: 'password' } - ], { validate: true })) - .then(() => { - expect.fail(); - }, error => { - expect(error.errors.length).to.equal(1); - expect(error.errors[0].message).to.match(/.*always invalid.*/); - }); + ], { validate: true }); + + expect.fail(); + } catch (error) { + expect(error.errors.length).to.equal(1); + expect(error.errors[0].message).to.match(/.*always invalid.*/); + } }); - it('should not validate', function() { - return this.User - .sync({ force: true }) - .then(() => this.User.bulkCreate([ - { password: 'password' } - ], { validate: false })) - .then(users => { - expect(users.length).to.equal(1); - }) - .then(() => this.User.bulkCreate([ - { password: 'password' } - ])) - .then(users => { - expect(users.length).to.equal(1); - }); + it('should not validate', async function() { + await this.User + .sync({ force: true }); + + const users0 = await this.User.bulkCreate([ + { password: 'password' } + ], { validate: false }); + + expect(users0.length).to.equal(1); + + const users = await this.User.bulkCreate([ + { password: 'password' } + ]); + + expect(users.length).to.equal(1); }); }); }); diff --git a/test/integration/model/bulk-create/include.test.js b/test/integration/model/bulk-create/include.test.js index 581f60f4d6c7..34d24fe12fdd 100644 --- a/test/integration/model/bulk-create/include.test.js +++ b/test/integration/model/bulk-create/include.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('bulkCreate', () => { describe('include', () => { - it('should bulkCreate data for BelongsTo relations', function() { + it('should bulkCreate data for BelongsTo relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -36,54 +36,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - title: 'Chair', - User: { - first_name: 'Mick', - last_name: 'Broadstone' - } - }, { - title: 'Table', - User: { - first_name: 'John', - last_name: 'Johnson' - } - }], { - include: [{ - model: User, - myOption: 'option' - }] - }).then(savedProducts => { - expect(savedProducts[0].isIncludeCreatedOnAfterCreate).to.be.true; - expect(savedProducts[0].User.createOptions.myOption).to.be.equal('option'); - - expect(savedProducts[1].isIncludeCreatedOnAfterCreate).to.be.true; - expect(savedProducts[1].User.createOptions.myOption).to.be.equal('option'); - - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [User] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [User] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].User).to.be.ok; - expect(persistedProducts[0].User.first_name).to.be.equal('Mick'); - expect(persistedProducts[0].User.last_name).to.be.equal('Broadstone'); - - expect(persistedProducts[1].User).to.be.ok; - expect(persistedProducts[1].User.first_name).to.be.equal('John'); - expect(persistedProducts[1].User.last_name).to.be.equal('Johnson'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + title: 'Chair', + User: { + first_name: 'Mick', + last_name: 'Broadstone' + } + }, { + title: 'Table', + User: { + first_name: 'John', + last_name: 'Johnson' + } + }], { + include: [{ + model: User, + myOption: 'option' + }] + }); + + expect(savedProducts[0].isIncludeCreatedOnAfterCreate).to.be.true; + expect(savedProducts[0].User.createOptions.myOption).to.be.equal('option'); + + expect(savedProducts[1].isIncludeCreatedOnAfterCreate).to.be.true; + expect(savedProducts[1].User.createOptions.myOption).to.be.equal('option'); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, + include: [User] + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [User] + }) + ]); + + expect(persistedProducts[0].User).to.be.ok; + expect(persistedProducts[0].User.first_name).to.be.equal('Mick'); + expect(persistedProducts[0].User.last_name).to.be.equal('Broadstone'); + + expect(persistedProducts[1].User).to.be.ok; + expect(persistedProducts[1].User.first_name).to.be.equal('John'); + expect(persistedProducts[1].User.last_name).to.be.equal('Johnson'); }); - it('should bulkCreate data for BelongsTo relations with no nullable FK', function() { + it('should bulkCreate data for BelongsTo relations with no nullable FK', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -97,36 +97,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - title: 'Chair', - User: { - first_name: 'Mick' - } - }, { - title: 'Table', - User: { - first_name: 'John' - } - }], { - include: [{ - model: User - }] - }).then(savedProducts => { - expect(savedProducts[0]).to.exist; - expect(savedProducts[0].title).to.be.equal('Chair'); - expect(savedProducts[0].User).to.exist; - expect(savedProducts[0].User.first_name).to.be.equal('Mick'); - - expect(savedProducts[1]).to.exist; - expect(savedProducts[1].title).to.be.equal('Table'); - expect(savedProducts[1].User).to.exist; - expect(savedProducts[1].User.first_name).to.be.equal('John'); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + title: 'Chair', + User: { + first_name: 'Mick' + } + }, { + title: 'Table', + User: { + first_name: 'John' + } + }], { + include: [{ + model: User + }] }); + + expect(savedProducts[0]).to.exist; + expect(savedProducts[0].title).to.be.equal('Chair'); + expect(savedProducts[0].User).to.exist; + expect(savedProducts[0].User.first_name).to.be.equal('Mick'); + + expect(savedProducts[1]).to.exist; + expect(savedProducts[1].title).to.be.equal('Table'); + expect(savedProducts[1].User).to.exist; + expect(savedProducts[1].User.first_name).to.be.equal('John'); }); - it('should bulkCreate data for BelongsTo relations with alias', function() { + it('should bulkCreate data for BelongsTo relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -137,45 +137,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Creator = Product.belongsTo(User, { as: 'creator' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - title: 'Chair', - creator: { - first_name: 'Matt', - last_name: 'Hansen' - } - }, { - title: 'Table', - creator: { - first_name: 'John', - last_name: 'Johnson' - } - }], { - include: [Creator] - }).then(savedProducts => { - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [Creator] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [Creator] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].creator).to.be.ok; - expect(persistedProducts[0].creator.first_name).to.be.equal('Matt'); - expect(persistedProducts[0].creator.last_name).to.be.equal('Hansen'); - - expect(persistedProducts[1].creator).to.be.ok; - expect(persistedProducts[1].creator.first_name).to.be.equal('John'); - expect(persistedProducts[1].creator.last_name).to.be.equal('Johnson'); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + title: 'Chair', + creator: { + first_name: 'Matt', + last_name: 'Hansen' + } + }, { + title: 'Table', + creator: { + first_name: 'John', + last_name: 'Johnson' + } + }], { + include: [Creator] }); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, + include: [Creator] + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [Creator] + }) + ]); + + expect(persistedProducts[0].creator).to.be.ok; + expect(persistedProducts[0].creator.first_name).to.be.equal('Matt'); + expect(persistedProducts[0].creator.last_name).to.be.equal('Hansen'); + + expect(persistedProducts[1].creator).to.be.ok; + expect(persistedProducts[1].creator.first_name).to.be.equal('John'); + expect(persistedProducts[1].creator.last_name).to.be.equal('Johnson'); }); - it('should bulkCreate data for HasMany relations', function() { + it('should bulkCreate data for HasMany relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -202,55 +202,56 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.hasMany(Tag); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - id: 1, - title: 'Chair', - Tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - id: 2, - title: 'Table', - Tags: [ - { id: 3, name: 'Gamma' }, - { id: 4, name: 'Delta' } - ] - }], { - include: [{ - model: Tag, - myOption: 'option' - }] - }).then(savedProducts => { - expect(savedProducts[0].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedProducts[0].Tags[0].createOptions.myOption).to.be.equal('option'); - expect(savedProducts[0].Tags[1].createOptions.myOption).to.be.equal('option'); - - expect(savedProducts[1].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedProducts[1].Tags[0].createOptions.myOption).to.be.equal('option'); - expect(savedProducts[1].Tags[1].createOptions.myOption).to.be.equal('option'); - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [Tag] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [Tag] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].Tags).to.be.ok; - expect(persistedProducts[0].Tags.length).to.equal(2); - - expect(persistedProducts[1].Tags).to.be.ok; - expect(persistedProducts[1].Tags.length).to.equal(2); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + id: 1, + title: 'Chair', + Tags: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + id: 2, + title: 'Table', + Tags: [ + { id: 3, name: 'Gamma' }, + { id: 4, name: 'Delta' } + ] + }], { + include: [{ + model: Tag, + myOption: 'option' + }] + }); + + expect(savedProducts[0].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedProducts[0].Tags[0].createOptions.myOption).to.be.equal('option'); + expect(savedProducts[0].Tags[1].createOptions.myOption).to.be.equal('option'); + + expect(savedProducts[1].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedProducts[1].Tags[0].createOptions.myOption).to.be.equal('option'); + expect(savedProducts[1].Tags[1].createOptions.myOption).to.be.equal('option'); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, + include: [Tag] + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [Tag] + }) + ]); + + expect(persistedProducts[0].Tags).to.be.ok; + expect(persistedProducts[0].Tags.length).to.equal(2); + + expect(persistedProducts[1].Tags).to.be.ok; + expect(persistedProducts[1].Tags.length).to.equal(2); }); - it('should bulkCreate data for HasMany relations with alias', function() { + it('should bulkCreate data for HasMany relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -260,45 +261,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Categories = Product.hasMany(Tag, { as: 'categories' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - id: 2, - title: 'Table', - categories: [ - { id: 3, name: 'Gamma' }, - { id: 4, name: 'Delta' } - ] - }], { + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + id: 1, + title: 'Chair', + categories: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + id: 2, + title: 'Table', + categories: [ + { id: 3, name: 'Gamma' }, + { id: 4, name: 'Delta' } + ] + }], { + include: [Categories] + }); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, include: [Categories] - }).then(savedProducts => { - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [Categories] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [Categories] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].categories).to.be.ok; - expect(persistedProducts[0].categories.length).to.equal(2); - - expect(persistedProducts[1].categories).to.be.ok; - expect(persistedProducts[1].categories.length).to.equal(2); - }); - }); - }); + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [Categories] + }) + ]); + + expect(persistedProducts[0].categories).to.be.ok; + expect(persistedProducts[0].categories.length).to.equal(2); + + expect(persistedProducts[1].categories).to.be.ok; + expect(persistedProducts[1].categories.length).to.equal(2); }); - it('should bulkCreate data for HasOne relations', function() { + it('should bulkCreate data for HasOne relations', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -309,38 +310,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'Muzzy', - Task: { - title: 'Eat Clocks' - } - }, { - username: 'Walker', - Task: { - title: 'Walk' - } - }], { - include: [Task] - }).then(savedUsers => { - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Task] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Task] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].Task).to.be.ok; - expect(persistedUsers[1].Task).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'Muzzy', + Task: { + title: 'Eat Clocks' + } + }, { + username: 'Walker', + Task: { + title: 'Walk' + } + }], { + include: [Task] }); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, + include: [Task] + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Task] + }) + ]); + + expect(persistedUsers[0].Task).to.be.ok; + expect(persistedUsers[1].Task).to.be.ok; }); - it('should bulkCreate data for HasOne relations with alias', function() { + it('should bulkCreate data for HasOne relations with alias', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -352,38 +353,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Job = User.hasOne(Task, { as: 'job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'Muzzy', - job: { - title: 'Eat Clocks' - } - }, { - username: 'Walker', - job: { - title: 'Walk' - } - }], { - include: [Job] - }).then(savedUsers => { - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Job] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Job] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].job).to.be.ok; - expect(persistedUsers[1].job).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'Muzzy', + job: { + title: 'Eat Clocks' + } + }, { + username: 'Walker', + job: { + title: 'Walk' + } + }], { + include: [Job] }); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, + include: [Job] + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Job] + }) + ]); + + expect(persistedUsers[0].job).to.be.ok; + expect(persistedUsers[1].job).to.be.ok; }); - it('should bulkCreate data for BelongsToMany relations', function() { + it('should bulkCreate data for BelongsToMany relations', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }, { @@ -415,53 +416,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.belongsToMany(Task, { through: 'user_task' }); Task.belongsToMany(User, { through: 'user_task' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'John', - Tasks: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - username: 'Jack', - Tasks: [ - { title: 'Prepare sandwich', active: true }, - { title: 'Each sandwich', active: false } - ] - }], { - include: [{ - model: Task, - myOption: 'option' - }] - }).then(savedUsers => { - expect(savedUsers[0].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedUsers[0].Tasks[0].createOptions.myOption).to.be.equal('option'); - expect(savedUsers[0].Tasks[1].createOptions.myOption).to.be.equal('option'); - - expect(savedUsers[1].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedUsers[1].Tasks[0].createOptions.myOption).to.be.equal('option'); - expect(savedUsers[1].Tasks[1].createOptions.myOption).to.be.equal('option'); - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Task] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Task] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].Tasks).to.be.ok; - expect(persistedUsers[0].Tasks.length).to.equal(2); - - expect(persistedUsers[1].Tasks).to.be.ok; - expect(persistedUsers[1].Tasks.length).to.equal(2); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'John', + Tasks: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + username: 'Jack', + Tasks: [ + { title: 'Prepare sandwich', active: true }, + { title: 'Each sandwich', active: false } + ] + }], { + include: [{ + model: Task, + myOption: 'option' + }] + }); + + expect(savedUsers[0].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedUsers[0].Tasks[0].createOptions.myOption).to.be.equal('option'); + expect(savedUsers[0].Tasks[1].createOptions.myOption).to.be.equal('option'); + + expect(savedUsers[1].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedUsers[1].Tasks[0].createOptions.myOption).to.be.equal('option'); + expect(savedUsers[1].Tasks[1].createOptions.myOption).to.be.equal('option'); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, + include: [Task] + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Task] + }) + ]); + + expect(persistedUsers[0].Tasks).to.be.ok; + expect(persistedUsers[0].Tasks.length).to.equal(2); + + expect(persistedUsers[1].Tasks).to.be.ok; + expect(persistedUsers[1].Tasks.length).to.equal(2); }); - it('should bulkCreate data for polymorphic BelongsToMany relations', function() { + it('should bulkCreate data for polymorphic BelongsToMany relations', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }, { @@ -520,66 +522,65 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.bulkCreate([{ - title: 'Polymorphic Associations', - tags: [ - { - name: 'polymorphic' - }, - { - name: 'associations' - } - ] - }, { - title: 'Second Polymorphic Associations', - tags: [ - { - name: 'second polymorphic' - }, - { - name: 'second associations' - } - ] - }], { - include: [{ - model: Tag, - as: 'tags', - through: { - model: ItemTag - } - }] - } - ); - }).then(savedPosts => { - // The saved post should include the two tags - expect(savedPosts[0].tags.length).to.equal(2); - expect(savedPosts[1].tags.length).to.equal(2); - // The saved post should be able to retrieve the two tags - // using the convenience accessor methods - return Promise.all([ - savedPosts[0].getTags(), - savedPosts[1].getTags() - ]); - }).then(savedTagGroups => { - // All nested tags should be returned - expect(savedTagGroups[0].length).to.equal(2); - expect(savedTagGroups[1].length).to.equal(2); - }).then(() => { - return ItemTag.findAll(); - }).then(itemTags => { - // Four "through" models should be created - expect(itemTags.length).to.equal(4); - // And their polymorphic field should be correctly set to 'post' - expect(itemTags[0].taggable).to.equal('post'); - expect(itemTags[1].taggable).to.equal('post'); - - expect(itemTags[2].taggable).to.equal('post'); - expect(itemTags[3].taggable).to.equal('post'); - }); + await this.sequelize.sync({ force: true }); + + const savedPosts = await Post.bulkCreate([{ + title: 'Polymorphic Associations', + tags: [ + { + name: 'polymorphic' + }, + { + name: 'associations' + } + ] + }, { + title: 'Second Polymorphic Associations', + tags: [ + { + name: 'second polymorphic' + }, + { + name: 'second associations' + } + ] + }], { + include: [{ + model: Tag, + as: 'tags', + through: { + model: ItemTag + } + }] + } + ); + + // The saved post should include the two tags + expect(savedPosts[0].tags.length).to.equal(2); + expect(savedPosts[1].tags.length).to.equal(2); + + // The saved post should be able to retrieve the two tags + // using the convenience accessor methods + const savedTagGroups = await Promise.all([ + savedPosts[0].getTags(), + savedPosts[1].getTags() + ]); + + // All nested tags should be returned + expect(savedTagGroups[0].length).to.equal(2); + expect(savedTagGroups[1].length).to.equal(2); + const itemTags = await ItemTag.findAll(); + // Four "through" models should be created + expect(itemTags.length).to.equal(4); + // And their polymorphic field should be correctly set to 'post' + expect(itemTags[0].taggable).to.equal('post'); + expect(itemTags[1].taggable).to.equal('post'); + + expect(itemTags[2].taggable).to.equal('post'); + expect(itemTags[3].taggable).to.equal('post'); }); - it('should bulkCreate data for BelongsToMany relations with alias', function() { + it('should bulkCreate data for BelongsToMany relations with alias', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); @@ -592,40 +593,40 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Jobs = User.belongsToMany(Task, { through: 'user_job', as: 'jobs' }); Task.belongsToMany(User, { through: 'user_job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'John', - jobs: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - username: 'Jack', - jobs: [ - { title: 'Prepare sandwich', active: true }, - { title: 'Eat sandwich', active: false } - ] - }], { + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'John', + jobs: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + username: 'Jack', + jobs: [ + { title: 'Prepare sandwich', active: true }, + { title: 'Eat sandwich', active: false } + ] + }], { + include: [Jobs] + }); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, include: [Jobs] - }).then(savedUsers => { - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Jobs] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Jobs] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].jobs).to.be.ok; - expect(persistedUsers[0].jobs.length).to.equal(2); - - expect(persistedUsers[1].jobs).to.be.ok; - expect(persistedUsers[1].jobs.length).to.equal(2); - }); - }); - }); + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Jobs] + }) + ]); + + expect(persistedUsers[0].jobs).to.be.ok; + expect(persistedUsers[0].jobs.length).to.equal(2); + + expect(persistedUsers[1].jobs).to.be.ok; + expect(persistedUsers[1].jobs.length).to.equal(2); }); }); }); diff --git a/test/integration/model/count.test.js b/test/integration/model/count.test.js index 34a9bf9308ba..f10257e956e2 100644 --- a/test/integration/model/count.test.js +++ b/test/integration/model/count.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('count', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -15,116 +15,122 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Project = this.sequelize.define('Project', { name: DataTypes.STRING }); - + this.User.hasMany(this.Project); this.Project.belongsTo(this.User); - - return this.sequelize.sync({ force: true }); + + await this.sequelize.sync({ force: true }); }); - it('should count rows', function() { - return this.User.bulkCreate([ + it('should count rows', async function() { + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' } - ]).then(() => { - return expect(this.User.count()).to.eventually.equal(2); - }); + ]); + + await expect(this.User.count()).to.eventually.equal(2); }); - it('should support include', function() { - return this.User.bulkCreate([ + it('should support include', async function() { + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' } - ]).then(() => this.User.findOne()) - .then(user => user.createProject({ name: 'project1' })) - .then(() => { - return expect(this.User.count({ - include: [{ - model: this.Project, - where: { name: 'project1' } - }] - })).to.eventually.equal(1); - }); + ]); + + const user = await this.User.findOne(); + await user.createProject({ name: 'project1' }); + + await expect(this.User.count({ + include: [{ + model: this.Project, + where: { name: 'project1' } + }] + })).to.eventually.equal(1); }); - it('should count groups correctly and return attributes', function() { - return this.User.bulkCreate([ + it('should count groups correctly and return attributes', async function() { + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' }, { username: 'valak', createdAt: new Date().setFullYear(2015) } - ]).then(() => this.User.count({ + ]); + + const users = await this.User.count({ attributes: ['createdAt'], group: ['createdAt'] - })).then(users => { - expect(users.length).to.be.eql(2); - expect(users[0].createdAt).to.exist; - expect(users[1].createdAt).to.exist; }); + + expect(users.length).to.be.eql(2); + expect(users[0].createdAt).to.exist; + expect(users[1].createdAt).to.exist; }); - it('should not return NaN', function() { - return this.User.bulkCreate([ + it('should not return NaN', async function() { + await this.User.bulkCreate([ { username: 'valak', age: 10 }, { username: 'conjuring', age: 20 }, { username: 'scary', age: 10 } - ]).then(() => this.User.count({ + ]); + + const result = await this.User.count({ where: { age: 10 }, group: ['age'], order: ['age'] - })).then(result => { - // TODO: `parseInt` should not be needed, see #10533 - expect(parseInt(result[0].count, 10)).to.be.eql(2); - return this.User.count({ - where: { username: 'fire' } - }); - }).then(count => { - expect(count).to.be.eql(0); - return this.User.count({ - where: { username: 'fire' }, - group: 'age' - }); - }).then(count => { - expect(count).to.be.eql([]); }); + + // TODO: `parseInt` should not be needed, see #10533 + expect(parseInt(result[0].count, 10)).to.be.eql(2); + + const count0 = await this.User.count({ + where: { username: 'fire' } + }); + + expect(count0).to.be.eql(0); + + const count = await this.User.count({ + where: { username: 'fire' }, + group: 'age' + }); + + expect(count).to.be.eql([]); }); - it('should be able to specify column for COUNT()', function() { - return this.User.bulkCreate([ + it('should be able to specify column for COUNT()', async function() { + await this.User.bulkCreate([ { username: 'ember', age: 10 }, { username: 'angular', age: 20 }, { username: 'mithril', age: 10 } - ]).then(() => this.User.count({ col: 'username' })) - .then(count => { - expect(count).to.be.eql(3); - return this.User.count({ - col: 'age', - distinct: true - }); - }) - .then(count => { - expect(count).to.be.eql(2); - }); + ]); + + const count0 = await this.User.count({ col: 'username' }); + expect(count0).to.be.eql(3); + + const count = await this.User.count({ + col: 'age', + distinct: true + }); + + expect(count).to.be.eql(2); }); - it('should be able to specify NO column for COUNT() with DISTINCT', function() { - return this.User.bulkCreate([ + it('should be able to specify NO column for COUNT() with DISTINCT', async function() { + await this.User.bulkCreate([ { username: 'ember', age: 10 }, { username: 'angular', age: 20 }, { username: 'mithril', age: 10 } - ]).then(() => { - return this.User.count({ - distinct: true - }); - }) - .then(count => { - expect(count).to.be.eql(3); - }); + ]); + + const count = await this.User.count({ + distinct: true + }); + + expect(count).to.be.eql(3); }); - it('should be able to use where clause on included models', function() { + it('should be able to use where clause on included models', async function() { const countOptions = { col: 'username', include: [this.Project], @@ -132,63 +138,64 @@ describe(Support.getTestDialectTeaser('Model'), () => { '$Projects.name$': 'project1' } }; - return this.User.bulkCreate([ + + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' } - ]).then(() => this.User.findOne()) - .then(user => user.createProject({ name: 'project1' })) - .then(() => { - return this.User.count(countOptions).then(count => { - expect(count).to.be.eql(1); - countOptions.where['$Projects.name$'] = 'project2'; - return this.User.count(countOptions); - }); - }) - .then(count => { - expect(count).to.be.eql(0); - }); + ]); + + const user = await this.User.findOne(); + await user.createProject({ name: 'project1' }); + const count0 = await this.User.count(countOptions); + expect(count0).to.be.eql(1); + countOptions.where['$Projects.name$'] = 'project2'; + const count = await this.User.count(countOptions); + expect(count).to.be.eql(0); }); - it('should be able to specify column for COUNT() with includes', function() { - return this.User.bulkCreate([ + it('should be able to specify column for COUNT() with includes', async function() { + await this.User.bulkCreate([ { username: 'ember', age: 10 }, { username: 'angular', age: 20 }, { username: 'mithril', age: 10 } - ]).then(() => this.User.count({ + ]); + + const count0 = await this.User.count({ col: 'username', distinct: true, include: [this.Project] - })).then(count => { - expect(count).to.be.eql(3); - return this.User.count({ - col: 'age', - distinct: true, - include: [this.Project] - }); - }).then(count => { - expect(count).to.be.eql(2); }); + + expect(count0).to.be.eql(3); + + const count = await this.User.count({ + col: 'age', + distinct: true, + include: [this.Project] + }); + + expect(count).to.be.eql(2); }); - it('should work correctly with include and whichever raw option', function() { + it('should work correctly with include and whichever raw option', async function() { const Post = this.sequelize.define('Post', {}); this.User.hasMany(Post); - return Post.sync({ force: true }) - .then(() => Promise.all([this.User.create({}), Post.create({})])) - .then(([user, post]) => user.addPost(post)) - .then(() => Promise.all([ - this.User.count(), - this.User.count({ raw: undefined }), - this.User.count({ raw: false }), - this.User.count({ raw: true }), - this.User.count({ include: Post }), - this.User.count({ include: Post, raw: undefined }), - this.User.count({ include: Post, raw: false }), - this.User.count({ include: Post, raw: true }) - ])) - .then(counts => { - expect(counts).to.deep.equal([1, 1, 1, 1, 1, 1, 1, 1]); - }); + await Post.sync({ force: true }); + const [user, post] = await Promise.all([this.User.create({}), Post.create({})]); + await user.addPost(post); + + const counts = await Promise.all([ + this.User.count(), + this.User.count({ raw: undefined }), + this.User.count({ raw: false }), + this.User.count({ raw: true }), + this.User.count({ include: Post }), + this.User.count({ include: Post, raw: undefined }), + this.User.count({ include: Post, raw: false }), + this.User.count({ include: Post, raw: true }) + ]); + + expect(counts).to.deep.equal([1, 1, 1, 1, 1, 1, 1, 1]); }); }); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 0c08969785c5..81ded27d43a9 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -14,68 +14,61 @@ const chai = require('chai'), pTimeout = require('p-timeout'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: DataTypes.STRING, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN, - uniqueName: { type: DataTypes.STRING, unique: true } - }); - this.Account = this.sequelize.define('Account', { - accountName: DataTypes.STRING - }); - this.Student = this.sequelize.define('Student', { - no: { type: DataTypes.INTEGER, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false } - }); - - return this.sequelize.sync({ force: true }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.User = this.sequelize.define('User', { + username: DataTypes.STRING, + secretValue: DataTypes.STRING, + data: DataTypes.STRING, + intVal: DataTypes.INTEGER, + theDate: DataTypes.DATE, + aBool: DataTypes.BOOLEAN, + uniqueName: { type: DataTypes.STRING, unique: true } }); + this.Account = this.sequelize.define('Account', { + accountName: DataTypes.STRING + }); + this.Student = this.sequelize.define('Student', { + no: { type: DataTypes.INTEGER, primaryKey: true }, + name: { type: DataTypes.STRING, allowNull: false } + }); + + await this.sequelize.sync({ force: true }); }); describe('findOrCreate', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return this.sequelize.transaction().then(t => { - return this.User.findOrCreate({ - where: { - username: 'Username' - }, - defaults: { - data: 'some data' - }, - transaction: t - }).then(() => { - return this.User.count().then(count => { - expect(count).to.equal(0); - return t.commit().then(() => { - return this.User.count().then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); + it('supports transactions', async function() { + const t = await this.sequelize.transaction(); + + await this.User.findOrCreate({ + where: { + username: 'Username' + }, + defaults: { + data: 'some data' + }, + transaction: t }); + + const count = await this.User.count(); + expect(count).to.equal(0); + await t.commit(); + const count0 = await this.User.count(); + expect(count0).to.equal(1); }); - it('supports more than one models per transaction', function() { - return this.sequelize.transaction().then(t => { - return this.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }, transaction: t }).then(() => { - return this.Account.findOrCreate({ where: { accountName: 'accountName' }, transaction: t }).then(() => { - return t.commit(); - }); - }); - }); + it('supports more than one models per transaction', async function() { + const t = await this.sequelize.transaction(); + await this.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }, transaction: t }); + await this.Account.findOrCreate({ where: { accountName: 'accountName' }, transaction: t }); + await t.commit(); }); } - it('should error correctly when defaults contain a unique key', function() { + it('should error correctly when defaults contain a unique key', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -87,23 +80,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'gottlieb' - }); - }).then(() => { - return expect(User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); + await User.sync({ force: true }); + + await User.create({ + username: 'gottlieb' }); + + await expect(User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb' + } + })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); }); - it('should error correctly when defaults contain a unique key and a non-existent field', function() { + it('should error correctly when defaults contain a unique key and a non-existent field', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -115,25 +108,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'gottlieb' - }); - }).then(() => { - return expect(User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb', - foo: 'bar', // field that's not a defined attribute - bar: 121 - } - })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); + await User.sync({ force: true }); + + await User.create({ + username: 'gottlieb' }); + + await expect(User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb', + foo: 'bar', // field that's not a defined attribute + bar: 121 + } + })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); }); - it('should error correctly when defaults contain a unique key and the where clause is complex', function() { + it('should error correctly when defaults contain a unique key and the where clause is complex', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -145,9 +138,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }) - .then(() => User.create({ username: 'gottlieb' })) - .then(() => User.findOrCreate({ + await User.sync({ force: true }); + await User.create({ username: 'gottlieb' }); + + try { + await User.findOrCreate({ where: { [Op.or]: [{ objectId: 'asdasdasd1' @@ -158,13 +153,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { defaults: { username: 'gottlieb' } - }).catch(error => { - expect(error).to.be.instanceof(Sequelize.UniqueConstraintError); - expect(error.errors[0].path).to.be.a('string', 'username'); - })); + }); + } catch (error) { + expect(error).to.be.instanceof(Sequelize.UniqueConstraintError); + expect(error.errors[0].path).to.be.a('string', 'username'); + } }); - it('should work with empty uuid primary key in where', function() { + it('should work with empty uuid primary key in where', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -177,18 +173,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.findOrCreate({ - where: {}, - defaults: { - name: Math.random().toString() - } - }); + await User.sync({ force: true }); + + await User.findOrCreate({ + where: {}, + defaults: { + name: Math.random().toString() + } }); }); if (!['sqlite', 'mssql'].includes(current.dialect.name)) { - it('should not deadlock with no existing entries and no outer transaction', function() { + it('should not deadlock with no existing entries and no outer transaction', async function() { const User = this.sequelize.define('User', { email: { type: DataTypes.STRING, @@ -200,19 +196,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return Promise.all(_.range(50).map(i => { - return User.findOrCreate({ - where: { - email: `unique.email.${i}@sequelizejs.com`, - companyId: Math.floor(Math.random() * 5) - } - }); - })); - }); + await User.sync({ force: true }); + + await Promise.all(_.range(50).map(i => { + return User.findOrCreate({ + where: { + email: `unique.email.${i}@sequelizejs.com`, + companyId: Math.floor(Math.random() * 5) + } + }); + })); }); - it('should not deadlock with existing entries and no outer transaction', function() { + it('should not deadlock with existing entries and no outer transaction', async function() { const User = this.sequelize.define('User', { email: { type: DataTypes.STRING, @@ -224,28 +220,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return Promise.all(_.range(50).map(i => { - return User.findOrCreate({ - where: { - email: `unique.email.${i}@sequelizejs.com`, - companyId: 2 - } - }); - })).then(() => { - return Promise.all(_.range(50).map(i => { - return User.findOrCreate({ - where: { - email: `unique.email.${i}@sequelizejs.com`, - companyId: 2 - } - }); - })); + await User.sync({ force: true }); + + await Promise.all(_.range(50).map(i => { + return User.findOrCreate({ + where: { + email: `unique.email.${i}@sequelizejs.com`, + companyId: 2 + } }); - }); + })); + + await Promise.all(_.range(50).map(i => { + return User.findOrCreate({ + where: { + email: `unique.email.${i}@sequelizejs.com`, + companyId: 2 + } + }); + })); }); - it('should not deadlock with concurrency duplicate entries and no outer transaction', function() { + it('should not deadlock with concurrency duplicate entries and no outer transaction', async function() { const User = this.sequelize.define('User', { email: { type: DataTypes.STRING, @@ -257,20 +253,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return Promise.all(_.range(50).map(() => { - return User.findOrCreate({ - where: { - email: 'unique.email.1@sequelizejs.com', - companyId: 2 - } - }); - })); - }); + await User.sync({ force: true }); + + await Promise.all(_.range(50).map(() => { + return User.findOrCreate({ + where: { + email: 'unique.email.1@sequelizejs.com', + companyId: 2 + } + }); + })); }); } - it('should support special characters in defaults', function() { + it('should support special characters in defaults', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.INTEGER, @@ -281,19 +277,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.findOrCreate({ - where: { - objectId: 1 - }, - defaults: { - description: '$$ and !! and :: and ? and ^ and * and \'' - } - }); + await User.sync({ force: true }); + + await User.findOrCreate({ + where: { + objectId: 1 + }, + defaults: { + description: '$$ and !! and :: and ? and ^ and * and \'' + } }); }); - it('should support bools in defaults', function() { + it('should support bools in defaults', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.INTEGER, @@ -302,72 +298,70 @@ describe(Support.getTestDialectTeaser('Model'), () => { bool: DataTypes.BOOLEAN }); - return User.sync({ force: true }).then(() => { - return User.findOrCreate({ - where: { - objectId: 1 - }, - defaults: { - bool: false - } - }); + await User.sync({ force: true }); + + await User.findOrCreate({ + where: { + objectId: 1 + }, + defaults: { + bool: false + } }); }); - it('returns instance if already existent. Single find field.', function() { + it('returns instance if already existent. Single find field.', async function() { const data = { username: 'Username' }; - return this.User.create(data).then(user => { - return this.User.findOrCreate({ where: { - username: user.username - } }).then(([_user, created]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(created).to.be.false; - }); - }); + const user = await this.User.create(data); + + const [_user, created] = await this.User.findOrCreate({ where: { + username: user.username + } }); + + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(created).to.be.false; }); - it('Returns instance if already existent. Multiple find fields.', function() { + it('Returns instance if already existent. Multiple find fields.', async function() { const data = { username: 'Username', data: 'ThisIsData' }; - return this.User.create(data).then(user => { - return this.User.findOrCreate({ where: data }).then(([_user, created]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(_user.data).to.equal('ThisIsData'); - expect(created).to.be.false; - }); - }); + const user = await this.User.create(data); + const [_user, created] = await this.User.findOrCreate({ where: data }); + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(_user.data).to.equal('ThisIsData'); + expect(created).to.be.false; }); - it('does not include exception catcher in response', function() { + it('does not include exception catcher in response', async function() { const data = { username: 'Username', data: 'ThisIsData' }; - return this.User.findOrCreate({ + const [user0] = await this.User.findOrCreate({ + where: data, + defaults: {} + }); + + expect(user0.dataValues.sequelize_caught_exception).to.be.undefined; + + const [user] = await this.User.findOrCreate({ where: data, defaults: {} - }).then(([user]) => { - expect(user.dataValues.sequelize_caught_exception).to.be.undefined; - }).then(() => { - return this.User.findOrCreate({ - where: data, - defaults: {} - }).then(([user]) => { - expect(user.dataValues.sequelize_caught_exception).to.be.undefined; - }); }); + + expect(user.dataValues.sequelize_caught_exception).to.be.undefined; }); - it('creates new instance with default value.', function() { + it('creates new instance with default value.', async function() { const data = { username: 'Username' }, @@ -375,86 +369,87 @@ describe(Support.getTestDialectTeaser('Model'), () => { data: 'ThisIsData' }; - return this.User.findOrCreate({ where: data, defaults: default_values }).then(([user, created]) => { - expect(user.username).to.equal('Username'); - expect(user.data).to.equal('ThisIsData'); - expect(created).to.be.true; - }); + const [user, created] = await this.User.findOrCreate({ where: data, defaults: default_values }); + expect(user.username).to.equal('Username'); + expect(user.data).to.equal('ThisIsData'); + expect(created).to.be.true; }); - it('supports .or() (only using default values)', function() { - return this.User.findOrCreate({ + it('supports .or() (only using default values)', async function() { + const [user, created] = await this.User.findOrCreate({ where: Sequelize.or({ username: 'Fooobzz' }, { secretValue: 'Yolo' }), defaults: { username: 'Fooobzz', secretValue: 'Yolo' } - }).then(([user, created]) => { - expect(user.username).to.equal('Fooobzz'); - expect(user.secretValue).to.equal('Yolo'); - expect(created).to.be.true; }); + + expect(user.username).to.equal('Fooobzz'); + expect(user.secretValue).to.equal('Yolo'); + expect(created).to.be.true; }); - it('should ignore option returning', function() { - return this.User.findOrCreate({ + it('should ignore option returning', async function() { + const [user, created] = await this.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'ThisIsData' }, returning: false - }).then(([user, created]) => { - expect(user.username).to.equal('Username'); - expect(user.data).to.equal('ThisIsData'); - expect(created).to.be.true; }); + + expect(user.username).to.equal('Username'); + expect(user.data).to.equal('ThisIsData'); + expect(created).to.be.true; }); if (current.dialect.supports.transactions) { - it('should release transaction when meeting errors', function() { - const test = times => { + it('should release transaction when meeting errors', async function() { + const test = async times => { if (times > 10) { return true; } - return pTimeout(this.Student.findOrCreate({ - where: { - no: 1 - } - }), 1000) - .catch(e => { - if (e instanceof Sequelize.ValidationError) return test(times + 1); - if (e instanceof pTimeout.TimeoutError) throw new Error(e); - throw e; - }); + + try { + return await pTimeout(this.Student.findOrCreate({ + where: { + no: 1 + } + }), 1000); + } catch (e) { + if (e instanceof Sequelize.ValidationError) return test(times + 1); + if (e instanceof pTimeout.TimeoutError) throw new Error(e); + throw e; + } }; - return test(0); + await test(0); }); } describe('several concurrent calls', () => { if (current.dialect.supports.transactions) { - it('works with a transaction', function() { - return this.sequelize.transaction().then(transaction => { - return Promise.all([ - this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), - this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }) - ]).then(([first, second]) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1]; - - // Depending on execution order and MAGIC either the first OR the second call should return true - expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR - - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - - expect(firstInstance.id).to.equal(secondInstance.id); - - return transaction.commit(); - }); - }); + it('works with a transaction', async function() { + const transaction = await this.sequelize.transaction(); + + const [first, second] = await Promise.all([ + this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), + this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }) + ]); + + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1]; + + // Depending on execution order and MAGIC either the first OR the second call should return true + expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR + + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; + + expect(firstInstance.id).to.equal(secondInstance.id); + + await transaction.commit(); }); } - (dialect !== 'sqlite' && dialect !== 'mssql' ? it : it.skip)('should not fail silently with concurrency higher than pool, a unique constraint and a create hook resulting in mismatched values', function() { + (dialect !== 'sqlite' && dialect !== 'mssql' ? it : it.skip)('should not fail silently with concurrency higher than pool, a unique constraint and a create hook resulting in mismatched values', async function() { const User = this.sequelize.define('user', { username: { type: DataTypes.STRING, @@ -479,21 +474,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { 'mick ' ]; - return User.sync({ force: true }).then(() => { - return Promise.all( - names.map(username => { - return User.findOrCreate({ where: { username } }).catch(err => { - spy(); - expect(err.message).to.equal('user#findOrCreate: value used for username was not equal for both the find and the create calls, \'mick \' vs \'mick\''); - }); - }) - ); - }).then(() => { - expect(spy).to.have.been.called; - }); + await User.sync({ force: true }); + + await Promise.all( + names.map(async username => { + try { + return await User.findOrCreate({ where: { username } }); + } catch (err) { + spy(); + expect(err.message).to.equal('user#findOrCreate: value used for username was not equal for both the find and the create calls, \'mick \' vs \'mick\''); + } + }) + ); + + expect(spy).to.have.been.called; }); - (dialect !== 'sqlite' ? it : it.skip)('should error correctly when defaults contain a unique key without a transaction', function() { + (dialect !== 'sqlite' ? it : it.skip)('should error correctly when defaults contain a unique key without a transaction', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -505,92 +502,100 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'gottlieb' - }); - }).then(() => { - return Promise.all([User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - }).then(() => { + await User.sync({ force: true }); + + await User.create({ + username: 'gottlieb' + }); + + return Promise.all([(async () => { + try { + await User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb' + } + }); + throw new Error('I should have ben rejected'); - }).catch(err => { + } catch (err) { expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; expect(err.fields).to.be.ok; - }), User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - }).then(() => { + } + })(), (async () => { + try { + await User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb' + } + }); + throw new Error('I should have ben rejected'); - }).catch(err => { + } catch (err) { expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; expect(err.fields).to.be.ok; - })]); - }); + } + })()]); }); // Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off - (dialect !== 'sqlite' ? it : it.skip)('works without a transaction', function() { - return Promise.all([ + (dialect !== 'sqlite' ? it : it.skip)('works without a transaction', async function() { + const [first, second] = await Promise.all([ this.User.findOrCreate({ where: { uniqueName: 'winner' } }), this.User.findOrCreate({ where: { uniqueName: 'winner' } }) - ]).then(([first, second]) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1]; + ]); - // Depending on execution order and MAGIC either the first OR the second call should return true - expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1]; - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; + // Depending on execution order and MAGIC either the first OR the second call should return true + expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR - expect(firstInstance.id).to.equal(secondInstance.id); - }); + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; + + expect(firstInstance.id).to.equal(secondInstance.id); }); }); }); describe('findCreateFind', () => { - (dialect !== 'sqlite' ? it : it.skip)('should work with multiple concurrent calls', function() { - return Promise.all([ + (dialect !== 'sqlite' ? it : it.skip)('should work with multiple concurrent calls', async function() { + const [first, second, third] = await Promise.all([ this.User.findOrCreate({ where: { uniqueName: 'winner' } }), this.User.findOrCreate({ where: { uniqueName: 'winner' } }), this.User.findOrCreate({ where: { uniqueName: 'winner' } }) - ]).then(([first, second, third]) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1], - thirdInstance = third[0], - thirdCreated = third[1]; + ]); - expect([firstCreated, secondCreated, thirdCreated].filter(value => { - return value; - }).length).to.equal(1); + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1], + thirdInstance = third[0], + thirdCreated = third[1]; - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - expect(thirdInstance).to.be.ok; + expect([firstCreated, secondCreated, thirdCreated].filter(value => { + return value; + }).length).to.equal(1); - expect(firstInstance.id).to.equal(secondInstance.id); - expect(secondInstance.id).to.equal(thirdInstance.id); - }); + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; + expect(thirdInstance).to.be.ok; + + expect(firstInstance.id).to.equal(secondInstance.id); + expect(secondInstance.id).to.equal(thirdInstance.id); }); }); describe('create', () => { - it('works with multiple non-integer primary keys with a default value', function() { + it('works with multiple non-integer primary keys with a default value', async function() { const User = this.sequelize.define('User', { 'id1': { primaryKey: true, @@ -608,16 +613,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user).to.be.ok; - expect(user.id1).to.be.ok; - expect(user.id2).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + expect(user).to.be.ok; + expect(user.id1).to.be.ok; + expect(user.id2).to.be.ok; }); - it('should return an error for a unique constraint error', function() { + it('should return an error for a unique constraint error', async function() { const User = this.sequelize.define('User', { 'email': { type: DataTypes.STRING, @@ -629,40 +632,39 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ email: 'hello@sequelize.com' }).then(() => { - return User.create({ email: 'hello@sequelize.com' }).then(() => { - assert(false); - }).catch(err => { - expect(err).to.be.ok; - expect(err).to.be.an.instanceof(Error); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ email: 'hello@sequelize.com' }); + + try { + await User.create({ email: 'hello@sequelize.com' }); + assert(false); + } catch (err) { + expect(err).to.be.ok; + expect(err).to.be.an.instanceof(Error); + } }); - it('works without any primary key', function() { + it('works without any primary key', async function() { const Log = this.sequelize.define('log', { level: DataTypes.STRING }); Log.removeAttribute('id'); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Log.create({ level: 'info' }), Log.bulkCreate([ - { level: 'error' }, - { level: 'debug' } - ])]); - }).then(() => { - return Log.findAll(); - }).then(logs => { - logs.forEach(log => { - expect(log.get('id')).not.to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([Log.create({ level: 'info' }), Log.bulkCreate([ + { level: 'error' }, + { level: 'debug' } + ])]); + + const logs = await Log.findAll(); + logs.forEach(log => { + expect(log.get('id')).not.to.be.ok; }); }); - it('should be able to set createdAt and updatedAt if using silent: true', function() { + it('should be able to set createdAt and updatedAt if using silent: true', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }, { @@ -672,31 +674,31 @@ describe(Support.getTestDialectTeaser('Model'), () => { const createdAt = new Date(2012, 10, 10, 10, 10, 10); const updatedAt = new Date(2011, 11, 11, 11, 11, 11); - return User.sync({ force: true }).then(() => { - return User.create({ - createdAt, - updatedAt - }, { - silent: true - }).then(user => { - expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); - expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); - - return User.findOne({ - where: { - updatedAt: { - [Op.ne]: null - } - } - }).then(user => { - expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); - expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); - }); - }); + await User.sync({ force: true }); + + const user = await User.create({ + createdAt, + updatedAt + }, { + silent: true }); + + expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); + expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); + + const user0 = await User.findOne({ + where: { + updatedAt: { + [Op.ne]: null + } + } + }); + + expect(createdAt.getTime()).to.equal(user0.get('createdAt').getTime()); + expect(updatedAt.getTime()).to.equal(user0.get('updatedAt').getTime()); }); - it('works with custom timestamps with a default value', function() { + it('works with custom timestamps with a default value', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING, date_of_birth: DataTypes.DATE, @@ -721,18 +723,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { force: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user).to.be.ok; - expect(user.created_time).to.be.ok; - expect(user.updated_time).to.be.ok; - expect(user.created_time.getMilliseconds()).not.to.equal(0); - expect(user.updated_time.getMilliseconds()).not.to.equal(0); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + expect(user).to.be.ok; + expect(user.created_time).to.be.ok; + expect(user.updated_time).to.be.ok; + expect(user.created_time.getMilliseconds()).not.to.equal(0); + expect(user.updated_time.getMilliseconds()).not.to.equal(0); }); - it('works with custom timestamps and underscored', function() { + it('works with custom timestamps and underscored', async function() { const User = this.sequelize.define('User', { }, { @@ -741,49 +741,40 @@ describe(Support.getTestDialectTeaser('Model'), () => { underscored: true }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user).to.be.ok; - expect(user.createdAt).to.be.ok; - expect(user.updatedAt).to.be.ok; + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + expect(user).to.be.ok; + expect(user.createdAt).to.be.ok; + expect(user.updatedAt).to.be.ok; - expect(user.created_at).not.to.be.ok; - expect(user.updated_at).not.to.be.ok; - }); - }); + expect(user.created_at).not.to.be.ok; + expect(user.updated_at).not.to.be.ok; }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return this.sequelize.transaction().then(t => { - return this.User.create({ username: 'user' }, { transaction: t }).then(() => { - return this.User.count().then(count => { - expect(count).to.equal(0); - return t.commit().then(() => { - return this.User.count().then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const t = await this.sequelize.transaction(); + await this.User.create({ username: 'user' }, { transaction: t }); + const count = await this.User.count(); + expect(count).to.equal(0); + await t.commit(); + const count0 = await this.User.count(); + expect(count0).to.equal(1); }); } if (current.dialect.supports.returnValues) { describe('return values', () => { - it('should make the autoincremented values available on the returned instances', function() { + it('should make the autoincremented values available on the returned instances', async function() { const User = this.sequelize.define('user', {}); - return User.sync({ force: true }).then(() => { - return User.create({}, { returning: true }).then(user => { - expect(user.get('id')).to.be.ok; - expect(user.get('id')).to.equal(1); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}, { returning: true }); + expect(user.get('id')).to.be.ok; + expect(user.get('id')).to.equal(1); }); - it('should make the autoincremented values available on the returned instances with custom fields', function() { + it('should make the autoincremented values available on the returned instances with custom fields', async function() { const User = this.sequelize.define('user', { maId: { type: DataTypes.INTEGER, @@ -793,36 +784,33 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({}, { returning: true }).then(user => { - expect(user.get('maId')).to.be.ok; - expect(user.get('maId')).to.equal(1); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}, { returning: true }); + expect(user.get('maId')).to.be.ok; + expect(user.get('maId')).to.equal(1); }); }); } - it('is possible to use casting when creating an instance', function() { + it('is possible to use casting when creating an instance', async function() { const type = dialect === 'mysql' || dialect === 'mariadb' ? 'signed' : 'integer'; let match = false; - return this.User.create({ + const user = await this.User.create({ intVal: this.sequelize.cast('1', type) }, { logging(sql) { expect(sql).to.match(new RegExp(`CAST\\(N?'1' AS ${type.toUpperCase()}\\)`)); match = true; } - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.intVal).to.equal(1); - expect(match).to.equal(true); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.intVal).to.equal(1); + expect(match).to.equal(true); }); - it('is possible to use casting multiple times mixed in with other utilities', function() { + it('is possible to use casting multiple times mixed in with other utilities', async function() { let type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer'), match = false; @@ -830,7 +818,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed'); } - return this.User.create({ + const user = await this.User.create({ intVal: type }, { logging(sql) { @@ -841,75 +829,67 @@ describe(Support.getTestDialectTeaser('Model'), () => { } match = true; } - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.intVal).to.equal(-1); - expect(match).to.equal(true); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.intVal).to.equal(-1); + expect(match).to.equal(true); }); - it('is possible to just use .literal() to bypass escaping', function() { - return this.User.create({ + it('is possible to just use .literal() to bypass escaping', async function() { + const user = await this.User.create({ intVal: this.sequelize.literal(`CAST(1-2 AS ${dialect === 'mysql' ? 'SIGNED' : 'INTEGER'})`) - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.intVal).to.equal(-1); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.intVal).to.equal(-1); }); - it('is possible to use funtions when creating an instance', function() { - return this.User.create({ + it('is possible to use funtions when creating an instance', async function() { + const user = await this.User.create({ secretValue: this.sequelize.fn('upper', 'sequelize') - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.secretValue).to.equal('SEQUELIZE'); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.secretValue).to.equal('SEQUELIZE'); }); - it('should escape $ in sequelize functions arguments', function() { - return this.User.create({ + it('should escape $ in sequelize functions arguments', async function() { + const user = await this.User.create({ secretValue: this.sequelize.fn('upper', '$sequelize') - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.secretValue).to.equal('$SEQUELIZE'); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.secretValue).to.equal('$SEQUELIZE'); }); - it('should work with a non-id named uuid primary key columns', function() { + it('should work with a non-id named uuid primary key columns', async function() { const Monkey = this.sequelize.define('Monkey', { monkeyId: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, allowNull: false } }); - return this.sequelize.sync({ force: true }).then(() => { - return Monkey.create(); - }).then(monkey => { - expect(monkey.get('monkeyId')).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const monkey = await Monkey.create(); + expect(monkey.get('monkeyId')).to.be.ok; }); - it('is possible to use functions as default values', function() { + it('is possible to use functions as default values', async function() { let userWithDefaults; if (dialect.startsWith('postgres')) { - return this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"').then(() => { - userWithDefaults = this.sequelize.define('userWithDefaults', { - uuid: { - type: 'UUID', - defaultValue: this.sequelize.fn('uuid_generate_v4') - } - }); - - return userWithDefaults.sync({ force: true }).then(() => { - return userWithDefaults.create({}).then(user => { - // uuid validation regex taken from http://stackoverflow.com/a/13653180/800016 - expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - }); - }); + await this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); + userWithDefaults = this.sequelize.define('userWithDefaults', { + uuid: { + type: 'UUID', + defaultValue: this.sequelize.fn('uuid_generate_v4') + } }); + + await userWithDefaults.sync({ force: true }); + const user = await userWithDefaults.create({}); + // uuid validation regex taken from http://stackoverflow.com/a/13653180/800016 + expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + return; } if (dialect === 'sqlite') { // The definition here is a bit hacky. sqlite expects () around the expression for default values, so we call a function without a name @@ -921,119 +901,116 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return userWithDefaults.sync({ force: true }).then(() => { - return userWithDefaults.create({}).then(user => { - return userWithDefaults.findByPk(user.id).then(user => { - const now = new Date(); - const pad = number => number.toString().padStart(2, '0'); + await userWithDefaults.sync({ force: true }); + const user = await userWithDefaults.create({}); + const user0 = await userWithDefaults.findByPk(user.id); + const now = new Date(); + const pad = number => number.toString().padStart(2, '0'); - expect(user.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); - }); - }); - }); + expect(user0.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); + + return; } // functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016 - return void 0; }); if (dialect === 'postgres') { - it('does not cast arrays for postgresql insert', function() { + it('does not cast arrays for postgresql insert', async function() { const User = this.sequelize.define('UserWithArray', { myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) }, mystr: { type: Sequelize.ARRAY(Sequelize.STRING) } }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ myvals: [], mystr: [] }, { - logging(sql) { - test = true; - expect(sql).to.contain('INSERT INTO "UserWithArrays" ("id","myvals","mystr","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4)'); - } - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + + await User.create({ myvals: [], mystr: [] }, { + logging(sql) { + test = true; + expect(sql).to.contain('INSERT INTO "UserWithArrays" ("id","myvals","mystr","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4)'); + } }); + + expect(test).to.be.true; }); - it('does not cast arrays for postgres update', function() { + it('does not cast arrays for postgres update', async function() { const User = this.sequelize.define('UserWithArray', { myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) }, mystr: { type: Sequelize.ARRAY(Sequelize.STRING) } }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ myvals: [1, 2, 3, 4], mystr: ['One', 'Two', 'Three', 'Four'] }).then(user => { - user.myvals = []; - user.mystr = []; - return user.save({ - logging(sql) { - test = true; - expect(sql).to.contain('UPDATE "UserWithArrays" SET "myvals"=$1,"mystr"=$2,"updatedAt"=$3 WHERE "id" = $4'); - } - }); - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + const user = await User.create({ myvals: [1, 2, 3, 4], mystr: ['One', 'Two', 'Three', 'Four'] }); + user.myvals = []; + user.mystr = []; + + await user.save({ + logging(sql) { + test = true; + expect(sql).to.contain('UPDATE "UserWithArrays" SET "myvals"=$1,"mystr"=$2,"updatedAt"=$3 WHERE "id" = $4'); + } }); + + expect(test).to.be.true; }); } - it("doesn't allow duplicated records with unique:true", function() { + it("doesn't allow duplicated records with unique:true", async function() { const User = this.sequelize.define('UserWithUniqueUsername', { username: { type: Sequelize.STRING, unique: true } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(() => { - return User.create({ username: 'foo' }).catch(err => { - if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; - expect(err).to.be.ok; - }); - }); - }); + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + + try { + await User.create({ username: 'foo' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + expect(err).to.be.ok; + } }); if (dialect === 'postgres' || dialect === 'sqlite') { - it("doesn't allow case-insensitive duplicated records using CITEXT", function() { + it("doesn't allow case-insensitive duplicated records using CITEXT", async function() { const User = this.sequelize.define('UserWithUniqueCITEXT', { username: { type: Sequelize.CITEXT, unique: true } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }); - }).then(() => { - return User.create({ username: 'fOO' }); - }).catch(err => { + try { + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + await User.create({ username: 'fOO' }); + } catch (err) { if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; - }); + } }); } if (current.dialect.supports.index.functionBased) { - it("doesn't allow duplicated records with unique function based indexes", function() { + it("doesn't allow duplicated records with unique function based indexes", async function() { const User = this.sequelize.define('UserWithUniqueUsernameFunctionIndex', { username: Sequelize.STRING, email: { type: Sequelize.STRING, unique: true } }); - return User.sync({ force: true }).then(() => { + try { + await User.sync({ force: true }); const tableName = User.getTableName(); - return this.sequelize.query(`CREATE UNIQUE INDEX lower_case_username ON "${tableName}" ((lower(username)))`); - }).then(() => { - return User.create({ username: 'foo' }); - }).then(() => { - return User.create({ username: 'foo' }); - }).catch(err => { + await this.sequelize.query(`CREATE UNIQUE INDEX lower_case_username ON "${tableName}" ((lower(username)))`); + await User.create({ username: 'foo' }); + await User.create({ username: 'foo' }); + } catch (err) { if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; - }); + } }); } - it('raises an error if created object breaks definition constraints', function() { + it('raises an error if created object breaks definition constraints', async function() { const UserNull = this.sequelize.define('UserWithNonNullSmth', { username: { type: Sequelize.STRING, unique: true }, smth: { type: Sequelize.STRING, allowNull: false } @@ -1041,18 +1018,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.options.omitNull = false; - return UserNull.sync({ force: true }).then(() => { - return UserNull.create({ username: 'foo2', smth: null }).catch(err => { - expect(err).to.exist; + await UserNull.sync({ force: true }); - const smth1 = err.get('smth')[0] || {}; + try { + await UserNull.create({ username: 'foo2', smth: null }); + } catch (err) { + expect(err).to.exist; - expect(smth1.path).to.equal('smth'); - expect(smth1.type || smth1.origin).to.match(/notNull Violation/); - }); - }); + const smth1 = err.get('smth')[0] || {}; + + expect(smth1.path).to.equal('smth'); + expect(smth1.type || smth1.origin).to.match(/notNull Violation/); + } }); - it('raises an error if created object breaks definition constraints', function() { + it('raises an error if created object breaks definition constraints', async function() { const UserNull = this.sequelize.define('UserWithNonNullSmth', { username: { type: Sequelize.STRING, unique: true }, smth: { type: Sequelize.STRING, allowNull: false } @@ -1060,35 +1039,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.options.omitNull = false; - return UserNull.sync({ force: true }).then(() => { - return UserNull.create({ username: 'foo', smth: 'foo' }).then(() => { - return UserNull.create({ username: 'foo', smth: 'bar' }).catch(err => { - if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; - expect(err).to.be.ok; - }); - }); - }); + await UserNull.sync({ force: true }); + await UserNull.create({ username: 'foo', smth: 'foo' }); + + try { + await UserNull.create({ username: 'foo', smth: 'bar' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + expect(err).to.be.ok; + } }); - it('raises an error if saving an empty string into a column allowing null or URL', function() { + it('raises an error if saving an empty string into a column allowing null or URL', async function() { const StringIsNullOrUrl = this.sequelize.define('StringIsNullOrUrl', { str: { type: Sequelize.STRING, allowNull: true, validate: { isURL: true } } }); this.sequelize.options.omitNull = false; - return StringIsNullOrUrl.sync({ force: true }).then(() => { - return StringIsNullOrUrl.create({ str: null }).then(str1 => { - expect(str1.str).to.be.null; - return StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' }).then(str2 => { - expect(str2.str).to.equal('http://sequelizejs.org'); - return StringIsNullOrUrl.create({ str: '' }).catch(err => { - expect(err).to.exist; - expect(err.get('str')[0].message).to.match(/Validation isURL on str failed/); - }); - }); - }); - }); + await StringIsNullOrUrl.sync({ force: true }); + const str1 = await StringIsNullOrUrl.create({ str: null }); + expect(str1.str).to.be.null; + const str2 = await StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' }); + expect(str2.str).to.equal('http://sequelizejs.org'); + + try { + await StringIsNullOrUrl.create({ str: '' }); + } catch (err) { + expect(err).to.exist; + expect(err.get('str')[0].message).to.match(/Validation isURL on str failed/); + } }); it('raises an error if you mess up the datatype', function() { @@ -1105,34 +1085,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { }).to.throw(Error, 'Unrecognized datatype for attribute "UserBadDataType.activity_date"'); }); - it('sets a 64 bit int in bigint', function() { + it('sets a 64 bit int in bigint', async function() { const User = this.sequelize.define('UserWithBigIntFields', { big: Sequelize.BIGINT }); - return User.sync({ force: true }).then(() => { - return User.create({ big: '9223372036854775807' }).then(user => { - expect(user.big).to.be.equal('9223372036854775807'); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ big: '9223372036854775807' }); + expect(user.big).to.be.equal('9223372036854775807'); }); - it('sets auto increment fields', function() { + it('sets auto increment fields', async function() { const User = this.sequelize.define('UserWithAutoIncrementField', { userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false } }); - return User.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user.userid).to.equal(1); - return User.create({}).then(user => { - expect(user.userid).to.equal(2); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}); + expect(user.userid).to.equal(1); + const user0 = await User.create({}); + expect(user0.userid).to.equal(2); }); - it('allows the usage of options as attribute', function() { + it('allows the usage of options as attribute', async function() { const User = this.sequelize.define('UserWithNameAndOptions', { name: Sequelize.STRING, options: Sequelize.TEXT @@ -1140,60 +1115,55 @@ describe(Support.getTestDialectTeaser('Model'), () => { const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); - return User.sync({ force: true }).then(() => { - return User - .create({ name: 'John Doe', options }) - .then(user => { - expect(user.options).to.equal(options); - }); - }); + await User.sync({ force: true }); + + const user = await User + .create({ name: 'John Doe', options }); + + expect(user.options).to.equal(options); }); - it('allows sql logging', function() { + it('allows sql logging', async function() { const User = this.sequelize.define('UserWithUniqueNameAndNonNullSmth', { name: { type: Sequelize.STRING, unique: true }, smth: { type: Sequelize.STRING, allowNull: false } }); let test = false; - return User.sync({ force: true }).then(() => { - return User - .create({ name: 'Fluffy Bunny', smth: 'else' }, { - logging(sql) { - expect(sql).to.exist; - test = true; - expect(sql.toUpperCase()).to.contain('INSERT'); - } - }); - }).then(() => { - expect(test).to.be.true; - }); + await User.sync({ force: true }); + + await User + .create({ name: 'Fluffy Bunny', smth: 'else' }, { + logging(sql) { + expect(sql).to.exist; + test = true; + expect(sql.toUpperCase()).to.contain('INSERT'); + } + }); + + expect(test).to.be.true; }); - it('should only store the values passed in the whitelist', function() { + it('should only store the values passed in the whitelist', async function() { const data = { username: 'Peter', secretValue: '42' }; - return this.User.create(data, { fields: ['username'] }).then(user => { - return this.User.findByPk(user.id).then(_user => { - expect(_user.username).to.equal(data.username); - expect(_user.secretValue).not.to.equal(data.secretValue); - expect(_user.secretValue).to.equal(null); - }); - }); + const user = await this.User.create(data, { fields: ['username'] }); + const _user = await this.User.findByPk(user.id); + expect(_user.username).to.equal(data.username); + expect(_user.secretValue).not.to.equal(data.secretValue); + expect(_user.secretValue).to.equal(null); }); - it('should store all values if no whitelist is specified', function() { + it('should store all values if no whitelist is specified', async function() { const data = { username: 'Peter', secretValue: '42' }; - return this.User.create(data).then(user => { - return this.User.findByPk(user.id).then(_user => { - expect(_user.username).to.equal(data.username); - expect(_user.secretValue).to.equal(data.secretValue); - }); - }); + const user = await this.User.create(data); + const _user = await this.User.findByPk(user.id); + expect(_user.username).to.equal(data.username); + expect(_user.secretValue).to.equal(data.secretValue); }); - it('can omit autoincremental columns', function() { + it('can omit autoincremental columns', async function() { const data = { title: 'Iliad' }, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT], sync = [], @@ -1211,85 +1181,73 @@ describe(Support.getTestDialectTeaser('Model'), () => { sync.push(b.sync({ force: true })); }); - return Promise.all(sync).then(() => { - books.forEach((b, index) => { - promises.push(b.create(data).then(book => { - expect(book.title).to.equal(data.title); - expect(book.author).to.equal(data.author); - expect(books[index].rawAttributes.id.type instanceof dataTypes[index]).to.be.ok; - })); - }); - return Promise.all(promises); + await Promise.all(sync); + books.forEach((b, index) => { + promises.push((async () => { + const book = await b.create(data); + expect(book.title).to.equal(data.title); + expect(book.author).to.equal(data.author); + expect(books[index].rawAttributes.id.type instanceof dataTypes[index]).to.be.ok; + })()); }); + + await Promise.all(promises); }); - it('saves data with single quote', function() { + it('saves data with single quote', async function() { const quote = "single'quote"; - return this.User.create({ data: quote }).then(user => { - expect(user.data).to.equal(quote); - return this.User.findOne({ where: { id: user.id } }).then(user => { - expect(user.data).to.equal(quote); - }); - }); + const user = await this.User.create({ data: quote }); + expect(user.data).to.equal(quote); + const user0 = await this.User.findOne({ where: { id: user.id } }); + expect(user0.data).to.equal(quote); }); - it('saves data with double quote', function() { + it('saves data with double quote', async function() { const quote = 'double"quote'; - return this.User.create({ data: quote }).then(user => { - expect(user.data).to.equal(quote); - return this.User.findOne({ where: { id: user.id } }).then(user => { - expect(user.data).to.equal(quote); - }); - }); + const user = await this.User.create({ data: quote }); + expect(user.data).to.equal(quote); + const user0 = await this.User.findOne({ where: { id: user.id } }); + expect(user0.data).to.equal(quote); }); - it('saves stringified JSON data', function() { + it('saves stringified JSON data', async function() { const json = JSON.stringify({ key: 'value' }); - return this.User.create({ data: json }).then(user => { - expect(user.data).to.equal(json); - return this.User.findOne({ where: { id: user.id } }).then(user => { - expect(user.data).to.equal(json); - }); - }); + const user = await this.User.create({ data: json }); + expect(user.data).to.equal(json); + const user0 = await this.User.findOne({ where: { id: user.id } }); + expect(user0.data).to.equal(json); }); - it('stores the current date in createdAt', function() { - return this.User.create({ username: 'foo' }).then(user => { - expect(parseInt(+user.createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); - }); + it('stores the current date in createdAt', async function() { + const user = await this.User.create({ username: 'foo' }); + expect(parseInt(+user.createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); }); - it('allows setting custom IDs', function() { - return this.User.create({ id: 42 }).then(user => { - expect(user.id).to.equal(42); - return this.User.findByPk(42).then(user => { - expect(user).to.exist; - }); - }); + it('allows setting custom IDs', async function() { + const user = await this.User.create({ id: 42 }); + expect(user.id).to.equal(42); + const user0 = await this.User.findByPk(42); + expect(user0).to.exist; }); - it('should allow blank creates (with timestamps: false)', function() { + it('should allow blank creates (with timestamps: false)', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.create({}, { fields: [] }).then(worker => { - expect(worker).to.be.ok; - }); - }); + await Worker.sync(); + const worker = await Worker.create({}, { fields: [] }); + expect(worker).to.be.ok; }); - it('should allow truly blank creates', function() { + it('should allow truly blank creates', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.create({}, { fields: [] }).then(worker => { - expect(worker).to.be.ok; - }); - }); + await Worker.sync(); + const worker = await Worker.create({}, { fields: [] }); + expect(worker).to.be.ok; }); - it('should only set passed fields', function() { + it('should only set passed fields', async function() { const User = this.sequelize.define('User', { 'email': { type: DataTypes.STRING @@ -1299,61 +1257,55 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - name: 'Yolo Bear', - email: 'yolo@bear.com' - }, { - fields: ['name'] - }).then(user => { - expect(user.name).to.be.ok; - expect(user.email).not.to.be.ok; - return User.findByPk(user.id).then(user => { - expect(user.name).to.be.ok; - expect(user.email).not.to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ + name: 'Yolo Bear', + email: 'yolo@bear.com' + }, { + fields: ['name'] }); + + expect(user.name).to.be.ok; + expect(user.email).not.to.be.ok; + const user0 = await User.findByPk(user.id); + expect(user0.name).to.be.ok; + expect(user0.email).not.to.be.ok; }); - it('Works even when SQL query has a values of transaction keywords such as BEGIN TRANSACTION', function() { + it('Works even when SQL query has a values of transaction keywords such as BEGIN TRANSACTION', async function() { const Task = this.sequelize.define('task', { title: DataTypes.STRING }); - return Task.sync({ force: true }) - .then(() => { - return Promise.all([ - Task.create({ title: 'BEGIN TRANSACTION' }), - Task.create({ title: 'COMMIT TRANSACTION' }), - Task.create({ title: 'ROLLBACK TRANSACTION' }), - Task.create({ title: 'SAVE TRANSACTION' }) - ]); - }) - .then(newTasks => { - expect(newTasks).to.have.lengthOf(4); - expect(newTasks[0].title).to.equal('BEGIN TRANSACTION'); - expect(newTasks[1].title).to.equal('COMMIT TRANSACTION'); - expect(newTasks[2].title).to.equal('ROLLBACK TRANSACTION'); - expect(newTasks[3].title).to.equal('SAVE TRANSACTION'); - }); + await Task.sync({ force: true }); + + const newTasks = await Promise.all([ + Task.create({ title: 'BEGIN TRANSACTION' }), + Task.create({ title: 'COMMIT TRANSACTION' }), + Task.create({ title: 'ROLLBACK TRANSACTION' }), + Task.create({ title: 'SAVE TRANSACTION' }) + ]); + + expect(newTasks).to.have.lengthOf(4); + expect(newTasks[0].title).to.equal('BEGIN TRANSACTION'); + expect(newTasks[1].title).to.equal('COMMIT TRANSACTION'); + expect(newTasks[2].title).to.equal('ROLLBACK TRANSACTION'); + expect(newTasks[3].title).to.equal('SAVE TRANSACTION'); }); describe('enums', () => { - it('correctly restores enum values', function() { + it('correctly restores enum values', async function() { const Item = this.sequelize.define('Item', { state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] } }); - return Item.sync({ force: true }).then(() => { - return Item.create({ state: 'available' }).then(_item => { - return Item.findOne({ where: { state: 'available' } }).then(item => { - expect(item.id).to.equal(_item.id); - }); - }); - }); + await Item.sync({ force: true }); + const _item = await Item.create({ state: 'available' }); + const item = await Item.findOne({ where: { state: 'available' } }); + expect(item.id).to.equal(_item.id); }); - it('allows null values', function() { + it('allows null values', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM, @@ -1362,63 +1314,61 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: null }).then(_enum => { - expect(_enum.state).to.be.null; - }); - }); + await Enum.sync({ force: true }); + const _enum = await Enum.create({ state: null }); + expect(_enum.state).to.be.null; }); describe('when defined via { field: Sequelize.ENUM }', () => { - it('allows values passed as parameters', function() { + it('allows values passed as parameters', async function() { const Enum = this.sequelize.define('Enum', { state: Sequelize.ENUM('happy', 'sad') }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); - it('allows values passed as an array', function() { + it('allows values passed as an array', async function() { const Enum = this.sequelize.define('Enum', { state: Sequelize.ENUM(['happy', 'sad']) }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); }); describe('when defined via { field: { type: Sequelize.ENUM } }', () => { - it('allows values passed as parameters', function() { + it('allows values passed as parameters', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM('happy', 'sad') } }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); - it('allows values passed as an array', function() { + it('allows values passed as an array', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM(['happy', 'sad']) } }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); }); describe('can safely sync multiple times', () => { - it('through the factory', function() { + it('through the factory', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM, @@ -1427,14 +1377,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Enum.sync({ force: true }).then(() => { - return Enum.sync().then(() => { - return Enum.sync({ force: true }); - }); - }); + await Enum.sync({ force: true }); + await Enum.sync(); + + await Enum.sync({ force: true }); }); - it('through sequelize', function() { + it('through sequelize', async function() { this.sequelize.define('Enum', { state: { type: Sequelize.ENUM, @@ -1443,34 +1392,32 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.sync().then(() => { - return this.sequelize.sync({ force: true }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.sequelize.sync(); + + await this.sequelize.sync({ force: true }); }); }); }); }); - it('should return autoIncrement primary key (create)', function() { + it('should return autoIncrement primary key (create)', async function() { const Maya = this.sequelize.define('Maya', {}); const M1 = {}; - return Maya.sync({ force: true }).then(() => Maya.create(M1, { returning: true })) - .then(m => { - expect(m.id).to.be.eql(1); - }); + await Maya.sync({ force: true }); + const m = await Maya.create(M1, { returning: true }); + expect(m.id).to.be.eql(1); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.create({}, { + await this.User.create({}, { logging: spy - }).then(() => { - expect(spy.called).to.be.ok; }); + + expect(spy.called).to.be.ok; }); }); diff --git a/test/integration/model/create/include.test.js b/test/integration/model/create/include.test.js index 78984a323071..01b651523d6d 100644 --- a/test/integration/model/create/include.test.js +++ b/test/integration/model/create/include.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('create', () => { describe('include', () => { - it('should create data for BelongsTo relations', function() { + it('should create data for BelongsTo relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -32,35 +32,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - title: 'Chair', - User: { - first_name: 'Mick', - last_name: 'Broadstone' - } - }, { - include: [{ - model: User, - myOption: 'option' - }] - }).then(savedProduct => { - expect(savedProduct.isIncludeCreatedOnAfterCreate).to.be.true; - expect(savedProduct.User.createOptions.myOption).to.be.equal('option'); - expect(savedProduct.User.createOptions.parentRecord).to.be.equal(savedProduct); - return Product.findOne({ - where: { id: savedProduct.id }, - include: [User] - }).then(persistedProduct => { - expect(persistedProduct.User).to.be.ok; - expect(persistedProduct.User.first_name).to.be.equal('Mick'); - expect(persistedProduct.User.last_name).to.be.equal('Broadstone'); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + title: 'Chair', + User: { + first_name: 'Mick', + last_name: 'Broadstone' + } + }, { + include: [{ + model: User, + myOption: 'option' + }] }); + + expect(savedProduct.isIncludeCreatedOnAfterCreate).to.be.true; + expect(savedProduct.User.createOptions.myOption).to.be.equal('option'); + expect(savedProduct.User.createOptions.parentRecord).to.be.equal(savedProduct); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [User] + }); + + expect(persistedProduct.User).to.be.ok; + expect(persistedProduct.User.first_name).to.be.equal('Mick'); + expect(persistedProduct.User.last_name).to.be.equal('Broadstone'); }); - it('should create data for BelongsTo relations with no nullable FK', function() { + it('should create data for BelongsTo relations with no nullable FK', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -74,26 +75,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - title: 'Chair', - User: { - first_name: 'Mick' - } - }, { - include: [{ - model: User - }] - }).then(savedProduct => { - expect(savedProduct).to.exist; - expect(savedProduct.title).to.be.equal('Chair'); - expect(savedProduct.User).to.exist; - expect(savedProduct.User.first_name).to.be.equal('Mick'); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + title: 'Chair', + User: { + first_name: 'Mick' + } + }, { + include: [{ + model: User + }] }); + + expect(savedProduct).to.exist; + expect(savedProduct.title).to.be.equal('Chair'); + expect(savedProduct.User).to.exist; + expect(savedProduct.User.first_name).to.be.equal('Mick'); }); - it('should create data for BelongsTo relations with alias', function() { + it('should create data for BelongsTo relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -104,29 +105,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Creator = Product.belongsTo(User, { as: 'creator' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - title: 'Chair', - creator: { - first_name: 'Matt', - last_name: 'Hansen' - } - }, { - include: [Creator] - }).then(savedProduct => { - return Product.findOne({ - where: { id: savedProduct.id }, - include: [Creator] - }).then(persistedProduct => { - expect(persistedProduct.creator).to.be.ok; - expect(persistedProduct.creator.first_name).to.be.equal('Matt'); - expect(persistedProduct.creator.last_name).to.be.equal('Hansen'); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + title: 'Chair', + creator: { + first_name: 'Matt', + last_name: 'Hansen' + } + }, { + include: [Creator] + }); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [Creator] }); + + expect(persistedProduct.creator).to.be.ok; + expect(persistedProduct.creator.first_name).to.be.equal('Matt'); + expect(persistedProduct.creator.last_name).to.be.equal('Hansen'); }); - it('should create data for HasMany relations', function() { + it('should create data for HasMany relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -151,37 +152,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.hasMany(Tag); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - id: 1, - title: 'Chair', - Tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - include: [{ - model: Tag, - myOption: 'option' - }] - }).then(savedProduct => { - expect(savedProduct.areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedProduct.Tags[0].createOptions.myOption).to.be.equal('option'); - expect(savedProduct.Tags[0].createOptions.parentRecord).to.be.equal(savedProduct); - expect(savedProduct.Tags[1].createOptions.myOption).to.be.equal('option'); - expect(savedProduct.Tags[1].createOptions.parentRecord).to.be.equal(savedProduct); - return Product.findOne({ - where: { id: savedProduct.id }, - include: [Tag] - }).then(persistedProduct => { - expect(persistedProduct.Tags).to.be.ok; - expect(persistedProduct.Tags.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + id: 1, + title: 'Chair', + Tags: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + include: [{ + model: Tag, + myOption: 'option' + }] }); + + expect(savedProduct.areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedProduct.Tags[0].createOptions.myOption).to.be.equal('option'); + expect(savedProduct.Tags[0].createOptions.parentRecord).to.be.equal(savedProduct); + expect(savedProduct.Tags[1].createOptions.myOption).to.be.equal('option'); + expect(savedProduct.Tags[1].createOptions.parentRecord).to.be.equal(savedProduct); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [Tag] + }); + + expect(persistedProduct.Tags).to.be.ok; + expect(persistedProduct.Tags.length).to.equal(2); }); - it('should create data for HasMany relations with alias', function() { + it('should create data for HasMany relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -191,29 +193,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Categories = Product.hasMany(Tag, { as: 'categories' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - include: [Categories] - }).then(savedProduct => { - return Product.findOne({ - where: { id: savedProduct.id }, - include: [Categories] - }).then(persistedProduct => { - expect(persistedProduct.categories).to.be.ok; - expect(persistedProduct.categories.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + id: 1, + title: 'Chair', + categories: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + include: [Categories] }); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [Categories] + }); + + expect(persistedProduct.categories).to.be.ok; + expect(persistedProduct.categories.length).to.equal(2); }); - it('should create data for HasOne relations', function() { + it('should create data for HasOne relations', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -224,26 +226,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'Muzzy', - Task: { - title: 'Eat Clocks' - } - }, { - include: [Task] - }).then(savedUser => { - return User.findOne({ - where: { id: savedUser.id }, - include: [Task] - }).then(persistedUser => { - expect(persistedUser.Task).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'Muzzy', + Task: { + title: 'Eat Clocks' + } + }, { + include: [Task] }); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Task] + }); + + expect(persistedUser.Task).to.be.ok; }); - it('should create data for HasOne relations with alias', function() { + it('should create data for HasOne relations with alias', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -255,26 +257,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Job = User.hasOne(Task, { as: 'job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'Muzzy', - job: { - title: 'Eat Clocks' - } - }, { - include: [Job] - }).then(savedUser => { - return User.findOne({ - where: { id: savedUser.id }, - include: [Job] - }).then(persistedUser => { - expect(persistedUser.job).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'Muzzy', + job: { + title: 'Eat Clocks' + } + }, { + include: [Job] + }); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Job] }); + + expect(persistedUser.job).to.be.ok; }); - it('should create data for BelongsToMany relations', function() { + it('should create data for BelongsToMany relations', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }, { @@ -302,36 +304,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.belongsToMany(Task, { through: 'user_task' }); Task.belongsToMany(User, { through: 'user_task' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'John', - Tasks: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - include: [{ - model: Task, - myOption: 'option' - }] - }).then(savedUser => { - expect(savedUser.areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedUser.Tasks[0].createOptions.myOption).to.be.equal('option'); - expect(savedUser.Tasks[0].createOptions.parentRecord).to.be.equal(savedUser); - expect(savedUser.Tasks[1].createOptions.myOption).to.be.equal('option'); - expect(savedUser.Tasks[1].createOptions.parentRecord).to.be.equal(savedUser); - return User.findOne({ - where: { id: savedUser.id }, - include: [Task] - }).then(persistedUser => { - expect(persistedUser.Tasks).to.be.ok; - expect(persistedUser.Tasks.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'John', + Tasks: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + include: [{ + model: Task, + myOption: 'option' + }] }); + + expect(savedUser.areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedUser.Tasks[0].createOptions.myOption).to.be.equal('option'); + expect(savedUser.Tasks[0].createOptions.parentRecord).to.be.equal(savedUser); + expect(savedUser.Tasks[1].createOptions.myOption).to.be.equal('option'); + expect(savedUser.Tasks[1].createOptions.parentRecord).to.be.equal(savedUser); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Task] + }); + + expect(persistedUser.Tasks).to.be.ok; + expect(persistedUser.Tasks.length).to.equal(2); }); - it('should create data for polymorphic BelongsToMany relations', function() { + it('should create data for polymorphic BelongsToMany relations', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }, { @@ -390,48 +393,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({ - title: 'Polymorphic Associations', - tags: [ - { - name: 'polymorphic' - }, - { - name: 'associations' - } - ] - }, { - include: [{ - model: Tag, - as: 'tags', - through: { - model: ItemTag - } - }] - } - ); - }).then(savedPost => { - // The saved post should include the two tags - expect(savedPost.tags.length).to.equal(2); - // The saved post should be able to retrieve the two tags - // using the convenience accessor methods - return savedPost.getTags(); - }).then(savedTags => { - // All nested tags should be returned - expect(savedTags.length).to.equal(2); - }).then(() => { - return ItemTag.findAll(); - }).then(itemTags => { - // Two "through" models should be created - expect(itemTags.length).to.equal(2); - // And their polymorphic field should be correctly set to 'post' - expect(itemTags[0].taggable).to.equal('post'); - expect(itemTags[1].taggable).to.equal('post'); - }); + await this.sequelize.sync({ force: true }); + + const savedPost = await Post.create({ + title: 'Polymorphic Associations', + tags: [ + { + name: 'polymorphic' + }, + { + name: 'associations' + } + ] + }, { + include: [{ + model: Tag, + as: 'tags', + through: { + model: ItemTag + } + }] + } + ); + + // The saved post should include the two tags + expect(savedPost.tags.length).to.equal(2); + // The saved post should be able to retrieve the two tags + // using the convenience accessor methods + const savedTags = await savedPost.getTags(); + // All nested tags should be returned + expect(savedTags.length).to.equal(2); + const itemTags = await ItemTag.findAll(); + // Two "through" models should be created + expect(itemTags.length).to.equal(2); + // And their polymorphic field should be correctly set to 'post' + expect(itemTags[0].taggable).to.equal('post'); + expect(itemTags[1].taggable).to.equal('post'); }); - it('should create data for BelongsToMany relations with alias', function() { + it('should create data for BelongsToMany relations with alias', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); @@ -444,25 +444,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Jobs = User.belongsToMany(Task, { through: 'user_job', as: 'jobs' }); Task.belongsToMany(User, { through: 'user_job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'John', - jobs: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - include: [Jobs] - }).then(savedUser => { - return User.findOne({ - where: { id: savedUser.id }, - include: [Jobs] - }).then(persistedUser => { - expect(persistedUser.jobs).to.be.ok; - expect(persistedUser.jobs.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'John', + jobs: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + include: [Jobs] }); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Jobs] + }); + + expect(persistedUser.jobs).to.be.ok; + expect(persistedUser.jobs.length).to.equal(2); }); }); }); diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index 2ed6d3cc4bfe..cd348631be85 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -15,7 +15,7 @@ const chai = require('chai'), promiseProps = require('p-props'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -26,220 +26,211 @@ describe(Support.getTestDialectTeaser('Model'), () => { binary: DataTypes.STRING(16, true) }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('findAll', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findAll({ where: { username: 'foo' } }).then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - return User.findAll({ where: { username: 'foo' }, transaction: t }).then(users3 => { - expect(users1.length).to.equal(0); - expect(users2.length).to.equal(1); - expect(users3.length).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const users1 = await User.findAll({ where: { username: 'foo' } }); + const users2 = await User.findAll({ transaction: t }); + const users3 = await User.findAll({ where: { username: 'foo' }, transaction: t }); + expect(users1.length).to.equal(0); + expect(users2.length).to.equal(1); + expect(users3.length).to.equal(1); + await t.rollback(); }); } - it('should not crash on an empty where array', function() { - return this.User.findAll({ + it('should not crash on an empty where array', async function() { + await this.User.findAll({ where: [] }); }); - it('should throw on an attempt to fetch no attributes', function() { - return expect(this.User.findAll({ attributes: [] })).to.be.rejectedWith( + it('should throw on an attempt to fetch no attributes', async function() { + await expect(this.User.findAll({ attributes: [] })).to.be.rejectedWith( Sequelize.QueryError, /^Attempted a SELECT query.+without selecting any columns$/ ); }); - it('should not throw if overall attributes are nonempty', function() { + it('should not throw if overall attributes are nonempty', async function() { const Post = this.sequelize.define('Post', { foo: DataTypes.STRING }); const Comment = this.sequelize.define('Comment', { bar: DataTypes.STRING }); Post.hasMany(Comment, { as: 'comments' }); - return Post.sync({ force: true }) - .then(() => Comment.sync({ force: true })) - .then(() => { - // Should not throw in this case, even - // though `attributes: []` is set for the main model - return Post.findAll({ - raw: true, - attributes: [], - include: [ - { - model: Comment, - as: 'comments', - attributes: [ - [Sequelize.fn('COUNT', Sequelize.col('comments.id')), 'commentCount'] - ] - } + await Post.sync({ force: true }); + await Comment.sync({ force: true }); + + // Should not throw in this case, even + // though `attributes: []` is set for the main model + await Post.findAll({ + raw: true, + attributes: [], + include: [ + { + model: Comment, + as: 'comments', + attributes: [ + [Sequelize.fn('COUNT', Sequelize.col('comments.id')), 'commentCount'] ] - }); - }); + } + ] + }); }); describe('special where conditions/smartWhere object', () => { - beforeEach(function() { + beforeEach(async function() { this.buf = Buffer.alloc(16); this.buf.fill('\x01'); - return this.User.bulkCreate([ + + await this.User.bulkCreate([ { username: 'boo', intVal: 5, theDate: '2013-01-01 12:00' }, { username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00', binary: this.buf } ]); }); - it('should be able to find rows where attribute is in a list of values', function() { - return this.User.findAll({ + it('should be able to find rows where attribute is in a list of values', async function() { + const users = await this.User.findAll({ where: { username: ['boo', 'boo2'] } - }).then(users => { - expect(users).to.have.length(2); }); + + expect(users).to.have.length(2); }); - it('should not break when trying to find rows using an array of primary keys', function() { - return this.User.findAll({ + it('should not break when trying to find rows using an array of primary keys', async function() { + await this.User.findAll({ where: { id: [1, 2, 3] } }); }); - it('should not break when using smart syntax on binary fields', function() { - return this.User.findAll({ + it('should not break when using smart syntax on binary fields', async function() { + const users = await this.User.findAll({ where: { binary: [this.buf, this.buf] } - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].binary.toString()).to.equal(this.buf.toString()); - expect(users[0].username).to.equal('boo2'); }); + + expect(users).to.have.length(1); + expect(users[0].binary.toString()).to.equal(this.buf.toString()); + expect(users[0].username).to.equal('boo2'); }); - it('should be able to find a row using like', function() { - return this.User.findAll({ + it('should be able to find a row using like', async function() { + const users = await this.User.findAll({ where: { username: { [Op.like]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row using not like', function() { - return this.User.findAll({ + it('should be able to find a row using not like', async function() { + const users = await this.User.findAll({ where: { username: { [Op.notLike]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); if (dialect === 'postgres') { - it('should be able to find a row using ilike', function() { - return this.User.findAll({ + it('should be able to find a row using ilike', async function() { + const users = await this.User.findAll({ where: { username: { [Op.iLike]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row using not ilike', function() { - return this.User.findAll({ + it('should be able to find a row using not ilike', async function() { + const users = await this.User.findAll({ where: { username: { [Op.notILike]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); } - it('should be able to find a row between a certain date using the between shortcut', function() { - return this.User.findAll({ + it('should be able to find a row between a certain date using the between shortcut', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2013-01-02', '2013-01-11'] } } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row not between a certain integer using the not between shortcut', function() { - return this.User.findAll({ + it('should be able to find a row not between a certain integer using the not between shortcut', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.notBetween]: [8, 10] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to handle false/true values just fine...', function() { + it('should be able to handle false/true values just fine...', async function() { const User = this.User; - return User.bulkCreate([ + await User.bulkCreate([ { username: 'boo5', aBool: false }, { username: 'boo6', aBool: true } - ]).then(() => { - return User.findAll({ where: { aBool: false } }).then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo5'); - return User.findAll({ where: { aBool: true } }).then(_users => { - expect(_users).to.have.length(1); - expect(_users[0].username).to.equal('boo6'); - }); - }); - }); + ]); + + const users = await User.findAll({ where: { aBool: false } }); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo5'); + const _users = await User.findAll({ where: { aBool: true } }); + expect(_users).to.have.length(1); + expect(_users[0].username).to.equal('boo6'); }); - it('should be able to handle false/true values through associations as well...', function() { + it('should be able to handle false/true values through associations as well...', async function() { const User = this.User, Passports = this.sequelize.define('Passports', { isActive: Sequelize.BOOLEAN @@ -248,43 +239,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasMany(Passports); Passports.belongsTo(User); - return User.sync({ force: true }).then(() => { - return Passports.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]).then(() => { - return Passports.bulkCreate([ - { isActive: true }, - { isActive: false } - ]).then(() => { - return User.findByPk(1).then(user => { - return Passports.findByPk(1).then(passport => { - return user.setPassports([passport]).then(() => { - return User.findByPk(2).then(_user => { - return Passports.findByPk(2).then(_passport => { - return _user.setPassports([_passport]).then(() => { - return _user.getPassports({ where: { isActive: false } }).then(theFalsePassport => { - return user.getPassports({ where: { isActive: true } }).then(theTruePassport => { - expect(theFalsePassport).to.have.length(1); - expect(theFalsePassport[0].isActive).to.be.false; - expect(theTruePassport).to.have.length(1); - expect(theTruePassport[0].isActive).to.be.true; - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Passports.sync({ force: true }); + + await User.bulkCreate([ + { username: 'boo5', aBool: false }, + { username: 'boo6', aBool: true } + ]); - it('should be able to handle binary values through associations as well...', function() { + await Passports.bulkCreate([ + { isActive: true }, + { isActive: false } + ]); + + const user = await User.findByPk(1); + const passport = await Passports.findByPk(1); + await user.setPassports([passport]); + const _user = await User.findByPk(2); + const _passport = await Passports.findByPk(2); + await _user.setPassports([_passport]); + const theFalsePassport = await _user.getPassports({ where: { isActive: false } }); + const theTruePassport = await user.getPassports({ where: { isActive: true } }); + expect(theFalsePassport).to.have.length(1); + expect(theFalsePassport[0].isActive).to.be.false; + expect(theTruePassport).to.have.length(1); + expect(theTruePassport[0].isActive).to.be.true; + }); + + it('should be able to handle binary values through associations as well...', async function() { const User = this.User; const Binary = this.sequelize.define('Binary', { id: { @@ -299,440 +281,431 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.belongsTo(Binary, { foreignKey: 'binary' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]).then(() => { - return Binary.bulkCreate([ - { id: buf1 }, - { id: buf2 } - ]).then(() => { - return User.findByPk(1).then(user => { - return Binary.findByPk(buf1).then(binary => { - return user.setBinary(binary).then(() => { - return User.findByPk(2).then(_user => { - return Binary.findByPk(buf2).then(_binary => { - return _user.setBinary(_binary).then(() => { - return _user.getBinary().then(_binaryRetrieved => { - return user.getBinary().then(binaryRetrieved => { - expect(binaryRetrieved.id).to.have.length(16); - expect(_binaryRetrieved.id).to.have.length(16); - expect(binaryRetrieved.id.toString()).to.be.equal(buf1.toString()); - expect(_binaryRetrieved.id.toString()).to.be.equal(buf2.toString()); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); - it('should be able to find a row between a certain date', function() { - return this.User.findAll({ + await User.bulkCreate([ + { username: 'boo5', aBool: false }, + { username: 'boo6', aBool: true } + ]); + + await Binary.bulkCreate([ + { id: buf1 }, + { id: buf2 } + ]); + + const user = await User.findByPk(1); + const binary = await Binary.findByPk(buf1); + await user.setBinary(binary); + const _user = await User.findByPk(2); + const _binary = await Binary.findByPk(buf2); + await _user.setBinary(_binary); + const _binaryRetrieved = await _user.getBinary(); + const binaryRetrieved = await user.getBinary(); + expect(binaryRetrieved.id).to.have.length(16); + expect(_binaryRetrieved.id).to.have.length(16); + expect(binaryRetrieved.id.toString()).to.be.equal(buf1.toString()); + expect(_binaryRetrieved.id.toString()).to.be.equal(buf2.toString()); + }); + + it('should be able to find a row between a certain date', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2013-01-02', '2013-01-11'] } } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row between a certain date and an additional where clause', function() { - return this.User.findAll({ + it('should be able to find a row between a certain date and an additional where clause', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2013-01-02', '2013-01-11'] }, intVal: 10 } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row not between a certain integer', function() { - return this.User.findAll({ + it('should be able to find a row not between a certain integer', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.notBetween]: [8, 10] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using not between and between logic', function() { - return this.User.findAll({ + it('should be able to find a row using not between and between logic', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2012-12-10', '2013-01-02'], [Op.notBetween]: ['2013-01-04', '2013-01-20'] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using not between and between logic with dates', function() { - return this.User.findAll({ + it('should be able to find a row using not between and between logic with dates', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: [new Date('2012-12-10'), new Date('2013-01-02')], [Op.notBetween]: [new Date('2013-01-04'), new Date('2013-01-20')] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using greater than or equal to logic with dates', function() { - return this.User.findAll({ + it('should be able to find a row using greater than or equal to logic with dates', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.gte]: new Date('2013-01-09') } } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row using greater than or equal to', function() { - return this.User.findOne({ + it('should be able to find a row using greater than or equal to', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.gte]: 6 } } - }).then(user => { - expect(user.username).to.equal('boo2'); - expect(user.intVal).to.equal(10); }); + + expect(user.username).to.equal('boo2'); + expect(user.intVal).to.equal(10); }); - it('should be able to find a row using greater than', function() { - return this.User.findOne({ + it('should be able to find a row using greater than', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.gt]: 5 } } - }).then(user => { - expect(user.username).to.equal('boo2'); - expect(user.intVal).to.equal(10); }); + + expect(user.username).to.equal('boo2'); + expect(user.intVal).to.equal(10); }); - it('should be able to find a row using lesser than or equal to', function() { - return this.User.findOne({ + it('should be able to find a row using lesser than or equal to', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.lte]: 5 } } - }).then(user => { - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); }); + + expect(user.username).to.equal('boo'); + expect(user.intVal).to.equal(5); }); - it('should be able to find a row using lesser than', function() { - return this.User.findOne({ + it('should be able to find a row using lesser than', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.lt]: 6 } } - }).then(user => { - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); }); + + expect(user.username).to.equal('boo'); + expect(user.intVal).to.equal(5); }); - it('should have no problem finding a row using lesser and greater than', function() { - return this.User.findAll({ + it('should have no problem finding a row using lesser and greater than', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.lt]: 6, [Op.gt]: 4 } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using not equal to logic', function() { - return this.User.findOne({ + it('should be able to find a row using not equal to logic', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.ne]: 10 } } - }).then(user => { - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); }); + + expect(user.username).to.equal('boo'); + expect(user.intVal).to.equal(5); }); - it('should be able to find multiple users with any of the special where logic properties', function() { - return this.User.findAll({ + it('should be able to find multiple users with any of the special where logic properties', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.lte]: 10 } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); - expect(users[1].username).to.equal('boo2'); - expect(users[1].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); + expect(users[1].username).to.equal('boo2'); + expect(users[1].intVal).to.equal(10); }); if (dialect === 'postgres' || dialect === 'sqlite') { - it('should be able to find multiple users with case-insensitive on CITEXT type', function() { + it('should be able to find multiple users with case-insensitive on CITEXT type', async function() { const User = this.sequelize.define('UsersWithCaseInsensitiveName', { username: Sequelize.CITEXT }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'lowercase' }, - { username: 'UPPERCASE' }, - { username: 'MIXEDcase' } - ]); - }).then(() => { - return User.findAll({ - where: { username: ['LOWERCASE', 'uppercase', 'mixedCase'] }, - order: [['id', 'ASC']] - }); - }).then(users => { - expect(users[0].username).to.equal('lowercase'); - expect(users[1].username).to.equal('UPPERCASE'); - expect(users[2].username).to.equal('MIXEDcase'); + await User.sync({ force: true }); + + await User.bulkCreate([ + { username: 'lowercase' }, + { username: 'UPPERCASE' }, + { username: 'MIXEDcase' } + ]); + + const users = await User.findAll({ + where: { username: ['LOWERCASE', 'uppercase', 'mixedCase'] }, + order: [['id', 'ASC']] }); + + expect(users[0].username).to.equal('lowercase'); + expect(users[1].username).to.equal('UPPERCASE'); + expect(users[2].username).to.equal('MIXEDcase'); }); } }); describe('eager loading', () => { - it('should not ignore where condition with empty includes, #8771', function() { - return this.User.bulkCreate([ + it('should not ignore where condition with empty includes, #8771', async function() { + await this.User.bulkCreate([ { username: 'D.E.N.N.I.S', intVal: 6 }, { username: 'F.R.A.N.K', intVal: 5 }, { username: 'W.I.L.D C.A.R.D', intVal: 8 } - ]).then(() => this.User.findAll({ + ]); + + const users = await this.User.findAll({ where: { intVal: 8 }, include: [] - })).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.be.equal('W.I.L.D C.A.R.D'); }); + + expect(users).to.have.length(1); + expect(users[0].get('username')).to.be.equal('W.I.L.D C.A.R.D'); }); describe('belongsTo', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('TaskBelongsTo', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Task.belongsTo(this.Worker); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.task.setWorker(this.worker); - }); - }); - }); - }); + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + + await this.task.setWorker(this.worker); }); - it('throws an error about unexpected input if include contains a non-object', function() { - return this.Worker.findAll({ include: [1] }).catch(err => { + it('throws an error about unexpected input if include contains a non-object', async function() { + try { + await this.Worker.findAll({ include: [1] }); + } catch (err) { expect(err.message).to.equal('Include unexpected. Element has to be either a Model, an Association or an object.'); - }); + } }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Worker.findAll({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Worker.findAll({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('TaskBelongsTo is not associated to Worker!'); - }); + } }); - it('returns the associated worker via task.worker', function() { - return this.Task.findAll({ + it('returns the associated worker via task.worker', async function() { + const tasks = await this.Task.findAll({ where: { title: 'homework' }, include: [this.Worker] - }).then(tasks => { - expect(tasks).to.exist; - expect(tasks[0].Worker).to.exist; - expect(tasks[0].Worker.name).to.equal('worker'); }); + + expect(tasks).to.exist; + expect(tasks[0].Worker).to.exist; + expect(tasks[0].Worker.name).to.equal('worker'); }); - it('returns the associated worker via task.worker, using limit and sort', function() { - return this.Task.findAll({ + it('returns the associated worker via task.worker, using limit and sort', async function() { + const tasks = await this.Task.findAll({ where: { title: 'homework' }, include: [this.Worker], limit: 1, order: [['title', 'DESC']] - }).then(tasks => { - expect(tasks).to.exist; - expect(tasks[0].Worker).to.exist; - expect(tasks[0].Worker.name).to.equal('worker'); }); + + expect(tasks).to.exist; + expect(tasks[0].Worker).to.exist; + expect(tasks[0].Worker.name).to.equal('worker'); }); }); describe('hasOne', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('TaskHasOne', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Worker.hasOne(this.Task); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setTaskHasOne(this.task); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findAll({ include: [this.Worker] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setTaskHasOne(this.task); + }); + + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findAll({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('Worker is not associated to TaskHasOne!'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [this.Task] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].TaskHasOne).to.exist; - expect(workers[0].TaskHasOne.title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].TaskHasOne).to.exist; + expect(workers[0].TaskHasOne.title).to.equal('homework'); }); }); describe('hasOne with alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Worker.hasOne(this.Task, { as: 'ToDo' }); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setToDo(this.task); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findAll({ include: [this.Task] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setToDo(this.task); + }); + + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findAll({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You must use the \'as\' keyword to specify the alias within your include statement.'); - }); + } }); - it('throws an error if alias is not associated', function() { - return this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error if alias is not associated', async function() { + try { + await this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDo).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].ToDo).to.exist; - expect(workers[0].ToDo.title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].ToDo).to.exist; + expect(workers[0].ToDo.title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(workers => { - expect(workers[0].ToDo.title).to.equal('homework'); }); + + expect(workers[0].ToDo.title).to.equal('homework'); }); }); describe('hasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('worker', { name: Sequelize.STRING }); this.Worker.hasMany(this.Task); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setTasks([this.task]); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findAll({ include: [this.Worker] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setTasks([this.task]); + }); + + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findAll({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('worker is not associated to task!'); - }); + } }); - it('returns the associated tasks via worker.tasks', function() { - return this.Worker.findAll({ + it('returns the associated tasks via worker.tasks', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [this.Task] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].tasks).to.exist; - expect(workers[0].tasks[0].title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].tasks).to.exist; + expect(workers[0].tasks[0].title).to.equal('homework'); }); // https://github.com/sequelize/sequelize/issues/8739 - it('supports sorting on renamed sub-query attribute', function() { + it('supports sorting on renamed sub-query attribute', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -742,30 +715,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Project = this.sequelize.define('project', { title: Sequelize.STRING }); User.hasMany(Project); - return User.sync({ force: true }) - .then(() => Project.sync({ force: true })) - .then(() => { - return User.bulkCreate([ - { name: 'a' }, - { name: 'b' }, - { name: 'c' } - ]); - }) - .then(() => { - return User.findAll({ - order: ['name'], - limit: 2, // to force use of a sub-query - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('a'); - expect(users[1].name).to.equal('b'); - }); + await User.sync({ force: true }); + await Project.sync({ force: true }); + + await User.bulkCreate([ + { name: 'a' }, + { name: 'b' }, + { name: 'c' } + ]); + + const users = await User.findAll({ + order: ['name'], + limit: 2, // to force use of a sub-query + include: [Project] + }); + + expect(users).to.have.lengthOf(2); + expect(users[0].name).to.equal('a'); + expect(users[1].name).to.equal('b'); }); - it('supports sorting DESC on renamed sub-query attribute', function() { + it('supports sorting DESC on renamed sub-query attribute', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -775,30 +745,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Project = this.sequelize.define('project', { title: Sequelize.STRING }); User.hasMany(Project); - return User.sync({ force: true }) - .then(() => Project.sync({ force: true })) - .then(() => { - return User.bulkCreate([ - { name: 'a' }, - { name: 'b' }, - { name: 'c' } - ]); - }) - .then(() => { - return User.findAll({ - order: [['name', 'DESC']], - limit: 2, - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('c'); - expect(users[1].name).to.equal('b'); - }); + await User.sync({ force: true }); + await Project.sync({ force: true }); + + await User.bulkCreate([ + { name: 'a' }, + { name: 'b' }, + { name: 'c' } + ]); + + const users = await User.findAll({ + order: [['name', 'DESC']], + limit: 2, + include: [Project] + }); + + expect(users).to.have.lengthOf(2); + expect(users[0].name).to.equal('c'); + expect(users[1].name).to.equal('b'); }); - it('supports sorting on multiple renamed sub-query attributes', function() { + it('supports sorting on multiple renamed sub-query attributes', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -812,133 +779,124 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Project = this.sequelize.define('project', { title: Sequelize.STRING }); User.hasMany(Project); - return User.sync({ force: true }) - .then(() => Project.sync({ force: true })) - .then(() => { - return User.bulkCreate([ - { name: 'a', age: 1 }, - { name: 'a', age: 2 }, - { name: 'b', age: 3 } - ]); - }) - .then(() => { - return User.findAll({ - order: [['name', 'ASC'], ['age', 'DESC']], - limit: 2, - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('a'); - expect(users[0].age).to.equal(2); - expect(users[1].name).to.equal('a'); - expect(users[1].age).to.equal(1); - }) - .then(() => { - return User.findAll({ - order: [['name', 'DESC'], 'age'], - limit: 2, - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('b'); - expect(users[1].name).to.equal('a'); - expect(users[1].age).to.equal(1); - }); + await User.sync({ force: true }); + await Project.sync({ force: true }); + + await User.bulkCreate([ + { name: 'a', age: 1 }, + { name: 'a', age: 2 }, + { name: 'b', age: 3 } + ]); + + const users0 = await User.findAll({ + order: [['name', 'ASC'], ['age', 'DESC']], + limit: 2, + include: [Project] + }); + + expect(users0).to.have.lengthOf(2); + expect(users0[0].name).to.equal('a'); + expect(users0[0].age).to.equal(2); + expect(users0[1].name).to.equal('a'); + expect(users0[1].age).to.equal(1); + + const users = await User.findAll({ + order: [['name', 'DESC'], 'age'], + limit: 2, + include: [Project] + }); + + expect(users).to.have.lengthOf(2); + expect(users[0].name).to.equal('b'); + expect(users[1].name).to.equal('a'); + expect(users[1].age).to.equal(1); }); }); describe('hasMany with alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Worker.hasMany(this.Task, { as: 'ToDos' }); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setToDos([this.task]); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findAll({ include: [this.Task] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setToDos([this.task]); + }); + + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findAll({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You must use the \'as\' keyword to specify the alias within your include statement.'); - }); + } }); - it('throws an error if alias is not associated', function() { - return this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error if alias is not associated', async function() { + try { + await this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDos).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].ToDos).to.exist; - expect(workers[0].ToDos[0].title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].ToDos).to.exist; + expect(workers[0].ToDos[0].title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(workers => { - expect(workers[0].ToDos[0].title).to.equal('homework'); }); + + expect(workers[0].ToDos[0].title).to.equal('homework'); }); }); describe('queryOptions', () => { - beforeEach(function() { - return this.User.create({ username: 'barfooz' }).then(user => { - this.user = user; - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'barfooz' }); + this.user = user; }); - it('should return a DAO when queryOptions are not set', function() { - return this.User.findAll({ where: { username: 'barfooz' } }).then(users => { - users.forEach(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when queryOptions are not set', async function() { + const users = await this.User.findAll({ where: { username: 'barfooz' } }); + users.forEach(user => { + expect(user).to.be.instanceOf(this.User); }); }); - it('should return a DAO when raw is false', function() { - return this.User.findAll({ where: { username: 'barfooz' }, raw: false }).then(users => { - users.forEach(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when raw is false', async function() { + const users = await this.User.findAll({ where: { username: 'barfooz' }, raw: false }); + users.forEach(user => { + expect(user).to.be.instanceOf(this.User); }); }); - it('should return raw data when raw is true', function() { - return this.User.findAll({ where: { username: 'barfooz' }, raw: true }).then(users => { - users.forEach(user => { - expect(user).to.not.be.instanceOf(this.User); - expect(users[0]).to.be.instanceOf(Object); - }); + it('should return raw data when raw is true', async function() { + const users = await this.User.findAll({ where: { username: 'barfooz' }, raw: true }); + users.forEach(user => { + expect(user).to.not.be.instanceOf(this.User); + expect(users[0]).to.be.instanceOf(Object); }); }); }); describe('include all', () => { - beforeEach(function() { + beforeEach(async function() { this.Continent = this.sequelize.define('continent', { name: Sequelize.STRING }); this.Country = this.sequelize.define('country', { name: Sequelize.STRING }); this.Industry = this.sequelize.define('industry', { name: Sequelize.STRING }); @@ -953,88 +911,84 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Country.hasMany(this.Person, { as: 'residents', foreignKey: 'CountryResidentId' }); this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - europe: this.Continent.create({ name: 'Europe' }), - england: this.Country.create({ name: 'England' }), - coal: this.Industry.create({ name: 'Coal' }), - bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }) - }).then(r => { - _.forEach(r, (item, itemName) => { - this[itemName] = item; - }); - return Promise.all([ - this.england.setContinent(this.europe), - this.england.addIndustry(this.coal), - this.bob.setCountry(this.england), - this.bob.setCountryResident(this.england) - ]); - }); - }); - }); + await this.sequelize.sync({ force: true }); - it('includes all associations', function() { - return this.Country.findAll({ include: [{ all: true }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].industries).to.exist; - expect(countries[0].people).to.exist; - expect(countries[0].residents).to.exist; + const r = await promiseProps({ + europe: this.Continent.create({ name: 'Europe' }), + england: this.Country.create({ name: 'England' }), + coal: this.Industry.create({ name: 'Coal' }), + bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }) }); - }); - it('includes specific type of association', function() { - return this.Country.findAll({ include: [{ all: 'BelongsTo' }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].industries).not.to.exist; - expect(countries[0].people).not.to.exist; - expect(countries[0].residents).not.to.exist; - }); - }); - - it('utilises specified attributes', function() { - return this.Country.findAll({ include: [{ all: 'HasMany', attributes: ['name'] }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].people).to.exist; - expect(countries[0].people[0]).to.exist; - expect(countries[0].people[0].name).not.to.be.undefined; - expect(countries[0].people[0].lastName).to.be.undefined; - expect(countries[0].residents).to.exist; - expect(countries[0].residents[0]).to.exist; - expect(countries[0].residents[0].name).not.to.be.undefined; - expect(countries[0].residents[0].lastName).to.be.undefined; + _.forEach(r, (item, itemName) => { + this[itemName] = item; }); - }); - it('is over-ruled by specified include', function() { - return this.Country.findAll({ include: [{ all: true }, { model: this.Continent, attributes: ['id'] }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].continent.name).to.be.undefined; - }); - }); - - it('includes all nested associations', function() { - return this.Continent.findAll({ include: [{ all: true, nested: true }] }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].industries).to.exist; - expect(continents[0].countries[0].people).to.exist; - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].continent).not.to.exist; - }); + await Promise.all([ + this.england.setContinent(this.europe), + this.england.addIndustry(this.coal), + this.bob.setCountry(this.england), + this.bob.setCountryResident(this.england) + ]); + }); + + it('includes all associations', async function() { + const countries = await this.Country.findAll({ include: [{ all: true }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].continent).to.exist; + expect(countries[0].industries).to.exist; + expect(countries[0].people).to.exist; + expect(countries[0].residents).to.exist; + }); + + it('includes specific type of association', async function() { + const countries = await this.Country.findAll({ include: [{ all: 'BelongsTo' }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].continent).to.exist; + expect(countries[0].industries).not.to.exist; + expect(countries[0].people).not.to.exist; + expect(countries[0].residents).not.to.exist; + }); + + it('utilises specified attributes', async function() { + const countries = await this.Country.findAll({ include: [{ all: 'HasMany', attributes: ['name'] }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].people).to.exist; + expect(countries[0].people[0]).to.exist; + expect(countries[0].people[0].name).not.to.be.undefined; + expect(countries[0].people[0].lastName).to.be.undefined; + expect(countries[0].residents).to.exist; + expect(countries[0].residents[0]).to.exist; + expect(countries[0].residents[0].name).not.to.be.undefined; + expect(countries[0].residents[0].lastName).to.be.undefined; + }); + + it('is over-ruled by specified include', async function() { + const countries = await this.Country.findAll({ include: [{ all: true }, { model: this.Continent, attributes: ['id'] }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].continent).to.exist; + expect(countries[0].continent.name).to.be.undefined; + }); + + it('includes all nested associations', async function() { + const continents = await this.Continent.findAll({ include: [{ all: true, nested: true }] }); + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].industries).to.exist; + expect(continents[0].countries[0].people).to.exist; + expect(continents[0].countries[0].residents).to.exist; + expect(continents[0].countries[0].continent).not.to.exist; }); }); describe('properly handles attributes:[] cases', () => { - beforeEach(function() { + beforeEach(async function() { this.Animal = this.sequelize.define('Animal', { name: Sequelize.STRING, age: Sequelize.INTEGER @@ -1049,44 +1003,46 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Kingdom.belongsToMany(this.Animal, { through: this.AnimalKingdom }); - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Animal.create({ name: 'Dog', age: 20 }), - this.Animal.create({ name: 'Cat', age: 30 }), - this.Animal.create({ name: 'Peacock', age: 25 }), - this.Animal.create({ name: 'Fish', age: 100 }) - ])) - .then(([a1, a2, a3, a4]) => Promise.all([ - this.Kingdom.create({ name: 'Earth' }), - this.Kingdom.create({ name: 'Water' }), - this.Kingdom.create({ name: 'Wind' }) - ]).then(([k1, k2, k3]) => - Promise.all([ - k1.addAnimals([a1, a2]), - k2.addAnimals([a4]), - k3.addAnimals([a3]) - ]) - )); - }); - - it('N:M with ignoring include.attributes only', function() { - return this.Kingdom.findAll({ + await this.sequelize.sync({ force: true }); + + const [a1, a2, a3, a4] = await Promise.all([ + this.Animal.create({ name: 'Dog', age: 20 }), + this.Animal.create({ name: 'Cat', age: 30 }), + this.Animal.create({ name: 'Peacock', age: 25 }), + this.Animal.create({ name: 'Fish', age: 100 }) + ]); + + const [k1, k2, k3] = await Promise.all([ + this.Kingdom.create({ name: 'Earth' }), + this.Kingdom.create({ name: 'Water' }), + this.Kingdom.create({ name: 'Wind' }) + ]); + + await Promise.all([ + k1.addAnimals([a1, a2]), + k2.addAnimals([a4]), + k3.addAnimals([a3]) + ]); + }); + + it('N:M with ignoring include.attributes only', async function() { + const kingdoms = await this.Kingdom.findAll({ include: [{ model: this.Animal, where: { age: { [Op.gte]: 29 } }, attributes: [] }] - }).then(kingdoms => { - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - // include.attributes:[] , model doesn't exists - expect(kingdom.Animals).to.not.exist; - }); + }); + + expect(kingdoms.length).to.be.eql(2); + kingdoms.forEach(kingdom => { + // include.attributes:[] , model doesn't exists + expect(kingdom.Animals).to.not.exist; }); }); - it('N:M with ignoring through.attributes only', function() { - return this.Kingdom.findAll({ + it('N:M with ignoring through.attributes only', async function() { + const kingdoms = await this.Kingdom.findAll({ include: [{ model: this.Animal, where: { age: { [Op.gte]: 29 } }, @@ -1094,17 +1050,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { attributes: [] } }] - }).then(kingdoms => { - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - expect(kingdom.Animals).to.exist; // include model exists - expect(kingdom.Animals[0].AnimalKingdom).to.not.exist; // through doesn't exists - }); + }); + + expect(kingdoms.length).to.be.eql(2); + kingdoms.forEach(kingdom => { + expect(kingdom.Animals).to.exist; // include model exists + expect(kingdom.Animals[0].AnimalKingdom).to.not.exist; // through doesn't exists }); }); - it('N:M with ignoring include.attributes but having through.attributes', function() { - return this.Kingdom.findAll({ + it('N:M with ignoring include.attributes but having through.attributes', async function() { + const kingdoms = await this.Kingdom.findAll({ include: [{ model: this.Animal, where: { age: { [Op.gte]: 29 } }, @@ -1113,12 +1069,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { attributes: ['mutation'] } }] - }).then(kingdoms => { - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - // include.attributes: [], model doesn't exists - expect(kingdom.Animals).to.not.exist; - }); + }); + + expect(kingdoms.length).to.be.eql(2); + kingdoms.forEach(kingdom => { + // include.attributes: [], model doesn't exists + expect(kingdom.Animals).to.not.exist; }); }); }); @@ -1126,7 +1082,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('order by eager loaded tables', () => { describe('HasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Continent = this.sequelize.define('continent', { name: Sequelize.STRING }); this.Country = this.sequelize.define('country', { name: Sequelize.STRING }); this.Person = this.sequelize.define('person', { name: Sequelize.STRING, lastName: Sequelize.STRING }); @@ -1138,72 +1094,72 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Country.hasMany(this.Person, { as: 'residents', foreignKey: 'CountryResidentId' }); this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - europe: this.Continent.create({ name: 'Europe' }), - asia: this.Continent.create({ name: 'Asia' }), - england: this.Country.create({ name: 'England' }), - france: this.Country.create({ name: 'France' }), - korea: this.Country.create({ name: 'Korea' }), - bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }), - fred: this.Person.create({ name: 'Fred', lastName: 'Able' }), - pierre: this.Person.create({ name: 'Pierre', lastName: 'Paris' }), - kim: this.Person.create({ name: 'Kim', lastName: 'Z' }) - }).then(r => { - _.forEach(r, (item, itemName) => { - this[itemName] = item; - }); - - return Promise.all([ - this.england.setContinent(this.europe), - this.france.setContinent(this.europe), - this.korea.setContinent(this.asia), - - this.bob.setCountry(this.england), - this.fred.setCountry(this.england), - this.pierre.setCountry(this.france), - this.kim.setCountry(this.korea), - - this.bob.setCountryResident(this.england), - this.fred.setCountryResident(this.france), - this.pierre.setCountryResident(this.korea), - this.kim.setCountryResident(this.england) - ]); - }); + await this.sequelize.sync({ force: true }); + + const r = await promiseProps({ + europe: this.Continent.create({ name: 'Europe' }), + asia: this.Continent.create({ name: 'Asia' }), + england: this.Country.create({ name: 'England' }), + france: this.Country.create({ name: 'France' }), + korea: this.Country.create({ name: 'Korea' }), + bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }), + fred: this.Person.create({ name: 'Fred', lastName: 'Able' }), + pierre: this.Person.create({ name: 'Pierre', lastName: 'Paris' }), + kim: this.Person.create({ name: 'Kim', lastName: 'Z' }) + }); + + _.forEach(r, (item, itemName) => { + this[itemName] = item; }); + + await Promise.all([ + this.england.setContinent(this.europe), + this.france.setContinent(this.europe), + this.korea.setContinent(this.asia), + + this.bob.setCountry(this.england), + this.fred.setCountry(this.england), + this.pierre.setCountry(this.france), + this.kim.setCountry(this.korea), + + this.bob.setCountryResident(this.england), + this.fred.setCountryResident(this.france), + this.pierre.setCountryResident(this.korea), + this.kim.setCountryResident(this.england) + ]); }); - it('sorts simply', function() { - return Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(params => { - return this.Continent.findAll({ + it('sorts simply', async function() { + await Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(async params => { + const continents = await this.Continent.findAll({ order: [['name', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); })); }); - it('sorts by 1st degree association', function() { - return Promise.all([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']].map(params => { - return this.Continent.findAll({ + it('sorts by 1st degree association', async function() { + await Promise.all([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']].map(async params => { + const continents = await this.Continent.findAll({ include: [this.Country], order: [[this.Country, 'name', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); })); }); - it('sorts simply and by 1st degree association with limit where 1st degree associated instances returned for second one and not the first', function() { - return Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(params => { - return this.Continent.findAll({ + it('sorts simply and by 1st degree association with limit where 1st degree associated instances returned for second one and not the first', async function() { + await Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, required: false, @@ -1213,83 +1169,83 @@ describe(Support.getTestDialectTeaser('Model'), () => { }], limit: 2, order: [['name', params[0]], [this.Country, 'name', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries.length).to.equal(0); - expect(continents[1]).to.exist; - expect(continents[1].name).to.equal(params[2]); - expect(continents[1].countries).to.exist; - expect(continents[1].countries.length).to.equal(1); - expect(continents[1].countries[0]).to.exist; - expect(continents[1].countries[0].name).to.equal(params[3]); }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries.length).to.equal(0); + expect(continents[1]).to.exist; + expect(continents[1].name).to.equal(params[2]); + expect(continents[1].countries).to.exist; + expect(continents[1].countries.length).to.equal(1); + expect(continents[1].countries[0]).to.exist; + expect(continents[1].countries[0].name).to.equal(params[3]); })); }); - it('sorts by 2nd degree association', function() { - return Promise.all([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']].map(params => { - return this.Continent.findAll({ + it('sorts by 2nd degree association', async function() { + await Promise.all([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person] }], order: [[this.Country, this.Person, 'lastName', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); - expect(continents[0].countries[0].people).to.exist; - expect(continents[0].countries[0].people[0]).to.exist; - expect(continents[0].countries[0].people[0].name).to.equal(params[3]); }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + expect(continents[0].countries[0].people).to.exist; + expect(continents[0].countries[0].people[0]).to.exist; + expect(continents[0].countries[0].people[0].name).to.equal(params[3]); })); }); - it('sorts by 2nd degree association with alias', function() { - return Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { - return this.Continent.findAll({ + it('sorts by 2nd degree association with alias', async function() { + await Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[this.Country, { model: this.Person, as: 'residents' }, 'lastName', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].residents[0]).to.exist; - expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + expect(continents[0].countries[0].residents).to.exist; + expect(continents[0].countries[0].residents[0]).to.exist; + expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); })); }); - it('sorts by 2nd degree association with alias while using limit', function() { - return Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(params => { - return this.Continent.findAll({ + it('sorts by 2nd degree association with alias while using limit', async function() { + await Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[{ model: this.Country }, { model: this.Person, as: 'residents' }, 'lastName', params[0]]], limit: 3 - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].residents[0]).to.exist; - expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + expect(continents[0].countries[0].residents).to.exist; + expect(continents[0].countries[0].residents[0]).to.exist; + expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); })); }); }); describe('ManyToMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Country = this.sequelize.define('country', { name: Sequelize.STRING }); this.Industry = this.sequelize.define('industry', { name: Sequelize.STRING }); this.IndustryCountry = this.sequelize.define('IndustryCountry', { numYears: Sequelize.INTEGER }); @@ -1297,163 +1253,150 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Country.belongsToMany(this.Industry, { through: this.IndustryCountry }); this.Industry.belongsToMany(this.Country, { through: this.IndustryCountry }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - england: this.Country.create({ name: 'England' }), - france: this.Country.create({ name: 'France' }), - korea: this.Country.create({ name: 'Korea' }), - energy: this.Industry.create({ name: 'Energy' }), - media: this.Industry.create({ name: 'Media' }), - tech: this.Industry.create({ name: 'Tech' }) - }).then(r => { - _.forEach(r, (item, itemName) => { - this[itemName] = item; - }); - - return Promise.all([ - this.england.addIndustry(this.energy, { through: { numYears: 20 } }), - this.england.addIndustry(this.media, { through: { numYears: 40 } }), - this.france.addIndustry(this.media, { through: { numYears: 80 } }), - this.korea.addIndustry(this.tech, { through: { numYears: 30 } }) - ]); - }); + await this.sequelize.sync({ force: true }); + + const r = await promiseProps({ + england: this.Country.create({ name: 'England' }), + france: this.Country.create({ name: 'France' }), + korea: this.Country.create({ name: 'Korea' }), + energy: this.Industry.create({ name: 'Energy' }), + media: this.Industry.create({ name: 'Media' }), + tech: this.Industry.create({ name: 'Tech' }) }); + + _.forEach(r, (item, itemName) => { + this[itemName] = item; + }); + + await Promise.all([ + this.england.addIndustry(this.energy, { through: { numYears: 20 } }), + this.england.addIndustry(this.media, { through: { numYears: 40 } }), + this.france.addIndustry(this.media, { through: { numYears: 80 } }), + this.korea.addIndustry(this.tech, { through: { numYears: 30 } }) + ]); }); - it('sorts by 1st degree association', function() { - return Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(params => { - return this.Country.findAll({ + it('sorts by 1st degree association', async function() { + await Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(async params => { + const countries = await this.Country.findAll({ include: [this.Industry], order: [[this.Industry, 'name', params[0]]] - }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].name).to.equal(params[1]); - expect(countries[0].industries).to.exist; - expect(countries[0].industries[0]).to.exist; - expect(countries[0].industries[0].name).to.equal(params[2]); }); + + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].name).to.equal(params[1]); + expect(countries[0].industries).to.exist; + expect(countries[0].industries[0]).to.exist; + expect(countries[0].industries[0].name).to.equal(params[2]); })); }); - it('sorts by 1st degree association while using limit', function() { - return Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(params => { - return this.Country.findAll({ + it('sorts by 1st degree association while using limit', async function() { + await Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(async params => { + const countries = await this.Country.findAll({ include: [this.Industry], order: [ [this.Industry, 'name', params[0]] ], limit: 3 - }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].name).to.equal(params[1]); - expect(countries[0].industries).to.exist; - expect(countries[0].industries[0]).to.exist; - expect(countries[0].industries[0].name).to.equal(params[2]); }); + + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].name).to.equal(params[1]); + expect(countries[0].industries).to.exist; + expect(countries[0].industries[0]).to.exist; + expect(countries[0].industries[0].name).to.equal(params[2]); })); }); - it('sorts by through table attribute', function() { - return Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'France', 'Media']].map(params => { - return this.Country.findAll({ + it('sorts by through table attribute', async function() { + await Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'France', 'Media']].map(async params => { + const countries = await this.Country.findAll({ include: [this.Industry], order: [[this.Industry, this.IndustryCountry, 'numYears', params[0]]] - }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].name).to.equal(params[1]); - expect(countries[0].industries).to.exist; - expect(countries[0].industries[0]).to.exist; - expect(countries[0].industries[0].name).to.equal(params[2]); }); + + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].name).to.equal(params[1]); + expect(countries[0].industries).to.exist; + expect(countries[0].industries[0]).to.exist; + expect(countries[0].industries[0].name).to.equal(params[2]); })); }); }); }); describe('normal findAll', () => { - beforeEach(function() { - return this.User.create({ username: 'user', data: 'foobar', theDate: moment().toDate() }).then(user => { - return this.User.create({ username: 'user2', data: 'bar', theDate: moment().toDate() }).then(user2 => { - this.users = [user].concat(user2); - }); - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'user', data: 'foobar', theDate: moment().toDate() }); + const user2 = await this.User.create({ username: 'user2', data: 'bar', theDate: moment().toDate() }); + this.users = [user].concat(user2); }); - it('finds all entries', function() { - return this.User.findAll().then(users => { - expect(users.length).to.equal(2); - }); + it('finds all entries', async function() { + const users = await this.User.findAll(); + expect(users.length).to.equal(2); }); - it('can also handle object notation', function() { - return this.User.findAll({ where: { id: this.users[1].id } }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].id).to.equal(this.users[1].id); - }); + it('can also handle object notation', async function() { + const users = await this.User.findAll({ where: { id: this.users[1].id } }); + expect(users.length).to.equal(1); + expect(users[0].id).to.equal(this.users[1].id); }); - it('sorts the results via id in ascending order', function() { - return this.User.findAll().then(users => { - expect(users.length).to.equal(2); - expect(users[0].id).to.be.below(users[1].id); - }); + it('sorts the results via id in ascending order', async function() { + const users = await this.User.findAll(); + expect(users.length).to.equal(2); + expect(users[0].id).to.be.below(users[1].id); }); - it('sorts the results via id in descending order', function() { - return this.User.findAll({ order: [['id', 'DESC']] }).then(users => { - expect(users[0].id).to.be.above(users[1].id); - }); + it('sorts the results via id in descending order', async function() { + const users = await this.User.findAll({ order: [['id', 'DESC']] }); + expect(users[0].id).to.be.above(users[1].id); }); - it('sorts the results via a date column', function() { - return this.User.create({ username: 'user3', data: 'bar', theDate: moment().add(2, 'hours').toDate() }).then(() => { - return this.User.findAll({ order: [['theDate', 'DESC']] }).then(users => { - expect(users[0].id).to.be.above(users[2].id); - }); - }); + it('sorts the results via a date column', async function() { + await this.User.create({ username: 'user3', data: 'bar', theDate: moment().add(2, 'hours').toDate() }); + const users = await this.User.findAll({ order: [['theDate', 'DESC']] }); + expect(users[0].id).to.be.above(users[2].id); }); - it('handles offset and limit', function() { - return this.User.bulkCreate([{ username: 'bobby' }, { username: 'tables' }]).then(() => { - return this.User.findAll({ limit: 2, offset: 2 }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].id).to.equal(3); - }); - }); + it('handles offset and limit', async function() { + await this.User.bulkCreate([{ username: 'bobby' }, { username: 'tables' }]); + const users = await this.User.findAll({ limit: 2, offset: 2 }); + expect(users.length).to.equal(2); + expect(users[0].id).to.equal(3); }); - it('should allow us to find IDs using capital letters', function() { + it('should allow us to find IDs using capital letters', async function() { const User = this.sequelize.define(`User${config.rand()}`, { ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, Login: { type: Sequelize.STRING } }); - return User.sync({ force: true }).then(() => { - return User.create({ Login: 'foo' }).then(() => { - return User.findAll({ where: { ID: 1 } }).then(user => { - expect(user).to.be.instanceof(Array); - expect(user).to.have.length(1); - }); - }); - }); + await User.sync({ force: true }); + await User.create({ Login: 'foo' }); + const user = await User.findAll({ where: { ID: 1 } }); + expect(user).to.be.instanceof(Array); + expect(user).to.have.length(1); }); - it('should be possible to order by sequelize.col()', function() { + it('should be possible to order by sequelize.col()', async function() { const Company = this.sequelize.define('Company', { name: Sequelize.STRING }); - return Company.sync().then(() => { - return Company.findAll({ - order: [this.sequelize.col('name')] - }); + await Company.sync(); + + await Company.findAll({ + order: [this.sequelize.col('name')] }); }); - it('should pull in dependent fields for a VIRTUAL', function() { + it('should pull in dependent fields for a VIRTUAL', async function() { const User = this.sequelize.define('User', { active: { type: Sequelize.VIRTUAL(Sequelize.BOOLEAN, ['createdAt']), @@ -1465,15 +1408,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { timestamps: true }); - return User.create().then(() => { - return User.findAll({ - attributes: ['active'] - }).then(users => { - users.forEach(user => { - expect(user.get('createdAt')).to.be.ok; - expect(user.get('active')).to.equal(true); - }); - }); + await User.create(); + + const users = await User.findAll({ + attributes: ['active'] + }); + + users.forEach(user => { + expect(user.get('createdAt')).to.be.ok; + expect(user.get('active')).to.equal(true); }); }); @@ -1500,7 +1443,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { await this.sequelize.sync({ force: true }); - return User.create({ + await User.create({ name: 'some user', Image: { path: 'folder1/folder2/logo.png' @@ -1509,110 +1452,99 @@ describe(Support.getTestDialectTeaser('Model'), () => { include: { model: Image } - }).then(() => { - return User.findAll({ - attributes: ['name'], - include: [{ - model: Image, - attributes: ['url'] - }] - }).then(users => { - users.forEach(user => { - expect(user.get('name')).to.equal('some user'); - expect(user.Image.get('url')).to.equal('https://my-cool-domain.com/folder1/folder2/logo.png'); - expect(user.Image.get('path')).to.equal('folder1/folder2/logo.png'); - }); - }); + }); + + const users = await User.findAll({ + attributes: ['name'], + include: [{ + model: Image, + attributes: ['url'] + }] + }); + + users.forEach(user => { + expect(user.get('name')).to.equal('some user'); + expect(user.Image.get('url')).to.equal('https://my-cool-domain.com/folder1/folder2/logo.png'); + expect(user.Image.get('path')).to.equal('folder1/folder2/logo.png'); }); }); - it('should throw for undefined where parameters', function() { - return this.User.findAll({ where: { username: undefined } }).then(() => { + it('should throw for undefined where parameters', async function() { + try { + await this.User.findAll({ where: { username: undefined } }); throw new Error('findAll should throw an error if where has a key with undefined value'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); - }); + } }); }); }); describe('findAndCountAll', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'user', data: 'foobar' }, { username: 'user2', data: 'bar' }, { username: 'bobby', data: 'foo' } - ]).then(() => { - return this.User.findAll().then(users => { - this.users = users; - }); - }); + ]); + + const users = await this.User.findAll(); + this.users = users; }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findAndCountAll().then(info1 => { - return User.findAndCountAll({ transaction: t }).then(info2 => { - expect(info1.count).to.equal(0); - expect(info2.count).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const info1 = await User.findAndCountAll(); + const info2 = await User.findAndCountAll({ transaction: t }); + expect(info1.count).to.equal(0); + expect(info2.count).to.equal(1); + await t.rollback(); }); } - it('handles where clause {only}', function() { - return this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } } }).then(info => { - expect(info.count).to.equal(2); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - }); + it('handles where clause {only}', async function() { + const info = await this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } } }); + expect(info.count).to.equal(2); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); }); - it('handles where clause with ordering {only}', function() { - return this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, order: [['id', 'ASC']] }).then(info => { - expect(info.count).to.equal(2); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - }); + it('handles where clause with ordering {only}', async function() { + const info = await this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, order: [['id', 'ASC']] }); + expect(info.count).to.equal(2); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); }); - it('handles offset', function() { - return this.User.findAndCountAll({ offset: 1 }).then(info => { - expect(info.count).to.equal(3); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - }); + it('handles offset', async function() { + const info = await this.User.findAndCountAll({ offset: 1 }); + expect(info.count).to.equal(3); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); }); - it('handles limit', function() { - return this.User.findAndCountAll({ limit: 1 }).then(info => { - expect(info.count).to.equal(3); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(1); - }); + it('handles limit', async function() { + const info = await this.User.findAndCountAll({ limit: 1 }); + expect(info.count).to.equal(3); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(1); }); - it('handles offset and limit', function() { - return this.User.findAndCountAll({ offset: 1, limit: 1 }).then(info => { - expect(info.count).to.equal(3); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(1); - }); + it('handles offset and limit', async function() { + const info = await this.User.findAndCountAll({ offset: 1, limit: 1 }); + expect(info.count).to.equal(3); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(1); }); - it('handles offset with includes', function() { + it('handles offset with includes', async function() { const Election = this.sequelize.define('Election', { name: Sequelize.STRING }); @@ -1626,147 +1558,128 @@ describe(Support.getTestDialectTeaser('Model'), () => { Citizen.hasMany(Election); Citizen.belongsToMany(Election, { as: 'Votes', through: 'ElectionsVotes' }); - return this.sequelize.sync().then(() => { - // Add some data - return Citizen.create({ name: 'Alice' }).then(alice => { - return Citizen.create({ name: 'Bob' }).then(bob => { - return Election.create({ name: 'Some election' }).then(() => { - return Election.create({ name: 'Some other election' }).then(election => { - return election.setCitizen(alice).then(() => { - return election.setVoters([alice, bob]).then(() => { - const criteria = { - offset: 5, - limit: 1, - where: { - name: 'Some election' - }, - include: [ - Citizen, // Election creator - { model: Citizen, as: 'Voters' } // Election voters - ] - }; - return Election.findAndCountAll(criteria).then(elections => { - expect(elections.count).to.equal(1); - expect(elections.rows.length).to.equal(0); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync(); + // Add some data + const alice = await Citizen.create({ name: 'Alice' }); + const bob = await Citizen.create({ name: 'Bob' }); + await Election.create({ name: 'Some election' }); + const election = await Election.create({ name: 'Some other election' }); + await election.setCitizen(alice); + await election.setVoters([alice, bob]); + const criteria = { + offset: 5, + limit: 1, + where: { + name: 'Some election' + }, + include: [ + Citizen, // Election creator + { model: Citizen, as: 'Voters' } // Election voters + ] + }; + const elections = await Election.findAndCountAll(criteria); + expect(elections.count).to.equal(1); + expect(elections.rows.length).to.equal(0); }); - it('handles attributes', function() { - return this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, attributes: ['data'] }).then(info => { - expect(info.count).to.equal(2); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - expect(info.rows[0].dataValues).to.not.have.property('username'); - expect(info.rows[1].dataValues).to.not.have.property('username'); - }); + it('handles attributes', async function() { + const info = await this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, attributes: ['data'] }); + expect(info.count).to.equal(2); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); + expect(info.rows[0].dataValues).to.not.have.property('username'); + expect(info.rows[1].dataValues).to.not.have.property('username'); }); }); describe('all', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'user', data: 'foobar' }, { username: 'user2', data: 'bar' } ]); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1.length).to.equal(0); - expect(users2.length).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1.length).to.equal(0); + expect(users2.length).to.equal(1); + await t.rollback(); }); } - it('should return all users', function() { - return this.User.findAll().then(users => { - expect(users.length).to.equal(2); - }); + it('should return all users', async function() { + const users = await this.User.findAll(); + expect(users.length).to.equal(2); }); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.findAll({ + await this.User.findAll({ where: {}, logging: spy - }).then(() => { - expect(spy.called).to.be.ok; }); + + expect(spy.called).to.be.ok; }); describe('rejectOnEmpty mode', () => { - it('works from model options', () => { + it('works from model options', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: true }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findAll({ - where: { - username: 'some-username-that-is-not-used-anywhere' - } - })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); - }); + await Model.sync({ force: true }); + + await expect(Model.findAll({ + where: { + username: 'some-username-that-is-not-used-anywhere' + } + })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('throws custom error with initialized', () => { + it('throws custom error with initialized', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: new Sequelize.ConnectionError('Some Error') //using custom error instance }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findAll({ - where: { - username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' - } - })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); - }); + await Model.sync({ force: true }); + + await expect(Model.findAll({ + where: { + username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' + } + })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); }); - it('throws custom error with instance', () => { + it('throws custom error with instance', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: Sequelize.ConnectionError //using custom error instance }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findAll({ - where: { - username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' - } - })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); - }); + await Model.sync({ force: true }); + + await expect(Model.findAll({ + where: { + username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' + } + })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); }); }); }); diff --git a/test/integration/model/findAll/group.test.js b/test/integration/model/findAll/group.test.js index 4780ca362649..fac604072b5a 100644 --- a/test/integration/model/findAll/group.test.js +++ b/test/integration/model/findAll/group.test.js @@ -10,8 +10,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('findAll', () => { describe('group', () => { - it('should correctly group with attributes, #3009', () => { - + it('should correctly group with attributes, #3009', async () => { const Post = current.define('Post', { id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false } @@ -24,38 +23,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.hasMany(Comment); - return current.sync({ force: true }).then(() => { - // Create an enviroment - return Post.bulkCreate([ - { name: 'post-1' }, - { name: 'post-2' } - ]); - }).then(() => { - return Comment.bulkCreate([ - { text: 'Market', PostId: 1 }, - { text: 'Text', PostId: 2 }, - { text: 'Abc', PostId: 2 }, - { text: 'Semaphor', PostId: 1 }, - { text: 'Text', PostId: 1 } - ]); - }).then(() => { - return Post.findAll({ - attributes: [[Sequelize.fn('COUNT', Sequelize.col('Comments.id')), 'comment_count']], - include: [ - { model: Comment, attributes: [] } - ], - group: ['Post.id'], - order: [ - ['id'] - ] - }); - }).then(posts => { - expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); - expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); + await current.sync({ force: true }); + + // Create an enviroment + await Post.bulkCreate([ + { name: 'post-1' }, + { name: 'post-2' } + ]); + + await Comment.bulkCreate([ + { text: 'Market', PostId: 1 }, + { text: 'Text', PostId: 2 }, + { text: 'Abc', PostId: 2 }, + { text: 'Semaphor', PostId: 1 }, + { text: 'Text', PostId: 1 } + ]); + + const posts = await Post.findAll({ + attributes: [[Sequelize.fn('COUNT', Sequelize.col('Comments.id')), 'comment_count']], + include: [ + { model: Comment, attributes: [] } + ], + group: ['Post.id'], + order: [ + ['id'] + ] }); + + expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); + expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); }); - it('should not add primary key when grouping using a belongsTo association', () => { + it('should not add primary key when grouping using a belongsTo association', async () => { const Post = current.define('Post', { id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false } @@ -69,36 +68,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.hasMany(Comment); Comment.belongsTo(Post); - return current.sync({ force: true }).then(() => { - return Post.bulkCreate([ - { name: 'post-1' }, - { name: 'post-2' } - ]); - }).then(() => { - return Comment.bulkCreate([ - { text: 'Market', PostId: 1 }, - { text: 'Text', PostId: 2 }, - { text: 'Abc', PostId: 2 }, - { text: 'Semaphor', PostId: 1 }, - { text: 'Text', PostId: 1 } - ]); - }).then(() => { - return Comment.findAll({ - attributes: ['PostId', [Sequelize.fn('COUNT', Sequelize.col('Comment.id')), 'comment_count']], - include: [ - { model: Post, attributes: [] } - ], - group: ['PostId'], - order: [ - ['PostId'] - ] - }); - }).then(posts => { - expect(posts[0].get().hasOwnProperty('id')).to.equal(false); - expect(posts[1].get().hasOwnProperty('id')).to.equal(false); - expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); - expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); + await current.sync({ force: true }); + + await Post.bulkCreate([ + { name: 'post-1' }, + { name: 'post-2' } + ]); + + await Comment.bulkCreate([ + { text: 'Market', PostId: 1 }, + { text: 'Text', PostId: 2 }, + { text: 'Abc', PostId: 2 }, + { text: 'Semaphor', PostId: 1 }, + { text: 'Text', PostId: 1 } + ]); + + const posts = await Comment.findAll({ + attributes: ['PostId', [Sequelize.fn('COUNT', Sequelize.col('Comment.id')), 'comment_count']], + include: [ + { model: Post, attributes: [] } + ], + group: ['PostId'], + order: [ + ['PostId'] + ] }); + + expect(posts[0].get().hasOwnProperty('id')).to.equal(false); + expect(posts[1].get().hasOwnProperty('id')).to.equal(false); + expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); + expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); }); }); }); diff --git a/test/integration/model/findAll/groupedLimit.test.js b/test/integration/model/findAll/groupedLimit.test.js index 558c273f1c26..0385c7464c23 100644 --- a/test/integration/model/findAll/groupedLimit.test.js +++ b/test/integration/model/findAll/groupedLimit.test.js @@ -25,7 +25,7 @@ if (current.dialect.supports['UNION ALL']) { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { age: Sequelize.INTEGER }); @@ -49,47 +49,47 @@ if (current.dialect.supports['UNION ALL']) { this.User.Tasks = this.User.hasMany(this.Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.bulkCreate([{ age: -5 }, { age: 45 }, { age: 7 }, { age: -9 }, { age: 8 }, { age: 15 }, { age: -9 }]), - this.Project.bulkCreate([{}, {}]), - this.Task.bulkCreate([{}, {}]) - ]); - }) - .then(() => Promise.all([this.User.findAll(), this.Project.findAll(), this.Task.findAll()])) - .then(([users, projects, tasks]) => { - this.projects = projects; - return Promise.all([ - projects[0].setMembers(users.slice(0, 4)), - projects[1].setMembers(users.slice(2)), - projects[0].setParanoidMembers(users.slice(0, 4)), - projects[1].setParanoidMembers(users.slice(2)), - users[2].setTasks(tasks) - ]); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + this.User.bulkCreate([{ age: -5 }, { age: 45 }, { age: 7 }, { age: -9 }, { age: 8 }, { age: 15 }, { age: -9 }]), + this.Project.bulkCreate([{}, {}]), + this.Task.bulkCreate([{}, {}]) + ]); + + const [users, projects, tasks] = await Promise.all([this.User.findAll(), this.Project.findAll(), this.Task.findAll()]); + this.projects = projects; + + await Promise.all([ + projects[0].setMembers(users.slice(0, 4)), + projects[1].setMembers(users.slice(2)), + projects[0].setParanoidMembers(users.slice(0, 4)), + projects[1].setParanoidMembers(users.slice(2)), + users[2].setTasks(tasks) + ]); }); describe('on: belongsToMany', () => { - it('maps attributes from a grouped limit to models', function() { - return this.User.findAll({ + it('maps attributes from a grouped limit to models', async function() { + const users = await this.User.findAll({ groupedLimit: { limit: 3, on: this.User.Projects, values: this.projects.map(item => item.get('id')) } - }).then(users => { - expect(users).to.have.length(5); - users.filter(u => u.get('id') !== 3).forEach(u => { - expect(u.get('projects')).to.have.length(1); - }); - users.filter(u => u.get('id') === 3).forEach(u => { - expect(u.get('projects')).to.have.length(2); - }); + }); + + expect(users).to.have.length(5); + users.filter(u => u.get('id') !== 3).forEach(u => { + expect(u.get('projects')).to.have.length(1); + }); + users.filter(u => u.get('id') === 3).forEach(u => { + expect(u.get('projects')).to.have.length(2); }); }); - it('maps attributes from a grouped limit to models with include', function() { - return this.User.findAll({ + it('maps attributes from a grouped limit to models with include', async function() { + const users = await this.User.findAll({ groupedLimit: { limit: 3, on: this.User.Projects, @@ -97,26 +97,26 @@ if (current.dialect.supports['UNION ALL']) { }, order: ['id'], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 2, 3 - project2 - 3, 4, 5 - */ - expect(users).to.have.length(5); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 2, 3, 4, 5]); - - expect(users[2].get('tasks')).to.have.length(2); - users.filter(u => u.get('id') !== 3).forEach(u => { - expect(u.get('projects')).to.have.length(1); - }); - users.filter(u => u.get('id') === 3).forEach(u => { - expect(u.get('projects')).to.have.length(2); - }); + }); + + /* + project1 - 1, 2, 3 + project2 - 3, 4, 5 + */ + expect(users).to.have.length(5); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 2, 3, 4, 5]); + + expect(users[2].get('tasks')).to.have.length(2); + users.filter(u => u.get('id') !== 3).forEach(u => { + expect(u.get('projects')).to.have.length(1); + }); + users.filter(u => u.get('id') === 3).forEach(u => { + expect(u.get('projects')).to.have.length(2); }); }); - it('works with computed order', function() { - return this.User.findAll({ + it('works with computed order', async function() { + const users = await this.User.findAll({ attributes: ['id'], groupedLimit: { limit: 3, @@ -127,18 +127,18 @@ if (current.dialect.supports['UNION ALL']) { Sequelize.fn('ABS', Sequelize.col('age')) ], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 3, 4 - project2 - 3, 5, 4 - */ - expect(users).to.have.length(4); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 4]); }); + + /* + project1 - 1, 3, 4 + project2 - 3, 5, 4 + */ + expect(users).to.have.length(4); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 4]); }); - it('works with multiple orders', function() { - return this.User.findAll({ + it('works with multiple orders', async function() { + const users = await this.User.findAll({ attributes: ['id'], groupedLimit: { limit: 3, @@ -150,18 +150,18 @@ if (current.dialect.supports['UNION ALL']) { ['id', 'DESC'] ], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 3, 4 - project2 - 3, 5, 7 - */ - expect(users).to.have.length(5); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); }); + + /* + project1 - 1, 3, 4 + project2 - 3, 5, 7 + */ + expect(users).to.have.length(5); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); }); - it('works with paranoid junction models', function() { - return this.User.findAll({ + it('works with paranoid junction models', async function() { + const users0 = await this.User.findAll({ attributes: ['id'], groupedLimit: { limit: 3, @@ -173,68 +173,68 @@ if (current.dialect.supports['UNION ALL']) { ['id', 'DESC'] ], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 3, 4 - project2 - 3, 5, 7 - */ - expect(users).to.have.length(5); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); - - return Promise.all([ - this.projects[0].setParanoidMembers(users.slice(0, 2)), - this.projects[1].setParanoidMembers(users.slice(4)) - ]); - }).then(() => { - return this.User.findAll({ - attributes: ['id'], - groupedLimit: { - limit: 3, - on: this.User.ParanoidProjects, - values: this.projects.map(item => item.get('id')) - }, - order: [ - Sequelize.fn('ABS', Sequelize.col('age')), - ['id', 'DESC'] - ], - include: [this.User.Tasks] - }); - }).then(users => { - /* - project1 - 1, 3 - project2 - 4 - */ - expect(users).to.have.length(3); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 4]); }); + + /* + project1 - 1, 3, 4 + project2 - 3, 5, 7 + */ + expect(users0).to.have.length(5); + expect(users0.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); + + await Promise.all([ + this.projects[0].setParanoidMembers(users0.slice(0, 2)), + this.projects[1].setParanoidMembers(users0.slice(4)) + ]); + + const users = await this.User.findAll({ + attributes: ['id'], + groupedLimit: { + limit: 3, + on: this.User.ParanoidProjects, + values: this.projects.map(item => item.get('id')) + }, + order: [ + Sequelize.fn('ABS', Sequelize.col('age')), + ['id', 'DESC'] + ], + include: [this.User.Tasks] + }); + + /* + project1 - 1, 3 + project2 - 4 + */ + expect(users).to.have.length(3); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 4]); }); }); describe('on: hasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user'); this.Task = this.sequelize.define('task'); this.User.Tasks = this.User.hasMany(this.Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.bulkCreate([{}, {}, {}]), - this.Task.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]) - ]); - }) - .then(() => Promise.all([this.User.findAll(), this.Task.findAll()])) - .then(([users, tasks]) => { - this.users = users; - return Promise.all([ - users[0].setTasks(tasks[0]), - users[1].setTasks(tasks.slice(1, 4)), - users[2].setTasks(tasks.slice(4)) - ]); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + this.User.bulkCreate([{}, {}, {}]), + this.Task.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]) + ]); + + const [users, tasks] = await Promise.all([this.User.findAll(), this.Task.findAll()]); + this.users = users; + + await Promise.all([ + users[0].setTasks(tasks[0]), + users[1].setTasks(tasks.slice(1, 4)), + users[2].setTasks(tasks.slice(4)) + ]); }); - it('Applies limit and order correctly', function() { - return this.Task.findAll({ + it('Applies limit and order correctly', async function() { + const tasks = await this.Task.findAll({ order: [ ['id', 'DESC'] ], @@ -243,15 +243,15 @@ if (current.dialect.supports['UNION ALL']) { on: this.User.Tasks, values: this.users.map(item => item.get('id')) } - }).then(tasks => { - const byUser = _.groupBy(tasks, _.property('userId')); - expect(Object.keys(byUser)).to.have.length(3); - - expect(byUser[1]).to.have.length(1); - expect(byUser[2]).to.have.length(3); - expect(_.invokeMap(byUser[2], 'get', 'id')).to.deep.equal([4, 3, 2]); - expect(byUser[3]).to.have.length(2); }); + + const byUser = _.groupBy(tasks, _.property('userId')); + expect(Object.keys(byUser)).to.have.length(3); + + expect(byUser[1]).to.have.length(1); + expect(byUser[2]).to.have.length(3); + expect(_.invokeMap(byUser[2], 'get', 'id')).to.deep.equal([4, 3, 2]); + expect(byUser[3]).to.have.length(2); }); }); }); diff --git a/test/integration/model/findAll/order.test.js b/test/integration/model/findAll/order.test.js index 7eb4f1f54478..35bfd50523c0 100644 --- a/test/integration/model/findAll/order.test.js +++ b/test/integration/model/findAll/order.test.js @@ -10,58 +10,58 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('findAll', () => { describe('order', () => { describe('Sequelize.literal()', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { email: DataTypes.STRING }); - return this.User.sync({ force: true }).then(() => { - return this.User.create({ - email: 'test@sequelizejs.com' - }); + await this.User.sync({ force: true }); + + await this.User.create({ + email: 'test@sequelizejs.com' }); }); if (current.dialect.name !== 'mssql') { - it('should work with order: literal()', function() { - return this.User.findAll({ + it('should work with order: literal()', async function() { + const users = await this.User.findAll({ order: this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`) - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.get('email')).to.be.ok; }); }); - it('should work with order: [literal()]', function() { - return this.User.findAll({ + it('should work with order: [literal()]', async function() { + const users = await this.User.findAll({ order: [this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`)] - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.get('email')).to.be.ok; }); }); - it('should work with order: [[literal()]]', function() { - return this.User.findAll({ + it('should work with order: [[literal()]]', async function() { + const users = await this.User.findAll({ order: [ [this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`)] ] - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.get('email')).to.be.ok; }); }); } }); describe('injections', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { name: DataTypes.STRING }); @@ -69,12 +69,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); this.User.belongsTo(this.Group); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); if (current.dialect.supports['ORDER NULLS']) { - it('should not throw with on NULLS LAST/NULLS FIRST', function() { - return this.User.findAll({ + it('should not throw with on NULLS LAST/NULLS FIRST', async function() { + await this.User.findAll({ include: [this.Group], order: [ ['id', 'ASC NULLS LAST'], @@ -84,16 +84,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); } - it('should not throw on a literal', function() { - return this.User.findAll({ + it('should not throw on a literal', async function() { + await this.User.findAll({ order: [ ['id', this.sequelize.literal('ASC, name DESC')] ] }); }); - it('should not throw with include when last order argument is a field', function() { - return this.User.findAll({ + it('should not throw with include when last order argument is a field', async function() { + await this.User.findAll({ include: [this.Group], order: [ [this.Group, 'id'] diff --git a/test/integration/model/findAll/separate.test.js b/test/integration/model/findAll/separate.test.js index 8ea9b50c625c..d81a848fcf89 100644 --- a/test/integration/model/findAll/separate.test.js +++ b/test/integration/model/findAll/separate.test.js @@ -9,7 +9,7 @@ const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { describe('findAll', () => { describe('separate with limit', () => { - it('should not throw syntax error (union)', () => { + it('should not throw syntax error (union)', async () => { // #9813 testcase const Project = current.define('Project', { name: DataTypes.STRING }); const LevelTwo = current.define('LevelTwo', { name: DataTypes.STRING }); @@ -22,47 +22,51 @@ describe(Support.getTestDialectTeaser('Model'), () => { LevelTwo.hasMany(LevelThree, { as: 'type_twos' }); LevelThree.belongsTo(LevelTwo); - return current.sync({ force: true }).then(() => { - return Promise.all([ - Project.create({ name: 'testProject' }), - LevelTwo.create({ name: 'testL21' }), - LevelTwo.create({ name: 'testL22' }) - ]); - }).then(([project, level21, level22]) => { - return Promise.all([ - project.addLevelTwo(level21), - project.addLevelTwo(level22) - ]); - }).then(() => { - // one include case - return Project.findAll({ - where: { name: 'testProject' }, - include: [ - { - model: LevelTwo, - include: [ - { - model: LevelThree, - as: 'type_ones', - where: { type: 0 }, - separate: true, - limit: 1, - order: [['createdAt', 'DESC']] - } - ] - } - ] - }); - }).then(projects => { - expect(projects).to.have.length(1); - expect(projects[0].LevelTwos).to.have.length(2); - expect(projects[0].LevelTwos[0].type_ones).to.have.length(0); - expect(projects[0].LevelTwos[1].type_ones).to.have.length(0); - }, () => { - expect.fail(); - }).then(() => { + try { + try { + await current.sync({ force: true }); + + const [project, level21, level22] = await Promise.all([ + Project.create({ name: 'testProject' }), + LevelTwo.create({ name: 'testL21' }), + LevelTwo.create({ name: 'testL22' }) + ]); + + await Promise.all([ + project.addLevelTwo(level21), + project.addLevelTwo(level22) + ]); + + // one include case + const projects0 = await Project.findAll({ + where: { name: 'testProject' }, + include: [ + { + model: LevelTwo, + include: [ + { + model: LevelThree, + as: 'type_ones', + where: { type: 0 }, + separate: true, + limit: 1, + order: [['createdAt', 'DESC']] + } + ] + } + ] + }); + + expect(projects0).to.have.length(1); + expect(projects0[0].LevelTwos).to.have.length(2); + expect(projects0[0].LevelTwos[0].type_ones).to.have.length(0); + expect(projects0[0].LevelTwos[1].type_ones).to.have.length(0); + } catch (err) { + expect.fail(); + } + // two includes case - return Project.findAll({ + const projects = await Project.findAll({ where: { name: 'testProject' }, include: [ { @@ -88,14 +92,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } ] }); - }).then(projects => { + expect(projects).to.have.length(1); expect(projects[0].LevelTwos).to.have.length(2); expect(projects[0].LevelTwos[0].type_ones).to.have.length(0); expect(projects[0].LevelTwos[1].type_ones).to.have.length(0); - }, () => { + } catch (err) { expect.fail(); - }); + } }); }); }); diff --git a/test/integration/model/findOne.test.js b/test/integration/model/findOne.test.js index 57e253769c06..a2b6d38fa023 100644 --- a/test/integration/model/findOne.test.js +++ b/test/integration/model/findOne.test.js @@ -11,7 +11,7 @@ const chai = require('chai'), current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -21,60 +21,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { aBool: DataTypes.BOOLEAN }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('findOne', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findOne({ - where: { username: 'foo' } - }).then(user1 => { - return User.findOne({ - where: { username: 'foo' }, - transaction: t - }).then(user2 => { - expect(user1).to.be.null; - expect(user2).to.not.be.null; - return t.rollback(); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + + const user1 = await User.findOne({ + where: { username: 'foo' } + }); + + const user2 = await User.findOne({ + where: { username: 'foo' }, + transaction: t }); + + expect(user1).to.be.null; + expect(user2).to.not.be.null; + await t.rollback(); }); } describe('general / basic function', () => { - beforeEach(function() { - return this.User.create({ username: 'barfooz' }).then(user => { - this.UserPrimary = this.sequelize.define('UserPrimary', { - specialkey: { - type: DataTypes.STRING, - primaryKey: true - } - }); - - return this.UserPrimary.sync({ force: true }).then(() => { - return this.UserPrimary.create({ specialkey: 'a string' }).then(() => { - this.user = user; - }); - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'barfooz' }); + this.UserPrimary = this.sequelize.define('UserPrimary', { + specialkey: { + type: DataTypes.STRING, + primaryKey: true + } }); + + await this.UserPrimary.sync({ force: true }); + await this.UserPrimary.create({ specialkey: 'a string' }); + this.user = user; }); if (dialect === 'mysql') { // Bit fields interpreted as boolean need conversion from buffer / bool. // Sqlite returns the inserted value as is, and postgres really should the built in bool type instead - it('allows bit fields as booleans', function() { + it('allows bit fields as booleans', async function() { let bitUser = this.sequelize.define('bituser', { bool: 'BIT(1)' }, { @@ -82,69 +76,65 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); // First use a custom data type def to create the bit field - return bitUser.sync({ force: true }).then(() => { - // Then change the definition to BOOLEAN - bitUser = this.sequelize.define('bituser', { - bool: DataTypes.BOOLEAN - }, { - timestamps: false - }); - - return bitUser.bulkCreate([ - { bool: 0 }, - { bool: 1 } - ]); - }).then(() => { - return bitUser.findAll(); - }).then(bitUsers => { - expect(bitUsers[0].bool).not.to.be.ok; - expect(bitUsers[1].bool).to.be.ok; + await bitUser.sync({ force: true }); + // Then change the definition to BOOLEAN + bitUser = this.sequelize.define('bituser', { + bool: DataTypes.BOOLEAN + }, { + timestamps: false }); + + await bitUser.bulkCreate([ + { bool: 0 }, + { bool: 1 } + ]); + + const bitUsers = await bitUser.findAll(); + expect(bitUsers[0].bool).not.to.be.ok; + expect(bitUsers[1].bool).to.be.ok; }); } - it('treats questionmarks in an array', function() { + it('treats questionmarks in an array', async function() { let test = false; - return this.UserPrimary.findOne({ + + await this.UserPrimary.findOne({ where: { 'specialkey': 'awesome' }, logging(sql) { test = true; expect(sql).to.match(/WHERE ["|`|[]UserPrimary["|`|\]]\.["|`|[]specialkey["|`|\]] = N?'awesome'/); } - }).then(() => { - expect(test).to.be.true; }); + + expect(test).to.be.true; }); - it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function() { - return this.UserPrimary.findByPk('a string').then(user => { - expect(user.specialkey).to.equal('a string'); - }); + it('doesn\'t throw an error when entering in a non integer value for a specified primary field', async function() { + const user = await this.UserPrimary.findByPk('a string'); + expect(user.specialkey).to.equal('a string'); }); - it('returns a single dao', function() { - return this.User.findByPk(this.user.id).then(user => { - expect(Array.isArray(user)).to.not.be.ok; - expect(user.id).to.equal(this.user.id); - expect(user.id).to.equal(1); - }); + it('returns a single dao', async function() { + const user = await this.User.findByPk(this.user.id); + expect(Array.isArray(user)).to.not.be.ok; + expect(user.id).to.equal(this.user.id); + expect(user.id).to.equal(1); }); - it('returns a single dao given a string id', function() { - return this.User.findByPk(this.user.id.toString()).then(user => { - expect(Array.isArray(user)).to.not.be.ok; - expect(user.id).to.equal(this.user.id); - expect(user.id).to.equal(1); - }); + it('returns a single dao given a string id', async function() { + const user = await this.User.findByPk(this.user.id.toString()); + expect(Array.isArray(user)).to.not.be.ok; + expect(user.id).to.equal(this.user.id); + expect(user.id).to.equal(1); }); - it('should make aliased attributes available', function() { - return this.User.findOne({ + it('should make aliased attributes available', async function() { + const user = await this.User.findOne({ where: { id: 1 }, attributes: ['id', ['username', 'name']] - }).then(user => { - expect(user.dataValues.name).to.equal('barfooz'); }); + + expect(user.dataValues.name).to.equal('barfooz'); }); it('should fail with meaningful error message on invalid attributes definition', function() { @@ -154,146 +144,133 @@ describe(Support.getTestDialectTeaser('Model'), () => { })).to.be.rejectedWith('["username"] is not a valid attribute definition. Please use the following format: [\'attribute definition\', \'alias\']'); }); - it('should not try to convert boolean values if they are not selected', function() { + it('should not try to convert boolean values if they are not selected', async function() { const UserWithBoolean = this.sequelize.define('UserBoolean', { active: Sequelize.BOOLEAN }); - return UserWithBoolean.sync({ force: true }).then(() => { - return UserWithBoolean.create({ active: true }).then(user => { - return UserWithBoolean.findOne({ where: { id: user.id }, attributes: ['id'] }).then(user => { - expect(user.active).not.to.exist; - }); - }); - }); + await UserWithBoolean.sync({ force: true }); + const user = await UserWithBoolean.create({ active: true }); + const user0 = await UserWithBoolean.findOne({ where: { id: user.id }, attributes: ['id'] }); + expect(user0.active).not.to.exist; }); - it('finds a specific user via where option', function() { - return this.User.findOne({ where: { username: 'barfooz' } }).then(user => { - expect(user.username).to.equal('barfooz'); - }); + it('finds a specific user via where option', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' } }); + expect(user.username).to.equal('barfooz'); }); - it('doesn\'t find a user if conditions are not matching', function() { - return this.User.findOne({ where: { username: 'foo' } }).then(user => { - expect(user).to.be.null; - }); + it('doesn\'t find a user if conditions are not matching', async function() { + const user = await this.User.findOne({ where: { username: 'foo' } }); + expect(user).to.be.null; }); - it('allows sql logging', function() { + it('allows sql logging', async function() { let test = false; - return this.User.findOne({ + + await this.User.findOne({ where: { username: 'foo' }, logging(sql) { test = true; expect(sql).to.exist; expect(sql.toUpperCase()).to.include('SELECT'); } - }).then(() => { - expect(test).to.be.true; }); + + expect(test).to.be.true; }); - it('ignores passed limit option', function() { - return this.User.findOne({ limit: 10 }).then(user => { - // it returns an object instead of an array - expect(Array.isArray(user)).to.not.be.ok; - expect(user.dataValues.hasOwnProperty('username')).to.be.ok; - }); + it('ignores passed limit option', async function() { + const user = await this.User.findOne({ limit: 10 }); + // it returns an object instead of an array + expect(Array.isArray(user)).to.not.be.ok; + expect(user.dataValues.hasOwnProperty('username')).to.be.ok; }); - it('finds entries via primary keys', function() { + it('finds entries via primary keys', async function() { const UserPrimary = this.sequelize.define('UserWithPrimaryKey', { identifier: { type: Sequelize.STRING, primaryKey: true }, name: Sequelize.STRING }); - return UserPrimary.sync({ force: true }).then(() => { - return UserPrimary.create({ - identifier: 'an identifier', - name: 'John' - }).then(u => { - expect(u.id).not.to.exist; - return UserPrimary.findByPk('an identifier').then(u2 => { - expect(u2.identifier).to.equal('an identifier'); - expect(u2.name).to.equal('John'); - }); - }); + await UserPrimary.sync({ force: true }); + + const u = await UserPrimary.create({ + identifier: 'an identifier', + name: 'John' }); + + expect(u.id).not.to.exist; + const u2 = await UserPrimary.findByPk('an identifier'); + expect(u2.identifier).to.equal('an identifier'); + expect(u2.name).to.equal('John'); }); - it('finds entries via a string primary key called id', function() { + it('finds entries via a string primary key called id', async function() { const UserPrimary = this.sequelize.define('UserWithPrimaryKey', { id: { type: Sequelize.STRING, primaryKey: true }, name: Sequelize.STRING }); - return UserPrimary.sync({ force: true }).then(() => { - return UserPrimary.create({ - id: 'a string based id', - name: 'Johnno' - }).then(() => { - return UserPrimary.findByPk('a string based id').then(u2 => { - expect(u2.id).to.equal('a string based id'); - expect(u2.name).to.equal('Johnno'); - }); - }); + await UserPrimary.sync({ force: true }); + + await UserPrimary.create({ + id: 'a string based id', + name: 'Johnno' }); + + const u2 = await UserPrimary.findByPk('a string based id'); + expect(u2.id).to.equal('a string based id'); + expect(u2.name).to.equal('Johnno'); }); - it('always honors ZERO as primary key', function() { + it('always honors ZERO as primary key', async function() { const permutations = [ 0, '0' ]; let count = 0; - return this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]).then(() => { - return Promise.all(permutations.map(perm => { - return this.User.findByPk(perm, { - logging(s) { - expect(s).to.include(0); - count++; - } - }).then(user => { - expect(user).to.be.null; - }); - })); - }).then(() => { - expect(count).to.be.equal(permutations.length); - }); + await this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]); + + await Promise.all(permutations.map(async perm => { + const user = await this.User.findByPk(perm, { + logging(s) { + expect(s).to.include(0); + count++; + } + }); + + expect(user).to.be.null; + })); + + expect(count).to.be.equal(permutations.length); }); - it('should allow us to find IDs using capital letters', function() { + it('should allow us to find IDs using capital letters', async function() { const User = this.sequelize.define(`User${config.rand()}`, { ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, Login: { type: Sequelize.STRING } }); - return User.sync({ force: true }).then(() => { - return User.create({ Login: 'foo' }).then(() => { - return User.findByPk(1).then(user => { - expect(user).to.exist; - expect(user.ID).to.equal(1); - }); - }); - }); + await User.sync({ force: true }); + await User.create({ Login: 'foo' }); + const user = await User.findByPk(1); + expect(user).to.exist; + expect(user.ID).to.equal(1); }); if (dialect === 'postgres' || dialect === 'sqlite') { - it('should allow case-insensitive find on CITEXT type', function() { + it('should allow case-insensitive find on CITEXT type', async function() { const User = this.sequelize.define('UserWithCaseInsensitiveName', { username: Sequelize.CITEXT }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'longUserNAME' }); - }).then(() => { - return User.findOne({ where: { username: 'LONGusername' } }); - }).then(user => { - expect(user).to.exist; - expect(user.username).to.equal('longUserNAME'); - }); + await User.sync({ force: true }); + await User.create({ username: 'longUserNAME' }); + const user = await User.findOne({ where: { username: 'LONGusername' } }); + expect(user).to.exist; + expect(user.username).to.equal('longUserNAME'); }); } }); @@ -303,87 +280,83 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); - this.init = function(callback) { - return this.sequelize.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return callback(); - }); - }); - }); + this.init = async function(callback) { + await this.sequelize.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + return callback(); }; }); describe('belongsTo', () => { describe('generic', () => { - it('throws an error about unexpected input if include contains a non-object', function() { - return this.Worker.findOne({ include: [1] }).catch(err => { + it('throws an error about unexpected input if include contains a non-object', async function() { + try { + await this.Worker.findOne({ include: [1] }); + } catch (err) { expect(err.message).to.equal('Include unexpected. Element has to be either a Model, an Association or an object.'); - }); + } }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Worker.findOne({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Worker.findOne({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is not associated to Worker!'); - }); + } }); - it('returns the associated worker via task.worker', function() { + it('returns the associated worker via task.worker', async function() { this.Task.belongsTo(this.Worker); - return this.init(() => { - return this.task.setWorker(this.worker).then(() => { - return this.Task.findOne({ - where: { title: 'homework' }, - include: [this.Worker] - }).then(task => { - expect(task).to.exist; - expect(task.Worker).to.exist; - expect(task.Worker.name).to.equal('worker'); - }); + + await this.init(async () => { + await this.task.setWorker(this.worker); + + const task = await this.Task.findOne({ + where: { title: 'homework' }, + include: [this.Worker] }); + + expect(task).to.exist; + expect(task.Worker).to.exist; + expect(task.Worker.name).to.equal('worker'); }); }); }); - it('returns the private and public ip', function() { + it('returns the private and public ip', async function() { const ctx = Object.create(this); ctx.Domain = ctx.sequelize.define('Domain', { ip: Sequelize.STRING }); ctx.Environment = ctx.sequelize.define('Environment', { name: Sequelize.STRING }); ctx.Environment.belongsTo(ctx.Domain, { as: 'PrivateDomain', foreignKey: 'privateDomainId' }); ctx.Environment.belongsTo(ctx.Domain, { as: 'PublicDomain', foreignKey: 'publicDomainId' }); - return ctx.Domain.sync({ force: true }).then(() => { - return ctx.Environment.sync({ force: true }).then(() => { - return ctx.Domain.create({ ip: '192.168.0.1' }).then(privateIp => { - return ctx.Domain.create({ ip: '91.65.189.19' }).then(publicIp => { - return ctx.Environment.create({ name: 'environment' }).then(env => { - return env.setPrivateDomain(privateIp).then(() => { - return env.setPublicDomain(publicIp).then(() => { - return ctx.Environment.findOne({ - where: { name: 'environment' }, - include: [ - { model: ctx.Domain, as: 'PrivateDomain' }, - { model: ctx.Domain, as: 'PublicDomain' } - ] - }).then(environment => { - expect(environment).to.exist; - expect(environment.PrivateDomain).to.exist; - expect(environment.PrivateDomain.ip).to.equal('192.168.0.1'); - expect(environment.PublicDomain).to.exist; - expect(environment.PublicDomain.ip).to.equal('91.65.189.19'); - }); - }); - }); - }); - }); - }); - }); + await ctx.Domain.sync({ force: true }); + await ctx.Environment.sync({ force: true }); + const privateIp = await ctx.Domain.create({ ip: '192.168.0.1' }); + const publicIp = await ctx.Domain.create({ ip: '91.65.189.19' }); + const env = await ctx.Environment.create({ name: 'environment' }); + await env.setPrivateDomain(privateIp); + await env.setPublicDomain(publicIp); + + const environment = await ctx.Environment.findOne({ + where: { name: 'environment' }, + include: [ + { model: ctx.Domain, as: 'PrivateDomain' }, + { model: ctx.Domain, as: 'PublicDomain' } + ] }); + + expect(environment).to.exist; + expect(environment.PrivateDomain).to.exist; + expect(environment.PrivateDomain.ip).to.equal('192.168.0.1'); + expect(environment.PublicDomain).to.exist; + expect(environment.PublicDomain.ip).to.equal('91.65.189.19'); }); - it('eager loads with non-id primary keys', function() { + it('eager loads with non-id primary keys', async function() { this.User = this.sequelize.define('UserPKeagerbelong', { username: { type: Sequelize.STRING, @@ -398,25 +371,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); this.User.belongsTo(this.Group); - return this.sequelize.sync({ force: true }).then(() => { - return this.Group.create({ name: 'people' }).then(() => { - return this.User.create({ username: 'someone', GroupPKeagerbelongName: 'people' }).then(() => { - return this.User.findOne({ - where: { - username: 'someone' - }, - include: [this.Group] - }).then(someUser => { - expect(someUser).to.exist; - expect(someUser.username).to.equal('someone'); - expect(someUser.GroupPKeagerbelong.name).to.equal('people'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.Group.create({ name: 'people' }); + await this.User.create({ username: 'someone', GroupPKeagerbelongName: 'people' }); + + const someUser = await this.User.findOne({ + where: { + username: 'someone' + }, + include: [this.Group] }); + + expect(someUser).to.exist; + expect(someUser.username).to.equal('someone'); + expect(someUser.GroupPKeagerbelong.name).to.equal('people'); }); - it('getting parent data in many to one relationship', function() { + it('getting parent data in many to one relationship', async function() { const User = this.sequelize.define('User', { id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true }, username: { type: Sequelize.STRING } @@ -431,36 +402,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasMany(Message); Message.belongsTo(User, { foreignKey: 'user_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'test_testerson' }).then(user => { - return Message.create({ user_id: user.id, message: 'hi there!' }).then(() => { - return Message.create({ user_id: user.id, message: 'a second message' }).then(() => { - return Message.findAll({ - where: { user_id: user.id }, - attributes: [ - 'user_id', - 'message' - ], - include: [{ model: User, attributes: ['username'] }] - }).then(messages => { - expect(messages.length).to.equal(2); - - expect(messages[0].message).to.equal('hi there!'); - expect(messages[0].User.username).to.equal('test_testerson'); - - expect(messages[1].message).to.equal('a second message'); - expect(messages[1].User.username).to.equal('test_testerson'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'test_testerson' }); + await Message.create({ user_id: user.id, message: 'hi there!' }); + await Message.create({ user_id: user.id, message: 'a second message' }); + + const messages = await Message.findAll({ + where: { user_id: user.id }, + attributes: [ + 'user_id', + 'message' + ], + include: [{ model: User, attributes: ['username'] }] }); + + expect(messages.length).to.equal(2); + + expect(messages[0].message).to.equal('hi there!'); + expect(messages[0].User.username).to.equal('test_testerson'); + + expect(messages[1].message).to.equal('a second message'); + expect(messages[1].User.username).to.equal('test_testerson'); }); - it('allows mulitple assocations of the same model with different alias', function() { + it('allows mulitple assocations of the same model with different alias', async function() { this.Worker.belongsTo(this.Task, { as: 'ToDo' }); this.Worker.belongsTo(this.Task, { as: 'DoTo' }); - return this.init(() => { + + await this.init(() => { return this.Worker.findOne({ include: [ { model: this.Task, as: 'ToDo' }, @@ -472,31 +441,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('hasOne', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasOne(this.Task); - return this.init(() => { + + await this.init(() => { return this.worker.setTask(this.task); }); }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findOne({ include: [this.Worker] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findOne({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('Worker is not associated to Task!'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [this.Task] - }).then(worker => { - expect(worker).to.exist; - expect(worker.Task).to.exist; - expect(worker.Task.title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.Task).to.exist; + expect(worker.Task.title).to.equal('homework'); }); - it('eager loads with non-id primary keys', function() { + it('eager loads with non-id primary keys', async function() { this.User = this.sequelize.define('UserPKeagerone', { username: { type: Sequelize.STRING, @@ -511,69 +483,73 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); this.Group.hasOne(this.User); - return this.sequelize.sync({ force: true }).then(() => { - return this.Group.create({ name: 'people' }).then(() => { - return this.User.create({ username: 'someone', GroupPKeageroneName: 'people' }).then(() => { - return this.Group.findOne({ - where: { - name: 'people' - }, - include: [this.User] - }).then(someGroup => { - expect(someGroup).to.exist; - expect(someGroup.name).to.equal('people'); - expect(someGroup.UserPKeagerone.username).to.equal('someone'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.Group.create({ name: 'people' }); + await this.User.create({ username: 'someone', GroupPKeageroneName: 'people' }); + + const someGroup = await this.Group.findOne({ + where: { + name: 'people' + }, + include: [this.User] }); + + expect(someGroup).to.exist; + expect(someGroup.name).to.equal('people'); + expect(someGroup.UserPKeagerone.username).to.equal('someone'); }); }); describe('hasOne with alias', () => { - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findOne({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findOne({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is not associated to Worker!'); - }); + } }); describe('alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasOne(this.Task, { as: 'ToDo' }); - return this.init(() => { + + await this.init(() => { return this.worker.setToDo(this.task); }); }); - it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', function() { - return this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', async function() { + try { + await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDo).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(worker => { - expect(worker).to.exist; - expect(worker.ToDo).to.exist; - expect(worker.ToDo.title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.ToDo).to.exist; + expect(worker.ToDo.title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(worker => { - expect(worker.ToDo.title).to.equal('homework'); }); + + expect(worker.ToDo.title).to.equal('homework'); }); - it('allows mulitple assocations of the same model with different alias', function() { + it('allows mulitple assocations of the same model with different alias', async function() { this.Worker.hasOne(this.Task, { as: 'DoTo' }); - return this.init(() => { + + await this.init(() => { return this.Worker.findOne({ include: [ { model: this.Task, as: 'ToDo' }, @@ -586,31 +562,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('hasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasMany(this.Task); - return this.init(() => { + + await this.init(() => { return this.worker.setTasks([this.task]); }); }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findOne({ include: [this.Worker] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findOne({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('Worker is not associated to Task!'); - }); + } }); - it('returns the associated tasks via worker.tasks', function() { - return this.Worker.findOne({ + it('returns the associated tasks via worker.tasks', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [this.Task] - }).then(worker => { - expect(worker).to.exist; - expect(worker.Tasks).to.exist; - expect(worker.Tasks[0].title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.Tasks).to.exist; + expect(worker.Tasks[0].title).to.equal('homework'); }); - it('including two has many relations should not result in duplicate values', function() { + it('including two has many relations should not result in duplicate values', async function() { this.Contact = this.sequelize.define('Contact', { name: DataTypes.STRING }); this.Photo = this.sequelize.define('Photo', { img: DataTypes.TEXT }); this.PhoneNumber = this.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT }); @@ -618,33 +597,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Contact.hasMany(this.Photo, { as: 'Photos' }); this.Contact.hasMany(this.PhoneNumber); - return this.sequelize.sync({ force: true }).then(() => { - return this.Contact.create({ name: 'Boris' }).then(someContact => { - return this.Photo.create({ img: 'img.jpg' }).then(somePhoto => { - return this.PhoneNumber.create({ phone: '000000' }).then(somePhone1 => { - return this.PhoneNumber.create({ phone: '111111' }).then(somePhone2 => { - return someContact.setPhotos([somePhoto]).then(() => { - return someContact.setPhoneNumbers([somePhone1, somePhone2]).then(() => { - return this.Contact.findOne({ - where: { - name: 'Boris' - }, - include: [this.PhoneNumber, { model: this.Photo, as: 'Photos' }] - }).then(fetchedContact => { - expect(fetchedContact).to.exist; - expect(fetchedContact.Photos.length).to.equal(1); - expect(fetchedContact.PhoneNumbers.length).to.equal(2); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const someContact = await this.Contact.create({ name: 'Boris' }); + const somePhoto = await this.Photo.create({ img: 'img.jpg' }); + const somePhone1 = await this.PhoneNumber.create({ phone: '000000' }); + const somePhone2 = await this.PhoneNumber.create({ phone: '111111' }); + await someContact.setPhotos([somePhoto]); + await someContact.setPhoneNumbers([somePhone1, somePhone2]); + + const fetchedContact = await this.Contact.findOne({ + where: { + name: 'Boris' + }, + include: [this.PhoneNumber, { model: this.Photo, as: 'Photos' }] }); + + expect(fetchedContact).to.exist; + expect(fetchedContact.Photos.length).to.equal(1); + expect(fetchedContact.PhoneNumbers.length).to.equal(2); }); - it('eager loads with non-id primary keys', function() { + it('eager loads with non-id primary keys', async function() { this.User = this.sequelize.define('UserPKeagerone', { username: { type: Sequelize.STRING, @@ -660,71 +633,74 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Group.belongsToMany(this.User, { through: 'group_user' }); this.User.belongsToMany(this.Group, { through: 'group_user' }); - return this.sequelize.sync({ force: true }).then(() => { - return this.User.create({ username: 'someone' }).then(someUser => { - return this.Group.create({ name: 'people' }).then(someGroup => { - return someUser.setGroupPKeagerones([someGroup]).then(() => { - return this.User.findOne({ - where: { - username: 'someone' - }, - include: [this.Group] - }).then(someUser => { - expect(someUser).to.exist; - expect(someUser.username).to.equal('someone'); - expect(someUser.GroupPKeagerones[0].name).to.equal('people'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const someUser = await this.User.create({ username: 'someone' }); + const someGroup = await this.Group.create({ name: 'people' }); + await someUser.setGroupPKeagerones([someGroup]); + + const someUser0 = await this.User.findOne({ + where: { + username: 'someone' + }, + include: [this.Group] }); + + expect(someUser0).to.exist; + expect(someUser0.username).to.equal('someone'); + expect(someUser0.GroupPKeagerones[0].name).to.equal('people'); }); }); describe('hasMany with alias', () => { - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findOne({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findOne({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is not associated to Worker!'); - }); + } }); describe('alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasMany(this.Task, { as: 'ToDos' }); - return this.init(() => { + + await this.init(() => { return this.worker.setToDos([this.task]); }); }); - it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', function() { - return this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', async function() { + try { + await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDos).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(worker => { - expect(worker).to.exist; - expect(worker.ToDos).to.exist; - expect(worker.ToDos[0].title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.ToDos).to.exist; + expect(worker.ToDos[0].title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(worker => { - expect(worker.ToDos[0].title).to.equal('homework'); }); + + expect(worker.ToDos[0].title).to.equal('homework'); }); - it('allows mulitple assocations of the same model with different alias', function() { + it('allows mulitple assocations of the same model with different alias', async function() { this.Worker.hasMany(this.Task, { as: 'DoTos' }); - return this.init(() => { + + await this.init(() => { return this.Worker.findOne({ include: [ { model: this.Task, as: 'ToDos' }, @@ -742,179 +718,180 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Tag = this.sequelize.define('Tag', { name: Sequelize.STRING }); }); - it('returns the associated models when using through as string and alias', function() { + it('returns the associated models when using through as string and alias', async function() { this.Product.belongsToMany(this.Tag, { as: 'tags', through: 'product_tag' }); this.Tag.belongsToMany(this.Product, { as: 'products', through: 'product_tag' }); - return this.sequelize.sync().then(() => { - return Promise.all([ - this.Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Handbag' }, - { title: 'Dress' }, - { title: 'Jan' } - ]), - this.Tag.bulkCreate([ - { name: 'Furniture' }, - { name: 'Clothing' }, - { name: 'People' } - ]) - ]).then(() => { - return Promise.all([ - this.Product.findAll(), - this.Tag.findAll() - ]); - }).then(([products, tags]) => { - this.products = products; - this.tags = tags; - return Promise.all([ - products[0].setTags([tags[0], tags[1]]), - products[1].addTag(tags[0]), - products[2].addTag(tags[1]), - products[3].setTags([tags[1]]), - products[4].setTags([tags[2]]) - ]).then(() => { - return Promise.all([ - this.Tag.findOne({ - where: { - id: tags[0].id - }, - include: [ - { model: this.Product, as: 'products' } - ] - }).then(tag => { - expect(tag).to.exist; - expect(tag.products.length).to.equal(2); - }), - tags[1].getProducts().then(products => { - expect(products.length).to.equal(3); - }), - this.Product.findOne({ - where: { - id: products[0].id - }, - include: [ - { model: this.Tag, as: 'tags' } - ] - }).then(product => { - expect(product).to.exist; - expect(product.tags.length).to.equal(2); - }), - products[1].getTags().then(tags => { - expect(tags.length).to.equal(1); - }) - ]); - }); - }); - }); - }); - - it('returns the associated models when using through as model and alias', function() { - // Exactly the same code as the previous test, just with a through model instance, and promisified - const ProductTag = this.sequelize.define('product_tag'); - - this.Product.belongsToMany(this.Tag, { as: 'tags', through: ProductTag }); - this.Tag.belongsToMany(this.Product, { as: 'products', through: ProductTag }); - - return this.sequelize.sync().then(() => { - return Promise.all([ - this.Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Handbag' }, - { title: 'Dress' }, - { title: 'Jan' } - ]), - this.Tag.bulkCreate([ - { name: 'Furniture' }, - { name: 'Clothing' }, - { name: 'People' } - ]) - ]); - }).then(() => { - return Promise.all([ - this.Product.findAll(), - this.Tag.findAll() - ]); - }).then(([products, tags]) => { - this.products = products; - this.tags = tags; - - return Promise.all([ - products[0].setTags([tags[0], tags[1]]), - products[1].addTag(tags[0]), - products[2].addTag(tags[1]), - products[3].setTags([tags[1]]), - products[4].setTags([tags[2]]) - ]); - }).then(() => { - return Promise.all([ - expect(this.Tag.findOne({ + await this.sequelize.sync(); + + await Promise.all([ + this.Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Handbag' }, + { title: 'Dress' }, + { title: 'Jan' } + ]), + this.Tag.bulkCreate([ + { name: 'Furniture' }, + { name: 'Clothing' }, + { name: 'People' } + ]) + ]); + + const [products, tags] = await Promise.all([ + this.Product.findAll(), + this.Tag.findAll() + ]); + + this.products = products; + this.tags = tags; + + await Promise.all([ + products[0].setTags([tags[0], tags[1]]), + products[1].addTag(tags[0]), + products[2].addTag(tags[1]), + products[3].setTags([tags[1]]), + products[4].setTags([tags[2]]) + ]); + + await Promise.all([ + (async () => { + const tag = await this.Tag.findOne({ where: { - id: this.tags[0].id + id: tags[0].id }, include: [ { model: this.Product, as: 'products' } ] - })).to.eventually.have.property('products').to.have.length(2), - expect(this.Product.findOne({ + }); + + expect(tag).to.exist; + expect(tag.products.length).to.equal(2); + })(), + tags[1].getProducts().then(products => { + expect(products.length).to.equal(3); + }), + (async () => { + const product = await this.Product.findOne({ where: { - id: this.products[0].id + id: products[0].id }, include: [ { model: this.Tag, as: 'tags' } ] - })).to.eventually.have.property('tags').to.have.length(2), - expect(this.tags[1].getProducts()).to.eventually.have.length(3), - expect(this.products[1].getTags()).to.eventually.have.length(1) - ]); - }); + }); + + expect(product).to.exist; + expect(product.tags.length).to.equal(2); + })(), + products[1].getTags().then(tags => { + expect(tags.length).to.equal(1); + }) + ]); + }); + + it('returns the associated models when using through as model and alias', async function() { + // Exactly the same code as the previous test, just with a through model instance, and promisified + const ProductTag = this.sequelize.define('product_tag'); + + this.Product.belongsToMany(this.Tag, { as: 'tags', through: ProductTag }); + this.Tag.belongsToMany(this.Product, { as: 'products', through: ProductTag }); + + await this.sequelize.sync(); + + await Promise.all([ + this.Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Handbag' }, + { title: 'Dress' }, + { title: 'Jan' } + ]), + this.Tag.bulkCreate([ + { name: 'Furniture' }, + { name: 'Clothing' }, + { name: 'People' } + ]) + ]); + + const [products, tags] = await Promise.all([ + this.Product.findAll(), + this.Tag.findAll() + ]); + + this.products = products; + this.tags = tags; + + await Promise.all([ + products[0].setTags([tags[0], tags[1]]), + products[1].addTag(tags[0]), + products[2].addTag(tags[1]), + products[3].setTags([tags[1]]), + products[4].setTags([tags[2]]) + ]); + + await Promise.all([ + expect(this.Tag.findOne({ + where: { + id: this.tags[0].id + }, + include: [ + { model: this.Product, as: 'products' } + ] + })).to.eventually.have.property('products').to.have.length(2), + expect(this.Product.findOne({ + where: { + id: this.products[0].id + }, + include: [ + { model: this.Tag, as: 'tags' } + ] + })).to.eventually.have.property('tags').to.have.length(2), + expect(this.tags[1].getProducts()).to.eventually.have.length(3), + expect(this.products[1].getTags()).to.eventually.have.length(1) + ]); }); }); }); describe('queryOptions', () => { - beforeEach(function() { - return this.User.create({ username: 'barfooz' }).then(user => { - this.user = user; - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'barfooz' }); + this.user = user; }); - it('should return a DAO when queryOptions are not set', function() { - return this.User.findOne({ where: { username: 'barfooz' } }).then(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when queryOptions are not set', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' } }); + expect(user).to.be.instanceOf(this.User); }); - it('should return a DAO when raw is false', function() { - return this.User.findOne({ where: { username: 'barfooz' }, raw: false }).then(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when raw is false', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: false }); + expect(user).to.be.instanceOf(this.User); }); - it('should return raw data when raw is true', function() { - return this.User.findOne({ where: { username: 'barfooz' }, raw: true }).then(user => { - expect(user).to.not.be.instanceOf(this.User); - expect(user).to.be.instanceOf(Object); - }); + it('should return raw data when raw is true', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: true }); + expect(user).to.not.be.instanceOf(this.User); + expect(user).to.be.instanceOf(Object); }); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.findOne({ + await this.User.findOne({ where: {}, logging: spy - }).then(() => { - expect(spy.called).to.be.ok; }); + + expect(spy.called).to.be.ok; }); describe('rejectOnEmpty mode', () => { - it('throws error when record not found by findOne', function() { - return expect(this.User.findOne({ + it('throws error when record not found by findOne', async function() { + await expect(this.User.findOne({ where: { username: 'ath-kantam-pradakshnami' }, @@ -922,14 +899,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('throws error when record not found by findByPk', function() { - return expect(this.User.findByPk(4732322332323333232344334354234, { + it('throws error when record not found by findByPk', async function() { + await expect(this.User.findByPk(4732322332323333232344334354234, { rejectOnEmpty: true })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('throws error when record not found by find', function() { - return expect(this.User.findOne({ + it('throws error when record not found by find', async function() { + await expect(this.User.findOne({ where: { username: 'some-username-that-is-not-used-anywhere' }, @@ -937,54 +914,51 @@ describe(Support.getTestDialectTeaser('Model'), () => { })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('works from model options', () => { + it('works from model options', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: true }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findOne({ - where: { - username: 'some-username-that-is-not-used-anywhere' - } - })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); - }); + await Model.sync({ force: true }); + + await expect(Model.findOne({ + where: { + username: 'some-username-that-is-not-used-anywhere' + } + })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('override model options', () => { + it('override model options', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: true }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findOne({ - rejectOnEmpty: false, - where: { - username: 'some-username-that-is-not-used-anywhere' - } - })).to.eventually.be.deep.equal(null); - }); + await Model.sync({ force: true }); + + await expect(Model.findOne({ + rejectOnEmpty: false, + where: { + username: 'some-username-that-is-not-used-anywhere' + } + })).to.eventually.be.deep.equal(null); }); - it('resolve null when disabled', () => { + it('resolve null when disabled', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findOne({ - where: { - username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' - } - })).to.eventually.be.equal(null); - }); + await Model.sync({ force: true }); + + await expect(Model.findOne({ + where: { + username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' + } + })).to.eventually.be.equal(null); }); }); }); diff --git a/test/integration/model/findOrBuild.test.js b/test/integration/model/findOrBuild.test.js index 983a3647402f..25d9eff089a1 100644 --- a/test/integration/model/findOrBuild.test.js +++ b/test/integration/model/findOrBuild.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -18,41 +18,43 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.User.hasMany(this.Project); this.Project.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('findOrBuild', () => { - it('initialize with includes', function() { - return this.User.bulkCreate([ + it('initialize with includes', async function() { + const [, user2] = await this.User.bulkCreate([ { username: 'Mello', age: 10 }, { username: 'Mello', age: 20 } - ], { returning: true }).then(([, user2]) => { - return this.Project.create({ - name: 'Investigate' - }).then(project => user2.setProjects([project])); - }).then(() => { - return this.User.findOrBuild({ - defaults: { - username: 'Mello', - age: 10 - }, - where: { - age: 20 - }, - include: [{ - model: this.Project - }] - }); - }).then(([user, created]) => { - expect(created).to.be.false; - expect(user.get('id')).to.be.ok; - expect(user.get('username')).to.equal('Mello'); - expect(user.get('age')).to.equal(20); - - expect(user.Projects).to.have.length(1); - expect(user.Projects[0].get('name')).to.equal('Investigate'); + ], { returning: true }); + + const project = await this.Project.create({ + name: 'Investigate' + }); + + await user2.setProjects([project]); + + const [user, created] = await this.User.findOrBuild({ + defaults: { + username: 'Mello', + age: 10 + }, + where: { + age: 20 + }, + include: [{ + model: this.Project + }] }); + + expect(created).to.be.false; + expect(user.get('id')).to.be.ok; + expect(user.get('username')).to.equal('Mello'); + expect(user.get('age')).to.equal(20); + + expect(user.Projects).to.have.length(1); + expect(user.Projects[0].get('name')).to.equal('Investigate'); }); }); }); diff --git a/test/integration/model/geography.test.js b/test/integration/model/geography.test.js index ede7ff497276..168f8cfbc9a3 100644 --- a/test/integration/model/geography.test.js +++ b/test/integration/model/geography.test.js @@ -10,16 +10,16 @@ const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.GEOGRAPHY) { describe('GEOGRAPHY', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('works with aliases fields', function() { + it('works with aliases fields', async function() { const Pub = this.sequelize.define('Pub', { location: { field: 'coordinates', type: DataTypes.GEOGRAPHY } }), @@ -33,15 +33,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return Pub.sync({ force: true }).then(() => { - return Pub.create({ location: point }); - }).then(pub => { - expect(pub).not.to.be.null; - expect(pub.location).to.be.deep.eql(point); - }); + await Pub.sync({ force: true }); + const pub = await Pub.create({ location: point }); + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722], @@ -53,13 +51,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722], @@ -81,27 +78,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }; const props = { username: 'username', geography: point1 }; - return User.create(props).then(() => { - return User.update({ geography: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geography: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(point2); }); }); describe('GEOGRAPHY(POINT)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('POINT') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722], @@ -113,13 +107,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722], @@ -141,27 +134,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }; const props = { username: 'username', geography: point1 }; - return User.create(props).then(() => { - return User.update({ geography: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geography: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(point2); }); }); describe('GEOGRAPHY(LINESTRING)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('LINESTRING') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]], @@ -173,13 +163,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; const point1 = { type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]], @@ -201,27 +190,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }; const props = { username: 'username', geography: point1 }; - return User.create(props).then(() => { - return User.update({ geography: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geography: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(point2); }); }); describe('GEOGRAPHY(POLYGON)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('POLYGON') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; const point = { type: 'Polygon', coordinates: [ @@ -236,13 +222,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; const polygon1 = { type: 'Polygon', coordinates: [ @@ -269,28 +254,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { }; const props = { username: 'username', geography: polygon1 }; - return User.create(props).then(() => { - return User.update({ geography: polygon2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(polygon2); - }); + await User.create(props); + await User.update({ geography: polygon2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(polygon2); }); }); if (current.dialect.name === 'postgres') { describe('GEOGRAPHY(POLYGON, SRID)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('POLYGON', 4326) }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; const point = { type: 'Polygon', coordinates: [ @@ -305,13 +287,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; const polygon1 = { type: 'Polygon', coordinates: [ @@ -338,27 +319,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }; const props = { username: 'username', geography: polygon1 }; - return User.create(props).then(() => { - return User.update({ geography: polygon2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(polygon2); - }); + await User.create(props); + await User.update({ geography: polygon2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(polygon2); }); }); } describe('sql injection attacks', () => { - beforeEach(function() { + beforeEach(async function() { this.Model = this.sequelize.define('Model', { location: DataTypes.GEOGRAPHY }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should properly escape the single quotes', function() { - return this.Model.create({ + it('should properly escape the single quotes', async function() { + await this.Model.create({ location: { type: 'Point', properties: { diff --git a/test/integration/model/geometry.test.js b/test/integration/model/geometry.test.js index c521f27b6f13..dea09c8d4ec1 100644 --- a/test/integration/model/geometry.test.js +++ b/test/integration/model/geometry.test.js @@ -12,55 +12,49 @@ const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.GEOMETRY) { describe('GEOMETRY', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('works with aliases fields', function() { + it('works with aliases fields', async function() { const Pub = this.sequelize.define('Pub', { location: { field: 'coordinates', type: DataTypes.GEOMETRY } }), point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return Pub.sync({ force: true }).then(() => { - return Pub.create({ location: point }); - }).then(pub => { - expect(pub).not.to.be.null; - expect(pub.location).to.be.deep.eql(point); - }); + await Pub.sync({ force: true }); + const pub = await Pub.create({ location: point }); + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; const props = { username: 'username', geometry: point1 }; - return User.create(props).then(() => { - return User.update({ geometry: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geometry: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(point2); }); - it('works with crs field', function() { + it('works with crs field', async function() { const Pub = this.sequelize.define('Pub', { location: { field: 'coordinates', type: DataTypes.GEOMETRY } }), @@ -73,51 +67,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return Pub.sync({ force: true }).then(() => { - return Pub.create({ location: point }); - }).then(pub => { - expect(pub).not.to.be.null; - expect(pub.location).to.be.deep.eql(point); - }); + await Pub.sync({ force: true }); + const pub = await Pub.create({ location: point }); + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); }); }); describe('GEOMETRY(POINT)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY('POINT') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; const props = { username: 'username', geometry: point1 }; - return User.create(props).then(() => { - return User.update({ geometry: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geometry: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(point2); }); - it('works with crs field', function() { + it('works with crs field', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722], crs: { @@ -128,49 +116,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); }); describe('GEOMETRY(LINESTRING)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY('LINESTRING') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const point1 = { type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]] }, point2 = { type: 'LineString', coordinates: [[101.0, 0.0], [102.0, 1.0]] }; const props = { username: 'username', geometry: point1 }; - return User.create(props).then(() => { - return User.update({ geometry: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geometry: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(point2); }); - it('works with crs field', function() { + it('works with crs field', async function() { const User = this.User; const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]], crs: { @@ -181,38 +164,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); }); describe('GEOMETRY(POLYGON)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY('POLYGON') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'Polygon', coordinates: [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] ] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('works with crs field', function() { + it('works with crs field', async function() { const User = this.User; const point = { type: 'Polygon', coordinates: [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], @@ -225,13 +206,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const polygon1 = { type: 'Polygon', coordinates: [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] @@ -242,26 +222,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }; const props = { username: 'username', geometry: polygon1 }; - return User.create(props).then(() => { - return User.update({ geometry: polygon2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(polygon2); - }); + await User.create(props); + await User.update({ geometry: polygon2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(polygon2); }); }); describe('sql injection attacks', () => { - beforeEach(function() { + beforeEach(async function() { this.Model = this.sequelize.define('Model', { location: DataTypes.GEOMETRY }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should properly escape the single quotes', function() { - return this.Model.create({ + it('should properly escape the single quotes', async function() { + await this.Model.create({ location: { type: 'Point', properties: { @@ -272,14 +249,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should properly escape the single quotes in coordinates', function() { - + it('should properly escape the single quotes in coordinates', async function() { // MySQL 5.7, those guys finally fixed this if (dialect === 'mysql' && semver.gte(this.sequelize.options.databaseVersion, '5.7.0')) { return; - } + } - return this.Model.create({ + await this.Model.create({ location: { type: 'Point', properties: { diff --git a/test/integration/model/increment.test.js b/test/integration/model/increment.test.js index a43abb0debf2..dd52ac549e51 100644 --- a/test/integration/model/increment.test.js +++ b/test/integration/model/increment.test.js @@ -15,7 +15,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true }, aNumber: { type: DataTypes.INTEGER }, @@ -23,26 +23,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { cNumber: { type: DataTypes.INTEGER, field: 'c_number' } }); - return this.User.sync({ force: true }).then(() => { - return this.User.bulkCreate([{ - id: 1, - aNumber: 0, - bNumber: 0 - }, { - id: 2, - aNumber: 0, - bNumber: 0 - }, { - id: 3, - aNumber: 0, - bNumber: 0 - }, { - id: 4, - aNumber: 0, - bNumber: 0, - cNumber: 0 - }]); - }); + await this.User.sync({ force: true }); + + await this.User.bulkCreate([{ + id: 1, + aNumber: 0, + bNumber: 0 + }, { + id: 2, + aNumber: 0, + bNumber: 0 + }, { + id: 3, + aNumber: 0, + bNumber: 0 + }, { + id: 4, + aNumber: 0, + bNumber: 0, + cNumber: 0 + }]); }); [ @@ -56,140 +56,112 @@ describe(Support.getTestDialectTeaser('Model'), () => { }; }); - it('supports where conditions', function() { - return this.User.findByPk(1).then(() => { - return this.User[method](['aNumber'], { by: 2, where: { id: 1 } }).then(() => { - return this.User.findByPk(2).then(user3 => { - expect(user3.aNumber).to.be.equal(this.assert(0, 0)); - }); - }); - }); + it('supports where conditions', async function() { + await this.User.findByPk(1); + await this.User[method](['aNumber'], { by: 2, where: { id: 1 } }); + const user3 = await this.User.findByPk(2); + expect(user3.aNumber).to.be.equal(this.assert(0, 0)); }); - it('uses correct column names for where conditions', function() { - return this.User[method](['aNumber'], { by: 2, where: { cNumber: 0 } }).then(() => { - return this.User.findByPk(4).then(user4 => { - expect(user4.aNumber).to.be.equal(this.assert(2, -2)); - }); - }); + it('uses correct column names for where conditions', async function() { + await this.User[method](['aNumber'], { by: 2, where: { cNumber: 0 } }); + const user4 = await this.User.findByPk(4); + expect(user4.aNumber).to.be.equal(this.assert(2, -2)); }); - it('should still work right with other concurrent increments', function() { - return this.User.findAll().then(aUsers => { - return Promise.all([ - this.User[method](['aNumber'], { by: 2, where: {} }), - this.User[method](['aNumber'], { by: 2, where: {} }), - this.User[method](['aNumber'], { by: 2, where: {} }) - ]).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 6, aUsers[i].aNumber - 6)); - } - }); - }); - }); + it('should still work right with other concurrent increments', async function() { + const aUsers = await this.User.findAll(); + + await Promise.all([ + this.User[method](['aNumber'], { by: 2, where: {} }), + this.User[method](['aNumber'], { by: 2, where: {} }), + this.User[method](['aNumber'], { by: 2, where: {} }) + ]); + + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 6, aUsers[i].aNumber - 6)); + } }); - it('with array', function() { - return this.User.findAll().then(aUsers => { - return this.User[method](['aNumber'], { by: 2, where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); - } - }); - }); - }); + it('with array', async function() { + const aUsers = await this.User.findAll(); + await this.User[method](['aNumber'], { by: 2, where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); + } }); - it('with single field', function() { - return this.User.findAll().then(aUsers => { - return this.User[method]('aNumber', { by: 2, where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); - } - }); - }); - }); + it('with single field', async function() { + const aUsers = await this.User.findAll(); + await this.User[method]('aNumber', { by: 2, where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); + } }); - it('with single field and no value', function() { - return this.User.findAll().then(aUsers => { - return this.User[method]('aNumber', { where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); - } - }); - }); - }); + it('with single field and no value', async function() { + const aUsers = await this.User.findAll(); + await this.User[method]('aNumber', { where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); + } }); - it('with key value pair', function() { - return this.User.findAll().then(aUsers => { - return this.User[method]({ 'aNumber': 1, 'bNumber': 2 }, { where: { } }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); - expect(bUsers[i].bNumber).to.equal(this.assert(aUsers[i].bNumber + 2, aUsers[i].bNumber - 2)); - } - }); - }); - }); + it('with key value pair', async function() { + const aUsers = await this.User.findAll(); + await this.User[method]({ 'aNumber': 1, 'bNumber': 2 }, { where: { } }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); + expect(bUsers[i].bNumber).to.equal(this.assert(aUsers[i].bNumber + 2, aUsers[i].bNumber - 2)); + } }); - it('should still work right with other concurrent updates', function() { - return this.User.findAll().then(aUsers => { - return this.User.update({ 'aNumber': 2 }, { where: {} }).then(() => { - return this.User[method](['aNumber'], { by: 2, where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - // for decrement 2 - 2 = 0 - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 4, aUsers[i].aNumber)); - } - }); - }); - }); - }); + it('should still work right with other concurrent updates', async function() { + const aUsers = await this.User.findAll(); + await this.User.update({ 'aNumber': 2 }, { where: {} }); + await this.User[method](['aNumber'], { by: 2, where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + // for decrement 2 - 2 = 0 + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 4, aUsers[i].aNumber)); + } }); - it('with timestamps set to true', function() { + it('with timestamps set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; - this.clock.tick(1000); - return User[method]('aNumber', { by: 1, where: {} }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); - }); + this.clock.tick(1000); + await User[method]('aNumber', { by: 1, where: {} }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); }); - it('with timestamps set to true and options.silent set to true', function() { + it('with timestamps set to true and options.silent set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return User[method]('aNumber', { by: 1, silent: true, where: { } }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await User[method]('aNumber', { by: 1, silent: true, where: { } }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); }); - it('should work with scopes', function() { + it('should work with scopes', async function() { const User = this.sequelize.define('User', { aNumber: DataTypes.INTEGER, name: DataTypes.STRING @@ -203,66 +175,58 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { - aNumber: 1, - name: 'Jeff' - }, - { - aNumber: 3, - name: 'Not Jeff' - } - ]); - }).then(() => { - return User.scope('jeff')[method]('aNumber', {}); - }).then(() => { - return User.scope('jeff').findOne(); - }).then(jeff => { - expect(jeff.aNumber).to.equal(this.assert(2, 0)); - }).then(() => { - return User.findOne({ - where: { - name: 'Not Jeff' - } - }); - }).then(notJeff => { - expect(notJeff.aNumber).to.equal(this.assert(3, 3)); + await User.sync({ force: true }); + + await User.bulkCreate([ + { + aNumber: 1, + name: 'Jeff' + }, + { + aNumber: 3, + name: 'Not Jeff' + } + ]); + + await User.scope('jeff')[method]('aNumber', {}); + const jeff = await User.scope('jeff').findOne(); + expect(jeff.aNumber).to.equal(this.assert(2, 0)); + + const notJeff = await User.findOne({ + where: { + name: 'Not Jeff' + } }); + + expect(notJeff.aNumber).to.equal(this.assert(3, 3)); }); - it('should not care for attributes in the instance scope', function() { + it('should not care for attributes in the instance scope', async function() { this.User.addScope('test', { attributes: ['foo', 'bar'] }); - return this.User.scope('test').create({ id: 5, aNumber: 5 }) - .then(createdUser => createdUser[method]('aNumber', { by: 2 })) - .then(() => this.User.findByPk(5)) - .then(user => { - expect(user.aNumber).to.equal(this.assert(7, 3)); - }); + const createdUser = await this.User.scope('test').create({ id: 5, aNumber: 5 }); + await createdUser[method]('aNumber', { by: 2 }); + const user = await this.User.findByPk(5); + expect(user.aNumber).to.equal(this.assert(7, 3)); }); - it('should not care for exclude-attributes in the instance scope', function() { + it('should not care for exclude-attributes in the instance scope', async function() { this.User.addScope('test', { attributes: { exclude: ['foo', 'bar'] } }); - return this.User.scope('test').create({ id: 5, aNumber: 5 }) - .then(createdUser => createdUser[method]('aNumber', { by: 2 })) - .then(() => this.User.findByPk(5)) - .then(user => { - expect(user.aNumber).to.equal(this.assert(7, 3)); - }); + const createdUser = await this.User.scope('test').create({ id: 5, aNumber: 5 }); + await createdUser[method]('aNumber', { by: 2 }); + const user = await this.User.findByPk(5); + expect(user.aNumber).to.equal(this.assert(7, 3)); }); - it('should not care for include-attributes in the instance scope', function() { + it('should not care for include-attributes in the instance scope', async function() { this.User.addScope('test', { attributes: { include: ['foo', 'bar'] } }); - return this.User.scope('test').create({ id: 5, aNumber: 5 }) - .then(createdUser => createdUser[method]('aNumber', { by: 2 })) - .then(() => this.User.findByPk(5)) - .then(user => { - expect(user.aNumber).to.equal(this.assert(7, 3)); - }); + const createdUser = await this.User.scope('test').create({ id: 5, aNumber: 5 }); + await createdUser[method]('aNumber', { by: 2 }); + const user = await this.User.findByPk(5); + expect(user.aNumber).to.equal(this.assert(7, 3)); }); }); diff --git a/test/integration/model/json.test.js b/test/integration/model/json.test.js index 0f712c9c86cb..d4828e72f2f3 100644 --- a/test/integration/model/json.test.js +++ b/test/integration/model/json.test.js @@ -12,7 +12,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.JSON) { describe('JSON', () => { - beforeEach(function() { + beforeEach(async function() { this.Event = this.sequelize.define('Event', { data: { type: DataTypes.JSON, @@ -22,44 +22,41 @@ describe(Support.getTestDialectTeaser('Model'), () => { json: DataTypes.JSON }); - return this.Event.sync({ force: true }); + await this.Event.sync({ force: true }); }); if (current.dialect.supports.lock) { - it('findOrCreate supports transactions, json and locks', function() { - return current.transaction().then(transaction => { - return this.Event.findOrCreate({ - where: { - json: { some: { input: 'Hello' } } - }, - defaults: { - json: { some: { input: 'Hello' }, input: [1, 2, 3] }, - data: { some: { input: 'There' }, input: [4, 5, 6] } - }, - transaction, - lock: transaction.LOCK.UPDATE, - logging: sql => { - if (sql.includes('SELECT') && !sql.includes('CREATE')) { - expect(sql.includes('FOR UPDATE')).to.be.true; - } + it('findOrCreate supports transactions, json and locks', async function() { + const transaction = await current.transaction(); + + await this.Event.findOrCreate({ + where: { + json: { some: { input: 'Hello' } } + }, + defaults: { + json: { some: { input: 'Hello' }, input: [1, 2, 3] }, + data: { some: { input: 'There' }, input: [4, 5, 6] } + }, + transaction, + lock: transaction.LOCK.UPDATE, + logging: sql => { + if (sql.includes('SELECT') && !sql.includes('CREATE')) { + expect(sql.includes('FOR UPDATE')).to.be.true; } - }).then(() => { - return this.Event.count().then(count => { - expect(count).to.equal(0); - return transaction.commit().then(() => { - return this.Event.count().then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); + } }); + + const count = await this.Event.count(); + expect(count).to.equal(0); + await transaction.commit(); + const count0 = await this.Event.count(); + expect(count0).to.equal(1); }); } describe('create', () => { - it('should create an instance with JSON data', function() { - return this.Event.create({ + it('should create an instance with JSON data', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -67,25 +64,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return this.Event.findAll().then(events => { - const event = events[0]; + }); - expect(event.get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); - }); + const events = await this.Event.findAll(); + const event = events[0]; + + expect(event.get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' }); }); }); describe('update', () => { - it('should update with JSON column (dot notation)', function() { - return this.Event.bulkCreate([{ + it('should update with JSON column (dot notation)', async function() { + await this.Event.bulkCreate([{ id: 1, data: { name: { @@ -103,7 +99,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Multiverse Scientist' } - }]).then(() => this.Event.update({ + }]); + + await this.Event.update({ 'data': { name: { first: 'Rick', @@ -115,19 +113,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { where: { 'data.name.first': 'Rick' } - })).then(() => this.Event.findByPk(2)).then(event => { - expect(event.get('data')).to.eql({ - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Galactic Fed Prisioner' - }); + }); + + const event = await this.Event.findByPk(2); + expect(event.get('data')).to.eql({ + name: { + first: 'Rick', + last: 'Sanchez' + }, + employment: 'Galactic Fed Prisioner' }); }); - it('should update with JSON column (JSON notation)', function() { - return this.Event.bulkCreate([{ + it('should update with JSON column (JSON notation)', async function() { + await this.Event.bulkCreate([{ id: 1, data: { name: { @@ -145,7 +144,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Multiverse Scientist' } - }]).then(() => this.Event.update({ + }]); + + await this.Event.update({ 'data': { name: { first: 'Rick', @@ -161,19 +162,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } } - })).then(() => this.Event.findByPk(2)).then(event => { - expect(event.get('data')).to.eql({ - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Galactic Fed Prisioner' - }); + }); + + const event = await this.Event.findByPk(2); + expect(event.get('data')).to.eql({ + name: { + first: 'Rick', + last: 'Sanchez' + }, + employment: 'Galactic Fed Prisioner' }); }); - it('should update an instance with JSON data', function() { - return this.Event.create({ + it('should update an instance with JSON data', async function() { + const event0 = await this.Event.create({ data: { name: { first: 'Homer', @@ -181,35 +183,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(event => { - return event.update({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: null - } - }); - }).then(() => { - return this.Event.findAll().then(events => { - const event = events[0]; + }); - expect(event.get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: null - }); - }); + await event0.update({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: null + } + }); + + const events = await this.Event.findAll(); + const event = events[0]; + + expect(event.get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: null }); }); }); describe('find', () => { - it('should be possible to query a nested value', function() { - return Promise.all([this.Event.create({ + it('should be possible to query a nested value', async function() { + await Promise.all([this.Event.create({ data: { name: { first: 'Homer', @@ -225,115 +226,116 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Housewife' } - })]).then(() => { - return this.Event.findAll({ - where: { - data: { - employment: 'Housewife' - } - } - }).then(events => { - const event = events[0]; + })]); - expect(events.length).to.equal(1); - expect(event.get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, + const events = await this.Event.findAll({ + where: { + data: { employment: 'Housewife' - }); - }); + } + } + }); + + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' }); }); - it('should be possible to query dates with array operators', function() { + it('should be possible to query dates with array operators', async function() { const now = moment().milliseconds(0).toDate(); const before = moment().milliseconds(0).subtract(1, 'day').toDate(); const after = moment().milliseconds(0).add(1, 'day').toDate(); - return Promise.all([this.Event.create({ + + await Promise.all([this.Event.create({ json: { user: 'Homer', lastLogin: now } - })]).then(() => { - return this.Event.findAll({ - where: { - json: { - lastLogin: now - } + })]); + + const events0 = await this.Event.findAll({ + where: { + json: { + lastLogin: now } - }).then(events => { - const event = events[0]; - - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - lastLogin: now.toISOString() - }); - }); - }).then(() => { - return this.Event.findAll({ - where: { - json: { - lastLogin: { [Op.between]: [before, after] } - } + } + }); + + const event0 = events0[0]; + + expect(events0.length).to.equal(1); + expect(event0.get('json')).to.eql({ + user: 'Homer', + lastLogin: now.toISOString() + }); + + const events = await this.Event.findAll({ + where: { + json: { + lastLogin: { [Op.between]: [before, after] } } - }).then(events => { - const event = events[0]; - - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - lastLogin: now.toISOString() - }); - }); + } + }); + + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('json')).to.eql({ + user: 'Homer', + lastLogin: now.toISOString() }); }); - it('should be possible to query a boolean with array operators', function() { - return Promise.all([this.Event.create({ + it('should be possible to query a boolean with array operators', async function() { + await Promise.all([this.Event.create({ json: { user: 'Homer', active: true } - })]).then(() => { - return this.Event.findAll({ - where: { - json: { - active: true - } - } - }).then(events => { - const event = events[0]; + })]); - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', + const events0 = await this.Event.findAll({ + where: { + json: { active: true - }); - }); - }).then(() => { - return this.Event.findAll({ - where: { - json: { - active: { [Op.in]: [true, false] } - } } - }).then(events => { - const event = events[0]; + } + }); - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - active: true - }); - }); + const event0 = events0[0]; + + expect(events0.length).to.equal(1); + expect(event0.get('json')).to.eql({ + user: 'Homer', + active: true + }); + + const events = await this.Event.findAll({ + where: { + json: { + active: { [Op.in]: [true, false] } + } + } + }); + + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('json')).to.eql({ + user: 'Homer', + active: true }); }); - it('should be possible to query a nested integer value', function() { - return Promise.all([this.Event.create({ + it('should be possible to query a nested integer value', async function() { + await Promise.all([this.Event.create({ data: { name: { first: 'Homer', @@ -349,32 +351,32 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, age: 37 } - })]).then(() => { - return this.Event.findAll({ - where: { - data: { - age: { - [Op.gt]: 38 - } + })]); + + const events = await this.Event.findAll({ + where: { + data: { + age: { + [Op.gt]: 38 } } - }).then(events => { - const event = events[0]; + } + }); - expect(events.length).to.equal(1); - expect(event.get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - age: 40 - }); - }); + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + age: 40 }); }); - it('should be possible to query a nested null value', function() { - return Promise.all([this.Event.create({ + it('should be possible to query a nested null value', async function() { + await Promise.all([this.Event.create({ data: { name: { first: 'Homer', @@ -390,28 +392,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: null } - })]).then(() => { - return this.Event.findAll({ - where: { - data: { - employment: null - } - } - }).then(events => { - expect(events.length).to.equal(1); - expect(events[0].get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, + })]); + + const events = await this.Event.findAll({ + where: { + data: { employment: null - }); - }); + } + } + }); + + expect(events.length).to.equal(1); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: null }); }); - it('should be possible to query for nested fields with hyphens/dashes, #8718', function() { - return Promise.all([this.Event.create({ + it('should be possible to query for nested fields with hyphens/dashes, #8718', async function() { + await Promise.all([this.Event.create({ data: { name: { first: 'Homer', @@ -432,37 +434,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: null } - })]).then(() => { - return this.Event.findAll({ - where: { - data: { - status_report: { - 'red-indicator': { - 'level$$level': true - } - } - } - } - }).then(events => { - expect(events.length).to.equal(1); - expect(events[0].get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, + })]); + + const events = await this.Event.findAll({ + where: { + data: { status_report: { 'red-indicator': { 'level$$level': true } - }, - employment: 'Nuclear Safety Inspector' - }); - }); + } + } + } + }); + + expect(events.length).to.equal(1); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + status_report: { + 'red-indicator': { + 'level$$level': true + } + }, + employment: 'Nuclear Safety Inspector' }); }); - it('should be possible to query multiple nested values', function() { - return this.Event.create({ + it('should be possible to query multiple nested values', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -470,63 +472,63 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return Promise.all([this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), this.Event.create({ + }); + + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); + + const events = await this.Event.findAll({ + where: { data: { name: { - first: 'Bart', last: 'Simpson' }, - employment: 'None' - } - })]); - }).then(() => { - return this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - }, - employment: { - [Op.ne]: 'None' - } + employment: { + [Op.ne]: 'None' } - }, - order: [ - ['id', 'ASC'] - ] - }).then(events => { - expect(events.length).to.equal(2); + } + }, + order: [ + ['id', 'ASC'] + ] + }); - expect(events[0].get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); + expect(events.length).to.equal(2); - expect(events[1].get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - }); - }); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + }); + + expect(events[1].get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' }); }); - it('should be possible to query a nested value and order results', function() { - return this.Event.create({ + it('should be possible to query a nested value and order results', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -534,69 +536,69 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return Promise.all([this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), this.Event.create({ + }); + + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); + + const events = await this.Event.findAll({ + where: { data: { name: { - first: 'Bart', last: 'Simpson' - }, - employment: 'None' - } - })]); - }).then(() => { - return this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } } - }, - order: [ - ['data.name.first'] - ] - }).then(events => { - expect(events.length).to.equal(3); + } + }, + order: [ + ['data.name.first'] + ] + }); - expect(events[0].get('data')).to.eql({ - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - }); + expect(events.length).to.equal(3); - expect(events[1].get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + }); - expect(events[2].get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - }); - }); + expect(events[1].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + }); + + expect(events[2].get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' }); }); }); describe('destroy', () => { - it('should be possible to destroy with where', function() { + it('should be possible to destroy with where', async function() { const conditionSearch = { where: { data: { @@ -605,7 +607,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return Promise.all([this.Event.create({ + await Promise.all([this.Event.create({ data: { name: { first: 'Elliot', @@ -629,26 +631,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'CTO' } - })]).then(() => { - return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); - }).then(() => { - return this.Event.destroy(conditionSearch); - }).then(() => { - return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0); - }); + })]); + + await expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); + await this.Event.destroy(conditionSearch); + + await expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0); }); }); describe('sql injection attacks', () => { - beforeEach(function() { + beforeEach(async function() { this.Model = this.sequelize.define('Model', { data: DataTypes.JSON }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should properly escape the single quotes', function() { - return this.Model.create({ + it('should properly escape the single quotes', async function() { + await this.Model.create({ data: { type: 'Point', properties: { @@ -658,8 +659,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should properly escape path keys', function() { - return this.Model.findAll({ + it('should properly escape path keys', async function() { + await this.Model.findAll({ raw: true, attributes: ['id'], where: { @@ -670,16 +671,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should properly escape path keys with sequelize.json', function() { - return this.Model.findAll({ + it('should properly escape path keys with sequelize.json', async function() { + await this.Model.findAll({ raw: true, attributes: ['id'], where: this.sequelize.json("data.id')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ", '1') }); }); - it('should properly escape the single quotes in array', function() { - return this.Model.create({ + it('should properly escape the single quotes in array', async function() { + await this.Model.create({ data: { type: 'Point', coordinates: [39.807222, "'); DELETE YOLO INJECTIONS; --"] @@ -687,37 +688,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should be possible to find with properly escaped select query', function() { - return this.Model.create({ + it('should be possible to find with properly escaped select query', async function() { + await this.Model.create({ data: { type: 'Point', properties: { exploit: "'); DELETE YOLO INJECTIONS; -- " } } - }).then(() => { - return this.Model.findOne({ - where: { - data: { - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - } + }); + + const result = await this.Model.findOne({ + where: { + data: { + type: 'Point', + properties: { + exploit: "'); DELETE YOLO INJECTIONS; -- " } } - }); - }).then(result => { - expect(result.get('data')).to.deep.equal({ - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - } - }); + } + }); + + expect(result.get('data')).to.deep.equal({ + type: 'Point', + properties: { + exploit: "'); DELETE YOLO INJECTIONS; -- " + } }); }); - it('should query an instance with JSONB data and order while trying to inject', function() { - return this.Event.create({ + it('should query an instance with JSONB data and order while trying to inject', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -725,63 +726,64 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return Promise.all([this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - })]); - }).then(() => { - if (current.options.dialect === 'sqlite') { - return this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } + }); + + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); + + if (current.options.dialect === 'sqlite') { + const events = await this.Event.findAll({ + where: { + data: { + name: { + last: 'Simpson' } - }, - order: [ - ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] - ] - }).then(events => { - expect(events).to.be.ok; - expect(events[0].get('data')).to.eql({ + } + }, + order: [ + ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] + ] + }); + + expect(events).to.be.ok; + expect(events[0].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + }); + return; + } + if (current.options.dialect === 'postgres') { + await expect(this.Event.findAll({ + where: { + data: { name: { - first: 'Homer', last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); - }); - } - if (current.options.dialect === 'postgres') { - return expect(this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } } - }, - order: [ - ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] - ] - })).to.eventually.be.rejectedWith(Error); - } - }); + } + }, + order: [ + ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] + ] + })).to.eventually.be.rejectedWith(Error); + } }); }); }); diff --git a/test/integration/model/optimistic_locking.test.js b/test/integration/model/optimistic_locking.test.js index ad5d9eb49009..b58282bfc9b3 100644 --- a/test/integration/model/optimistic_locking.test.js +++ b/test/integration/model/optimistic_locking.test.js @@ -8,7 +8,7 @@ const expect = chai.expect; describe(Support.getTestDialectTeaser('Model'), () => { describe('optimistic locking', () => { let Account; - beforeEach(function() { + beforeEach(async function() { Account = this.sequelize.define('Account', { number: { type: DataTypes.INTEGER @@ -16,65 +16,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, { version: true }); - return Account.sync({ force: true }); + await Account.sync({ force: true }); }); - it('should increment the version on save', () => { - return Account.create({ number: 1 }).then(account => { - account.number += 1; - expect(account.version).to.eq(0); - return account.save(); - }).then(account => { - expect(account.version).to.eq(1); - }); + it('should increment the version on save', async () => { + const account0 = await Account.create({ number: 1 }); + account0.number += 1; + expect(account0.version).to.eq(0); + const account = await account0.save(); + expect(account.version).to.eq(1); }); - it('should increment the version on update', () => { - return Account.create({ number: 1 }).then(account => { - expect(account.version).to.eq(0); - return account.update({ number: 2 }); - }).then(account => { - expect(account.version).to.eq(1); - account.number += 1; - return account.save(); - }).then(account => { - expect(account.number).to.eq(3); - expect(account.version).to.eq(2); - }); + it('should increment the version on update', async () => { + const account1 = await Account.create({ number: 1 }); + expect(account1.version).to.eq(0); + const account0 = await account1.update({ number: 2 }); + expect(account0.version).to.eq(1); + account0.number += 1; + const account = await account0.save(); + expect(account.number).to.eq(3); + expect(account.version).to.eq(2); }); - it('prevents stale instances from being saved', () => { - return expect(Account.create({ number: 1 }).then(accountA => { - return Account.findByPk(accountA.id).then(accountB => { - accountA.number += 1; - return accountA.save().then(() => { return accountB; }); - }); - }).then(accountB => { + it('prevents stale instances from being saved', async () => { + await expect((async () => { + const accountA = await Account.create({ number: 1 }); + const accountB0 = await Account.findByPk(accountA.id); + accountA.number += 1; + await accountA.save(); + const accountB = await accountB0; accountB.number += 1; - return accountB.save(); - })).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError); + return await accountB.save(); + })()).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError); }); - it('increment() also increments the version', () => { - return Account.create({ number: 1 }).then(account => { - expect(account.version).to.eq(0); - return account.increment('number', { by: 1 } ); - }).then(account => { - return account.reload(); - }).then(account => { - expect(account.version).to.eq(1); - }); + it('increment() also increments the version', async () => { + const account1 = await Account.create({ number: 1 }); + expect(account1.version).to.eq(0); + const account0 = await account1.increment('number', { by: 1 } ); + const account = await account0.reload(); + expect(account.version).to.eq(1); }); - it('decrement() also increments the version', () => { - return Account.create({ number: 1 }).then(account => { - expect(account.version).to.eq(0); - return account.decrement('number', { by: 1 } ); - }).then(account => { - return account.reload(); - }).then(account => { - expect(account.version).to.eq(1); - }); + it('decrement() also increments the version', async () => { + const account1 = await Account.create({ number: 1 }); + expect(account1.version).to.eq(0); + const account0 = await account1.decrement('number', { by: 1 } ); + const account = await account0.reload(); + expect(account.version).to.eq(1); }); }); }); diff --git a/test/integration/model/paranoid.test.js b/test/integration/model/paranoid.test.js index 81836bdd716e..9033ad795d52 100644 --- a/test/integration/model/paranoid.test.js +++ b/test/integration/model/paranoid.test.js @@ -17,7 +17,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.restore(); }); - it('should be able to soft delete with timestamps', function() { + it('should be able to soft delete with timestamps', async function() { const Account = this.sequelize.define('Account', { ownerId: { type: DataTypes.INTEGER, @@ -32,32 +32,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { timestamps: true }); - return Account.sync({ force: true }) - .then(() => Account.create({ ownerId: 12 })) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - return Account.destroy({ where: { ownerId: 12 } }) - .then(result => { - expect(result).to.be.equal(1); - }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(0); - return Account.count({ paranoid: false }); - }) - .then(count => { - expect(count).to.be.equal(1); - return Account.restore({ where: { ownerId: 12 } }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - }); + await Account.sync({ force: true }); + await Account.create({ ownerId: 12 }); + const count2 = await Account.count(); + expect(count2).to.be.equal(1); + const result = await Account.destroy({ where: { ownerId: 12 } }); + expect(result).to.be.equal(1); + const count1 = await Account.count(); + expect(count1).to.be.equal(0); + const count0 = await Account.count({ paranoid: false }); + expect(count0).to.be.equal(1); + await Account.restore({ where: { ownerId: 12 } }); + const count = await Account.count(); + expect(count).to.be.equal(1); }); - it('should be able to soft delete without timestamps', function() { + it('should be able to soft delete without timestamps', async function() { const Account = this.sequelize.define('Account', { ownerId: { type: DataTypes.INTEGER, @@ -80,26 +70,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { updatedAt: false }); - return Account.sync({ force: true }) - .then(() => Account.create({ ownerId: 12 })) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - return Account.destroy({ where: { ownerId: 12 } }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(0); - return Account.count({ paranoid: false }); - }) - .then(count => { - expect(count).to.be.equal(1); - return Account.restore({ where: { ownerId: 12 } }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - }); + await Account.sync({ force: true }); + await Account.create({ ownerId: 12 }); + const count2 = await Account.count(); + expect(count2).to.be.equal(1); + await Account.destroy({ where: { ownerId: 12 } }); + const count1 = await Account.count(); + expect(count1).to.be.equal(0); + const count0 = await Account.count({ paranoid: false }); + expect(count0).to.be.equal(1); + await Account.restore({ where: { ownerId: 12 } }); + const count = await Account.count(); + expect(count).to.be.equal(1); }); if (current.dialect.supports.JSON) { @@ -124,12 +106,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - beforeEach(function() { - return this.Model.sync({ force: true }); + beforeEach(async function() { + await this.Model.sync({ force: true }); }); - it('should soft delete with JSON condition', function() { - return this.Model.bulkCreate([{ + it('should soft delete with JSON condition', async function() { + await this.Model.bulkCreate([{ name: 'One', data: { field: { @@ -143,7 +125,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { deep: false } } - }]).then(() => this.Model.destroy({ + }]); + + await this.Model.destroy({ where: { data: { field: { @@ -151,10 +135,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } } - })).then(() => this.Model.findAll()).then(records => { - expect(records.length).to.equal(1); - expect(records[0].get('name')).to.equal('Two'); }); + + const records = await this.Model.findAll(); + expect(records.length).to.equal(1); + expect(records[0].get('name')).to.equal('Two'); }); }); } diff --git a/test/integration/model/schema.test.js b/test/integration/model/schema.test.js index 80e6577166b0..376ea9758bde 100644 --- a/test/integration/model/schema.test.js +++ b/test/integration/model/schema.test.js @@ -47,18 +47,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { current.options.schema = null; }); - beforeEach('build restaurant tables', function() { - return current.createSchema(SCHEMA_TWO) - .then(() => { - return Promise.all([ - this.RestaurantOne.sync({ force: true }), - this.RestaurantTwo.sync({ force: true }) - ]); - }); + beforeEach('build restaurant tables', async function() { + await current.createSchema(SCHEMA_TWO); + + await Promise.all([ + this.RestaurantOne.sync({ force: true }), + this.RestaurantTwo.sync({ force: true }) + ]); }); - afterEach('drop schemas', () => { - return current.dropSchema(SCHEMA_TWO); + afterEach('drop schemas', async () => { + await current.dropSchema(SCHEMA_TWO); }); describe('Add data via model.create, retrieve via model.findOne', () => { @@ -67,81 +66,79 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(this.RestaurantTwo._schema).to.equal(SCHEMA_TWO); }); - it('should be able to insert data into default table using create', function() { - return this.RestaurantOne.create({ + it('should be able to insert data into default table using create', async function() { + await this.RestaurantOne.create({ foo: 'one' - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - return this.RestaurantTwo.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.be.null; }); + + const obj0 = await this.RestaurantOne.findOne({ + where: { foo: 'one' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('one'); + + const obj = await this.RestaurantTwo.findOne({ + where: { foo: 'one' } + }); + + expect(obj).to.be.null; }); - it('should be able to insert data into schema table using create', function() { - return this.RestaurantTwo.create({ + it('should be able to insert data into schema table using create', async function() { + await this.RestaurantTwo.create({ foo: 'two' - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - return this.RestaurantOne.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.be.null; }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + + const obj = await this.RestaurantOne.findOne({ + where: { foo: 'two' } + }); + + expect(obj).to.be.null; }); }); describe('Get associated data in public schema via include', () => { - beforeEach(function() { - return Promise.all([ + beforeEach(async function() { + await Promise.all([ this.LocationOne.sync({ force: true }), this.LocationTwo.sync({ force: true }) - ]).then(() => { - return this.LocationTwo.create({ name: 'HQ' }); - }).then(() => { - return this.LocationTwo.findOne({ where: { name: 'HQ' } }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.name).to.equal('HQ'); - locationId = obj.id; - return this.LocationOne.findOne({ where: { name: 'HQ' } }); - }).then(obj => { - expect(obj).to.be.null; - }); + ]); + + await this.LocationTwo.create({ name: 'HQ' }); + const obj0 = await this.LocationTwo.findOne({ where: { name: 'HQ' } }); + expect(obj0).to.not.be.null; + expect(obj0.name).to.equal('HQ'); + locationId = obj0.id; + const obj = await this.LocationOne.findOne({ where: { name: 'HQ' } }); + expect(obj).to.be.null; }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { - return this.RestaurantTwo.create({ + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { + await this.RestaurantTwo.create({ foo: 'two', location_id: locationId - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' }, include: [{ - model: this.LocationTwo, as: 'location' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); - return this.RestaurantOne.findOne({ where: { foo: 'two' } }); - }).then(obj => { - expect(obj).to.be.null; }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' }, include: [{ + model: this.LocationTwo, as: 'location' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + expect(obj0.location).to.not.be.null; + expect(obj0.location.name).to.equal('HQ'); + const obj = await this.RestaurantOne.findOne({ where: { foo: 'two' } }); + expect(obj).to.be.null; }); }); }); @@ -182,337 +179,307 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); - beforeEach('build restaurant tables', function() { - return Promise.all([ + beforeEach('build restaurant tables', async function() { + await Promise.all([ current.createSchema(SCHEMA_ONE), current.createSchema(SCHEMA_TWO) - ]).then(() => { - return Promise.all([ - this.RestaurantOne.sync({ force: true }), - this.RestaurantTwo.sync({ force: true }) - ]); - }); + ]); + + await Promise.all([ + this.RestaurantOne.sync({ force: true }), + this.RestaurantTwo.sync({ force: true }) + ]); }); - afterEach('drop schemas', () => { - return Promise.all([ + afterEach('drop schemas', async () => { + await Promise.all([ current.dropSchema(SCHEMA_ONE), current.dropSchema(SCHEMA_TWO) ]); }); describe('Add data via model.create, retrieve via model.findOne', () => { - it('should be able to insert data into the table in schema_one using create', function() { - let restaurantId; - - return this.RestaurantOne.create({ + it('should be able to insert data into the table in schema_one using create', async function() { + await this.RestaurantOne.create({ foo: 'one', location_id: locationId - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return this.RestaurantOne.findByPk(restaurantId); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - return this.RestaurantTwo.findOne({ where: { foo: 'one' } }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); }); - }); - it('should be able to insert data into the table in schema_two using create', function() { - let restaurantId; + const obj0 = await this.RestaurantOne.findOne({ + where: { foo: 'one' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('one'); + const restaurantId = obj0.id; + const obj = await this.RestaurantOne.findByPk(restaurantId); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); + const RestaurantObj = await this.RestaurantTwo.findOne({ where: { foo: 'one' } }); + expect(RestaurantObj).to.be.null; + }); - return this.RestaurantTwo.create({ + it('should be able to insert data into the table in schema_two using create', async function() { + await this.RestaurantTwo.create({ foo: 'two', location_id: locationId - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return this.RestaurantTwo.findByPk(restaurantId); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - return this.RestaurantOne.findOne({ where: { foo: 'two' } }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + const restaurantId = obj0.id; + const obj = await this.RestaurantTwo.findByPk(restaurantId); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('two'); + const RestaurantObj = await this.RestaurantOne.findOne({ where: { foo: 'two' } }); + expect(RestaurantObj).to.be.null; }); }); describe('Persist and retrieve data', () => { - it('should be able to insert data into both schemas using instance.save and retrieve/count it', function() { + it('should be able to insert data into both schemas using instance.save and retrieve/count it', async function() { //building and saving in random order to make sure calling // .schema doesn't impact model prototype let restaurauntModel = this.RestaurantOne.build({ bar: 'one.1' }); - return restaurauntModel.save() - .then(() => { - restaurauntModel = this.RestaurantTwo.build({ bar: 'two.1' }); - return restaurauntModel.save(); - }).then(() => { - restaurauntModel = this.RestaurantOne.build({ bar: 'one.2' }); - return restaurauntModel.save(); - }).then(() => { - restaurauntModel = this.RestaurantTwo.build({ bar: 'two.2' }); - return restaurauntModel.save(); - }).then(() => { - restaurauntModel = this.RestaurantTwo.build({ bar: 'two.3' }); - return restaurauntModel.save(); - }).then(() => { - return this.RestaurantOne.findAll(); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return this.RestaurantOne.findAndCountAll(); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.rows.length).to.equal(2); - expect(restaurantsOne.count).to.equal(2); - restaurantsOne.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return this.RestaurantOne.findAll({ - where: { bar: { [Op.like]: '%.1' } } - }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(1); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return this.RestaurantOne.count(); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(2); - return this.RestaurantTwo.findAll(); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return this.RestaurantTwo.findAndCountAll(); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.rows.length).to.equal(3); - expect(restaurantsTwo.count).to.equal(3); - restaurantsTwo.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return this.RestaurantTwo.findAll({ - where: { bar: { [Op.like]: '%.3' } } - }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return this.RestaurantTwo.count(); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(3); - }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantTwo.build({ bar: 'two.1' }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantOne.build({ bar: 'one.2' }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantTwo.build({ bar: 'two.2' }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantTwo.build({ bar: 'two.3' }); + await restaurauntModel.save(); + const restaurantsOne1 = await this.RestaurantOne.findAll(); + expect(restaurantsOne1).to.not.be.null; + expect(restaurantsOne1.length).to.equal(2); + restaurantsOne1.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsOne0 = await this.RestaurantOne.findAndCountAll(); + expect(restaurantsOne0).to.not.be.null; + expect(restaurantsOne0.rows.length).to.equal(2); + expect(restaurantsOne0.count).to.equal(2); + restaurantsOne0.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + + const restaurantsOne = await this.RestaurantOne.findAll({ + where: { bar: { [Op.like]: '%.1' } } + }); + + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(1); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const count0 = await this.RestaurantOne.count(); + expect(count0).to.not.be.null; + expect(count0).to.equal(2); + const restaurantsTwo1 = await this.RestaurantTwo.findAll(); + expect(restaurantsTwo1).to.not.be.null; + expect(restaurantsTwo1.length).to.equal(3); + restaurantsTwo1.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + const restaurantsTwo0 = await this.RestaurantTwo.findAndCountAll(); + expect(restaurantsTwo0).to.not.be.null; + expect(restaurantsTwo0.rows.length).to.equal(3); + expect(restaurantsTwo0.count).to.equal(3); + restaurantsTwo0.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + + const restaurantsTwo = await this.RestaurantTwo.findAll({ + where: { bar: { [Op.like]: '%.3' } } + }); + + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(1); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + const count = await this.RestaurantTwo.count(); + expect(count).to.not.be.null; + expect(count).to.equal(3); }); }); describe('Get associated data in public schema via include', () => { - beforeEach(function() { + beforeEach(async function() { const Location = this.Location; - return Location.sync({ force: true }) - .then(() => { - return Location.create({ name: 'HQ' }).then(() => { - return Location.findOne({ where: { name: 'HQ' } }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.name).to.equal('HQ'); - locationId = obj.id; - }); - }); - }) - .catch(err => { - expect(err).to.be.null; - }); + try { + await Location.sync({ force: true }); + await Location.create({ name: 'HQ' }); + const obj = await Location.findOne({ where: { name: 'HQ' } }); + expect(obj).to.not.be.null; + expect(obj.name).to.equal('HQ'); + locationId = obj.id; + } catch (err) { + expect(err).to.be.null; + } }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { - return this.RestaurantOne.create({ + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { + await this.RestaurantOne.create({ foo: 'one', location_id: locationId - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' }, include: [{ - model: this.Location, as: 'location' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); }); + + const obj = await this.RestaurantOne.findOne({ + where: { foo: 'one' }, include: [{ + model: this.Location, as: 'location' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); + expect(obj.location).to.not.be.null; + expect(obj.location.name).to.equal('HQ'); }); }); describe('Get schema specific associated data via include', () => { - beforeEach(function() { + beforeEach(async function() { const Employee = this.Employee; - return Promise.all([ + + await Promise.all([ Employee.schema(SCHEMA_ONE).sync({ force: true }), Employee.schema(SCHEMA_TWO).sync({ force: true }) ]); }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { - let restaurantId; - - return this.RestaurantOne.create({ + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { + await this.RestaurantOne.create({ foo: 'one' - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return this.EmployeeOne.create({ - first_name: 'Restaurant', - last_name: 'one', - restaurant_id: restaurantId - }); - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' }, include: [{ - model: this.EmployeeOne, as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('one'); - return obj.getEmployees({ schema: SCHEMA_ONE }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('one'); - return this.EmployeeOne.findOne({ - where: { last_name: 'one' }, include: [{ - model: this.RestaurantOne, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('one'); - return obj.getRestaurant({ schema: SCHEMA_ONE }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('one'); }); - }); + const obj1 = await this.RestaurantOne.findOne({ + where: { foo: 'one' } + }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('one'); + const restaurantId = obj1.id; + + await this.EmployeeOne.create({ + first_name: 'Restaurant', + last_name: 'one', + restaurant_id: restaurantId + }); + + const obj0 = await this.RestaurantOne.findOne({ + where: { foo: 'one' }, include: [{ + model: this.EmployeeOne, as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('one'); + const employees = await obj0.getEmployees({ schema: SCHEMA_ONE }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('one'); + + const obj = await this.EmployeeOne.findOne({ + where: { last_name: 'one' }, include: [{ + model: this.RestaurantOne, as: 'restaurant' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('one'); + const restaurant = await obj.getRestaurant({ schema: SCHEMA_ONE }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('one'); + }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { - let restaurantId; - return this.RestaurantTwo.create({ + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { + await this.RestaurantTwo.create({ foo: 'two' - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return this.Employee.schema(SCHEMA_TWO).create({ - first_name: 'Restaurant', - last_name: 'two', - restaurant_id: restaurantId - }); - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' }, include: [{ - model: this.Employee.schema(SCHEMA_TWO), as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('two'); - return obj.getEmployees({ schema: SCHEMA_TWO }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('two'); - return this.Employee.schema(SCHEMA_TWO).findOne({ - where: { last_name: 'two' }, include: [{ - model: this.RestaurantTwo, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('two'); - return obj.getRestaurant({ schema: SCHEMA_TWO }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('two'); }); + + const obj1 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' } + }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('two'); + const restaurantId = obj1.id; + + await this.Employee.schema(SCHEMA_TWO).create({ + first_name: 'Restaurant', + last_name: 'two', + restaurant_id: restaurantId + }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' }, include: [{ + model: this.Employee.schema(SCHEMA_TWO), as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('two'); + const employees = await obj0.getEmployees({ schema: SCHEMA_TWO }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('two'); + + const obj = await this.Employee.schema(SCHEMA_TWO).findOne({ + where: { last_name: 'two' }, include: [{ + model: this.RestaurantTwo, as: 'restaurant' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('two'); + const restaurant = await obj.getRestaurant({ schema: SCHEMA_TWO }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('two'); }); }); describe('concurency tests', () => { - it('should build and persist instances to 2 schemas concurrently in any order', function() { + it('should build and persist instances to 2 schemas concurrently in any order', async function() { const Restaurant = this.Restaurant; let restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.1' }); const restaurauntModelSchema2 = Restaurant.schema(SCHEMA_TWO).build({ bar: 'two.1' }); - return restaurauntModelSchema1.save() - .then(() => { - restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.2' }); - return restaurauntModelSchema2.save(); - }).then(() => { - return restaurauntModelSchema1.save(); - }).then(() => { - return Restaurant.schema(SCHEMA_ONE).findAll(); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.schema(SCHEMA_TWO).findAll(); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); + await restaurauntModelSchema1.save(); + restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.2' }); + await restaurauntModelSchema2.save(); + await restaurauntModelSchema1.save(); + const restaurantsOne = await Restaurant.schema(SCHEMA_ONE).findAll(); + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(2); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsTwo = await Restaurant.schema(SCHEMA_TWO).findAll(); + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(1); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); }); }); describe('regressions', () => { - it('should be able to sync model with schema', function() { + it('should be able to sync model with schema', async function() { const User = this.sequelize.define('User1', { name: DataTypes.STRING, value: DataTypes.INTEGER @@ -539,23 +506,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return Promise.all([ - this.sequelize.queryInterface.describeTable(User.tableName, SCHEMA_ONE), - this.sequelize.queryInterface.describeTable(Task.tableName, SCHEMA_TWO) - ]); - }).then(([user, task]) => { - expect(user).to.be.ok; - expect(task).to.be.ok; - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + + const [user, task] = await Promise.all([ + this.sequelize.queryInterface.describeTable(User.tableName, SCHEMA_ONE), + this.sequelize.queryInterface.describeTable(Task.tableName, SCHEMA_TWO) + ]); + + expect(user).to.be.ok; + expect(task).to.be.ok; }); // TODO: this should work with MSSQL / MariaDB too // Need to fix addSchema return type if (dialect.match(/^postgres/)) { - it('defaults to schema provided to sync() for references #11276', function() { + it('defaults to schema provided to sync() for references #11276', async function() { const User = this.sequelize.define('UserXYZ', { uid: { type: DataTypes.INTEGER, @@ -569,19 +535,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { Task.belongsTo(User); - return User.sync({ force: true, schema: SCHEMA_ONE }).then(() => { - return Task.sync({ force: true, schema: SCHEMA_ONE }); - }).then(() => { - return User.schema(SCHEMA_ONE).create({}); - }).then(user => { - return Task.schema(SCHEMA_ONE).create({}).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ({ schema: SCHEMA_ONE }); - }); - }); - }).then(user => { - expect(user).to.be.ok; - }); + await User.sync({ force: true, schema: SCHEMA_ONE }); + await Task.sync({ force: true, schema: SCHEMA_ONE }); + const user0 = await User.schema(SCHEMA_ONE).create({}); + const task = await Task.schema(SCHEMA_ONE).create({}); + await task.setUserXYZ(user0); + const user = await task.getUserXYZ({ schema: SCHEMA_ONE }); + expect(user).to.be.ok; }); } }); diff --git a/test/integration/model/scope.test.js b/test/integration/model/scope.test.js index fb4f31428985..aa2db393ffb8 100644 --- a/test/integration/model/scope.test.js +++ b/test/integration/model/scope.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -57,66 +57,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should be able to merge attributes as array', function() { - return this.ScopeMe.scope('lowAccess', 'withName').findOne() - .then(record => { - expect(record.other_value).to.exist; - expect(record.username).to.exist; - expect(record.access_level).to.exist; - }); + it('should be able to merge attributes as array', async function() { + const record = await this.ScopeMe.scope('lowAccess', 'withName').findOne(); + expect(record.other_value).to.exist; + expect(record.username).to.exist; + expect(record.access_level).to.exist; }); - it('should work with Symbol operators', function() { - return this.ScopeMe.scope('highAccess').findOne() - .then(record => { - expect(record.username).to.equal('tobi'); - return this.ScopeMe.scope('lessThanFour').findAll(); - }) - .then(records => { - expect(records).to.have.length(2); - expect(records[0].get('access_level')).to.equal(3); - expect(records[1].get('access_level')).to.equal(3); - return this.ScopeMe.scope('issue8473').findAll(); - }) - .then(records => { - expect(records).to.have.length(1); - expect(records[0].get('access_level')).to.equal(5); - expect(records[0].get('other_value')).to.equal(10); - }); + it('should work with Symbol operators', async function() { + const record = await this.ScopeMe.scope('highAccess').findOne(); + expect(record.username).to.equal('tobi'); + const records0 = await this.ScopeMe.scope('lessThanFour').findAll(); + expect(records0).to.have.length(2); + expect(records0[0].get('access_level')).to.equal(3); + expect(records0[1].get('access_level')).to.equal(3); + const records = await this.ScopeMe.scope('issue8473').findAll(); + expect(records).to.have.length(1); + expect(records[0].get('access_level')).to.equal(5); + expect(records[0].get('other_value')).to.equal(10); }); - it('should keep symbols after default assignment', function() { - return this.ScopeMe.scope('highAccess').findOne() - .then(record => { - expect(record.username).to.equal('tobi'); - return this.ScopeMe.scope('lessThanFour').findAll({ - where: {} - }); - }) - .then(records => { - expect(records).to.have.length(2); - expect(records[0].get('access_level')).to.equal(3); - expect(records[1].get('access_level')).to.equal(3); - return this.ScopeMe.scope('issue8473').findAll(); - }); + it('should keep symbols after default assignment', async function() { + const record = await this.ScopeMe.scope('highAccess').findOne(); + expect(record.username).to.equal('tobi'); + + const records = await this.ScopeMe.scope('lessThanFour').findAll({ + where: {} + }); + + expect(records).to.have.length(2); + expect(records[0].get('access_level')).to.equal(3); + expect(records[1].get('access_level')).to.equal(3); + await this.ScopeMe.scope('issue8473').findAll(); }); - it('should not throw error with sequelize.where', function() { - return this.ScopeMe.scope('like_t').findAll() - .then(records => { - expect(records).to.have.length(2); - }); + it('should not throw error with sequelize.where', async function() { + const records = await this.ScopeMe.scope('like_t').findAll(); + expect(records).to.have.length(2); }); }); }); diff --git a/test/integration/model/scope/aggregate.test.js b/test/integration/model/scope/aggregate.test.js index 97fb1146ae88..620fea0b650b 100644 --- a/test/integration/model/scope/aggregate.test.js +++ b/test/integration/model/scope/aggregate.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('aggregate', () => { - beforeEach(function() { + beforeEach(async function() { this.Child = this.sequelize.define('Child', { priority: Sequelize.INTEGER }); @@ -50,50 +50,48 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Child.belongsTo(this.ScopeMe); this.ScopeMe.hasMany(this.Child); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }).then(() => { - return this.ScopeMe.findAll(); - }).then(records => { - return Promise.all([ - records[0].createChild({ - priority: 1 - }), - records[1].createChild({ - priority: 2 - }) - ]); - }); + await this.sequelize.sync({ force: true }); + const records0 = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + await this.ScopeMe.bulkCreate(records0); + const records = await this.ScopeMe.findAll(); + + await Promise.all([ + records[0].createChild({ + priority: 1 + }), + records[1].createChild({ + priority: 2 + }) + ]); }); - it('should apply defaultScope', function() { - return expect(this.ScopeMe.aggregate( '*', 'count' )).to.eventually.equal(2); + it('should apply defaultScope', async function() { + await expect(this.ScopeMe.aggregate( '*', 'count' )).to.eventually.equal(2); }); - it('should be able to override default scope', function() { - return expect(this.ScopeMe.aggregate( '*', 'count', { where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); + it('should be able to override default scope', async function() { + await expect(this.ScopeMe.aggregate( '*', 'count', { where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); }); - it('should be able to unscope', function() { - return expect(this.ScopeMe.unscoped().aggregate( '*', 'count' )).to.eventually.equal(4); + it('should be able to unscope', async function() { + await expect(this.ScopeMe.unscoped().aggregate( '*', 'count' )).to.eventually.equal(4); }); - it('should be able to apply other scopes', function() { - return expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count' )).to.eventually.equal(3); + it('should be able to apply other scopes', async function() { + await expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count' )).to.eventually.equal(3); }); - it('should be able to merge scopes with where', function() { - return expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count', { where: { username: 'dan' } })).to.eventually.equal(1); + it('should be able to merge scopes with where', async function() { + await expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count', { where: { username: 'dan' } })).to.eventually.equal(1); }); - it('should be able to use where on include', function() { - return expect(this.ScopeMe.scope('withInclude').aggregate( 'ScopeMe.id', 'count', { + it('should be able to use where on include', async function() { + await expect(this.ScopeMe.scope('withInclude').aggregate( 'ScopeMe.id', 'count', { plain: true, dataType: new Sequelize.INTEGER(), includeIgnoreAttributes: false, @@ -105,27 +103,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); if (Support.sequelize.dialect.supports.schemas) { - it('aggregate with schema', function() { + it('aggregate with schema', async function() { this.Hero = this.sequelize.define('Hero', { codename: Sequelize.STRING }, { schema: 'heroschema' }); - return this.sequelize.createSchema('heroschema') - .then(() => { - return this.sequelize.sync({ force: true }); - }) - .then(() => { - const records = [ - { codename: 'hulk' }, - { codename: 'rantanplan' } - ]; - return this.Hero.bulkCreate(records); - }) - .then(() => { - return expect( - this.Hero.unscoped().aggregate('*', 'count', - { schema: 'heroschema' })).to.eventually.equal( - 2); - }); + await this.sequelize.createSchema('heroschema'); + await this.sequelize.sync({ force: true }); + const records = [ + { codename: 'hulk' }, + { codename: 'rantanplan' } + ]; + await this.Hero.bulkCreate(records); + + await expect( + this.Hero.unscoped().aggregate('*', 'count', + { schema: 'heroschema' })).to.eventually.equal(2); }); } }); diff --git a/test/integration/model/scope/associations.test.js b/test/integration/model/scope/associations.test.js index fc32523d73f3..a004489ab0af 100644 --- a/test/integration/model/scope/associations.test.js +++ b/test/integration/model/scope/associations.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('associations', () => { - beforeEach(function() { + beforeEach(async function() { const sequelize = this.sequelize; this.ScopeMe = this.sequelize.define('ScopeMe', { @@ -96,214 +96,195 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.ScopeMe.belongsTo(this.Company); this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.ScopeMe.create({ id: 1, username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }), - this.ScopeMe.create({ id: 2, username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }), - this.ScopeMe.create({ id: 3, username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }), - this.ScopeMe.create({ id: 4, username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 }), - this.ScopeMe.create({ id: 5, username: 'bob', email: 'bob@foobar.com', access_level: 1, other_value: 9, parent_id: 5 }), - this.Company.create({ id: 1, active: true }), - this.Company.create({ id: 2, active: false }) - ]); - }).then(([u1, u2, u3, u4, u5, c1, c2]) => { - return Promise.all([ - c1.setUsers([u1, u2, u3, u4]), - c2.setUsers([u5]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const [u1, u2, u3, u4, u5, c1, c2] = await Promise.all([ + this.ScopeMe.create({ id: 1, username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }), + this.ScopeMe.create({ id: 2, username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }), + this.ScopeMe.create({ id: 3, username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }), + this.ScopeMe.create({ id: 4, username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 }), + this.ScopeMe.create({ id: 5, username: 'bob', email: 'bob@foobar.com', access_level: 1, other_value: 9, parent_id: 5 }), + this.Company.create({ id: 1, active: true }), + this.Company.create({ id: 2, active: false }) + ]); + + await Promise.all([ + c1.setUsers([u1, u2, u3, u4]), + c2.setUsers([u5]) + ]); }); describe('include', () => { - it('should scope columns properly', function() { + it('should scope columns properly', async function() { // Will error with ambigous column if id is not scoped properly to `Company`.`id` - return expect(this.Company.findAll({ + await expect(this.Company.findAll({ where: { id: 1 }, include: [this.UserAssociation] })).not.to.be.rejected; }); - it('should apply default scope when including an associations', function() { - return this.Company.findAll({ + it('should apply default scope when including an associations', async function() { + const obj = await this.Company.findAll({ include: [this.UserAssociation] - }).then(obj => obj[0]).then(company => { - expect(company.users).to.have.length(2); }); + + const company = await obj[0]; + expect(company.users).to.have.length(2); }); - it('should apply default scope when including a model', function() { - return this.Company.findAll({ + it('should apply default scope when including a model', async function() { + const obj = await this.Company.findAll({ include: [{ model: this.ScopeMe, as: 'users' }] - }).then(obj => obj[0]).then(company => { - expect(company.users).to.have.length(2); }); + + const company = await obj[0]; + expect(company.users).to.have.length(2); }); - it('should be able to include a scoped model', function() { - return this.Company.findAll({ + it('should be able to include a scoped model', async function() { + const obj = await this.Company.findAll({ include: [{ model: this.ScopeMe.scope('isTony'), as: 'users' }] - }).then(obj => obj[0]).then(company => { - expect(company.users).to.have.length(1); - expect(company.users[0].get('username')).to.equal('tony'); }); + + const company = await obj[0]; + expect(company.users).to.have.length(1); + expect(company.users[0].get('username')).to.equal('tony'); }); }); describe('get', () => { - beforeEach(function() { - return Promise.all([ + beforeEach(async function() { + const [p, companies] = await Promise.all([ this.Project.create(), this.Company.unscoped().findAll() - ]).then(([p, companies]) => { - return p.setCompanies(companies); - }); + ]); + + await p.setCompanies(companies); }); describe('it should be able to unscope', () => { - it('hasMany', function() { - return this.Company.findByPk(1).then(company => { - return company.getUsers({ scope: false }); - }).then(users => { - expect(users).to.have.length(4); - }); + it('hasMany', async function() { + const company = await this.Company.findByPk(1); + const users = await company.getUsers({ scope: false }); + expect(users).to.have.length(4); }); - it('hasOne', function() { - return this.Profile.create({ + it('hasOne', async function() { + await this.Profile.create({ active: false, userId: 1 - }).then(() => { - return this.ScopeMe.findByPk(1); - }).then(user => { - return user.getProfile({ scope: false }); - }).then(profile => { - expect(profile).to.be.ok; }); + + const user = await this.ScopeMe.findByPk(1); + const profile = await user.getProfile({ scope: false }); + expect(profile).to.be.ok; }); - it('belongsTo', function() { - return this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }).then(user => { - return user.getCompany({ scope: false }); - }).then(company => { - expect(company).to.be.ok; - }); + it('belongsTo', async function() { + const user = await this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }); + const company = await user.getCompany({ scope: false }); + expect(company).to.be.ok; }); - it('belongsToMany', function() { - return this.Project.findAll().then(obj => obj[0]).then(p => { - return p.getCompanies({ scope: false }); - }).then(companies => { - expect(companies).to.have.length(2); - }); + it('belongsToMany', async function() { + const obj = await this.Project.findAll(); + const p = await obj[0]; + const companies = await p.getCompanies({ scope: false }); + expect(companies).to.have.length(2); }); }); describe('it should apply default scope', () => { - it('hasMany', function() { - return this.Company.findByPk(1).then(company => { - return company.getUsers(); - }).then(users => { - expect(users).to.have.length(2); - }); + it('hasMany', async function() { + const company = await this.Company.findByPk(1); + const users = await company.getUsers(); + expect(users).to.have.length(2); }); - it('hasOne', function() { - return this.Profile.create({ + it('hasOne', async function() { + await this.Profile.create({ active: false, userId: 1 - }).then(() => { - return this.ScopeMe.findByPk(1); - }).then(user => { - return user.getProfile(); - }).then(profile => { - expect(profile).not.to.be.ok; }); + + const user = await this.ScopeMe.findByPk(1); + const profile = await user.getProfile(); + expect(profile).not.to.be.ok; }); - it('belongsTo', function() { - return this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }).then(user => { - return user.getCompany(); - }).then(company => { - expect(company).not.to.be.ok; - }); + it('belongsTo', async function() { + const user = await this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }); + const company = await user.getCompany(); + expect(company).not.to.be.ok; }); - it('belongsToMany', function() { - return this.Project.findAll().then(obj => obj[0]).then(p => { - return p.getCompanies(); - }).then(companies => { - expect(companies).to.have.length(1); - expect(companies[0].get('active')).to.be.ok; - }); + it('belongsToMany', async function() { + const obj = await this.Project.findAll(); + const p = await obj[0]; + const companies = await p.getCompanies(); + expect(companies).to.have.length(1); + expect(companies[0].get('active')).to.be.ok; }); }); describe('it should be able to apply another scope', () => { - it('hasMany', function() { - return this.Company.findByPk(1).then(company => { - return company.getUsers({ scope: 'isTony' }); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('tony'); - }); + it('hasMany', async function() { + const company = await this.Company.findByPk(1); + const users = await company.getUsers({ scope: 'isTony' }); + expect(users).to.have.length(1); + expect(users[0].get('username')).to.equal('tony'); }); - it('hasOne', function() { - return this.Profile.create({ + it('hasOne', async function() { + await this.Profile.create({ active: true, userId: 1 - }).then(() => { - return this.ScopeMe.findByPk(1); - }).then(user => { - return user.getProfile({ scope: 'notActive' }); - }).then(profile => { - expect(profile).not.to.be.ok; }); + + const user = await this.ScopeMe.findByPk(1); + const profile = await user.getProfile({ scope: 'notActive' }); + expect(profile).not.to.be.ok; }); - it('belongsTo', function() { - return this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }).then(user => { - return user.getCompany({ scope: 'notActive' }); - }).then(company => { - expect(company).to.be.ok; - }); + it('belongsTo', async function() { + const user = await this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }); + const company = await user.getCompany({ scope: 'notActive' }); + expect(company).to.be.ok; }); - it('belongsToMany', function() { - return this.Project.findAll().then(obj => obj[0]).then(p => { - return p.getCompanies({ scope: 'reversed' }); - }).then(companies => { - expect(companies).to.have.length(2); - expect(companies[0].id).to.equal(2); - expect(companies[1].id).to.equal(1); - }); + it('belongsToMany', async function() { + const obj = await this.Project.findAll(); + const p = await obj[0]; + const companies = await p.getCompanies({ scope: 'reversed' }); + expect(companies).to.have.length(2); + expect(companies[0].id).to.equal(2); + expect(companies[1].id).to.equal(1); }); }); }); describe('scope with includes', () => { - beforeEach(function() { - return Promise.all([ + beforeEach(async function() { + const [c, p1, p2] = await Promise.all([ this.Company.findByPk(1), this.Project.create({ id: 1, active: true }), this.Project.create({ id: 2, active: false }) - ]).then(([c, p1, p2]) => { - return c.setProjects([p1, p2]); - }); + ]); + + await c.setProjects([p1, p2]); }); - it('should scope columns properly', function() { - return expect(this.ScopeMe.scope('includeActiveProjects').findAll()).not.to.be.rejected; + it('should scope columns properly', async function() { + await expect(this.ScopeMe.scope('includeActiveProjects').findAll()).not.to.be.rejected; }); - it('should apply scope conditions', function() { - return this.ScopeMe.scope('includeActiveProjects').findOne({ where: { id: 1 } }).then(user => { - expect(user.company.projects).to.have.length(1); - }); + it('should apply scope conditions', async function() { + const user = await this.ScopeMe.scope('includeActiveProjects').findOne({ where: { id: 1 } }); + expect(user.company.projects).to.have.length(1); }); describe('with different format', () => { - it('should not throw error', function() { + it('should not throw error', async function() { const Child = this.sequelize.define('Child'); const Parent = this.sequelize.define('Parent', {}, { defaultScope: { @@ -322,18 +303,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { Child.belongsTo(Parent); Parent.hasOne(Child); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Child.create(), Parent.create()]); - }).then(([child, parent]) => { - return parent.setChild(child); - }).then(() => { - return Parent.scope('children', 'alsoChildren').findOne(); - }); + await this.sequelize.sync({ force: true }); + const [child, parent] = await Promise.all([Child.create(), Parent.create()]); + await parent.setChild(child); + + await Parent.scope('children', 'alsoChildren').findOne(); }); }); describe('with find options', () => { - it('should merge includes correctly', function() { + it('should merge includes correctly', async function() { const Child = this.sequelize.define('Child', { name: Sequelize.STRING }); const Parent = this.sequelize.define('Parent', { name: Sequelize.STRING }); Parent.addScope('testScope1', { @@ -346,32 +325,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); Parent.hasMany(Child); - return this.sequelize.sync({ force: true }) - .then(() => { - return Promise.all([ - Parent.create({ name: 'parent1' }).then(parent => parent.createChild({ name: 'child1' })), - Parent.create({ name: 'parent2' }).then(parent => parent.createChild({ name: 'child2' })) - ]); - }) - .then(() => { - return Parent.scope('testScope1').findOne({ - include: [{ - model: Child, - attributes: { exclude: ['name'] } - }] - }); - }) - .then(parent => { - expect(parent.get('name')).to.equal('parent2'); - expect(parent.Children).to.have.length(1); - expect(parent.Children[0].dataValues).not.to.have.property('name'); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Parent.create({ name: 'parent1' }).then(parent => parent.createChild({ name: 'child1' })), + Parent.create({ name: 'parent2' }).then(parent => parent.createChild({ name: 'child2' })) + ]); + + const parent = await Parent.scope('testScope1').findOne({ + include: [{ + model: Child, + attributes: { exclude: ['name'] } + }] + }); + + expect(parent.get('name')).to.equal('parent2'); + expect(parent.Children).to.have.length(1); + expect(parent.Children[0].dataValues).not.to.have.property('name'); }); }); }); describe('scope with options', () => { - it('should return correct object included foreign_key', function() { + it('should return correct object included foreign_key', async function() { const Child = this.sequelize.define('Child', { secret: Sequelize.STRING }, { @@ -387,16 +363,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { Child.belongsTo(Parent); Parent.hasOne(Child); - return this.sequelize.sync({ force: true }) - .then(() => Child.create({ secret: 'super secret' })) - .then(() => Child.scope('public').findOne()) - .then(user => { - expect(user.dataValues).to.have.property('ParentId'); - expect(user.dataValues).not.to.have.property('secret'); - }); + await this.sequelize.sync({ force: true }); + await Child.create({ secret: 'super secret' }); + const user = await Child.scope('public').findOne(); + expect(user.dataValues).to.have.property('ParentId'); + expect(user.dataValues).not.to.have.property('secret'); }); - it('should return correct object included foreign_key with defaultScope', function() { + it('should return correct object included foreign_key with defaultScope', async function() { const Child = this.sequelize.define('Child', { secret: Sequelize.STRING }, { @@ -409,53 +383,49 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Parent = this.sequelize.define('Parent'); Child.belongsTo(Parent); - return this.sequelize.sync({ force: true }) - .then(() => Child.create({ secret: 'super secret' })) - .then(() => Child.findOne()) - .then(user => { - expect(user.dataValues).to.have.property('ParentId'); - expect(user.dataValues).not.to.have.property('secret'); - }); + await this.sequelize.sync({ force: true }); + await Child.create({ secret: 'super secret' }); + const user = await Child.findOne(); + expect(user.dataValues).to.have.property('ParentId'); + expect(user.dataValues).not.to.have.property('secret'); }); - it('should not throw error', function() { + it('should not throw error', async function() { const Clientfile = this.sequelize.define('clientfile'); const Mission = this.sequelize.define('mission', { secret: Sequelize.STRING }); const Building = this.sequelize.define('building'); const MissionAssociation = Clientfile.hasOne(Mission); const BuildingAssociation = Clientfile.hasOne(Building); - return this.sequelize.sync({ force: true }) - .then(() => { - return Clientfile.findAll({ - include: [ - { - association: 'mission', - where: { - secret: 'foo' - } - }, - { - association: 'building' - } - ] - }); - }) - .then(() => { - return Clientfile.findAll({ - include: [ - { - association: MissionAssociation, - where: { - secret: 'foo' - } - }, - { - association: BuildingAssociation - } - ] - }); - }); + await this.sequelize.sync({ force: true }); + + await Clientfile.findAll({ + include: [ + { + association: 'mission', + where: { + secret: 'foo' + } + }, + { + association: 'building' + } + ] + }); + + await Clientfile.findAll({ + include: [ + { + association: MissionAssociation, + where: { + secret: 'foo' + } + }, + { + association: BuildingAssociation + } + ] + }); }); }); }); diff --git a/test/integration/model/scope/count.test.js b/test/integration/model/scope/count.test.js index a869b703ddcb..cc08fcd17af0 100644 --- a/test/integration/model/scope/count.test.js +++ b/test/integration/model/scope/count.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('count', () => { - beforeEach(function() { + beforeEach(async function() { this.Child = this.sequelize.define('Child', { priority: Sequelize.INTEGER }); @@ -80,66 +80,64 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Child.belongsTo(this.ScopeMe); this.ScopeMe.hasMany(this.Child); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, aliasValue: 12 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, aliasValue: 5 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, aliasValue: 1 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, aliasValue: 10 } - ]; - return this.ScopeMe.bulkCreate(records); - }).then(() => { - return this.ScopeMe.findAll(); - }).then(records => { - return Promise.all([ - records[0].createChild({ - priority: 1 - }), - records[1].createChild({ - priority: 2 - }) - ]); - }); + await this.sequelize.sync({ force: true }); + const records0 = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, aliasValue: 12 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, aliasValue: 5 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, aliasValue: 1 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, aliasValue: 10 } + ]; + await this.ScopeMe.bulkCreate(records0); + const records = await this.ScopeMe.findAll(); + + await Promise.all([ + records[0].createChild({ + priority: 1 + }), + records[1].createChild({ + priority: 2 + }) + ]); }); - it('should apply defaultScope', function() { - return expect(this.ScopeMe.count()).to.eventually.equal(2); + it('should apply defaultScope', async function() { + await expect(this.ScopeMe.count()).to.eventually.equal(2); }); - it('should be able to override default scope', function() { - return expect(this.ScopeMe.count({ where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); + it('should be able to override default scope', async function() { + await expect(this.ScopeMe.count({ where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); }); - it('should be able to unscope', function() { - return expect(this.ScopeMe.unscoped().count()).to.eventually.equal(4); + it('should be able to unscope', async function() { + await expect(this.ScopeMe.unscoped().count()).to.eventually.equal(4); }); - it('should be able to apply other scopes', function() { - return expect(this.ScopeMe.scope('lowAccess').count()).to.eventually.equal(3); + it('should be able to apply other scopes', async function() { + await expect(this.ScopeMe.scope('lowAccess').count()).to.eventually.equal(3); }); - it('should be able to merge scopes with where', function() { - return expect(this.ScopeMe.scope('lowAccess').count({ where: { username: 'dan' } })).to.eventually.equal(1); + it('should be able to merge scopes with where', async function() { + await expect(this.ScopeMe.scope('lowAccess').count({ where: { username: 'dan' } })).to.eventually.equal(1); }); - it('should be able to merge scopes with where on aliased fields', function() { - return expect(this.ScopeMe.scope('withAliasedField').count({ where: { aliasValue: 5 } })).to.eventually.equal(1); + it('should be able to merge scopes with where on aliased fields', async function() { + await expect(this.ScopeMe.scope('withAliasedField').count({ where: { aliasValue: 5 } })).to.eventually.equal(1); }); - it('should ignore the order option if it is found within the scope', function() { - return expect(this.ScopeMe.scope('withOrder').count()).to.eventually.equal(4); + it('should ignore the order option if it is found within the scope', async function() { + await expect(this.ScopeMe.scope('withOrder').count()).to.eventually.equal(4); }); - it('should be able to use where on include', function() { - return expect(this.ScopeMe.scope('withInclude').count()).to.eventually.equal(1); + it('should be able to use where on include', async function() { + await expect(this.ScopeMe.scope('withInclude').count()).to.eventually.equal(1); }); - it('should be able to use include with function scope', function() { - return expect(this.ScopeMe.scope('withIncludeFunction').count()).to.eventually.equal(1); + it('should be able to use include with function scope', async function() { + await expect(this.ScopeMe.scope('withIncludeFunction').count()).to.eventually.equal(1); }); - it('should be able to use include with function scope and string association', function() { - return expect(this.ScopeMe.scope('withIncludeFunctionAndStringAssociation').count()).to.eventually.equal(1); + it('should be able to use include with function scope and string association', async function() { + await expect(this.ScopeMe.scope('withIncludeFunctionAndStringAssociation').count()).to.eventually.equal(1); }); }); }); diff --git a/test/integration/model/scope/destroy.test.js b/test/integration/model/scope/destroy.test.js index ba602b8eb229..ce66450c505a 100644 --- a/test/integration/model/scope/destroy.test.js +++ b/test/integration/model/scope/destroy.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('destroy', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -34,70 +34,59 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should apply defaultScope', function() { - return this.ScopeMe.destroy({ where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('username')).to.equal('tony'); - expect(users[1].get('username')).to.equal('fred'); - }); + it('should apply defaultScope', async function() { + await this.ScopeMe.destroy({ where: {} }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(2); + expect(users[0].get('username')).to.equal('tony'); + expect(users[1].get('username')).to.equal('fred'); }); - it('should be able to override default scope', function() { - return this.ScopeMe.destroy({ where: { access_level: { [Op.lt]: 5 } } }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('username')).to.equal('tobi'); - expect(users[1].get('username')).to.equal('dan'); - }); + it('should be able to override default scope', async function() { + await this.ScopeMe.destroy({ where: { access_level: { [Op.lt]: 5 } } }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(2); + expect(users[0].get('username')).to.equal('tobi'); + expect(users[1].get('username')).to.equal('dan'); }); - it('should be able to unscope destroy', function() { - return this.ScopeMe.unscoped().destroy({ where: {} }).then(() => { - return expect(this.ScopeMe.unscoped().findAll()).to.eventually.have.length(0); - }); + it('should be able to unscope destroy', async function() { + await this.ScopeMe.unscoped().destroy({ where: {} }); + await expect(this.ScopeMe.unscoped().findAll()).to.eventually.have.length(0); }); - it('should be able to apply other scopes', function() { - return this.ScopeMe.scope('lowAccess').destroy({ where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('tobi'); - }); + it('should be able to apply other scopes', async function() { + await this.ScopeMe.scope('lowAccess').destroy({ where: {} }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(1); + expect(users[0].get('username')).to.equal('tobi'); }); - it('should be able to merge scopes with where', function() { - return this.ScopeMe.scope('lowAccess').destroy({ where: { username: 'dan' } }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(3); - expect(users[0].get('username')).to.equal('tony'); - expect(users[1].get('username')).to.equal('tobi'); - expect(users[2].get('username')).to.equal('fred'); - }); + it('should be able to merge scopes with where', async function() { + await this.ScopeMe.scope('lowAccess').destroy({ where: { username: 'dan' } }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(3); + expect(users[0].get('username')).to.equal('tony'); + expect(users[1].get('username')).to.equal('tobi'); + expect(users[2].get('username')).to.equal('fred'); }); - it('should work with empty where', function() { - return this.ScopeMe.scope('lowAccess').destroy().then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('tobi'); - }); + it('should work with empty where', async function() { + await this.ScopeMe.scope('lowAccess').destroy(); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(1); + expect(users[0].get('username')).to.equal('tobi'); }); }); }); diff --git a/test/integration/model/scope/find.test.js b/test/integration/model/scope/find.test.js index 19b9ee5731c9..49604344dcf5 100644 --- a/test/integration/model/scope/find.test.js +++ b/test/integration/model/scope/find.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scopes', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -62,93 +62,83 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.ScopeMe.hasMany(this.DefaultScopeExclude); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should be able use where in scope', function() { - return this.ScopeMe.scope({ where: { parent_id: 2 } }).findAll().then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('tobi'); - }); + it('should be able use where in scope', async function() { + const users = await this.ScopeMe.scope({ where: { parent_id: 2 } }).findAll(); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('tobi'); }); - it('should be able to combine scope and findAll where clauses', function() { - return this.ScopeMe.scope({ where: { parent_id: 1 } }).findAll({ where: { access_level: 3 } }).then(users => { - expect(users).to.have.length(2); - expect(['tony', 'fred'].includes(users[0].username)).to.be.true; - expect(['tony', 'fred'].includes(users[1].username)).to.be.true; - }); + it('should be able to combine scope and findAll where clauses', async function() { + const users = await this.ScopeMe.scope({ where: { parent_id: 1 } }).findAll({ where: { access_level: 3 } }); + expect(users).to.have.length(2); + expect(['tony', 'fred'].includes(users[0].username)).to.be.true; + expect(['tony', 'fred'].includes(users[1].username)).to.be.true; }); - it('should be able to use a defaultScope if declared', function() { - return this.ScopeMe.findAll().then(users => { - expect(users).to.have.length(2); - expect([10, 5].includes(users[0].access_level)).to.be.true; - expect([10, 5].includes(users[1].access_level)).to.be.true; - expect(['dan', 'tobi'].includes(users[0].username)).to.be.true; - expect(['dan', 'tobi'].includes(users[1].username)).to.be.true; - }); + it('should be able to use a defaultScope if declared', async function() { + const users = await this.ScopeMe.findAll(); + expect(users).to.have.length(2); + expect([10, 5].includes(users[0].access_level)).to.be.true; + expect([10, 5].includes(users[1].access_level)).to.be.true; + expect(['dan', 'tobi'].includes(users[0].username)).to.be.true; + expect(['dan', 'tobi'].includes(users[1].username)).to.be.true; }); - it('should be able to handle $and in scopes', function() { - return this.ScopeMe.scope('andScope').findAll().then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('tony'); - }); + it('should be able to handle $and in scopes', async function() { + const users = await this.ScopeMe.scope('andScope').findAll(); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('tony'); }); describe('should not overwrite', () => { - it('default scope with values from previous finds', function() { - return this.ScopeMe.findAll({ where: { other_value: 10 } }).then(users => { - expect(users).to.have.length(1); - - return this.ScopeMe.findAll(); - }).then(users => { - // This should not have other_value: 10 - expect(users).to.have.length(2); - }); + it('default scope with values from previous finds', async function() { + const users0 = await this.ScopeMe.findAll({ where: { other_value: 10 } }); + expect(users0).to.have.length(1); + const users = await this.ScopeMe.findAll(); + // This should not have other_value: 10 + expect(users).to.have.length(2); }); - it('other scopes with values from previous finds', function() { - return this.ScopeMe.scope('highValue').findAll({ where: { access_level: 10 } }).then(users => { - expect(users).to.have.length(1); + it('other scopes with values from previous finds', async function() { + const users0 = await this.ScopeMe.scope('highValue').findAll({ where: { access_level: 10 } }); + expect(users0).to.have.length(1); - return this.ScopeMe.scope('highValue').findAll(); - }).then(users => { - // This should not have other_value: 10 - expect(users).to.have.length(2); - }); + const users = await this.ScopeMe.scope('highValue').findAll(); + // This should not have other_value: 10 + expect(users).to.have.length(2); }); }); - it('should have no problem performing findOrCreate', function() { - return this.ScopeMe.findOrCreate({ where: { username: 'fake' } }).then(([user]) => { - expect(user.username).to.equal('fake'); - }); + it('should have no problem performing findOrCreate', async function() { + const [user] = await this.ScopeMe.findOrCreate({ where: { username: 'fake' } }); + expect(user.username).to.equal('fake'); }); - it('should work when included with default scope', function() { - return this.ScopeMe.findOne({ + it('should work when included with default scope', async function() { + await this.ScopeMe.findOne({ include: [this.DefaultScopeExclude] }); }); }); describe('scope in associations', () => { - it('should work when association with a virtual column queried with default scope', function() { + it('should work when association with a virtual column queried with default scope', async function() { const Game = this.sequelize.define('Game', { name: Sequelize.TEXT }); - + const User = this.sequelize.define('User', { login: Sequelize.TEXT, session: { @@ -164,18 +154,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } }); - + Game.hasMany(User); - return this.sequelize.sync({ force: true }).then(() => { - return Game.findAll({ - include: [{ - model: User - }] - }); - }).then(games => { - expect(games).to.have.lengthOf(0); + await this.sequelize.sync({ force: true }); + + const games = await Game.findAll({ + include: [{ + model: User + }] }); + + expect(games).to.have.lengthOf(0); }); }); }); diff --git a/test/integration/model/scope/findAndCountAll.test.js b/test/integration/model/scope/findAndCountAll.test.js index 1bceb2238fe4..529fa64993ea 100644 --- a/test/integration/model/scope/findAndCountAll.test.js +++ b/test/integration/model/scope/findAndCountAll.test.js @@ -11,7 +11,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('findAndCountAll', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -40,59 +40,50 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should apply defaultScope', function() { - return this.ScopeMe.findAndCountAll().then(result => { - expect(result.count).to.equal(2); - expect(result.rows.length).to.equal(2); - }); + it('should apply defaultScope', async function() { + const result = await this.ScopeMe.findAndCountAll(); + expect(result.count).to.equal(2); + expect(result.rows.length).to.equal(2); }); - it('should be able to override default scope', function() { - return this.ScopeMe.findAndCountAll({ where: { access_level: { [Op.gt]: 5 } } }) - .then(result => { - expect(result.count).to.equal(1); - expect(result.rows.length).to.equal(1); - }); + it('should be able to override default scope', async function() { + const result = await this.ScopeMe.findAndCountAll({ where: { access_level: { [Op.gt]: 5 } } }); + expect(result.count).to.equal(1); + expect(result.rows.length).to.equal(1); }); - it('should be able to unscope', function() { - return this.ScopeMe.unscoped().findAndCountAll({ limit: 1 }) - .then(result => { - expect(result.count).to.equal(4); - expect(result.rows.length).to.equal(1); - }); + it('should be able to unscope', async function() { + const result = await this.ScopeMe.unscoped().findAndCountAll({ limit: 1 }); + expect(result.count).to.equal(4); + expect(result.rows.length).to.equal(1); }); - it('should be able to apply other scopes', function() { - return this.ScopeMe.scope('lowAccess').findAndCountAll() - .then(result => { - expect(result.count).to.equal(3); - }); + it('should be able to apply other scopes', async function() { + const result = await this.ScopeMe.scope('lowAccess').findAndCountAll(); + expect(result.count).to.equal(3); }); - it('should be able to merge scopes with where', function() { - return this.ScopeMe.scope('lowAccess') - .findAndCountAll({ where: { username: 'dan' } }).then(result => { - expect(result.count).to.equal(1); - }); + it('should be able to merge scopes with where', async function() { + const result = await this.ScopeMe.scope('lowAccess') + .findAndCountAll({ where: { username: 'dan' } }); + + expect(result.count).to.equal(1); }); - it('should ignore the order option if it is found within the scope', function() { - return this.ScopeMe.scope('withOrder').findAndCountAll() - .then(result => { - expect(result.count).to.equal(4); - }); + it('should ignore the order option if it is found within the scope', async function() { + const result = await this.ScopeMe.scope('withOrder').findAndCountAll(); + expect(result.count).to.equal(4); }); }); }); diff --git a/test/integration/model/scope/merge.test.js b/test/integration/model/scope/merge.test.js index 5fe36704a82b..45e840a70789 100644 --- a/test/integration/model/scope/merge.test.js +++ b/test/integration/model/scope/merge.test.js @@ -9,8 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('simple merge', () => { - beforeEach(function() { - + beforeEach(async function() { this.Foo = this.sequelize.define('foo', { name: Sequelize.STRING }, { timestamps: false }); this.Bar = this.sequelize.define('bar', { name: Sequelize.STRING }, { timestamps: false }); this.Baz = this.sequelize.define('baz', { name: Sequelize.STRING }, { timestamps: false }); @@ -18,10 +17,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Foo.belongsTo(this.Baz, { foreignKey: 'bazId' }); this.Foo.hasOne(this.Bar, { foreignKey: 'fooId' }); - this.createEntries = () => { - return this.Baz.create({ name: 'The Baz' }) - .then(baz => this.Foo.create({ name: 'The Foo', bazId: baz.id })) - .then(foo => this.Bar.create({ name: 'The Bar', fooId: foo.id })); + this.createEntries = async () => { + const baz = await this.Baz.create({ name: 'The Baz' }); + const foo = await this.Foo.create({ name: 'The Foo', bazId: baz.id }); + return this.Bar.create({ name: 'The Bar', fooId: foo.id }); }; this.scopes = { @@ -32,24 +31,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Foo.addScope('includeBar', this.scopes.includeBar); this.Foo.addScope('includeBaz', this.scopes.includeBaz); - return this.sequelize.sync({ force: true }).then(this.createEntries); - + await this.createEntries(await this.sequelize.sync({ force: true })); }); - it('should merge simple scopes correctly', function() { - return this.Foo.scope('includeBar', 'includeBaz').findOne().then(result => { - const json = result.toJSON(); - expect(json.bar).to.be.ok; - expect(json.baz).to.be.ok; - expect(json.bar.name).to.equal('The Bar'); - expect(json.baz.name).to.equal('The Baz'); - }); + it('should merge simple scopes correctly', async function() { + const result = await this.Foo.scope('includeBar', 'includeBaz').findOne(); + const json = result.toJSON(); + expect(json.bar).to.be.ok; + expect(json.baz).to.be.ok; + expect(json.bar.name).to.equal('The Bar'); + expect(json.baz.name).to.equal('The Baz'); }); }); describe('complex merge', () => { - beforeEach(function() { - + beforeEach(async function() { this.Foo = this.sequelize.define('foo', { name: Sequelize.STRING }, { timestamps: false }); this.Bar = this.sequelize.define('bar', { name: Sequelize.STRING }, { timestamps: false }); this.Baz = this.sequelize.define('baz', { name: Sequelize.STRING }, { timestamps: false }); @@ -142,35 +138,35 @@ describe(Support.getTestDialectTeaser('Model'), () => { 'excludeBazName' ]).toArray(); - return this.sequelize.sync({ force: true }).then(this.createFooWithDescendants); - + await this.createFooWithDescendants(await this.sequelize.sync({ force: true })); }); - it('should merge complex scopes correctly regardless of their order', function() { - return Promise.all(this.scopePermutations.map(scopes => this.Foo.scope(...scopes).findOne())).then(results => { - const first = results.shift().toJSON(); - for (const result of results) { - expect(result.toJSON()).to.deep.equal(first); - } - }); + it('should merge complex scopes correctly regardless of their order', async function() { + const results = await Promise.all(this.scopePermutations.map(scopes => this.Foo.scope(...scopes).findOne())); + const first = results.shift().toJSON(); + for (const result of results) { + expect(result.toJSON()).to.deep.equal(first); + } }); - it('should merge complex scopes with findAll options correctly regardless of their order', function() { - return Promise.all(this.scopePermutations.map(([a, b, c, d]) => this.Foo.scope(a, b, c).findAll(this.scopes[d]).then(x => x[0]))).then(results => { - const first = results.shift().toJSON(); - for (const result of results) { - expect(result.toJSON()).to.deep.equal(first); - } - }); + it('should merge complex scopes with findAll options correctly regardless of their order', async function() { + const results = await Promise.all(this.scopePermutations.map(async ([a, b, c, d]) => { + const x = await this.Foo.scope(a, b, c).findAll(this.scopes[d]); + return x[0]; + })); + + const first = results.shift().toJSON(); + for (const result of results) { + expect(result.toJSON()).to.deep.equal(first); + } }); - it('should merge complex scopes with findOne options correctly regardless of their order', function() { - return Promise.all(this.scopePermutations.map(([a, b, c, d]) => this.Foo.scope(a, b, c).findOne(this.scopes[d]))).then(results => { - const first = results.shift().toJSON(); - for (const result of results) { - expect(result.toJSON()).to.deep.equal(first); - } - }); + it('should merge complex scopes with findOne options correctly regardless of their order', async function() { + const results = await Promise.all(this.scopePermutations.map(([a, b, c, d]) => this.Foo.scope(a, b, c).findOne(this.scopes[d]))); + const first = results.shift().toJSON(); + for (const result of results) { + expect(result.toJSON()).to.deep.equal(first); + } }); }); diff --git a/test/integration/model/scope/update.test.js b/test/integration/model/scope/update.test.js index cfb195f0e12b..50a3d339d6b9 100644 --- a/test/integration/model/scope/update.test.js +++ b/test/integration/model/scope/update.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('update', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -34,73 +34,62 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should apply defaultScope', function() { - return this.ScopeMe.update({ username: 'ruben' }, { where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); - expect(users[1].get('email')).to.equal('dan@sequelizejs.com'); - }); + it('should apply defaultScope', async function() { + await this.ScopeMe.update({ username: 'ruben' }, { where: {} }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); + expect(users).to.have.length(2); + expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); + expect(users[1].get('email')).to.equal('dan@sequelizejs.com'); }); - it('should be able to override default scope', function() { - return this.ScopeMe.update({ username: 'ruben' }, { where: { access_level: { [Op.lt]: 5 } } }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('email')).to.equal('tony@sequelizejs.com'); - expect(users[1].get('email')).to.equal('fred@foobar.com'); - }); + it('should be able to override default scope', async function() { + await this.ScopeMe.update({ username: 'ruben' }, { where: { access_level: { [Op.lt]: 5 } } }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); + expect(users).to.have.length(2); + expect(users[0].get('email')).to.equal('tony@sequelizejs.com'); + expect(users[1].get('email')).to.equal('fred@foobar.com'); }); - it('should be able to unscope destroy', function() { - return this.ScopeMe.unscoped().update({ username: 'ruben' }, { where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(rubens => { - expect(rubens.every(r => r.get('username') === 'ruben')).to.be.true; - }); + it('should be able to unscope destroy', async function() { + await this.ScopeMe.unscoped().update({ username: 'ruben' }, { where: {} }); + const rubens = await this.ScopeMe.unscoped().findAll(); + expect(rubens.every(r => r.get('username') === 'ruben')).to.be.true; }); - it('should be able to apply other scopes', function() { - return this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: { [Op.ne]: 'ruben' } } }); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); - }); + it('should be able to apply other scopes', async function() { + await this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: {} }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: { [Op.ne]: 'ruben' } } }); + expect(users).to.have.length(1); + expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); }); - it('should be able to merge scopes with where', function() { - return this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: { username: 'dan' } }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('email')).to.equal('dan@sequelizejs.com'); - }); + it('should be able to merge scopes with where', async function() { + await this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: { username: 'dan' } }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); + expect(users).to.have.length(1); + expect(users[0].get('email')).to.equal('dan@sequelizejs.com'); }); - it('should work with empty where', function() { - return this.ScopeMe.scope('lowAccess').update({ + it('should work with empty where', async function() { + await this.ScopeMe.scope('lowAccess').update({ username: 'ruby' - }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruby' } }); - }).then(users => { - expect(users).to.have.length(3); - users.forEach(user => { - expect(user.get('username')).to.equal('ruby'); - }); + }); + + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruby' } }); + expect(users).to.have.length(3); + users.forEach(user => { + expect(user.get('username')).to.equal('ruby'); }); }); }); diff --git a/test/integration/model/searchPath.test.js b/test/integration/model/searchPath.test.js index 05e24283f03e..8f8a41d4600f 100644 --- a/test/integration/model/searchPath.test.js +++ b/test/integration/model/searchPath.test.js @@ -52,453 +52,426 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - beforeEach('build restaurant tables', function() { + beforeEach('build restaurant tables', async function() { const Restaurant = this.Restaurant; - return current.createSchema('schema_one').then(() => { - return current.createSchema('schema_two'); - }).then(() => { - return Restaurant.sync({ force: true, searchPath: SEARCH_PATH_ONE }); - }).then(() => { - return Restaurant.sync({ force: true, searchPath: SEARCH_PATH_TWO }); - }).catch(err => { + + try { + await current.createSchema('schema_one'); + await current.createSchema('schema_two'); + await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_ONE }); + await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_TWO }); + } catch (err) { expect(err).to.be.null; - }); + } }); - afterEach('drop schemas', () => { - return current.dropSchema('schema_one').then(() => { - return current.dropSchema('schema_two'); - }); + afterEach('drop schemas', async () => { + await current.dropSchema('schema_one'); + await current.dropSchema('schema_two'); }); describe('enum case', () => { - it('able to refresh enum when searchPath is used', function() { - return this.Location.sync({ force: true }); + it('able to refresh enum when searchPath is used', async function() { + await this.Location.sync({ force: true }); }); }); describe('Add data via model.create, retrieve via model.findOne', () => { - it('should be able to insert data into the table in schema_one using create', function() { + it('should be able to insert data into the table in schema_one using create', async function() { const Restaurant = this.Restaurant; - let restaurantId; - return Restaurant.create({ + await Restaurant.create({ foo: 'one', location_id: locationId - }, { searchPath: SEARCH_PATH_ONE }) - .then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_ONE }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - }); + }, { searchPath: SEARCH_PATH_ONE }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('one'); + const restaurantId = obj0.id; + const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_ONE }); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); }); - it('should fail to insert data into schema_two using create', function() { + it('should fail to insert data into schema_two using create', async function() { const Restaurant = this.Restaurant; - return Restaurant.create({ - foo: 'test' - }, { searchPath: SEARCH_PATH_TWO }).catch(err => { + try { + await Restaurant.create({ + foo: 'test' + }, { searchPath: SEARCH_PATH_TWO }); + } catch (err) { expect(err).to.not.be.null; - }); + } }); - it('should be able to insert data into the table in schema_two using create', function() { + it('should be able to insert data into the table in schema_two using create', async function() { const Restaurant = this.Restaurant; - let restaurantId; - return Restaurant.create({ + await Restaurant.create({ foo: 'two', location_id: locationId - }, { searchPath: SEARCH_PATH_TWO }) - .then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_TWO }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - }); + }, { searchPath: SEARCH_PATH_TWO }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + const restaurantId = obj0.id; + const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_TWO }); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('two'); }); - it('should fail to find schema_one object in schema_two', function() { + it('should fail to find schema_one object in schema_two', async function() { const Restaurant = this.Restaurant; - return Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_TWO }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); + const RestaurantObj = await Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_TWO }); + expect(RestaurantObj).to.be.null; }); - it('should fail to find schema_two object in schema_one', function() { + it('should fail to find schema_two object in schema_one', async function() { const Restaurant = this.Restaurant; - return Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_ONE }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); + const RestaurantObj = await Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_ONE }); + expect(RestaurantObj).to.be.null; }); }); describe('Add data via instance.save, retrieve via model.findAll', () => { - it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', function() { + it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', async function() { const Restaurant = this.Restaurant; let restaurauntModel = Restaurant.build({ bar: 'one.1' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }) - .then(() => { - restaurauntModel = Restaurant.build({ bar: 'one.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.1' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.3' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.rows.length).to.equal(2); - expect(restaurantsOne.count).to.equal(2); - restaurantsOne.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.rows.length).to.equal(3); - expect(restaurantsTwo.count).to.equal(3); - restaurantsTwo.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'one.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'two.1' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.3' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + const restaurantsOne0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); + expect(restaurantsOne0).to.not.be.null; + expect(restaurantsOne0.length).to.equal(2); + restaurantsOne0.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsOne = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE }); + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.rows.length).to.equal(2); + expect(restaurantsOne.count).to.equal(2); + restaurantsOne.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsTwo0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); + expect(restaurantsTwo0).to.not.be.null; + expect(restaurantsTwo0.length).to.equal(3); + restaurantsTwo0.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + const restaurantsTwo = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO }); + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.rows.length).to.equal(3); + expect(restaurantsTwo.count).to.equal(3); + restaurantsTwo.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); }); }); describe('Add data via instance.save, retrieve via model.count and model.find', () => { - it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', function() { + it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', async function() { const Restaurant = this.Restaurant; let restaurauntModel = Restaurant.build({ bar: 'one.1' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'one.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.1' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = Restaurant.build({ bar: 'two.3' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return Restaurant.findAll({ - where: { bar: { [Op.like]: 'one%' } }, - searchPath: SEARCH_PATH_ONE - }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.count({ searchPath: SEARCH_PATH_ONE }); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(2); - return Restaurant.findAll({ - where: { bar: { [Op.like]: 'two%' } }, - searchPath: SEARCH_PATH_TWO - }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return Restaurant.count({ searchPath: SEARCH_PATH_TWO }); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(3); + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'one.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'two.1' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.3' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + + const restaurantsOne = await Restaurant.findAll({ + where: { bar: { [Op.like]: 'one%' } }, + searchPath: SEARCH_PATH_ONE + }); + + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(2); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const count0 = await Restaurant.count({ searchPath: SEARCH_PATH_ONE }); + expect(count0).to.not.be.null; + expect(count0).to.equal(2); + + const restaurantsTwo = await Restaurant.findAll({ + where: { bar: { [Op.like]: 'two%' } }, + searchPath: SEARCH_PATH_TWO + }); + + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(3); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); }); + const count = await Restaurant.count({ searchPath: SEARCH_PATH_TWO }); + expect(count).to.not.be.null; + expect(count).to.equal(3); }); }); describe('Get associated data in public schema via include', () => { - beforeEach(function() { + beforeEach(async function() { const Location = this.Location; - return Location.sync({ force: true }) - .then(() => { - return Location.create({ name: 'HQ' }).then(() => { - return Location.findOne({ where: { name: 'HQ' } }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.name).to.equal('HQ'); - locationId = obj.id; - }); - }); - }) - .catch(err => { - expect(err).to.be.null; - }); + try { + await Location.sync({ force: true }); + await Location.create({ name: 'HQ' }); + const obj = await Location.findOne({ where: { name: 'HQ' } }); + expect(obj).to.not.be.null; + expect(obj.name).to.equal('HQ'); + locationId = obj.id; + } catch (err) { + expect(err).to.be.null; + } }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { const Restaurant = this.Restaurant; const Location = this.Location; - return Restaurant.create({ + await Restaurant.create({ foo: 'one', location_id: locationId - }, { searchPath: SEARCH_PATH_ONE }).then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, include: [{ - model: Location, as: 'location' - }], searchPath: SEARCH_PATH_ONE - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); + }, { searchPath: SEARCH_PATH_ONE }); + + const obj = await Restaurant.findOne({ + where: { foo: 'one' }, include: [{ + model: Location, as: 'location' + }], searchPath: SEARCH_PATH_ONE }); + + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); + expect(obj.location).to.not.be.null; + expect(obj.location.name).to.equal('HQ'); }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { const Restaurant = this.Restaurant; const Location = this.Location; - return Restaurant.create({ + await Restaurant.create({ foo: 'two', location_id: locationId - }, { searchPath: SEARCH_PATH_TWO }).then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, include: [{ - model: Location, as: 'location' - }], searchPath: SEARCH_PATH_TWO - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); + }, { searchPath: SEARCH_PATH_TWO }); + + const obj = await Restaurant.findOne({ + where: { foo: 'two' }, include: [{ + model: Location, as: 'location' + }], searchPath: SEARCH_PATH_TWO }); + + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('two'); + expect(obj.location).to.not.be.null; + expect(obj.location.name).to.equal('HQ'); }); }); describe('Get schema specific associated data via include', () => { - beforeEach(function() { + beforeEach(async function() { const Employee = this.Employee; - return Employee.sync({ force: true, searchPath: SEARCH_PATH_ONE }) - .then(() => { - return Employee.sync({ force: true, searchPath: SEARCH_PATH_TWO }); - }) - .catch(err => { - expect(err).to.be.null; - }); + + try { + await Employee.sync({ force: true, searchPath: SEARCH_PATH_ONE }); + await Employee.sync({ force: true, searchPath: SEARCH_PATH_TWO }); + } catch (err) { + expect(err).to.be.null; + } }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { const Restaurant = this.Restaurant; const Employee = this.Employee; - let restaurantId; - - return Restaurant.create({ + await Restaurant.create({ foo: 'one' - }, { searchPath: SEARCH_PATH_ONE }).then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return Employee.create({ - first_name: 'Restaurant', - last_name: 'one', - restaurant_id: restaurantId - }, { searchPath: SEARCH_PATH_ONE }); - }).then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ - model: Employee, as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('one'); - return obj.getEmployees({ searchPath: SEARCH_PATH_ONE }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('one'); - return Employee.findOne({ - where: { last_name: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ - model: Restaurant, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('one'); - return obj.getRestaurant({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('one'); + }, { searchPath: SEARCH_PATH_ONE }); + + const obj1 = await Restaurant.findOne({ + where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE + }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('one'); + const restaurantId = obj1.id; + + await Employee.create({ + first_name: 'Restaurant', + last_name: 'one', + restaurant_id: restaurantId + }, { searchPath: SEARCH_PATH_ONE }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ + model: Employee, as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('one'); + const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_ONE }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('one'); + + const obj = await Employee.findOne({ + where: { last_name: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ + model: Restaurant, as: 'restaurant' + }] }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('one'); + const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_ONE }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('one'); }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { const Restaurant = this.Restaurant; const Employee = this.Employee; - let restaurantId; - return Restaurant.create({ + await Restaurant.create({ foo: 'two' - }, { searchPath: SEARCH_PATH_TWO }).then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return Employee.create({ - first_name: 'Restaurant', - last_name: 'two', - restaurant_id: restaurantId - }, { searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ - model: Employee, as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('two'); - return obj.getEmployees({ searchPath: SEARCH_PATH_TWO }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('two'); - return Employee.findOne({ - where: { last_name: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ - model: Restaurant, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('two'); - return obj.getRestaurant({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('two'); + }, { searchPath: SEARCH_PATH_TWO }); + + const obj1 = await Restaurant.findOne({ + where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('two'); + const restaurantId = obj1.id; + + await Employee.create({ + first_name: 'Restaurant', + last_name: 'two', + restaurant_id: restaurantId + }, { searchPath: SEARCH_PATH_TWO }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ + model: Employee, as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('two'); + const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_TWO }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('two'); + + const obj = await Employee.findOne({ + where: { last_name: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ + model: Restaurant, as: 'restaurant' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('two'); + const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_TWO }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('two'); }); }); describe('concurency tests', () => { - it('should build and persist instances to 2 schemas concurrently in any order', function() { + it('should build and persist instances to 2 schemas concurrently in any order', async function() { const Restaurant = this.Restaurant; let restaurauntModelSchema1 = Restaurant.build({ bar: 'one.1' }); const restaurauntModelSchema2 = Restaurant.build({ bar: 'two.1' }); - return restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }) - .then(() => { - restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' }); - return restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); - }).then(() => { - return Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); + await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' }); + await restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); + await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); + const restaurantsOne = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(2); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsTwo = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(1); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); }); }); describe('Edit data via instance.update, retrieve updated instance via model.findAll', () => { - it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', function() { + it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', async function() { const Restaurant = this.Restaurant; - return Promise.all([ - Restaurant.create({ foo: 'one', bar: '1' }, { searchPath: SEARCH_PATH_ONE }) - .then(rnt => rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_ONE })), + const rnt = await Restaurant.create({ foo: 'one', bar: '1' }, { searchPath: SEARCH_PATH_ONE }); + + await Promise.all([ + await rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_ONE }), Restaurant.create({ foo: 'one', bar: '2' }, { searchPath: SEARCH_PATH_ONE }) .then(rnt => rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_ONE })), Restaurant.create({ foo: 'two', bar: '1' }, { searchPath: SEARCH_PATH_TWO }) .then(rnt => rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_TWO })), Restaurant.create({ foo: 'two', bar: '2' }, { searchPath: SEARCH_PATH_TWO }) .then(rnt => rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_TWO })) - ]).then(() => Promise.all([ - Restaurant.findAll({ - where: { bar: 'x.1' }, - searchPath: SEARCH_PATH_ONE - }).then(restaurantsOne => { + ]); + + await Promise.all([ + (async () => { + const restaurantsOne = await Restaurant.findAll({ + where: { bar: 'x.1' }, + searchPath: SEARCH_PATH_ONE + }); + expect(restaurantsOne.length).to.equal(1); expect(restaurantsOne[0].foo).to.equal('one'); expect(restaurantsOne[0].bar).to.equal('x.1'); - }), - Restaurant.findAll({ - where: { bar: 'x.2' }, - searchPath: SEARCH_PATH_TWO - }).then(restaurantsTwo => { + })(), + (async () => { + const restaurantsTwo = await Restaurant.findAll({ + where: { bar: 'x.2' }, + searchPath: SEARCH_PATH_TWO + }); + expect(restaurantsTwo.length).to.equal(1); expect(restaurantsTwo[0].foo).to.equal('two'); expect(restaurantsTwo[0].bar).to.equal('x.2'); - }) - ])); + })() + ]); }); }); }); diff --git a/test/integration/model/sum.test.js b/test/integration/model/sum.test.js index 17dff1be2b2d..4f0aad682f24 100644 --- a/test/integration/model/sum.test.js +++ b/test/integration/model/sum.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.Payment = this.sequelize.define('Payment', { amount: DataTypes.DECIMAL, mood: { @@ -15,28 +15,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return this.Payment.bulkCreate([ - { amount: 5, mood: 'neutral' }, - { amount: -5, mood: 'neutral' }, - { amount: 10, mood: 'happy' }, - { amount: 90, mood: 'happy' } - ]); - }); + await this.sequelize.sync({ force: true }); + + await this.Payment.bulkCreate([ + { amount: 5, mood: 'neutral' }, + { amount: -5, mood: 'neutral' }, + { amount: 10, mood: 'happy' }, + { amount: 90, mood: 'happy' } + ]); }); describe('sum', () => { - it('should sum without rows', function() { - return expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).to.eventually.be.equal(0); + it('should sum without rows', async function() { + await expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).to.eventually.be.equal(0); }); - it('should sum when is 0', function() { - return expect(this.Payment.sum('amount', { where: { mood: 'neutral' } })).to.eventually.be.equal(0); + it('should sum when is 0', async function() { + await expect(this.Payment.sum('amount', { where: { mood: 'neutral' } })).to.eventually.be.equal(0); }); - it('should sum', function() { - return expect(this.Payment.sum('amount', { where: { mood: 'happy' } })).to.eventually.be.equal(100); + it('should sum', async function() { + await expect(this.Payment.sum('amount', { where: { mood: 'happy' } })).to.eventually.be.equal(100); }); }); }); diff --git a/test/integration/model/sync.test.js b/test/integration/model/sync.test.js index f6fbf7483564..630fd9baaa73 100644 --- a/test/integration/model/sync.test.js +++ b/test/integration/model/sync.test.js @@ -8,171 +8,168 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('sync', () => { - beforeEach(function() { + beforeEach(async function() { this.testSync = this.sequelize.define('testSync', { dummy: Sequelize.STRING }); - return this.testSync.drop(); + await this.testSync.drop(); }); - it('should remove a column if it exists in the databases schema but not the model', function() { + it('should remove a column if it exists in the databases schema but not the model', async function() { const User = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.INTEGER, badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } }); - return this.sequelize.sync() - .then(() => { - this.sequelize.define('testSync', { - name: Sequelize.STRING - }); - }) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => User.describe()) - .then(data => { - expect(data).to.not.have.ownProperty('age'); - expect(data).to.not.have.ownProperty('badge_number'); - expect(data).to.not.have.ownProperty('badgeNumber'); - expect(data).to.have.ownProperty('name'); - }); + await this.sequelize.sync(); + this.sequelize.define('testSync', { + name: Sequelize.STRING + }); + await this.sequelize.sync({ alter: true }); + const data = await User.describe(); + expect(data).to.not.have.ownProperty('age'); + expect(data).to.not.have.ownProperty('badge_number'); + expect(data).to.not.have.ownProperty('badgeNumber'); + expect(data).to.have.ownProperty('name'); }); - it('should add a column if it exists in the model but not the database', function() { + it('should add a column if it exists in the model but not the database', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.INTEGER, - height: { type: Sequelize.INTEGER, field: 'height_cm' } - })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('age'); - expect(data).to.have.ownProperty('height_cm'); - expect(data).not.to.have.ownProperty('height'); - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING, + age: Sequelize.INTEGER, + height: { type: Sequelize.INTEGER, field: 'height_cm' } + }); + + await this.sequelize.sync({ alter: true }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('age'); + expect(data).to.have.ownProperty('height_cm'); + expect(data).not.to.have.ownProperty('height'); }); - it('should not remove columns if drop is set to false in alter configuration', function() { + it('should not remove columns if drop is set to false in alter configuration', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.INTEGER }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING - })) - .then(() => this.sequelize.sync({ alter: { drop: false } })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('name'); - expect(data).to.have.ownProperty('age'); - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING + }); + + await this.sequelize.sync({ alter: { drop: false } }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('name'); + expect(data).to.have.ownProperty('age'); }); - it('should remove columns if drop is set to true in alter configuration', function() { + it('should remove columns if drop is set to true in alter configuration', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.INTEGER }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING - })) - .then(() => this.sequelize.sync({ alter: { drop: true } })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('name'); - expect(data).not.to.have.ownProperty('age'); - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING + }); + + await this.sequelize.sync({ alter: { drop: true } }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('name'); + expect(data).not.to.have.ownProperty('age'); }); - it('should alter a column using the correct column name (#9515)', function() { + it('should alter a column using the correct column name (#9515)', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING, - badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } - })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('badge_number'); - expect(data).not.to.have.ownProperty('badgeNumber'); - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING, + badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } + }); + + await this.sequelize.sync({ alter: true }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('badge_number'); + expect(data).not.to.have.ownProperty('badgeNumber'); }); - it('should change a column if it exists in the model but is different in the database', function() { + it('should change a column if it exists in the model but is different in the database', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.INTEGER }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.STRING - })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('age'); - expect(data.age.type).to.have.string('CHAR'); // CHARACTER VARYING, VARCHAR(n) - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING, + age: Sequelize.STRING + }); + + await this.sequelize.sync({ alter: true }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('age'); + expect(data.age.type).to.have.string('CHAR'); // CHARACTER VARYING, VARCHAR(n) }); - it('should not alter table if data type does not change', function() { + it('should not alter table if data type does not change', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.STRING }); - return this.sequelize.sync() - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.findOne()) - .then(data => { - expect(data.dataValues.name).to.eql('test'); - expect(data.dataValues.age).to.eql('1'); - }); + await this.sequelize.sync(); + await testSync.create({ name: 'test', age: '1' }); + await this.sequelize.sync({ alter: true }); + const data = await testSync.findOne(); + expect(data.dataValues.name).to.eql('test'); + expect(data.dataValues.age).to.eql('1'); }); - it('should properly create composite index without affecting individual fields', function() { + it('should properly create composite index without affecting individual fields', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.STRING }, { indexes: [{ unique: true, fields: ['name', 'age'] }] }); - return this.sequelize.sync() - .then(() => testSync.create({ name: 'test' })) - .then(() => testSync.create({ name: 'test2' })) - .then(() => testSync.create({ name: 'test3' })) - .then(() => testSync.create({ age: '1' })) - .then(() => testSync.create({ age: '2' })) - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(() => testSync.create({ name: 'test', age: '2' })) - .then(() => testSync.create({ name: 'test2', age: '2' })) - .then(() => testSync.create({ name: 'test3', age: '2' })) - .then(() => testSync.create({ name: 'test3', age: '1' })) - .then(data => { - expect(data.dataValues.name).to.eql('test3'); - expect(data.dataValues.age).to.eql('1'); - }); + await this.sequelize.sync(); + await testSync.create({ name: 'test' }); + await testSync.create({ name: 'test2' }); + await testSync.create({ name: 'test3' }); + await testSync.create({ age: '1' }); + await testSync.create({ age: '2' }); + await testSync.create({ name: 'test', age: '1' }); + await testSync.create({ name: 'test', age: '2' }); + await testSync.create({ name: 'test2', age: '2' }); + await testSync.create({ name: 'test3', age: '2' }); + const data = await testSync.create({ name: 'test3', age: '1' }); + expect(data.dataValues.name).to.eql('test3'); + expect(data.dataValues.age).to.eql('1'); }); - it('should properly create composite index that fails on constraint violation', function() { + it('should properly create composite index that fails on constraint violation', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.STRING }, { indexes: [{ unique: true, fields: ['name', 'age'] }] }); - return this.sequelize.sync() - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(data => expect(data).not.to.be.ok, error => expect(error).to.be.ok); + + try { + await this.sequelize.sync(); + await testSync.create({ name: 'test', age: '1' }); + const data = await testSync.create({ name: 'test', age: '1' }); + await expect(data).not.to.be.ok; + } catch (error) { + await expect(error).to.be.ok; + } }); - it('should properly alter tables when there are foreign keys', function() { + it('should properly alter tables when there are foreign keys', async function() { const foreignKeyTestSyncA = this.sequelize.define('foreignKeyTestSyncA', { dummy: Sequelize.STRING }); @@ -184,13 +181,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { foreignKeyTestSyncA.hasMany(foreignKeyTestSyncB); foreignKeyTestSyncB.belongsTo(foreignKeyTestSyncA); - return this.sequelize.sync({ alter: true }) - .then(() => this.sequelize.sync({ alter: true })); + await this.sequelize.sync({ alter: true }); + + await this.sequelize.sync({ alter: true }); }); describe('indexes', () => { describe('with alter:true', () => { - it('should not duplicate named indexes after multiple sync calls', function() { + it('should not duplicate named indexes after multiple sync calls', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING @@ -210,28 +208,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ sync: true }) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => this.sequelize.getQueryInterface().showIndex(User.getTableName())) - .then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(4); - } else { - expect(results).to.have.length(4 + 1); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.name === 'another_index_email_mobile')).to.have.length(1); - expect(results.filter(r => r.name === 'another_index_phone_mobile')).to.have.length(1); - expect(results.filter(r => r.name === 'another_index_email')).to.have.length(1); - expect(results.filter(r => r.name === 'another_index_mobile')).to.have.length(1); - }); + await User.sync({ sync: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(4); + } else { + expect(results).to.have.length(4 + 1); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.name === 'another_index_email_mobile')).to.have.length(1); + expect(results.filter(r => r.name === 'another_index_phone_mobile')).to.have.length(1); + expect(results.filter(r => r.name === 'another_index_email')).to.have.length(1); + expect(results.filter(r => r.name === 'another_index_mobile')).to.have.length(1); }); - it('should not duplicate unnamed indexes after multiple sync calls', function() { + it('should not duplicate unnamed indexes after multiple sync calls', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING @@ -251,24 +247,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ sync: true }) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => this.sequelize.getQueryInterface().showIndex(User.getTableName())) - .then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(4); - } else { - expect(results).to.have.length(4 + 1); - expect(results.filter(r => r.primary)).to.have.length(1); - } - }); + await User.sync({ sync: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(4); + } else { + expect(results).to.have.length(4 + 1); + expect(results.filter(r => r.primary)).to.have.length(1); + } }); }); - it('should create only one unique index for unique:true column', function() { + it('should create only one unique index for unique:true column', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -276,22 +270,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(1); - } else { - expect(results).to.have.length(2); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(1); + } else { + expect(results).to.have.length(2); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); }); - it('should create only one unique index for unique:true columns', function() { + it('should create only one unique index for unique:true columns', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -303,22 +295,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(2); - } else { - expect(results).to.have.length(3); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(2); - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(2); + } else { + expect(results).to.have.length(3); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(2); }); - it('should create only one unique index for unique:true columns taking care of options.indexes', function() { + it('should create only one unique index for unique:true columns taking care of options.indexes', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -334,23 +324,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(3); - } else { - expect(results).to.have.length(4); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(3); - expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(3); + } else { + expect(results).to.have.length(4); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(3); + expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); }); - it('should create only one unique index for unique:name column', function() { + it('should create only one unique index for unique:name column', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -358,27 +346,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(1); - } else { - expect(results).to.have.length(2); - expect(results.filter(r => r.primary)).to.have.length(1); - } + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(1); + } else { + expect(results).to.have.length(2); + expect(results.filter(r => r.primary)).to.have.length(1); + } - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); - if (!['postgres', 'sqlite'].includes(dialect)) { - // Postgres/SQLite doesn't support naming indexes in create table - expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); - } - }); + if (!['postgres', 'sqlite'].includes(dialect)) { + // Postgres/SQLite doesn't support naming indexes in create table + expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); + } }); - it('should create only one unique index for unique:name columns', function() { + it('should create only one unique index for unique:name columns', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -390,23 +376,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(1); - } else { - expect(results).to.have.length(2); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); - if (!['postgres', 'sqlite'].includes(dialect)) { - // Postgres/SQLite doesn't support naming indexes in create table - expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); - } - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(1); + } else { + expect(results).to.have.length(2); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); + if (!['postgres', 'sqlite'].includes(dialect)) { + // Postgres/SQLite doesn't support naming indexes in create table + expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); + } }); }); }); diff --git a/test/integration/model/update.test.js b/test/integration/model/update.test.js index 862f7ccae985..91fbb3f42fac 100644 --- a/test/integration/model/update.test.js +++ b/test/integration/model/update.test.js @@ -10,7 +10,7 @@ const _ = require('lodash'); describe(Support.getTestDialectTeaser('Model'), () => { describe('update', () => { - beforeEach(function() { + beforeEach(async function() { this.Account = this.sequelize.define('Account', { ownerId: { type: DataTypes.INTEGER, @@ -21,41 +21,42 @@ describe(Support.getTestDialectTeaser('Model'), () => { type: DataTypes.STRING } }); - return this.Account.sync({ force: true }); + await this.Account.sync({ force: true }); }); - it('should only update the passed fields', function() { - return this.Account - .create({ ownerId: 2 }) - .then(account => this.Account.update({ - name: Math.random().toString() - }, { - where: { - id: account.get('id') - } - })); + it('should only update the passed fields', async function() { + const account = await this.Account + .create({ ownerId: 2 }); + + await this.Account.update({ + name: Math.random().toString() + }, { + where: { + id: account.get('id') + } + }); }); describe('skips update query', () => { - it('if no data to update', function() { + it('if no data to update', async function() { const spy = sinon.spy(); - return this.Account.create({ ownerId: 3 }).then(() => { - return this.Account.update({ - unknownField: 'haha' - }, { - where: { - ownerId: 3 - }, - logging: spy - }); - }).then(result => { - expect(result[0]).to.equal(0); - expect(spy.called, 'Update query was issued when no data to update').to.be.false; + await this.Account.create({ ownerId: 3 }); + + const result = await this.Account.update({ + unknownField: 'haha' + }, { + where: { + ownerId: 3 + }, + logging: spy }); + + expect(result[0]).to.equal(0); + expect(spy.called, 'Update query was issued when no data to update').to.be.false; }); - it('skips when timestamps disabled', function() { + it('skips when timestamps disabled', async function() { const Model = this.sequelize.define('Model', { ownerId: { type: DataTypes.INTEGER, @@ -70,105 +71,94 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); const spy = sinon.spy(); - return Model.sync({ force: true }) - .then(() => Model.create({ ownerId: 3 })) - .then(() => { - return Model.update({ - unknownField: 'haha' - }, { - where: { - ownerId: 3 - }, - logging: spy - }); - }) - .then(result => { - expect(result[0]).to.equal(0); - expect(spy.called, 'Update query was issued when no data to update').to.be.false; - }); + await Model.sync({ force: true }); + await Model.create({ ownerId: 3 }); + + const result = await Model.update({ + unknownField: 'haha' + }, { + where: { + ownerId: 3 + }, + logging: spy + }); + + expect(result[0]).to.equal(0); + expect(spy.called, 'Update query was issued when no data to update').to.be.false; }); }); - it('changed should be false after reload', function() { - return this.Account.create({ ownerId: 2, name: 'foo' }) - .then(account => { - account.name = 'bar'; - expect(account.changed()[0]).to.equal('name'); - return account.reload(); - }) - .then(account => { - expect(account.changed()).to.equal(false); - }); + it('changed should be false after reload', async function() { + const account0 = await this.Account.create({ ownerId: 2, name: 'foo' }); + account0.name = 'bar'; + expect(account0.changed()[0]).to.equal('name'); + const account = await account0.reload(); + expect(account.changed()).to.equal(false); }); - it('should ignore undefined values without throwing not null validation', function() { + it('should ignore undefined values without throwing not null validation', async function() { const ownerId = 2; - return this.Account.create({ + + const account0 = await this.Account.create({ ownerId, name: Math.random().toString() - }).then(account => { - return this.Account.update({ - name: Math.random().toString(), - ownerId: undefined - }, { - where: { - id: account.get('id') - } - }); - }).then(() => { - return this.Account.findOne(); - }).then(account => { - expect(account.ownerId).to.be.equal(ownerId); }); + + await this.Account.update({ + name: Math.random().toString(), + ownerId: undefined + }, { + where: { + id: account0.get('id') + } + }); + + const account = await this.Account.findOne(); + expect(account.ownerId).to.be.equal(ownerId); }); if (_.get(current.dialect.supports, 'returnValues.returning')) { - it('should return the updated record', function() { - return this.Account.create({ ownerId: 2 }).then(account => { - return this.Account.update({ name: 'FooBar' }, { - where: { - id: account.get('id') - }, - returning: true - }).then(([, accounts]) => { - const firstAcc = accounts[0]; - expect(firstAcc.ownerId).to.be.equal(2); - expect(firstAcc.name).to.be.equal('FooBar'); - }); + it('should return the updated record', async function() { + const account = await this.Account.create({ ownerId: 2 }); + + const [, accounts] = await this.Account.update({ name: 'FooBar' }, { + where: { + id: account.get('id') + }, + returning: true }); + + const firstAcc = accounts[0]; + expect(firstAcc.ownerId).to.be.equal(2); + expect(firstAcc.name).to.be.equal('FooBar'); }); } if (current.dialect.supports['LIMIT ON UPDATE']) { - it('should only update one row', function() { - return this.Account.create({ + it('should only update one row', async function() { + await this.Account.create({ ownerId: 2, name: 'Account Name 1' - }) - .then(() => { - return this.Account.create({ - ownerId: 2, - name: 'Account Name 2' - }); - }) - .then(() => { - return this.Account.create({ - ownerId: 2, - name: 'Account Name 3' - }); - }) - .then(() => { - const options = { - where: { - ownerId: 2 - }, - limit: 1 - }; - return this.Account.update({ name: 'New Name' }, options); - }) - .then(account => { - expect(account[0]).to.equal(1); - }); + }); + + await this.Account.create({ + ownerId: 2, + name: 'Account Name 2' + }); + + await this.Account.create({ + ownerId: 2, + name: 'Account Name 3' + }); + + const options = { + where: { + ownerId: 2 + }, + limit: 1 + }; + const account = await this.Account.update({ name: 'New Name' }, options); + expect(account[0]).to.equal(1); }); } }); diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index 11d10d486ddf..fbbe6a8d8e89 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -22,7 +22,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.reset(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { username: DataTypes.STRING, foo: { @@ -54,62 +54,56 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); if (current.dialect.supports.upserts) { describe('upsert', () => { - it('works with upsert on id', function() { - return this.User.upsert({ id: 42, username: 'john' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ id: 42, username: 'doe' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findByPk(42); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); + it('works with upsert on id', async function() { + const created0 = await this.User.upsert({ id: 42, username: 'john' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const created = await this.User.upsert({ id: 42, username: 'doe' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + + const user = await this.User.findByPk(42); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('works with upsert on a composite key', function() { - return this.User.upsert({ foo: 'baz', bar: 19, username: 'john' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findOne({ where: { foo: 'baz', bar: 19 } }); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); + it('works with upsert on a composite key', async function() { + const created0 = await this.User.upsert({ foo: 'baz', bar: 19, username: 'john' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const created = await this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + + const user = await this.User.findOne({ where: { foo: 'baz', bar: 19 } }); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('should work with UUIDs wth default values', function() { + it('should work with UUIDs wth default values', async function() { const User = this.sequelize.define('User', { id: { primaryKey: true, @@ -124,12 +118,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.upsert({ name: 'John Doe' }); - }); + await User.sync({ force: true }); + + await User.upsert({ name: 'John Doe' }); }); - it('works with upsert on a composite primary key', function() { + it('works with upsert on a composite primary key', async function() { const User = this.sequelize.define('user', { a: { type: Sequelize.STRING, @@ -142,47 +136,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return Promise.all([ - // Create two users - User.upsert({ a: 'a', b: 'b', username: 'john' }), - User.upsert({ a: 'a', b: 'a', username: 'curt' }) - ]); - }).then(([created1, created2]) => { - if (dialect === 'sqlite') { - expect(created1).to.be.undefined; - expect(created2).to.be.undefined; - } else { - expect(created1).to.be.ok; - expect(created2).to.be.ok; - } - - this.clock.tick(1000); - // Update the first one - return User.upsert({ a: 'a', b: 'b', username: 'doe' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return User.findOne({ where: { a: 'a', b: 'b' } }); - }).then(user1 => { - expect(user1.createdAt).to.be.ok; - expect(user1.username).to.equal('doe'); - expect(user1.updatedAt).to.be.afterTime(user1.createdAt); - - return User.findOne({ where: { a: 'a', b: 'a' } }); - }).then(user2 => { - // The second one should not be updated - expect(user2.createdAt).to.be.ok; - expect(user2.username).to.equal('curt'); - expect(user2.updatedAt).to.equalTime(user2.createdAt); - }); + await User.sync({ force: true }); + + const [created1, created2] = await Promise.all([ + // Create two users + User.upsert({ a: 'a', b: 'b', username: 'john' }), + User.upsert({ a: 'a', b: 'a', username: 'curt' }) + ]); + + if (dialect === 'sqlite') { + expect(created1).to.be.undefined; + expect(created2).to.be.undefined; + } else { + expect(created1).to.be.ok; + expect(created2).to.be.ok; + } + + this.clock.tick(1000); + // Update the first one + const created = await User.upsert({ a: 'a', b: 'b', username: 'doe' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + + const user1 = await User.findOne({ where: { a: 'a', b: 'b' } }); + expect(user1.createdAt).to.be.ok; + expect(user1.username).to.equal('doe'); + expect(user1.updatedAt).to.be.afterTime(user1.createdAt); + + const user2 = await User.findOne({ where: { a: 'a', b: 'a' } }); + // The second one should not be updated + expect(user2.createdAt).to.be.ok; + expect(user2.username).to.equal('curt'); + expect(user2.updatedAt).to.equalTime(user2.createdAt); }); - it('supports validations', function() { + it('supports validations', async function() { const User = this.sequelize.define('user', { email: { type: Sequelize.STRING, @@ -192,10 +183,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return expect(User.upsert({ email: 'notanemail' })).to.eventually.be.rejectedWith(Sequelize.ValidationError); + await expect(User.upsert({ email: 'notanemail' })).to.eventually.be.rejectedWith(Sequelize.ValidationError); }); - it('supports skipping validations', function() { + it('supports skipping validations', async function() { const User = this.sequelize.define('user', { email: { type: Sequelize.STRING, @@ -207,169 +198,142 @@ describe(Support.getTestDialectTeaser('Model'), () => { const options = { validate: false }; - return User.sync({ force: true }) - .then(() => User.upsert({ id: 1, email: 'notanemail' }, options)) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - }); + await User.sync({ force: true }); + const created = await User.upsert({ id: 1, email: 'notanemail' }, options); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).to.be.ok; + } }); - it('works with BLOBs', function() { - return this.User.upsert({ id: 42, username: 'john', blob: Buffer.from('kaj') }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ id: 42, username: 'doe', blob: Buffer.from('andrea') }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findByPk(42); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.blob.toString()).to.equal('andrea'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); + it('works with BLOBs', async function() { + const created0 = await this.User.upsert({ id: 42, username: 'john', blob: Buffer.from('kaj') }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const created = await this.User.upsert({ id: 42, username: 'doe', blob: Buffer.from('andrea') }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + + const user = await this.User.findByPk(42); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.blob.toString()).to.equal('andrea'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('works with .field', function() { - return this.User.upsert({ id: 42, baz: 'foo' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - return this.User.upsert({ id: 42, baz: 'oof' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findByPk(42); - }).then(user => { - expect(user.baz).to.equal('oof'); - }); + it('works with .field', async function() { + const created0 = await this.User.upsert({ id: 42, baz: 'foo' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + + const created = await this.User.upsert({ id: 42, baz: 'oof' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + + const user = await this.User.findByPk(42); + expect(user.baz).to.equal('oof'); }); - it('works with primary key using .field', function() { - return this.ModelWithFieldPK.upsert({ userId: 42, foo: 'first' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.ModelWithFieldPK.upsert({ userId: 42, foo: 'second' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.ModelWithFieldPK.findOne({ where: { userId: 42 } }); - }).then(instance => { - expect(instance.foo).to.equal('second'); - }); + it('works with primary key using .field', async function() { + const created0 = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'first' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const created = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'second' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + + const instance = await this.ModelWithFieldPK.findOne({ where: { userId: 42 } }); + expect(instance.foo).to.equal('second'); }); - it('works with database functions', function() { - return this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1') }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - return this.User.findByPk(42); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.foo).to.equal('MIXEDCASE2'); - }); + it('works with database functions', async function() { + const created0 = await this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1') }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const created = await this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + const user = await this.User.findByPk(42); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.foo).to.equal('MIXEDCASE2'); }); - it('does not overwrite createdAt time on update', function() { - let originalCreatedAt; - let originalUpdatedAt; + it('does not overwrite createdAt time on update', async function() { const clock = sinon.useFakeTimers(); - return this.User.create({ id: 42, username: 'john' }).then(() => { - return this.User.findByPk(42); - }).then(user => { - originalCreatedAt = user.createdAt; - originalUpdatedAt = user.updatedAt; - clock.tick(5000); - return this.User.upsert({ id: 42, username: 'doe' }); - }).then(() => { - return this.User.findByPk(42); - }).then(user => { - expect(user.updatedAt).to.be.gt(originalUpdatedAt); - expect(user.createdAt).to.deep.equal(originalCreatedAt); - clock.restore(); - }); + await this.User.create({ id: 42, username: 'john' }); + const user0 = await this.User.findByPk(42); + const originalCreatedAt = user0.createdAt; + const originalUpdatedAt = user0.updatedAt; + clock.tick(5000); + await this.User.upsert({ id: 42, username: 'doe' }); + const user = await this.User.findByPk(42); + expect(user.updatedAt).to.be.gt(originalUpdatedAt); + expect(user.createdAt).to.deep.equal(originalCreatedAt); + clock.restore(); }); - it('does not update using default values', function() { - return this.User.create({ id: 42, username: 'john', baz: 'new baz value' }).then(() => { - return this.User.findByPk(42); - }).then(user => { - // 'username' should be 'john' since it was set - expect(user.username).to.equal('john'); - // 'baz' should be 'new baz value' since it was set - expect(user.baz).to.equal('new baz value'); - return this.User.upsert({ id: 42, username: 'doe' }); - }).then(() => { - return this.User.findByPk(42); - }).then(user => { - // 'username' was updated - expect(user.username).to.equal('doe'); - // 'baz' should still be 'new baz value' since it was not updated - expect(user.baz).to.equal('new baz value'); - }); + it('does not update using default values', async function() { + await this.User.create({ id: 42, username: 'john', baz: 'new baz value' }); + const user0 = await this.User.findByPk(42); + // 'username' should be 'john' since it was set + expect(user0.username).to.equal('john'); + // 'baz' should be 'new baz value' since it was set + expect(user0.baz).to.equal('new baz value'); + await this.User.upsert({ id: 42, username: 'doe' }); + const user = await this.User.findByPk(42); + // 'username' was updated + expect(user.username).to.equal('doe'); + // 'baz' should still be 'new baz value' since it was not updated + expect(user.baz).to.equal('new baz value'); }); - it('does not update when setting current values', function() { - return this.User.create({ id: 42, username: 'john' }).then(() => { - return this.User.findByPk(42); - }).then(user => { - return this.User.upsert({ id: user.id, username: user.username }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - // After set node-mysql flags = '-FOUND_ROWS' / foundRows=false - // result from upsert should be false when upsert a row to its current value - // https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html - expect(created).to.equal(false); - } - }); + it('does not update when setting current values', async function() { + await this.User.create({ id: 42, username: 'john' }); + const user = await this.User.findByPk(42); + const created = await this.User.upsert({ id: user.id, username: user.username }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + // After set node-mysql flags = '-FOUND_ROWS' / foundRows=false + // result from upsert should be false when upsert a row to its current value + // https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html + expect(created).to.equal(false); + } }); - it('works when two separate uniqueKeys are passed', function() { + it('works when two separate uniqueKeys are passed', async function() { const User = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -384,34 +348,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); const clock = sinon.useFakeTimers(); - return User.sync({ force: true }).then(() => { - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - clock.tick(1000); - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - clock.tick(1000); - return User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); - }) - .then(user => { - expect(user.createdAt).to.be.ok; - expect(user.city).to.equal('New City'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); - }); + await User.sync({ force: true }); + const created0 = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + clock.tick(1000); + const created = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + clock.tick(1000); + const user = await User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); + expect(user.createdAt).to.be.ok; + expect(user.city).to.equal('New City'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('works when indexes are created via indexes array', function() { + it('works when indexes are created via indexes array', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -426,31 +384,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { }] }); - return User.sync({ force: true }).then(() => { - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - return User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); - }) - .then(user => { - expect(user.createdAt).to.be.ok; - expect(user.city).to.equal('New City'); - }); - }); + await User.sync({ force: true }); + const created0 = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + const created = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + const user = await User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); + expect(user.createdAt).to.be.ok; + expect(user.city).to.equal('New City'); }); - it('works when composite indexes are created via indexes array', () => { + it('works when composite indexes are created via indexes array', async () => { const User = current.define('User', { name: DataTypes.STRING, address: DataTypes.STRING, @@ -462,32 +414,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { }] }); - return User.sync({ force: true }).then(() => { - return User.upsert({ name: 'user1', address: 'address', city: 'City' }) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - return User.upsert({ name: 'user1', address: 'address', city: 'New City' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - return User.findOne({ where: { name: 'user1', address: 'address' } }); - }) - .then(user => { - expect(user.createdAt).to.be.ok; - expect(user.city).to.equal('New City'); - }); - }); + await User.sync({ force: true }); + const created0 = await User.upsert({ name: 'user1', address: 'address', city: 'City' }); + if (dialect === 'sqlite') { + expect(created0).to.be.undefined; + } else { + expect(created0).to.be.ok; + } + const created = await User.upsert({ name: 'user1', address: 'address', city: 'New City' }); + if (dialect === 'sqlite') { + expect(created).to.be.undefined; + } else { + expect(created).not.to.be.ok; + } + const user = await User.findOne({ where: { name: 'user1', address: 'address' } }); + expect(user.createdAt).to.be.ok; + expect(user.city).to.equal('New City'); }); if (dialect === 'mssql') { - it('Should throw foreignKey violation for MERGE statement as ForeignKeyConstraintError', function() { + it('Should throw foreignKey violation for MERGE statement as ForeignKeyConstraintError', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -502,16 +448,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { username: DataTypes.STRING }); Posts.belongsTo(User, { foreignKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'user1' })) - .then(() => { - return expect(Posts.upsert({ title: 'Title', username: 'user2' })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'user1' }); + await expect(Posts.upsert({ title: 'Title', username: 'user2' })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); } if (dialect.match(/^postgres/)) { - it('works when deletedAt is Infinity and part of primary key', function() { + it('works when deletedAt is Infinity and part of primary key', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING, @@ -528,43 +472,41 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return User.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ name: 'user1' }), - User.create({ name: 'user2', deletedAt: Infinity }), - - // this record is soft deleted - User.create({ name: 'user3', deletedAt: -Infinity }) - ]).then(() => { - return User.upsert({ name: 'user1', address: 'address' }); - }).then(() => { - return User.findAll({ - where: { address: null } - }); - }).then(users => { - expect(users).to.have.lengthOf(2); - }); + await User.sync({ force: true }); + + await Promise.all([ + User.create({ name: 'user1' }), + User.create({ name: 'user2', deletedAt: Infinity }), + + // this record is soft deleted + User.create({ name: 'user3', deletedAt: -Infinity }) + ]); + + await User.upsert({ name: 'user1', address: 'address' }); + + const users = await User.findAll({ + where: { address: null } }); + + expect(users).to.have.lengthOf(2); }); } if (current.dialect.supports.returnValues) { describe('with returning option', () => { - it('works with upsert on id', function() { - return this.User.upsert({ id: 42, username: 'john' }, { returning: true }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('john'); - expect(created).to.be.true; - - return this.User.upsert({ id: 42, username: 'doe' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('doe'); - expect(created).to.be.false; - }); + it('works with upsert on id', async function() { + const [user0, created0] = await this.User.upsert({ id: 42, username: 'john' }, { returning: true }); + expect(user0.get('id')).to.equal(42); + expect(user0.get('username')).to.equal('john'); + expect(created0).to.be.true; + + const [user, created] = await this.User.upsert({ id: 42, username: 'doe' }, { returning: true }); + expect(user.get('id')).to.equal(42); + expect(user.get('username')).to.equal('doe'); + expect(created).to.be.false; }); - it('works for table with custom primary key field', function() { + it('works for table with custom primary key field', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, @@ -577,22 +519,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.upsert({ id: 42, username: 'john' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('john'); - expect(created).to.be.true; - - return User.upsert({ id: 42, username: 'doe' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('doe'); - expect(created).to.be.false; - }); + await User.sync({ force: true }); + const [user0, created0] = await User.upsert({ id: 42, username: 'john' }, { returning: true }); + expect(user0.get('id')).to.equal(42); + expect(user0.get('username')).to.equal('john'); + expect(created0).to.be.true; + + const [user, created] = await User.upsert({ id: 42, username: 'doe' }, { returning: true }); + expect(user.get('id')).to.equal(42); + expect(user.get('username')).to.equal('doe'); + expect(created).to.be.false; }); - it('works for non incrementing primaryKey', function() { + it('works for non incrementing primaryKey', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.STRING, @@ -604,19 +543,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.upsert({ id: 'surya', username: 'john' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal('surya'); - expect(user.get('username')).to.equal('john'); - expect(created).to.be.true; - - return User.upsert({ id: 'surya', username: 'doe' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal('surya'); - expect(user.get('username')).to.equal('doe'); - expect(created).to.be.false; - }); + await User.sync({ force: true }); + const [user0, created0] = await User.upsert({ id: 'surya', username: 'john' }, { returning: true }); + expect(user0.get('id')).to.equal('surya'); + expect(user0.get('username')).to.equal('john'); + expect(created0).to.be.true; + + const [user, created] = await User.upsert({ id: 'surya', username: 'doe' }, { returning: true }); + expect(user.get('id')).to.equal('surya'); + expect(user.get('username')).to.equal('doe'); + expect(created).to.be.false; }); }); } From e34efd477d051af6796c277406be26bb1d2c2f23 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Thu, 21 May 2020 23:39:46 -0500 Subject: [PATCH 156/414] test: asyncify integration/*.js (#12286) --- test/integration/cls.test.js | 51 +- test/integration/configuration.test.js | 124 +- test/integration/data-types.test.js | 636 ++++---- test/integration/error.test.js | 148 +- test/integration/include.test.js | 1278 ++++++++-------- test/integration/instance.test.js | 772 +++++----- test/integration/instance.validations.test.js | 457 +++--- test/integration/json.test.js | 350 ++--- test/integration/model.test.js | 473 +++--- test/integration/replication.test.js | 32 +- test/integration/schema.test.js | 42 +- test/integration/sequelize.test.js | 1316 ++++++++--------- .../integration/sequelize.transaction.test.js | 210 ++- test/integration/support.js | 4 +- test/integration/timezone.test.js | 57 +- test/integration/transaction.test.js | 936 ++++++------ test/integration/trigger.test.js | 70 +- test/integration/utils.test.js | 58 +- test/integration/vectors.test.js | 19 +- 19 files changed, 3338 insertions(+), 3695 deletions(-) diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js index 9372cefe0d81..2a96f4969a0d 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -29,8 +29,8 @@ if (current.dialect.supports.transactions) { }); describe('context', () => { - it('does not use continuation storage on manually managed transactions', function() { - return Sequelize._clsRun(async () => { + it('does not use continuation storage on manually managed transactions', async function() { + await Sequelize._clsRun(async () => { const transaction = await this.sequelize.transaction(); expect(this.ns.get('transaction')).not.to.be.ok; await transaction.rollback(); @@ -52,14 +52,13 @@ if (current.dialect.supports.transactions) { expect(t1id).not.to.equal(t2id); }); - it('supports nested promise chains', function() { - return this.sequelize.transaction(() => { + it('supports nested promise chains', async function() { + await this.sequelize.transaction(async () => { const tid = this.ns.get('transaction').id; - return this.User.findAll().then(() => { - expect(this.ns.get('transaction').id).to.be.ok; - expect(this.ns.get('transaction').id).to.equal(tid); - }); + await this.User.findAll(); + expect(this.ns.get('transaction').id).to.be.ok; + expect(this.ns.get('transaction').id).to.equal(tid); }); }); @@ -100,8 +99,8 @@ if (current.dialect.supports.transactions) { expect(this.ns.get('transaction')).not.to.be.ok; }); - it('does not leak outside findOrCreate', function() { - return this.User.findOrCreate({ + it('does not leak outside findOrCreate', async function() { + await this.User.findOrCreate({ where: { name: 'Kafka' }, @@ -110,26 +109,25 @@ if (current.dialect.supports.transactions) { throw new Error('The transaction was not properly assigned'); } } - }).then(() => { - return this.User.findAll(); }); + + await this.User.findAll(); }); }); describe('sequelize.query integration', () => { - it('automagically uses the transaction in all calls', function() { - return this.sequelize.transaction(() => { - return this.User.create({ name: 'bob' }).then(() => { - return Promise.all([ - expect(this.User.findAll({ transaction: null })).to.eventually.have.length(0), - expect(this.User.findAll({})).to.eventually.have.length(1) - ]); - }); + it('automagically uses the transaction in all calls', async function() { + await this.sequelize.transaction(async () => { + await this.User.create({ name: 'bob' }); + return Promise.all([ + expect(this.User.findAll({ transaction: null })).to.eventually.have.length(0), + expect(this.User.findAll({})).to.eventually.have.length(1) + ]); }); }); - it('automagically uses the transaction in all calls with async/await', function() { - return this.sequelize.transaction(async () => { + it('automagically uses the transaction in all calls with async/await', async function() { + await this.sequelize.transaction(async () => { await this.User.create({ name: 'bob' }); expect(await this.User.findAll({ transaction: null })).to.have.length(0); expect(await this.User.findAll({})).to.have.length(1); @@ -141,10 +139,11 @@ if (current.dialect.supports.transactions) { expect(Sequelize._cls).to.equal(this.ns); }); - it('promises returned by sequelize.query are correctly patched', function() { - return this.sequelize.transaction(t => - this.sequelize.query('select 1', { type: Sequelize.QueryTypes.SELECT }) - .then(() => expect(this.ns.get('transaction')).to.equal(t)) + it('promises returned by sequelize.query are correctly patched', async function() { + await this.sequelize.transaction(async t => { + await this.sequelize.query('select 1', { type: Sequelize.QueryTypes.SELECT }); + return expect(this.ns.get('transaction')).to.equal(t); + } ); }); }); diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js index fa315f32bbdb..aa71c77920e2 100644 --- a/test/integration/configuration.test.js +++ b/test/integration/configuration.test.js @@ -17,7 +17,7 @@ if (dialect === 'sqlite') { describe(Support.getTestDialectTeaser('Configuration'), () => { describe('Connections problems should fail with a nice message', () => { - it('when we don\'t have the correct server details', () => { + it('when we don\'t have the correct server details', async () => { const options = { logging: false, host: '0.0.0.1', @@ -42,10 +42,10 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { } const seq = new Sequelize(...constructorArgs); - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(...willBeRejectedWithArgs); + await expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(...willBeRejectedWithArgs); }); - it('when we don\'t have the correct login information', () => { + it('when we don\'t have the correct login information', async () => { if (dialect === 'mssql') { // NOTE: Travis seems to be having trouble with this test against the // AWS instance. Works perfectly fine on a local setup. @@ -56,9 +56,10 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { const seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', { logging: false, host: config[dialect].host, port: 1, dialect }); if (dialect === 'sqlite') { // SQLite doesn't require authentication and `select 1 as hello` is a valid query, so this should be fulfilled not rejected for it. - return expect(seq.query('select 1 as hello')).to.eventually.be.fulfilled; + await expect(seq.query('select 1 as hello')).to.eventually.be.fulfilled; + } else { + await expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionRefusedError, 'connect ECONNREFUSED'); } - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionRefusedError, 'connect ECONNREFUSED'); }); it('when we don\'t have a valid dialect.', () => { @@ -70,7 +71,7 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { describe('Instantiation with arguments', () => { if (dialect === 'sqlite') { - it('should respect READONLY / READWRITE connection modes', () => { + it('should respect READONLY / READWRITE connection modes', async () => { const p = path.join(__dirname, '../tmp', 'foo.sqlite'); const createTableFoo = 'CREATE TABLE foo (faz TEXT);'; const createTableBar = 'CREATE TABLE bar (baz TEXT);'; @@ -79,65 +80,62 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { return promisify(fs.access)(p, fs.R_OK | fs.W_OK); }; - return promisify(fs.unlink)(p) - .catch(err => { + try { + try { + await promisify(fs.unlink)(p); + } catch (err) { expect(err.code).to.equal('ENOENT'); - }) - .then(() => { - const sequelizeReadOnly = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READONLY - } - }); - const sequelizeReadWrite = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READWRITE - } - }); - - expect(sequelizeReadOnly.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READONLY); - expect(sequelizeReadWrite.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READWRITE); - - return Promise.all([ - sequelizeReadOnly.query(createTableFoo) - .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file'), - sequelizeReadWrite.query(createTableFoo) - .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file') - ]); - }) - .then(() => { - // By default, sqlite creates a connection that's READWRITE | CREATE - const sequelize = new Sequelize('sqlite://foo', { - storage: p - }); - return sequelize.query(createTableFoo); - }) - .then(testAccess) - .then(() => { - const sequelizeReadOnly = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READONLY - } - }); - const sequelizeReadWrite = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READWRITE - } - }); - - return Promise.all([ - sequelizeReadOnly.query(createTableBar) - .should.be.rejectedWith(Error, 'SQLITE_READONLY: attempt to write a readonly database'), - sequelizeReadWrite.query(createTableBar) - ]); - }) - .finally(() => { - return promisify(fs.unlink)(p); + } + + const sequelizeReadOnly0 = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READONLY + } + }); + const sequelizeReadWrite0 = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READWRITE + } }); + + expect(sequelizeReadOnly0.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READONLY); + expect(sequelizeReadWrite0.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READWRITE); + + await Promise.all([ + sequelizeReadOnly0.query(createTableFoo) + .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file'), + sequelizeReadWrite0.query(createTableFoo) + .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file') + ]); + + // By default, sqlite creates a connection that's READWRITE | CREATE + const sequelize = new Sequelize('sqlite://foo', { + storage: p + }); + await testAccess(await sequelize.query(createTableFoo)); + const sequelizeReadOnly = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READONLY + } + }); + const sequelizeReadWrite = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READWRITE + } + }); + + await Promise.all([ + sequelizeReadOnly.query(createTableBar) + .should.be.rejectedWith(Error, 'SQLITE_READONLY: attempt to write a readonly database'), + sequelizeReadWrite.query(createTableBar) + ]); + } finally { + await promisify(fs.unlink)(p); + } }); } }); diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js index 10839de8c94c..b18aa58d4072 100644 --- a/test/integration/data-types.test.js +++ b/test/integration/data-types.test.js @@ -21,7 +21,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { this.sequelize.connectionManager.refreshTypeParser(DataTypes[dialect]); // Reload custom parsers }); - it('allows me to return values from a custom parse function', () => { + it('allows me to return values from a custom parse function', async () => { const parse = Sequelize.DATE.parse = sinon.spy(value => { return moment(value, 'YYYY-MM-DD HH:mm:ss'); }); @@ -41,23 +41,23 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { timestamps: false }); - return current.sync({ force: true }).then(() => { - return User.create({ - dateField: moment('2011 10 31', 'YYYY MM DD') - }); - }).then(() => { - return User.findAll().then(obj => obj[0]); - }).then(user => { - expect(parse).to.have.been.called; - expect(stringify).to.have.been.called; + await current.sync({ force: true }); - expect(moment.isMoment(user.dateField)).to.be.ok; - - delete Sequelize.DATE.parse; + await User.create({ + dateField: moment('2011 10 31', 'YYYY MM DD') }); + + const obj = await User.findAll(); + const user = obj[0]; + expect(parse).to.have.been.called; + expect(stringify).to.have.been.called; + + expect(moment.isMoment(user.dateField)).to.be.ok; + + delete Sequelize.DATE.parse; }); - const testSuccess = function(Type, value, options) { + const testSuccess = async function(Type, value, options) { const parse = Type.constructor.parse = sinon.spy(value => { return value; }); @@ -78,29 +78,27 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { timestamps: false }); - return current.sync({ force: true }).then(() => { + await current.sync({ force: true }); - current.refreshTypes(); - - return User.create({ - field: value - }); - }).then(() => { - return User.findAll().then(obj => obj[0]); - }).then(() => { - expect(parse).to.have.been.called; - if (options && options.useBindParam) { - expect(bindParam).to.have.been.called; - } else { - expect(stringify).to.have.been.called; - } + current.refreshTypes(); - delete Type.constructor.parse; - delete Type.constructor.prototype.stringify; - if (options && options.useBindParam) { - delete Type.constructor.prototype.bindParam; - } + await User.create({ + field: value }); + + await User.findAll(); + expect(parse).to.have.been.called; + if (options && options.useBindParam) { + expect(bindParam).to.have.been.called; + } else { + expect(stringify).to.have.been.called; + } + + delete Type.constructor.parse; + delete Type.constructor.prototype.stringify; + if (options && options.useBindParam) { + delete Type.constructor.prototype.bindParam; + } }; const testFailure = function(Type) { @@ -114,229 +112,231 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { }; if (current.dialect.supports.JSON) { - it('calls parse and stringify for JSON', () => { + it('calls parse and stringify for JSON', async () => { const Type = new Sequelize.JSON(); - return testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); + await testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); }); } if (current.dialect.supports.JSONB) { - it('calls parse and stringify for JSONB', () => { + it('calls parse and stringify for JSONB', async () => { const Type = new Sequelize.JSONB(); - return testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); + await testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); }); } if (current.dialect.supports.HSTORE) { - it('calls parse and bindParam for HSTORE', () => { + it('calls parse and bindParam for HSTORE', async () => { const Type = new Sequelize.HSTORE(); - return testSuccess(Type, { test: 42, nested: false }, { useBindParam: true }); + await testSuccess(Type, { test: 42, nested: false }, { useBindParam: true }); }); } if (current.dialect.supports.RANGE) { - it('calls parse and bindParam for RANGE', () => { + it('calls parse and bindParam for RANGE', async () => { const Type = new Sequelize.RANGE(new Sequelize.INTEGER()); - return testSuccess(Type, [1, 2], { useBindParam: true }); + await testSuccess(Type, [1, 2], { useBindParam: true }); }); } - it('calls parse and stringify for DATE', () => { + it('calls parse and stringify for DATE', async () => { const Type = new Sequelize.DATE(); - return testSuccess(Type, new Date()); + await testSuccess(Type, new Date()); }); - it('calls parse and stringify for DATEONLY', () => { + it('calls parse and stringify for DATEONLY', async () => { const Type = new Sequelize.DATEONLY(); - return testSuccess(Type, moment(new Date()).format('YYYY-MM-DD')); + await testSuccess(Type, moment(new Date()).format('YYYY-MM-DD')); }); - it('calls parse and stringify for TIME', () => { + it('calls parse and stringify for TIME', async () => { const Type = new Sequelize.TIME(); - return testSuccess(Type, moment(new Date()).format('HH:mm:ss')); + await testSuccess(Type, moment(new Date()).format('HH:mm:ss')); }); - it('calls parse and stringify for BLOB', () => { + it('calls parse and stringify for BLOB', async () => { const Type = new Sequelize.BLOB(); - return testSuccess(Type, 'foobar', { useBindParam: true }); + await testSuccess(Type, 'foobar', { useBindParam: true }); }); - it('calls parse and stringify for CHAR', () => { + it('calls parse and stringify for CHAR', async () => { const Type = new Sequelize.CHAR(); - return testSuccess(Type, 'foobar'); + await testSuccess(Type, 'foobar'); }); - it('calls parse and stringify/bindParam for STRING', () => { + it('calls parse and stringify/bindParam for STRING', async () => { const Type = new Sequelize.STRING(); // mssql has a _bindParam function that checks if STRING was created with // the boolean param (if so it outputs a Buffer bind param). This override // isn't needed for other dialects if (dialect === 'mssql') { - return testSuccess(Type, 'foobar', { useBindParam: true }); + await testSuccess(Type, 'foobar', { useBindParam: true }); + } else { + await testSuccess(Type, 'foobar'); } - return testSuccess(Type, 'foobar'); }); - it('calls parse and stringify for TEXT', () => { + it('calls parse and stringify for TEXT', async () => { const Type = new Sequelize.TEXT(); if (dialect === 'mssql') { // Text uses nvarchar, same type as string testFailure(Type); } else { - return testSuccess(Type, 'foobar'); + await testSuccess(Type, 'foobar'); } }); - it('calls parse and stringify for BOOLEAN', () => { + it('calls parse and stringify for BOOLEAN', async () => { const Type = new Sequelize.BOOLEAN(); - return testSuccess(Type, true); + await testSuccess(Type, true); }); - it('calls parse and stringify for INTEGER', () => { + it('calls parse and stringify for INTEGER', async () => { const Type = new Sequelize.INTEGER(); - return testSuccess(Type, 1); + await testSuccess(Type, 1); }); - it('calls parse and stringify for DECIMAL', () => { + it('calls parse and stringify for DECIMAL', async () => { const Type = new Sequelize.DECIMAL(); - return testSuccess(Type, 1.5); + await testSuccess(Type, 1.5); }); - it('calls parse and stringify for BIGINT', () => { + it('calls parse and stringify for BIGINT', async () => { const Type = new Sequelize.BIGINT(); if (dialect === 'mssql') { // Same type as integer testFailure(Type); } else { - return testSuccess(Type, 1); + await testSuccess(Type, 1); } }); - it('should handle JS BigInt type', function() { + it('should handle JS BigInt type', async function() { const User = this.sequelize.define('user', { age: Sequelize.BIGINT }); const age = BigInt(Number.MAX_SAFE_INTEGER) * 2n; - return User.sync({ force: true }).then(() => { - return User.create({ age }); - }).then(user => { - expect(BigInt(user.age).toString()).to.equal(age.toString()); - return User.findAll({ - where: { age } - }); - }).then(users => { - expect(users).to.have.lengthOf(1); - expect(BigInt(users[0].age).toString()).to.equal(age.toString()); + await User.sync({ force: true }); + const user = await User.create({ age }); + expect(BigInt(user.age).toString()).to.equal(age.toString()); + + const users = await User.findAll({ + where: { age } }); + + expect(users).to.have.lengthOf(1); + expect(BigInt(users[0].age).toString()).to.equal(age.toString()); }); if (dialect === 'mysql') { - it('should handle TINYINT booleans', function() { + it('should handle TINYINT booleans', async function() { const User = this.sequelize.define('user', { id: { type: Sequelize.TINYINT, primaryKey: true }, isRegistered: Sequelize.TINYINT }); - return User.sync({ force: true }).then(() => { - return User.create({ id: 1, isRegistered: true }); - }).then(registeredUser => { - expect(registeredUser.isRegistered).to.equal(true); - return User.findOne({ - where: { - id: 1, - isRegistered: true - } - }); - }).then(registeredUser => { - expect(registeredUser).to.be.ok; - expect(registeredUser.isRegistered).to.equal(1); - - return User.create({ id: 2, isRegistered: false }); - }).then(unregisteredUser => { - expect(unregisteredUser.isRegistered).to.equal(false); - return User.findOne({ - where: { - id: 2, - isRegistered: false - } - }); - }).then(unregisteredUser => { - expect(unregisteredUser).to.be.ok; - expect(unregisteredUser.isRegistered).to.equal(0); + await User.sync({ force: true }); + const registeredUser0 = await User.create({ id: 1, isRegistered: true }); + expect(registeredUser0.isRegistered).to.equal(true); + + const registeredUser = await User.findOne({ + where: { + id: 1, + isRegistered: true + } }); + + expect(registeredUser).to.be.ok; + expect(registeredUser.isRegistered).to.equal(1); + + const unregisteredUser0 = await User.create({ id: 2, isRegistered: false }); + expect(unregisteredUser0.isRegistered).to.equal(false); + + const unregisteredUser = await User.findOne({ + where: { + id: 2, + isRegistered: false + } + }); + + expect(unregisteredUser).to.be.ok; + expect(unregisteredUser.isRegistered).to.equal(0); }); } - it('calls parse and bindParam for DOUBLE', () => { + it('calls parse and bindParam for DOUBLE', async () => { const Type = new Sequelize.DOUBLE(); - return testSuccess(Type, 1.5, { useBindParam: true }); + await testSuccess(Type, 1.5, { useBindParam: true }); }); - it('calls parse and bindParam for FLOAT', () => { + it('calls parse and bindParam for FLOAT', async () => { const Type = new Sequelize.FLOAT(); if (dialect === 'postgres') { // Postgres doesn't have float, maps to either decimal or double testFailure(Type); } else { - return testSuccess(Type, 1.5, { useBindParam: true }); + await testSuccess(Type, 1.5, { useBindParam: true }); } }); - it('calls parse and bindParam for REAL', () => { + it('calls parse and bindParam for REAL', async () => { const Type = new Sequelize.REAL(); - return testSuccess(Type, 1.5, { useBindParam: true }); + await testSuccess(Type, 1.5, { useBindParam: true }); }); - it('calls parse and stringify for UUID', () => { + it('calls parse and stringify for UUID', async () => { const Type = new Sequelize.UUID(); // there is no dialect.supports.UUID yet if (['postgres', 'sqlite'].includes(dialect)) { - return testSuccess(Type, uuid.v4()); + await testSuccess(Type, uuid.v4()); + } else { + // No native uuid type + testFailure(Type); } - // No native uuid type - testFailure(Type); }); - it('calls parse and stringify for CIDR', () => { + it('calls parse and stringify for CIDR', async () => { const Type = new Sequelize.CIDR(); if (['postgres'].includes(dialect)) { - return testSuccess(Type, '10.1.2.3/32'); + await testSuccess(Type, '10.1.2.3/32'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for INET', () => { + it('calls parse and stringify for INET', async () => { const Type = new Sequelize.INET(); if (['postgres'].includes(dialect)) { - return testSuccess(Type, '127.0.0.1'); + await testSuccess(Type, '127.0.0.1'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for CITEXT', () => { + it('calls parse and stringify for CITEXT', async () => { const Type = new Sequelize.CITEXT(); if (dialect === 'sqlite') { @@ -345,38 +345,41 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { } if (dialect === 'postgres') { - return testSuccess(Type, 'foobar'); + await testSuccess(Type, 'foobar'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for MACADDR', () => { + it('calls parse and stringify for MACADDR', async () => { const Type = new Sequelize.MACADDR(); if (['postgres'].includes(dialect)) { - return testSuccess(Type, '01:23:45:67:89:ab'); + await testSuccess(Type, '01:23:45:67:89:ab'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for ENUM', () => { + it('calls parse and stringify for ENUM', async () => { const Type = new Sequelize.ENUM('hat', 'cat'); if (['postgres'].includes(dialect)) { - return testSuccess(Type, 'hat'); + await testSuccess(Type, 'hat'); + } else { + testFailure(Type); } - testFailure(Type); }); if (current.dialect.supports.GEOMETRY) { - it('calls parse and bindParam for GEOMETRY', () => { + it('calls parse and bindParam for GEOMETRY', async () => { const Type = new Sequelize.GEOMETRY(); - return testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }, { useBindParam: true }); + await testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }, { useBindParam: true }); }); - it('should parse an empty GEOMETRY field', () => { + it('should parse an empty GEOMETRY field', async () => { const Type = new Sequelize.GEOMETRY(); // MySQL 5.7 or above doesn't support POINT EMPTY @@ -384,7 +387,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { return; } - return new Promise((resolve, reject) => { + const runTests = await new Promise((resolve, reject) => { if (/^postgres/.test(dialect)) { current.query('SELECT PostGIS_Lib_Version();') .then(result => { @@ -397,37 +400,36 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { } else { resolve(true); } - }).then(runTests => { - if (current.dialect.supports.GEOMETRY && runTests) { - current.refreshTypes(); - - const User = current.define('user', { field: Type }, { timestamps: false }); - const point = { type: 'Point', coordinates: [] }; - - return current.sync({ force: true }).then(() => { - return User.create({ - //insert a empty GEOMETRY type - field: point - }); - }).then(() => { - //This case throw unhandled exception - return User.findAll(); - }).then(users =>{ - if (dialect === 'mysql' || dialect === 'mariadb') { - // MySQL will return NULL, because they lack EMPTY geometry data support. - expect(users[0].field).to.be.eql(null); - } else if (dialect === 'postgres' || dialect === 'postgres-native') { - //Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996 - expect(users[0].field).to.be.deep.eql({ type: 'Point', coordinates: [0, 0] }); - } else { - expect(users[0].field).to.be.deep.eql(point); - } - }); - } }); + + if (current.dialect.supports.GEOMETRY && runTests) { + current.refreshTypes(); + + const User = current.define('user', { field: Type }, { timestamps: false }); + const point = { type: 'Point', coordinates: [] }; + + await current.sync({ force: true }); + + await User.create({ + //insert a empty GEOMETRY type + field: point + }); + + //This case throw unhandled exception + const users = await User.findAll(); + if (dialect === 'mysql' || dialect === 'mariadb') { + // MySQL will return NULL, because they lack EMPTY geometry data support. + expect(users[0].field).to.be.eql(null); + } else if (dialect === 'postgres' || dialect === 'postgres-native') { + //Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996 + expect(users[0].field).to.be.deep.eql({ type: 'Point', coordinates: [0, 0] }); + } else { + expect(users[0].field).to.be.deep.eql(point); + } + } }); - it('should parse null GEOMETRY field', () => { + it('should parse null GEOMETRY field', async () => { const Type = new Sequelize.GEOMETRY(); current.refreshTypes(); @@ -435,48 +437,46 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const User = current.define('user', { field: Type }, { timestamps: false }); const point = null; - return current.sync({ force: true }).then(() => { - return User.create({ - // insert a null GEOMETRY type - field: point - }); - }).then(() => { - //This case throw unhandled exception - return User.findAll(); - }).then(users =>{ - expect(users[0].field).to.be.eql(null); + await current.sync({ force: true }); + + await User.create({ + // insert a null GEOMETRY type + field: point }); + + //This case throw unhandled exception + const users = await User.findAll(); + expect(users[0].field).to.be.eql(null); }); } if (dialect === 'postgres' || dialect === 'sqlite') { // postgres actively supports IEEE floating point literals, and sqlite doesn't care what we throw at it - it('should store and parse IEEE floating point literals (NaN and Infinity)', function() { + it('should store and parse IEEE floating point literals (NaN and Infinity)', async function() { const Model = this.sequelize.define('model', { float: Sequelize.FLOAT, double: Sequelize.DOUBLE, real: Sequelize.REAL }); - return Model.sync({ force: true }).then(() => { - return Model.create({ - id: 1, - float: NaN, - double: Infinity, - real: -Infinity - }); - }).then(() => { - return Model.findOne({ where: { id: 1 } }); - }).then(user => { - expect(user.get('float')).to.be.NaN; - expect(user.get('double')).to.eq(Infinity); - expect(user.get('real')).to.eq(-Infinity); + await Model.sync({ force: true }); + + await Model.create({ + id: 1, + float: NaN, + double: Infinity, + real: -Infinity }); + + const user = await Model.findOne({ where: { id: 1 } }); + expect(user.get('float')).to.be.NaN; + expect(user.get('double')).to.eq(Infinity); + expect(user.get('real')).to.eq(-Infinity); }); } if (dialect === 'postgres' || dialect === 'mysql') { - it('should parse DECIMAL as string', function() { + it('should parse DECIMAL as string', async function() { const Model = this.sequelize.define('model', { decimal: Sequelize.DECIMAL, decimalPre: Sequelize.DECIMAL(10, 4), @@ -494,31 +494,28 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { decimalWithFloatParser: 0.12345678 }; - return Model.sync({ force: true }).then(() => { - return Model.create(sampleData); - }).then(() => { - return Model.findByPk(1); - }).then(user => { - /** - * MYSQL default precision is 10 and scale is 0 - * Thus test case below will return number without any fraction values - */ - if (dialect === 'mysql') { - expect(user.get('decimal')).to.be.eql('12345678'); - } else { - expect(user.get('decimal')).to.be.eql('12345678.12345678'); - } + await Model.sync({ force: true }); + await Model.create(sampleData); + const user = await Model.findByPk(1); + /** + * MYSQL default precision is 10 and scale is 0 + * Thus test case below will return number without any fraction values + */ + if (dialect === 'mysql') { + expect(user.get('decimal')).to.be.eql('12345678'); + } else { + expect(user.get('decimal')).to.be.eql('12345678.12345678'); + } - expect(user.get('decimalPre')).to.be.eql('123456.1234'); - expect(user.get('decimalWithParser')).to.be.eql('12345678123456781.123456781234567'); - expect(user.get('decimalWithIntParser')).to.be.eql('1.2340'); - expect(user.get('decimalWithFloatParser')).to.be.eql('0.12345678'); - }); + expect(user.get('decimalPre')).to.be.eql('123456.1234'); + expect(user.get('decimalWithParser')).to.be.eql('12345678123456781.123456781234567'); + expect(user.get('decimalWithIntParser')).to.be.eql('1.2340'); + expect(user.get('decimalWithFloatParser')).to.be.eql('0.12345678'); }); } if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { - it('should parse BIGINT as string', function() { + it('should parse BIGINT as string', async function() { const Model = this.sequelize.define('model', { jewelPurity: Sequelize.BIGINT }); @@ -528,19 +525,16 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { jewelPurity: '9223372036854775807' }; - return Model.sync({ force: true }).then(() => { - return Model.create(sampleData); - }).then(() => { - return Model.findByPk(1); - }).then(user => { - expect(user.get('jewelPurity')).to.be.eql(sampleData.jewelPurity); - expect(user.get('jewelPurity')).to.be.string; - }); + await Model.sync({ force: true }); + await Model.create(sampleData); + const user = await Model.findByPk(1); + expect(user.get('jewelPurity')).to.be.eql(sampleData.jewelPurity); + expect(user.get('jewelPurity')).to.be.string; }); } if (dialect === 'postgres') { - it('should return Int4 range properly #5747', function() { + it('should return Int4 range properly #5747', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.INTEGER), @@ -549,19 +543,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { } }); - return Model.sync({ force: true }) - .then(() => Model.create({ interval: [1, 4] }) ) - .then(() => Model.findAll() ) - .then(([m]) => { - expect(m.interval[0].value).to.be.eql(1); - expect(m.interval[1].value).to.be.eql(4); - }); + await Model.sync({ force: true }); + await Model.create({ interval: [1, 4] }); + const [m] = await Model.findAll(); + expect(m.interval[0].value).to.be.eql(1); + expect(m.interval[1].value).to.be.eql(4); }); } if (current.dialect.supports.RANGE) { - it('should allow date ranges to be generated with default bounds inclusion #8176', function() { + it('should allow date ranges to be generated with default bounds inclusion #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -573,19 +565,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDate2 = new Date(testDate1.getTime() + 10000); const testDateRange = [testDate1, testDate2]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne()) - .then(m => { - expect(m).to.exist; - expect(m.interval[0].value).to.be.eql(testDate1); - expect(m.interval[1].value).to.be.eql(testDate2); - expect(m.interval[0].inclusive).to.be.eql(true); - expect(m.interval[1].inclusive).to.be.eql(false); - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + const m = await Model.findOne(); + expect(m).to.exist; + expect(m.interval[0].value).to.be.eql(testDate1); + expect(m.interval[1].value).to.be.eql(testDate2); + expect(m.interval[0].inclusive).to.be.eql(true); + expect(m.interval[1].inclusive).to.be.eql(false); }); - it('should allow date ranges to be generated using a single range expression to define bounds inclusion #8176', function() { + it('should allow date ranges to be generated using a single range expression to define bounds inclusion #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -597,19 +587,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDate2 = new Date(testDate1.getTime() + 10000); const testDateRange = [{ value: testDate1, inclusive: false }, { value: testDate2, inclusive: true }]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne()) - .then(m => { - expect(m).to.exist; - expect(m.interval[0].value).to.be.eql(testDate1); - expect(m.interval[1].value).to.be.eql(testDate2); - expect(m.interval[0].inclusive).to.be.eql(false); - expect(m.interval[1].inclusive).to.be.eql(true); - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + const m = await Model.findOne(); + expect(m).to.exist; + expect(m.interval[0].value).to.be.eql(testDate1); + expect(m.interval[1].value).to.be.eql(testDate2); + expect(m.interval[0].inclusive).to.be.eql(false); + expect(m.interval[1].inclusive).to.be.eql(true); }); - it('should allow date ranges to be generated using a composite range expression #8176', function() { + it('should allow date ranges to be generated using a composite range expression #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -621,19 +609,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDate2 = new Date(testDate1.getTime() + 10000); const testDateRange = [testDate1, { value: testDate2, inclusive: true }]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne()) - .then(m => { - expect(m).to.exist; - expect(m.interval[0].value).to.be.eql(testDate1); - expect(m.interval[1].value).to.be.eql(testDate2); - expect(m.interval[0].inclusive).to.be.eql(true); - expect(m.interval[1].inclusive).to.be.eql(true); - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + const m = await Model.findOne(); + expect(m).to.exist; + expect(m.interval[0].value).to.be.eql(testDate1); + expect(m.interval[1].value).to.be.eql(testDate2); + expect(m.interval[0].inclusive).to.be.eql(true); + expect(m.interval[1].inclusive).to.be.eql(true); }); - it('should correctly return ranges when using predicates that define bounds inclusion #8176', function() { + it('should correctly return ranges when using predicates that define bounds inclusion #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -646,101 +632,90 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDateRange = [testDate1, testDate2]; const dateRangePredicate = [{ value: testDate1, inclusive: true }, { value: testDate1, inclusive: true }]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne({ - where: { - interval: { [Op.overlap]: dateRangePredicate } - } - })) - .then(m => { - expect(m).to.exist; - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + + const m = await Model.findOne({ + where: { + interval: { [Op.overlap]: dateRangePredicate } + } + }); + + expect(m).to.exist; }); } - it('should allow spaces in ENUM', function() { + it('should allow spaces in ENUM', async function() { const Model = this.sequelize.define('user', { name: Sequelize.STRING, type: Sequelize.ENUM(['action', 'mecha', 'canon', 'class s']) }); - return Model.sync({ force: true }).then(() => { - return Model.create({ name: 'sakura', type: 'class s' }); - }).then(record => { - expect(record.type).to.be.eql('class s'); - }); + await Model.sync({ force: true }); + const record = await Model.create({ name: 'sakura', type: 'class s' }); + expect(record.type).to.be.eql('class s'); }); - it('should return YYYY-MM-DD format string for DATEONLY', function() { + it('should return YYYY-MM-DD format string for DATEONLY', async function() { const Model = this.sequelize.define('user', { stamp: Sequelize.DATEONLY }); const testDate = moment().format('YYYY-MM-DD'); const newDate = new Date(); - return Model.sync({ force: true }) - .then(() => Model.create({ stamp: testDate })) - .then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + await Model.sync({ force: true }); + const record4 = await Model.create({ stamp: testDate }); + expect(typeof record4.stamp).to.be.eql('string'); + expect(record4.stamp).to.be.eql(testDate); - return Model.findByPk(record.id); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + const record3 = await Model.findByPk(record4.id); + expect(typeof record3.stamp).to.be.eql('string'); + expect(record3.stamp).to.be.eql(testDate); - return record.update({ - stamp: testDate - }); - }).then(record => { - return record.reload(); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); - - return record.update({ - stamp: newDate - }); - }).then(record => { - return record.reload(); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - const recordDate = new Date(record.stamp); - expect(recordDate.getUTCFullYear()).to.equal(newDate.getUTCFullYear()); - expect(recordDate.getUTCDate()).to.equal(newDate.getUTCDate()); - expect(recordDate.getUTCMonth()).to.equal(newDate.getUTCMonth()); - }); + const record2 = await record3.update({ + stamp: testDate + }); + + const record1 = await record2.reload(); + expect(typeof record1.stamp).to.be.eql('string'); + expect(record1.stamp).to.be.eql(testDate); + + const record0 = await record1.update({ + stamp: newDate + }); + + const record = await record0.reload(); + expect(typeof record.stamp).to.be.eql('string'); + const recordDate = new Date(record.stamp); + expect(recordDate.getUTCFullYear()).to.equal(newDate.getUTCFullYear()); + expect(recordDate.getUTCDate()).to.equal(newDate.getUTCDate()); + expect(recordDate.getUTCMonth()).to.equal(newDate.getUTCMonth()); }); - it('should return set DATEONLY field to NULL correctly', function() { + it('should return set DATEONLY field to NULL correctly', async function() { const Model = this.sequelize.define('user', { stamp: Sequelize.DATEONLY }); const testDate = moment().format('YYYY-MM-DD'); - return Model.sync({ force: true }) - .then(() => Model.create({ stamp: testDate })) - .then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + await Model.sync({ force: true }); + const record2 = await Model.create({ stamp: testDate }); + expect(typeof record2.stamp).to.be.eql('string'); + expect(record2.stamp).to.be.eql(testDate); - return Model.findByPk(record.id); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + const record1 = await Model.findByPk(record2.id); + expect(typeof record1.stamp).to.be.eql('string'); + expect(record1.stamp).to.be.eql(testDate); - return record.update({ - stamp: null - }); - }).then(record => { - return record.reload(); - }).then(record => { - expect(record.stamp).to.be.eql(null); - }); + const record0 = await record1.update({ + stamp: null + }); + + const record = await record0.reload(); + expect(record.stamp).to.be.eql(null); }); - it('should be able to cast buffer as boolean', function() { + it('should be able to cast buffer as boolean', async function() { const ByteModel = this.sequelize.define('Model', { byteToBool: this.sequelize.Sequelize.BLOB }, { @@ -753,18 +728,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { timestamps: false }); - return ByteModel.sync({ + await ByteModel.sync({ force: true - }).then(() => { - return ByteModel.create({ - byteToBool: Buffer.from([true]) - }); - }).then(byte => { - expect(byte.byteToBool).to.be.ok; + }); - return BoolModel.findByPk(byte.id); - }).then(bool => { - expect(bool.byteToBool).to.be.true; + const byte = await ByteModel.create({ + byteToBool: Buffer.from([true]) }); + + expect(byte.byteToBool).to.be.ok; + + const bool = await BoolModel.findByPk(byte.id); + expect(bool.byteToBool).to.be.true; }); }); diff --git a/test/integration/error.test.js b/test/integration/error.test.js index 59ab2ced8c24..0f0304f80917 100644 --- a/test/integration/error.test.js +++ b/test/integration/error.test.js @@ -255,7 +255,7 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { }); describe('OptimisticLockError', () => { - it('got correct error type and message', function() { + it('got correct error type and message', async function() { const Account = this.sequelize.define('Account', { number: { type: Sequelize.INTEGER @@ -264,22 +264,21 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { version: true }); - return Account.sync({ force: true }).then(() => { - const result = Account.create({ number: 1 }).then(accountA => { - return Account.findByPk(accountA.id).then(accountB => { - accountA.number += 1; - return accountA.save().then(() => { return accountB; }); - }); - }).then(accountB => { - accountB.number += 1; - return accountB.save(); - }); - - return Promise.all([ - expect(result).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError), - expect(result).to.eventually.be.rejectedWith('Attempting to update a stale model instance: Account') - ]); - }); + await Account.sync({ force: true }); + const result = (async () => { + const accountA = await Account.create({ number: 1 }); + const accountB0 = await Account.findByPk(accountA.id); + accountA.number += 1; + await accountA.save(); + const accountB = await accountB0; + accountB.number += 1; + return await accountB.save(); + })(); + + await Promise.all([ + expect(result).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError), + expect(result).to.eventually.be.rejectedWith('Attempting to update a stale model instance: Account') + ]); }); }); @@ -295,7 +294,7 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } ].forEach(constraintTest => { - it(`Can be intercepted as ${constraintTest.type} using .catch`, function() { + it(`Can be intercepted as ${constraintTest.type} using .catch`, async function() { const spy = sinon.spy(), User = this.sequelize.define('user', { first_name: { @@ -309,21 +308,22 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { }); const record = { first_name: 'jan', last_name: 'meier' }; - return this.sequelize.sync({ force: true }).then(() => { - return User.create(record); - }).then(() => { - return User.create(record).catch(err => { - if (!(err instanceof constraintTest.exception)) throw err; - return spy(err); - }); - }).then(() => { - expect(spy).to.have.been.calledOnce; - }); + await this.sequelize.sync({ force: true }); + await User.create(record); + + try { + await User.create(record); + } catch (err) { + if (!(err instanceof constraintTest.exception)) throw err; + await spy(err); + } + + expect(spy).to.have.been.calledOnce; }); }); - it('Supports newlines in keys', function() { + it('Supports newlines in keys', async function() { const spy = sinon.spy(), User = this.sequelize.define('user', { name: { @@ -332,20 +332,20 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ name: 'jan' }); - }).then(() => { - // If the error was successfully parsed, we can catch it! - return User.create({ name: 'jan' }).catch(err => { - if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; - return spy(err); - }); - }).then(() => { - expect(spy).to.have.been.calledOnce; - }); + await this.sequelize.sync({ force: true }); + await User.create({ name: 'jan' }); + + try { + await User.create({ name: 'jan' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + await spy(err); + } + + expect(spy).to.have.been.calledOnce; }); - it('Works when unique keys are not defined in sequelize', function() { + it('Works when unique keys are not defined in sequelize', async function() { let User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -353,23 +353,21 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } }, { timestamps: false }); - return this.sequelize.sync({ force: true }).then(() => { - // Now let's pretend the index was created by someone else, and sequelize doesn't know about it - User = this.sequelize.define('user', { - name: Sequelize.STRING - }, { timestamps: false }); - - return User.create({ name: 'jan' }); - }).then(() => { - // It should work even though the unique key is not defined in the model - return expect(User.create({ name: 'jan' })).to.be.rejectedWith(Sequelize.UniqueConstraintError); - }).then(() => { - // And when the model is not passed at all - return expect(this.sequelize.query('INSERT INTO users (name) VALUES (\'jan\')')).to.be.rejectedWith(Sequelize.UniqueConstraintError); - }); + await this.sequelize.sync({ force: true }); + // Now let's pretend the index was created by someone else, and sequelize doesn't know about it + User = this.sequelize.define('user', { + name: Sequelize.STRING + }, { timestamps: false }); + + await User.create({ name: 'jan' }); + // It should work even though the unique key is not defined in the model + await expect(User.create({ name: 'jan' })).to.be.rejectedWith(Sequelize.UniqueConstraintError); + + // And when the model is not passed at all + await expect(this.sequelize.query('INSERT INTO users (name) VALUES (\'jan\')')).to.be.rejectedWith(Sequelize.UniqueConstraintError); }); - it('adds parent and sql properties', function() { + it('adds parent and sql properties', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -377,28 +375,22 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } }, { timestamps: false }); - return this.sequelize.sync({ force: true }) - .then(() => { - return User.create({ name: 'jan' }); - }).then(() => { - // Unique key - return expect(User.create({ name: 'jan' })).to.be.rejected; - }).then(error => { - expect(error).to.be.instanceOf(Sequelize.UniqueConstraintError); - expect(error).to.have.property('parent'); - expect(error).to.have.property('original'); - expect(error).to.have.property('sql'); - - return User.create({ id: 2, name: 'jon' }); - }).then(() => { - // Primary key - return expect(User.create({ id: 2, name: 'jon' })).to.be.rejected; - }).then(error => { - expect(error).to.be.instanceOf(Sequelize.UniqueConstraintError); - expect(error).to.have.property('parent'); - expect(error).to.have.property('original'); - expect(error).to.have.property('sql'); - }); + await this.sequelize.sync({ force: true }); + await User.create({ name: 'jan' }); + // Unique key + const error0 = await expect(User.create({ name: 'jan' })).to.be.rejected; + expect(error0).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(error0).to.have.property('parent'); + expect(error0).to.have.property('original'); + expect(error0).to.have.property('sql'); + + await User.create({ id: 2, name: 'jon' }); + // Primary key + const error = await expect(User.create({ id: 2, name: 'jon' })).to.be.rejected; + expect(error).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(error).to.have.property('parent'); + expect(error).to.have.property('original'); + expect(error).to.have.property('sql'); }); }); }); diff --git a/test/integration/include.test.js b/test/integration/include.test.js index f54a2507e34a..7b049f9e2124 100644 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -16,149 +16,144 @@ const sortById = function(a, b) { describe(Support.getTestDialectTeaser('Include'), () => { describe('find', () => { - it('should support an empty belongsTo include', function() { + it('should support an empty belongsTo include', async function() { const Company = this.sequelize.define('Company', {}), User = this.sequelize.define('User', {}); User.belongsTo(Company, { as: 'Employer' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create(); - }).then(() => { - return User.findOne({ - include: [{ model: Company, as: 'Employer' }] - }).then(user => { - expect(user).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + await User.create(); + + const user = await User.findOne({ + include: [{ model: Company, as: 'Employer' }] }); + + expect(user).to.be.ok; }); - it('should support a belongsTo association reference', function() { + it('should support a belongsTo association reference', async function() { const Company = this.sequelize.define('Company', {}), User = this.sequelize.define('User', {}), Employer = User.belongsTo(Company, { as: 'Employer' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create(); - }).then(() => { - return User.findOne({ - include: [Employer] - }).then(user => { - expect(user).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + await User.create(); + + const user = await User.findOne({ + include: [Employer] }); + + expect(user).to.be.ok; }); - it('should support to use associations with Sequelize.col', function() { + it('should support to use associations with Sequelize.col', async function() { const Table1 = this.sequelize.define('Table1'); const Table2 = this.sequelize.define('Table2'); const Table3 = this.sequelize.define('Table3', { value: DataTypes.INTEGER }); Table1.hasOne(Table2, { foreignKey: 'Table1Id' }); Table2.hasMany(Table3, { as: 'Tables3', foreignKey: 'Table2Id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Table1.create().then(table1 => { - return Table2.create({ - Table1Id: table1.get('id') - }); - }).then(table2 => { - return Table3.bulkCreate([ - { - Table2Id: table2.get('id'), - value: 5 - }, - { - Table2Id: table2.get('id'), - value: 7 - } - ], { - validate: true - }); - }); - }).then(() => { - return Table1.findAll({ - raw: true, - attributes: [ - [Sequelize.fn('SUM', Sequelize.col('Table2.Tables3.value')), 'sum'] - ], - include: [ - { - model: Table2, - attributes: [], - include: [ - { - model: Table3, - as: 'Tables3', - attributes: [] - } - ] - } - ] - }).then(result => { - expect(result.length).to.equal(1); - expect(parseInt(result[0].sum, 10)).to.eq(12); - }); + await this.sequelize.sync({ force: true }); + const table1 = await Table1.create(); + + const table2 = await Table2.create({ + Table1Id: table1.get('id') + }); + + await Table3.bulkCreate([ + { + Table2Id: table2.get('id'), + value: 5 + }, + { + Table2Id: table2.get('id'), + value: 7 + } + ], { + validate: true + }); + + const result = await Table1.findAll({ + raw: true, + attributes: [ + [Sequelize.fn('SUM', Sequelize.col('Table2.Tables3.value')), 'sum'] + ], + include: [ + { + model: Table2, + attributes: [], + include: [ + { + model: Table3, + as: 'Tables3', + attributes: [] + } + ] + } + ] }); + + expect(result.length).to.equal(1); + expect(parseInt(result[0].sum, 10)).to.eq(12); }); - it('should support a belongsTo association reference with a where', function() { + it('should support a belongsTo association reference with a where', async function() { const Company = this.sequelize.define('Company', { name: DataTypes.STRING }), User = this.sequelize.define('User', {}), Employer = User.belongsTo(Company, { as: 'Employer', foreignKey: 'employerId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Company.create({ - name: 'CyberCorp' - }).then(company => { - return User.create({ - employerId: company.get('id') - }); - }); - }).then(() => { - return User.findOne({ - include: [ - { association: Employer, where: { name: 'CyberCorp' } } - ] - }).then(user => { - expect(user).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const company = await Company.create({ + name: 'CyberCorp' + }); + + await User.create({ + employerId: company.get('id') + }); + + const user = await User.findOne({ + include: [ + { association: Employer, where: { name: 'CyberCorp' } } + ] }); + + expect(user).to.be.ok; }); - it('should support a empty hasOne include', function() { + it('should support a empty hasOne include', async function() { const Company = this.sequelize.define('Company', {}), Person = this.sequelize.define('Person', {}); Company.hasOne(Person, { as: 'CEO' }); - return this.sequelize.sync({ force: true }).then(() => { - return Company.create().then(() => { - return Company.findOne({ - include: [{ model: Person, as: 'CEO' }] - }).then(company => { - expect(company).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + await Company.create(); + + const company = await Company.findOne({ + include: [{ model: Person, as: 'CEO' }] }); + + expect(company).to.be.ok; }); - it('should support a hasOne association reference', function() { + it('should support a hasOne association reference', async function() { const Company = this.sequelize.define('Company', {}), Person = this.sequelize.define('Person', {}), CEO = Company.hasOne(Person, { as: 'CEO' }); - return this.sequelize.sync({ force: true }).then(() => { - return Company.create(); - }).then(() => { - return Company.findOne({ - include: [CEO] - }); - }).then(user => { - expect(user).to.be.ok; + await this.sequelize.sync({ force: true }); + await Company.create(); + + const user = await Company.findOne({ + include: [CEO] }); + + expect(user).to.be.ok; }); - it('should support including a belongsTo association rather than a model/as pair', function() { + it('should support including a belongsTo association rather than a model/as pair', async function() { const Company = this.sequelize.define('Company', {}), Person = this.sequelize.define('Person', {}); @@ -166,91 +161,84 @@ describe(Support.getTestDialectTeaser('Include'), () => { Employer: Person.belongsTo(Company, { as: 'employer' }) }; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Person.create(), Company.create()]).then(([person, company]) => { - return person.setEmployer(company); - }); - }).then(() => { - return Person.findOne({ - include: [Person.relation.Employer] - }).then(person => { - expect(person).to.be.ok; - expect(person.employer).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const [person0, company] = await Promise.all([Person.create(), Company.create()]); + await person0.setEmployer(company); + + const person = await Person.findOne({ + include: [Person.relation.Employer] }); + + expect(person).to.be.ok; + expect(person.employer).to.be.ok; }); - it('should support a hasMany association reference', function() { + it('should support a hasMany association reference', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}), Tasks = User.hasMany(Task); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return user.createTask(); - }).then(() => { - return User.findOne({ - include: [Tasks] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.tasks).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create(); + await user0.createTask(); + + const user = await User.findOne({ + include: [Tasks] }); + + expect(user).to.be.ok; + expect(user.tasks).to.be.ok; }); - it('should support a hasMany association reference with a where condition', function() { + it('should support a hasMany association reference with a where condition', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', { title: DataTypes.STRING }), Tasks = User.hasMany(Task); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return Promise.all([user.createTask({ - title: 'trivial' - }), user.createTask({ - title: 'pursuit' - })]); - }).then(() => { - return User.findOne({ - include: [ - { association: Tasks, where: { title: 'trivial' } } - ] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.tasks).to.be.ok; - expect(user.tasks.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create(); + + await Promise.all([user0.createTask({ + title: 'trivial' + }), user0.createTask({ + title: 'pursuit' + })]); + + const user = await User.findOne({ + include: [ + { association: Tasks, where: { title: 'trivial' } } + ] }); + + expect(user).to.be.ok; + expect(user.tasks).to.be.ok; + expect(user.tasks.length).to.equal(1); }); - it('should support a belongsToMany association reference', function() { + it('should support a belongsToMany association reference', async function() { const User = this.sequelize.define('user', {}), Group = this.sequelize.define('group', {}), Groups = User.belongsToMany(Group, { through: 'UserGroup' }); Group.belongsToMany(User, { through: 'UserGroup' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return user.createGroup(); - }); - }).then(() => { - return User.findOne({ - include: [Groups] - }).then(user => { - expect(user).to.be.ok; - expect(user.groups).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create(); + await user0.createGroup(); + + const user = await User.findOne({ + include: [Groups] }); + + expect(user).to.be.ok; + expect(user.groups).to.be.ok; }); - it('should support a simple nested belongsTo -> belongsTo include', function() { + it('should support a simple nested belongsTo -> belongsTo include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); @@ -258,32 +246,33 @@ describe(Support.getTestDialectTeaser('Include'), () => { Task.belongsTo(User); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - task: Task.create(), - user: User.create(), - group: Group.create() - }).then(props => { - return Promise.all([props.task.setUser(props.user), props.user.setGroup(props.group)]).then(() => props); - }).then(props => { - return Task.findOne({ - where: { - id: props.task.id - }, - include: [ - { model: User, include: [ - { model: Group } - ] } - ] - }).then(task => { - expect(task.User).to.be.ok; - expect(task.User.Group).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const props0 = await promiseProps({ + task: Task.create(), + user: User.create(), + group: Group.create() + }); + + await Promise.all([props0.task.setUser(props0.user), props0.user.setGroup(props0.group)]); + const props = props0; + + const task = await Task.findOne({ + where: { + id: props.task.id + }, + include: [ + { model: User, include: [ + { model: Group } + ] } + ] }); + + expect(task.User).to.be.ok; + expect(task.User.Group).to.be.ok; }); - it('should support a simple sibling set of belongsTo include', function() { + it('should support a simple sibling set of belongsTo include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); @@ -291,30 +280,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { Task.belongsTo(User); Task.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ - User: {}, - Group: {} - }, { - include: [User, Group] - }); - }).then(task => { - return Task.findOne({ - where: { - id: task.id - }, - include: [ - { model: User }, - { model: Group } - ] - }); - }).then(task => { - expect(task.User).to.be.ok; - expect(task.Group).to.be.ok; + await this.sequelize.sync({ force: true }); + + const task0 = await Task.create({ + User: {}, + Group: {} + }, { + include: [User, Group] + }); + + const task = await Task.findOne({ + where: { + id: task0.id + }, + include: [ + { model: User }, + { model: Group } + ] }); + + expect(task.User).to.be.ok; + expect(task.Group).to.be.ok; }); - it('should support a simple nested hasOne -> hasOne include', function() { + it('should support a simple nested hasOne -> hasOne include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); @@ -323,31 +312,31 @@ describe(Support.getTestDialectTeaser('Include'), () => { Group.hasOne(User); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - Task: {}, - Group: {} - }, { - include: [Task, Group] - }); - }).then(user => { - return Group.findOne({ - where: { - id: user.Group.id - }, - include: [ - { model: User, include: [ - { model: Task } - ] } - ] - }); - }).then(group => { - expect(group.User).to.be.ok; - expect(group.User.Task).to.be.ok; + await this.sequelize.sync({ force: true }); + + const user = await User.create({ + Task: {}, + Group: {} + }, { + include: [Task, Group] }); + + const group = await Group.findOne({ + where: { + id: user.Group.id + }, + include: [ + { model: User, include: [ + { model: Task } + ] } + ] + }); + + expect(group.User).to.be.ok; + expect(group.User.Task).to.be.ok; }); - it('should support a simple nested hasMany -> belongsTo include', function() { + it('should support a simple nested hasMany -> belongsTo include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project', {}); @@ -355,41 +344,40 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Task); Task.belongsTo(Project); - return this.sequelize.sync({ force: true }).then(() => { - return Project.bulkCreate([{ id: 1 }, { id: 2 }]); - }).then(() => { - return User.create({ - Tasks: [ - { ProjectId: 1 }, - { ProjectId: 2 }, - { ProjectId: 1 }, - { ProjectId: 2 } - ] - }, { - include: [Task] - }); - }).then(user => { - return User.findOne({ - where: { - id: user.id - }, - include: [ - { model: Task, include: [ - { model: Project } - ] } - ] - }); - }).then(user => { - expect(user.Tasks).to.be.ok; - expect(user.Tasks.length).to.equal(4); + await this.sequelize.sync({ force: true }); + await Project.bulkCreate([{ id: 1 }, { id: 2 }]); - user.Tasks.forEach(task => { - expect(task.Project).to.be.ok; - }); + const user0 = await User.create({ + Tasks: [ + { ProjectId: 1 }, + { ProjectId: 2 }, + { ProjectId: 1 }, + { ProjectId: 2 } + ] + }, { + include: [Task] + }); + + const user = await User.findOne({ + where: { + id: user0.id + }, + include: [ + { model: Task, include: [ + { model: Project } + ] } + ] + }); + + expect(user.Tasks).to.be.ok; + expect(user.Tasks.length).to.equal(4); + + user.Tasks.forEach(task => { + expect(task.Project).to.be.ok; }); }); - it('should support a simple nested belongsTo -> hasMany include', function() { + it('should support a simple nested belongsTo -> hasMany include', async function() { const Task = this.sequelize.define('Task', {}), Worker = this.sequelize.define('Worker', {}), Project = this.sequelize.define('Project', {}); @@ -398,32 +386,32 @@ describe(Support.getTestDialectTeaser('Include'), () => { Project.hasMany(Worker); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return Project.create({ - Workers: [{}], - Tasks: [{}, {}, {}, {}] - }, { - include: [Worker, Task] - }); - }).then(project => { - return Worker.findOne({ - where: { - id: project.Workers[0].id - }, - include: [ - { model: Project, include: [ - { model: Task } - ] } - ] - }); - }).then(worker => { - expect(worker.Project).to.be.ok; - expect(worker.Project.Tasks).to.be.ok; - expect(worker.Project.Tasks.length).to.equal(4); + await this.sequelize.sync({ force: true }); + + const project = await Project.create({ + Workers: [{}], + Tasks: [{}, {}, {}, {}] + }, { + include: [Worker, Task] }); + + const worker = await Worker.findOne({ + where: { + id: project.Workers[0].id + }, + include: [ + { model: Project, include: [ + { model: Task } + ] } + ] + }); + + expect(worker.Project).to.be.ok; + expect(worker.Project.Tasks).to.be.ok; + expect(worker.Project.Tasks.length).to.equal(4); }); - it('should support a simple nested hasMany to hasMany include', function() { + it('should support a simple nested hasMany to hasMany include', async function() { const User = this.sequelize.define('User', {}), Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -436,60 +424,60 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: 'product_tag' }); Tag.belongsToMany(Product, { through: 'product_tag' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ - id: 1, - Products: [ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' }, - { title: 'Bed' } - ] - }, { - include: [Product] - }).then(() => { - return Product.findAll({ order: [['id']] }); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll({ order: [['id']] }); - }) - ]); - }).then(([products, tags]) => { - return Promise.all([ - products[0].setTags([tags[0], tags[2]]), - products[1].setTags([tags[1]]), - products[2].setTags([tags[0], tags[1], tags[2]]) - ]); - }).then(() => { - return User.findOne({ - where: { - id: 1 - }, - include: [ - { model: Product, include: [ - { model: Tag } - ] } - ], - order: [ - User.rawAttributes.id, - [Product, 'id'] + await this.sequelize.sync({ force: true }); + + const [products, tags] = await Promise.all([ + User.create({ + id: 1, + Products: [ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' }, + { title: 'Bed' } ] - }); - }).then(user => { - expect(user.Products.length).to.equal(4); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[2].Tags.length).to.equal(3); - expect(user.Products[3].Tags.length).to.equal(0); + }, { + include: [Product] + }).then(() => { + return Product.findAll({ order: [['id']] }); + }), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll({ order: [['id']] }); + }) + ]); + + await Promise.all([ + products[0].setTags([tags[0], tags[2]]), + products[1].setTags([tags[1]]), + products[2].setTags([tags[0], tags[1], tags[2]]) + ]); + + const user = await User.findOne({ + where: { + id: 1 + }, + include: [ + { model: Product, include: [ + { model: Tag } + ] } + ], + order: [ + User.rawAttributes.id, + [Product, 'id'] + ] }); + + expect(user.Products.length).to.equal(4); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[2].Tags.length).to.equal(3); + expect(user.Products[3].Tags.length).to.equal(0); }); - it('should support an include with multiple different association types', function() { + it('should support an include with multiple different association types', async function() { const User = this.sequelize.define('User', {}), Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -534,78 +522,78 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Product.create({ - id: 1, - title: 'Chair', - Prices: [{ value: 5 }, { value: 10 }] - }, { include: [Price] }), - Product.create({ - id: 2, - title: 'Desk', - Prices: [{ value: 5 }, { value: 10 }, { value: 15 }, { value: 20 }] - }, { include: [Price] }), - User.create({ - id: 1, - Memberships: [ - { id: 1, Group: { name: 'Developers' }, Rank: { name: 'Admin', canInvite: 1, canRemove: 1 } }, - { id: 2, Group: { name: 'Designers' }, Rank: { name: 'Member', canInvite: 1, canRemove: 0 } } - ] - }, { - include: { model: GroupMember, as: 'Memberships', include: [Group, Rank] } - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]); - }).then(([product1, product2, user, tags]) => { - return Promise.all([ - user.setProducts([product1, product2]), - product1.setTags([tags[0], tags[2]]), - product2.setTags([tags[1]]), - product1.setCategory(tags[1]) - ]); - }).then(() => { - return User.findOne({ - where: { id: 1 }, - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - Rank - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - Price - ] } + await this.sequelize.sync({ force: true }); + + const [product1, product2, user0, tags] = await Promise.all([ + Product.create({ + id: 1, + title: 'Chair', + Prices: [{ value: 5 }, { value: 10 }] + }, { include: [Price] }), + Product.create({ + id: 2, + title: 'Desk', + Prices: [{ value: 5 }, { value: 10 }, { value: 15 }, { value: 20 }] + }, { include: [Price] }), + User.create({ + id: 1, + Memberships: [ + { id: 1, Group: { name: 'Developers' }, Rank: { name: 'Admin', canInvite: 1, canRemove: 1 } }, + { id: 2, Group: { name: 'Designers' }, Rank: { name: 'Member', canInvite: 1, canRemove: 0 } } ] - }); - }).then(user => { - user.Memberships.sort(sortById); - expect(user.Memberships.length).to.equal(2); - expect(user.Memberships[0].Group.name).to.equal('Developers'); - expect(user.Memberships[0].Rank.canRemove).to.equal(1); - expect(user.Memberships[1].Group.name).to.equal('Designers'); - expect(user.Memberships[1].Rank.canRemove).to.equal(0); - - user.Products.sort(sortById); - expect(user.Products.length).to.equal(2); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[0].Category).to.be.ok; - expect(user.Products[1].Category).not.to.be.ok; - - expect(user.Products[0].Prices.length).to.equal(2); - expect(user.Products[1].Prices.length).to.equal(4); + }, { + include: { model: GroupMember, as: 'Memberships', include: [Group, Rank] } + }), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll(); + }) + ]); + + await Promise.all([ + user0.setProducts([product1, product2]), + product1.setTags([tags[0], tags[2]]), + product2.setTags([tags[1]]), + product1.setCategory(tags[1]) + ]); + + const user = await User.findOne({ + where: { id: 1 }, + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + Rank + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + Price + ] } + ] }); + + user.Memberships.sort(sortById); + expect(user.Memberships.length).to.equal(2); + expect(user.Memberships[0].Group.name).to.equal('Developers'); + expect(user.Memberships[0].Rank.canRemove).to.equal(1); + expect(user.Memberships[1].Group.name).to.equal('Designers'); + expect(user.Memberships[1].Rank.canRemove).to.equal(0); + + user.Products.sort(sortById); + expect(user.Products.length).to.equal(2); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[0].Category).to.be.ok; + expect(user.Products[1].Category).not.to.be.ok; + + expect(user.Products[0].Prices.length).to.equal(2); + expect(user.Products[1].Prices.length).to.equal(4); }); - it('should support specifying attributes', function() { + it('should support specifying attributes', async function() { const Project = this.sequelize.define('Project', { title: Sequelize.STRING }); @@ -618,30 +606,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { Project.hasMany(Task); Task.belongsTo(Project); - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ - title: 'FooBar', - Project: { title: 'BarFoo' } - }, { - include: [Project] - }); - }).then(() => { - return Task.findAll({ - attributes: ['title'], - include: [ - { model: Project, attributes: ['title'] } - ] - }); - }).then(tasks => { - expect(tasks[0].title).to.equal('FooBar'); - expect(tasks[0].Project.title).to.equal('BarFoo'); + await this.sequelize.sync({ force: true }); + + await Task.create({ + title: 'FooBar', + Project: { title: 'BarFoo' } + }, { + include: [Project] + }); - expect(_.omit(tasks[0].get(), 'Project')).to.deep.equal({ title: 'FooBar' }); - expect(tasks[0].Project.get()).to.deep.equal({ title: 'BarFoo' }); + const tasks = await Task.findAll({ + attributes: ['title'], + include: [ + { model: Project, attributes: ['title'] } + ] }); + + expect(tasks[0].title).to.equal('FooBar'); + expect(tasks[0].Project.title).to.equal('BarFoo'); + + expect(_.omit(tasks[0].get(), 'Project')).to.deep.equal({ title: 'FooBar' }); + expect(tasks[0].Project.get()).to.deep.equal({ title: 'BarFoo' }); }); - it('should support Sequelize.literal and renaming of attributes in included model attributes', function() { + it('should support Sequelize.literal and renaming of attributes in included model attributes', async function() { const Post = this.sequelize.define('Post', {}); const PostComment = this.sequelize.define('PostComment', { someProperty: Sequelize.VIRTUAL, // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field @@ -650,75 +638,71 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.hasMany(PostComment); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({}); - }).then(post => { - return post.createPostComment({ - comment_title: 'WAT' - }); - }).then(() => { - let findAttributes; - if (dialect === 'mssql') { - findAttributes = [ - Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "PostComments.someProperty"'), - [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] - ]; - } else { - findAttributes = [ - Sequelize.literal('EXISTS(SELECT 1) AS "PostComments.someProperty"'), - [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] - ]; - } - findAttributes.push(['comment_title', 'commentTitle']); - - return Post.findAll({ - include: [ - { - model: PostComment, - attributes: findAttributes - } - ] - }); - }).then(posts => { - expect(posts[0].PostComments[0].get('someProperty')).to.be.ok; - expect(posts[0].PostComments[0].get('someProperty2')).to.be.ok; - expect(posts[0].PostComments[0].get('commentTitle')).to.equal('WAT'); + await this.sequelize.sync({ force: true }); + const post = await Post.create({}); + + await post.createPostComment({ + comment_title: 'WAT' + }); + + let findAttributes; + if (dialect === 'mssql') { + findAttributes = [ + Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "PostComments.someProperty"'), + [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] + ]; + } else { + findAttributes = [ + Sequelize.literal('EXISTS(SELECT 1) AS "PostComments.someProperty"'), + [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] + ]; + } + findAttributes.push(['comment_title', 'commentTitle']); + + const posts = await Post.findAll({ + include: [ + { + model: PostComment, + attributes: findAttributes + } + ] }); + + expect(posts[0].PostComments[0].get('someProperty')).to.be.ok; + expect(posts[0].PostComments[0].get('someProperty2')).to.be.ok; + expect(posts[0].PostComments[0].get('commentTitle')).to.equal('WAT'); }); - it('should support self associated hasMany (with through) include', function() { + it('should support self associated hasMany (with through) include', async function() { const Group = this.sequelize.define('Group', { name: DataTypes.STRING }); Group.belongsToMany(Group, { through: 'groups_outsourcing_companies', as: 'OutsourcingCompanies' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Group.bulkCreate([ - { name: 'SoccerMoms' }, - { name: 'Coca Cola' }, - { name: 'Dell' }, - { name: 'Pepsi' } - ]); - }).then(() => { - return Group.findAll(); - }).then(groups => { - ctx.groups = groups; - return groups[0].setOutsourcingCompanies(groups.slice(1)); - }).then(() => { - return Group.findOne({ - where: { - id: ctx.groups[0].id - }, - include: [{ model: Group, as: 'OutsourcingCompanies' }] - }); - }).then(group => { - expect(group.OutsourcingCompanies).to.have.length(3); + await this.sequelize.sync({ force: true }); + + await Group.bulkCreate([ + { name: 'SoccerMoms' }, + { name: 'Coca Cola' }, + { name: 'Dell' }, + { name: 'Pepsi' } + ]); + + const groups = await Group.findAll(); + await groups[0].setOutsourcingCompanies(groups.slice(1)); + + const group = await Group.findOne({ + where: { + id: groups[0].id + }, + include: [{ model: Group, as: 'OutsourcingCompanies' }] }); + + expect(group.OutsourcingCompanies).to.have.length(3); }); - it('should support including date fields, with the correct timeszone', function() { + it('should support including date fields, with the correct timeszone', async function() { const User = this.sequelize.define('user', { dateField: Sequelize.DATE }, { timestamps: false }), @@ -729,29 +713,27 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsToMany(Group, { through: 'group_user' }); Group.belongsToMany(User, { through: 'group_user' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ dateField: Date.UTC(2014, 1, 20) }), - Group.create({ dateField: Date.UTC(2014, 1, 20) }) - ]); - }).then(([user, group]) => { - ctx.user = user; - return user.addGroup(group); - }).then(() => { - return User.findOne({ - where: { - id: ctx.user.id - }, - include: [Group] - }); - }).then(user => { - expect(user.dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(user.groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + await this.sequelize.sync({ force: true }); + + const [user0, group] = await Promise.all([ + User.create({ dateField: Date.UTC(2014, 1, 20) }), + Group.create({ dateField: Date.UTC(2014, 1, 20) }) + ]); + + await user0.addGroup(group); + + const user = await User.findOne({ + where: { + id: user0.id + }, + include: [Group] }); + + expect(user.dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(user.groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); }); - it('should support include when retrieving associated objects', function() { + it('should support include when retrieving associated objects', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }), @@ -773,35 +755,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { as: 'Members' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ name: 'Owner' }), - User.create({ name: 'Member' }), - Group.create({ name: 'Group' }) - ]); - }).then(([owner, member, group]) => { - ctx.owner = owner; - ctx.member = member; - ctx.group = group; - return owner.addGroup(group); - }).then(() => { - return ctx.group.addMember(ctx.member); - }).then(() => { - return ctx.owner.getGroups({ - include: [{ - model: User, - as: 'Members' - }] - }); - }).then(groups => { - expect(groups.length).to.equal(1); - expect(groups[0].Members[0].name).to.equal('Member'); + await this.sequelize.sync({ force: true }); + + const [owner, member, group] = await Promise.all([ + User.create({ name: 'Owner' }), + User.create({ name: 'Member' }), + Group.create({ name: 'Group' }) + ]); + + await owner.addGroup(group); + await group.addMember(member); + + const groups = await owner.getGroups({ + include: [{ + model: User, + as: 'Members' + }] }); + + expect(groups.length).to.equal(1); + expect(groups[0].Members[0].name).to.equal('Member'); }); }); - const createUsersAndItems = function() { + const createUsersAndItems = async function() { const User = this.sequelize.define('User', {}), Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }); @@ -811,46 +788,46 @@ describe(Support.getTestDialectTeaser('Include'), () => { this.User = User; this.Item = Item; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }), - Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' } - ]).then(() => { - return Item.findAll(); - }) - ]); - }).then(([users, items]) => { - return Promise.all([ - users[0].setItem(items[0]), - users[1].setItem(items[1]), - users[2].setItem(items[2]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const [users, items] = await Promise.all([ + User.bulkCreate([{}, {}, {}]).then(() => { + return User.findAll(); + }), + Item.bulkCreate([ + { 'test': 'abc' }, + { 'test': 'def' }, + { 'test': 'ghi' } + ]).then(() => { + return Item.findAll(); + }) + ]); + + return Promise.all([ + users[0].setItem(items[0]), + users[1].setItem(items[1]), + users[2].setItem(items[2]) + ]); }; describe('where', () => { - beforeEach(function() { - return createUsersAndItems.bind(this)(); + beforeEach(async function() { + await createUsersAndItems.bind(this)(); }); - it('should support Sequelize.and()', function() { - return this.User.findAll({ + it('should support Sequelize.and()', async function() { + const result = await this.User.findAll({ include: [ { model: this.Item, where: Sequelize.and({ test: 'def' }) } ] - }).then(result => { - expect(result.length).to.eql(1); - expect(result[0].Item.test).to.eql('def'); }); + + expect(result.length).to.eql(1); + expect(result[0].Item.test).to.eql('def'); }); - it('should support Sequelize.or()', function() { - return expect(this.User.findAll({ + it('should support Sequelize.or()', async function() { + await expect(this.User.findAll({ include: [ { model: this.Item, where: Sequelize.or({ test: 'def' @@ -863,26 +840,26 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); describe('findAndCountAll', () => { - it('should include associations to findAndCountAll', function() { - return createUsersAndItems.bind(this)().then(() => { - return this.User.findAndCountAll({ - include: [ - { model: this.Item, where: { - test: 'def' - } } - ] - }); - }).then(result => { - expect(result.count).to.eql(1); + it('should include associations to findAndCountAll', async function() { + await createUsersAndItems.bind(this)(); - expect(result.rows.length).to.eql(1); - expect(result.rows[0].Item.test).to.eql('def'); + const result = await this.User.findAndCountAll({ + include: [ + { model: this.Item, where: { + test: 'def' + } } + ] }); + + expect(result.count).to.eql(1); + + expect(result.rows.length).to.eql(1); + expect(result.rows[0].Item.test).to.eql('def'); }); }); describe('association getter', () => { - it('should support getting an include on a N:M association getter', function() { + it('should support getting an include on a N:M association getter', async function() { const Question = this.sequelize.define('Question', {}), Answer = this.sequelize.define('Answer', {}), Questionnaire = this.sequelize.define('Questionnaire', {}); @@ -893,18 +870,17 @@ describe(Support.getTestDialectTeaser('Include'), () => { Questionnaire.hasMany(Question); Question.belongsTo(Questionnaire); - return this.sequelize.sync({ force: true }).then(() => { - return Questionnaire.create(); - }).then(questionnaire => { - return questionnaire.getQuestions({ - include: Answer - }); + await this.sequelize.sync({ force: true }); + const questionnaire = await Questionnaire.create(); + + await questionnaire.getQuestions({ + include: Answer }); }); }); describe('right join', () => { - it('should support getting an include with a right join', function() { + it('should support getting an include with a right join', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }), @@ -915,30 +891,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Group); Group.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ name: 'User 1' }), - User.create({ name: 'User 2' }), - User.create({ name: 'User 3' }), - Group.create({ name: 'A Group' }) - ]); - }).then(() => { - return Group.findAll({ - include: [{ - model: User, - right: true - }] - }); - }).then(groups => { - if (current.dialect.supports['RIGHT JOIN']) { - expect(groups.length).to.equal(3); - } else { - expect(groups.length).to.equal(1); - } + await this.sequelize.sync({ force: true }); + + await Promise.all([ + User.create({ name: 'User 1' }), + User.create({ name: 'User 2' }), + User.create({ name: 'User 3' }), + Group.create({ name: 'A Group' }) + ]); + + const groups = await Group.findAll({ + include: [{ + model: User, + right: true + }] }); + + if (current.dialect.supports['RIGHT JOIN']) { + expect(groups.length).to.equal(3); + } else { + expect(groups.length).to.equal(1); + } }); - it('should support getting an include through with a right join', function() { + it('should support getting an include through with a right join', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }), @@ -962,47 +938,41 @@ describe(Support.getTestDialectTeaser('Include'), () => { constraints: false }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ name: 'Member 1' }), - User.create({ name: 'Member 2' }), - Group.create({ name: 'Group 1' }), - Group.create({ name: 'Group 2' }) - ]); - }).then(([member1, member2, group1, group2]) => { - ctx.member1 = member1; - ctx.member2 = member2; - ctx.group1 = group1; - ctx.group2 = group2; - }).then(() => { - return Promise.all([ - ctx.group1.addMember(ctx.member1), - ctx.group1.addMember(ctx.member2), - ctx.group2.addMember(ctx.member1) - ]); - }).then(() => { - return ctx.group2.destroy(); - }).then(() => { - return Group.findAll({ - include: [{ - model: User, - as: 'Members', - right: true - }] - }); - }).then(groups => { - if (current.dialect.supports['RIGHT JOIN']) { - expect(groups.length).to.equal(2); - } else { - expect(groups.length).to.equal(1); - } + await this.sequelize.sync({ force: true }); + + const [member1, member2, group1, group2] = await Promise.all([ + User.create({ name: 'Member 1' }), + User.create({ name: 'Member 2' }), + Group.create({ name: 'Group 1' }), + Group.create({ name: 'Group 2' }) + ]); + + await Promise.all([ + group1.addMember(member1), + group1.addMember(member2), + group2.addMember(member1) + ]); + + await group2.destroy(); + + const groups = await Group.findAll({ + include: [{ + model: User, + as: 'Members', + right: true + }] }); + + if (current.dialect.supports['RIGHT JOIN']) { + expect(groups.length).to.equal(2); + } else { + expect(groups.length).to.equal(1); + } }); }); describe('nested includes', () => { - beforeEach(function() { + beforeEach(async function() { const Employee = this.sequelize.define('Employee', { 'name': DataTypes.STRING }); const Team = this.sequelize.define('Team', { 'name': DataTypes.STRING }); const Clearence = this.sequelize.define('Clearence', { 'level': DataTypes.INTEGER }); @@ -1015,29 +985,29 @@ describe(Support.getTestDialectTeaser('Include'), () => { this.Team = Team; this.Clearence = Clearence; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Team.create({ name: 'TeamA' }), - Team.create({ name: 'TeamB' }), - Employee.create({ name: 'John' }), - Employee.create({ name: 'Jane' }), - Employee.create({ name: 'Josh' }), - Employee.create({ name: 'Jill' }), - Clearence.create({ level: 3 }), - Clearence.create({ level: 5 }) - ]).then(instances => { - return Promise.all([ - instances[0].addMembers([instances[2], instances[3]]), - instances[1].addMembers([instances[4], instances[5]]), - instances[2].setClearence(instances[6]), - instances[3].setClearence(instances[7]) - ]); - }); - }); + await this.sequelize.sync({ force: true }); + + const instances = await Promise.all([ + Team.create({ name: 'TeamA' }), + Team.create({ name: 'TeamB' }), + Employee.create({ name: 'John' }), + Employee.create({ name: 'Jane' }), + Employee.create({ name: 'Josh' }), + Employee.create({ name: 'Jill' }), + Clearence.create({ level: 3 }), + Clearence.create({ level: 5 }) + ]); + + await Promise.all([ + instances[0].addMembers([instances[2], instances[3]]), + instances[1].addMembers([instances[4], instances[5]]), + instances[2].setClearence(instances[6]), + instances[3].setClearence(instances[7]) + ]); }); - it('should not ripple grandchild required to top level find when required of child is set to false', function() { - return this.Team.findAll({ + it('should not ripple grandchild required to top level find when required of child is set to false', async function() { + const teams = await this.Team.findAll({ include: [ { association: this.Team.Members, @@ -1050,13 +1020,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { ] } ] - }).then(teams => { - expect(teams).to.have.length(2); }); + + expect(teams).to.have.length(2); }); - it('should support eager loading associations using the name of the relation (string)', function() { - return this.Team.findOne({ + it('should support eager loading associations using the name of the relation (string)', async function() { + const team = await this.Team.findOne({ where: { name: 'TeamA' }, @@ -1066,13 +1036,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { required: true } ] - }).then(team => { - expect(team.members).to.have.length(2); }); + + expect(team.members).to.have.length(2); }); - it('should not ripple grandchild required to top level find when required of child is not given (implicitly false)', function() { - return this.Team.findAll({ + it('should not ripple grandchild required to top level find when required of child is not given (implicitly false)', async function() { + const teams = await this.Team.findAll({ include: [ { association: this.Team.Members, @@ -1084,13 +1054,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { ] } ] - }).then(teams => { - expect(teams).to.have.length(2); }); + + expect(teams).to.have.length(2); }); - it('should ripple grandchild required to top level find when required of child is set to true as well', function() { - return this.Team.findAll({ + it('should ripple grandchild required to top level find when required of child is set to true as well', async function() { + const teams = await this.Team.findAll({ include: [ { association: this.Team.Members, @@ -1103,9 +1073,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { ] } ] - }).then(teams => { - expect(teams).to.have.length(1); }); + + expect(teams).to.have.length(1); }); }); diff --git a/test/integration/instance.test.js b/test/integration/instance.test.js index f51b2bf6ec8c..099b783b0b08 100644 --- a/test/integration/instance.test.js +++ b/test/integration/instance.test.js @@ -21,7 +21,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -53,20 +53,18 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('Escaping', () => { - it('is done properly for special characters', function() { + it('is done properly for special characters', async function() { // Ideally we should test more: "\0\n\r\b\t\\\'\"\x1a" // But this causes sqlite to fail and exits the entire test suite immediately const bio = `${dialect}'"\n`; // Need to add the dialect here so in case of failure I know what DB it failed for - return this.User.create({ username: bio }).then(u1 => { - return this.User.findByPk(u1.id).then(u2 => { - expect(u2.username).to.equal(bio); - }); - }); + const u1 = await this.User.create({ username: bio }); + const u2 = await this.User.findByPk(u1.id); + expect(u2.username).to.equal(bio); }); }); @@ -77,41 +75,34 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(user.isNewRecord).to.be.ok; }); - it('returns false for saved objects', function() { - return this.User.build({ username: 'user' }).save().then(user => { - expect(user.isNewRecord).to.not.be.ok; - }); + it('returns false for saved objects', async function() { + const user = await this.User.build({ username: 'user' }).save(); + expect(user.isNewRecord).to.not.be.ok; }); - it('returns false for created objects', function() { - return this.User.create({ username: 'user' }).then(user => { - expect(user.isNewRecord).to.not.be.ok; - }); + it('returns false for created objects', async function() { + const user = await this.User.create({ username: 'user' }); + expect(user.isNewRecord).to.not.be.ok; }); - it('returns false for objects found by find method', function() { - return this.User.create({ username: 'user' }).then(() => { - return this.User.create({ username: 'user' }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.isNewRecord).to.not.be.ok; - }); - }); - }); + it('returns false for objects found by find method', async function() { + await this.User.create({ username: 'user' }); + const user = await this.User.create({ username: 'user' }); + const user0 = await this.User.findByPk(user.id); + expect(user0.isNewRecord).to.not.be.ok; }); - it('returns false for objects found by findAll method', function() { + it('returns false for objects found by findAll method', async function() { const users = []; for (let i = 0; i < 10; i++) { users[i] = { username: 'user' }; } - return this.User.bulkCreate(users).then(() => { - return this.User.findAll().then(users => { - users.forEach(u => { - expect(u.isNewRecord).to.not.be.ok; - }); - }); + await this.User.bulkCreate(users); + const users0 = await this.User.findAll(); + users0.forEach(u => { + expect(u.isNewRecord).to.not.be.ok; }); }); }); @@ -174,260 +165,225 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('allowNull date', () => { - it('should be just "null" and not Date with Invalid Date', function() { - return this.User.build({ username: 'a user' }).save().then(() => { - return this.User.findOne({ where: { username: 'a user' } }).then(user => { - expect(user.dateAllowNullTrue).to.be.null; - }); - }); + it('should be just "null" and not Date with Invalid Date', async function() { + await this.User.build({ username: 'a user' }).save(); + const user = await this.User.findOne({ where: { username: 'a user' } }); + expect(user.dateAllowNullTrue).to.be.null; }); - it('should be the same valid date when saving the date', function() { + it('should be the same valid date when saving the date', async function() { const date = new Date(); - return this.User.build({ username: 'a user', dateAllowNullTrue: date }).save().then(() => { - return this.User.findOne({ where: { username: 'a user' } }).then(user => { - expect(user.dateAllowNullTrue.toString()).to.equal(date.toString()); - }); - }); + await this.User.build({ username: 'a user', dateAllowNullTrue: date }).save(); + const user = await this.User.findOne({ where: { username: 'a user' } }); + expect(user.dateAllowNullTrue.toString()).to.equal(date.toString()); }); }); describe('super user boolean', () => { - it('should default to false', function() { - return this.User.build({ + it('should default to false', async function() { + await this.User.build({ username: 'a user' }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.false; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.false; }); - it('should override default when given truthy boolean', function() { - return this.User.build({ + it('should override default when given truthy boolean', async function() { + await this.User.build({ username: 'a user', isSuperUser: true }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.true; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.true; }); - it('should override default when given truthy boolean-string ("true")', function() { - return this.User.build({ + it('should override default when given truthy boolean-string ("true")', async function() { + await this.User.build({ username: 'a user', isSuperUser: 'true' }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.true; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.true; }); - it('should override default when given truthy boolean-int (1)', function() { - return this.User.build({ + it('should override default when given truthy boolean-int (1)', async function() { + await this.User.build({ username: 'a user', isSuperUser: 1 }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.true; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.true; }); - it('should throw error when given value of incorrect type', function() { + it('should throw error when given value of incorrect type', async function() { let callCount = 0; - return this.User.build({ - username: 'a user', - isSuperUser: 'INCORRECT_VALUE_TYPE' - }) - .save() - .then(() => { - callCount += 1; + try { + await this.User.build({ + username: 'a user', + isSuperUser: 'INCORRECT_VALUE_TYPE' }) - .catch(err => { - expect(callCount).to.equal(0); - expect(err).to.exist; - expect(err.message).to.exist; - }); + .save(); + + callCount += 1; + } catch (err) { + expect(callCount).to.equal(0); + expect(err).to.exist; + expect(err.message).to.exist; + } }); }); }); describe('complete', () => { - it('gets triggered if an error occurs', function() { - return this.User.findOne({ where: ['asdasdasd'] }).catch(err => { + it('gets triggered if an error occurs', async function() { + try { + await this.User.findOne({ where: ['asdasdasd'] }); + } catch (err) { expect(err).to.exist; expect(err.message).to.exist; - }); + } }); - it('gets triggered if everything was ok', function() { - return this.User.count().then(result => { - expect(result).to.exist; - }); + it('gets triggered if everything was ok', async function() { + const result = await this.User.count(); + expect(result).to.exist; }); }); describe('findAll', () => { - beforeEach(function() { + beforeEach(async function() { this.ParanoidUser = this.sequelize.define('ParanoidUser', { username: { type: DataTypes.STRING } }, { paranoid: true }); this.ParanoidUser.hasOne(this.ParanoidUser); - return this.ParanoidUser.sync({ force: true }); + await this.ParanoidUser.sync({ force: true }); }); - it('sql should have paranoid condition', function() { - return this.ParanoidUser.create({ username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll(); - }) - .then(users => { - expect(users).to.have.length(1); - return users[0].destroy(); - }) - .then(() => { - return this.ParanoidUser.findAll(); - }) - .then(users => { - expect(users).to.have.length(0); - }); + it('sql should have paranoid condition', async function() { + await this.ParanoidUser.create({ username: 'cuss' }); + const users0 = await this.ParanoidUser.findAll(); + expect(users0).to.have.length(1); + await users0[0].destroy(); + const users = await this.ParanoidUser.findAll(); + expect(users).to.have.length(0); }); - it('sequelize.and as where should include paranoid condition', function() { - return this.ParanoidUser.create({ username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.and({ - username: 'cuss' - }) - }); - }) - .then(users => { - expect(users).to.have.length(1); - return users[0].destroy(); + it('sequelize.and as where should include paranoid condition', async function() { + await this.ParanoidUser.create({ username: 'cuss' }); + + const users0 = await this.ParanoidUser.findAll({ + where: this.sequelize.and({ + username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.and({ - username: 'cuss' - }) - }); + }); + + expect(users0).to.have.length(1); + await users0[0].destroy(); + + const users = await this.ParanoidUser.findAll({ + where: this.sequelize.and({ + username: 'cuss' }) - .then(users => { - expect(users).to.have.length(0); - }); + }); + + expect(users).to.have.length(0); }); - it('sequelize.or as where should include paranoid condition', function() { - return this.ParanoidUser.create({ username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.or({ - username: 'cuss' - }) - }); - }) - .then(users => { - expect(users).to.have.length(1); - return users[0].destroy(); + it('sequelize.or as where should include paranoid condition', async function() { + await this.ParanoidUser.create({ username: 'cuss' }); + + const users0 = await this.ParanoidUser.findAll({ + where: this.sequelize.or({ + username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.or({ - username: 'cuss' - }) - }); + }); + + expect(users0).to.have.length(1); + await users0[0].destroy(); + + const users = await this.ParanoidUser.findAll({ + where: this.sequelize.or({ + username: 'cuss' }) - .then(users => { - expect(users).to.have.length(0); - }); - }); + }); - it('escapes a single single quotes properly in where clauses', function() { - return this.User - .create({ username: "user'name" }) - .then(() => { - return this.User.findAll({ - where: { username: "user'name" } - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal("user'name"); - }); - }); + expect(users).to.have.length(0); }); - it('escapes two single quotes properly in where clauses', function() { - return this.User - .create({ username: "user''name" }) - .then(() => { - return this.User.findAll({ - where: { username: "user''name" } - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal("user''name"); - }); - }); - }); + it('escapes a single single quotes properly in where clauses', async function() { + await this.User + .create({ username: "user'name" }); - it('returns the timestamps if no attributes have been specified', function() { - return this.User.create({ username: 'fnord' }).then(() => { - return this.User.findAll().then(users => { - expect(users[0].createdAt).to.exist; - }); + const users = await this.User.findAll({ + where: { username: "user'name" } }); + + expect(users.length).to.equal(1); + expect(users[0].username).to.equal("user'name"); }); - it('does not return the timestamps if the username attribute has been specified', function() { - return this.User.create({ username: 'fnord' }).then(() => { - return this.User.findAll({ attributes: ['username'] }).then(users => { - expect(users[0].createdAt).not.to.exist; - expect(users[0].username).to.exist; - }); + it('escapes two single quotes properly in where clauses', async function() { + await this.User + .create({ username: "user''name" }); + + const users = await this.User.findAll({ + where: { username: "user''name" } }); + + expect(users.length).to.equal(1); + expect(users[0].username).to.equal("user''name"); }); - it('creates the deletedAt property, when defining paranoid as true', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - expect(users[0].deletedAt).to.be.null; - }); - }); + it('returns the timestamps if no attributes have been specified', async function() { + await this.User.create({ username: 'fnord' }); + const users = await this.User.findAll(); + expect(users[0].createdAt).to.exist; }); - it('destroys a record with a primary key of something other than id', function() { + it('does not return the timestamps if the username attribute has been specified', async function() { + await this.User.create({ username: 'fnord' }); + const users = await this.User.findAll({ attributes: ['username'] }); + expect(users[0].createdAt).not.to.exist; + expect(users[0].username).to.exist; + }); + + it('creates the deletedAt property, when defining paranoid as true', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + expect(users[0].deletedAt).to.be.null; + }); + + it('destroys a record with a primary key of something other than id', async function() { const UserDestroy = this.sequelize.define('UserDestroy', { newId: { type: DataTypes.STRING, @@ -436,77 +392,58 @@ describe(Support.getTestDialectTeaser('Instance'), () => { email: DataTypes.STRING }); - return UserDestroy.sync().then(() => { - return UserDestroy.create({ newId: '123ABC', email: 'hello' }).then(() => { - return UserDestroy.findOne({ where: { email: 'hello' } }).then(user => { - return user.destroy(); - }); - }); - }); + await UserDestroy.sync(); + await UserDestroy.create({ newId: '123ABC', email: 'hello' }); + const user = await UserDestroy.findOne({ where: { email: 'hello' } }); + + await user.destroy(); }); - it('sets deletedAt property to a specific date when deleting an instance', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return users[0].destroy().then(() => { - expect(users[0].deletedAt.getMonth).to.exist; + it('sets deletedAt property to a specific date when deleting an instance', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + await users[0].destroy(); + expect(users[0].deletedAt.getMonth).to.exist; - return users[0].reload({ paranoid: false }).then(user => { - expect(user.deletedAt.getMonth).to.exist; - }); - }); - }); - }); + const user = await users[0].reload({ paranoid: false }); + expect(user.deletedAt.getMonth).to.exist; }); - it('keeps the deletedAt-attribute with value null, when running update', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return users[0].update({ username: 'newFnord' }).then(user => { - expect(user.deletedAt).not.to.exist; - }); - }); - }); + it('keeps the deletedAt-attribute with value null, when running update', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + const user = await users[0].update({ username: 'newFnord' }); + expect(user.deletedAt).not.to.exist; }); - it('keeps the deletedAt-attribute with value null, when updating associations', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return this.ParanoidUser.create({ username: 'linkedFnord' }).then(linkedUser => { - return users[0].setParanoidUser(linkedUser).then(user => { - expect(user.deletedAt).not.to.exist; - }); - }); - }); - }); + it('keeps the deletedAt-attribute with value null, when updating associations', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + const linkedUser = await this.ParanoidUser.create({ username: 'linkedFnord' }); + const user = await users[0].setParanoidUser(linkedUser); + expect(user.deletedAt).not.to.exist; }); - it('can reuse query option objects', function() { - return this.User.create({ username: 'fnord' }).then(() => { - const query = { where: { username: 'fnord' } }; - return this.User.findAll(query).then(users => { - expect(users[0].username).to.equal('fnord'); - return this.User.findAll(query).then(users => { - expect(users[0].username).to.equal('fnord'); - }); - }); - }); + it('can reuse query option objects', async function() { + await this.User.create({ username: 'fnord' }); + const query = { where: { username: 'fnord' } }; + const users = await this.User.findAll(query); + expect(users[0].username).to.equal('fnord'); + const users0 = await this.User.findAll(query); + expect(users0[0].username).to.equal('fnord'); }); }); describe('findOne', () => { - it('can reuse query option objects', function() { - return this.User.create({ username: 'fnord' }).then(() => { - const query = { where: { username: 'fnord' } }; - return this.User.findOne(query).then(user => { - expect(user.username).to.equal('fnord'); - return this.User.findOne(query).then(user => { - expect(user.username).to.equal('fnord'); - }); - }); - }); - }); - it('returns null for null, undefined, and unset boolean values', function() { + it('can reuse query option objects', async function() { + await this.User.create({ username: 'fnord' }); + const query = { where: { username: 'fnord' } }; + const user = await this.User.findOne(query); + expect(user.username).to.equal('fnord'); + const user0 = await this.User.findOne(query); + expect(user0.username).to.equal('fnord'); + }); + it('returns null for null, undefined, and unset boolean values', async function() { const Setting = this.sequelize.define('SettingHelper', { setting_key: DataTypes.STRING, bool_value: { type: DataTypes.BOOLEAN, allowNull: true }, @@ -514,28 +451,23 @@ describe(Support.getTestDialectTeaser('Instance'), () => { bool_value3: { type: DataTypes.BOOLEAN, allowNull: true } }, { timestamps: false, logging: false }); - return Setting.sync({ force: true }).then(() => { - return Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined }).then(() => { - return Setting.findOne({ where: { setting_key: 'test' } }).then(setting => { - expect(setting.bool_value).to.equal(null); - expect(setting.bool_value2).to.equal(null); - expect(setting.bool_value3).to.equal(null); - }); - }); - }); + await Setting.sync({ force: true }); + await Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined }); + const setting = await Setting.findOne({ where: { setting_key: 'test' } }); + expect(setting.bool_value).to.equal(null); + expect(setting.bool_value2).to.equal(null); + expect(setting.bool_value3).to.equal(null); }); }); describe('equals', () => { - it('can compare records with Date field', function() { - return this.User.create({ username: 'fnord' }).then(user1 => { - return this.User.findOne({ where: { username: 'fnord' } }).then(user2 => { - expect(user1.equals(user2)).to.be.true; - }); - }); + it('can compare records with Date field', async function() { + const user1 = await this.User.create({ username: 'fnord' }); + const user2 = await this.User.findOne({ where: { username: 'fnord' } }); + expect(user1.equals(user2)).to.be.true; }); - it('does not compare the existence of associations', function() { + it('does not compare the existence of associations', async function() { this.UserAssociationEqual = this.sequelize.define('UserAssociationEquals', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -549,80 +481,65 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.UserAssociationEqual.hasMany(this.ProjectAssociationEqual, { as: 'Projects', foreignKey: 'userId' }); this.ProjectAssociationEqual.belongsTo(this.UserAssociationEqual, { as: 'Users', foreignKey: 'userId' }); - return this.UserAssociationEqual.sync({ force: true }).then(() => { - return this.ProjectAssociationEqual.sync({ force: true }).then(() => { - return this.UserAssociationEqual.create({ username: 'jimhalpert' }).then(user1 => { - return this.ProjectAssociationEqual.create({ title: 'A Cool Project' }).then(project1 => { - return user1.setProjects([project1]).then(() => { - return this.UserAssociationEqual.findOne({ where: { username: 'jimhalpert' }, include: [{ model: this.ProjectAssociationEqual, as: 'Projects' }] }).then(user2 => { - return this.UserAssociationEqual.create({ username: 'pambeesly' }).then(user3 => { - expect(user1.get('Projects')).to.not.exist; - expect(user2.get('Projects')).to.exist; - expect(user1.equals(user2)).to.be.true; - expect(user2.equals(user1)).to.be.true; - expect(user1.equals(user3)).to.not.be.true; - expect(user3.equals(user1)).to.not.be.true; - }); - }); - }); - }); - }); - }); - }); + await this.UserAssociationEqual.sync({ force: true }); + await this.ProjectAssociationEqual.sync({ force: true }); + const user1 = await this.UserAssociationEqual.create({ username: 'jimhalpert' }); + const project1 = await this.ProjectAssociationEqual.create({ title: 'A Cool Project' }); + await user1.setProjects([project1]); + const user2 = await this.UserAssociationEqual.findOne({ where: { username: 'jimhalpert' }, include: [{ model: this.ProjectAssociationEqual, as: 'Projects' }] }); + const user3 = await this.UserAssociationEqual.create({ username: 'pambeesly' }); + expect(user1.get('Projects')).to.not.exist; + expect(user2.get('Projects')).to.exist; + expect(user1.equals(user2)).to.be.true; + expect(user2.equals(user1)).to.be.true; + expect(user1.equals(user3)).to.not.be.true; + expect(user3.equals(user1)).to.not.be.true; }); }); describe('values', () => { - it('returns all values', function() { + it('returns all values', async function() { const User = this.sequelize.define('UserHelper', { username: DataTypes.STRING }, { timestamps: false, logging: false }); - return User.sync().then(() => { - const user = User.build({ username: 'foo' }); - expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); - }); + await User.sync(); + const user = User.build({ username: 'foo' }); + expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); }); }); describe('isSoftDeleted', () => { - beforeEach(function() { + beforeEach(async function() { this.ParanoidUser = this.sequelize.define('ParanoidUser', { username: { type: DataTypes.STRING } }, { paranoid: true }); - return this.ParanoidUser.sync({ force: true }); + await this.ParanoidUser.sync({ force: true }); }); - it('should return false when model is just created', function() { - return this.ParanoidUser.create({ username: 'foo' }).then(user => { - expect(user.isSoftDeleted()).to.be.false; - }); + it('should return false when model is just created', async function() { + const user = await this.ParanoidUser.create({ username: 'foo' }); + expect(user.isSoftDeleted()).to.be.false; }); - it('returns false if user is not soft deleted', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - expect(users[0].isSoftDeleted()).to.be.false; - }); - }); + it('returns false if user is not soft deleted', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + expect(users[0].isSoftDeleted()).to.be.false; }); - it('returns true if user is soft deleted', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return users[0].destroy().then(() => { - expect(users[0].isSoftDeleted()).to.be.true; + it('returns true if user is soft deleted', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + await users[0].destroy(); + expect(users[0].isSoftDeleted()).to.be.true; - return users[0].reload({ paranoid: false }).then(user => { - expect(user.isSoftDeleted()).to.be.true; - }); - }); - }); - }); + const user = await users[0].reload({ paranoid: false }); + expect(user.isSoftDeleted()).to.be.true; }); - it('works with custom `deletedAt` field name', function() { + it('works with custom `deletedAt` field name', async function() { this.ParanoidUserWithCustomDeletedAt = this.sequelize.define('ParanoidUserWithCustomDeletedAt', { username: { type: DataTypes.STRING } }, { @@ -632,21 +549,16 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.ParanoidUserWithCustomDeletedAt.hasOne(this.ParanoidUser); - return this.ParanoidUserWithCustomDeletedAt.sync({ force: true }).then(() => { - return this.ParanoidUserWithCustomDeletedAt.create({ username: 'fnord' }).then(() => { - return this.ParanoidUserWithCustomDeletedAt.findAll().then(users => { - expect(users[0].isSoftDeleted()).to.be.false; + await this.ParanoidUserWithCustomDeletedAt.sync({ force: true }); + await this.ParanoidUserWithCustomDeletedAt.create({ username: 'fnord' }); + const users = await this.ParanoidUserWithCustomDeletedAt.findAll(); + expect(users[0].isSoftDeleted()).to.be.false; - return users[0].destroy().then(() => { - expect(users[0].isSoftDeleted()).to.be.true; + await users[0].destroy(); + expect(users[0].isSoftDeleted()).to.be.true; - return users[0].reload({ paranoid: false }).then(user => { - expect(user.isSoftDeleted()).to.be.true; - }); - }); - }); - }); - }); + const user = await users[0].reload({ paranoid: false }); + expect(user.isSoftDeleted()).to.be.true; }); }); @@ -656,7 +568,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { await expect(user.restore()).to.be.rejectedWith(Error, 'Model is not paranoid'); }); - it('restores a previously deleted model', function() { + it('restores a previously deleted model', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -669,124 +581,106 @@ describe(Support.getTestDialectTeaser('Instance'), () => { { username: 'Paul', secretValue: '43' }, { username: 'Bob', secretValue: '44' }]; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate(data); - }).then(() => { - return ParanoidUser.findOne({ where: { secretValue: '42' } }); - }).then(user => { - return user.destroy().then(() => { - return user.restore(); - }); - }).then(() => { - return ParanoidUser.findOne({ where: { secretValue: '42' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.username).to.equal('Peter'); - }); + await ParanoidUser.sync({ force: true }); + await ParanoidUser.bulkCreate(data); + const user0 = await ParanoidUser.findOne({ where: { secretValue: '42' } }); + await user0.destroy(); + await user0.restore(); + const user = await ParanoidUser.findOne({ where: { secretValue: '42' } }); + expect(user).to.be.ok; + expect(user.username).to.equal('Peter'); }); - it('supports custom deletedAt field', function() { + it('supports custom deletedAt field', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, destroyTime: DataTypes.DATE }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - return user.restore(); - }).then(user => { - expect(user.destroyTime).to.not.be.ok; - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.not.be.ok; - expect(user.deletedAt).to.not.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + expect(user1.destroyTime).to.be.ok; + expect(user1.deletedAt).to.not.be.ok; + const user0 = await user1.restore(); + expect(user0.destroyTime).to.not.be.ok; + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.destroyTime).to.not.be.ok; + expect(user.deletedAt).to.not.be.ok; }); - it('supports custom deletedAt field name', function() { + it('supports custom deletedAt field name', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, deletedAt: { type: DataTypes.DATE, field: 'deleted_at' } }, { paranoid: true }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.deletedAt).to.be.ok; - expect(user.dataValues.deleted_at).to.not.be.ok; - return user.restore(); - }).then(user => { - expect(user.dataValues.deletedAt).to.not.be.ok; - expect(user.dataValues.deleted_at).to.not.be.ok; - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - expect(user.deleted_at).to.not.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + expect(user1.dataValues.deletedAt).to.be.ok; + expect(user1.dataValues.deleted_at).to.not.be.ok; + const user0 = await user1.restore(); + expect(user0.dataValues.deletedAt).to.not.be.ok; + expect(user0.dataValues.deleted_at).to.not.be.ok; + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.deletedAt).to.not.be.ok; + expect(user.deleted_at).to.not.be.ok; }); - it('supports custom deletedAt field and database column', function() { + it('supports custom deletedAt field and database column', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, destroyTime: { type: DataTypes.DATE, field: 'destroy_time' } }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.destroyTime).to.be.ok; - expect(user.dataValues.deletedAt).to.not.be.ok; - expect(user.dataValues.destroy_time).to.not.be.ok; - return user.restore(); - }).then(user => { - expect(user.dataValues.destroyTime).to.not.be.ok; - expect(user.dataValues.destroy_time).to.not.be.ok; - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.not.be.ok; - expect(user.destroy_time).to.not.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + expect(user1.dataValues.destroyTime).to.be.ok; + expect(user1.dataValues.deletedAt).to.not.be.ok; + expect(user1.dataValues.destroy_time).to.not.be.ok; + const user0 = await user1.restore(); + expect(user0.dataValues.destroyTime).to.not.be.ok; + expect(user0.dataValues.destroy_time).to.not.be.ok; + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.destroyTime).to.not.be.ok; + expect(user.destroy_time).to.not.be.ok; }); - it('supports custom default value', function() { + it('supports custom default value', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, deletedAt: { type: DataTypes.DATE, defaultValue: new Date(0) } }, { paranoid: true }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - return user.restore(); - }).then(user => { - expect(user.dataValues.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + const user0 = await user1.restore(); + expect(user0.dataValues.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); }); }); }); diff --git a/test/integration/instance.validations.test.js b/test/integration/instance.validations.test.js index 192fbbc08638..52b4b3deccdf 100644 --- a/test/integration/instance.validations.test.js +++ b/test/integration/instance.validations.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('#update', () => { - it('should allow us to update specific columns without tripping the validations', function() { + it('should allow us to update specific columns without tripping the validations', async function() { const User = this.sequelize.define('model', { username: Sequelize.STRING, email: { @@ -22,20 +22,17 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob', email: 'hello@world.com' }).then(user => { - return User - .update({ username: 'toni' }, { where: { id: user.id } }) - .then(() => { - return User.findByPk(1).then(user => { - expect(user.username).to.equal('toni'); - }); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ username: 'bob', email: 'hello@world.com' }); + + await User + .update({ username: 'toni' }, { where: { id: user.id } }); + + const user0 = await User.findByPk(1); + expect(user0.username).to.equal('toni'); }); - it('should be able to emit an error upon updating when a validation has failed from an instance', function() { + it('should be able to emit an error upon updating when a validation has failed from an instance', async function() { const Model = this.sequelize.define('model', { name: { type: Sequelize.STRING, @@ -46,17 +43,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Model.sync({ force: true }).then(() => { - return Model.create({ name: 'World' }).then(model => { - return model.update({ name: '' }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); - }); - }); - }); + await Model.sync({ force: true }); + const model = await Model.create({ name: 'World' }); + + try { + await model.update({ name: '' }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); + } }); - it('should be able to emit an error upon updating when a validation has failed from the factory', function() { + it('should be able to emit an error upon updating when a validation has failed from the factory', async function() { const Model = this.sequelize.define('model', { name: { type: Sequelize.STRING, @@ -67,17 +65,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Model.sync({ force: true }).then(() => { - return Model.create({ name: 'World' }).then(() => { - return Model.update({ name: '' }, { where: { id: 1 } }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); - }); - }); - }); + await Model.sync({ force: true }); + await Model.create({ name: 'World' }); + + try { + await Model.update({ name: '' }, { where: { id: 1 } }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); + } }); - it('should enforce a unique constraint', function() { + it('should enforce a unique constraint', async function() { const Model = this.sequelize.define('model', { uniqueName: { type: Sequelize.STRING, unique: 'uniqueName' } }); @@ -85,24 +84,19 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { { uniqueName: 'unique name one' }, { uniqueName: 'unique name two' } ]; - return Model.sync({ force: true }) - .then(() => { - return Model.create(records[0]); - }).then(instance => { - expect(instance).to.be.ok; - return Model.create(records[1]); - }).then(instance => { - expect(instance).to.be.ok; - return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName'); - expect(err.errors[0].message).to.include('must be unique'); - }); - }); - - it('should allow a custom unique constraint error message', function() { + await Model.sync({ force: true }); + const instance0 = await Model.create(records[0]); + expect(instance0).to.be.ok; + const instance = await Model.create(records[1]); + expect(instance).to.be.ok; + const err = await expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; + expect(err).to.be.an.instanceOf(Error); + expect(err.errors).to.have.length(1); + expect(err.errors[0].path).to.include('uniqueName'); + expect(err.errors[0].message).to.include('must be unique'); + }); + + it('should allow a custom unique constraint error message', async function() { const Model = this.sequelize.define('model', { uniqueName: { type: Sequelize.STRING, @@ -113,24 +107,19 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { { uniqueName: 'unique name one' }, { uniqueName: 'unique name two' } ]; - return Model.sync({ force: true }) - .then(() => { - return Model.create(records[0]); - }).then(instance => { - expect(instance).to.be.ok; - return Model.create(records[1]); - }).then(instance => { - expect(instance).to.be.ok; - return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName'); - expect(err.errors[0].message).to.equal('custom unique error message'); - }); - }); - - it('should handle multiple unique messages correctly', function() { + await Model.sync({ force: true }); + const instance0 = await Model.create(records[0]); + expect(instance0).to.be.ok; + const instance = await Model.create(records[1]); + expect(instance).to.be.ok; + const err = await expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; + expect(err).to.be.an.instanceOf(Error); + expect(err.errors).to.have.length(1); + expect(err.errors[0].path).to.include('uniqueName'); + expect(err.errors[0].message).to.equal('custom unique error message'); + }); + + it('should handle multiple unique messages correctly', async function() { const Model = this.sequelize.define('model', { uniqueName1: { type: Sequelize.STRING, @@ -146,31 +135,26 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { { uniqueName1: 'unique name one', uniqueName2: 'this is ok' }, { uniqueName1: 'this is ok', uniqueName2: 'unique name one' } ]; - return Model.sync({ force: true }) - .then(() => { - return Model.create(records[0]); - }).then(instance => { - expect(instance).to.be.ok; - return expect(Model.create(records[1])).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName1'); - expect(err.errors[0].message).to.equal('custom unique error message 1'); - - return expect(Model.create(records[2])).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName2'); - expect(err.errors[0].message).to.equal('custom unique error message 2'); - }); + await Model.sync({ force: true }); + const instance = await Model.create(records[0]); + expect(instance).to.be.ok; + const err0 = await expect(Model.create(records[1])).to.be.rejected; + expect(err0).to.be.an.instanceOf(Error); + expect(err0.errors).to.have.length(1); + expect(err0.errors[0].path).to.include('uniqueName1'); + expect(err0.errors[0].message).to.equal('custom unique error message 1'); + + const err = await expect(Model.create(records[2])).to.be.rejected; + expect(err).to.be.an.instanceOf(Error); + expect(err.errors).to.have.length(1); + expect(err.errors[0].path).to.include('uniqueName2'); + expect(err.errors[0].message).to.equal('custom unique error message 2'); }); }); describe('#create', () => { describe('generic', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: { type: Sequelize.STRING, @@ -189,34 +173,31 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { Project.hasOne(Task); Task.belongsTo(Project); - return this.sequelize.sync({ force: true }).then(() => { - this.Project = Project; - this.Task = Task; - }); + await this.sequelize.sync({ force: true }); + this.Project = Project; + this.Task = Task; }); - it('correctly throws an error using create method ', function() { - return this.Project.create({ name: 'nope' }).catch(err => { + it('correctly throws an error using create method ', async function() { + try { + await this.Project.create({ name: 'nope' }); + } catch (err) { expect(err).to.have.ownProperty('name'); - }); + } }); - it('correctly validates using create method ', function() { - return this.Project.create({}).then(project => { - return this.Task.create({ something: 1 }).then(task => { - return project.setTask(task).then(task => { - expect(task.ProjectId).to.not.be.null; - return task.setProject(project).then(project => { - expect(project.ProjectId).to.not.be.null; - }); - }); - }); - }); + it('correctly validates using create method ', async function() { + const project = await this.Project.create({}); + const task = await this.Task.create({ something: 1 }); + const task0 = await project.setTask(task); + expect(task0.ProjectId).to.not.be.null; + const project0 = await task0.setProject(project); + expect(project0.ProjectId).to.not.be.null; }); }); describe('explicitly validating primary/auto incremented columns', () => { - it('should emit an error when we try to enter in a string for the id key without validation arguments', function() { + it('should emit an error when we try to enter in a string for the id key without validation arguments', async function() { const User = this.sequelize.define('UserId', { id: { type: Sequelize.INTEGER, @@ -228,15 +209,17 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ id: 'helloworld' }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('id')[0].message).to.equal('Validation isInt on id failed'); - }); - }); + await User.sync({ force: true }); + + try { + await User.create({ id: 'helloworld' }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('id')[0].message).to.equal('Validation isInt on id failed'); + } }); - it('should emit an error when we try to enter in a string for an auto increment key (not named id)', function() { + it('should emit an error when we try to enter in a string for an auto increment key (not named id)', async function() { const User = this.sequelize.define('UserId', { username: { type: Sequelize.INTEGER, @@ -248,16 +231,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'helloworldhelloworld' }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('username')[0].message).to.equal('Username must be an integer!'); - }); - }); + await User.sync({ force: true }); + + try { + await User.create({ username: 'helloworldhelloworld' }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('username')[0].message).to.equal('Username must be an integer!'); + } }); describe('primaryKey with the name as id with arguments for it\'s validatio', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('UserId', { id: { type: Sequelize.INTEGER, @@ -269,36 +254,40 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should emit an error when we try to enter in a string for the id key with validation arguments', function() { - return this.User.create({ id: 'helloworld' }).catch(err => { + it('should emit an error when we try to enter in a string for the id key with validation arguments', async function() { + try { + await this.User.create({ id: 'helloworld' }); + } catch (err) { expect(err).to.be.an.instanceOf(Error); expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - }); + } }); - it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', function() { + it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', async function() { const user = this.User.build({ id: 'helloworld' }); - return expect(user.validate()).to.be.rejected.then(err => { - expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - }); + const err = await expect(user.validate()).to.be.rejected; + expect(err.get('id')[0].message).to.equal('ID must be an integer!'); }); - it('should emit an error when we try to .save()', function() { + it('should emit an error when we try to .save()', async function() { const user = this.User.build({ id: 'helloworld' }); - return user.save().catch(err => { + + try { + await user.save(); + } catch (err) { expect(err).to.be.an.instanceOf(Error); expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - }); + } }); }); }); describe('pass all paths when validating', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: { type: Sequelize.STRING, @@ -325,25 +314,25 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { Project.hasOne(Task); Task.belongsTo(Project); - return Project.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - this.Project = Project; - this.Task = Task; - }); - }); + await Project.sync({ force: true }); + await Task.sync({ force: true }); + this.Project = Project; + this.Task = Task; }); - it('produce 3 errors', function() { - return this.Project.create({}).catch(err => { + it('produce 3 errors', async function() { + try { + await this.Project.create({}); + } catch (err) { expect(err).to.be.an.instanceOf(Error); delete err.stack; // longStackTraces expect(err.errors).to.have.length(3); - }); + } }); }); describe('not null schema validation', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: { type: Sequelize.STRING, @@ -354,13 +343,12 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - this.Project = Project; - }); + await this.sequelize.sync({ force: true }); + this.Project = Project; }); - it('correctly throws an error using create method ', function() { - return this.Project.create({}) + it('correctly throws an error using create method ', async function() { + await this.Project.create({}) .then(() => { throw new Error('Validation must be failed'); }, () => { @@ -368,18 +356,20 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('correctly throws an error using create method with default generated messages', function() { - return this.Project.create({}).catch(err => { + it('correctly throws an error using create method with default generated messages', async function() { + try { + await this.Project.create({}); + } catch (err) { expect(err).to.have.property('name', 'SequelizeValidationError'); expect(err.message).equal('notNull Violation: Project.name cannot be null'); expect(err.errors).to.be.an('array').and.have.length(1); expect(err.errors[0]).to.have.property('message', 'Project.name cannot be null'); - }); + } }); }); }); - it('correctly validates using custom validation methods', function() { + it('correctly validates using custom validation methods', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: { type: Sequelize.STRING, @@ -397,43 +387,39 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingUser = User.build({ name: '3' }); - return expect(failingUser.validate()).to.be.rejected.then(error => { - expect(error).to.be.an.instanceOf(Error); - expect(error.get('name')[0].message).to.equal("name should equal '2'"); + const error = await expect(failingUser.validate()).to.be.rejected; + expect(error).to.be.an.instanceOf(Error); + expect(error.get('name')[0].message).to.equal("name should equal '2'"); - const successfulUser = User.build({ name: '2' }); - return expect(successfulUser.validate()).not.to.be.rejected; - }); + const successfulUser = User.build({ name: '2' }); + + await expect(successfulUser.validate()).not.to.be.rejected; }); - it('supports promises with custom validation methods', function() { + it('supports promises with custom validation methods', async function() { const User = this.sequelize.define(`User${config.rand()}`, { name: { type: Sequelize.STRING, validate: { - customFn(val) { - return User.findAll() - .then(() => { - if (val === 'error') { - throw new Error('Invalid username'); - } - }); + async customFn(val) { + await User.findAll(); + if (val === 'error') { + throw new Error('Invalid username'); + } } } } }); - return User.sync().then(() => { - return expect(User.build({ name: 'error' }).validate()).to.be.rejected.then(error => { - expect(error).to.be.instanceof(Sequelize.ValidationError); - expect(error.get('name')[0].message).to.equal('Invalid username'); + await User.sync(); + const error = await expect(User.build({ name: 'error' }).validate()).to.be.rejected; + expect(error).to.be.instanceof(Sequelize.ValidationError); + expect(error.get('name')[0].message).to.equal('Invalid username'); - return expect(User.build({ name: 'no error' }).validate()).not.to.be.rejected; - }); - }); + await expect(User.build({ name: 'no error' }).validate()).not.to.be.rejected; }); - it('skips other validations if allowNull is true and the value is null', function() { + it('skips other validations if allowNull is true and the value is null', async function() { const User = this.sequelize.define(`User${config.rand()}`, { age: { type: Sequelize.INTEGER, @@ -444,16 +430,15 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User + const error = await expect(User .build({ age: -1 }) .validate()) - .to.be.rejected - .then(error => { - expect(error.get('age')[0].message).to.equal('must be positive'); - }); + .to.be.rejected; + + expect(error.get('age')[0].message).to.equal('must be positive'); }); - it('validates a model with custom model-wide validation methods', function() { + it('validates a model with custom model-wide validation methods', async function() { const Foo = this.sequelize.define(`Foo${config.rand()}`, { field1: { type: Sequelize.INTEGER, @@ -473,21 +458,20 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(Foo + const error = await expect(Foo .build({ field1: null, field2: null }) .validate()) - .to.be.rejected - .then(error => { - expect(error.get('xnor')[0].message).to.equal('xnor failed'); - - return expect(Foo - .build({ field1: 33, field2: null }) - .validate()) - .not.to.be.rejected; - }); + .to.be.rejected; + + expect(error.get('xnor')[0].message).to.equal('xnor failed'); + + await expect(Foo + .build({ field1: 33, field2: null }) + .validate()) + .not.to.be.rejected; }); - it('validates model with a validator whose arg is an Array successfully twice in a row', function() { + it('validates model with a validator whose arg is an Array successfully twice in a row', async function() { const Foo = this.sequelize.define(`Foo${config.rand()}`, { bar: { type: Sequelize.STRING, @@ -497,12 +481,11 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }), foo = Foo.build({ bar: 'a' }); - return expect(foo.validate()).not.to.be.rejected.then(() => { - return expect(foo.validate()).not.to.be.rejected; - }); + await expect(foo.validate()).not.to.be.rejected; + await expect(foo.validate()).not.to.be.rejected; }); - it('validates enums', function() { + it('validates enums', async function() { const values = ['value1', 'value2']; const Bar = this.sequelize.define(`Bar${config.rand()}`, { @@ -517,13 +500,12 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingBar = Bar.build({ field: 'value3' }); - return expect(failingBar.validate()).to.be.rejected.then(errors => { - expect(errors.get('field')).to.have.length(1); - expect(errors.get('field')[0].message).to.equal('Validation isIn on field failed'); - }); + const errors = await expect(failingBar.validate()).to.be.rejected; + expect(errors.get('field')).to.have.length(1); + expect(errors.get('field')[0].message).to.equal('Validation isIn on field failed'); }); - it('skips validations for the given fields', function() { + it('skips validations for the given fields', async function() { const values = ['value1', 'value2']; const Bar = this.sequelize.define(`Bar${config.rand()}`, { @@ -538,10 +520,10 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingBar = Bar.build({ field: 'value3' }); - return expect(failingBar.validate({ skip: ['field'] })).not.to.be.rejected; + await expect(failingBar.validate({ skip: ['field'] })).not.to.be.rejected; }); - it('skips validations for fields with value that is SequelizeMethod', function() { + it('skips validations for fields with value that is SequelizeMethod', async function() { const values = ['value1', 'value2']; const Bar = this.sequelize.define(`Bar${config.rand()}`, { @@ -556,10 +538,10 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const failingBar = Bar.build({ field: this.sequelize.literal('5 + 1') }); - return expect(failingBar.validate()).not.to.be.rejected; + await expect(failingBar.validate()).not.to.be.rejected; }); - it('raises an error if saving a different value into an immutable field', function() { + it('raises an error if saving a different value into an immutable field', async function() { const User = this.sequelize.define('User', { name: { type: Sequelize.STRING, @@ -569,18 +551,15 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ name: 'RedCat' }).then(user => { - expect(user.getDataValue('name')).to.equal('RedCat'); - user.setDataValue('name', 'YellowCat'); - return expect(user.save()).to.be.rejected.then(errors => { - expect(errors.get('name')[0].message).to.eql('Validation isImmutable on name failed'); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ name: 'RedCat' }); + expect(user.getDataValue('name')).to.equal('RedCat'); + user.setDataValue('name', 'YellowCat'); + const errors = await expect(user.save()).to.be.rejected; + expect(errors.get('name')[0].message).to.eql('Validation isImmutable on name failed'); }); - it('allows setting an immutable field if the record is unsaved', function() { + it('allows setting an immutable field if the record is unsaved', async function() { const User = this.sequelize.define('User', { name: { type: Sequelize.STRING, @@ -594,94 +573,94 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(user.getDataValue('name')).to.equal('RedCat'); user.setDataValue('name', 'YellowCat'); - return expect(user.validate()).not.to.be.rejected; + await expect(user.validate()).not.to.be.rejected; }); - it('raises an error for array on a STRING', function() { + it('raises an error for array on a STRING', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING } }); - return expect(User.build({ + await expect(User.build({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for array on a STRING(20)', function() { + it('raises an error for array on a STRING(20)', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING(20) } }); - return expect(User.build({ + await expect(User.build({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for array on a TEXT', function() { + it('raises an error for array on a TEXT', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.TEXT } }); - return expect(User.build({ + await expect(User.build({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for {} on a STRING', function() { + it('raises an error for {} on a STRING', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING } }); - return expect(User.build({ + await expect(User.build({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for {} on a STRING(20)', function() { + it('raises an error for {} on a STRING(20)', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING(20) } }); - return expect(User.build({ + await expect(User.build({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for {} on a TEXT', function() { + it('raises an error for {} on a TEXT', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.TEXT } }); - return expect(User.build({ + await expect(User.build({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('does not raise an error for null on a STRING (where null is allowed)', function() { + it('does not raise an error for null on a STRING (where null is allowed)', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING } }); - return expect(User.build({ + await expect(User.build({ email: null }).validate()).not.to.be.rejected; }); - it('validates VIRTUAL fields', function() { + it('validates VIRTUAL fields', async function() { const User = this.sequelize.define('user', { password_hash: Sequelize.STRING, salt: Sequelize.STRING, @@ -701,7 +680,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Promise.all([ + await Promise.all([ expect(User.build({ password: 'short', salt: '42' @@ -715,7 +694,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { ]); }); - it('allows me to add custom validation functions to validator.js', function() { + it('allows me to add custom validation functions to validator.js', async function() { this.sequelize.Validator.extend('isExactly7Characters', val => { return val.length === 7; }); @@ -729,14 +708,14 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(User.build({ + await expect(User.build({ name: 'abcdefg' - }).validate()).not.to.be.rejected.then(() => { - return expect(User.build({ - name: 'a' - }).validate()).to.be.rejected; - }).then(errors => { - expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters on name failed'); - }); + }).validate()).not.to.be.rejected; + + const errors = await expect(User.build({ + name: 'a' + }).validate()).to.be.rejected; + + expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters on name failed'); }); }); diff --git a/test/integration/json.test.js b/test/integration/json.test.js index c831bbf286b3..37a511e5d4db 100644 --- a/test/integration/json.test.js +++ b/test/integration/json.test.js @@ -11,7 +11,7 @@ const chai = require('chai'), describe('model', () => { if (current.dialect.supports.JSON) { describe('json', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, emergency_contact: DataTypes.JSON, @@ -20,21 +20,19 @@ describe('model', () => { this.Order = this.sequelize.define('Order'); this.Order.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should tell me that a column is json', function() { - return this.sequelize.queryInterface.describeTable('Users') - .then(table => { - // expected for mariadb 10.4 : https://jira.mariadb.org/browse/MDEV-15558 - if (dialect !== 'mariadb') { - expect(table.emergency_contact.type).to.equal('JSON'); - } - }); + it('should tell me that a column is json', async function() { + const table = await this.sequelize.queryInterface.describeTable('Users'); + // expected for mariadb 10.4 : https://jira.mariadb.org/browse/MDEV-15558 + if (dialect !== 'mariadb') { + expect(table.emergency_contact.type).to.equal('JSON'); + } }); - it('should use a placeholder for json with insert', function() { - return this.User.create({ + it('should use a placeholder for json with insert', async function() { + await this.User.create({ username: 'bob', emergency_contact: { name: 'joe', phones: [1337, 42] } }, { @@ -49,246 +47,226 @@ describe('model', () => { }); }); - it('should insert json using a custom field name', function() { + it('should insert json using a custom field name', async function() { this.UserFields = this.sequelize.define('UserFields', { emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' } }); - return this.UserFields.sync({ force: true }).then(() => { - return this.UserFields.create({ - emergencyContact: { name: 'joe', phones: [1337, 42] } - }).then(user => { - expect(user.emergencyContact.name).to.equal('joe'); - }); + await this.UserFields.sync({ force: true }); + + const user = await this.UserFields.create({ + emergencyContact: { name: 'joe', phones: [1337, 42] } }); + + expect(user.emergencyContact.name).to.equal('joe'); }); - it('should update json using a custom field name', function() { + it('should update json using a custom field name', async function() { this.UserFields = this.sequelize.define('UserFields', { emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' } }); - return this.UserFields.sync({ force: true }).then(() => { - return this.UserFields.create({ - emergencyContact: { name: 'joe', phones: [1337, 42] } - }).then(user => { - user.emergencyContact = { name: 'larry' }; - return user.save(); - }).then(user => { - expect(user.emergencyContact.name).to.equal('larry'); - }); + await this.UserFields.sync({ force: true }); + + const user0 = await this.UserFields.create({ + emergencyContact: { name: 'joe', phones: [1337, 42] } }); + + user0.emergencyContact = { name: 'larry' }; + const user = await user0.save(); + expect(user.emergencyContact.name).to.equal('larry'); }); - it('should be able retrieve json value as object', function() { + it('should be able retrieve json value as object', async function() { const emergencyContact = { name: 'kate', phone: 1337 }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ where: { username: 'swen' }, attributes: ['emergency_contact'] }); - }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user0.emergency_contact).to.eql(emergencyContact); + const user = await this.User.findOne({ where: { username: 'swen' }, attributes: ['emergency_contact'] }); + expect(user.emergency_contact).to.eql(emergencyContact); }); - it('should be able to retrieve element of array by index', function() { + it('should be able to retrieve element of array by index', async function() { const emergencyContact = { name: 'kate', phones: [1337, 42] }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.phones[1]'), 'firstEmergencyNumber']] - }); - }) - .then(user => { - expect(parseInt(user.getDataValue('firstEmergencyNumber'), 10)).to.equal(42); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user0.emergency_contact).to.eql(emergencyContact); + + const user = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.phones[1]'), 'firstEmergencyNumber']] + }); + + expect(parseInt(user.getDataValue('firstEmergencyNumber'), 10)).to.equal(42); }); - it('should be able to retrieve root level value of an object by key', function() { + it('should be able to retrieve root level value of an object by key', async function() { const emergencyContact = { kate: 1337 }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.kate'), 'katesNumber']] - }); - }) - .then(user => { - expect(parseInt(user.getDataValue('katesNumber'), 10)).to.equal(1337); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user0.emergency_contact).to.eql(emergencyContact); + + const user = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.kate'), 'katesNumber']] + }); + + expect(parseInt(user.getDataValue('katesNumber'), 10)).to.equal(1337); }); - it('should be able to retrieve nested value of an object by path', function() { + it('should be able to retrieve nested value of an object by path', async function() { const emergencyContact = { kate: { email: 'kate@kate.com', phones: [1337, 42] } }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.kate.email'), 'katesEmail']] - }); - }).then(user => { - expect(user.getDataValue('katesEmail')).to.equal('kate@kate.com'); - }).then(() => { - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.kate.phones[1]'), 'katesFirstPhone']] - }); - }).then(user => { - expect(parseInt(user.getDataValue('katesFirstPhone'), 10)).to.equal(42); - }); + const user1 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user1.emergency_contact).to.eql(emergencyContact); + + const user0 = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.kate.email'), 'katesEmail']] + }); + + expect(user0.getDataValue('katesEmail')).to.equal('kate@kate.com'); + + const user = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.kate.phones[1]'), 'katesFirstPhone']] + }); + + expect(parseInt(user.getDataValue('katesFirstPhone'), 10)).to.equal(42); }); - it('should be able to retrieve a row based on the values of the json document', function() { - return Promise.all([ + it('should be able to retrieve a row based on the values of the json document', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json('emergency_contact.name', 'kate'), - attributes: ['username', 'emergency_contact'] - }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('kate'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json('emergency_contact.name', 'kate'), + attributes: ['username', 'emergency_contact'] }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using the nested query language', function() { - return Promise.all([ + it('should be able to query using the nested query language', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json({ emergency_contact: { name: 'kate' } }) - }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('kate'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json({ emergency_contact: { name: 'kate' } }) }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using dot notation', function() { - return Promise.all([ + it('should be able to query using dot notation', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.name', 'joe') }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('joe'); - }); + ]); + + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact.name', 'joe') }); + expect(user.emergency_contact.name).to.equal('joe'); }); - it('should be able to query using dot notation with uppercase name', function() { - return Promise.all([ + it('should be able to query using dot notation with uppercase name', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - attributes: [[Sequelize.json('emergencyContact.name'), 'contactName']], - where: Sequelize.json('emergencyContact.name', 'joe') - }); - }).then(user => { - expect(user.get('contactName')).to.equal('joe'); + ]); + + const user = await this.User.findOne({ + attributes: [[Sequelize.json('emergencyContact.name'), 'contactName']], + where: Sequelize.json('emergencyContact.name', 'joe') }); + + expect(user.get('contactName')).to.equal('joe'); }); - it('should be able to query array using property accessor', function() { - return Promise.all([ + it('should be able to query array using property accessor', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: ['kate', 'joe'] }), this.User.create({ username: 'anna', emergency_contact: [{ name: 'joe' }] }) - ]).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.0', 'kate') }); - }).then(user => { - expect(user.username).to.equal('swen'); - }).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact[0].name', 'joe') }); - }).then(user => { - expect(user.username).to.equal('anna'); - }); + ]); + + const user0 = await this.User.findOne({ where: Sequelize.json('emergency_contact.0', 'kate') }); + expect(user0.username).to.equal('swen'); + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact[0].name', 'joe') }); + expect(user.username).to.equal('anna'); }); - it('should be able to store values that require JSON escaping', function() { + it('should be able to store values that require JSON escaping', async function() { const text = 'Multi-line \'$string\' needing "escaping" for $$ and $1 type values'; - return this.User.create({ + const user0 = await this.User.create({ username: 'swen', emergency_contact: { value: text } - }).then(user => { - expect(user.isNewRecord).to.equal(false); - }).then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); - }).then(user => { - expect(user.username).to.equal('swen'); }); + + expect(user0.isNewRecord).to.equal(false); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); - it('should be able to findOrCreate with values that require JSON escaping', function() { + it('should be able to findOrCreate with values that require JSON escaping', async function() { const text = 'Multi-line \'$string\' needing "escaping" for $$ and $1 type values'; - return this.User.findOrCreate({ + const user0 = await this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } - }).then(user => { - expect(!user.isNewRecord).to.equal(true); - }).then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); - }).then(user => { - expect(user.username).to.equal('swen'); }); + + expect(!user0.isNewRecord).to.equal(true); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); // JSONB Supports this, but not JSON in postgres/mysql if (current.dialect.name === 'sqlite') { - it('should be able to find with just string', function() { - return this.User.create({ + it('should be able to find with just string', async function() { + await this.User.create({ username: 'swen123', emergency_contact: 'Unknown' - }).then(() => { - return this.User.findOne({ where: { - emergency_contact: 'Unknown' - } }); - }).then(user => { - expect(user.username).to.equal('swen123'); }); + + const user = await this.User.findOne({ where: { + emergency_contact: 'Unknown' + } }); + + expect(user.username).to.equal('swen123'); }); } - it('should be able retrieve json value with nested include', function() { - return this.User.create({ + it('should be able retrieve json value with nested include', async function() { + const user = await this.User.create({ emergency_contact: { name: 'kate' } - }).then(user => { - return this.Order.create({ UserId: user.id }); - }).then(() => { - return this.Order.findAll({ - attributes: ['id'], - include: [{ - model: this.User, - attributes: [ - [this.sequelize.json('emergency_contact.name'), 'katesName'] - ] - }] - }); - }).then(orders => { - expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); + + await this.Order.create({ UserId: user.id }); + + const orders = await this.Order.findAll({ + attributes: ['id'], + include: [{ + model: this.User, + attributes: [ + [this.sequelize.json('emergency_contact.name'), 'katesName'] + ] + }] + }); + + expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); }); } if (current.dialect.supports.JSONB) { describe('jsonb', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, emergency_contact: DataTypes.JSONB @@ -296,29 +274,29 @@ describe('model', () => { this.Order = this.sequelize.define('Order'); this.Order.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should be able retrieve json value with nested include', function() { - return this.User.create({ + it('should be able retrieve json value with nested include', async function() { + const user = await this.User.create({ emergency_contact: { name: 'kate' } - }).then(user => { - return this.Order.create({ UserId: user.id }); - }).then(() => { - return this.Order.findAll({ - attributes: ['id'], - include: [{ - model: this.User, - attributes: [ - [this.sequelize.json('emergency_contact.name'), 'katesName'] - ] - }] - }); - }).then(orders => { - expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); + + await this.Order.create({ UserId: user.id }); + + const orders = await this.Order.findAll({ + attributes: ['id'], + include: [{ + model: this.User, + attributes: [ + [this.sequelize.json('emergency_contact.name'), 'katesName'] + ] + }] + }); + + expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); }); } diff --git a/test/integration/model.test.js b/test/integration/model.test.js index c980b1c71eca..c6bff55502a4 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -284,13 +284,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); await User.sync({ force: true }); - await Promise.all([ - User.create({ username: 'tobi', email: 'tobi@tobi.me' }), - User.create({ username: 'tobi', email: 'tobi@tobi.me' }) - ]).catch(err => { + + try { + await Promise.all([ + User.create({ username: 'tobi', email: 'tobi@tobi.me' }), + User.create({ username: 'tobi', email: 'tobi@tobi.me' }) + ]); + } catch (err) { if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err.message).to.equal('User and email must be unique'); - }); + } }); // If you use migrations to create unique indexes that have explicit names and/or contain fields @@ -319,13 +322,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { email: { type: Sequelize.STRING, unique: 'user_and_email_index' } }); - await Promise.all([ - User.create({ user_id: 1, email: 'tobi@tobi.me' }), - User.create({ user_id: 1, email: 'tobi@tobi.me' }) - ]).catch(err => { + try { + await Promise.all([ + User.create({ user_id: 1, email: 'tobi@tobi.me' }), + User.create({ user_id: 1, email: 'tobi@tobi.me' }) + ]); + } catch (err) { if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err.message).to.equal('User and email must be unique'); - }); + } }); it('should allow the user to specify indexes in options', async function() { @@ -1934,9 +1939,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); afterEach(async function() { - await this.sequelize.dropSchema('schema_test') - .finally(() => this.sequelize.dropSchema('special')) - .finally(() => this.sequelize.dropSchema('prefix')); + try { + await this.sequelize.dropSchema('schema_test'); + } finally { + await this.sequelize.dropSchema('special'); + await this.sequelize.dropSchema('prefix'); + } }); it('should be able to drop with schemas', async function() { @@ -2065,80 +2073,79 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(test).to.be.true; }); - it('should be able to create and update records under any valid schematic', function() { + it('should be able to create and update records under any valid schematic', async function() { let logged = 0; - return this.UserPublic.sync({ force: true }).then(UserPublicSync => { - return UserPublicSync.create({ age: 3 }, { - logging: UserPublic => { - logged++; - if (dialect === 'postgres') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('"special"."UserSpecials"'); - expect(UserPublic).to.include('INSERT INTO "UserPublics"'); - } else if (dialect === 'sqlite') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); - expect(UserPublic).to.include('INSERT INTO `UserPublics`'); - } else if (dialect === 'mssql') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('[special].[UserSpecials]'); - expect(UserPublic).to.include('INSERT INTO [UserPublics]'); - } else if (dialect === 'mariadb') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special`.`UserSpecials`'); - expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1); - } else { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); - expect(UserPublic).to.include('INSERT INTO `UserPublics`'); - } + const UserPublicSync = await this.UserPublic.sync({ force: true }); + + await UserPublicSync.create({ age: 3 }, { + logging: UserPublic => { + logged++; + if (dialect === 'postgres') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('"special"."UserSpecials"'); + expect(UserPublic).to.include('INSERT INTO "UserPublics"'); + } else if (dialect === 'sqlite') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); + expect(UserPublic).to.include('INSERT INTO `UserPublics`'); + } else if (dialect === 'mssql') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('[special].[UserSpecials]'); + expect(UserPublic).to.include('INSERT INTO [UserPublics]'); + } else if (dialect === 'mariadb') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special`.`UserSpecials`'); + expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1); + } else { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); + expect(UserPublic).to.include('INSERT INTO `UserPublics`'); } - }).then(() => { - return this.UserSpecialSync.schema('special').create({ age: 3 }, { - logging(UserSpecial) { - logged++; - if (dialect === 'postgres') { - expect(UserSpecial).to.include('INSERT INTO "special"."UserSpecials"'); - } else if (dialect === 'sqlite') { - expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); - } else if (dialect === 'mssql') { - expect(UserSpecial).to.include('INSERT INTO [special].[UserSpecials]'); - } else if (dialect === 'mariadb') { - expect(UserSpecial).to.include('INSERT INTO `special`.`UserSpecials`'); - } else { - expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); - } - } - }).then(UserSpecial => { - return UserSpecial.update({ age: 5 }, { - logging(user) { - logged++; - if (dialect === 'postgres') { - expect(user).to.include('UPDATE "special"."UserSpecials"'); - } else if (dialect === 'mssql') { - expect(user).to.include('UPDATE [special].[UserSpecials]'); - } else if (dialect === 'mariadb') { - expect(user).to.include('UPDATE `special`.`UserSpecials`'); - } else { - expect(user).to.include('UPDATE `special.UserSpecials`'); - } - } - }); - }); - }).then(() => { - expect(logged).to.equal(3); - }); + } + }); + + const UserSpecial = await this.UserSpecialSync.schema('special').create({ age: 3 }, { + logging(UserSpecial) { + logged++; + if (dialect === 'postgres') { + expect(UserSpecial).to.include('INSERT INTO "special"."UserSpecials"'); + } else if (dialect === 'sqlite') { + expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); + } else if (dialect === 'mssql') { + expect(UserSpecial).to.include('INSERT INTO [special].[UserSpecials]'); + } else if (dialect === 'mariadb') { + expect(UserSpecial).to.include('INSERT INTO `special`.`UserSpecials`'); + } else { + expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); + } + } + }); + + await UserSpecial.update({ age: 5 }, { + logging(user) { + logged++; + if (dialect === 'postgres') { + expect(user).to.include('UPDATE "special"."UserSpecials"'); + } else if (dialect === 'mssql') { + expect(user).to.include('UPDATE [special].[UserSpecials]'); + } else if (dialect === 'mariadb') { + expect(user).to.include('UPDATE `special`.`UserSpecials`'); + } else { + expect(user).to.include('UPDATE `special.UserSpecials`'); + } + } }); + + expect(logged).to.equal(3); }); }); describe('references', () => { - beforeEach(function() { + beforeEach(async function() { this.Author = this.sequelize.define('author', { firstName: Sequelize.STRING }); - return this.sequelize.getQueryInterface().dropTable('posts', { force: true }).then(() => { - return this.sequelize.getQueryInterface().dropTable('authors', { force: true }); - }).then(() => { - return this.Author.sync(); - }); + await this.sequelize.getQueryInterface().dropTable('posts', { force: true }); + await this.sequelize.getQueryInterface().dropTable('authors', { force: true }); + + await this.Author.sync(); }); - it('uses an existing dao factory and references the author table', function() { + it('uses an existing dao factory and references the author table', async function() { const authorIdColumn = { type: Sequelize.INTEGER, references: { model: this.Author, key: 'id' } }; const Post = this.sequelize.define('post', { @@ -2150,7 +2157,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.belongsTo(this.Author); // The posts table gets dropped in the before filter. - return Post.sync({ logging: _.once(sql => { + await Post.sync({ logging: _.once(sql => { if (dialect === 'postgres') { expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/); } else if (dialect === 'mysql' || dialect === 'mariadb') { @@ -2165,7 +2172,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }) }); }); - it('uses a table name as a string and references the author table', function() { + it('uses a table name as a string and references the author table', async function() { const authorIdColumn = { type: Sequelize.INTEGER, references: { model: 'authors', key: 'id' } }; const Post = this.sequelize.define('post', { title: Sequelize.STRING, authorId: authorIdColumn }); @@ -2174,7 +2181,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.belongsTo(this.Author); // The posts table gets dropped in the before filter. - return Post.sync({ logging: _.once(sql => { + await Post.sync({ logging: _.once(sql => { if (dialect === 'postgres') { expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/); } else if (dialect === 'mysql' || dialect === 'mariadb') { @@ -2189,7 +2196,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }) }); }); - it('emits an error event as the referenced table name is invalid', function() { + it('emits an error event as the referenced table name is invalid', async function() { const authorIdColumn = { type: Sequelize.INTEGER, references: { model: '4uth0r5', key: 'id' } }; const Post = this.sequelize.define('post', { title: Sequelize.STRING, authorId: authorIdColumn }); @@ -2197,8 +2204,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Author.hasMany(Post); Post.belongsTo(this.Author); - // The posts table gets dropped in the before filter. - return Post.sync().then(() => { + try { + // The posts table gets dropped in the before filter. + await Post.sync(); if (dialect === 'sqlite') { // sorry ... but sqlite is too stupid to understand whats going on ... expect(1).to.equal(1); @@ -2206,9 +2214,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { // the parser should not end up here ... expect(2).to.equal(1); } - - return; - }).catch(err => { + } catch (err) { if (dialect === 'mysql') { // MySQL 5.7 or above doesn't support POINT EMPTY if (semver.gte(current.options.databaseVersion, '5.6.0')) { @@ -2228,10 +2234,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { } else { throw new Error('Undefined dialect!'); } - }); + } }); - it('works with comments', function() { + it('works with comments', async function() { // Test for a case where the comment was being moved to the end of the table when there was also a reference on the column, see #1521 const Member = this.sequelize.define('Member', {}); const idColumn = { @@ -2245,47 +2251,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.define('Profile', { id: idColumn }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); }); describe('blob', () => { - beforeEach(function() { + beforeEach(async function() { this.BlobUser = this.sequelize.define('blobUser', { data: Sequelize.BLOB }); - return this.BlobUser.sync({ force: true }); + await this.BlobUser.sync({ force: true }); }); describe('buffers', () => { - it('should be able to take a buffer as parameter to a BLOB field', function() { - return this.BlobUser.create({ + it('should be able to take a buffer as parameter to a BLOB field', async function() { + const user = await this.BlobUser.create({ data: Buffer.from('Sequelize') - }).then(user => { - expect(user).to.be.ok; }); + + expect(user).to.be.ok; }); - it('should return a buffer when fetching a blob', function() { - return this.BlobUser.create({ + it('should return a buffer when fetching a blob', async function() { + const user = await this.BlobUser.create({ data: Buffer.from('Sequelize') - }).then(user => { - return this.BlobUser.findByPk(user.id).then(user => { - expect(user.data).to.be.an.instanceOf(Buffer); - expect(user.data.toString()).to.have.string('Sequelize'); - }); }); + + const user0 = await this.BlobUser.findByPk(user.id); + expect(user0.data).to.be.an.instanceOf(Buffer); + expect(user0.data.toString()).to.have.string('Sequelize'); }); - it('should work when the database returns null', function() { - return this.BlobUser.create({ + it('should work when the database returns null', async function() { + const user = await this.BlobUser.create({ // create a null column - }).then(user => { - return this.BlobUser.findByPk(user.id).then(user => { - expect(user.data).to.be.null; - }); }); + + const user0 = await this.BlobUser.findByPk(user.id); + expect(user0.data).to.be.null; }); }); @@ -2296,23 +2300,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { // data is passed in, in string form? Very unclear, and very different. describe('strings', () => { - it('should be able to take a string as parameter to a BLOB field', function() { - return this.BlobUser.create({ + it('should be able to take a string as parameter to a BLOB field', async function() { + const user = await this.BlobUser.create({ data: 'Sequelize' - }).then(user => { - expect(user).to.be.ok; }); + + expect(user).to.be.ok; }); - it('should return a buffer when fetching a BLOB, even when the BLOB was inserted as a string', function() { - return this.BlobUser.create({ + it('should return a buffer when fetching a BLOB, even when the BLOB was inserted as a string', async function() { + const user = await this.BlobUser.create({ data: 'Sequelize' - }).then(user => { - return this.BlobUser.findByPk(user.id).then(user => { - expect(user.data).to.be.an.instanceOf(Buffer); - expect(user.data.toString()).to.have.string('Sequelize'); - }); }); + + const user0 = await this.BlobUser.findByPk(user.id); + expect(user0.data).to.be.an.instanceOf(Buffer); + expect(user0.data.toString()).to.have.string('Sequelize'); }); }); } @@ -2321,96 +2324,92 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('paranoid is true and where is an array', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }, { paranoid: true }); this.Project = this.sequelize.define('Project', { title: DataTypes.STRING }, { paranoid: true }); this.Project.belongsToMany(this.User, { through: 'project_user' }); this.User.belongsToMany(this.Project, { through: 'project_user' }); - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate([{ - username: 'leia' - }, { - username: 'luke' - }, { - username: 'vader' - }]).then(() => { - return this.Project.bulkCreate([{ - title: 'republic' - }, { - title: 'empire' - }]).then(() => { - return this.User.findAll().then(users => { - return this.Project.findAll().then(projects => { - const leia = users[0], - luke = users[1], - vader = users[2], - republic = projects[0], - empire = projects[1]; - return leia.setProjects([republic]).then(() => { - return luke.setProjects([republic]).then(() => { - return vader.setProjects([empire]).then(() => { - return leia.destroy(); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await this.User.bulkCreate([{ + username: 'leia' + }, { + username: 'luke' + }, { + username: 'vader' + }]); + + await this.Project.bulkCreate([{ + title: 'republic' + }, { + title: 'empire' + }]); + + const users = await this.User.findAll(); + const projects = await this.Project.findAll(); + const leia = users[0], + luke = users[1], + vader = users[2], + republic = projects[0], + empire = projects[1]; + await leia.setProjects([republic]); + await luke.setProjects([republic]); + await vader.setProjects([empire]); + + await leia.destroy(); }); - it('should not fail when array contains Sequelize.or / and', function() { - return this.User.findAll({ + it('should not fail when array contains Sequelize.or / and', async function() { + const res = await this.User.findAll({ where: [ this.sequelize.or({ username: 'vader' }, { username: 'luke' }), this.sequelize.and({ id: [1, 2, 3] }) ] - }) - .then(res => { - expect(res).to.have.length(2); - }); + }); + + expect(res).to.have.length(2); }); - it('should fail when array contains strings', function() { - return expect(this.User.findAll({ + it('should fail when array contains strings', async function() { + await expect(this.User.findAll({ where: ['this is a mistake', ['dont do it!']] })).to.eventually.be.rejectedWith(Error, 'Support for literal replacements in the `where` object has been removed.'); }); - it('should not fail with an include', function() { - return this.User.findAll({ + it('should not fail with an include', async function() { + const users = await this.User.findAll({ where: this.sequelize.literal(`${this.sequelize.queryInterface.queryGenerator.quoteIdentifiers('Projects.title')} = ${this.sequelize.queryInterface.queryGenerator.escape('republic')}`), include: [ { model: this.Project } ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('luke'); }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('luke'); }); - it('should not overwrite a specified deletedAt by setting paranoid: false', function() { + it('should not overwrite a specified deletedAt by setting paranoid: false', async function() { let tableName = ''; if (this.User.name) { tableName = `${this.sequelize.queryInterface.queryGenerator.quoteIdentifier(this.User.name)}.`; } - return this.User.findAll({ + + const users = await this.User.findAll({ paranoid: false, where: this.sequelize.literal(`${tableName + this.sequelize.queryInterface.queryGenerator.quoteIdentifier('deletedAt')} IS NOT NULL `), include: [ { model: this.Project } ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('leia'); }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('leia'); }); - it('should not overwrite a specified deletedAt (complex query) by setting paranoid: false', function() { - return this.User.findAll({ + it('should not overwrite a specified deletedAt (complex query) by setting paranoid: false', async function() { + const res = await this.User.findAll({ paranoid: false, where: [ this.sequelize.or({ username: 'leia' }, { username: 'luke' }), @@ -2419,98 +2418,94 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.or({ deletedAt: null }, { deletedAt: { [Op.gt]: new Date(0) } }) ) ] - }) - .then(res => { - expect(res).to.have.length(2); - }); + }); + + expect(res).to.have.length(2); }); }); if (dialect !== 'sqlite' && current.dialect.supports.transactions) { - it('supports multiple async transactions', function() { + it('supports multiple async transactions', async function() { this.timeout(90000); - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - const testAsync = function() { - return sequelize.transaction().then(t => { - return User.create({ - username: 'foo' - }, { - transaction: t - }).then(() => { - return User.findAll({ - where: { - username: 'foo' - } - }).then(users => { - expect(users).to.have.length(0); - }); - }).then(() => { - return User.findAll({ - where: { - username: 'foo' - }, - transaction: t - }).then(users => { - expect(users).to.have.length(1); - }); - }).then(() => { - return t; - }); - }).then(t => { - return t.rollback(); - }); - }; - return User.sync({ force: true }).then(() => { - const tasks = []; - for (let i = 0; i < 1000; i++) { - tasks.push(testAsync); + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + const testAsync = async function() { + const t0 = await sequelize.transaction(); + + await User.create({ + username: 'foo' + }, { + transaction: t0 + }); + + const users0 = await User.findAll({ + where: { + username: 'foo' } - return pMap(tasks, entry => { - return entry(); - }, { - // Needs to be one less than ??? else the non transaction query won't ever get a connection - concurrency: (sequelize.config.pool && sequelize.config.pool.max || 5) - 1 - }); }); + + expect(users0).to.have.length(0); + + const users = await User.findAll({ + where: { + username: 'foo' + }, + transaction: t0 + }); + + expect(users).to.have.length(1); + const t = t0; + return t.rollback(); + }; + await User.sync({ force: true }); + const tasks = []; + for (let i = 0; i < 1000; i++) { + tasks.push(testAsync); + } + + await pMap(tasks, entry => { + return entry(); + }, { + // Needs to be one less than ??? else the non transaction query won't ever get a connection + concurrency: (sequelize.config.pool && sequelize.config.pool.max || 5) - 1 }); }); } describe('Unique', () => { - it('should set unique when unique is true', function() { + it('should set unique when unique is true', async function() { const uniqueTrue = this.sequelize.define('uniqueTrue', { str: { type: Sequelize.STRING, unique: true } }); - return uniqueTrue.sync({ force: true, logging: _.after(2, _.once(s => { + await uniqueTrue.sync({ force: true, logging: _.after(2, _.once(s => { expect(s).to.match(/UNIQUE/); })) }); }); - it('should not set unique when unique is false', function() { + it('should not set unique when unique is false', async function() { const uniqueFalse = this.sequelize.define('uniqueFalse', { str: { type: Sequelize.STRING, unique: false } }); - return uniqueFalse.sync({ force: true, logging: _.after(2, _.once(s => { + await uniqueFalse.sync({ force: true, logging: _.after(2, _.once(s => { expect(s).not.to.match(/UNIQUE/); })) }); }); - it('should not set unique when unique is unset', function() { + it('should not set unique when unique is unset', async function() { const uniqueUnset = this.sequelize.define('uniqueUnset', { str: { type: Sequelize.STRING } }); - return uniqueUnset.sync({ force: true, logging: _.after(2, _.once(s => { + await uniqueUnset.sync({ force: true, logging: _.after(2, _.once(s => { expect(s).not.to.match(/UNIQUE/); })) }); }); }); - it('should be possible to use a key named UUID as foreign key', function() { + it('should be possible to use a key named UUID as foreign key', async function() { this.sequelize.define('project', { UserId: { type: Sequelize.STRING, @@ -2534,11 +2529,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('bulkCreate', () => { - it('errors - should return array of errors if validate and individualHooks are true', function() { + it('errors - should return array of errors if validate and individualHooks are true', async function() { const data = [{ username: null }, { username: null }, { username: null }]; @@ -2554,15 +2549,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - expect(user.bulkCreate(data, { - validate: true, - individualHooks: true - })).to.be.rejectedWith(errors.AggregateError); - }); + await this.sequelize.sync({ force: true }); + expect(user.bulkCreate(data, { + validate: true, + individualHooks: true + })).to.be.rejectedWith(errors.AggregateError); }); - it('should not use setter when renaming fields in dataValues', function() { + it('should not use setter when renaming fields in dataValues', async function() { const user = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -2582,13 +2576,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); const data = [{ username: 'jon' }]; - return this.sequelize.sync({ force: true }).then(() => { - return user.bulkCreate(data).then(() => { - return user.findAll().then(users1 => { - expect(users1[0].username).to.equal('jon'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await user.bulkCreate(data); + const users1 = await user.findAll(); + expect(users1[0].username).to.equal('jon'); }); }); }); diff --git a/test/integration/replication.test.js b/test/integration/replication.test.js index 2bcc9c2bcadf..65c1ba483351 100644 --- a/test/integration/replication.test.js +++ b/test/integration/replication.test.js @@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('Replication'), () => { let sandbox; let readSpy, writeSpy; - beforeEach(function() { + beforeEach(async function() { sandbox = sinon.createSandbox(); this.sequelize = Support.getSequelizeInstance(null, null, null, { @@ -33,11 +33,9 @@ describe(Support.getTestDialectTeaser('Replication'), () => { } }); - return this.User.sync({ force: true }) - .then(() => { - readSpy = sandbox.spy(this.sequelize.connectionManager.pool.read, 'acquire'); - writeSpy = sandbox.spy(this.sequelize.connectionManager.pool.write, 'acquire'); - }); + await this.User.sync({ force: true }); + readSpy = sandbox.spy(this.sequelize.connectionManager.pool.read, 'acquire'); + writeSpy = sandbox.spy(this.sequelize.connectionManager.pool.write, 'acquire'); }); afterEach(() => { @@ -54,25 +52,25 @@ describe(Support.getTestDialectTeaser('Replication'), () => { chai.expect(readSpy.notCalled).eql(true); } - it('should be able to make a write', function() { - return this.User.create({ + it('should be able to make a write', async function() { + await expectWriteCalls(await this.User.create({ firstName: Math.random().toString() - }).then(expectWriteCalls); + })); }); - it('should be able to make a read', function() { - return this.User.findAll().then(expectReadCalls); + it('should be able to make a read', async function() { + await expectReadCalls(await this.User.findAll()); }); - it('should run read-only transactions on the replica', function() { - return this.sequelize.transaction({ readOnly: true }, transaction => { + it('should run read-only transactions on the replica', async function() { + await expectReadCalls(await this.sequelize.transaction({ readOnly: true }, transaction => { return this.User.findAll({ transaction }); - }).then(expectReadCalls); + })); }); - it('should run non-read-only transactions on the primary', function() { - return this.sequelize.transaction(transaction => { + it('should run non-read-only transactions on the primary', async function() { + await expectWriteCalls(await this.sequelize.transaction(transaction => { return this.User.findAll({ transaction }); - }).then(expectWriteCalls); + })); }); }); diff --git a/test/integration/schema.test.js b/test/integration/schema.test.js index f5756b2a8e51..4bfe96b97a80 100644 --- a/test/integration/schema.test.js +++ b/test/integration/schema.test.js @@ -6,43 +6,37 @@ const chai = require('chai'), DataTypes = require('../../lib/data-types'); describe(Support.getTestDialectTeaser('Schema'), () => { - beforeEach(function() { - return this.sequelize.createSchema('testschema'); + beforeEach(async function() { + await this.sequelize.createSchema('testschema'); }); - afterEach(function() { - return this.sequelize.dropSchema('testschema'); + afterEach(async function() { + await this.sequelize.dropSchema('testschema'); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: { type: DataTypes.INTEGER } }, { schema: 'testschema' }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('supports increment', function() { - return this.User.create({ aNumber: 1 }).then(user => { - return user.increment('aNumber', { by: 3 }); - }).then(result => { - return result.reload(); - }).then(user => { - expect(user).to.be.ok; - expect(user.aNumber).to.be.equal(4); - }); + it('supports increment', async function() { + const user0 = await this.User.create({ aNumber: 1 }); + const result = await user0.increment('aNumber', { by: 3 }); + const user = await result.reload(); + expect(user).to.be.ok; + expect(user.aNumber).to.be.equal(4); }); - it('supports decrement', function() { - return this.User.create({ aNumber: 10 }).then(user => { - return user.decrement('aNumber', { by: 3 }); - }).then(result => { - return result.reload(); - }).then(user => { - expect(user).to.be.ok; - expect(user.aNumber).to.be.equal(7); - }); + it('supports decrement', async function() { + const user0 = await this.User.create({ aNumber: 10 }); + const result = await user0.decrement('aNumber', { by: 3 }); + const user = await result.reload(); + expect(user).to.be.ok; + expect(user.aNumber).to.be.equal(7); }); }); diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index c66f56d6113b..cb50a36c27f1 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -77,8 +77,8 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (dialect !== 'sqlite') { describe('authenticate', () => { describe('with valid credentials', () => { - it('triggers the success event', function() { - return this.sequelize.authenticate(); + it('triggers the success event', async function() { + await this.sequelize.authenticate(); }); }); @@ -88,41 +88,44 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { this.sequelizeWithInvalidConnection = new Sequelize('wat', 'trololo', 'wow', options); }); - it('triggers the error event', function() { - return this - .sequelizeWithInvalidConnection - .authenticate() - .catch(err => { - expect(err).to.not.be.null; - }); + it('triggers the error event', async function() { + try { + await this + .sequelizeWithInvalidConnection + .authenticate(); + } catch (err) { + expect(err).to.not.be.null; + } }); - it('triggers an actual RangeError or ConnectionError', function() { - return this - .sequelizeWithInvalidConnection - .authenticate() - .catch(err => { - expect( - err instanceof RangeError || - err instanceof Sequelize.ConnectionError - ).to.be.ok; - }); + it('triggers an actual RangeError or ConnectionError', async function() { + try { + await this + .sequelizeWithInvalidConnection + .authenticate(); + } catch (err) { + expect( + err instanceof RangeError || + err instanceof Sequelize.ConnectionError + ).to.be.ok; + } }); - it('triggers the actual adapter error', function() { - return this - .sequelizeWithInvalidConnection - .authenticate() - .catch(err => { - console.log(err); - expect( - err.message.includes('connect ECONNREFUSED') || - err.message.includes('invalid port number') || - err.message.match(/should be >=? 0 and < 65536/) || - err.message.includes('Login failed for user') || - err.message.includes('must be > 0 and < 65536') - ).to.be.ok; - }); + it('triggers the actual adapter error', async function() { + try { + await this + .sequelizeWithInvalidConnection + .authenticate(); + } catch (err) { + console.log(err); + expect( + err.message.includes('connect ECONNREFUSED') || + err.message.includes('invalid port number') || + err.message.match(/should be >=? 0 and < 65536/) || + err.message.includes('Login failed for user') || + err.message.includes('must be > 0 and < 65536') + ).to.be.ok; + } }); }); @@ -131,38 +134,41 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { this.sequelizeWithInvalidCredentials = new Sequelize('localhost', 'wtf', 'lol', this.sequelize.options); }); - it('triggers the error event', function() { - return this - .sequelizeWithInvalidCredentials - .authenticate() - .catch(err => { - expect(err).to.not.be.null; - }); + it('triggers the error event', async function() { + try { + await this + .sequelizeWithInvalidCredentials + .authenticate(); + } catch (err) { + expect(err).to.not.be.null; + } }); - it('triggers an actual sequlize error', function() { - return this - .sequelizeWithInvalidCredentials - .authenticate() - .catch(err => { - expect(err).to.be.instanceof(Sequelize.Error); - }); + it('triggers an actual sequlize error', async function() { + try { + await this + .sequelizeWithInvalidCredentials + .authenticate(); + } catch (err) { + expect(err).to.be.instanceof(Sequelize.Error); + } }); - it('triggers the error event when using replication', () => { - return new Sequelize('sequelize', null, null, { - dialect, - replication: { - read: { - host: 'localhost', - username: 'omg', - password: 'lol' + it('triggers the error event when using replication', async () => { + try { + await new Sequelize('sequelize', null, null, { + dialect, + replication: { + read: { + host: 'localhost', + username: 'omg', + password: 'lol' + } } - } - }).authenticate() - .catch(err => { - expect(err).to.not.be.null; - }); + }).authenticate(); + } catch (err) { + expect(err).to.not.be.null; + } }); }); }); @@ -221,7 +227,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { console.log.restore && console.log.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -237,37 +243,37 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { qq('createdAt') }, ${qq('updatedAt') }) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('executes a query the internal way', function() { - return this.sequelize.query(this.insertQuery, { raw: true }); + it('executes a query the internal way', async function() { + await this.sequelize.query(this.insertQuery, { raw: true }); }); - it('executes a query if only the sql is passed', function() { - return this.sequelize.query(this.insertQuery); + it('executes a query if only the sql is passed', async function() { + await this.sequelize.query(this.insertQuery); }); - it('executes a query if a placeholder value is an array', function() { - return this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + + it('executes a query if a placeholder value is an array', async function() { + await this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + `${qq('createdAt')}, ${qq('updatedAt')}) VALUES ?;`, { replacements: [[ ['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'], ['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'] ]] - }) - .then(() => this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: this.sequelize.QueryTypes.SELECT - })) - .then(rows => { - expect(rows).to.be.lengthOf(2); - expect(rows[0].username).to.be.equal('john'); - expect(rows[1].username).to.be.equal('michael'); - }); + }); + + const rows = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: this.sequelize.QueryTypes.SELECT + }); + + expect(rows).to.be.lengthOf(2); + expect(rows[0].username).to.be.equal('john'); + expect(rows[1].username).to.be.equal('michael'); }); describe('retry', () => { - it('properly bind parameters on extra retries', function() { + it('properly bind parameters on extra retries', async function() { const payload = { username: 'test', createdAt: '2010-10-10 00:00:00', @@ -276,7 +282,9 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { const spy = sinon.spy(); - return expect(this.User.create(payload).then(() => this.sequelize.query(` + await this.User.create(payload); + + await expect(this.sequelize.query(` INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt); `, { bind: payload, @@ -287,41 +295,40 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { /Validation/ ] } - }))).to.be.rejectedWith(Sequelize.UniqueConstraintError).then(() => { - expect(spy.callCount).to.eql(3); - }); + })).to.be.rejectedWith(Sequelize.UniqueConstraintError); + + expect(spy.callCount).to.eql(3); }); }); describe('logging', () => { - it('executes a query with global benchmarking option and custom logger', () => { + it('executes a query with global benchmarking option and custom logger', async () => { const logger = sinon.spy(); const sequelize = Support.createSequelizeInstance({ logging: logger, benchmark: true }); - return sequelize.query('select 1;').then(() => { - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); + await sequelize.query('select 1;'); + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); + expect(typeof logger.args[0][1] === 'number').to.be.true; }); - it('executes a query with benchmarking option and custom logger', function() { + it('executes a query with benchmarking option and custom logger', async function() { const logger = sinon.spy(); - return this.sequelize.query('select 1;', { + await this.sequelize.query('select 1;', { logging: logger, benchmark: true - }).then(() => { - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); - expect(typeof logger.args[0][1] === 'number').to.be.true; }); + + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); + expect(typeof logger.args[0][1] === 'number').to.be.true; }); describe('log sql when set logQueryParameters', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize = Support.createSequelizeInstance({ benchmark: true, logQueryParameters: true @@ -342,175 +349,163 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { timestamps: false }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('add parameters in log sql', function() { + it('add parameters in log sql', async function() { let createSql, updateSql; - return this.User.create({ + + const user = await this.User.create({ username: 'john', emailAddress: 'john@gmail.com' }, { logging: s =>{ createSql = s; } - }).then(user=>{ - user.username = 'li'; - return user.save({ - logging: s =>{ - updateSql = s; - } - }); - }).then(()=>{ - expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); - expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); }); + + user.username = 'li'; + + await user.save({ + logging: s =>{ + updateSql = s; + } + }); + + expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); + expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); }); - it('add parameters in log sql when use bind value', function() { + it('add parameters in log sql when use bind value', async function() { let logSql; const typeCast = dialect === 'postgres' ? '::text' : ''; - return this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }) - .then(()=>{ - expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); - }); + await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }); + expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); }); }); }); - it('executes select queries correctly', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`select * from ${qq(this.User.tableName)}`); - }).then(([users]) => { - expect(users.map(u => { return u.username; })).to.include('john'); - }); + it('executes select queries correctly', async function() { + await this.sequelize.query(this.insertQuery); + const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`); + expect(users.map(u => { return u.username; })).to.include('john'); }); - it('executes select queries correctly when quoteIdentifiers is false', function() { + it('executes select queries correctly when quoteIdentifiers is false', async function() { const seq = Object.create(this.sequelize); seq.options.quoteIdentifiers = false; - return seq.query(this.insertQuery).then(() => { - return seq.query(`select * from ${qq(this.User.tableName)}`); - }).then(([users]) => { - expect(users.map(u => { return u.username; })).to.include('john'); - }); + await seq.query(this.insertQuery); + const [users] = await seq.query(`select * from ${qq(this.User.tableName)}`); + expect(users.map(u => { return u.username; })).to.include('john'); }); - it('executes select query with dot notation results', function() { - return this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`).then(() => { - return this.sequelize.query(this.insertQuery); - }).then(() => { - return this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); - }).then(([users]) => { - expect(users).to.deep.equal([{ 'user.username': 'john' }]); - }); + it('executes select query with dot notation results', async function() { + await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); + await this.sequelize.query(this.insertQuery); + const [users] = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); + expect(users).to.deep.equal([{ 'user.username': 'john' }]); }); - it('executes select query with dot notation results and nest it', function() { - return this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`).then(() => { - return this.sequelize.query(this.insertQuery); - }).then(() => { - return this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); - }).then(users => { - expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); - }); + it('executes select query with dot notation results and nest it', async function() { + await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); + await this.sequelize.query(this.insertQuery); + const users = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); + expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); }); if (dialect === 'mysql') { - it('executes stored procedures', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query('DROP PROCEDURE IF EXISTS foo').then(() => { - return this.sequelize.query( - `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` - ).then(() => { - return this.sequelize.query('CALL foo()').then(users => { - expect(users.map(u => { return u.username; })).to.include('john'); - }); - }); - }); - }); + it('executes stored procedures', async function() { + await this.sequelize.query(this.insertQuery); + await this.sequelize.query('DROP PROCEDURE IF EXISTS foo'); + + await this.sequelize.query( + `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` + ); + + const users = await this.sequelize.query('CALL foo()'); + expect(users.map(u => { return u.username; })).to.include('john'); }); } else { console.log('FIXME: I want to be supported in this dialect as well :-('); } - it('uses the passed model', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User - }); - }).then(users => { - expect(users[0]).to.be.instanceof(this.User); + it('uses the passed model', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + model: this.User }); + + expect(users[0]).to.be.instanceof(this.User); }); - it('maps the field names to attributes based on the passed model', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User, - mapToModel: true - }); - }).then(users => { - expect(users[0].emailAddress).to.be.equal('john@gmail.com'); + it('maps the field names to attributes based on the passed model', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + model: this.User, + mapToModel: true }); + + expect(users[0].emailAddress).to.be.equal('john@gmail.com'); }); - it('arbitrarily map the field names', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'userName', email_address: 'email' } - }); - }).then(users => { - expect(users[0].userName).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); + it('arbitrarily map the field names', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: 'SELECT', + fieldMap: { username: 'userName', email_address: 'email' } }); + + expect(users[0].userName).to.be.equal('john'); + expect(users[0].email).to.be.equal('john@gmail.com'); }); - it('keeps field names that are mapped to the same name', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'username', email_address: 'email' } - }); - }).then(users => { - expect(users[0].username).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); + it('keeps field names that are mapped to the same name', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: 'SELECT', + fieldMap: { username: 'username', email_address: 'email' } }); + + expect(users[0].username).to.be.equal('john'); + expect(users[0].email).to.be.equal('john@gmail.com'); }); - it('reject if `values` and `options.replacements` are both passed', function() { - return this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) + it('reject if `values` and `options.replacements` are both passed', async function() { + await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) .should.be.rejectedWith(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); }); - it('reject if `sql.bind` and `options.bind` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) + it('reject if `sql.bind` and `options.bind` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) .should.be.rejectedWith(Error, 'Both `sql.bind` and `options.bind` cannot be set at the same time'); }); - it('reject if `options.replacements` and `options.bind` are both passed', function() { - return this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) + it('reject if `options.replacements` and `options.bind` are both passed', async function() { + await this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); }); - it('reject if `sql.bind` and `sql.values` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) + it('reject if `sql.bind` and `sql.values` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); }); - it('reject if `sql.bind` and `options.replacements`` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) + it('reject if `sql.bind` and `options.replacements`` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); }); - it('reject if `options.bind` and `sql.replacements` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) + it('reject if `options.bind` and `sql.replacements` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); }); - it('properly adds and escapes replacement value', function() { + it('properly adds and escapes replacement value', async function() { let logSql; const number = 1, date = new Date(), @@ -519,7 +514,8 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { buffer = Buffer.from('t\'e"st'); date.setMilliseconds(0); - return this.sequelize.query({ + + const result = await this.sequelize.query({ query: 'select ? as number, ? as date,? as string,? as boolean,? as buffer', values: [number, date, string, boolean, buffer] }, { @@ -527,25 +523,25 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { logging(s) { logSql = s; } - }).then(result => { - const res = result[0] || {}; - res.date = res.date && new Date(res.date); - res.boolean = res.boolean && true; - if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { - res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); - } - expect(res).to.deep.equal({ - number, - date, - string, - boolean, - buffer - }); - expect(logSql).to.not.include('?'); }); + + const res = result[0] || {}; + res.date = res.date && new Date(res.date); + res.boolean = res.boolean && true; + if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { + res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); + } + expect(res).to.deep.equal({ + number, + date, + string, + boolean, + buffer + }); + expect(logSql).to.not.include('?'); }); - it('it allows to pass custom class instances', function() { + it('it allows to pass custom class instances', async function() { let logSql; class SQLStatement { constructor() { @@ -555,274 +551,260 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { return 'select ? as foo, ? as bar'; } } - return this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); + const result = await this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + expect(logSql).to.not.include('?'); }); - it('uses properties `query` and `values` if query is tagged', function() { + it('uses properties `query` and `values` if query is tagged', async function() { let logSql; - return this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); + const result = await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + expect(logSql).to.not.include('?'); }); - it('uses properties `query` and `bind` if query is tagged', function() { + it('uses properties `query` and `bind` if query is tagged', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; let logSql; - return this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - } else if (dialect === 'mssql') { - expect(logSql).to.include('@0'); - expect(logSql).to.include('@1'); - } else if (dialect === 'mysql') { - expect(logSql.match(/\?/g).length).to.equal(2); - } - }); + const result = await this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + expect(logSql).to.include('$2'); + } else if (dialect === 'mssql') { + expect(logSql).to.include('@0'); + expect(logSql).to.include('@1'); + } else if (dialect === 'mysql') { + expect(logSql.match(/\?/g).length).to.equal(2); + } }); - it('dot separated attributes when doing a raw query without nest', function() { + it('dot separated attributes when doing a raw query without nest', async function() { const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - return expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); + await expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); }); - it('destructs dot separated attributes when doing a raw query using nest', function() { + it('destructs dot separated attributes when doing a raw query using nest', async function() { const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - return this.sequelize.query(sql, { raw: true, nest: true }).then(result => { - expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); - }); + const result = await this.sequelize.query(sql, { raw: true, nest: true }); + expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); }); - it('replaces token with the passed array', function() { - return this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - }); + it('replaces token with the passed array', async function() { + const result = await this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); }); - it('replaces named parameters with the passed object', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + it('replaces named parameters with the passed object', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); }); - it('replaces named parameters with the passed object and ignore those which does not qualify', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + it('replaces named parameters with the passed object and ignore those which does not qualify', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); }); - it('replaces named parameters with the passed object using the same key twice', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + it('replaces named parameters with the passed object using the same key twice', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); }); - it('replaces named parameters with the passed object having a null property', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) + it('replaces named parameters with the passed object having a null property', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: null }]); }); - it('reject when key is missing in the passed object', function() { - return this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) + it('reject when key is missing in the passed object', async function() { + await this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); }); - it('reject with the passed number', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) + it('reject with the passed number', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); }); - it('reject with the passed empty object', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) + it('reject with the passed empty object', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); }); - it('reject with the passed string', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) + it('reject with the passed string', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); }); - it('reject with the passed date', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) + it('reject with the passed date', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); }); - it('binds token with the passed array', function() { + it('binds token with the passed array', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; let logSql; - return this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); + const result = await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + } }); - it('binds named parameters with the passed object', function() { + it('binds named parameters with the passed object', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; let logSql; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - } - if (dialect === 'sqlite') { - expect(logSql).to.include('$one'); - } - }); + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres') { + expect(logSql).to.include('$1'); + } + if (dialect === 'sqlite') { + expect(logSql).to.include('$one'); + } }); - it('binds named parameters with the passed object using the same key twice', function() { + it('binds named parameters with the passed object using the same key twice', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; let logSql; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - expect(logSql).to.not.include('$3'); - } - }); + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); + if (dialect === 'postgres') { + expect(logSql).to.include('$1'); + expect(logSql).to.include('$2'); + expect(logSql).to.not.include('$3'); + } }); - it('binds named parameters with the passed object having a null property', function() { + it('binds named parameters with the passed object having a null property', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); - }); + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); }); - it('binds named parameters array handles escaped $$', function() { + it('binds named parameters array handles escaped $$', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; let logSql; - return this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); + const result = await this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + } }); - it('binds named parameters object handles escaped $$', function() { + it('binds named parameters object handles escaped $$', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); - }); + const result = await this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); }); - it('escape where has $ on the middle of characters', function() { + it('escape where has $ on the middle of characters', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }).then(result => { - expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); - }); + const result = await this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }); + expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); }); if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { - it('does not improperly escape arrays of strings bound to named parameters', function() { - return this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: '"string"' }]); - }); + it('does not improperly escape arrays of strings bound to named parameters', async function() { + const result = await this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }); + expect(result[0]).to.deep.equal([{ foo: '"string"' }]); }); } - it('reject when binds passed with object and numeric $1 is also present', function() { + it('reject when binds passed with object and numeric $1 is also present', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) + + await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject when binds passed as array and $alpha is also present', function() { + it('reject when binds passed as array and $alpha is also present', async function() { const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) + + await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject when bind key is $0 with the passed array', function() { - return this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + it('reject when bind key is $0 with the passed array', async function() { + await this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject when bind key is $01 with the passed array', function() { - return this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + it('reject when bind key is $01 with the passed array', async function() { + await this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject when bind key is missing in the passed array', function() { - return this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + it('reject when bind key is missing in the passed array', async function() { + await this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject when bind key is missing in the passed object', function() { - return this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) + it('reject when bind key is missing in the passed object', async function() { + await this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject with the passed number for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) + it('reject with the passed number for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject with the passed empty object for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) + it('reject with the passed empty object for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject with the passed string for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) + it('reject with the passed string for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('reject with the passed date for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) + it('reject with the passed date for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); }); - it('handles AS in conjunction with functions just fine', function() { + it('handles AS in conjunction with functions just fine', async function() { let datetime = dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'; if (dialect === 'mssql') { datetime = 'GETDATE()'; } - return this.sequelize.query(`SELECT ${datetime} AS t`).then(([result]) => { - expect(moment(result[0].t).isValid()).to.be.true; - }); + const [result] = await this.sequelize.query(`SELECT ${datetime} AS t`); + expect(moment(result[0].t).isValid()).to.be.true; }); if (Support.getTestDialect() === 'postgres') { - it('replaces named parameters with the passed object and ignores casts', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + it('replaces named parameters with the passed object and ignores casts', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); }); - it('supports WITH queries', function() { - return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) + it('supports WITH queries', async function() { + await expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) .to.eventually.deep.equal([{ 'sum': '5050' }]); }); } if (Support.getTestDialect() === 'sqlite') { - it('binds array parameters for upsert are replaced. $$ unescapes only once', function() { + it('binds array parameters for upsert are replaced. $$ unescapes only once', async function() { let logSql; - return this.sequelize.query('select $1 as foo, $2 as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: [1, 2], logging(s) { logSql = s; } }).then(() => { - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); + await this.sequelize.query('select $1 as foo, $2 as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: [1, 2], logging(s) { logSql = s; } }); + // sqlite.exec does not return a result + expect(logSql).to.not.include('$one'); + expect(logSql).to.include('\'$$\''); }); - it('binds named parameters for upsert are replaced. $$ unescapes only once', function() { + it('binds named parameters for upsert are replaced. $$ unescapes only once', async function() { let logSql; - return this.sequelize.query('select $one as foo, $two as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }).then(() => { - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); + await this.sequelize.query('select $one as foo, $two as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + // sqlite.exec does not return a result + expect(logSql).to.not.include('$one'); + expect(logSql).to.include('\'$$\''); }); } @@ -892,34 +874,30 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { .to.be.rejectedWith(TypeError, 'options.transaction is required'); }); - it('one value', function() { - return this.sequelize.transaction().then(t => { - this.t = t; - return this.sequelize.set({ foo: 'bar' }, { transaction: t }); - }).then(() => { - return this.sequelize.query('SELECT @foo as `foo`', { plain: true, transaction: this.t }); - }).then(data => { - expect(data).to.be.ok; - expect(data.foo).to.be.equal('bar'); - return this.t.commit(); - }); + it('one value', async function() { + const t = await this.sequelize.transaction(); + this.t = t; + await this.sequelize.set({ foo: 'bar' }, { transaction: t }); + const data = await this.sequelize.query('SELECT @foo as `foo`', { plain: true, transaction: this.t }); + expect(data).to.be.ok; + expect(data.foo).to.be.equal('bar'); + await this.t.commit(); }); - it('multiple values', function() { - return this.sequelize.transaction().then(t => { - this.t = t; - return this.sequelize.set({ - foo: 'bar', - foos: 'bars' - }, { transaction: t }); - }).then(() => { - return this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', { plain: true, transaction: this.t }); - }).then(data => { - expect(data).to.be.ok; - expect(data.foo).to.be.equal('bar'); - expect(data.foos).to.be.equal('bars'); - return this.t.commit(); - }); + it('multiple values', async function() { + const t = await this.sequelize.transaction(); + this.t = t; + + await this.sequelize.set({ + foo: 'bar', + foos: 'bars' + }, { transaction: t }); + + const data = await this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', { plain: true, transaction: this.t }); + expect(data).to.be.ok; + expect(data.foo).to.be.equal('bar'); + expect(data.foos).to.be.equal('bars'); + await this.t.commit(); }); }); } @@ -961,21 +939,19 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(DAO.options.rowFormat).to.equal('default'); }); - it('uses the passed tableName', function() { + it('uses the passed tableName', async function() { const Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: 'photos' }); - return Photo.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showAllTables().then(tableNames => { - if (dialect === 'mssql' || dialect === 'mariadb') { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.include('photos'); - }); - }); + await Photo.sync({ force: true }); + let tableNames = await this.sequelize.getQueryInterface().showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.include('photos'); }); }); describe('truncate', () => { - it('truncates all models', function() { + it('truncates all models', async function() { const Project = this.sequelize.define(`project${config.rand()}`, { id: { type: DataTypes.INTEGER, @@ -985,47 +961,38 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { title: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return Project.create({ title: 'bla' }); - }).then(project => { - expect(project).to.exist; - expect(project.title).to.equal('bla'); - expect(project.id).to.equal(1); - return this.sequelize.truncate().then(() => { - return Project.findAll({}); - }); - }).then(projects => { - expect(projects).to.exist; - expect(projects).to.have.length(0); - }); + await this.sequelize.sync({ force: true }); + const project = await Project.create({ title: 'bla' }); + expect(project).to.exist; + expect(project.title).to.equal('bla'); + expect(project.id).to.equal(1); + await this.sequelize.truncate(); + const projects = await Project.findAll({}); + expect(projects).to.exist; + expect(projects).to.have.length(0); }); }); describe('sync', () => { - it('synchronizes all models', function() { + it('synchronizes all models', async function() { const Project = this.sequelize.define(`project${config.rand()}`, { title: DataTypes.STRING }); const Task = this.sequelize.define(`task${config.rand()}`, { title: DataTypes.STRING }); - return Project.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return Project.create({ title: 'bla' }).then(() => { - return Task.create({ title: 'bla' }).then(task => { - expect(task).to.exist; - expect(task.title).to.equal('bla'); - }); - }); - }); - }); + await Project.sync({ force: true }); + await Task.sync({ force: true }); + await Project.create({ title: 'bla' }); + const task = await Task.create({ title: 'bla' }); + expect(task).to.exist; + expect(task.title).to.equal('bla'); }); - it('works with correct database credentials', function() { + it('works with correct database credentials', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - return User.sync().then(() => { - expect(true).to.be.true; - }); + await User.sync(); + expect(true).to.be.true; }); - it('fails with incorrect match condition', function() { + it('fails with incorrect match condition', async function() { const sequelize = new Sequelize('cyber_bird', 'user', 'pass', { dialect: this.sequelize.options.dialect }); @@ -1033,43 +1000,44 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true, match: /$phoenix/ })) + await expect(sequelize.sync({ force: true, match: /$phoenix/ })) .to.be.rejectedWith('Database "cyber_bird" does not match sync match parameter "/$phoenix/"'); }); if (dialect !== 'sqlite') { - it('fails for incorrect connection even when no models are defined', function() { + it('fails for incorrect connection even when no models are defined', async function() { const sequelize = new Sequelize('cyber_bird', 'user', 'pass', { dialect: this.sequelize.options.dialect }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('fails with incorrect database credentials (1)', function() { + it('fails with incorrect database credentials (1)', async function() { this.sequelizeWithInvalidCredentials = new Sequelize('omg', 'bar', null, _.omit(this.sequelize.options, ['host'])); const User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT }); - return User2.sync() - .then(() => { expect.fail(); }) - .catch(err => { - if (dialect === 'postgres' || dialect === 'postgres-native') { - assert([ - 'fe_sendauth: no password supplied', - 'role "bar" does not exist', - 'FATAL: role "bar" does not exist', - 'password authentication failed for user "bar"' - ].includes(err.message.trim())); - } else if (dialect === 'mssql') { - expect(err.message).to.equal('Login failed for user \'bar\'.'); - } else { - expect(err.message.toString()).to.match(/.*Access denied.*/); - } - }); + try { + await User2.sync(); + expect.fail(); + } catch (err) { + if (dialect === 'postgres' || dialect === 'postgres-native') { + assert([ + 'fe_sendauth: no password supplied', + 'role "bar" does not exist', + 'FATAL: role "bar" does not exist', + 'password authentication failed for user "bar"' + ].includes(err.message.trim())); + } else if (dialect === 'mssql') { + expect(err.message).to.equal('Login failed for user \'bar\'.'); + } else { + expect(err.message.toString()).to.match(/.*Access denied.*/); + } + } }); - it('fails with incorrect database credentials (2)', function() { + it('fails with incorrect database credentials (2)', async function() { const sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect }); @@ -1077,10 +1045,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('fails with incorrect database credentials (3)', function() { + it('fails with incorrect database credentials (3)', async function() { const sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect, port: 99999 @@ -1089,10 +1057,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('fails with incorrect database credentials (4)', function() { + it('fails with incorrect database credentials (4)', async function() { const sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect, port: 99999, @@ -1102,10 +1070,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('returns an error correctly if unable to sync a foreign key referenced model', function() { + it('returns an error correctly if unable to sync a foreign key referenced model', async function() { this.sequelize.define('Application', { authorID: { type: Sequelize.BIGINT, @@ -1117,10 +1085,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { } }); - return expect(this.sequelize.sync()).to.be.rejected; + await expect(this.sequelize.sync()).to.be.rejected; }); - it('handles this dependant foreign key constraints', function() { + it('handles this dependant foreign key constraints', async function() { const block = this.sequelize.define('block', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING @@ -1145,17 +1113,16 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { foreignKeyConstraint: true }); - return this.sequelize.sync(); + await this.sequelize.sync(); }); } - it('return the sequelize instance after syncing', function() { - return this.sequelize.sync().then(sequelize => { - expect(sequelize).to.deep.equal(this.sequelize); - }); + it('return the sequelize instance after syncing', async function() { + const sequelize = await this.sequelize.sync(); + expect(sequelize).to.deep.equal(this.sequelize); }); - it('return the single dao after syncing', function() { + it('return the single dao after syncing', async function() { const block = this.sequelize.define('block', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING @@ -1165,12 +1132,11 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { paranoid: false }); - return block.sync().then(result => { - expect(result).to.deep.equal(block); - }); + const result = await block.sync(); + expect(result).to.deep.equal(block); }); - it('handles alter: true with underscore correctly', function() { + it('handles alter: true with underscore correctly', async function() { this.sequelize.define('access_metric', { user_id: { type: DataTypes.INTEGER @@ -1179,7 +1145,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { underscored: true }); - return this.sequelize.sync({ + await this.sequelize.sync({ alter: true }); }); @@ -1195,18 +1161,16 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { this.User = this.sequelize.define('UserTest', { username: DataTypes.STRING }); }); - it('through Sequelize.sync()', function() { + it('through Sequelize.sync()', async function() { this.spy.resetHistory(); - return this.sequelize.sync({ force: true, logging: false }).then(() => { - expect(this.spy.notCalled).to.be.true; - }); + await this.sequelize.sync({ force: true, logging: false }); + expect(this.spy.notCalled).to.be.true; }); - it('through DAOFactory.sync()', function() { + it('through DAOFactory.sync()', async function() { this.spy.resetHistory(); - return this.User.sync({ force: true, logging: false }).then(() => { - expect(this.spy.notCalled).to.be.true; - }); + await this.User.sync({ force: true, logging: false }); + expect(this.spy.notCalled).to.be.true; }); }); @@ -1223,11 +1187,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); describe('drop should work', () => { - it('correctly succeeds', function() { + it('correctly succeeds', async function() { const User = this.sequelize.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return User.drop(); - }); + await User.sync({ force: true }); + await User.drop(); }); }); @@ -1247,13 +1210,13 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { DataTypes.ENUM('scheduled', 'active', 'finished') ].forEach(status => { describe('enum', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize = Support.createSequelizeInstance({ typeValidation: true }); this.Review = this.sequelize.define('review', { status }); - return this.Review.sync({ force: true }); + await this.Review.sync({ force: true }); }); it('raises an error if no values are defined', function() { @@ -1264,25 +1227,24 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }).to.throw(Error, 'Values for ENUM have not been defined.'); }); - it('correctly stores values', function() { - return this.Review.create({ status: 'active' }).then(review => { - expect(review.status).to.equal('active'); - }); + it('correctly stores values', async function() { + const review = await this.Review.create({ status: 'active' }); + expect(review.status).to.equal('active'); }); - it('correctly loads values', function() { - return this.Review.create({ status: 'active' }).then(() => { - return this.Review.findAll().then(reviews => { - expect(reviews[0].status).to.equal('active'); - }); - }); + it('correctly loads values', async function() { + await this.Review.create({ status: 'active' }); + const reviews = await this.Review.findAll(); + expect(reviews[0].status).to.equal('active'); }); - it("doesn't save an instance if value is not in the range of enums", function() { - return this.Review.create({ status: 'fnord' }).catch(err => { + it("doesn't save an instance if value is not in the range of enums", async function() { + try { + await this.Review.create({ status: 'fnord' }); + } catch (err) { expect(err).to.be.instanceOf(Error); expect(err.message).to.equal('"fnord" is not a valid choice in ["scheduled","active","finished"]'); - }); + } }); }); }); @@ -1294,18 +1256,17 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { { id: { type: DataTypes.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } } ].forEach(customAttributes => { - it('should be able to override options on the default attributes', function() { + it('should be able to override options on the default attributes', async function() { const Picture = this.sequelize.define('picture', _.cloneDeep(customAttributes)); - return Picture.sync({ force: true }).then(() => { - Object.keys(customAttributes).forEach(attribute => { - Object.keys(customAttributes[attribute]).forEach(option => { - const optionValue = customAttributes[attribute][option]; - if (typeof optionValue === 'function' && optionValue() instanceof DataTypes.ABSTRACT) { - expect(Picture.rawAttributes[attribute][option] instanceof optionValue).to.be.ok; - } else { - expect(Picture.rawAttributes[attribute][option]).to.be.equal(optionValue); - } - }); + await Picture.sync({ force: true }); + Object.keys(customAttributes).forEach(attribute => { + Object.keys(customAttributes[attribute]).forEach(option => { + const optionValue = customAttributes[attribute][option]; + if (typeof optionValue === 'function' && optionValue() instanceof DataTypes.ABSTRACT) { + expect(Picture.rawAttributes[attribute][option] instanceof optionValue).to.be.ok; + } else { + expect(Picture.rawAttributes[attribute][option]).to.be.equal(optionValue); + } }); }); }); @@ -1315,236 +1276,181 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (current.dialect.supports.transactions) { describe('transaction', () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelizeWithTransaction = sequelize; - }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelizeWithTransaction = sequelize; }); it('is a transaction method available', () => { expect(Support.Sequelize).to.respondTo('transaction'); }); - it('passes a transaction object to the callback', function() { - return this.sequelizeWithTransaction.transaction().then(t => { - expect(t).to.be.instanceOf(Transaction); - }); + it('passes a transaction object to the callback', async function() { + const t = await this.sequelizeWithTransaction.transaction(); + expect(t).to.be.instanceOf(Transaction); }); - it('allows me to define a callback on the result', function() { - return this.sequelizeWithTransaction.transaction().then(t => { - return t.commit(); - }); + it('allows me to define a callback on the result', async function() { + const t = await this.sequelizeWithTransaction.transaction(); + await t.commit(); }); if (dialect === 'sqlite') { - it('correctly scopes transaction from other connections', function() { + it('correctly scopes transaction from other connections', async function() { const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); - const count = transaction => { + const count = async transaction => { const sql = this.sequelizeWithTransaction.getQueryInterface().queryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); - return this.sequelizeWithTransaction.query(sql, { plain: true, transaction }).then(result => { - return result.cnt; - }); + const result = await this.sequelizeWithTransaction.query(sql, { plain: true, transaction }); + + return result.cnt; }; - return TransactionTest.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction(); - }).then(t1 => { - this.t1 = t1; - return this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); - }).then(() => { - return expect(count()).to.eventually.equal(0); - }).then(() => { - return expect(count(this.t1)).to.eventually.equal(1); - }).then(() => { - return this.t1.commit(); - }).then(() => { - return expect(count()).to.eventually.equal(1); - }); + await TransactionTest.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + this.t1 = t1; + await this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); + await expect(count()).to.eventually.equal(0); + await expect(count(this.t1)).to.eventually.equal(1); + await this.t1.commit(); + + await expect(count()).to.eventually.equal(1); }); } else { - it('correctly handles multiple transactions', function() { + it('correctly handles multiple transactions', async function() { const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); const aliasesMapping = new Map([['_0', 'cnt']]); - const count = transaction => { + const count = async transaction => { const sql = this.sequelizeWithTransaction.getQueryInterface().queryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); - return this.sequelizeWithTransaction.query(sql, { plain: true, transaction, aliasesMapping }).then(result => { - return parseInt(result.cnt, 10); - }); + const result = await this.sequelizeWithTransaction.query(sql, { plain: true, transaction, aliasesMapping }); + + return parseInt(result.cnt, 10); }; - return TransactionTest.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction(); - }).then(t1 => { - this.t1 = t1; - return this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); - }).then(() => { - return this.sequelizeWithTransaction.transaction(); - }).then(t2 => { - this.t2 = t2; - return this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('bar');`, { transaction: t2 }); - }).then(() => { - return expect(count()).to.eventually.equal(0); - }).then(() => { - return expect(count(this.t1)).to.eventually.equal(1); - }).then(() => { - return expect(count(this.t2)).to.eventually.equal(1); - }).then(() => { - - return this.t2.rollback(); - }).then(() => { - return expect(count()).to.eventually.equal(0); - }).then(() => { - return this.t1.commit(); - }).then(() => { - return expect(count()).to.eventually.equal(1); - }); + await TransactionTest.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + this.t1 = t1; + await this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction(); + this.t2 = t2; + await this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('bar');`, { transaction: t2 }); + await expect(count()).to.eventually.equal(0); + await expect(count(this.t1)).to.eventually.equal(1); + await expect(count(this.t2)).to.eventually.equal(1); + await this.t2.rollback(); + await expect(count()).to.eventually.equal(0); + await this.t1.commit(); + + await expect(count()).to.eventually.equal(1); }); } - it('supports nested transactions using savepoints', function() { + it('supports nested transactions using savepoints', async function() { const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction().then(t1 => { - return User.create({ username: 'foo' }, { transaction: t1 }).then(user => { - return this.sequelizeWithTransaction.transaction({ transaction: t1 }).then(t2 => { - return user.update({ username: 'bar' }, { transaction: t2 }).then(() => { - return t2.commit().then(() => { - return user.reload({ transaction: t1 }).then(newUser => { - expect(newUser.username).to.equal('bar'); - return t1.commit(); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + const user = await User.create({ username: 'foo' }, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction({ transaction: t1 }); + await user.update({ username: 'bar' }, { transaction: t2 }); + await t2.commit(); + const newUser = await user.reload({ transaction: t1 }); + expect(newUser.username).to.equal('bar'); + + await t1.commit(); }); describe('supports rolling back to savepoints', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelizeWithTransaction.define('user', {}); - return this.sequelizeWithTransaction.sync({ force: true }); + await this.sequelizeWithTransaction.sync({ force: true }); }); - it('rolls back to the first savepoint, undoing everything', function() { - return this.sequelizeWithTransaction.transaction().then(transaction => { - this.transaction = transaction; - - return this.sequelizeWithTransaction.transaction({ transaction }); - }).then(sp1 => { - this.sp1 = sp1; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); - }).then(sp2 => { - this.sp2 = sp2; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(2); - - return this.sp1.rollback(); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(0); - - return this.transaction.rollback(); - }); + it('rolls back to the first savepoint, undoing everything', async function() { + const transaction = await this.sequelizeWithTransaction.transaction(); + this.transaction = transaction; + + const sp1 = await this.sequelizeWithTransaction.transaction({ transaction }); + this.sp1 = sp1; + await this.User.create({}, { transaction: this.transaction }); + const sp2 = await this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); + this.sp2 = sp2; + await this.User.create({}, { transaction: this.transaction }); + const users0 = await this.User.findAll({ transaction: this.transaction }); + expect(users0).to.have.length(2); + + await this.sp1.rollback(); + const users = await this.User.findAll({ transaction: this.transaction }); + expect(users).to.have.length(0); + + await this.transaction.rollback(); }); - it('rolls back to the most recent savepoint, only undoing recent changes', function() { - return this.sequelizeWithTransaction.transaction().then(transaction => { - this.transaction = transaction; - - return this.sequelizeWithTransaction.transaction({ transaction }); - }).then(sp1 => { - this.sp1 = sp1; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); - }).then(sp2 => { - this.sp2 = sp2; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(2); - - return this.sp2.rollback(); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(1); - - return this.transaction.rollback(); - }); + it('rolls back to the most recent savepoint, only undoing recent changes', async function() { + const transaction = await this.sequelizeWithTransaction.transaction(); + this.transaction = transaction; + + const sp1 = await this.sequelizeWithTransaction.transaction({ transaction }); + this.sp1 = sp1; + await this.User.create({}, { transaction: this.transaction }); + const sp2 = await this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); + this.sp2 = sp2; + await this.User.create({}, { transaction: this.transaction }); + const users0 = await this.User.findAll({ transaction: this.transaction }); + expect(users0).to.have.length(2); + + await this.sp2.rollback(); + const users = await this.User.findAll({ transaction: this.transaction }); + expect(users).to.have.length(1); + + await this.transaction.rollback(); }); }); - it('supports rolling back a nested transaction', function() { + it('supports rolling back a nested transaction', async function() { const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction().then(t1 => { - return User.create({ username: 'foo' }, { transaction: t1 }).then(user => { - return this.sequelizeWithTransaction.transaction({ transaction: t1 }).then(t2 => { - return user.update({ username: 'bar' }, { transaction: t2 }).then(() => { - return t2.rollback().then(() => { - return user.reload({ transaction: t1 }).then(newUser => { - expect(newUser.username).to.equal('foo'); - return t1.commit(); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + const user = await User.create({ username: 'foo' }, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction({ transaction: t1 }); + await user.update({ username: 'bar' }, { transaction: t2 }); + await t2.rollback(); + const newUser = await user.reload({ transaction: t1 }); + expect(newUser.username).to.equal('foo'); + + await t1.commit(); }); - it('supports rolling back outermost transaction', function() { + it('supports rolling back outermost transaction', async function() { const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction().then(t1 => { - return User.create({ username: 'foo' }, { transaction: t1 }).then(user => { - return this.sequelizeWithTransaction.transaction({ transaction: t1 }).then(t2 => { - return user.update({ username: 'bar' }, { transaction: t2 }).then(() => { - return t1.rollback().then(() => { - return User.findAll().then(users => { - expect(users.length).to.equal(0); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + const user = await User.create({ username: 'foo' }, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction({ transaction: t1 }); + await user.update({ username: 'bar' }, { transaction: t2 }); + await t1.rollback(); + const users = await User.findAll(); + expect(users.length).to.equal(0); }); }); } }); describe('databaseVersion', () => { - it('should database/dialect version', function() { - return this.sequelize.databaseVersion().then(version => { - expect(typeof version).to.equal('string'); - expect(version).to.be.ok; - }); + it('should database/dialect version', async function() { + const version = await this.sequelize.databaseVersion(); + expect(typeof version).to.equal('string'); + expect(version).to.be.ok; }); }); describe('paranoid deletedAt non-null default value', () => { - it('should use defaultValue of deletedAt in paranoid clause and restore', function() { + it('should use defaultValue of deletedAt in paranoid clause and restore', async function() { const epochObj = new Date(0), epoch = Number(epochObj); const User = this.sequelize.define('user', { @@ -1557,44 +1463,38 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'user1' }).then(user => { - expect(Number(user.deletedAt)).to.equal(epoch); - return User.findOne({ - where: { - username: 'user1' - } - }).then(user => { - expect(user).to.exist; - expect(Number(user.deletedAt)).to.equal(epoch); - return user.destroy(); - }).then(destroyedUser => { - expect(destroyedUser.deletedAt).to.exist; - expect(Number(destroyedUser.deletedAt)).not.to.equal(epoch); - return User.findByPk(destroyedUser.id, { paranoid: false }); - }).then(fetchedDestroyedUser => { - expect(fetchedDestroyedUser.deletedAt).to.exist; - expect(Number(fetchedDestroyedUser.deletedAt)).not.to.equal(epoch); - return fetchedDestroyedUser.restore(); - }).then(restoredUser => { - expect(Number(restoredUser.deletedAt)).to.equal(epoch); - return User.destroy({ where: { - username: 'user1' - } }); - }).then(() => { - return User.count(); - }).then(count => { - expect(count).to.equal(0); - return User.restore(); - }).then(() => { - return User.findAll(); - }).then(nonDeletedUsers => { - expect(nonDeletedUsers.length).to.equal(1); - nonDeletedUsers.forEach(u => { - expect(Number(u.deletedAt)).to.equal(epoch); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'user1' }); + expect(Number(user.deletedAt)).to.equal(epoch); + + const user0 = await User.findOne({ + where: { + username: 'user1' + } + }); + + expect(user0).to.exist; + expect(Number(user0.deletedAt)).to.equal(epoch); + const destroyedUser = await user0.destroy(); + expect(destroyedUser.deletedAt).to.exist; + expect(Number(destroyedUser.deletedAt)).not.to.equal(epoch); + const fetchedDestroyedUser = await User.findByPk(destroyedUser.id, { paranoid: false }); + expect(fetchedDestroyedUser.deletedAt).to.exist; + expect(Number(fetchedDestroyedUser.deletedAt)).not.to.equal(epoch); + const restoredUser = await fetchedDestroyedUser.restore(); + expect(Number(restoredUser.deletedAt)).to.equal(epoch); + + await User.destroy({ where: { + username: 'user1' + } }); + + const count = await User.count(); + expect(count).to.equal(0); + await User.restore(); + const nonDeletedUsers = await User.findAll(); + expect(nonDeletedUsers.length).to.equal(1); + nonDeletedUsers.forEach(u => { + expect(Number(u.deletedAt)).to.equal(epoch); }); }); }); diff --git a/test/integration/sequelize.transaction.test.js b/test/integration/sequelize.transaction.test.js index ddc55461e2e5..86feee7f1793 100644 --- a/test/integration/sequelize.transaction.test.js +++ b/test/integration/sequelize.transaction.test.js @@ -12,142 +12,126 @@ if (current.dialect.supports.transactions) { describe(Support.getTestDialectTeaser('Sequelize#transaction'), () => { describe('then', () => { - it('gets triggered once a transaction has been successfully committed', function() { + it('gets triggered once a transaction has been successfully committed', async function() { let called = false; - return this + + const t = await this .sequelize - .transaction().then(t => { - return t.commit().then(() => { - called = 1; - }); - }) - .then(() => { - expect(called).to.be.ok; - }); + .transaction(); + + await t.commit(); + called = 1; + expect(called).to.be.ok; }); - it('gets triggered once a transaction has been successfully rolled back', function() { + it('gets triggered once a transaction has been successfully rolled back', async function() { let called = false; - return this + + const t = await this .sequelize - .transaction().then(t => { - return t.rollback().then(() => { - called = 1; - }); - }) - .then(() => { - expect(called).to.be.ok; - }); + .transaction(); + + await t.rollback(); + called = 1; + expect(called).to.be.ok; }); if (Support.getTestDialect() !== 'sqlite') { - it('works for long running transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.User = sequelize.define('User', { - name: Support.Sequelize.STRING - }, { timestamps: false }); - - return sequelize.sync({ force: true }); - }).then(() => { - return this.sequelize.transaction(); - }).then(t => { - let query = 'select sleep(2);'; - - switch (Support.getTestDialect()) { - case 'postgres': - query = 'select pg_sleep(2);'; - break; - case 'sqlite': - query = 'select sqlite3_sleep(2000);'; - break; - case 'mssql': - query = 'WAITFOR DELAY \'00:00:02\';'; - break; - default: - break; - } - - return this.sequelize.query(query, { transaction: t }).then(() => { - return this.User.create({ name: 'foo' }); - }).then(() => { - return this.sequelize.query(query, { transaction: t }); - }).then(() => { - return t.commit(); - }); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].name).to.equal('foo'); - }); + it('works for long running transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.User = sequelize.define('User', { + name: Support.Sequelize.STRING + }, { timestamps: false }); + + await sequelize.sync({ force: true }); + const t = await this.sequelize.transaction(); + let query = 'select sleep(2);'; + + switch (Support.getTestDialect()) { + case 'postgres': + query = 'select pg_sleep(2);'; + break; + case 'sqlite': + query = 'select sqlite3_sleep(2000);'; + break; + case 'mssql': + query = 'WAITFOR DELAY \'00:00:02\';'; + break; + default: + break; + } + + await this.sequelize.query(query, { transaction: t }); + await this.User.create({ name: 'foo' }); + await this.sequelize.query(query, { transaction: t }); + await t.commit(); + const users = await this.User.findAll(); + expect(users.length).to.equal(1); + expect(users[0].name).to.equal('foo'); }); } }); describe('complex long running example', () => { - it('works with promise syntax', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const Test = sequelize.define('Test', { - id: { type: Support.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - name: { type: Support.Sequelize.STRING } - }); - - return sequelize.sync({ force: true }).then(() => { - return sequelize.transaction().then(transaction => { - expect(transaction).to.be.instanceOf(Transaction); - - return Test - .create({ name: 'Peter' }, { transaction }) - .then(() => { - return delay(1000).then(() => { - return transaction - .commit() - .then(() => { return Test.count(); }) - .then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); - }); + it('works with promise syntax', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Test = sequelize.define('Test', { + id: { type: Support.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, + name: { type: Support.Sequelize.STRING } }); + + await sequelize.sync({ force: true }); + const transaction = await sequelize.transaction(); + expect(transaction).to.be.instanceOf(Transaction); + + await Test + .create({ name: 'Peter' }, { transaction }); + + await delay(1000); + + await transaction + .commit(); + + const count = await Test.count(); + expect(count).to.equal(1); }); }); describe('concurrency', () => { describe('having tables with uniqueness constraints', () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.Model = sequelize.define('Model', { - name: { type: Support.Sequelize.STRING, unique: true } - }, { - timestamps: false - }); - - return this.Model.sync({ force: true }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.Model = sequelize.define('Model', { + name: { type: Support.Sequelize.STRING, unique: true } + }, { + timestamps: false }); + + await this.Model.sync({ force: true }); }); - it('triggers the error event for the second transactions', function() { - return this.sequelize.transaction().then(t1 => { - return this.sequelize.transaction().then(t2 => { - return this.Model.create({ name: 'omnom' }, { transaction: t1 }).then(() => { - return Promise.all([ - this.Model.create({ name: 'omnom' }, { transaction: t2 }).catch(err => { - expect(err).to.be.ok; - return t2.rollback(); - }), - delay(100).then(() => { - return t1.commit(); - }) - ]); - }); - }); - }); + it('triggers the error event for the second transactions', async function() { + const t1 = await this.sequelize.transaction(); + const t2 = await this.sequelize.transaction(); + await this.Model.create({ name: 'omnom' }, { transaction: t1 }); + + await Promise.all([ + (async () => { + try { + return await this.Model.create({ name: 'omnom' }, { transaction: t2 }); + } catch (err) { + expect(err).to.be.ok; + return t2.rollback(); + } + })(), + delay(100).then(() => { + return t1.commit(); + }) + ]); }); }); }); diff --git a/test/integration/support.js b/test/integration/support.js index 7d884bf63476..5ca88076d8a5 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -13,8 +13,8 @@ before(function() { }); }); -beforeEach(function() { - return Support.clearDatabase(this.sequelize); +beforeEach(async function() { + await Support.clearDatabase(this.sequelize); }); afterEach(function() { diff --git a/test/integration/timezone.test.js b/test/integration/timezone.test.js index 92f9f11491d3..8349288e74db 100644 --- a/test/integration/timezone.test.js +++ b/test/integration/timezone.test.js @@ -18,7 +18,7 @@ if (dialect !== 'sqlite') { }); }); - it('returns the same value for current timestamp', function() { + it('returns the same value for current timestamp', async function() { let now = 'now()'; const startQueryTime = Date.now(); @@ -27,48 +27,45 @@ if (dialect !== 'sqlite') { } const query = `SELECT ${now} as now`; - return Promise.all([ + + const [now1, now2] = await Promise.all([ this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }), this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }) - ]).then(([now1, now2]) => { - const elapsedQueryTime = Date.now() - startQueryTime + 1001; - expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); - }); + ]); + + const elapsedQueryTime = Date.now() - startQueryTime + 1001; + expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); }); if (dialect === 'mysql' || dialect === 'mariadb') { - it('handles existing timestamps', function() { + it('handles existing timestamps', async function() { const NormalUser = this.sequelize.define('user', {}), TimezonedUser = this.sequelizeWithTimezone.define('user', {}); - return this.sequelize.sync({ force: true }).then(() => { - return NormalUser.create({}); - }).then(normalUser => { - this.normalUser = normalUser; - return TimezonedUser.findByPk(normalUser.id); - }).then(timezonedUser => { - // Expect 7 hours difference, in milliseconds. - // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp - // this test does not apply to PG, since it stores the timezone along with the timestamp. - expect(this.normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 1000); - }); + await this.sequelize.sync({ force: true }); + const normalUser = await NormalUser.create({}); + this.normalUser = normalUser; + const timezonedUser = await TimezonedUser.findByPk(normalUser.id); + // Expect 7 hours difference, in milliseconds. + // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp + // this test does not apply to PG, since it stores the timezone along with the timestamp. + expect(this.normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 1000); }); - it('handles named timezones', function() { + it('handles named timezones', async function() { const NormalUser = this.sequelize.define('user', {}), TimezonedUser = this.sequelizeWithNamedTimezone.define('user', {}); - return this.sequelize.sync({ force: true }).then(() => { - return TimezonedUser.create({}); - }).then(timezonedUser => { - return Promise.all([ - NormalUser.findByPk(timezonedUser.id), - TimezonedUser.findByPk(timezonedUser.id) - ]); - }).then(([normalUser, timezonedUser]) => { - // Expect 5 hours difference, in milliseconds, +/- 1 hour for DST - expect(normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 4 * 1000 * -1, 60 * 60 * 1000); - }); + await this.sequelize.sync({ force: true }); + const timezonedUser0 = await TimezonedUser.create({}); + + const [normalUser, timezonedUser] = await Promise.all([ + NormalUser.findByPk(timezonedUser0.id), + TimezonedUser.findByPk(timezonedUser0.id) + ]); + + // Expect 5 hours difference, in milliseconds, +/- 1 hour for DST + expect(normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 4 * 1000 * -1, 60 * 60 * 1000); }); } }); diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 3ac395726c58..b5215c257cc6 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -55,55 +55,59 @@ if (current.dialect.supports.transactions) { }); describe('autoCallback', () => { - it('supports automatically committing', function() { - return this.sequelize.transaction(() => { - return Promise.resolve(); - }); + it('supports automatically committing', async function() { + await this.sequelize.transaction(async () => {}); }); - it('supports automatically rolling back with a thrown error', function() { + it('supports automatically rolling back with a thrown error', async function() { let t; - return expect(this.sequelize.transaction(transaction => { + + await expect(this.sequelize.transaction(transaction => { t = transaction; throw new Error('Yolo'); - })).to.eventually.be.rejected.then(() => { - expect(t.finished).to.be.equal('rollback'); - }); + })).to.eventually.be.rejected; + + expect(t.finished).to.be.equal('rollback'); }); - it('supports automatically rolling back with a rejection', function() { + it('supports automatically rolling back with a rejection', async function() { let t; - return expect(this.sequelize.transaction(transaction => { + + await expect(this.sequelize.transaction(async transaction => { t = transaction; - return Promise.reject(new Error('Swag')); - })).to.eventually.be.rejected.then(() => { - expect(t.finished).to.be.equal('rollback'); - }); + throw new Error('Swag'); + })).to.eventually.be.rejected; + + expect(t.finished).to.be.equal('rollback'); }); - it('supports running hooks when a transaction is committed', function() { + it('supports running hooks when a transaction is committed', async function() { const hook = sinon.spy(); let transaction; - return expect(this.sequelize.transaction(t => { - transaction = t; - transaction.afterCommit(hook); - return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT }); - }).then(() => { + + await expect((async () => { + await this.sequelize.transaction(t => { + transaction = t; + transaction.afterCommit(hook); + return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT }); + }); + expect(hook).to.have.been.calledOnce; expect(hook).to.have.been.calledWith(transaction); - }) + })() ).to.eventually.be.fulfilled; }); - it('does not run hooks when a transaction is rolled back', function() { + it('does not run hooks when a transaction is rolled back', async function() { const hook = sinon.spy(); - return expect(this.sequelize.transaction(transaction => { + + await expect(this.sequelize.transaction(async transaction => { transaction.afterCommit(hook); - return Promise.reject(new Error('Rollback')); + throw new Error('Rollback'); }) - ).to.eventually.be.rejected.then(() => { - expect(hook).to.not.have.been.called; - }); + ).to.eventually.be.rejected; + + expect(hook).to.not.have.been.called; }); //Promise rejection test is specific to postgres @@ -152,7 +156,8 @@ if (current.dialect.supports.transactions) { }); it('does not allow queries immediately after commit call', async function() { - await expect(this.sequelize.transaction().then(async t => { + await expect((async () => { + const t = await this.sequelize.transaction(); await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); await Promise.all([ expect(t.commit()).to.eventually.be.fulfilled, @@ -161,28 +166,27 @@ if (current.dialect.supports.transactions) { /commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ ).and.have.deep.property('sql').that.equal('SELECT 1+1') ]); - })).to.be.eventually.fulfilled; + })()).to.be.eventually.fulfilled; }); - it('does not allow queries after rollback', function() { - return expect( - this.sequelize.transaction().then(t => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }).then(() => { - return t.rollback(); - }).then(() => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); - }); - }) + it('does not allow queries after rollback', async function() { + await expect( + (async () => { + const t = await this.sequelize.transaction(); + await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + await t.rollback(); + return await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + })() ).to.eventually.be.rejected; }); - it('should not rollback if connection was not acquired', function() { + it('should not rollback if connection was not acquired', async function() { this.sinon.stub(this.sequelize.connectionManager, '_connect') .returns(new Promise(() => {})); const transaction = new Transaction(this.sequelize); - return expect(transaction.rollback()) + await expect(transaction.rollback()) .to.eventually.be.rejectedWith('Transaction cannot be rolled back because it never started'); }); @@ -200,174 +204,184 @@ if (current.dialect.supports.transactions) { ).to.eventually.be.fulfilled; }); - it('does not allow commits after commit', function() { - return expect( - this.sequelize.transaction().then(t => { - return t.commit().then(() => { - return t.commit(); - }); - }) + it('does not allow commits after commit', async function() { + await expect( + (async () => { + const t = await this.sequelize.transaction(); + await t.commit(); + return await t.commit(); + })() ).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: commit'); }); - it('should run hooks if a non-auto callback transaction is committed', function() { + it('should run hooks if a non-auto callback transaction is committed', async function() { const hook = sinon.spy(); let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit().then(() => { + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + await t.commit(); expect(hook).to.have.been.calledOnce; expect(hook).to.have.been.calledWith(t); - }); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); throw err; - }); + } + throw err; } - throw err; - }) + })() ).to.eventually.be.fulfilled; }); - it('should not run hooks if a non-auto callback transaction is rolled back', function() { + it('should not run hooks if a non-auto callback transaction is rolled back', async function() { const hook = sinon.spy(); - return expect( - this.sequelize.transaction().then(t => { + + await expect( + (async () => { + const t = await this.sequelize.transaction(); t.afterCommit(hook); - return t.rollback().then(() => { - expect(hook).to.not.have.been.called; - }); - }) + await t.rollback(); + expect(hook).to.not.have.been.called; + })() ).to.eventually.be.fulfilled; }); - it('should throw an error if null is passed to afterCommit', function() { + it('should throw an error if null is passed to afterCommit', async function() { const hook = null; let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit(); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + return await t.commit(); + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); throw err; - }); + } + throw err; } - throw err; - }) + })() ).to.eventually.be.rejectedWith('"fn" must be a function'); }); - it('should throw an error if undefined is passed to afterCommit', function() { + it('should throw an error if undefined is passed to afterCommit', async function() { const hook = undefined; let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit(); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + return await t.commit(); + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); throw err; - }); + } + throw err; } - throw err; - }) + })() ).to.eventually.be.rejectedWith('"fn" must be a function'); }); - it('should throw an error if an object is passed to afterCommit', function() { + it('should throw an error if an object is passed to afterCommit', async function() { const hook = {}; let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.afterCommit(hook); - return t.commit(); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + return await t.commit(); + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); throw err; - }); + } + throw err; } - throw err; - }) + })() ).to.eventually.be.rejectedWith('"fn" must be a function'); }); - it('does not allow commits after rollback', function() { - return expect(this.sequelize.transaction().then(t => { - return t.rollback().then(() => { - return t.commit(); - }); - })).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: rollback'); + it('does not allow commits after rollback', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await t.rollback(); + return await t.commit(); + })()).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: rollback'); }); - it('does not allow rollbacks after commit', function() { - return expect(this.sequelize.transaction().then(t => { - return t.commit().then(() => { - return t.rollback(); - }); - })).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: commit'); + it('does not allow rollbacks after commit', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await t.commit(); + return await t.rollback(); + })()).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: commit'); }); - it('does not allow rollbacks after rollback', function() { - return expect(this.sequelize.transaction().then(t => { - return t.rollback().then(() => { - return t.rollback(); - }); - })).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: rollback'); + it('does not allow rollbacks after rollback', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await t.rollback(); + return await t.rollback(); + })()).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: rollback'); }); - it('works even if a transaction: null option is passed', function() { + it('works even if a transaction: null option is passed', async function() { this.sinon.spy(this.sequelize, 'query'); - return this.sequelize.transaction({ + const t = await this.sequelize.transaction({ transaction: null - }).then(t => { - return t.commit().then(() => { - expect(this.sequelize.query.callCount).to.be.greaterThan(0); - - for (let i = 0; i < this.sequelize.query.callCount; i++) { - expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); - } - }); }); + + await t.commit(); + expect(this.sequelize.query.callCount).to.be.greaterThan(0); + + for (let i = 0; i < this.sequelize.query.callCount; i++) { + expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); + } }); - it('works even if a transaction: undefined option is passed', function() { + it('works even if a transaction: undefined option is passed', async function() { this.sinon.spy(this.sequelize, 'query'); - return this.sequelize.transaction({ + const t = await this.sequelize.transaction({ transaction: undefined - }).then(t => { - return t.commit().then(() => { - expect(this.sequelize.query.callCount).to.be.greaterThan(0); - - for (let i = 0; i < this.sequelize.query.callCount; i++) { - expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); - } - }); }); + + await t.commit(); + expect(this.sequelize.query.callCount).to.be.greaterThan(0); + + for (let i = 0; i < this.sequelize.query.callCount; i++) { + expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); + } }); if (dialect === 'mysql' || dialect === 'mariadb') { describe('deadlock handling', () => { - it('should treat deadlocked transaction as rollback', function() { + it('should treat deadlocked transaction as rollback', async function() { const Task = this.sequelize.define('task', { id: { type: Sequelize.INTEGER, @@ -382,19 +396,22 @@ if (current.dialect.supports.transactions) { // execute a query, we expect the newly-created rows to be destroyed when we forcibly rollback by // throwing an error. // tl;dr; This test is designed to ensure that this function never inserts and commits a new row. - const update = (from, to) => this.sequelize.transaction(transaction => { - return Task.findAll({ - where: { - id: { - [Sequelize.Op.eq]: from - } - }, - lock: 'UPDATE', - transaction - }) - .then(() => delay(10)) - .then(() => { - return Task.update({ id: to }, { + const update = async (from, to) => this.sequelize.transaction(async transaction => { + try { + try { + await Task.findAll({ + where: { + id: { + [Sequelize.Op.eq]: from + } + }, + lock: 'UPDATE', + transaction + }); + + await delay(10); + + await Task.update({ id: to }, { where: { id: { [Sequelize.Op.ne]: to @@ -403,80 +420,73 @@ if (current.dialect.supports.transactions) { lock: transaction.LOCK.UPDATE, transaction }); - }) - .catch(e => { console.log(e.message); }) - .then(() => Task.create({ id: 2 }, { transaction })) - .catch(e => { console.log(e.message); }) - .then(() => { throw new Error('Rollback!'); }); + } catch (e) { + console.log(e.message); + } + + await Task.create({ id: 2 }, { transaction }); + } catch (e) { + console.log(e.message); + } + + throw new Error('Rollback!'); }).catch(() => {}); - return this.sequelize.sync({ force: true }) - .then(() => Task.create({ id: 0 })) - .then(() => Task.create({ id: 1 })) - .then(() => Promise.all([ - update(1, 0), - update(0, 1) - ])) - .then(() => { - return Task.count().then(count => { - // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. - expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); - }); - }); + await this.sequelize.sync({ force: true }); + await Task.create({ id: 0 }); + await Task.create({ id: 1 }); + + await Promise.all([ + update(1, 0), + update(0, 1) + ]); + + const count = await Task.count(); + // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. + expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); }); }); } if (dialect === 'sqlite') { - it('provides persistent transactions', () => { + it('provides persistent transactions', async () => { const sequelize = new Support.Sequelize('database', 'username', 'password', { dialect: 'sqlite' }), User = sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN }); - let persistentTransaction; - return sequelize.transaction().then(t => { - return sequelize.sync({ transaction: t }).then(( ) => { - return t; - }); - }).then(t => { - return User.create({}, { transaction: t }).then(( ) => { - return t.commit(); - }); - }).then(() => { - return sequelize.transaction().then(t => { - persistentTransaction = t; - }); - }).then(() => { - return User.findAll({ transaction: persistentTransaction }).then(users => { - expect(users.length).to.equal(1); - return persistentTransaction.commit(); - }); - }); + const t1 = await sequelize.transaction(); + await sequelize.sync({ transaction: t1 }); + const t0 = t1; + await User.create({}, { transaction: t0 }); + await t0.commit(); + const persistentTransaction = await sequelize.transaction(); + const users = await User.findAll({ transaction: persistentTransaction }); + expect(users.length).to.equal(1); + + await persistentTransaction.commit(); }); } if (current.dialect.supports.transactionOptions.type) { describe('transaction types', () => { - it('should support default transaction type DEFERRED', function() { - return this.sequelize.transaction({ - }).then(t => { - return t.rollback().then(() => { - expect(t.options.type).to.equal('DEFERRED'); - }); + it('should support default transaction type DEFERRED', async function() { + const t = await this.sequelize.transaction({ }); + + await t.rollback(); + expect(t.options.type).to.equal('DEFERRED'); }); Object.keys(Transaction.TYPES).forEach(key => { - it(`should allow specification of ${key} type`, function() { - return this.sequelize.transaction({ + it(`should allow specification of ${key} type`, async function() { + const t = await this.sequelize.transaction({ type: key - }).then(t => { - return t.rollback().then(() => { - expect(t.options.type).to.equal(Transaction.TYPES[key]); - }); }); + + await t.rollback(); + expect(t.options.type).to.equal(Transaction.TYPES[key]); }); }); @@ -485,61 +495,50 @@ if (current.dialect.supports.transactions) { } if (dialect === 'sqlite') { - it('automatically retries on SQLITE_BUSY failure', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - const newTransactionFunc = function() { - return sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE }).then(t => { - return User.create({}, { transaction: t }).then(( ) => { - return t.commit(); - }); - }); - }; - return Promise.all([newTransactionFunc(), newTransactionFunc()]).then(() => { - return User.findAll().then(users => { - expect(users.length).to.equal(2); - }); - }); - }); - }); + it('automatically retries on SQLITE_BUSY failure', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + await User.sync({ force: true }); + const newTransactionFunc = async function() { + const t = await sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE }); + await User.create({}, { transaction: t }); + return t.commit(); + }; + await Promise.all([newTransactionFunc(), newTransactionFunc()]); + const users = await User.findAll(); + expect(users.length).to.equal(2); }); - it('fails with SQLITE_BUSY when retry.match is changed', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { id: { type: Support.Sequelize.INTEGER, primaryKey: true }, username: Support.Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - const newTransactionFunc = function() { - return sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: { match: ['NO_MATCH'] } }).then(t => { - // introduce delay to force the busy state race condition to fail - return delay(1000).then(() => { - return User.create({ id: null, username: `test ${t.id}` }, { transaction: t }).then(() => { - return t.commit(); - }); - }); - }); - }; - return expect(Promise.all([newTransactionFunc(), newTransactionFunc()])).to.be.rejectedWith('SQLITE_BUSY: database is locked'); - }); - }); + it('fails with SQLITE_BUSY when retry.match is changed', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { id: { type: Support.Sequelize.INTEGER, primaryKey: true }, username: Support.Sequelize.STRING }); + await User.sync({ force: true }); + const newTransactionFunc = async function() { + const t = await sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: { match: ['NO_MATCH'] } }); + // introduce delay to force the busy state race condition to fail + await delay(1000); + await User.create({ id: null, username: `test ${t.id}` }, { transaction: t }); + return t.commit(); + }; + await expect(Promise.all([newTransactionFunc(), newTransactionFunc()])).to.be.rejectedWith('SQLITE_BUSY: database is locked'); }); } describe('isolation levels', () => { - it('should read the most recent committed rows when using the READ COMMITTED isolation level', function() { + it('should read the most recent committed rows when using the READ COMMITTED isolation level', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING }); - return expect( + await expect( this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, transaction => { - return User.findAll({ transaction }) - .then(users => expect( users ).to.have.lengthOf(0)) - .then(() => User.create({ username: 'jan' })) // Create a User outside of the transaction - .then(() => User.findAll({ transaction })) - .then(users => expect( users ).to.have.lengthOf(1)); // We SHOULD see the created user inside the transaction + return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, async transaction => { + const users0 = await User.findAll({ transaction }); + await expect( users0 ).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + return expect( users ).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction }); }) ).to.eventually.be.fulfilled; @@ -547,19 +546,19 @@ if (current.dialect.supports.transactions) { // mssql is excluded because it implements REPREATABLE READ using locks rather than a snapshot, and will see the new row if (!['sqlite', 'mssql'].includes(dialect)) { - it('should not read newly committed rows when using the REPEATABLE READ isolation level', function() { + it('should not read newly committed rows when using the REPEATABLE READ isolation level', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING }); - return expect( + await expect( this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ }, transaction => { - return User.findAll({ transaction }) - .then(users => expect( users ).to.have.lengthOf(0)) - .then(() => User.create({ username: 'jan' })) // Create a User outside of the transaction - .then(() => User.findAll({ transaction })) - .then(users => expect( users ).to.have.lengthOf(0)); // We SHOULD NOT see the created user inside the transaction + return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ }, async transaction => { + const users0 = await User.findAll({ transaction }); + await expect( users0 ).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + return expect( users ).to.have.lengthOf(0); // We SHOULD NOT see the created user inside the transaction }); }) ).to.eventually.be.fulfilled; @@ -568,27 +567,25 @@ if (current.dialect.supports.transactions) { // PostgreSQL is excluded because it detects Serialization Failure on commit instead of acquiring locks on the read rows if (!['sqlite', 'postgres', 'postgres-native'].includes(dialect)) { - it('should block updates after reading a row using SERIALIZABLE', function() { + it('should block updates after reading a row using SERIALIZABLE', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING }), transactionSpy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }).then(transaction => { - return User.findAll( { transaction } ) - .then(() => Promise.all([// Update should not succeed before transaction has committed - User.update({ username: 'joe' }, { - where: { - username: 'jan' - } - }).then(() => expect(transactionSpy).to.have.been.called ), delay(2000) - .then(() => transaction.commit()) - .then(transactionSpy)])); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const transaction = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }); + await User.findAll( { transaction } ); + + await Promise.all([// Update should not succeed before transaction has committed + User.update({ username: 'joe' }, { + where: { + username: 'jan' + } + }).then(() => expect(transactionSpy).to.have.been.called ), delay(2000) + .then(() => transaction.commit()) + .then(transactionSpy)]); }); } @@ -597,7 +594,7 @@ if (current.dialect.supports.transactions) { if (current.dialect.supports.lock) { describe('row locking', () => { - it('supports for update', function() { + it('supports for update', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN @@ -605,186 +602,185 @@ if (current.dialect.supports.transactions) { t1Spy = sinon.spy(), t2Spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findOne({ - where: { - username: 'jan' - }, - lock: t1.LOCK.UPDATE, - transaction: t1 - }).then(t1Jan => { - return this.sequelize.transaction({ - isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED - }).then(t2 => { - return Promise.all([User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.UPDATE, - transaction: t2 - }).then(() => { - t2Spy(); - return t2.commit().then(() => { - expect(t2Spy).to.have.been.calledAfter(t1Spy); // Find should not succeed before t1 has committed - }); - }), t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - t1Spy(); - return delay(2000).then(() => { - return t1.commit(); - }); - })]); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const t1 = await this.sequelize.transaction(); + + const t1Jan = await User.findOne({ + where: { + username: 'jan' + }, + lock: t1.LOCK.UPDATE, + transaction: t1 }); + + const t2 = await this.sequelize.transaction({ + isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED + }); + + await Promise.all([(async () => { + await User.findOne({ + where: { + username: 'jan' + }, + lock: t2.LOCK.UPDATE, + transaction: t2 + }); + + t2Spy(); + await t2.commit(); + expect(t2Spy).to.have.been.calledAfter(t1Spy); // Find should not succeed before t1 has committed + })(), (async () => { + await t1Jan.update({ + awesome: true + }, { + transaction: t1 + }); + + t1Spy(); + await delay(2000); + return await t1.commit(); + })()]); }); if (current.dialect.supports.skipLocked) { - it('supports for update with skip locked', function() { + it('supports for update with skip locked', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create( - { username: 'jan' } - ), - User.create( - { username: 'joe' } - ) - ]); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findAll({ - limit: 1, - lock: true, - transaction: t1 - }).then(results => { - const firstUserId = results[0].id; - return this.sequelize.transaction().then(t2 => { - return User.findAll({ - limit: 1, - lock: true, - skipLocked: true, - transaction: t2 - }).then(secondResults => { - expect(secondResults[0].id).to.not.equal(firstUserId); - return Promise.all([ - t1.commit(), - t2.commit() - ]); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + User.create( + { username: 'jan' } + ), + User.create( + { username: 'joe' } + ) + ]); + + const t1 = await this.sequelize.transaction(); + + const results = await User.findAll({ + limit: 1, + lock: true, + transaction: t1 }); + + const firstUserId = results[0].id; + const t2 = await this.sequelize.transaction(); + + const secondResults = await User.findAll({ + limit: 1, + lock: true, + skipLocked: true, + transaction: t2 + }); + + expect(secondResults[0].id).to.not.equal(firstUserId); + + await Promise.all([ + t1.commit(), + t2.commit() + ]); }); } - it('fail locking with outer joins', function() { + it('fail locking with outer joins', async function() { const User = this.sequelize.define('User', { username: Support.Sequelize.STRING }), Task = this.sequelize.define('Task', { title: Support.Sequelize.STRING, active: Support.Sequelize.BOOLEAN }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'John' }), - Task.create({ title: 'Get rich', active: false }) - ]).then(([john, task1]) => { - return john.setTasks([task1]); - }) - .then(() => { - return this.sequelize.transaction(t1 => { - - if (current.dialect.supports.lockOuterJoinFailure) { - - return expect(User.findOne({ - where: { - username: 'John' - }, - include: [Task], - lock: t1.LOCK.UPDATE, - transaction: t1 - })).to.be.rejectedWith('FOR UPDATE cannot be applied to the nullable side of an outer join'); - } - - return User.findOne({ - where: { - username: 'John' - }, - include: [Task], - lock: t1.LOCK.UPDATE, - transaction: t1 - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1] = await Promise.all([ + User.create({ username: 'John' }), + Task.create({ title: 'Get rich', active: false }) + ]); + + await john.setTasks([task1]); + + await this.sequelize.transaction(t1 => { + + if (current.dialect.supports.lockOuterJoinFailure) { + + return expect(User.findOne({ + where: { + username: 'John' + }, + include: [Task], + lock: t1.LOCK.UPDATE, + transaction: t1 + })).to.be.rejectedWith('FOR UPDATE cannot be applied to the nullable side of an outer join'); + } + + return User.findOne({ + where: { + username: 'John' + }, + include: [Task], + lock: t1.LOCK.UPDATE, + transaction: t1 + }); }); }); if (current.dialect.supports.lockOf) { - it('supports for update of table', function() { + it('supports for update of table', async function() { const User = this.sequelize.define('User', { username: Support.Sequelize.STRING }, { tableName: 'Person' }), Task = this.sequelize.define('Task', { title: Support.Sequelize.STRING, active: Support.Sequelize.BOOLEAN }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'John' }), - Task.create({ title: 'Get rich', active: false }), - Task.create({ title: 'Die trying', active: false }) - ]).then(([john, task1]) => { - return john.setTasks([task1]); - }) - .then(() => { - return this.sequelize.transaction(t1 => { - return User.findOne({ - where: { - username: 'John' - }, - include: [Task], - lock: { - level: t1.LOCK.UPDATE, - of: User - }, - transaction: t1 - }).then(t1John => { - // should not be blocked by the lock of the other transaction - return this.sequelize.transaction(t2 => { - return Task.update({ - active: true - }, { - where: { - active: false - }, - transaction: t2 - }); - }).then(() => { - return t1John.save({ - transaction: t1 - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1] = await Promise.all([ + User.create({ username: 'John' }), + Task.create({ title: 'Get rich', active: false }), + Task.create({ title: 'Die trying', active: false }) + ]); + + await john.setTasks([task1]); + + await this.sequelize.transaction(async t1 => { + const t1John = await User.findOne({ + where: { + username: 'John' + }, + include: [Task], + lock: { + level: t1.LOCK.UPDATE, + of: User + }, + transaction: t1 + }); + + // should not be blocked by the lock of the other transaction + await this.sequelize.transaction(t2 => { + return Task.update({ + active: true + }, { + where: { + active: false + }, + transaction: t2 }); + }); + + return t1John.save({ + transaction: t1 + }); }); }); } if (current.dialect.supports.lockKey) { - it('supports for key share', function() { + it('supports for key share', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN @@ -792,42 +788,43 @@ if (current.dialect.supports.transactions) { t1Spy = sinon.spy(), t2Spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findOne({ - where: { - username: 'jan' - }, - lock: t1.LOCK.NO_KEY_UPDATE, - transaction: t1 - }).then(t1Jan => { - return this.sequelize.transaction().then(t2 => { - return Promise.all([User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.KEY_SHARE, - transaction: t2 - }).then(() => { - t2Spy(); - return t2.commit(); - }), t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - return delay(2000).then(() => { - t1Spy(); - expect(t1Spy).to.have.been.calledAfter(t2Spy); - return t1.commit(); - }); - })]); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const t1 = await this.sequelize.transaction(); + + const t1Jan = await User.findOne({ + where: { + username: 'jan' + }, + lock: t1.LOCK.NO_KEY_UPDATE, + transaction: t1 }); + + const t2 = await this.sequelize.transaction(); + + await Promise.all([(async () => { + await User.findOne({ + where: { + username: 'jan' + }, + lock: t2.LOCK.KEY_SHARE, + transaction: t2 + }); + + t2Spy(); + return await t2.commit(); + })(), (async () => { + await t1Jan.update({ + awesome: true + }, { + transaction: t1 + }); + + await delay(2000); + t1Spy(); + expect(t1Spy).to.have.been.calledAfter(t2Spy); + return await t1.commit(); + })()]); }); } @@ -855,21 +852,24 @@ if (current.dialect.supports.transactions) { }); await Promise.all([ - User.findByPk(user.id, { - transaction: t2 - }).then(async t2Jan => { + (async () => { + const t2Jan = await User.findByPk(user.id, { + transaction: t2 + }); + t2FindSpy(); await t2Jan.update({ awesome: false }, { transaction: t2 }); t2UpdateSpy(); await t2.commit(); - }), - t1Jan.update({ awesome: true }, { transaction: t1 }).then(async () => { + })(), + (async () => { + await t1Jan.update({ awesome: true }, { transaction: t1 }); await delay(2000); t1CommitSpy(); await t1.commit(); - }) + })() ]); // (t2) find call should have returned before (t1) commit diff --git a/test/integration/trigger.test.js b/test/integration/trigger.test.js index 16fd3ae505e4..6877fbdd8841 100644 --- a/test/integration/trigger.test.js +++ b/test/integration/trigger.test.js @@ -22,7 +22,7 @@ if (current.dialect.supports.tmpTableTrigger) { 'select * from deleted\n' + 'end\n'; - beforeEach(function() { + beforeEach(async function() { User = this.sequelize.define('user', { username: { type: Sequelize.STRING, @@ -32,56 +32,52 @@ if (current.dialect.supports.tmpTableTrigger) { hasTrigger: true }); - return User.sync({ force: true }).then(() => { - return this.sequelize.query(triggerQuery, { type: this.sequelize.QueryTypes.RAW }); - }); + await User.sync({ force: true }); + + await this.sequelize.query(triggerQuery, { type: this.sequelize.QueryTypes.RAW }); }); - it('should return output rows after insert', () => { - return User.create({ + it('should return output rows after insert', async () => { + await User.create({ username: 'triggertest' - }).then(() => { - return expect(User.findOne({ username: 'triggertest' })).to.eventually.have.property('username').which.equals('triggertest'); }); + + await expect(User.findOne({ username: 'triggertest' })).to.eventually.have.property('username').which.equals('triggertest'); }); - it('should return output rows after instance update', () => { - return User.create({ + it('should return output rows after instance update', async () => { + const user = await User.create({ username: 'triggertest' - }).then(user => { - user.username = 'usernamechanged'; - return user.save(); - }) - .then(() => { - return expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); - }); + }); + + user.username = 'usernamechanged'; + await user.save(); + await expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); }); - it('should return output rows after Model update', () => { - return User.create({ + it('should return output rows after Model update', async () => { + const user = await User.create({ username: 'triggertest' - }).then(user => { - return User.update({ - username: 'usernamechanged' - }, { - where: { - id: user.get('id') - } - }); - }) - .then(() => { - return expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); - }); + }); + + await User.update({ + username: 'usernamechanged' + }, { + where: { + id: user.get('id') + } + }); + + await expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); }); - it('should successfully delete with a trigger on the table', () => { - return User.create({ + it('should successfully delete with a trigger on the table', async () => { + const user = await User.create({ username: 'triggertest' - }).then(user => { - return user.destroy(); - }).then(() => { - return expect(User.findOne({ username: 'triggertest' })).to.eventually.be.null; }); + + await user.destroy(); + await expect(User.findOne({ username: 'triggertest' })).to.eventually.be.null; }); }); }); diff --git a/test/integration/utils.test.js b/test/integration/utils.test.js index 2e5a25318afb..622047e61b21 100644 --- a/test/integration/utils.test.js +++ b/test/integration/utils.test.js @@ -138,33 +138,33 @@ describe(Support.getTestDialectTeaser('Utils'), () => { describe('Sequelize.fn', () => { let Airplane; - beforeEach(function() { + beforeEach(async function() { Airplane = this.sequelize.define('Airplane', { wings: DataTypes.INTEGER, engines: DataTypes.INTEGER }); - return Airplane.sync({ force: true }).then(() => { - return Airplane.bulkCreate([ - { - wings: 2, - engines: 0 - }, { - wings: 4, - engines: 1 - }, { - wings: 2, - engines: 2 - } - ]); - }); + await Airplane.sync({ force: true }); + + await Airplane.bulkCreate([ + { + wings: 2, + engines: 0 + }, { + wings: 4, + engines: 1 + }, { + wings: 2, + engines: 2 + } + ]); }); if (Support.getTestDialect() !== 'mssql') { - it('accepts condition object (with cast)', function() { + it('accepts condition object (with cast)', async function() { const type = Support.getTestDialect() === 'mysql' ? 'unsigned' : 'int'; - return Airplane.findAll({ + const [airplane] = await Airplane.findAll({ attributes: [ [this.sequelize.fn('COUNT', '*'), 'count'], [Sequelize.fn('SUM', Sequelize.cast({ @@ -179,18 +179,18 @@ describe(Support.getTestDialectTeaser('Utils'), () => { } }, type)), 'count-engines-wings'] ] - }).then(([airplane]) => { - // TODO: `parseInt` should not be needed, see #10533 - expect(parseInt(airplane.get('count'), 10)).to.equal(3); - expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); - expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); + + // TODO: `parseInt` should not be needed, see #10533 + expect(parseInt(airplane.get('count'), 10)).to.equal(3); + expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); + expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); } if (Support.getTestDialect() !== 'mssql' && Support.getTestDialect() !== 'postgres') { - it('accepts condition object (auto casting)', function() { - return Airplane.findAll({ + it('accepts condition object (auto casting)', async function() { + const [airplane] = await Airplane.findAll({ attributes: [ [this.sequelize.fn('COUNT', '*'), 'count'], [Sequelize.fn('SUM', { @@ -205,12 +205,12 @@ describe(Support.getTestDialectTeaser('Utils'), () => { } }), 'count-engines-wings'] ] - }).then(([airplane]) => { - // TODO: `parseInt` should not be needed, see #10533 - expect(airplane.get('count')).to.equal(3); - expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); - expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); + + // TODO: `parseInt` should not be needed, see #10533 + expect(airplane.get('count')).to.equal(3); + expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); + expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); } }); diff --git a/test/integration/vectors.test.js b/test/integration/vectors.test.js index e1b7cfc7d0a0..e604d57a4267 100644 --- a/test/integration/vectors.test.js +++ b/test/integration/vectors.test.js @@ -8,22 +8,21 @@ const chai = require('chai'), chai.should(); describe(Support.getTestDialectTeaser('Vectors'), () => { - it('should not allow insert backslash', function() { + it('should not allow insert backslash', async function() { const Student = this.sequelize.define('student', { name: Sequelize.STRING }, { tableName: 'student' }); - return Student.sync({ force: true }).then(() => { - return Student.create({ - name: 'Robert\\\'); DROP TABLE "students"; --' - }).then(result => { - expect(result.get('name')).to.equal('Robert\\\'); DROP TABLE "students"; --'); - return Student.findAll(); - }).then(result => { - expect(result[0].name).to.equal('Robert\\\'); DROP TABLE "students"; --'); - }); + await Student.sync({ force: true }); + + const result0 = await Student.create({ + name: 'Robert\\\'); DROP TABLE "students"; --' }); + + expect(result0.get('name')).to.equal('Robert\\\'); DROP TABLE "students"; --'); + const result = await Student.findAll(); + expect(result[0].name).to.equal('Robert\\\'); DROP TABLE "students"; --'); }); }); From fd11d98f990d9f95523111272c6ca28782d9ac05 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 22 May 2020 01:36:50 -0500 Subject: [PATCH 157/414] test(integration/query-interface): asyncify (#12293) --- .../query-interface/changeColumn.test.js | 306 +++++++++--------- .../query-interface/createTable.test.js | 194 ++++++----- .../query-interface/describeTable.test.js | 219 ++++++------- .../query-interface/dropEnum.test.js | 31 +- .../query-interface/removeColumn.test.js | 184 +++++------ 5 files changed, 442 insertions(+), 492 deletions(-) diff --git a/test/integration/query-interface/changeColumn.test.js b/test/integration/query-interface/changeColumn.test.js index 62ccc9a2ec6d..82817a08b1b1 100644 --- a/test/integration/query-interface/changeColumn.test.js +++ b/test/integration/query-interface/changeColumn.test.js @@ -12,47 +12,47 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('changeColumn', () => { - it('should support schemas', function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - currency: DataTypes.INTEGER - }).then(() => { - return this.queryInterface.changeColumn({ - tableName: 'users', - schema: 'archive' - }, 'currency', { - type: DataTypes.FLOAT - }); - }).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - if (dialect === 'postgres' || dialect === 'postgres-native') { - expect(table.currency.type).to.equal('DOUBLE PRECISION'); - } else { - expect(table.currency.type).to.equal('FLOAT'); - } - }); + it('should support schemas', async function() { + await this.sequelize.createSchema('archive'); + + await this.queryInterface.createTable({ + tableName: 'users', + schema: 'archive' + }, { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + currency: DataTypes.INTEGER }); + + await this.queryInterface.changeColumn({ + tableName: 'users', + schema: 'archive' + }, 'currency', { + type: DataTypes.FLOAT + }); + + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' + }); + + if (dialect === 'postgres' || dialect === 'postgres-native') { + expect(table.currency.type).to.equal('DOUBLE PRECISION'); + } else { + expect(table.currency.type).to.equal('FLOAT'); + } }); - it('should change columns', function() { - return this.queryInterface.createTable({ + it('should change columns', async function() { + await this.queryInterface.createTable({ tableName: 'users' }, { id: { @@ -61,67 +61,67 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { autoIncrement: true }, currency: DataTypes.INTEGER - }).then(() => { - return this.queryInterface.changeColumn('users', 'currency', { - type: DataTypes.FLOAT, - allowNull: true - }); - }).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users' - }); - }).then(table => { - if (dialect === 'postgres' || dialect === 'postgres-native') { - expect(table.currency.type).to.equal('DOUBLE PRECISION'); - } else { - expect(table.currency.type).to.equal('FLOAT'); - } }); + + await this.queryInterface.changeColumn('users', 'currency', { + type: DataTypes.FLOAT, + allowNull: true + }); + + const table = await this.queryInterface.describeTable({ + tableName: 'users' + }); + + if (dialect === 'postgres' || dialect === 'postgres-native') { + expect(table.currency.type).to.equal('DOUBLE PRECISION'); + } else { + expect(table.currency.type).to.equal('FLOAT'); + } }); // MSSQL doesn't support using a modified column in a check constraint. // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql if (dialect !== 'mssql') { - it('should work with enums (case 1)', function() { - return this.queryInterface.createTable({ + it('should work with enums (case 1)', async function() { + await this.queryInterface.createTable({ tableName: 'users' }, { firstName: DataTypes.STRING - }).then(() => { - return this.queryInterface.changeColumn('users', 'firstName', { - type: DataTypes.ENUM(['value1', 'value2', 'value3']) - }); + }); + + await this.queryInterface.changeColumn('users', 'firstName', { + type: DataTypes.ENUM(['value1', 'value2', 'value3']) }); }); - it('should work with enums (case 2)', function() { - return this.queryInterface.createTable({ + it('should work with enums (case 2)', async function() { + await this.queryInterface.createTable({ tableName: 'users' }, { firstName: DataTypes.STRING - }).then(() => { - return this.queryInterface.changeColumn('users', 'firstName', { - type: DataTypes.ENUM, - values: ['value1', 'value2', 'value3'] - }); + }); + + await this.queryInterface.changeColumn('users', 'firstName', { + type: DataTypes.ENUM, + values: ['value1', 'value2', 'value3'] }); }); - it('should work with enums with schemas', function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { - firstName: DataTypes.STRING - }); - }).then(() => { - return this.queryInterface.changeColumn({ - tableName: 'users', - schema: 'archive' - }, 'firstName', { - type: DataTypes.ENUM(['value1', 'value2', 'value3']) - }); + it('should work with enums with schemas', async function() { + await this.sequelize.createSchema('archive'); + + await this.queryInterface.createTable({ + tableName: 'users', + schema: 'archive' + }, { + firstName: DataTypes.STRING + }); + + await this.queryInterface.changeColumn({ + tableName: 'users', + schema: 'archive' + }, 'firstName', { + type: DataTypes.ENUM(['value1', 'value2', 'value3']) }); }); } @@ -129,8 +129,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { //SQlite natively doesn't support ALTER Foreign key if (dialect !== 'sqlite') { describe('should support foreign keys', () => { - beforeEach(function() { - return this.queryInterface.createTable('users', { + beforeEach(async function() { + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -140,41 +140,39 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { type: DataTypes.INTEGER, allowNull: false } - }).then(() => { - return this.queryInterface.createTable('level', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); }); - }); - it('able to change column to foreign key', function() { - return this.queryInterface.getForeignKeyReferencesForTable('users').then( foreignKeys => { - expect(foreignKeys).to.be.an('array'); - expect(foreignKeys).to.be.empty; - return this.queryInterface.changeColumn('users', 'level_id', { + await this.queryInterface.createTable('level', { + id: { type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }); - }).then(() => { - return this.queryInterface.getForeignKeyReferencesForTable('users'); - }).then(newForeignKeys => { - expect(newForeignKeys).to.be.an('array'); - expect(newForeignKeys).to.have.lengthOf(1); - expect(newForeignKeys[0].columnName).to.be.equal('level_id'); + primaryKey: true, + autoIncrement: true + } + }); + }); + + it('able to change column to foreign key', async function() { + const foreignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + expect(foreignKeys).to.be.an('array'); + expect(foreignKeys).to.be.empty; + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + references: { + model: 'level', + key: 'id' + }, + onUpdate: 'cascade', + onDelete: 'cascade' }); + + const newForeignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + expect(newForeignKeys).to.be.an('array'); + expect(newForeignKeys).to.have.lengthOf(1); + expect(newForeignKeys[0].columnName).to.be.equal('level_id'); }); - it('able to change column property without affecting other properties', function() { - let firstTable, firstForeignKeys; + it('able to change column property without affecting other properties', async function() { // 1. look for users table information // 2. change column level_id on users to have a Foreign Key // 3. look for users table Foreign Keys information @@ -182,58 +180,56 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { // 5. look for new foreign keys information // 6. look for new table structure information // 7. compare foreign keys and tables(before and after the changes) - return this.queryInterface.describeTable({ + const firstTable = await this.queryInterface.describeTable({ tableName: 'users' - }).then( describedTable => { - firstTable = describedTable; - return this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }); - }).then( () => { - return this.queryInterface.getForeignKeyReferencesForTable('users'); - }).then( keys => { - firstForeignKeys = keys; - return this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - allowNull: true - }); - }).then( () => { - return this.queryInterface.getForeignKeyReferencesForTable('users'); - }).then( newForeignKeys => { - expect(firstForeignKeys.length).to.be.equal(newForeignKeys.length); - expect(firstForeignKeys[0].columnName).to.be.equal('level_id'); - expect(firstForeignKeys[0].columnName).to.be.equal(newForeignKeys[0].columnName); - - return this.queryInterface.describeTable({ - tableName: 'users' - }); - }).then( describedTable => { - expect(describedTable.level_id).to.have.property('allowNull'); - expect(describedTable.level_id.allowNull).to.not.equal(firstTable.level_id.allowNull); - expect(describedTable.level_id.allowNull).to.be.equal(true); }); + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + references: { + model: 'level', + key: 'id' + }, + onUpdate: 'cascade', + onDelete: 'cascade' + }); + + const keys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + const firstForeignKeys = keys; + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + allowNull: true + }); + + const newForeignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + expect(firstForeignKeys.length).to.be.equal(newForeignKeys.length); + expect(firstForeignKeys[0].columnName).to.be.equal('level_id'); + expect(firstForeignKeys[0].columnName).to.be.equal(newForeignKeys[0].columnName); + + const describedTable = await this.queryInterface.describeTable({ + tableName: 'users' + }); + + expect(describedTable.level_id).to.have.property('allowNull'); + expect(describedTable.level_id.allowNull).to.not.equal(firstTable.level_id.allowNull); + expect(describedTable.level_id.allowNull).to.be.equal(true); }); - it('should change the comment of column', function() { - return this.queryInterface.describeTable({ + it('should change the comment of column', async function() { + const describedTable = await this.queryInterface.describeTable({ tableName: 'users' - }).then(describedTable => { - expect(describedTable.level_id.comment).to.be.equal(null); - return this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - comment: 'FooBar' - }); - }).then(() => { - return this.queryInterface.describeTable({ tableName: 'users' }); - }).then(describedTable2 => { - expect(describedTable2.level_id.comment).to.be.equal('FooBar'); }); + + expect(describedTable.level_id.comment).to.be.equal(null); + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + comment: 'FooBar' + }); + + const describedTable2 = await this.queryInterface.describeTable({ tableName: 'users' }); + expect(describedTable2.level_id.comment).to.be.equal('FooBar'); }); }); } diff --git a/test/integration/query-interface/createTable.test.js b/test/integration/query-interface/createTable.test.js index 51a4304922c3..7f40a2faa8df 100644 --- a/test/integration/query-interface/createTable.test.js +++ b/test/integration/query-interface/createTable.test.js @@ -12,29 +12,27 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); // FIXME: These tests should make assertions against the created table using describeTable describe('createTable', () => { - it('should create a auto increment primary key', function() { - return this.queryInterface.createTable('TableWithPK', { + it('should create a auto increment primary key', async function() { + await this.queryInterface.createTable('TableWithPK', { table_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.insert(null, 'TableWithPK', {}, { raw: true, returning: true, plain: true }) - .then(([response]) => { - expect(response.table_id || typeof response !== 'object' && response).to.be.ok; - }); }); + + const [response] = await this.queryInterface.insert(null, 'TableWithPK', {}, { raw: true, returning: true, plain: true }); + expect(response.table_id || typeof response !== 'object' && response).to.be.ok; }); - it('should create unique constraint with uniqueKeys', function() { - return this.queryInterface.createTable('MyTable', { + it('should create unique constraint with uniqueKeys', async function() { + await this.queryInterface.createTable('MyTable', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -55,131 +53,123 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { fields: ['name'] } } - }).then(() => { - return this.queryInterface.showIndex('MyTable'); - }).then(indexes => { - switch (dialect) { - case 'postgres': - case 'postgres-native': - case 'sqlite': - case 'mssql': - - // name + email - expect(indexes[0].unique).to.be.true; - expect(indexes[0].fields[0].attribute).to.equal('name'); - expect(indexes[0].fields[1].attribute).to.equal('email'); - - // name - expect(indexes[1].unique).to.be.true; - expect(indexes[1].fields[0].attribute).to.equal('name'); - break; - - case 'mariadb': - case 'mysql': - // name + email - expect(indexes[1].unique).to.be.true; - expect(indexes[1].fields[0].attribute).to.equal('name'); - expect(indexes[1].fields[1].attribute).to.equal('email'); - - // name - expect(indexes[2].unique).to.be.true; - expect(indexes[2].fields[0].attribute).to.equal('name'); - break; - - default: - throw new Error(`Not implemented fpr ${dialect}`); - } }); + + const indexes = await this.queryInterface.showIndex('MyTable'); + switch (dialect) { + case 'postgres': + case 'postgres-native': + case 'sqlite': + case 'mssql': + + // name + email + expect(indexes[0].unique).to.be.true; + expect(indexes[0].fields[0].attribute).to.equal('name'); + expect(indexes[0].fields[1].attribute).to.equal('email'); + + // name + expect(indexes[1].unique).to.be.true; + expect(indexes[1].fields[0].attribute).to.equal('name'); + break; + case 'mariadb': + case 'mysql': + // name + email + expect(indexes[1].unique).to.be.true; + expect(indexes[1].fields[0].attribute).to.equal('name'); + expect(indexes[1].fields[1].attribute).to.equal('email'); + + // name + expect(indexes[2].unique).to.be.true; + expect(indexes[2].fields[0].attribute).to.equal('name'); + break; + default: + throw new Error(`Not implemented fpr ${dialect}`); + } }); - it('should work with schemas', function() { - return this.sequelize.createSchema('hero').then(() => { - return this.queryInterface.createTable('User', { - name: { - type: DataTypes.STRING - } - }, { - schema: 'hero' - }); + it('should work with schemas', async function() { + await this.sequelize.createSchema('hero'); + + await this.queryInterface.createTable('User', { + name: { + type: DataTypes.STRING + } + }, { + schema: 'hero' }); }); describe('enums', () => { - it('should work with enums (1)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (1)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: DataTypes.ENUM('value1', 'value2', 'value3') - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (2)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (2)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: { type: DataTypes.ENUM, values: ['value1', 'value2', 'value3'] } - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (3)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (3)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: { type: DataTypes.ENUM, values: ['value1', 'value2', 'value3'], field: 'otherName' } - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (4)', function() { - return this.queryInterface.createSchema('archive').then(() => { - return this.queryInterface.createTable('SomeTable', { - someEnum: { - type: DataTypes.ENUM, - values: ['value1', 'value2', 'value3'], - field: 'otherName' - } - }, { schema: 'archive' }); - }).then(() => { - return this.queryInterface.describeTable('SomeTable', { schema: 'archive' }); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); + it('should work with enums (4)', async function() { + await this.queryInterface.createSchema('archive'); + + await this.queryInterface.createTable('SomeTable', { + someEnum: { + type: DataTypes.ENUM, + values: ['value1', 'value2', 'value3'], + field: 'otherName' } - }); + }, { schema: 'archive' }); + + const table = await this.queryInterface.describeTable('SomeTable', { schema: 'archive' }); + if (dialect.includes('postgres')) { + expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (5)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (5)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: { type: DataTypes.ENUM(['COMMENT']), comment: 'special enum col' } - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.someEnum.special).to.deep.equal(['COMMENT']); - expect(table.someEnum.comment).to.equal('special enum col'); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.someEnum.special).to.deep.equal(['COMMENT']); + expect(table.someEnum.comment).to.equal('special enum col'); + } }); }); }); diff --git a/test/integration/query-interface/describeTable.test.js b/test/integration/query-interface/describeTable.test.js index 7671423b0e16..5785a8ba5e6e 100644 --- a/test/integration/query-interface/describeTable.test.js +++ b/test/integration/query-interface/describeTable.test.js @@ -12,13 +12,13 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('describeTable', () => { if (Support.sequelize.dialect.supports.schemas) { - it('reads the metadata of the table with schema', function() { + it('reads the metadata of the table with schema', async function() { const MyTable1 = this.sequelize.define('my_table', { username1: DataTypes.STRING }); @@ -27,36 +27,25 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { username2: DataTypes.STRING }, { schema: 'test_meta' }); - return this.sequelize.createSchema('test_meta') - .then(() => { - return MyTable1.sync({ force: true }); - }) - .then(() => { - return MyTable2.sync({ force: true }); - }) - .then(() => { - return this.queryInterface.describeTable('my_tables', 'test_meta'); - }) - .then(metadata => { - expect(metadata.username2).not.to.be.undefined; - }) - .then(() => { - return this.queryInterface.describeTable('my_tables'); - }) - .then(metadata => { - expect(metadata.username1).not.to.be.undefined; - return this.sequelize.dropSchema('test_meta'); - }); + await this.sequelize.createSchema('test_meta'); + await MyTable1.sync({ force: true }); + await MyTable2.sync({ force: true }); + const metadata0 = await this.queryInterface.describeTable('my_tables', 'test_meta'); + expect(metadata0.username2).not.to.be.undefined; + const metadata = await this.queryInterface.describeTable('my_tables'); + expect(metadata.username1).not.to.be.undefined; + + await this.sequelize.dropSchema('test_meta'); }); } - it('rejects when no data is available', function() { - return expect( + it('rejects when no data is available', async function() { + await expect( this.queryInterface.describeTable('_some_random_missing_table') ).to.be.rejectedWith('No description found for "_some_random_missing_table" table. Check the table name and schema; remember, they _are_ case sensitive.'); }); - it('reads the metadata of the table', function() { + it('reads the metadata of the table', async function() { const Users = this.sequelize.define('_Users', { username: DataTypes.STRING, city: { @@ -68,81 +57,79 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { enumVals: DataTypes.ENUM('hello', 'world') }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.describeTable('_Users').then(metadata => { - const id = metadata.id; - const username = metadata.username; - const city = metadata.city; - const isAdmin = metadata.isAdmin; - const enumVals = metadata.enumVals; - - expect(id.primaryKey).to.be.true; - - if (['mysql', 'mssql'].includes(dialect)) { - expect(id.autoIncrement).to.be.true; - } - - let assertVal = 'VARCHAR(255)'; - switch (dialect) { - case 'postgres': - assertVal = 'CHARACTER VARYING(255)'; - break; - case 'mssql': - assertVal = 'NVARCHAR(255)'; - break; - } - expect(username.type).to.equal(assertVal); - expect(username.allowNull).to.be.true; - - switch (dialect) { - case 'sqlite': - expect(username.defaultValue).to.be.undefined; - break; - default: - expect(username.defaultValue).to.be.null; - } - - switch (dialect) { - case 'sqlite': - expect(city.defaultValue).to.be.null; - break; - } - - assertVal = 'TINYINT(1)'; - switch (dialect) { - case 'postgres': - assertVal = 'BOOLEAN'; - break; - case 'mssql': - assertVal = 'BIT'; - break; - } - expect(isAdmin.type).to.equal(assertVal); - expect(isAdmin.allowNull).to.be.true; - switch (dialect) { - case 'sqlite': - expect(isAdmin.defaultValue).to.be.undefined; - break; - default: - expect(isAdmin.defaultValue).to.be.null; - } - - if (dialect.match(/^postgres/)) { - expect(enumVals.special).to.be.instanceof(Array); - expect(enumVals.special).to.have.length(2); - } else if (dialect === 'mysql') { - expect(enumVals.type).to.eql('ENUM(\'hello\',\'world\')'); - } - - if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { - expect(city.comment).to.equal('Users City'); - expect(username.comment).to.equal(null); - } - }); - }); + await Users.sync({ force: true }); + const metadata = await this.queryInterface.describeTable('_Users'); + const id = metadata.id; + const username = metadata.username; + const city = metadata.city; + const isAdmin = metadata.isAdmin; + const enumVals = metadata.enumVals; + + expect(id.primaryKey).to.be.true; + + if (['mysql', 'mssql'].includes(dialect)) { + expect(id.autoIncrement).to.be.true; + } + + let assertVal = 'VARCHAR(255)'; + switch (dialect) { + case 'postgres': + assertVal = 'CHARACTER VARYING(255)'; + break; + case 'mssql': + assertVal = 'NVARCHAR(255)'; + break; + } + expect(username.type).to.equal(assertVal); + expect(username.allowNull).to.be.true; + + switch (dialect) { + case 'sqlite': + expect(username.defaultValue).to.be.undefined; + break; + default: + expect(username.defaultValue).to.be.null; + } + + switch (dialect) { + case 'sqlite': + expect(city.defaultValue).to.be.null; + break; + } + + assertVal = 'TINYINT(1)'; + switch (dialect) { + case 'postgres': + assertVal = 'BOOLEAN'; + break; + case 'mssql': + assertVal = 'BIT'; + break; + } + expect(isAdmin.type).to.equal(assertVal); + expect(isAdmin.allowNull).to.be.true; + switch (dialect) { + case 'sqlite': + expect(isAdmin.defaultValue).to.be.undefined; + break; + default: + expect(isAdmin.defaultValue).to.be.null; + } + + if (dialect.match(/^postgres/)) { + expect(enumVals.special).to.be.instanceof(Array); + expect(enumVals.special).to.have.length(2); + } else if (dialect === 'mysql') { + expect(enumVals.type).to.eql('ENUM(\'hello\',\'world\')'); + } + + if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { + expect(city.comment).to.equal('Users City'); + expect(username.comment).to.equal(null); + } }); - it('should correctly determine the primary key columns', function() { + it('should correctly determine the primary key columns', async function() { const Country = this.sequelize.define('_Country', { code: { type: DataTypes.STRING, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false } @@ -160,26 +147,20 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Country.sync({ force: true }).then(() => { - return this.queryInterface.describeTable('_Country').then( - metacountry => { - expect(metacountry.code.primaryKey).to.eql(true); - expect(metacountry.name.primaryKey).to.eql(false); - - return Alumni.sync({ force: true }).then(() => { - return this.queryInterface.describeTable('_Alumni').then( - metalumni => { - expect(metalumni.year.primaryKey).to.eql(true); - expect(metalumni.num.primaryKey).to.eql(true); - expect(metalumni.username.primaryKey).to.eql(false); - expect(metalumni.dob.primaryKey).to.eql(false); - expect(metalumni.dod.primaryKey).to.eql(false); - expect(metalumni.ctrycod.primaryKey).to.eql(false); - expect(metalumni.city.primaryKey).to.eql(false); - }); - }); - }); - }); + await Country.sync({ force: true }); + const metacountry = await this.queryInterface.describeTable('_Country'); + expect(metacountry.code.primaryKey).to.eql(true); + expect(metacountry.name.primaryKey).to.eql(false); + + await Alumni.sync({ force: true }); + const metalumni = await this.queryInterface.describeTable('_Alumni'); + expect(metalumni.year.primaryKey).to.eql(true); + expect(metalumni.num.primaryKey).to.eql(true); + expect(metalumni.username.primaryKey).to.eql(false); + expect(metalumni.dob.primaryKey).to.eql(false); + expect(metalumni.dod.primaryKey).to.eql(false); + expect(metalumni.ctrycod.primaryKey).to.eql(false); + expect(metalumni.city.primaryKey).to.eql(false); }); }); }); diff --git a/test/integration/query-interface/dropEnum.test.js b/test/integration/query-interface/dropEnum.test.js index 9ee1c2a0b9de..595d347162c1 100644 --- a/test/integration/query-interface/dropEnum.test.js +++ b/test/integration/query-interface/dropEnum.test.js @@ -12,13 +12,13 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('dropEnum', () => { - beforeEach(function() { - return this.queryInterface.createTable('menus', { + beforeEach(async function() { + await this.queryInterface.createTable('menus', { structuretype: { type: DataTypes.ENUM('menus', 'submenu', 'routine'), allowNull: true @@ -35,20 +35,15 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { }); if (dialect === 'postgres') { - it('should be able to drop the specified enum', function() { - return this.queryInterface.removeColumn('menus', 'structuretype').then(() => { - return this.queryInterface.pgListEnums('menus'); - }).then(enumList => { - expect(enumList).to.have.lengthOf(1); - expect(enumList[0]).to.have.property('enum_name').and.to.equal('enum_menus_structuretype'); - }).then(() => { - return this.queryInterface.dropEnum('enum_menus_structuretype'); - }).then(() => { - return this.queryInterface.pgListEnums('menus'); - }).then(enumList => { - expect(enumList).to.be.an('array'); - expect(enumList).to.have.lengthOf(0); - }); + it('should be able to drop the specified enum', async function() { + await this.queryInterface.removeColumn('menus', 'structuretype'); + const enumList0 = await this.queryInterface.pgListEnums('menus'); + expect(enumList0).to.have.lengthOf(1); + expect(enumList0[0]).to.have.property('enum_name').and.to.equal('enum_menus_structuretype'); + await this.queryInterface.dropEnum('enum_menus_structuretype'); + const enumList = await this.queryInterface.pgListEnums('menus'); + expect(enumList).to.be.an('array'); + expect(enumList).to.have.lengthOf(0); }); } }); diff --git a/test/integration/query-interface/removeColumn.test.js b/test/integration/query-interface/removeColumn.test.js index 69338852049e..983d7a9d8ace 100644 --- a/test/integration/query-interface/removeColumn.test.js +++ b/test/integration/query-interface/removeColumn.test.js @@ -12,14 +12,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('removeColumn', () => { describe('(without a schema)', () => { - beforeEach(function() { - return this.queryInterface.createTable('users', { + beforeEach(async function() { + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -46,41 +46,31 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { }); }); - it('should be able to remove a column with a default value', function() { - return this.queryInterface.removeColumn('users', 'firstName').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('firstName'); - }); + it('should be able to remove a column with a default value', async function() { + await this.queryInterface.removeColumn('users', 'firstName'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('firstName'); }); - it('should be able to remove a column without default value', function() { - return this.queryInterface.removeColumn('users', 'lastName').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('lastName'); - }); + it('should be able to remove a column without default value', async function() { + await this.queryInterface.removeColumn('users', 'lastName'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('lastName'); }); - it('should be able to remove a column with a foreign key constraint', function() { - return this.queryInterface.removeColumn('users', 'manager').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('manager'); - }); + it('should be able to remove a column with a foreign key constraint', async function() { + await this.queryInterface.removeColumn('users', 'manager'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('manager'); }); - it('should be able to remove a column with primaryKey', function() { - return this.queryInterface.removeColumn('users', 'manager').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('manager'); - return this.queryInterface.removeColumn('users', 'id'); - }).then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('id'); - }); + it('should be able to remove a column with primaryKey', async function() { + await this.queryInterface.removeColumn('users', 'manager'); + const table0 = await this.queryInterface.describeTable('users'); + expect(table0).to.not.have.property('manager'); + await this.queryInterface.removeColumn('users', 'id'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('id'); }); // From MSSQL documentation on ALTER COLUMN: @@ -88,85 +78,83 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { // - Used in a CHECK or UNIQUE constraint. // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql#arguments if (dialect !== 'mssql') { - it('should be able to remove a column with unique contraint', function() { - return this.queryInterface.removeColumn('users', 'email').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('email'); - }); + it('should be able to remove a column with unique contraint', async function() { + await this.queryInterface.removeColumn('users', 'email'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('email'); }); } }); describe('(with a schema)', () => { - beforeEach(function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - firstName: { - type: DataTypes.STRING, - defaultValue: 'Someone' - }, - lastName: { - type: DataTypes.STRING - }, - email: { - type: DataTypes.STRING, - unique: true - } - }); + beforeEach(async function() { + await this.sequelize.createSchema('archive'); + + await this.queryInterface.createTable({ + tableName: 'users', + schema: 'archive' + }, { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + firstName: { + type: DataTypes.STRING, + defaultValue: 'Someone' + }, + lastName: { + type: DataTypes.STRING + }, + email: { + type: DataTypes.STRING, + unique: true + } }); }); - it('should be able to remove a column with a default value', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column with a default value', async function() { + await this.queryInterface.removeColumn({ tableName: 'users', schema: 'archive' }, 'firstName' - ).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('firstName'); + ); + + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' }); + + expect(table).to.not.have.property('firstName'); }); - it('should be able to remove a column without default value', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column without default value', async function() { + await this.queryInterface.removeColumn({ tableName: 'users', schema: 'archive' }, 'lastName' - ).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('lastName'); + ); + + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' }); + + expect(table).to.not.have.property('lastName'); }); - it('should be able to remove a column with primaryKey', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column with primaryKey', async function() { + await this.queryInterface.removeColumn({ + tableName: 'users', + schema: 'archive' + }, 'id'); + + const table = await this.queryInterface.describeTable({ tableName: 'users', schema: 'archive' - }, 'id').then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('id'); }); + + expect(table).to.not.have.property('id'); }); // From MSSQL documentation on ALTER COLUMN: @@ -174,18 +162,18 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { // - Used in a CHECK or UNIQUE constraint. // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql#arguments if (dialect !== 'mssql') { - it('should be able to remove a column with unique contraint', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column with unique contraint', async function() { + await this.queryInterface.removeColumn({ + tableName: 'users', + schema: 'archive' + }, 'email'); + + const table = await this.queryInterface.describeTable({ tableName: 'users', schema: 'archive' - }, 'email').then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('email'); }); + + expect(table).to.not.have.property('email'); }); } }); From b17e7d8f9d77e2a97ac54f1123af767e2cfa4281 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 22 May 2020 01:39:11 -0500 Subject: [PATCH 158/414] test(integration/include): asyncify (#12294) --- test/integration/include/findAll.test.js | 2064 ++++++++--------- .../include/findAndCountAll.test.js | 490 ++-- test/integration/include/findOne.test.js | 362 ++- test/integration/include/limit.test.js | 1078 ++++----- test/integration/include/paranoid.test.js | 69 +- test/integration/include/schema.test.js | 1001 ++++---- test/integration/include/separate.test.js | 694 +++--- 7 files changed, 2875 insertions(+), 2883 deletions(-) diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index efbd4c77fac1..25d6a0214bc5 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -171,7 +171,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }; }); - it('should work on a nested set of relations with a where condition in between relations', function() { + it('should work on a nested set of relations with a where condition in between relations', async function() { const User = this.sequelize.define('User', {}), SubscriptionForm = this.sequelize.define('SubscriptionForm', {}), Collection = this.sequelize.define('Collection', {}), @@ -194,42 +194,42 @@ describe(Support.getTestDialectTeaser('Include'), () => { Category.hasMany(SubCategory, { foreignKey: 'boundCategory' }); SubCategory.belongsTo(Category, { foreignKey: 'boundCategory' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.findOne({ - include: [ - { - model: SubscriptionForm, - include: [ - { - model: Collection, - where: { - id: 13 - } - }, - { - model: Category, - include: [ - { - model: SubCategory - }, - { - model: Capital, - include: [ - { - model: Category - } - ] - } - ] + await this.sequelize.sync({ force: true }); + + await User.findOne({ + include: [ + { + model: SubscriptionForm, + include: [ + { + model: Collection, + where: { + id: 13 } - ] - } - ] - }); + }, + { + model: Category, + include: [ + { + model: SubCategory + }, + { + model: Capital, + include: [ + { + model: Category + } + ] + } + ] + } + ] + } + ] }); }); - it('should accept nested `where` and `limit` at the same time', function() { + it('should accept nested `where` and `limit` at the same time', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -248,47 +248,47 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Set.bulkCreate([ - { title: 'office' } - ]), Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ])]).then(() => { - return Promise.all([Set.findAll(), Product.findAll(), Tag.findAll()]); - }).then(([sets, products, tags]) => { - return Promise.all([ - sets[0].addProducts([products[0], products[1]]), - products[0].addTag(tags[0], { priority: 1 }).then(() => { - return products[0].addTag(tags[1], { priority: 2 }); - }).then(() => { - return products[0].addTag(tags[2], { priority: 1 }); - }), - products[1].addTag(tags[1], { priority: 2 }).then(() => { - return products[2].addTag(tags[1], { priority: 3 }); - }).then(() => { - return products[2].addTag(tags[2], { priority: 0 }); - }) - ]); + await this.sequelize.sync({ force: true }); + + await Promise.all([Set.bulkCreate([ + { title: 'office' } + ]), Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ])]); + + const [sets, products, tags] = await Promise.all([Set.findAll(), Product.findAll(), Tag.findAll()]); + + await Promise.all([ + sets[0].addProducts([products[0], products[1]]), + products[0].addTag(tags[0], { priority: 1 }).then(() => { + return products[0].addTag(tags[1], { priority: 2 }); }).then(() => { - return Set.findAll({ - include: [{ - model: Product, - include: [{ - model: Tag, - where: { - name: 'A' - } - }] - }], - limit: 1 - }); - }); + return products[0].addTag(tags[2], { priority: 1 }); + }), + products[1].addTag(tags[1], { priority: 2 }).then(() => { + return products[2].addTag(tags[1], { priority: 3 }); + }).then(() => { + return products[2].addTag(tags[2], { priority: 0 }); + }) + ]); + + await Set.findAll({ + include: [{ + model: Product, + include: [{ + model: Tag, + where: { + name: 'A' + } + }] + }], + limit: 1 }); }); @@ -425,7 +425,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { } }); - it('should support many levels of belongsTo', function() { + it('should support many levels of belongsTo', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), C = this.sequelize.define('c', {}), @@ -443,74 +443,74 @@ describe(Support.getTestDialectTeaser('Include'), () => { F.belongsTo(G); G.belongsTo(H); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([A.bulkCreate([ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ]).then(() => { - return A.findAll(); - }), (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; - - singles.forEach(model => { - promise = promise.then(() => { - return model.create({}).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); + await this.sequelize.sync({ force: true }); - promise = promise.then(() => { - return b; - }); + const [as0, b] = await Promise.all([A.bulkCreate([ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ]).then(() => { + return A.findAll(); + }), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; + + singles.forEach(model => { + promise = (async () => { + await promise; + const instance = await model.create({}); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + return; + } + previousInstance = b = instance; + })(); + }); - return promise; - })([B, C, D, E, F, G, H])]).then(([as, b]) => { - return Promise.all(as.map(a => { - return a.setB(b); - })); - }).then(() => { - return A.findAll({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, include: [ - { model: H } - ] } - ] } + promise = promise.then(() => { + return b; + }); + + return promise; + })([B, C, D, E, F, G, H])]); + + await Promise.all(as0.map(a => { + return a.setB(b); + })); + + const as = await A.findAll({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(as => { - expect(as.length).to.be.ok; + ] } + ] } + ] + }); - as.forEach(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); - }); + expect(as.length).to.be.ok; + + as.forEach(a => { + expect(a.b.c.d.e.f.g.h).to.be.ok; }); }); - it('should support many levels of belongsTo (with a lower level having a where)', function() { + it('should support many levels of belongsTo (with a lower level having a where)', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), C = this.sequelize.define('c', {}), @@ -532,82 +532,82 @@ describe(Support.getTestDialectTeaser('Include'), () => { F.belongsTo(G); G.belongsTo(H); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([A.bulkCreate([ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ]).then(() => { - return A.findAll(); - }), (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; + await this.sequelize.sync({ force: true }); - singles.forEach(model => { - const values = {}; + const [as0, b] = await Promise.all([A.bulkCreate([ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ]).then(() => { + return A.findAll(); + }), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; + + singles.forEach(model => { + const values = {}; + + if (model.name === 'g') { + values.name = 'yolo'; + } - if (model.name === 'g') { - values.name = 'yolo'; + promise = (async () => { + await promise; + const instance = await model.create(values); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + return; } + previousInstance = b = instance; + })(); + }); - promise = promise.then(() => { - return model.create(values).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); + promise = promise.then(() => { + return b; + }); - promise = promise.then(() => { - return b; - }); + return promise; + })([B, C, D, E, F, G, H])]); - return promise; - })([B, C, D, E, F, G, H])]).then(([as, b]) => { - return Promise.all(as.map(a => { - return a.setB(b); - })); - }).then(() => { - return A.findAll({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, where: { - name: 'yolo' - }, include: [ - { model: H } - ] } - ] } + await Promise.all(as0.map(a => { + return a.setB(b); + })); + + const as = await A.findAll({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, where: { + name: 'yolo' + }, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(as => { - expect(as.length).to.be.ok; + ] } + ] } + ] + }); - as.forEach(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); - }); + expect(as.length).to.be.ok; + + as.forEach(a => { + expect(a.b.c.d.e.f.g.h).to.be.ok; }); }); - it('should support ordering with only belongsTo includes', function() { + it('should support ordering with only belongsTo includes', async function() { const User = this.sequelize.define('User', {}), Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }), Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }); @@ -616,74 +616,74 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); User.belongsTo(Order); - return this.sequelize.sync().then(() => { - return promiseProps({ - users: User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }), - items: Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' }, - { 'test': 'jkl' } - ]).then(() => { - return Item.findAll({ order: ['id'] }); - }), - orders: Order.bulkCreate([ - { 'position': 2 }, - { 'position': 3 }, - { 'position': 1 } - ]).then(() => { - return Order.findAll({ order: ['id'] }); - }) - }).then(results => { - const user1 = results.users[0]; - const user2 = results.users[1]; - const user3 = results.users[2]; - - const item1 = results.items[0]; - const item2 = results.items[1]; - const item3 = results.items[2]; - const item4 = results.items[3]; - - const order1 = results.orders[0]; - const order2 = results.orders[1]; - const order3 = results.orders[2]; - - return Promise.all([ - user1.setItemA(item1), - user1.setItemB(item2), - user1.setOrder(order3), - user2.setItemA(item3), - user2.setItemB(item4), - user2.setOrder(order2), - user3.setItemA(item1), - user3.setItemB(item4), - user3.setOrder(order1) - ]); - }).then(() => { - return User.findAll({ - 'include': [ - { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, - { 'model': Item, 'as': 'itemB' }, - Order], - 'order': [ - [Order, 'position'] - ] - }).then(as => { - expect(as.length).to.eql(2); + await this.sequelize.sync(); + + const results = await promiseProps({ + users: User.bulkCreate([{}, {}, {}]).then(() => { + return User.findAll(); + }), + items: Item.bulkCreate([ + { 'test': 'abc' }, + { 'test': 'def' }, + { 'test': 'ghi' }, + { 'test': 'jkl' } + ]).then(() => { + return Item.findAll({ order: ['id'] }); + }), + orders: Order.bulkCreate([ + { 'position': 2 }, + { 'position': 3 }, + { 'position': 1 } + ]).then(() => { + return Order.findAll({ order: ['id'] }); + }) + }); - expect(as[0].itemA.test).to.eql('abc'); - expect(as[1].itemA.test).to.eql('abc'); + const user1 = results.users[0]; + const user2 = results.users[1]; + const user3 = results.users[2]; + + const item1 = results.items[0]; + const item2 = results.items[1]; + const item3 = results.items[2]; + const item4 = results.items[3]; + + const order1 = results.orders[0]; + const order2 = results.orders[1]; + const order3 = results.orders[2]; + + await Promise.all([ + user1.setItemA(item1), + user1.setItemB(item2), + user1.setOrder(order3), + user2.setItemA(item3), + user2.setItemB(item4), + user2.setOrder(order2), + user3.setItemA(item1), + user3.setItemB(item4), + user3.setOrder(order1) + ]); - expect(as[0].Order.position).to.eql(1); - expect(as[1].Order.position).to.eql(2); - }); - }); + const as = await User.findAll({ + 'include': [ + { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, + { 'model': Item, 'as': 'itemB' }, + Order], + 'order': [ + [Order, 'position'] + ] }); + + expect(as.length).to.eql(2); + + expect(as[0].itemA.test).to.eql('abc'); + expect(as[1].itemA.test).to.eql('abc'); + + expect(as[0].Order.position).to.eql(1); + expect(as[1].Order.position).to.eql(2); }); - it('should include attributes from through models', function() { + it('should include attributes from through models', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -697,84 +697,84 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]).then(() => { - return Product.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - }).then(results => { - return Promise.all([ - results.products[0].addTag(results.tags[0], { through: { priority: 1 } }), - results.products[0].addTag(results.tags[1], { through: { priority: 2 } }), - results.products[1].addTag(results.tags[1], { through: { priority: 1 } }), - results.products[2].addTag(results.tags[0], { through: { priority: 3 } }), - results.products[2].addTag(results.tags[1], { through: { priority: 1 } }), - results.products[2].addTag(results.tags[2], { through: { priority: 2 } }) - ]); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag } - ], - order: [ - ['id', 'ASC'], - [Tag, 'id', 'ASC'] - ] - }).then(products => { - expect(products[0].Tags[0].ProductTag.priority).to.equal(1); - expect(products[0].Tags[1].ProductTag.priority).to.equal(2); + await this.sequelize.sync({ force: true }); - expect(products[1].Tags[0].ProductTag.priority).to.equal(1); + const results = await promiseProps({ + products: Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]).then(() => { + return Product.findAll(); + }), + tags: Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll(); + }) + }); - expect(products[2].Tags[0].ProductTag.priority).to.equal(3); - expect(products[2].Tags[1].ProductTag.priority).to.equal(1); - expect(products[2].Tags[2].ProductTag.priority).to.equal(2); - }); - }); + await Promise.all([ + results.products[0].addTag(results.tags[0], { through: { priority: 1 } }), + results.products[0].addTag(results.tags[1], { through: { priority: 2 } }), + results.products[1].addTag(results.tags[1], { through: { priority: 1 } }), + results.products[2].addTag(results.tags[0], { through: { priority: 3 } }), + results.products[2].addTag(results.tags[1], { through: { priority: 1 } }), + results.products[2].addTag(results.tags[2], { through: { priority: 2 } }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag } + ], + order: [ + ['id', 'ASC'], + [Tag, 'id', 'ASC'] + ] }); + + expect(products[0].Tags[0].ProductTag.priority).to.equal(1); + expect(products[0].Tags[1].ProductTag.priority).to.equal(2); + + expect(products[1].Tags[0].ProductTag.priority).to.equal(1); + + expect(products[2].Tags[0].ProductTag.priority).to.equal(3); + expect(products[2].Tags[1].ProductTag.priority).to.equal(1); + expect(products[2].Tags[2].ProductTag.priority).to.equal(2); }); - it('should support a required belongsTo include', function() { + it('should support a required belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([{}, {}]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return results.users[2].setGroup(results.groups[1]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([{}, {}]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}, {}]).then(() => { + return User.findAll(); + }) }); + + await results.users[2].setGroup(results.groups[1]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] + }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -782,37 +782,37 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - expect(users[0].Group.name).to.equal('A'); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; + expect(users[0].Group.name).to.equal('A'); }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -820,39 +820,39 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - users.forEach(user => { - expect(user.Group).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] + }); + + users.forEach(user => { + expect(user.Group).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany not required', function() { - const Address = this.sequelize.define('Address', { 'active': DataTypes.BOOLEAN }), - Street = this.sequelize.define('Street', { 'active': DataTypes.BOOLEAN }), + it('should be possible to define a belongsTo include as required with child hasMany not required', async function() { + const Address = this.sequelize.define('Address', { 'active': DataTypes.BOOLEAN }), + Street = this.sequelize.define('Street', { 'active': DataTypes.BOOLEAN }), User = this.sequelize.define('User', { 'username': DataTypes.STRING }); // Associate @@ -863,33 +863,31 @@ describe(Support.getTestDialectTeaser('Include'), () => { Street.hasMany(Address, { foreignKey: 'streetId' }); // Sync - return this.sequelize.sync({ force: true }).then(() => { - return Street.create({ active: true }).then(street => { - return Address.create({ active: true, streetId: street.id }).then(address => { - return User.create({ username: 'John', addressId: address.id }).then(() => { - return User.findOne({ - where: { username: 'John' }, - include: [{ - model: Address, - required: true, - where: { - active: true - }, - include: [{ - model: Street - }] - }] - }).then(john => { - expect(john.Address).to.be.ok; - expect(john.Address.Street).to.be.ok; - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const street = await Street.create({ active: true }); + const address = await Address.create({ active: true, streetId: street.id }); + await User.create({ username: 'John', addressId: address.id }); + + const john = await User.findOne({ + where: { username: 'John' }, + include: [{ + model: Address, + required: true, + where: { + active: true + }, + include: [{ + model: Street + }] + }] }); + + expect(john.Address).to.be.ok; + expect(john.Address.Street).to.be.ok; }); - it('should be possible to define a belongsTo include as required with child hasMany with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -901,48 +899,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }), - categories: Category.bulkCreate([{}, {}]).then(() => { - return Category.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]), - Promise.all(results.groups.map(group => { - return group.setCategories(results.categories); - })) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }), + categories: Category.bulkCreate([{}, {}]).then(() => { + return Category.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]), + Promise.all(results.groups.map(group => { + return group.setCategories(results.categories); + })) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -954,48 +952,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group, { as: 'Team' }); Group.hasMany(Category, { as: 'Tags' }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }), - categories: Category.bulkCreate([{}, {}]).then(() => { - return Category.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[0].setTeam(results.groups[1]), - results.users[1].setTeam(results.groups[0]), - Promise.all(results.groups.map(group => { - return group.setTags(results.categories); - })) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, as: 'Team', include: [ - { model: Category, as: 'Tags' } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Team).to.be.ok; - expect(user.Team.Tags).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }), + categories: Category.bulkCreate([{}, {}]).then(() => { + return Category.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setTeam(results.groups[1]), + results.users[1].setTeam(results.groups[0]), + Promise.all(results.groups.map(group => { + return group.setTags(results.categories); + })) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, as: 'Team', include: [ + { model: Category, as: 'Tags' } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Team).to.be.ok; + expect(user.Team.Tags).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1007,48 +1005,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }), - categories: Category.bulkCreate([{}, {}]).then(() => { - return Category.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]), - Promise.all(results.groups.map(group => { - return group.setCategories(results.categories); - })) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category, required: false } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }), + categories: Category.bulkCreate([{}, {}]).then(() => { + return Category.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]), + Promise.all(results.groups.map(group => { + return group.setCategories(results.categories); + })) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category, required: false } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to extend the on clause with a where option on a hasOne include', function() { + it('should be possible to extend the on clause with a where option on a hasOne include', async function() { const User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project', { title: DataTypes.STRING @@ -1056,37 +1054,37 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasOne(Project, { as: 'LeaderOf' }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - projects: Project.bulkCreate([ - { title: 'Alpha' }, - { title: 'Beta' } - ]).then(() => { - return Project.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[1].setLeaderOf(results.projects[1]), - results.users[0].setLeaderOf(results.projects[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].LeaderOf).to.be.ok; - expect(users[0].LeaderOf.title).to.equal('Beta'); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + projects: Project.bulkCreate([ + { title: 'Alpha' }, + { title: 'Beta' } + ]).then(() => { + return Project.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[1].setLeaderOf(results.projects[1]), + results.users[0].setLeaderOf(results.projects[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].LeaderOf).to.be.ok; + expect(users[0].LeaderOf.title).to.equal('Beta'); }); - it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function() { + it('should be possible to extend the on clause with a where option on a hasMany include with a through model', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -1100,42 +1098,42 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]).then(() => { - return Product.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - }).then(results => { - return Promise.all([ - results.products[0].addTag(results.tags[0], { priority: 1 }), - results.products[0].addTag(results.tags[1], { priority: 2 }), - results.products[1].addTag(results.tags[1], { priority: 1 }), - results.products[2].addTag(results.tags[0], { priority: 3 }), - results.products[2].addTag(results.tags[1], { priority: 1 }), - results.products[2].addTag(results.tags[2], { priority: 2 }) - ]); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag, where: { name: 'C' } } - ] - }).then(products => { - expect(products.length).to.equal(1); - expect(products[0].Tags.length).to.equal(1); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + products: Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]).then(() => { + return Product.findAll(); + }), + tags: Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll(); + }) }); + + await Promise.all([ + results.products[0].addTag(results.tags[0], { priority: 1 }), + results.products[0].addTag(results.tags[1], { priority: 2 }), + results.products[1].addTag(results.tags[1], { priority: 1 }), + results.products[2].addTag(results.tags[0], { priority: 3 }), + results.products[2].addTag(results.tags[1], { priority: 1 }), + results.products[2].addTag(results.tags[2], { priority: 2 }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag, where: { name: 'C' } } + ] + }); + + expect(products.length).to.equal(1); + expect(products[0].Tags.length).to.equal(1); }); it('should be possible to extend the on clause with a where option on nested includes', async function() { @@ -1203,10 +1201,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { ]); for (const i of [0, 1, 2, 3, 4]) { const user = await User.create({ name: 'FooBarzz' }); - const products = await Product.bulkCreate([ + + await Product.bulkCreate([ { title: 'Chair' }, { title: 'Desk' } - ]).then(() => Product.findAll()); + ]); + + const products = await Product.findAll(); await Promise.all([ GroupMember.bulkCreate([ { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, @@ -1262,7 +1263,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { } }); - it('should be possible to use limit and a where with a belongsTo include', function() { + it('should be possible to use limit and a where with a belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1270,160 +1271,159 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}, {}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[0].setGroup(results.groups[0]), - results.users[1].setGroup(results.groups[0]), - results.users[2].setGroup(results.groups[0]), - results.users[3].setGroup(results.groups[1]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ], - limit: 2 - }).then(users => { - expect(users.length).to.equal(2); - - users.forEach(user => { - expect(user.Group.name).to.equal('A'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}, {}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[0]), + results.users[1].setGroup(results.groups[0]), + results.users[2].setGroup(results.groups[0]), + results.users[3].setGroup(results.groups[1]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ], + limit: 2 + }); + + expect(users.length).to.equal(2); + + users.forEach(user => { + expect(user.Group.name).to.equal('A'); }); }); - it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['id', 'title'], - include: [ - { model: this.models.Company, where: { name: 'NYSE' } }, - { model: this.models.Tag }, - { model: this.models.Price } - ], - limit: 3, - order: [ - [this.sequelize.col(`${this.models.Product.name}.id`), 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(3); + it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', async function() { + await this.fixtureA(); - products.forEach(product => { - expect(product.Company.name).to.equal('NYSE'); - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - }); - }); + const products = await this.models.Product.findAll({ + attributes: ['id', 'title'], + include: [ + { model: this.models.Company, where: { name: 'NYSE' } }, + { model: this.models.Tag }, + { model: this.models.Price } + ], + limit: 3, + order: [ + [this.sequelize.col(`${this.models.Product.name}.id`), 'ASC'] + ] + }); + + expect(products.length).to.equal(3); + + products.forEach(product => { + expect(product.Company.name).to.equal('NYSE'); + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; }); }); - it('should be possible to have the primary key in attributes', function() { + it('should be possible to have the primary key in attributes', async function() { const Parent = this.sequelize.define('Parent', {}); const Child1 = this.sequelize.define('Child1', {}); Parent.hasMany(Child1); Child1.belongsTo(Parent); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Parent.create(), - Child1.create() - ]); - }).then(([parent, child]) => { - return parent.addChild1(child).then(() => { - return parent; - }); - }).then(parent => { - return Child1.findOne({ - include: [ - { - model: Parent, - attributes: ['id'], // This causes a duplicated entry in the query - where: { - id: parent.id - } + await this.sequelize.sync({ force: true }); + + const [parent0, child] = await Promise.all([ + Parent.create(), + Child1.create() + ]); + + await parent0.addChild1(child); + const parent = parent0; + + await Child1.findOne({ + include: [ + { + model: Parent, + attributes: ['id'], // This causes a duplicated entry in the query + where: { + id: parent.id } - ] - }); + } + ] }); }); - it('should be possible to turn off the attributes for the through table', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.Tag, through: { attributes: [] }, required: true } - ] - }).then(products => { - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - product.Tags.forEach(tag => { - expect(tag.get().productTags).not.to.be.ok; - }); - }); + it('should be possible to turn off the attributes for the through table', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { model: this.models.Tag, through: { attributes: [] }, required: true } + ] + }); + + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + product.Tags.forEach(tag => { + expect(tag.get().productTags).not.to.be.ok; }); }); }); - it('should be possible to select on columns inside a through table', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { - model: this.models.Tag, - through: { - where: { - ProductId: 3 - } - }, - required: true - } - ] - }).then(products => { - expect(products).have.length(1); - }); + it('should be possible to select on columns inside a through table', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { + model: this.models.Tag, + through: { + where: { + ProductId: 3 + } + }, + required: true + } + ] }); + + expect(products).have.length(1); }); - it('should be possible to select on columns inside a through table and a limit', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { - model: this.models.Tag, - through: { - where: { - ProductId: 3 - } - }, - required: true - } - ], - limit: 5 - }).then(products => { - expect(products).have.length(1); - }); + it('should be possible to select on columns inside a through table and a limit', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { + model: this.models.Tag, + through: { + where: { + ProductId: 3 + } + }, + required: true + } + ], + limit: 5 }); + + expect(products).have.length(1); }); // Test case by @eshell - it('should be possible not to include the main id in the attributes', function() { + it('should be possible not to include the main id in the attributes', async function() { const Member = this.sequelize.define('Member', { id: { type: Sequelize.BIGINT, @@ -1457,101 +1457,99 @@ describe(Support.getTestDialectTeaser('Include'), () => { Album.belongsTo(Member); Member.hasMany(Album); - return this.sequelize.sync({ force: true }).then(() => { - const members = [], - albums = [], - memberCount = 20; + await this.sequelize.sync({ force: true }); + const members = [], + albums = [], + memberCount = 20; + + for (let i = 1; i <= memberCount; i++) { + members.push({ + id: i, + email: `email${i}@lmu.com`, + password: `testing${i}` + }); + albums.push({ + title: `Album${i}`, + MemberId: i + }); + } - for (let i = 1; i <= memberCount; i++) { - members.push({ - id: i, - email: `email${i}@lmu.com`, - password: `testing${i}` - }); - albums.push({ - title: `Album${i}`, - MemberId: i - }); - } + await Member.bulkCreate(members); + await Album.bulkCreate(albums); - return Member.bulkCreate(members).then(() => { - return Album.bulkCreate(albums).then(() => { - return Member.findAll({ - attributes: ['email'], - include: [ - { - model: Album - } - ] - }).then(members => { - expect(members.length).to.equal(20); - members.forEach(member => { - expect(member.get('id')).not.to.be.ok; - expect(member.Albums.length).to.equal(1); - }); - }); - }); - }); + const members0 = await Member.findAll({ + attributes: ['email'], + include: [ + { + model: Album + } + ] + }); + + expect(members0.length).to.equal(20); + members0.forEach(member => { + expect(member.get('id')).not.to.be.ok; + expect(member.Albums.length).to.equal(1); }); }); - it('should be possible to use limit and a where on a hasMany with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag }, - { model: this.models.Price, where: { - value: { [Op.gt]: 5 } - } } - ], - limit: 6, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(6); + it('should be possible to use limit and a where on a hasMany with additional includes', async function() { + await this.fixtureA(); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag }, + { model: this.models.Price, where: { + value: { [Op.gt]: 5 } + } } + ], + limit: 6, + order: [ + ['id', 'ASC'] + ] + }); - product.Prices.forEach(price => { - expect(price.value).to.be.above(5); - }); - }); + expect(products.length).to.equal(6); + + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Prices.forEach(price => { + expect(price.value).to.be.above(5); }); }); }); - it('should be possible to use limit and a where on a hasMany with a through model with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, - { model: this.models.Price } - ], - limit: 10, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(10); + it('should be possible to use limit and a where on a hasMany with a through model with additional includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, + { model: this.models.Price } + ], + limit: 10, + order: [ + ['id', 'ASC'] + ] + }); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + expect(products.length).to.equal(10); - product.Tags.forEach(tag => { - expect(['A', 'B', 'C']).to.include(tag.name); - }); - }); + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Tags.forEach(tag => { + expect(['A', 'B', 'C']).to.include(tag.name); }); }); }); - it('should support including date fields, with the correct timeszone', function() { + it('should support including date fields, with the correct timeszone', async function() { const User = this.sequelize.define('user', { dateField: Sequelize.DATE }, { timestamps: false }), @@ -1562,54 +1560,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsToMany(Group, { through: 'group_user' }); Group.belongsToMany(User, { through: 'group_user' }); - return this.sequelize.sync().then(() => { - return User.create({ dateField: Date.UTC(2014, 1, 20) }).then(user => { - return Group.create({ dateField: Date.UTC(2014, 1, 20) }).then(group => { - return user.addGroup(group).then(() => { - return User.findAll({ - where: { - id: user.id - }, - include: [Group] - }).then(users => { - expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - }); - }); - }); - }); + await this.sequelize.sync(); + const user = await User.create({ dateField: Date.UTC(2014, 1, 20) }); + const group = await Group.create({ dateField: Date.UTC(2014, 1, 20) }); + await user.addGroup(group); + + const users = await User.findAll({ + where: { + id: user.id + }, + include: [Group] }); + + expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); }); - it('should still pull the main record(s) when an included model is not required and has where restrictions without matches', function() { + it('should still pull the main record(s) when an included model is not required and has where restrictions without matches', async function() { const A = this.sequelize.define('a', { name: DataTypes.STRING(40) }), B = this.sequelize.define('b', { name: DataTypes.STRING(40) }); A.belongsToMany(B, { through: 'a_b' }); B.belongsToMany(A, { through: 'a_b' }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return A.create({ - name: 'Foobar' - }); - }) - .then(() => { - return A.findAll({ - where: { name: 'Foobar' }, - include: [ - { model: B, where: { name: 'idontexist' }, required: false } - ] - }); - }) - .then(as => { - expect(as.length).to.equal(1); - expect(as[0].get('bs')).deep.equal([]); - }); + await this.sequelize + .sync({ force: true }); + + await A.create({ + name: 'Foobar' + }); + + const as = await A.findAll({ + where: { name: 'Foobar' }, + include: [ + { model: B, where: { name: 'idontexist' }, required: false } + ] + }); + + expect(as.length).to.equal(1); + expect(as[0].get('bs')).deep.equal([]); }); - it('should work with paranoid, a main record where, an include where, and a limit', function() { + it('should work with paranoid, a main record where, an include where, and a limit', async function() { const Post = this.sequelize.define('post', { date: DataTypes.DATE, 'public': DataTypes.BOOLEAN @@ -1623,38 +1615,38 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.hasMany(Category); Category.belongsTo(Post); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Post.create({ 'public': true }), - Post.create({ 'public': true }), - Post.create({ 'public': true }), - Post.create({ 'public': true }) - ]).then(posts => { - return Promise.all(posts.slice(1, 3).map(post => { - return post.createCategory({ slug: 'food' }); - })); - }).then(() => { - return Post.findAll({ - limit: 2, + await this.sequelize.sync({ force: true }); + + const posts0 = await Promise.all([ + Post.create({ 'public': true }), + Post.create({ 'public': true }), + Post.create({ 'public': true }), + Post.create({ 'public': true }) + ]); + + await Promise.all(posts0.slice(1, 3).map(post => { + return post.createCategory({ slug: 'food' }); + })); + + const posts = await Post.findAll({ + limit: 2, + where: { + 'public': true + }, + include: [ + { + model: Category, where: { - 'public': true - }, - include: [ - { - model: Category, - where: { - slug: 'food' - } - } - ] - }).then(posts => { - expect(posts.length).to.equal(2); - }); - }); + slug: 'food' + } + } + ] }); + + expect(posts.length).to.equal(2); }); - it('should work on a nested set of required 1:1 relations', function() { + it('should work on a nested set of required 1:1 relations', async function() { const Person = this.sequelize.define('Person', { name: { type: Sequelize.STRING, @@ -1714,26 +1706,26 @@ describe(Support.getTestDialectTeaser('Include'), () => { onDelete: 'CASCADE' }); - return this.sequelize.sync({ force: true }).then(() => { - return Person.findAll({ - offset: 0, - limit: 20, - attributes: ['id', 'name'], + await this.sequelize.sync({ force: true }); + + await Person.findAll({ + offset: 0, + limit: 20, + attributes: ['id', 'name'], + include: [{ + model: UserPerson, + required: true, + attributes: ['rank'], include: [{ - model: UserPerson, + model: User, required: true, - attributes: ['rank'], - include: [{ - model: User, - required: true, - attributes: ['login'] - }] + attributes: ['login'] }] - }); + }] }); }); - it('should work with an empty include.where', function() { + it('should work with an empty include.where', async function() { const User = this.sequelize.define('User', {}), Company = this.sequelize.define('Company', {}), Group = this.sequelize.define('Group', {}); @@ -1742,17 +1734,17 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsToMany(Group, { through: 'UsersGroups' }); Group.belongsToMany(User, { through: 'UsersGroups' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: {} }, - { model: Company, where: {} } - ] - }); + await this.sequelize.sync({ force: true }); + + await User.findAll({ + include: [ + { model: Group, where: {} }, + { model: Company, where: {} } + ] }); }); - it('should be able to order on the main table and a required belongsTo relation with custom tablenames and limit ', function() { + it('should be able to order on the main table and a required belongsTo relation with custom tablenames and limit ', async function() { const User = this.sequelize.define('User', { lastName: DataTypes.STRING }, { tableName: 'dem_users' }); @@ -1763,44 +1755,44 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Company); Company.hasMany(User); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ lastName: 'Albertsen' }), - User.create({ lastName: 'Zenith' }), - User.create({ lastName: 'Hansen' }), - Company.create({ rank: 1 }), - Company.create({ rank: 2 }) - ]).then(([albertsen, zenith, hansen, company1, company2]) => { - return Promise.all([ - albertsen.setCompany(company1), - zenith.setCompany(company2), - hansen.setCompany(company2) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Company, required: true } - ], - order: [ - [Company, 'rank', 'ASC'], - ['lastName', 'DESC'] - ], - limit: 5 - }).then(users => { - expect(users[0].lastName).to.equal('Albertsen'); - expect(users[0].Company.rank).to.equal(1); - - expect(users[1].lastName).to.equal('Zenith'); - expect(users[1].Company.rank).to.equal(2); - - expect(users[2].lastName).to.equal('Hansen'); - expect(users[2].Company.rank).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const [albertsen, zenith, hansen, company1, company2] = await Promise.all([ + User.create({ lastName: 'Albertsen' }), + User.create({ lastName: 'Zenith' }), + User.create({ lastName: 'Hansen' }), + Company.create({ rank: 1 }), + Company.create({ rank: 2 }) + ]); + + await Promise.all([ + albertsen.setCompany(company1), + zenith.setCompany(company2), + hansen.setCompany(company2) + ]); + + const users = await User.findAll({ + include: [ + { model: Company, required: true } + ], + order: [ + [Company, 'rank', 'ASC'], + ['lastName', 'DESC'] + ], + limit: 5 }); + + expect(users[0].lastName).to.equal('Albertsen'); + expect(users[0].Company.rank).to.equal(1); + + expect(users[1].lastName).to.equal('Zenith'); + expect(users[1].Company.rank).to.equal(2); + + expect(users[2].lastName).to.equal('Hansen'); + expect(users[2].Company.rank).to.equal(2); }); - it('should ignore include with attributes: [] (used for aggregates)', function() { + it('should ignore include with attributes: [] (used for aggregates)', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }), @@ -1810,40 +1802,40 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.Comments = Post.hasMany(Comment, { as: 'comments' }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({ - title: Math.random().toString(), - comments: [ - { content: Math.random().toString() }, - { content: Math.random().toString() }, - { content: Math.random().toString() } - ] - }, { - include: [Post.Comments] - }); - }).then(() => { - return Post.findAll({ - attributes: [ - [this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount'] - ], - include: [ - { association: Post.Comments, attributes: [] } - ], - group: [ - 'Post.id' - ] - }); - }).then(posts => { - expect(posts.length).to.equal(1); + await this.sequelize.sync({ force: true }); - const post = posts[0]; + await Post.create({ + title: Math.random().toString(), + comments: [ + { content: Math.random().toString() }, + { content: Math.random().toString() }, + { content: Math.random().toString() } + ] + }, { + include: [Post.Comments] + }); - expect(post.get('comments')).not.to.be.ok; - expect(parseInt(post.get('commentCount'), 10)).to.equal(3); + const posts = await Post.findAll({ + attributes: [ + [this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount'] + ], + include: [ + { association: Post.Comments, attributes: [] } + ], + group: [ + 'Post.id' + ] }); + + expect(posts.length).to.equal(1); + + const post = posts[0]; + + expect(post.get('comments')).not.to.be.ok; + expect(parseInt(post.get('commentCount'), 10)).to.equal(3); }); - it('should not add primary key when including and aggregating with raw: true', function() { + it('should not add primary key when including and aggregating with raw: true', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }), @@ -1853,39 +1845,38 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.Comments = Post.hasMany(Comment, { as: 'comments' }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({ - title: Math.random().toString(), - comments: [ - { content: Math.random().toString() }, - { content: Math.random().toString() }, - { content: Math.random().toString() } - ] - }, { - include: [Post.Comments] - }); - }).then(() => { - return Post.findAll({ - attributes: [], - include: [ - { - association: Post.Comments, - attributes: [[this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount']] - } - ], - raw: true - }); - }).then(posts => { - expect(posts.length).to.equal(1); + await this.sequelize.sync({ force: true }); - const post = posts[0]; - expect(post.id).not.to.be.ok; - expect(parseInt(post['comments.commentCount'], 10)).to.equal(3); + await Post.create({ + title: Math.random().toString(), + comments: [ + { content: Math.random().toString() }, + { content: Math.random().toString() }, + { content: Math.random().toString() } + ] + }, { + include: [Post.Comments] }); - }); - it('Should return posts with nested include with inner join with a m:n association', function() { + const posts = await Post.findAll({ + attributes: [], + include: [ + { + association: Post.Comments, + attributes: [[this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount']] + } + ], + raw: true + }); + + expect(posts.length).to.equal(1); + + const post = posts[0]; + expect(post.id).not.to.be.ok; + expect(parseInt(post['comments.commentCount'], 10)).to.equal(3); + }); + it('Should return posts with nested include with inner join with a m:n association', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -1942,45 +1933,46 @@ describe(Support.getTestDialectTeaser('Include'), () => { otherKey: 'entity_id' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(() => TaggableSentient.create({ nametag: 'bob' })) - .then(() => Entity.create({ creator: 'bob' })) - .then(entity => Promise.all([ - Post.create({ post_id: entity.entity_id }), - entity.addTags('bob') - ])) - .then(() => Post.findAll({ + await this.sequelize.sync({ force: true }); + await User.create({ username: 'bob' }); + await TaggableSentient.create({ nametag: 'bob' }); + const entity = await Entity.create({ creator: 'bob' }); + + await Promise.all([ + Post.create({ post_id: entity.entity_id }), + entity.addTags('bob') + ]); + + const posts = await Post.findAll({ + include: [{ + model: Entity, + required: true, include: [{ - model: Entity, + model: User, + required: true + }, { + model: TaggableSentient, + as: 'tags', required: true, - include: [{ - model: User, - required: true - }, { - model: TaggableSentient, - as: 'tags', - required: true, - through: { - where: { - tag_name: ['bob'] - } + through: { + where: { + tag_name: ['bob'] } - }] - }], - limit: 5, - offset: 0 - })) - .then(posts => { - expect(posts.length).to.equal(1); - expect(posts[0].Entity.creator).to.equal('bob'); - expect(posts[0].Entity.tags.length).to.equal(1); - expect(posts[0].Entity.tags[0].EntityTag.tag_name).to.equal('bob'); - expect(posts[0].Entity.tags[0].EntityTag.entity_id).to.equal(posts[0].post_id); - }); + } + }] + }], + limit: 5, + offset: 0 + }); + + expect(posts.length).to.equal(1); + expect(posts[0].Entity.creator).to.equal('bob'); + expect(posts[0].Entity.tags.length).to.equal(1); + expect(posts[0].Entity.tags[0].EntityTag.tag_name).to.equal('bob'); + expect(posts[0].Entity.tags[0].EntityTag.entity_id).to.equal(posts[0].post_id); }); - it('should be able to generate a correct request with inner and outer join', function() { + it('should be able to generate a correct request with inner and outer join', async function() { const Customer = this.sequelize.define('customer', { name: DataTypes.STRING }); @@ -2007,45 +1999,45 @@ describe(Support.getTestDialectTeaser('Include'), () => { Shipment.belongsTo(Order); Order.hasOne(Shipment); - return this.sequelize.sync({ force: true }).then(() => { - return Shipment.findOne({ + await this.sequelize.sync({ force: true }); + + await Shipment.findOne({ + include: [{ + model: Order, + required: true, include: [{ - model: Order, - required: true, + model: Customer, include: [{ - model: Customer, - include: [{ - model: ShippingAddress, - where: { verified: true } - }] + model: ShippingAddress, + where: { verified: true } }] }] - }); + }] }); }); - it('should be able to generate a correct request for entity with 1:n and m:1 associations and limit', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.User }, - { model: this.models.Price } - ], - limit: 10 - }).then( products => { - expect(products).to.be.an('array'); - expect(products).to.be.lengthOf(10); - for (const product of products) { - expect(product.title).to.be.a('string'); - // checking that internally added fields used to handle 'BelongsTo' associations are not leaked to result - expect(product.UserId).to.be.equal(undefined); - // checking that included models are on their places - expect(product.User).to.satisfy( User => User === null || User instanceof this.models.User ); - expect(product.Prices).to.be.an('array'); - } - }); + it('should be able to generate a correct request for entity with 1:n and m:1 associations and limit', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { model: this.models.User }, + { model: this.models.Price } + ], + limit: 10 }); + + expect(products).to.be.an('array'); + expect(products).to.be.lengthOf(10); + for (const product of products) { + expect(product.title).to.be.a('string'); + // checking that internally added fields used to handle 'BelongsTo' associations are not leaked to result + expect(product.UserId).to.be.equal(undefined); + // checking that included models are on their places + expect(product.User).to.satisfy( User => User === null || User instanceof this.models.User ); + expect(product.Prices).to.be.an('array'); + } }); }); }); diff --git a/test/integration/include/findAndCountAll.test.js b/test/integration/include/findAndCountAll.test.js index 9da2f2a66ee4..22597038719e 100644 --- a/test/integration/include/findAndCountAll.test.js +++ b/test/integration/include/findAndCountAll.test.js @@ -17,7 +17,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); describe('findAndCountAll', () => { - it('should be able to include two required models with a limit. Result rows should match limit.', function() { + it('should be able to include two required models with a limit. Result rows should match limit.', async function() { const Project = this.sequelize.define('Project', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING(40) }), Task = this.sequelize.define('Task', { name: DataTypes.STRING(40), fk: DataTypes.INTEGER }), Employee = this.sequelize.define('Employee', { name: DataTypes.STRING(40), fk: DataTypes.INTEGER }); @@ -29,47 +29,47 @@ describe(Support.getTestDialectTeaser('Include'), () => { Employee.belongsTo(Project, { foreignKey: 'fk', constraints: false }); // Sync them - return this.sequelize.sync({ force: true }).then(() => { - // Create an enviroment - return Promise.all([Project.bulkCreate([ - { id: 1, name: 'No tasks' }, - { id: 2, name: 'No tasks no employees' }, - { id: 3, name: 'No employees' }, - { id: 4, name: 'In progress A' }, - { id: 5, name: 'In progress B' }, - { id: 6, name: 'In progress C' } - ]), Task.bulkCreate([ - { name: 'Important task', fk: 3 }, - { name: 'Important task', fk: 4 }, - { name: 'Important task', fk: 5 }, - { name: 'Important task', fk: 6 } - ]), Employee.bulkCreate([ - { name: 'Jane Doe', fk: 1 }, - { name: 'John Doe', fk: 4 }, - { name: 'Jane John Doe', fk: 5 }, - { name: 'John Jane Doe', fk: 6 } - ])]).then(() =>{ - //Find all projects with tasks and employees - const availableProjects = 3; - const limit = 2; - - return Project.findAndCountAll({ - include: [{ - model: Task, required: true - }, - { - model: Employee, required: true - }], - limit - }).then(result => { - expect(result.count).to.be.equal(availableProjects); - expect(result.rows.length).to.be.equal(limit, 'Complete set of available rows were not returned.'); - }); - }); + await this.sequelize.sync({ force: true }); + + // Create an enviroment + await Promise.all([Project.bulkCreate([ + { id: 1, name: 'No tasks' }, + { id: 2, name: 'No tasks no employees' }, + { id: 3, name: 'No employees' }, + { id: 4, name: 'In progress A' }, + { id: 5, name: 'In progress B' }, + { id: 6, name: 'In progress C' } + ]), Task.bulkCreate([ + { name: 'Important task', fk: 3 }, + { name: 'Important task', fk: 4 }, + { name: 'Important task', fk: 5 }, + { name: 'Important task', fk: 6 } + ]), Employee.bulkCreate([ + { name: 'Jane Doe', fk: 1 }, + { name: 'John Doe', fk: 4 }, + { name: 'Jane John Doe', fk: 5 }, + { name: 'John Jane Doe', fk: 6 } + ])]); + + //Find all projects with tasks and employees + const availableProjects = 3; + const limit = 2; + + const result = await Project.findAndCountAll({ + include: [{ + model: Task, required: true + }, + { + model: Employee, required: true + }], + limit }); + + expect(result.count).to.be.equal(availableProjects); + expect(result.rows.length).to.be.equal(limit, 'Complete set of available rows were not returned.'); }); - it('should be able to include a required model. Result rows should match count', function() { + it('should be able to include a required model. Result rows should match count', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING(40) }, { paranoid: true }), SomeConnection = this.sequelize.define('SomeConnection', { m: DataTypes.STRING(40), @@ -93,79 +93,80 @@ describe(Support.getTestDialectTeaser('Include'), () => { C.hasMany(SomeConnection, { foreignKey: 'fk', constraints: false }); // Sync them - return this.sequelize.sync({ force: true }).then(() => { - // Create an enviroment - - return Promise.all([User.bulkCreate([ - { name: 'Youtube' }, - { name: 'Facebook' }, - { name: 'Google' }, - { name: 'Yahoo' }, - { name: '404' } - ]), SomeConnection.bulkCreate([ // Lets count, m: A and u: 1 - { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted - { u: 2, m: 'A', fk: 1 }, - { u: 3, m: 'A', fk: 1 }, - { u: 4, m: 'A', fk: 1 }, - { u: 5, m: 'A', fk: 1 }, - { u: 1, m: 'B', fk: 1 }, - { u: 2, m: 'B', fk: 1 }, - { u: 3, m: 'B', fk: 1 }, - { u: 4, m: 'B', fk: 1 }, - { u: 5, m: 'B', fk: 1 }, - { u: 1, m: 'C', fk: 1 }, - { u: 2, m: 'C', fk: 1 }, - { u: 3, m: 'C', fk: 1 }, - { u: 4, m: 'C', fk: 1 }, - { u: 5, m: 'C', fk: 1 }, - { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted - { u: 4, m: 'A', fk: 2 }, - { u: 2, m: 'A', fk: 2 }, - { u: 1, m: 'A', fk: 3 }, // 3 - { u: 2, m: 'A', fk: 3 }, - { u: 3, m: 'A', fk: 3 }, - { u: 2, m: 'B', fk: 2 }, - { u: 1, m: 'A', fk: 4 }, // 4 - { u: 4, m: 'A', fk: 2 } - ]), A.bulkCreate([ - { name: 'Just' }, - { name: 'for' }, - { name: 'testing' }, - { name: 'proposes' }, - { name: 'only' } - ]), B.bulkCreate([ - { name: 'this should not' }, - { name: 'be loaded' } - ]), C.bulkCreate([ - { name: 'because we only want A' } - ])]).then(() => { - // Delete some of conns to prove the concept - return SomeConnection.destroy({ where: { - m: 'A', - u: 1, - fk: [1, 2] - } }).then(() => { - this.clock.tick(1000); - // Last and most important queries ( we connected 4, but deleted 2, witch means we must get 2 only ) - return A.findAndCountAll({ - include: [{ - model: SomeConnection, required: true, - where: { - m: 'A', // Pseudo Polymorphy - u: 1 - } - }], - limit: 5 - }).then(result => { - expect(result.count).to.be.equal(2); - expect(result.rows.length).to.be.equal(2); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + // Create an enviroment + + await Promise.all([User.bulkCreate([ + { name: 'Youtube' }, + { name: 'Facebook' }, + { name: 'Google' }, + { name: 'Yahoo' }, + { name: '404' } + ]), SomeConnection.bulkCreate([ // Lets count, m: A and u: 1 + { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted + { u: 2, m: 'A', fk: 1 }, + { u: 3, m: 'A', fk: 1 }, + { u: 4, m: 'A', fk: 1 }, + { u: 5, m: 'A', fk: 1 }, + { u: 1, m: 'B', fk: 1 }, + { u: 2, m: 'B', fk: 1 }, + { u: 3, m: 'B', fk: 1 }, + { u: 4, m: 'B', fk: 1 }, + { u: 5, m: 'B', fk: 1 }, + { u: 1, m: 'C', fk: 1 }, + { u: 2, m: 'C', fk: 1 }, + { u: 3, m: 'C', fk: 1 }, + { u: 4, m: 'C', fk: 1 }, + { u: 5, m: 'C', fk: 1 }, + { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted + { u: 4, m: 'A', fk: 2 }, + { u: 2, m: 'A', fk: 2 }, + { u: 1, m: 'A', fk: 3 }, // 3 + { u: 2, m: 'A', fk: 3 }, + { u: 3, m: 'A', fk: 3 }, + { u: 2, m: 'B', fk: 2 }, + { u: 1, m: 'A', fk: 4 }, // 4 + { u: 4, m: 'A', fk: 2 } + ]), A.bulkCreate([ + { name: 'Just' }, + { name: 'for' }, + { name: 'testing' }, + { name: 'proposes' }, + { name: 'only' } + ]), B.bulkCreate([ + { name: 'this should not' }, + { name: 'be loaded' } + ]), C.bulkCreate([ + { name: 'because we only want A' } + ])]); + + // Delete some of conns to prove the concept + await SomeConnection.destroy({ where: { + m: 'A', + u: 1, + fk: [1, 2] + } }); + + this.clock.tick(1000); + + // Last and most important queries ( we connected 4, but deleted 2, witch means we must get 2 only ) + const result = await A.findAndCountAll({ + include: [{ + model: SomeConnection, required: true, + where: { + m: 'A', // Pseudo Polymorphy + u: 1 + } + }], + limit: 5 }); + + expect(result.count).to.be.equal(2); + expect(result.rows.length).to.be.equal(2); }); - it('should count on a where and not use an uneeded include', function() { + it('should count on a where and not use an uneeded include', async function() { const Project = this.sequelize.define('Project', { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, project_name: { type: DataTypes.STRING } @@ -180,28 +181,25 @@ describe(Support.getTestDialectTeaser('Include'), () => { let userId = null; - return User.sync({ force: true }).then(() => { - return Project.sync({ force: true }); - }).then(() => { - return Promise.all([User.create(), Project.create(), Project.create(), Project.create()]); - }).then(results => { - const user = results[0]; - userId = user.id; - return user.setProjects([results[1], results[2], results[3]]); - }).then(() => { - return User.findAndCountAll({ - where: { id: userId }, - include: [Project], - distinct: true - }); - }).then(result => { - expect(result.rows.length).to.equal(1); - expect(result.rows[0].Projects.length).to.equal(3); - expect(result.count).to.equal(1); + await User.sync({ force: true }); + await Project.sync({ force: true }); + const results = await Promise.all([User.create(), Project.create(), Project.create(), Project.create()]); + const user = results[0]; + userId = user.id; + await user.setProjects([results[1], results[2], results[3]]); + + const result = await User.findAndCountAll({ + where: { id: userId }, + include: [Project], + distinct: true }); + + expect(result.rows.length).to.equal(1); + expect(result.rows[0].Projects.length).to.equal(3); + expect(result.count).to.equal(1); }); - it('should return the correct count and rows when using a required belongsTo and a limit', function() { + it('should return the correct count and rows when using a required belongsTo and a limit', async function() { const s = this.sequelize, Foo = s.define('Foo', {}), Bar = s.define('Bar', {}); @@ -209,63 +207,60 @@ describe(Support.getTestDialectTeaser('Include'), () => { Foo.hasMany(Bar); Bar.belongsTo(Foo); - return s.sync({ force: true }).then(() => { - // Make five instances of Foo - return Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); - }).then(() => { - // Make four instances of Bar, related to the last four instances of Foo - return Bar.bulkCreate([{ 'FooId': 2 }, { 'FooId': 3 }, { 'FooId': 4 }, { 'FooId': 5 }]); - }).then(() => { - // Query for the first two instances of Foo which have related Bars - return Foo.findAndCountAll({ - include: [{ model: Bar, required: true }], - limit: 2 - }).then(result => { - return Promise.resolve(Foo.findAll({ - include: [{ model: Bar, required: true }], - limit: 2 - }).then(items => { - expect(items.length).to.equal(2); - })).then(() => result); - }); - }).then(result => { - expect(result.count).to.equal(4); - - // The first two of those should be returned due to the limit (Foo - // instances 2 and 3) - expect(result.rows.length).to.equal(2); + await s.sync({ force: true }); + // Make five instances of Foo + await Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); + // Make four instances of Bar, related to the last four instances of Foo + await Bar.bulkCreate([{ 'FooId': 2 }, { 'FooId': 3 }, { 'FooId': 4 }, { 'FooId': 5 }]); + + // Query for the first two instances of Foo which have related Bars + const result0 = await Foo.findAndCountAll({ + include: [{ model: Bar, required: true }], + limit: 2 }); + + const items = await Foo.findAll({ + include: [{ model: Bar, required: true }], + limit: 2 + }); + + expect(items.length).to.equal(2); + + const result = result0; + expect(result.count).to.equal(4); + + // The first two of those should be returned due to the limit (Foo + // instances 2 and 3) + expect(result.rows.length).to.equal(2); }); - it('should return the correct count and rows when using a required belongsTo with a where condition and a limit', function() { + it('should return the correct count and rows when using a required belongsTo with a where condition and a limit', async function() { const Foo = this.sequelize.define('Foo', {}), Bar = this.sequelize.define('Bar', { m: DataTypes.STRING(40) }); Foo.hasMany(Bar); Bar.belongsTo(Foo); - return this.sequelize.sync({ force: true }).then(() => { - return Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); - }).then(() => { - // Make four instances of Bar, related to the first two instances of Foo - return Bar.bulkCreate([{ 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'no' }, { 'FooId': 2, m: 'yes' }]); - }).then(() => { - // Query for the first instance of Foo which have related Bars with m === 'yes' - return Foo.findAndCountAll({ - include: [{ model: Bar, where: { m: 'yes' } }], - limit: 1, - distinct: true - }); - }).then(result => { - // There should be 2 instances matching the query (Instances 1 and 2), see the findAll statement - expect(result.count).to.equal(2); - - // The first one of those should be returned due to the limit (Foo instance 1) - expect(result.rows.length).to.equal(1); + await this.sequelize.sync({ force: true }); + await Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); + // Make four instances of Bar, related to the first two instances of Foo + await Bar.bulkCreate([{ 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'no' }, { 'FooId': 2, m: 'yes' }]); + + // Query for the first instance of Foo which have related Bars with m === 'yes' + const result = await Foo.findAndCountAll({ + include: [{ model: Bar, where: { m: 'yes' } }], + limit: 1, + distinct: true }); + + // There should be 2 instances matching the query (Instances 1 and 2), see the findAll statement + expect(result.count).to.equal(2); + + // The first one of those should be returned due to the limit (Foo instance 1) + expect(result.rows.length).to.equal(1); }); - it('should correctly filter, limit and sort when multiple includes and types of associations are present.', function() { + it('should correctly filter, limit and sort when multiple includes and types of associations are present.', async function() { const TaskTag = this.sequelize.define('TaskTag', { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING } @@ -293,52 +288,52 @@ describe(Support.getTestDialectTeaser('Include'), () => { Project.belongsTo(User); Task.belongsTo(Project); Task.belongsToMany(Tag, { through: TaskTag }); + // Sync them - return this.sequelize.sync({ force: true }).then(() => { - // Create an enviroment - return User.bulkCreate([ - { name: 'user-name-1' }, - { name: 'user-name-2' } - ]).then(() => { - return Project.bulkCreate([ - { m: 'A', UserId: 1 }, - { m: 'A', UserId: 2 } - ]); - }).then(() => { - return Task.bulkCreate([ - { ProjectId: 1, name: 'Just' }, - { ProjectId: 1, name: 'for' }, - { ProjectId: 2, name: 'testing' }, - { ProjectId: 2, name: 'proposes' } - ]); - }) - .then(() => { - // Find All Tasks with Project(m=a) and User(name=user-name-2) - return Task.findAndCountAll({ - limit: 1, - offset: 0, - order: [['id', 'DESC']], - include: [ - { - model: Project, - where: { [Op.and]: [{ m: 'A' }] }, - include: [{ - model: User, - where: { [Op.and]: [{ name: 'user-name-2' }] } - } - ] - }, - { model: Tag } - ] - }); - }); - }).then(result => { - expect(result.count).to.equal(2); - expect(result.rows.length).to.equal(1); + await this.sequelize.sync({ force: true }); + + // Create an enviroment + await User.bulkCreate([ + { name: 'user-name-1' }, + { name: 'user-name-2' } + ]); + + await Project.bulkCreate([ + { m: 'A', UserId: 1 }, + { m: 'A', UserId: 2 } + ]); + + await Task.bulkCreate([ + { ProjectId: 1, name: 'Just' }, + { ProjectId: 1, name: 'for' }, + { ProjectId: 2, name: 'testing' }, + { ProjectId: 2, name: 'proposes' } + ]); + + // Find All Tasks with Project(m=a) and User(name=user-name-2) + const result = await Task.findAndCountAll({ + limit: 1, + offset: 0, + order: [['id', 'DESC']], + include: [ + { + model: Project, + where: { [Op.and]: [{ m: 'A' }] }, + include: [{ + model: User, + where: { [Op.and]: [{ name: 'user-name-2' }] } + } + ] + }, + { model: Tag } + ] }); + + expect(result.count).to.equal(2); + expect(result.rows.length).to.equal(1); }); - it('should properly work with sequelize.function', function() { + it('should properly work with sequelize.function', async function() { const sequelize = this.sequelize; const User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, @@ -353,41 +348,40 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Project); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { first_name: 'user-fname-1', last_name: 'user-lname-1' }, - { first_name: 'user-fname-2', last_name: 'user-lname-2' }, - { first_name: 'user-xfname-1', last_name: 'user-xlname-1' } - ]); - }).then(() => { - return Project.bulkCreate([ - { name: 'naam-satya', UserId: 1 }, - { name: 'guru-satya', UserId: 2 }, - { name: 'app-satya', UserId: 2 } - ]); - }).then(() => { - return User.findAndCountAll({ - limit: 1, - offset: 1, - where: sequelize.or( - { first_name: { [Op.like]: '%user-fname%' } }, - { last_name: { [Op.like]: '%user-lname%' } } - ), - include: [ - { - model: Project, - required: true, - where: { name: { - [Op.in]: ['naam-satya', 'guru-satya'] - } } - } - ] - }); - }).then(result => { - expect(result.count).to.equal(2); - expect(result.rows.length).to.equal(1); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([ + { first_name: 'user-fname-1', last_name: 'user-lname-1' }, + { first_name: 'user-fname-2', last_name: 'user-lname-2' }, + { first_name: 'user-xfname-1', last_name: 'user-xlname-1' } + ]); + + await Project.bulkCreate([ + { name: 'naam-satya', UserId: 1 }, + { name: 'guru-satya', UserId: 2 }, + { name: 'app-satya', UserId: 2 } + ]); + + const result = await User.findAndCountAll({ + limit: 1, + offset: 1, + where: sequelize.or( + { first_name: { [Op.like]: '%user-fname%' } }, + { last_name: { [Op.like]: '%user-lname%' } } + ), + include: [ + { + model: Project, + required: true, + where: { name: { + [Op.in]: ['naam-satya', 'guru-satya'] + } } + } + ] }); + expect(result.count).to.equal(2); + expect(result.rows.length).to.equal(1); }); }); }); diff --git a/test/integration/include/findOne.test.js b/test/integration/include/findOne.test.js index 5c315899d9f6..7ea0dcb18cdd 100644 --- a/test/integration/include/findOne.test.js +++ b/test/integration/include/findOne.test.js @@ -9,10 +9,10 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Include'), () => { describe('findOne', () => { - it('should include a non required model, with conditions and two includes N:M 1:M', function( ) { - const A = this.sequelize.define('A', { name: DataTypes.STRING(40) }, { paranoid: true }), - B = this.sequelize.define('B', { name: DataTypes.STRING(40) }, { paranoid: true }), - C = this.sequelize.define('C', { name: DataTypes.STRING(40) }, { paranoid: true }), + it('should include a non required model, with conditions and two includes N:M 1:M', async function() { + const A = this.sequelize.define('A', { name: DataTypes.STRING(40) }, { paranoid: true }), + B = this.sequelize.define('B', { name: DataTypes.STRING(40) }, { paranoid: true }), + C = this.sequelize.define('C', { name: DataTypes.STRING(40) }, { paranoid: true }), D = this.sequelize.define('D', { name: DataTypes.STRING(40) }, { paranoid: true }); // Associations @@ -29,19 +29,19 @@ describe(Support.getTestDialectTeaser('Include'), () => { D.hasMany(B); - return this.sequelize.sync({ force: true }).then(() => { - return A.findOne({ - include: [ - { model: B, required: false, include: [ - { model: C, required: false }, - { model: D } - ] } - ] - }); + await this.sequelize.sync({ force: true }); + + await A.findOne({ + include: [ + { model: B, required: false, include: [ + { model: C, required: false }, + { model: D } + ] } + ] }); }); - it('should work with a 1:M to M:1 relation with a where on the last include', function() { + it('should work with a 1:M to M:1 relation with a where on the last include', async function() { const Model = this.sequelize.define('Model', {}); const Model2 = this.sequelize.define('Model2', {}); const Model4 = this.sequelize.define('Model4', { something: { type: DataTypes.INTEGER } }); @@ -52,18 +52,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { Model2.hasMany(Model4); Model4.belongsTo(Model2); - return this.sequelize.sync({ force: true }).then(() => { - return Model.findOne({ - include: [ - { model: Model2, include: [ - { model: Model4, where: { something: 2 } } - ] } - ] - }); + await this.sequelize.sync({ force: true }); + + await Model.findOne({ + include: [ + { model: Model2, include: [ + { model: Model4, where: { something: 2 } } + ] } + ] }); }); - it('should include a model with a where condition but no required', function() { + it('should include a model with a where condition but no required', async function() { const User = this.sequelize.define('User', {}, { paranoid: false }), Task = this.sequelize.define('Task', { deletedAt: { @@ -74,29 +74,29 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Task, { foreignKey: 'userId' }); Task.belongsTo(User, { foreignKey: 'userId' }); - return this.sequelize.sync({ + await this.sequelize.sync({ force: true - }).then(() => { - return User.create(); - }).then(user => { - return Task.bulkCreate([ - { userId: user.get('id'), deletedAt: new Date() }, - { userId: user.get('id'), deletedAt: new Date() }, - { userId: user.get('id'), deletedAt: new Date() } - ]); - }).then(() => { - return User.findOne({ - include: [ - { model: Task, where: { deletedAt: null }, required: false } - ] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.Tasks.length).to.equal(0); }); + + const user0 = await User.create(); + + await Task.bulkCreate([ + { userId: user0.get('id'), deletedAt: new Date() }, + { userId: user0.get('id'), deletedAt: new Date() }, + { userId: user0.get('id'), deletedAt: new Date() } + ]); + + const user = await User.findOne({ + include: [ + { model: Task, where: { deletedAt: null }, required: false } + ] + }); + + expect(user).to.be.ok; + expect(user.Tasks.length).to.equal(0); }); - it('should include a model with a where clause when the PK field name and attribute name are different', function() { + it('should include a model with a where clause when the PK field name and attribute name are different', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -112,28 +112,28 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Task, { foreignKey: 'userId' }); Task.belongsTo(User, { foreignKey: 'userId' }); - return this.sequelize.sync({ + await this.sequelize.sync({ force: true - }).then(() => { - return User.create(); - }).then(user => { - return Task.bulkCreate([ - { userId: user.get('id'), searchString: 'one' }, - { userId: user.get('id'), searchString: 'two' } - ]); - }).then(() => { - return User.findOne({ - include: [ - { model: Task, where: { searchString: 'one' } } - ] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.Tasks.length).to.equal(1); }); + + const user0 = await User.create(); + + await Task.bulkCreate([ + { userId: user0.get('id'), searchString: 'one' }, + { userId: user0.get('id'), searchString: 'two' } + ]); + + const user = await User.findOne({ + include: [ + { model: Task, where: { searchString: 'one' } } + ] + }); + + expect(user).to.be.ok; + expect(user.Tasks.length).to.equal(1); }); - it('should include a model with a through.where and required true clause when the PK field name and attribute name are different', function() { + it('should include a model with a through.where and required true clause when the PK field name and attribute name are different', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), AB = this.sequelize.define('a_b', { @@ -147,29 +147,24 @@ describe(Support.getTestDialectTeaser('Include'), () => { A.belongsToMany(B, { through: AB }); B.belongsToMany(A, { through: AB }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return Promise.all([A.create({}), B.create({})]); - }) - .then(([a, b]) => { - return a.addB(b, { through: { name: 'Foobar' } }); - }) - .then(() => { - return A.findOne({ - include: [ - { model: B, through: { where: { name: 'Foobar' } }, required: true } - ] - }); - }) - .then(a => { - expect(a).to.not.equal(null); - expect(a.get('bs')).to.have.length(1); - }); + await this.sequelize + .sync({ force: true }); + + const [a0, b] = await Promise.all([A.create({}), B.create({})]); + await a0.addB(b, { through: { name: 'Foobar' } }); + + const a = await A.findOne({ + include: [ + { model: B, through: { where: { name: 'Foobar' } }, required: true } + ] + }); + + expect(a).to.not.equal(null); + expect(a.get('bs')).to.have.length(1); }); - it('should still pull the main record when an included model is not required and has where restrictions without matches', function() { + it('should still pull the main record when an included model is not required and has where restrictions without matches', async function() { const A = this.sequelize.define('a', { name: DataTypes.STRING(40) }), @@ -180,28 +175,25 @@ describe(Support.getTestDialectTeaser('Include'), () => { A.belongsToMany(B, { through: 'a_b' }); B.belongsToMany(A, { through: 'a_b' }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return A.create({ - name: 'Foobar' - }); - }) - .then(() => { - return A.findOne({ - where: { name: 'Foobar' }, - include: [ - { model: B, where: { name: 'idontexist' }, required: false } - ] - }); - }) - .then(a => { - expect(a).to.not.equal(null); - expect(a.get('bs')).to.deep.equal([]); - }); + await this.sequelize + .sync({ force: true }); + + await A.create({ + name: 'Foobar' + }); + + const a = await A.findOne({ + where: { name: 'Foobar' }, + include: [ + { model: B, where: { name: 'idontexist' }, required: false } + ] + }); + + expect(a).to.not.equal(null); + expect(a.get('bs')).to.deep.equal([]); }); - it('should support a nested include (with a where)', function() { + it('should support a nested include (with a where)', async function() { const A = this.sequelize.define('A', { name: DataTypes.STRING }); @@ -220,53 +212,47 @@ describe(Support.getTestDialectTeaser('Include'), () => { B.hasMany(C); C.belongsTo(B); - return this.sequelize - .sync({ force: true }) - .then(() => { - return A.findOne({ + await this.sequelize + .sync({ force: true }); + + const a = await A.findOne({ + include: [ + { + model: B, + where: { flag: true }, include: [ { - model: B, - where: { flag: true }, - include: [ - { - model: C - } - ] + model: C } ] - }); - }) - .then(a => { - expect(a).to.not.exist; - }); + } + ] + }); + + expect(a).to.not.exist; }); - it('should support a belongsTo with the targetKey option', function() { + it('should support a belongsTo with the targetKey option', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, unique: true } }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.removeAttribute('id'); Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newTask.setUser(newUser).then(() => { - return Task.findOne({ - where: { title: 'some task' }, - include: [{ model: User }] - }) - .then(foundTask => { - expect(foundTask).to.be.ok; - expect(foundTask.User.username).to.equal('bob'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + + const foundTask = await Task.findOne({ + where: { title: 'some task' }, + include: [{ model: User }] }); + + expect(foundTask).to.be.ok; + expect(foundTask.User.username).to.equal('bob'); }); - it('should support many levels of belongsTo (with a lower level having a where)', function() { + it('should support many levels of belongsTo (with a lower level having a where)', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), C = this.sequelize.define('c', {}), @@ -288,65 +274,65 @@ describe(Support.getTestDialectTeaser('Include'), () => { F.belongsTo(G); G.belongsTo(H); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([A.create({}), (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; + await this.sequelize.sync({ force: true }); - singles.forEach(model => { - const values = {}; + const [a0, b] = await Promise.all([A.create({}), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; - if (model.name === 'g') { - values.name = 'yolo'; + singles.forEach(model => { + const values = {}; + + if (model.name === 'g') { + values.name = 'yolo'; + } + + promise = (async () => { + await promise; + const instance = await model.create(values); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + return; } + previousInstance = b = instance; + })(); + }); - promise = promise.then(() => { - return model.create(values).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); - - promise = promise.then(() => { - return b; - }); - - return promise; - })([B, C, D, E, F, G, H])]).then(([a, b]) => { - return a.setB(b); - }).then(() => { - return A.findOne({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, where: { - name: 'yolo' - }, include: [ - { model: H } - ] } - ] } + promise = promise.then(() => { + return b; + }); + + return promise; + })([B, C, D, E, F, G, H])]); + + await a0.setB(b); + + const a = await A.findOne({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, where: { + name: 'yolo' + }, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); + ] } + ] } + ] }); + + expect(a.b.c.d.e.f.g.h).to.be.ok; }); - it('should work with combinding a where and a scope', function() { + it('should work with combinding a where and a scope', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: DataTypes.STRING @@ -362,13 +348,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Post, { foreignKey: 'owner_id', scope: { owner_type: 'user' }, as: 'UserPosts', constraints: false }); Post.belongsTo(User, { foreignKey: 'owner_id', as: 'Owner', constraints: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.findOne({ - where: { id: 2 }, - include: [ - { model: Post, as: 'UserPosts', where: { 'private': true } } - ] - }); + await this.sequelize.sync({ force: true }); + + await User.findOne({ + where: { id: 2 }, + include: [ + { model: Post, as: 'UserPosts', where: { 'private': true } } + ] }); }); }); diff --git a/test/integration/include/limit.test.js b/test/integration/include/limit.test.js index d1e3e0d921d8..9ff45e10009a 100644 --- a/test/integration/include/limit.test.js +++ b/test/integration/include/limit.test.js @@ -125,587 +125,621 @@ describe(Support.getTestDialectTeaser('Include'), () => { /* * many-to-many */ - it('supports many-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')) - ])) - .then(([projects, users]) => Promise.all([ - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]) - ])) - .then(() => this.Project.findAll({ + it('supports many-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + where: { + name: 'Alice' + } + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); + }); + + it('supports 2 levels of required many-to-many associations', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, hobbies] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, - where: { - name: 'Alice' - } - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + model: this.Hobby, + required: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 levels of required many-to-many associations', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - ])) - .then(([projects, users, hobbies]) => Promise.all([ - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]) - ])) - .then(() => this.Project.findAll({ + it('supports 2 levels of required many-to-many associations with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, hobbies] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, - required: true, - include: [{ - model: this.Hobby, - required: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + model: this.Hobby, + where: { + name: 'archery' + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 levels of required many-to-many associations with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - ])) - .then(([projects, users, hobbies]) => Promise.all([ - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]) - ])) - .then(() => this.Project.findAll({ + it('supports 2 levels of required many-to-many associations with through.where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, hobbies] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, + model: this.Hobby, required: true, - include: [{ - model: this.Hobby, + through: { where: { - name: 'archery' + HobbyName: 'archery' } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 levels of required many-to-many associations with through.where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - ])) - .then(([projects, users, hobbies]) => Promise.all([ - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]) - ])) - .then(() => this.Project.findAll({ + it('supports 3 levels of required many-to-many associations with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [tasks, projects, users, hobbies] = await Promise.all([ + this.Task.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + tasks[0].addProject(projects[0]), + tasks[1].addProject(projects[1]), + tasks[2].addProject(projects[2]), + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]) + ]); + + const result = await this.Task.findAll({ + include: [{ + model: this.Project, + required: true, include: [{ model: this.User, required: true, include: [{ model: this.Hobby, - required: true, - through: { - where: { - HobbyName: 'archery' - } + where: { + name: 'archery' } }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); - }); - - it('supports 3 levels of required many-to-many associations with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Task.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - ])) - .then(([tasks, projects, users, hobbies]) => Promise.all([ - tasks[0].addProject(projects[0]), - tasks[1].addProject(projects[1]), - tasks[2].addProject(projects[2]), - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]) - ])) - .then(() => this.Task.findAll({ - include: [{ - model: this.Project, - required: true, - include: [{ - model: this.User, - required: true, - include: [{ - model: this.Hobby, - where: { - name: 'archery' - } - }] - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports required many-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')) - ])) - .then(([projects, users]) => Promise.all([// alpha - projects[0].addUser(users[0]), // charlie - projects[2].addUser(users[0])])) - .then(() => this.Project.findAll({ - include: [{ - model: this.User, - required: true - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + it('supports required many-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')) + ]); + + await Promise.all([// alpha + projects[0].addUser(users[0]), // charlie + projects[2].addUser(users[0])]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 required many-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), - this.User.bulkCreate(build('Alice', 'Bob', 'David')), - this.Task.bulkCreate(build('a', 'c', 'd')) - ])) - .then(([projects, users, tasks]) => Promise.all([ - projects[0].addUser(users[0]), - projects[0].addTask(tasks[0]), - projects[1].addUser(users[1]), - projects[2].addTask(tasks[1]), - projects[3].addUser(users[2]), - projects[3].addTask(tasks[2]) - ])) - .then(() => this.Project.findAll({ - include: [{ - model: this.User, - required: true - }, { - model: this.Task, - required: true - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('delta'); - }); + it('supports 2 required many-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, tasks] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), + this.User.bulkCreate(build('Alice', 'Bob', 'David')), + this.Task.bulkCreate(build('a', 'c', 'd')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[0].addTask(tasks[0]), + projects[1].addUser(users[1]), + projects[2].addTask(tasks[1]), + projects[3].addUser(users[2]), + projects[3].addTask(tasks[2]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true + }, { + model: this.Task, + required: true + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('delta'); }); /* * one-to-many */ - it('supports required one-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Comment.bulkCreate(build('comment0', 'comment1')) - ])) - .then(([posts, comments]) => Promise.all([posts[0].addComment(comments[0]), posts[2].addComment(comments[1])])) - .then(() => this.Post.findAll({ - include: [{ - model: this.Comment, - required: true - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + it('supports required one-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, comments] = await Promise.all([ + this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Comment.bulkCreate(build('comment0', 'comment1')) + ]); + + await Promise.all([posts[0].addComment(comments[0]), posts[2].addComment(comments[1])]); + + const result = await this.Post.findAll({ + include: [{ + model: this.Comment, + required: true + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports required one-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - ])) - .then(([posts, comments]) => Promise.all([ - posts[0].addComment(comments[0]), - posts[1].addComment(comments[1]), - posts[2].addComment(comments[2]) - ])) - .then(() => this.Post.findAll({ - include: [{ - model: this.Comment, - required: true, - where: { - [Op.or]: [{ - name: 'comment0' - }, { - name: 'comment2' - }] - } - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + it('supports required one-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, comments] = await Promise.all([ + this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) + ]); + + await Promise.all([ + posts[0].addComment(comments[0]), + posts[1].addComment(comments[1]), + posts[2].addComment(comments[2]) + ]); + + const result = await this.Post.findAll({ + include: [{ + model: this.Comment, + required: true, + where: { + [Op.or]: [{ + name: 'comment0' + }, { + name: 'comment2' + }] + } + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports required one-to-many association with where clause (findOne)', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - ])) - .then(([posts, comments]) => Promise.all([ - posts[0].addComment(comments[0]), - posts[1].addComment(comments[1]), - posts[2].addComment(comments[2]) - ])) - .then(() => this.Post.findOne({ - include: [{ - model: this.Comment, - required: true, - where: { - name: 'comment2' - } - }] - })) - .then(post => { - expect(post.name).to.equal('charlie'); - }); + it('supports required one-to-many association with where clause (findOne)', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, comments] = await Promise.all([ + this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) + ]); + + await Promise.all([ + posts[0].addComment(comments[0]), + posts[1].addComment(comments[1]), + posts[2].addComment(comments[2]) + ]); + + const post = await this.Post.findOne({ + include: [{ + model: this.Comment, + required: true, + where: { + name: 'comment2' + } + }] + }); + + expect(post.name).to.equal('charlie'); }); - it('supports 2 levels of required one-to-many associations', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Post.bulkCreate(build('post0', 'post1', 'post2')), - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - ])) - .then(([users, posts, comments]) => Promise.all([ - users[0].addPost(posts[0]), - users[1].addPost(posts[1]), - users[3].addPost(posts[2]), - posts[0].addComment(comments[0]), - posts[2].addComment(comments[2]) - ])) - .then(() => this.User.findAll({ + it('supports 2 levels of required one-to-many associations', async function() { + await this.sequelize.sync({ force: true }); + + const [users, posts, comments] = await Promise.all([ + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Post.bulkCreate(build('post0', 'post1', 'post2')), + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) + ]); + + await Promise.all([ + users[0].addPost(posts[0]), + users[1].addPost(posts[1]), + users[3].addPost(posts[2]), + posts[0].addComment(comments[0]), + posts[2].addComment(comments[2]) + ]); + + const result = await this.User.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.Comment, - required: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('David'); - }); + model: this.Comment, + required: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('David'); }); /* * mixed many-to-many, one-to-many and many-to-one */ - it('supports required one-to-many association with nested required many-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Post.bulkCreate(build('alpha', 'charlie', 'delta')), - this.Tag.bulkCreate(build('atag', 'btag', 'dtag')) - ])) - .then(([users, posts, tags]) => Promise.all([ - users[0].addPost(posts[0]), - users[2].addPost(posts[1]), - users[3].addPost(posts[2]), - posts[0].addTag([tags[0]]), - posts[2].addTag([tags[2]]) - ])) - .then(() => this.User.findAll({ + it('supports required one-to-many association with nested required many-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [users, posts, tags] = await Promise.all([ + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Post.bulkCreate(build('alpha', 'charlie', 'delta')), + this.Tag.bulkCreate(build('atag', 'btag', 'dtag')) + ]); + + await Promise.all([ + users[0].addPost(posts[0]), + users[2].addPost(posts[1]), + users[3].addPost(posts[2]), + posts[0].addTag([tags[0]]), + posts[2].addTag([tags[2]]) + ]); + + const result = await this.User.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.Tag, - required: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('David'); - }); + model: this.Tag, + required: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('David'); }); - it('supports required many-to-many association with nested required one-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), - this.User.bulkCreate(build('Alice', 'Bob', 'David')), - this.Post.bulkCreate(build('post0', 'post1', 'post2')) - ])) - .then(([projects, users, posts]) => Promise.all([ - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[3].addUser(users[2]), - users[0].addPost([posts[0]]), - users[2].addPost([posts[2]]) - ])) - .then(() => this.Project.findAll({ + it('supports required many-to-many association with nested required one-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, posts] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), + this.User.bulkCreate(build('Alice', 'Bob', 'David')), + this.Post.bulkCreate(build('post0', 'post1', 'post2')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[3].addUser(users[2]), + users[0].addPost([posts[0]]), + users[2].addPost([posts[2]]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, + model: this.Post, required: true, - include: [{ - model: this.Post, - required: true, - duplicating: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('delta'); - }); + duplicating: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('delta'); }); - it('supports required many-to-one association with nested many-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - ])) - .then(([posts, users, hobbies]) => Promise.all([ - posts[0].setUser(users[0]), - posts[1].setUser(users[1]), - posts[3].setUser(users[3]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]), - users[3].addHobby(hobbies[0]) - ])) - .then(() => this.Post.findAll({ + it('supports required many-to-one association with nested many-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, users, hobbies] = await Promise.all([ + this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + posts[0].setUser(users[0]), + posts[1].setUser(users[1]), + posts[3].setUser(users[3]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]), + users[3].addHobby(hobbies[0]) + ]); + + const result = await this.Post.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, - required: true, - include: [{ - model: this.Hobby, - where: { - name: 'archery' - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('post3'); - }); + model: this.Hobby, + where: { + name: 'archery' + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('post3'); }); - it('supports required many-to-one association with nested many-to-many association with through.where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - ])) - .then(([posts, users, hobbies]) => Promise.all([ - posts[0].setUser(users[0]), - posts[1].setUser(users[1]), - posts[3].setUser(users[3]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]), - users[3].addHobby(hobbies[0]) - ])) - .then(() => this.Post.findAll({ + it('supports required many-to-one association with nested many-to-many association with through.where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, users, hobbies] = await Promise.all([ + this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + posts[0].setUser(users[0]), + posts[1].setUser(users[1]), + posts[3].setUser(users[3]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]), + users[3].addHobby(hobbies[0]) + ]); + + const result = await this.Post.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, + model: this.Hobby, required: true, - include: [{ - model: this.Hobby, - required: true, - through: { - where: { - HobbyName: 'archery' - } + through: { + where: { + HobbyName: 'archery' } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('post3'); - }); + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('post3'); }); - it('supports required many-to-one association with multiple nested associations with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2', 'comment3', 'comment4', 'comment5')), - this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3', 'post4')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Tag.bulkCreate(build('tag0', 'tag1')) - ])) - .then(([comments, posts, users, tags]) => Promise.all([ - comments[0].setPost(posts[0]), - comments[1].setPost(posts[1]), - comments[3].setPost(posts[2]), - comments[4].setPost(posts[3]), - comments[5].setPost(posts[4]), - posts[0].addTag(tags[0]), - posts[3].addTag(tags[0]), - posts[4].addTag(tags[0]), - posts[1].addTag(tags[1]), - posts[0].setUser(users[0]), - posts[2].setUser(users[0]), - posts[4].setUser(users[0]), - posts[1].setUser(users[1]) - ])) - .then(() => this.Comment.findAll({ + it('supports required many-to-one association with multiple nested associations with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [comments, posts, users, tags] = await Promise.all([ + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2', 'comment3', 'comment4', 'comment5')), + this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3', 'post4')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Tag.bulkCreate(build('tag0', 'tag1')) + ]); + + await Promise.all([ + comments[0].setPost(posts[0]), + comments[1].setPost(posts[1]), + comments[3].setPost(posts[2]), + comments[4].setPost(posts[3]), + comments[5].setPost(posts[4]), + posts[0].addTag(tags[0]), + posts[3].addTag(tags[0]), + posts[4].addTag(tags[0]), + posts[1].addTag(tags[1]), + posts[0].setUser(users[0]), + posts[2].setUser(users[0]), + posts[4].setUser(users[0]), + posts[1].setUser(users[1]) + ]); + + const result = await this.Comment.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.User, - where: { - name: 'Alice' - } - }, { - model: this.Tag, - where: { - name: 'tag0' - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('comment5'); - }); + model: this.User, + where: { + name: 'Alice' + } + }, { + model: this.Tag, + where: { + name: 'tag0' + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('comment5'); }); - it('supports required many-to-one association with nested one-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')), - this.Post.bulkCreate(build('post0', 'post1', 'post2')), - this.Footnote.bulkCreate(build('footnote0', 'footnote1', 'footnote2')) - ])) - .then(([comments, posts, footnotes]) => Promise.all([ - comments[0].setPost(posts[0]), - comments[1].setPost(posts[1]), - comments[2].setPost(posts[2]), - posts[0].addFootnote(footnotes[0]), - posts[1].addFootnote(footnotes[1]), - posts[2].addFootnote(footnotes[2]) - ])) - .then(() => this.Comment.findAll({ + it('supports required many-to-one association with nested one-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [comments, posts, footnotes] = await Promise.all([ + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')), + this.Post.bulkCreate(build('post0', 'post1', 'post2')), + this.Footnote.bulkCreate(build('footnote0', 'footnote1', 'footnote2')) + ]); + + await Promise.all([ + comments[0].setPost(posts[0]), + comments[1].setPost(posts[1]), + comments[2].setPost(posts[2]), + posts[0].addFootnote(footnotes[0]), + posts[1].addFootnote(footnotes[1]), + posts[2].addFootnote(footnotes[2]) + ]); + + const result = await this.Comment.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.Footnote, - where: { - [Op.or]: [{ - name: 'footnote0' - }, { - name: 'footnote2' - }] - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('comment2'); - }); + model: this.Footnote, + where: { + [Op.or]: [{ + name: 'footnote0' + }, { + name: 'footnote2' + }] + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('comment2'); }); }); }); diff --git a/test/integration/include/paranoid.test.js b/test/integration/include/paranoid.test.js index 633a1bdd4ff5..9f88e8baf1f6 100644 --- a/test/integration/include/paranoid.test.js +++ b/test/integration/include/paranoid.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Paranoid'), () => { - beforeEach(function( ) { + beforeEach(async function() { const S = this.sequelize, DT = DataTypes, @@ -29,7 +29,7 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { D.belongsToMany(A, { through: 'a_d' }); - return S.sync({ force: true }); + await S.sync({ force: true }); }); before(function() { @@ -40,7 +40,7 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { this.clock.restore(); }); - it('paranoid with timestamps: false should be ignored / not crash', function() { + it('paranoid with timestamps: false should be ignored / not crash', async function() { const S = this.sequelize, Test = S.define('Test', { name: DataTypes.STRING @@ -49,12 +49,12 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { paranoid: true }); - return S.sync({ force: true }).then(() => { - return Test.findByPk(1); - }); + await S.sync({ force: true }); + + await Test.findByPk(1); }); - it('test if non required is marked as false', function( ) { + it('test if non required is marked as false', async function() { const A = this.A, B = this.B, options = { @@ -66,12 +66,11 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { ] }; - return A.findOne(options).then(() => { - expect(options.include[0].required).to.be.equal(false); - }); + await A.findOne(options); + expect(options.include[0].required).to.be.equal(false); }); - it('test if required is marked as true', function( ) { + it('test if required is marked as true', async function() { const A = this.A, B = this.B, options = { @@ -83,12 +82,11 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { ] }; - return A.findOne(options).then(() => { - expect(options.include[0].required).to.be.equal(true); - }); + await A.findOne(options); + expect(options.include[0].required).to.be.equal(true); }); - it('should not load paranoid, destroyed instances, with a non-paranoid parent', function() { + it('should not load paranoid, destroyed instances, with a non-paranoid parent', async function() { const X = this.sequelize.define('x', { name: DataTypes.STRING }, { @@ -104,27 +102,26 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { X.hasMany(Y); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - X.create(), - Y.create() - ]); - }).then(([x, y]) => { - this.x = x; - this.y = y; - - return x.addY(y); - }).then(() => { - return this.y.destroy(); - }).then(() => { - //prevent CURRENT_TIMESTAMP to be same - this.clock.tick(1000); - - return X.findAll({ - include: [Y] - }).then(obj => obj[0]); - }).then(x => { - expect(x.ys).to.have.length(0); + await this.sequelize.sync({ force: true }); + + const [x0, y] = await Promise.all([ + X.create(), + Y.create() + ]); + + this.x = x0; + this.y = y; + + await x0.addY(y); + await this.y.destroy(); + //prevent CURRENT_TIMESTAMP to be same + this.clock.tick(1000); + + const obj = await X.findAll({ + include: [Y] }); + + const x = await obj[0]; + expect(x.ys).to.have.length(0); }); }); diff --git a/test/integration/include/schema.test.js b/test/integration/include/schema.test.js index c0ceeb18e53c..bfffa78f80f0 100644 --- a/test/integration/include/schema.test.js +++ b/test/integration/include/schema.test.js @@ -16,11 +16,11 @@ const sortById = function(a, b) { describe(Support.getTestDialectTeaser('Includes with schemas'), () => { describe('findAll', () => { - afterEach(function() { - return this.sequelize.dropSchema('account'); + afterEach(async function() { + await this.sequelize.dropSchema('account'); }); - beforeEach(function() { + beforeEach(async function() { this.fixtureA = async function() { await this.sequelize.dropSchema('account'); await this.sequelize.createSchema('account'); @@ -176,7 +176,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { ]); } }; - return this.sequelize.createSchema('account'); + await this.sequelize.createSchema('account'); }); it('should support an include with multiple different association types', async function() { @@ -383,7 +383,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { }); }); - it('should support ordering with only belongsTo includes', function() { + it('should support ordering with only belongsTo includes', async function() { const User = this.sequelize.define('SpecialUser', {}, { schema: 'account' }), Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }, { schema: 'account' }), Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }, { schema: 'account' }); @@ -392,59 +392,59 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); User.belongsTo(Order); - return this.sequelize.sync().then(() => { - return Promise.all([ - User.bulkCreate([{}, {}, {}]), - Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' }, - { 'test': 'jkl' } - ]), - Order.bulkCreate([ - { 'position': 2 }, - { 'position': 3 }, - { 'position': 1 } - ]) - ]).then(() => { - return Promise.all([ - User.findAll(), - Item.findAll({ order: ['id'] }), - Order.findAll({ order: ['id'] }) - ]); - }).then(([users, items, orders]) => { - return Promise.all([ - users[0].setItemA(items[0]), - users[0].setItemB(items[1]), - users[0].setOrder(orders[2]), - users[1].setItemA(items[2]), - users[1].setItemB(items[3]), - users[1].setOrder(orders[1]), - users[2].setItemA(items[0]), - users[2].setItemB(items[3]), - users[2].setOrder(orders[0]) - ]); - }).then(() => { - return User.findAll({ - 'include': [ - { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, - { 'model': Item, 'as': 'itemB' }, - Order], - 'order': [ - [Order, 'position'] - ] - }).then(as => { - expect(as.length).to.eql(2); - expect(as[0].itemA.test).to.eql('abc'); - expect(as[1].itemA.test).to.eql('abc'); - expect(as[0].Order.position).to.eql(1); - expect(as[1].Order.position).to.eql(2); - }); - }); + await this.sequelize.sync(); + + await Promise.all([ + User.bulkCreate([{}, {}, {}]), + Item.bulkCreate([ + { 'test': 'abc' }, + { 'test': 'def' }, + { 'test': 'ghi' }, + { 'test': 'jkl' } + ]), + Order.bulkCreate([ + { 'position': 2 }, + { 'position': 3 }, + { 'position': 1 } + ]) + ]); + + const [users, items, orders] = await Promise.all([ + User.findAll(), + Item.findAll({ order: ['id'] }), + Order.findAll({ order: ['id'] }) + ]); + + await Promise.all([ + users[0].setItemA(items[0]), + users[0].setItemB(items[1]), + users[0].setOrder(orders[2]), + users[1].setItemA(items[2]), + users[1].setItemB(items[3]), + users[1].setOrder(orders[1]), + users[2].setItemA(items[0]), + users[2].setItemB(items[3]), + users[2].setOrder(orders[0]) + ]); + + const as = await User.findAll({ + 'include': [ + { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, + { 'model': Item, 'as': 'itemB' }, + Order], + 'order': [ + [Order, 'position'] + ] }); + + expect(as.length).to.eql(2); + expect(as[0].itemA.test).to.eql('abc'); + expect(as[1].itemA.test).to.eql('abc'); + expect(as[0].Order.position).to.eql(1); + expect(as[1].Order.position).to.eql(2); }); - it('should include attributes from through models', function() { + it('should include attributes from through models', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }, { schema: 'account' }), @@ -458,84 +458,84 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ]).then(() => { - return Promise.all([ - Product.findAll(), - Tag.findAll() - ]); - }).then(([products, tags]) => { - return Promise.all([ - products[0].addTag(tags[0], { through: { priority: 1 } }), - products[0].addTag(tags[1], { through: { priority: 2 } }), - products[1].addTag(tags[1], { through: { priority: 1 } }), - products[2].addTag(tags[0], { through: { priority: 3 } }), - products[2].addTag(tags[1], { through: { priority: 1 } }), - products[2].addTag(tags[2], { through: { priority: 2 } }) - ]); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag } - ], - order: [ - ['id', 'ASC'], - [Tag, 'id', 'ASC'] - ] - }).then(products => { - expect(products[0].Tags[0].ProductTag.priority).to.equal(1); - expect(products[0].Tags[1].ProductTag.priority).to.equal(2); - expect(products[1].Tags[0].ProductTag.priority).to.equal(1); - expect(products[2].Tags[0].ProductTag.priority).to.equal(3); - expect(products[2].Tags[1].ProductTag.priority).to.equal(1); - expect(products[2].Tags[2].ProductTag.priority).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]) + ]); + + const [products0, tags] = await Promise.all([ + Product.findAll(), + Tag.findAll() + ]); + + await Promise.all([ + products0[0].addTag(tags[0], { through: { priority: 1 } }), + products0[0].addTag(tags[1], { through: { priority: 2 } }), + products0[1].addTag(tags[1], { through: { priority: 1 } }), + products0[2].addTag(tags[0], { through: { priority: 3 } }), + products0[2].addTag(tags[1], { through: { priority: 1 } }), + products0[2].addTag(tags[2], { through: { priority: 2 } }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag } + ], + order: [ + ['id', 'ASC'], + [Tag, 'id', 'ASC'] + ] }); + + expect(products[0].Tags[0].ProductTag.priority).to.equal(1); + expect(products[0].Tags[1].ProductTag.priority).to.equal(2); + expect(products[1].Tags[0].ProductTag.priority).to.equal(1); + expect(products[2].Tags[0].ProductTag.priority).to.equal(3); + expect(products[2].Tags[1].ProductTag.priority).to.equal(1); + expect(products[2].Tags[2].ProductTag.priority).to.equal(2); }); - it('should support a required belongsTo include', function() { + it('should support a required belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', {}, { schema: 'account' }); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([{}, {}]), - User.bulkCreate([{}, {}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll() - ]); - }).then(([groups, users]) => { - return users[2].setGroup(groups[1]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([{}, {}]), + User.bulkCreate([{}, {}, {}]) + ]); + + const [groups, users0] = await Promise.all([ + Group.findAll(), + User.findAll() + ]); + + await users0[2].setGroup(groups[1]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -543,38 +543,38 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll() - ]); - }).then(([groups, users]) => { - return Promise.all([ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - expect(users[0].Group.name).to.equal('A'); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]) + ]); + + const [groups, users0] = await Promise.all([ + Group.findAll(), + User.findAll() + ]); + + await Promise.all([ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; + expect(users[0].Group.name).to.equal('A'); }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -582,38 +582,38 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll() - ]); - }).then(([groups, users]) => { - return Promise.all([ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - users.forEach(user => { - expect(user.Group).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]) + ]); + + const [groups, users0] = await Promise.all([ + Group.findAll(), + User.findAll() + ]); + + await Promise.all([ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] + }); + + users.forEach(user => { + expect(user.Group).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -625,49 +625,49 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]), - Category.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll(), - Category.findAll() - ]); - }).then(([groups, users, categories]) => { - const promises = [ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]; - groups.forEach(group => { - promises.push(group.setCategories(categories)); - }); - return Promise.all(promises); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]), + Category.bulkCreate([{}, {}]) + ]); + + const [groups, users0, categories] = await Promise.all([ + Group.findAll(), + User.findAll(), + Category.findAll() + ]); + + const promises = [ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]; + groups.forEach(group => { + promises.push(group.setCategories(categories)); + }); + await Promise.all(promises); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -679,49 +679,49 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group, { as: 'Team' }); Group.hasMany(Category, { as: 'Tags' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]), - Category.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll(), - Category.findAll() - ]); - }).then(([groups, users, categories]) => { - const promises = [ - users[0].setTeam(groups[1]), - users[1].setTeam(groups[0]) - ]; - groups.forEach(group => { - promises.push(group.setTags(categories)); - }); - return Promise.all(promises); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, as: 'Team', include: [ - { model: Category, as: 'Tags' } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Team).to.be.ok; - expect(user.Team.Tags).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]), + Category.bulkCreate([{}, {}]) + ]); + + const [groups, users0, categories] = await Promise.all([ + Group.findAll(), + User.findAll(), + Category.findAll() + ]); + + const promises = [ + users0[0].setTeam(groups[1]), + users0[1].setTeam(groups[0]) + ]; + groups.forEach(group => { + promises.push(group.setTags(categories)); + }); + await Promise.all(promises); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, as: 'Team', include: [ + { model: Category, as: 'Tags' } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Team).to.be.ok; + expect(user.Team.Tags).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -733,49 +733,49 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]), - Category.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll(), - Category.findAll() - ]); - }).then(([groups, users, categories]) => { - const promises = [ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]; - groups.forEach(group => { - promises.push(group.setCategories(categories)); - }); - return Promise.all(promises); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category, required: false } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]), + Category.bulkCreate([{}, {}]) + ]); + + const [groups, users0, categories] = await Promise.all([ + Group.findAll(), + User.findAll(), + Category.findAll() + ]); + + const promises = [ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]; + groups.forEach(group => { + promises.push(group.setCategories(categories)); + }); + await Promise.all(promises); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category, required: false } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to extend the on clause with a where option on a hasOne include', function() { + it('should be possible to extend the on clause with a where option on a hasOne include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Project = this.sequelize.define('Project', { title: DataTypes.STRING @@ -783,38 +783,38 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.hasOne(Project, { as: 'LeaderOf' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Project.bulkCreate([ - { title: 'Alpha' }, - { title: 'Beta' } - ]), - User.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Project.findAll(), - User.findAll() - ]); - }).then(([projects, users]) => { - return Promise.all([ - users[1].setLeaderOf(projects[1]), - users[0].setLeaderOf(projects[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].LeaderOf).to.be.ok; - expect(users[0].LeaderOf.title).to.equal('Beta'); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Project.bulkCreate([ + { title: 'Alpha' }, + { title: 'Beta' } + ]), + User.bulkCreate([{}, {}]) + ]); + + const [projects, users0] = await Promise.all([ + Project.findAll(), + User.findAll() + ]); + + await Promise.all([ + users0[1].setLeaderOf(projects[1]), + users0[0].setLeaderOf(projects[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].LeaderOf).to.be.ok; + expect(users[0].LeaderOf.title).to.equal('Beta'); }); - it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function() { + it('should be possible to extend the on clause with a where option on a hasMany include with a through model', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }, { schema: 'account' }), @@ -828,43 +828,43 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ]).then(() => { - return Promise.all([ - Product.findAll(), - Tag.findAll() - ]); - }).then(([products, tags]) => { - return Promise.all([ - products[0].addTag(tags[0], { priority: 1 }), - products[0].addTag(tags[1], { priority: 2 }), - products[1].addTag(tags[1], { priority: 1 }), - products[2].addTag(tags[0], { priority: 3 }), - products[2].addTag(tags[1], { priority: 1 }), - products[2].addTag(tags[2], { priority: 2 }) - ]); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag, where: { name: 'C' } } - ] - }).then(products => { - expect(products.length).to.equal(1); - expect(products[0].Tags.length).to.equal(1); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]) + ]); + + const [products0, tags] = await Promise.all([ + Product.findAll(), + Tag.findAll() + ]); + + await Promise.all([ + products0[0].addTag(tags[0], { priority: 1 }), + products0[0].addTag(tags[1], { priority: 2 }), + products0[1].addTag(tags[1], { priority: 1 }), + products0[2].addTag(tags[0], { priority: 3 }), + products0[2].addTag(tags[1], { priority: 1 }), + products0[2].addTag(tags[2], { priority: 2 }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag, where: { name: 'C' } } + ] }); + + expect(products.length).to.equal(1); + expect(products[0].Tags.length).to.equal(1); }); it('should be possible to extend the on clause with a where option on nested includes', async function() { @@ -993,7 +993,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { } }); - it('should be possible to use limit and a where with a belongsTo include', function() { + it('should be possible to use limit and a where with a belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1001,123 +1001,123 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return promiseProps({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}, {}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.all([ - results.users[1].setGroup(results.groups[0]), - results.users[2].setGroup(results.groups[0]), - results.users[3].setGroup(results.groups[1]), - results.users[0].setGroup(results.groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ], - limit: 2 - }).then(users => { - expect(users.length).to.equal(2); - - users.forEach(user => { - expect(user.Group.name).to.equal('A'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}, {}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[1].setGroup(results.groups[0]), + results.users[2].setGroup(results.groups[0]), + results.users[3].setGroup(results.groups[1]), + results.users[0].setGroup(results.groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ], + limit: 2 + }); + + expect(users.length).to.equal(2); + + users.forEach(user => { + expect(user.Group.name).to.equal('A'); }); }); - it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.Company, where: { name: 'NYSE' } }, - { model: this.models.Tag }, - { model: this.models.Price } - ], - limit: 3, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(3); - - products.forEach(product => { - expect(product.Company.name).to.equal('NYSE'); - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - }); - }); + it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { model: this.models.Company, where: { name: 'NYSE' } }, + { model: this.models.Tag }, + { model: this.models.Price } + ], + limit: 3, + order: [ + ['id', 'ASC'] + ] + }); + + expect(products.length).to.equal(3); + + products.forEach(product => { + expect(product.Company.name).to.equal('NYSE'); + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; }); }); - it('should be possible to use limit and a where on a hasMany with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag }, - { model: this.models.Price, where: { - value: { [Op.gt]: 5 } - } } - ], - limit: 6, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(6); + it('should be possible to use limit and a where on a hasMany with additional includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag }, + { model: this.models.Price, where: { + value: { [Op.gt]: 5 } + } } + ], + limit: 6, + order: [ + ['id', 'ASC'] + ] + }); + + expect(products.length).to.equal(6); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; - product.Prices.forEach(price => { - expect(price.value).to.be.above(5); - }); - }); + product.Prices.forEach(price => { + expect(price.value).to.be.above(5); }); }); }); - it('should be possible to use limit and a where on a hasMany with a through model with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, - { model: this.models.Price } - ], - limit: 10, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(10); + it('should be possible to use limit and a where on a hasMany with a through model with additional includes', async function() { + await this.fixtureA(); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, + { model: this.models.Price } + ], + limit: 10, + order: [ + ['id', 'ASC'] + ] + }); - product.Tags.forEach(tag => { - expect(['A', 'B', 'C']).to.include(tag.name); - }); - }); + expect(products.length).to.equal(10); + + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Tags.forEach(tag => { + expect(['A', 'B', 'C']).to.include(tag.name); }); }); }); - it('should support including date fields, with the correct timezone', function() { + it('should support including date fields, with the correct timezone', async function() { const User = this.sequelize.define('user', { dateField: Sequelize.DATE }, { timestamps: false, schema: 'account' }), @@ -1128,34 +1128,31 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsToMany(Group, { through: 'group_user' }); Group.belongsToMany(User, { through: 'group_user' }); - return this.sequelize.sync().then(() => { - return User.create({ dateField: Date.UTC(2014, 1, 20) }).then(user => { - return Group.create({ dateField: Date.UTC(2014, 1, 20) }).then(group => { - return user.addGroup(group).then(() => { - return User.findAll({ - where: { - id: user.id - }, - include: [Group] - }).then(users => { - if (dialect === 'sqlite') { - expect(new Date(users[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(new Date(users[0].groups[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); - } else { - expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - } - }); - }); - }); - }); + await this.sequelize.sync(); + const user = await User.create({ dateField: Date.UTC(2014, 1, 20) }); + const group = await Group.create({ dateField: Date.UTC(2014, 1, 20) }); + await user.addGroup(group); + + const users = await User.findAll({ + where: { + id: user.id + }, + include: [Group] }); + + if (dialect === 'sqlite') { + expect(new Date(users[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(new Date(users[0].groups[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); + } else { + expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + } }); }); describe('findOne', () => { - it('should work with schemas', function() { + it('should work with schemas', async function() { const UserModel = this.sequelize.define('User', { Id: { type: DataTypes.INTEGER, @@ -1207,21 +1204,21 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { foreignKey: 'UserId' }); - return this.sequelize.dropSchema('hero').then(() => { - return this.sequelize.createSchema('hero'); - }).then(() => { - return this.sequelize.sync({ force: true }).then(() => { - return UserModel.findOne({ - where: { - Id: 1 - }, - include: [{ - model: ResumeModel, - as: 'Resume' - }] - }); - }); - }).then(() => this.sequelize.dropSchema('hero')); + await this.sequelize.dropSchema('hero'); + await this.sequelize.createSchema('hero'); + await this.sequelize.sync({ force: true }); + + await UserModel.findOne({ + where: { + Id: 1 + }, + include: [{ + model: ResumeModel, + as: 'Resume' + }] + }); + + await this.sequelize.dropSchema('hero'); }); }); }); diff --git a/test/integration/include/separate.test.js b/test/integration/include/separate.test.js index 709aa3c36791..aea667455c47 100644 --- a/test/integration/include/separate.test.js +++ b/test/integration/include/separate.test.js @@ -11,55 +11,55 @@ const chai = require('chai'), if (current.dialect.supports.groupedLimit) { describe(Support.getTestDialectTeaser('Include'), () => { describe('separate', () => { - it('should run a hasMany association in a separate query', function() { + it('should run a hasMany association in a separate query', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', {}), sqlSpy = sinon.spy(); User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), User.create({ - id: 2, - tasks: [ - {} - ] - }, { - include: [User.Tasks] - })]).then(() => { - return User.findAll({ - include: [ - { association: User.Tasks, separate: true } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(3); - expect(users[1].get('tasks')).to.be.ok; - expect(users[1].get('tasks').length).to.equal(1); - - expect(users[0].get('tasks')[0].createdAt).to.be.ok; - expect(users[0].get('tasks')[0].updatedAt).to.be.ok; - - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {} + ] + }, { + include: [User.Tasks] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Tasks, separate: true } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(3); + expect(users[1].get('tasks')).to.be.ok; + expect(users[1].get('tasks').length).to.equal(1); + + expect(users[0].get('tasks')[0].createdAt).to.be.ok; + expect(users[0].get('tasks')[0].updatedAt).to.be.ok; + + expect(sqlSpy).to.have.been.calledTwice; }); - it('should work even if the id was not included', function() { + it('should work even if the id was not included', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING }), @@ -68,36 +68,36 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }).then(() => { - return User.findAll({ - attributes: ['name'], - include: [ - { association: User.Tasks, separate: true } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(3); - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] }); + + const users = await User.findAll({ + attributes: ['name'], + include: [ + { association: User.Tasks, separate: true } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy + }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(3); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should work even if include does not specify foreign key attribute with custom sourceKey', function() { + it('should work even if include does not specify foreign key attribute with custom sourceKey', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING, userExtraId: { @@ -116,47 +116,44 @@ if (current.dialect.supports.groupedLimit) { sourceKey: 'userExtraId' }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return User.create({ - id: 1, - userExtraId: 222, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }); - }) - .then(() => { - return User.findAll({ - attributes: ['name'], - include: [ - { - attributes: [ - 'title' - ], - association: User.Tasks, - separate: true - } - ], - order: [ - ['id', 'ASC'] + await this.sequelize + .sync({ force: true }); + + await User.create({ + id: 1, + userExtraId: 222, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }); + + const users = await User.findAll({ + attributes: ['name'], + include: [ + { + attributes: [ + 'title' ], - logging: sqlSpy - }); - }) - .then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(3); - expect(sqlSpy).to.have.been.calledTwice; - }); + association: User.Tasks, + separate: true + } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy + }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(3); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should not break a nested include with null values', function() { + it('should not break a nested include with null values', async function() { const User = this.sequelize.define('User', {}), Team = this.sequelize.define('Team', {}), Company = this.sequelize.define('Company', {}); @@ -164,18 +161,17 @@ if (current.dialect.supports.groupedLimit) { User.Team = User.belongsTo(Team); Team.Company = Team.belongsTo(Company); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}); - }).then(() => { - return User.findAll({ - include: [ - { association: User.Team, include: [Team.Company] } - ] - }); + await this.sequelize.sync({ force: true }); + await User.create({}); + + await User.findAll({ + include: [ + { association: User.Team, include: [Team.Company] } + ] }); }); - it('should run a hasMany association with limit in a separate query', function() { + it('should run a hasMany association with limit in a separate query', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { userId: { @@ -187,47 +183,47 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks', foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), User.create({ - id: 2, - tasks: [ - {}, - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - })]).then(() => { - return User.findAll({ - include: [ - { association: User.Tasks, limit: 2 } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(2); - expect(users[1].get('tasks')).to.be.ok; - expect(users[1].get('tasks').length).to.equal(2); - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {}, + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Tasks, limit: 2 } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(2); + expect(users[1].get('tasks')).to.be.ok; + expect(users[1].get('tasks').length).to.equal(2); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should run a nested (from a non-separate include) hasMany association in a separate query', function() { + it('should run a nested (from a non-separate include) hasMany association in a separate query', async function() { const User = this.sequelize.define('User', {}), Company = this.sequelize.define('Company'), Task = this.sequelize.define('Task', {}), @@ -236,54 +232,54 @@ if (current.dialect.supports.groupedLimit) { User.Company = User.belongsTo(Company, { as: 'company' }); Company.Tasks = Company.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - company: { - tasks: [ - {}, - {}, - {} - ] - } - }, { - include: [ - { association: User.Company, include: [Company.Tasks] } + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + company: { + tasks: [ + {}, + {}, + {} ] - }), User.create({ - id: 2, - company: { - tasks: [ - {} - ] - } - }, { - include: [ - { association: User.Company, include: [Company.Tasks] } + } + }, { + include: [ + { association: User.Company, include: [Company.Tasks] } + ] + }), User.create({ + id: 2, + company: { + tasks: [ + {} ] - })]).then(() => { - return User.findAll({ - include: [ - { association: User.Company, include: [ - { association: Company.Tasks, separate: true } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('company').get('tasks')).to.be.ok; - expect(users[0].get('company').get('tasks').length).to.equal(3); - expect(users[1].get('company').get('tasks')).to.be.ok; - expect(users[1].get('company').get('tasks').length).to.equal(1); - expect(sqlSpy).to.have.been.calledTwice; - }); + } + }, { + include: [ + { association: User.Company, include: [Company.Tasks] } + ] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Company, include: [ + { association: Company.Tasks, separate: true } + ] } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('company').get('tasks')).to.be.ok; + expect(users[0].get('company').get('tasks').length).to.equal(3); + expect(users[1].get('company').get('tasks')).to.be.ok; + expect(users[1].get('company').get('tasks').length).to.equal(1); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should work having a separate include between a parent and child include', function() { + it('should work having a separate include between a parent and child include', async function() { const User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project'), Company = this.sequelize.define('Company'), @@ -294,49 +290,49 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.Project = Task.belongsTo(Project, { as: 'project' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Company.create({ - id: 1, - users: [ - { - tasks: [ - { project: {} }, - { project: {} }, - { project: {} } - ] - } - ] - }, { - include: [ - { association: Company.Users, include: [ - { association: User.Tasks, include: [ - Task.Project - ] } - ] } - ] - })]).then(() => { - return Company.findAll({ - include: [ - { association: Company.Users, include: [ - { association: User.Tasks, separate: true, include: [ - Task.Project - ] } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(companies => { - expect(sqlSpy).to.have.been.calledTwice; + await this.sequelize.sync({ force: true }); - expect(companies[0].users[0].tasks[0].project).to.be.ok; - }); + await Promise.all([Company.create({ + id: 1, + users: [ + { + tasks: [ + { project: {} }, + { project: {} }, + { project: {} } + ] + } + ] + }, { + include: [ + { association: Company.Users, include: [ + { association: User.Tasks, include: [ + Task.Project + ] } + ] } + ] + })]); + + const companies = await Company.findAll({ + include: [ + { association: Company.Users, include: [ + { association: User.Tasks, separate: true, include: [ + Task.Project + ] } + ] } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(sqlSpy).to.have.been.calledTwice; + + expect(companies[0].users[0].tasks[0].project).to.be.ok; }); - it('should run two nested hasMany association in a separate queries', function() { + it('should run two nested hasMany association in a separate queries', async function() { const User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project', {}), Task = this.sequelize.define('Task', {}), @@ -345,79 +341,79 @@ if (current.dialect.supports.groupedLimit) { User.Projects = User.hasMany(Project, { as: 'projects' }); Project.Tasks = Project.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - projects: [ - { - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, - { - id: 2, - tasks: [ - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } - ] - }), User.create({ - id: 2, - projects: [ - { - id: 3, - tasks: [ - {}, - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } - ] - })]).then(() => { - return User.findAll({ - include: [ - { association: User.Projects, separate: true, include: [ - { association: Project.Tasks, separate: true } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - const u1projects = users[0].get('projects'); - - expect(u1projects).to.be.ok; - expect(u1projects[0].get('tasks')).to.be.ok; - expect(u1projects[1].get('tasks')).to.be.ok; - expect(u1projects.length).to.equal(2); - - // WTB ES2015 syntax ... - expect(u1projects.find(p => p.id === 1).get('tasks').length).to.equal(3); - expect(u1projects.find(p => p.id === 2).get('tasks').length).to.equal(1); - - expect(users[1].get('projects')).to.be.ok; - expect(users[1].get('projects')[0].get('tasks')).to.be.ok; - expect(users[1].get('projects').length).to.equal(1); - expect(users[1].get('projects')[0].get('tasks').length).to.equal(2); - - expect(sqlSpy).to.have.been.calledThrice; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + projects: [ + { + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, + { + id: 2, + tasks: [ + {} + ] + } + ] + }, { + include: [ + { association: User.Projects, include: [Project.Tasks] } + ] + }), User.create({ + id: 2, + projects: [ + { + id: 3, + tasks: [ + {}, + {} + ] + } + ] + }, { + include: [ + { association: User.Projects, include: [Project.Tasks] } + ] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Projects, separate: true, include: [ + { association: Project.Tasks, separate: true } + ] } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + const u1projects = users[0].get('projects'); + + expect(u1projects).to.be.ok; + expect(u1projects[0].get('tasks')).to.be.ok; + expect(u1projects[1].get('tasks')).to.be.ok; + expect(u1projects.length).to.equal(2); + + // WTB ES2015 syntax ... + expect(u1projects.find(p => p.id === 1).get('tasks').length).to.equal(3); + expect(u1projects.find(p => p.id === 2).get('tasks').length).to.equal(1); + + expect(users[1].get('projects')).to.be.ok; + expect(users[1].get('projects')[0].get('tasks')).to.be.ok; + expect(users[1].get('projects').length).to.equal(1); + expect(users[1].get('projects')[0].get('tasks').length).to.equal(2); + + expect(sqlSpy).to.have.been.calledThrice; }); - it('should work with two schema models in a hasMany association', function() { + it('should work with two schema models in a hasMany association', async function() { const User = this.sequelize.define('User', {}, { schema: 'archive' }), Task = this.sequelize.define('Task', { id: { type: DataTypes.INTEGER, primaryKey: true }, @@ -426,54 +422,50 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('archive').then(() => { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([User.create({ - id: 1, - tasks: [ - { id: 1, title: 'b' }, - { id: 2, title: 'd' }, - { id: 3, title: 'c' }, - { id: 4, title: 'a' } - ] - }, { - include: [User.Tasks] - }), User.create({ - id: 2, - tasks: [ - { id: 5, title: 'a' }, - { id: 6, title: 'c' }, - { id: 7, title: 'b' } - ] - }, { - include: [User.Tasks] - })]); - }).then(() => { - return User.findAll({ - include: [{ model: Task, limit: 2, as: 'tasks', order: [['id', 'ASC']] }], - order: [ - ['id', 'ASC'] - ] - }).then(result => { - expect(result[0].tasks.length).to.equal(2); - expect(result[0].tasks[0].title).to.equal('b'); - expect(result[0].tasks[1].title).to.equal('d'); - - expect(result[1].tasks.length).to.equal(2); - expect(result[1].tasks[0].title).to.equal('a'); - expect(result[1].tasks[1].title).to.equal('c'); - return this.sequelize.dropSchema('archive').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('archive'); - } - }); - }); - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('archive'); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + { id: 1, title: 'b' }, + { id: 2, title: 'd' }, + { id: 3, title: 'c' }, + { id: 4, title: 'a' } + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + { id: 5, title: 'a' }, + { id: 6, title: 'c' }, + { id: 7, title: 'b' } + ] + }, { + include: [User.Tasks] + })]); + + const result = await User.findAll({ + include: [{ model: Task, limit: 2, as: 'tasks', order: [['id', 'ASC']] }], + order: [ + ['id', 'ASC'] + ] }); + + expect(result[0].tasks.length).to.equal(2); + expect(result[0].tasks[0].title).to.equal('b'); + expect(result[0].tasks[1].title).to.equal('d'); + + expect(result[1].tasks.length).to.equal(2); + expect(result[1].tasks[0].title).to.equal('a'); + expect(result[1].tasks[1].title).to.equal('c'); + await this.sequelize.dropSchema('archive'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('archive'); + } }); it('should work with required non-separate parent and required child', async function() { From c31d68da2a603f8946f398ef75f5ccac941841c7 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Fri, 22 May 2020 01:39:39 -0500 Subject: [PATCH 159/414] test(integration/sequelize): asyncify (#12295) --- test/integration/sequelize/deferrable.test.js | 71 +++++++++---------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/test/integration/sequelize/deferrable.test.js b/test/integration/sequelize/deferrable.test.js index 994f4b7957a0..11c85c0ae540 100644 --- a/test/integration/sequelize/deferrable.test.js +++ b/test/integration/sequelize/deferrable.test.js @@ -14,7 +14,7 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) { describe(Support.getTestDialectTeaser('Sequelize'), () => { describe('Deferrable', () => { beforeEach(function() { - this.run = function(deferrable, options) { + this.run = async function(deferrable, options) { options = options || {}; const taskTableName = options.taskTableName || `tasks_${config.rand()}`; @@ -42,69 +42,62 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { } ); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return this.sequelize.transaction(transactionOptions, t => { - return Task - .create({ title: 'a task', user_id: -1 }, { transaction: t }) - .then(task => { - return Promise.all([task, User.create({}, { transaction: t })]); - }) - .then(([task, user]) => { - task.user_id = user.id; - return task.save({ transaction: t }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + + return this.sequelize.transaction(transactionOptions, async t => { + const task0 = await Task + .create({ title: 'a task', user_id: -1 }, { transaction: t }); + + const [task, user] = await Promise.all([task0, User.create({}, { transaction: t })]); + task.user_id = user.id; + return task.save({ transaction: t }); }); }; }); describe('NOT', () => { - it('does not allow the violation of the foreign key constraint', function() { - return expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + it('does not allow the violation of the foreign key constraint', async function() { + await expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); }); describe('INITIALLY_IMMEDIATE', () => { - it('allows the violation of the foreign key constraint if the transaction is deferred', function() { - return this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE) - .then(task => { - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); - }); + it('allows the violation of the foreign key constraint if the transaction is deferred', async function() { + const task = await this + .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE); + + expect(task.title).to.equal('a task'); + expect(task.user_id).to.equal(1); }); - it('does not allow the violation of the foreign key constraint if the transaction is not deffered', function() { - return expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { + it('does not allow the violation of the foreign key constraint if the transaction is not deffered', async function() { + await expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { deferrable: undefined })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); - it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', function() { + it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', async function() { const taskTableName = `tasks_${config.rand()}`; - return this + const task = await this .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]), taskTableName - }) - .then(task => { - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); }); + + expect(task.title).to.equal('a task'); + expect(task.user_id).to.equal(1); }); }); describe('INITIALLY_DEFERRED', () => { - it('allows the violation of the foreign key constraint', function() { - return this - .run(Sequelize.Deferrable.INITIALLY_DEFERRED) - .then(task => { - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); - }); + it('allows the violation of the foreign key constraint', async function() { + const task = await this + .run(Sequelize.Deferrable.INITIALLY_DEFERRED); + + expect(task.title).to.equal('a task'); + expect(task.user_id).to.equal(1); }); }); }); From d7fcf213496f3842cbdc2b78413dc8814da155d0 Mon Sep 17 00:00:00 2001 From: Andy Edwards Date: Sat, 23 May 2020 02:33:26 -0500 Subject: [PATCH 160/414] fix: add missing sql and parameters properties to some query errors (#12299) --- lib/dialects/mssql/query.js | 2 ++ lib/dialects/postgres/query.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 7ee46fdbab12..ea88cacfb6dc 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -53,6 +53,8 @@ class Query extends AbstractQuery { return new Promise((resolve, reject) => { const handleTransaction = err => { if (err) { + err.sql = sql; + err.parameters = parameters; reject(this.formatError(err)); return; } diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 15ea21410e37..50feaaadf195 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -126,6 +126,8 @@ class Query extends AbstractQuery { if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { if (rows[0].sequelize_caught_exception !== null) { throw this.formatError({ + sql, + parameters, code: '23505', detail: rows[0].sequelize_caught_exception }); From b2bccb8aa3faeb6a891fd9f7d3858ebff5b9c32f Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 23 May 2020 14:59:40 +0530 Subject: [PATCH 161/414] build: update dependencies (#12300) --- lib/dialects/abstract/query-generator.js | 2 +- .../abstract/query-generator/transaction.js | 2 +- lib/dialects/abstract/query.js | 2 +- lib/dialects/mssql/connection-manager.js | 2 +- lib/utils.js | 4 +- package-lock.json | 1485 +++++++++++------ package.json | 40 +- scripts/appveyor-setup.ps1 | 1 + .../dialects/mssql/connection-manager.test.js | 22 +- 9 files changed, 1020 insertions(+), 540 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 9cb0f285aa36..8b603fab6ab6 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -2,7 +2,7 @@ const util = require('util'); const _ = require('lodash'); -const uuidv4 = require('uuid/v4'); +const uuidv4 = require('uuid').v4; const semver = require('semver'); const Utils = require('../../utils'); diff --git a/lib/dialects/abstract/query-generator/transaction.js b/lib/dialects/abstract/query-generator/transaction.js index 06aeae5e810c..c047008f9696 100644 --- a/lib/dialects/abstract/query-generator/transaction.js +++ b/lib/dialects/abstract/query-generator/transaction.js @@ -1,6 +1,6 @@ 'use strict'; -const uuidv4 = require('uuid/v4'); +const uuidv4 = require('uuid').v4; const TransactionQueries = { /** diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index f5a755ce0fd9..7e640b83fee4 100644 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -5,7 +5,7 @@ const SqlString = require('../../sql-string'); const QueryTypes = require('../../query-types'); const Dot = require('dottie'); const deprecations = require('../../utils/deprecations'); -const uuid = require('uuid/v4'); +const uuid = require('uuid').v4; class AbstractQuery { diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index 32f336bf3e5e..b5de1900d64c 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -38,7 +38,7 @@ class ConnectionManager extends AbstractConnectionManager { options: { port: parseInt(config.port, 10), database: config.database, - encrypt: false + trustServerCertificate: true } }; diff --git a/lib/utils.js b/lib/utils.js index 538cec2791a9..4299619bac0e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,8 +3,8 @@ const DataTypes = require('./data-types'); const SqlString = require('./sql-string'); const _ = require('lodash'); -const uuidv1 = require('uuid/v1'); -const uuidv4 = require('uuid/v4'); +const uuidv1 = require('uuid').v1; +const uuidv4 = require('uuid').v4; const operators = require('./operators'); const operatorsSet = new Set(Object.values(operators)); diff --git a/package-lock.json b/package-lock.json index a4aa41c0c7ba..852f7bbe57dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,47 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@azure/ms-rest-azure-env": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz", + "integrity": "sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA==", + "dev": true + }, + "@azure/ms-rest-js": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-1.8.15.tgz", + "integrity": "sha512-kIB71V3DcrA4iysBbOsYcxd4WWlOE7OFtCUYNfflPODM0lbIR23A236QeTn5iAeYwcHmMjR/TAKp5KQQh/WqoQ==", + "dev": true, + "requires": { + "@types/tunnel": "0.0.0", + "axios": "^0.19.0", + "form-data": "^2.3.2", + "tough-cookie": "^2.4.3", + "tslib": "^1.9.2", + "tunnel": "0.0.6", + "uuid": "^3.2.1", + "xml2js": "^0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "@azure/ms-rest-nodeauth": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-2.0.2.tgz", + "integrity": "sha512-KmNNICOxt3EwViAJI3iu2VH8t8BQg5J2rSAyO4IUYLF9ZwlyYsP419pdvl4NBUhluAP2cgN7dfD2V6E6NOMZlQ==", + "dev": true, + "requires": { + "@azure/ms-rest-azure-env": "^1.1.2", + "@azure/ms-rest-js": "^1.8.7", + "adal-node": "^0.1.28" + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -494,6 +535,52 @@ } } }, + "@definitelytyped/header-parser": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@definitelytyped/header-parser/-/header-parser-0.0.34.tgz", + "integrity": "sha512-/yTifMAhYKB8SFH3pSlAQmcBzrk7UyqpEz9/vJKaMKdzRpJrxmc1zWMP+hwJtJTVCjAK+Ul4m3i1GZQrTZfymw==", + "dev": true, + "requires": { + "@definitelytyped/typescript-versions": "^0.0.34", + "@types/parsimmon": "^1.10.1", + "parsimmon": "^1.13.0" + } + }, + "@definitelytyped/typescript-versions": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@definitelytyped/typescript-versions/-/typescript-versions-0.0.34.tgz", + "integrity": "sha512-7IqWcbHKYbfY8Lt7AigXDa29cbz3gynzBHMjwMUCeLnex8D682M6OW8uBLouvVHCr+YENL58tQB3dn0Zos8mFQ==", + "dev": true + }, + "@definitelytyped/utils": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@definitelytyped/utils/-/utils-0.0.34.tgz", + "integrity": "sha512-C1mlA9ixRfv7PPmO99hJZ8Ii2roKY+7GoIwBPh5uYSF6WfABoVlzyWX3LsF6fy1MwpZbempC+r81iDm2QeYTsw==", + "dev": true, + "requires": { + "@definitelytyped/typescript-versions": "^0.0.34", + "@types/node": "^12.12.29", + "charm": "^1.0.2", + "fs-extra": "^8.1.0", + "fstream": "^1.0.12", + "npm-registry-client": "^8.6.0", + "tar": "^2.2.2", + "tar-stream": "1.6.2" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", @@ -569,6 +656,12 @@ "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true }, + "@js-joda/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-2.0.0.tgz", + "integrity": "sha512-OWm/xa9O9e4ugzNHoRT3IsXZZYfaV6Ia1aRwctOmCQ2GYWMnhKBzMC1WomqCh/oGxEZKNtPy5xv5//VIAOgMqw==", + "dev": true + }, "@marionebl/sander": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@marionebl/sander/-/sander-0.6.1.tgz", @@ -1159,9 +1252,9 @@ "dev": true }, "@types/node": { - "version": "12.12.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.37.tgz", - "integrity": "sha512-4mXKoDptrXAwZErQHrLzpe0FN/0Wmf5JRniSVIdwUrtDf9wnmEV1teCNLBo/TwuXhkK/bVegoEn/wmb+x0AuPg==" + "version": "12.12.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.42.tgz", + "integrity": "sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -1176,9 +1269,9 @@ "dev": true }, "@types/parsimmon": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@types/parsimmon/-/parsimmon-1.10.1.tgz", - "integrity": "sha512-MoF2IC9oGSgArJwlxdst4XsvWuoYfNUWtBw0kpnCi6K05kV+Ecl7siEeJ40tgCbI9uqEMGQL/NlPMRv6KVkY5Q==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@types/parsimmon/-/parsimmon-1.10.2.tgz", + "integrity": "sha512-WVugAiBoLsmay9IPrLJoMnmLTP0cWPbc4w5c5suTevyhaJW9TWGyPbkFraNUk5YULf8vQ5C/3NBEQcIs6XfTcg==", "dev": true }, "@types/retry": { @@ -1187,6 +1280,15 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "@types/tunnel": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.0.tgz", + "integrity": "sha512-FGDp0iBRiBdPjOgjJmn1NH0KDLN+Z8fRmo+9J7XGBhubq1DPrGrbmG4UTlGzrpbCpesMqD0sWkzi27EYkOMHyg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/validator": { "version": "10.11.3", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", @@ -1217,9 +1319,9 @@ "dev": true }, "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", "dev": true }, "acorn-globals": { @@ -1265,9 +1367,15 @@ }, "dependencies": { "@types/node": { - "version": "8.10.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", - "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==", + "version": "8.10.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz", + "integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true } } @@ -1312,9 +1420,9 @@ } }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true }, "ansi-escapes": { @@ -1335,9 +1443,9 @@ } }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { @@ -1529,6 +1637,15 @@ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "dev": true }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "dev": true, + "requires": { + "follow-redirects": "1.5.10" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -1709,12 +1826,6 @@ "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", "dev": true }, - "big-number": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/big-number/-/big-number-1.0.0.tgz", - "integrity": "sha512-cHUzdT+mMXd1ozht8n5ZwBlNiPO/4zCqqkyp3lF1TMPsRJLXUbQ7cKnfXRkrW475H5SOtSOP0HFeihNbpa53MQ==", - "dev": true - }, "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", @@ -1722,15 +1833,24 @@ "dev": true }, "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1768,6 +1888,22 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, "buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -1780,6 +1916,18 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", "dev": true }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "buffer-writer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", @@ -1792,6 +1940,12 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -1893,9 +2047,9 @@ } }, "chai-datetime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/chai-datetime/-/chai-datetime-1.5.0.tgz", - "integrity": "sha1-N0LxiwJMdbdqK37uKRZiMkRnWWw=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/chai-datetime/-/chai-datetime-1.6.0.tgz", + "integrity": "sha512-/KGUjV0c/dyiBsY+WSMrYRNoOejzHFWTBx2GJ5XoAKtLfVP6qGVEkgmPlNpZpiyZZ/x0evkZ0CZ4O63G5J/psw==", "dev": true, "requires": { "chai": ">1.9.0" @@ -1918,6 +2072,15 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "charm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-1.0.2.tgz", + "integrity": "sha1-it02cVOm2aWBMxBSxAkJkdqZXjU=", + "dev": true, + "requires": { + "inherits": "^2.0.1" + } + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -1925,13 +2088,13 @@ "dev": true }, "cheerio": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", - "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", "dev": true, "requires": { "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", + "dom-serializer": "~0.1.1", "entities": "~1.1.1", "htmlparser2": "^3.9.1", "lodash": "^4.15.0", @@ -1999,54 +2162,87 @@ } }, "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" }, "dependencies": { "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } } } @@ -2066,6 +2262,33 @@ "string-width": "^2.1.1", "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "clone": { @@ -2204,6 +2427,18 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2410,12 +2645,6 @@ "assert-plus": "^1.0.0" } }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true - }, "date-utils": { "version": "1.2.21", "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", @@ -2504,6 +2733,23 @@ } } }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2513,16 +2759,6 @@ "object-keys": "^1.0.12" } }, - "definitelytyped-header-parser": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.9.0.tgz", - "integrity": "sha512-slbwZ5h5lasB12t+9EAGYr060aCMqEXp6cwD7CoTriK40HNDYU56/XQ6S4sbjBK8ReGRMnB/uDx0elKkb4kuQA==", - "dev": true, - "requires": { - "@types/parsimmon": "^1.3.0", - "parsimmon": "^1.2.0" - } - }, "delay": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", @@ -2548,9 +2784,9 @@ "dev": true }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, "deprecation": { @@ -2656,19 +2892,26 @@ "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, "dts-critic": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.0.2.tgz", - "integrity": "sha512-wkRb9FOBXNwpB14nxHbevbyGm42KhA2YGtYsIrfDMvVWDaRMQVYodYtUbuNAH7WUPFMd0ywaqnmzGJl+E6yjsg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.2.3.tgz", + "integrity": "sha512-CErYGgQiloLH0PZ/vrLH5+WgpPbHiOj77qFF+6pGuGtlQzb43oFUSS9Qetr4y9fAg2ZOG9ZvGp7h+jhh0kkbAg==", "dev": true, "requires": { + "@definitelytyped/header-parser": "0.0.34", "command-exists": "^1.2.8", - "definitelytyped-header-parser": "^3.8.2", "rimraf": "^3.0.2", "semver": "^6.2.0", - "typescript": "^3.7.5", + "tmp": "^0.2.1", + "typescript": "^3.9.2", "yargs": "^12.0.5" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -2724,6 +2967,34 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -2757,25 +3028,27 @@ } }, "dtslint": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-3.4.2.tgz", - "integrity": "sha512-qvZN5jI849zG8cjirBzyetK2ZGRa3rO8ExhcirqDlkas251Wt8TlZFKvW8wDGQ++fHLA8omINNxWvPBCb8AXhA==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-3.6.4.tgz", + "integrity": "sha512-D9qwC0N945ge+CENZ1Dtm6I72fc8dAgQpyAde1XoiChR+mk8dC9uzToJNORe7SI0P3dSSULofwjsg7rzQ/iplw==", "dev": true, "requires": { - "definitelytyped-header-parser": "3.9.0", - "dts-critic": "^3.0.2", + "@definitelytyped/header-parser": "0.0.34", + "@definitelytyped/typescript-versions": "0.0.34", + "@definitelytyped/utils": "0.0.34", + "dts-critic": "^3.2.3", "fs-extra": "^6.0.1", "json-stable-stringify": "^1.0.1", "strip-json-comments": "^2.0.1", "tslint": "5.14.0", - "typescript": "^3.9.0-dev.20200425", + "typescript": "^4.0.0-dev.20200523", "yargs": "^15.1.0" }, "dependencies": { "typescript": { - "version": "3.9.0-dev.20200425", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200425.tgz", - "integrity": "sha512-YjP3XoXMSJvza4COs+jXEu1ctdG8+vnieJvv6xJB5iFJIRpzSIF0YBTLn0TOu4oDZe9LyTtjMQkV8k/eemBnpA==", + "version": "4.0.0-dev.20200523", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.0-dev.20200523.tgz", + "integrity": "sha512-BMe4bsXbdGABN84olraexKpJ7bnJlSPhcfCUDLEEeBXtD554FG75Aad5TWOgrmwgXYvlcpLrh9oYA18AO33JmA==", "dev": true } } @@ -2821,9 +3094,9 @@ } }, "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-2.0.0.tgz", + "integrity": "sha512-5YRYHhvhYzV/FC4AiMdeSIg3jAYGq9xFvbhZMpPlJoBsfYgrw2DSCYeXfat6tYBu45PWiyRr3+flaCPPmviPaA==", "dev": true }, "emitter-listener": { @@ -2850,6 +3123,15 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", + "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", + "dev": true, + "requires": { + "ansi-colors": "^3.2.1" + } + }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -3026,6 +3308,20 @@ "taffydb": "2.7.3" }, "dependencies": { + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, "fs-extra": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", @@ -3867,25 +4163,51 @@ "readable-stream": "^2.3.6" } }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { @@ -3910,6 +4232,12 @@ "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", "dev": true }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, "fs-extra": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", @@ -3922,9 +4250,9 @@ } }, "fs-jetpack": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-2.2.3.tgz", - "integrity": "sha512-MldfoKMz2NwpvP3UFfVXLp4NCncy9yxGamgBK6hofFaisnWoGvgkAyTtKwcq++leztgZuM4ywrZEaUtiyVfWgA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-2.4.0.tgz", + "integrity": "sha512-S/o9Dd7K9A7gicVU32eT8G0kHcmSu0rCVdP79P0MWInKFb8XpTc8Syhoo66k9no+HDshtlh4pUJTws8X+8fdFQ==", "dev": true, "requires": { "minimatch": "^3.0.2", @@ -3986,6 +4314,29 @@ "dev": true, "optional": true }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4012,43 +4363,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "generate-function": { @@ -4970,15 +5284,6 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-observable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", - "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", - "dev": true, - "requires": { - "symbol-observable": "^1.1.0" - } - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -5185,6 +5490,12 @@ "requires": { "aggregate-error": "^3.0.0" } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -5273,6 +5584,12 @@ "esprima": "^4.0.0" } }, + "jsbi": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.2.tgz", + "integrity": "sha512-5nDXo1X9QVaXK/Cpb5VECV9ss1QPbjUuk1qSruHB1PK/g39Sd414K4nci99ElFDZv0vzxDEnKn3o49/Tn9Yagw==", + "dev": true + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -5530,19 +5847,20 @@ } }, "lint-staged": { - "version": "10.1.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.7.tgz", - "integrity": "sha512-ZkK8t9Ep/AHuJQKV95izSa+DqotftGnSsNeEmCSqbQ6j4C4H0jDYhEZqVOGD1Q2Oe227igbqjMWycWyYbQtpoA==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.6.tgz", + "integrity": "sha512-2oEBWyPZHkdyjKcIv2U6ay80Q52ZMlZZrUnfsV0WTVcgzPlt3o2t5UFy2v8ETUTsIDZ0xSJVnffWCgD3LF6xTQ==", "dev": true, "requires": { "chalk": "^4.0.0", - "commander": "^5.0.0", + "cli-truncate": "2.1.0", + "commander": "^5.1.0", "cosmiconfig": "^6.0.0", "debug": "^4.1.1", "dedent": "^0.7.0", - "execa": "^4.0.0", - "listr": "^0.14.3", - "log-symbols": "^3.0.0", + "execa": "^4.0.1", + "listr2": "^2.0.2", + "log-symbols": "^4.0.0", "micromatch": "^4.0.2", "normalize-path": "^3.0.0", "please-upgrade-node": "^3.2.0", @@ -5605,9 +5923,9 @@ } }, "execa": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", - "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -5686,168 +6004,89 @@ } } }, - "listr": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", - "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "listr2": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.0.4.tgz", + "integrity": "sha512-oJaAcplPsa72rKW0eg4P4LbEJjhH+UO2I8uqR/I2wzHrVg16ohSfUy0SlcHS21zfYXxtsUpL8YXGHjyfWMR0cg==", "dev": true, "requires": { "@samverschueren/stream-to-observable": "^0.3.0", - "is-observable": "^1.1.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.5.0", - "listr-verbose-renderer": "^0.5.0", - "p-map": "^2.0.0", - "rxjs": "^6.3.3" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true - }, - "listr-update-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", - "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^2.3.0", - "strip-ansi": "^3.0.1" + "chalk": "^4.0.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "elegant-spinner": "^2.0.0", + "enquirer": "^2.3.5", + "figures": "^3.2.0", + "indent-string": "^4.0.0", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "pad": "^3.2.0", + "rxjs": "^6.5.5", + "through": "^2.3.8", + "uuid": "^7.0.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "dev": true, "requires": { - "chalk": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "color-name": "~1.1.4" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true - } - } - }, - "listr-verbose-renderer": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", - "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "cli-cursor": "^2.1.0", - "date-fns": "^1.27.2", - "figures": "^2.0.0" - }, - "dependencies": { - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "has-flag": "^4.0.0" } }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "dev": true } } }, @@ -6036,73 +6275,161 @@ "dev": true }, "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^2.4.2" + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "color-name": "~1.1.4" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" } }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" } }, "wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } } } @@ -6218,26 +6545,26 @@ }, "dependencies": { "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", + "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", "dev": true } } }, "markdownlint": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.19.0.tgz", - "integrity": "sha512-+MsWOnYVUH4klcKM7iRx5cno9FQMDAb6FC6mWlZkeXPwIaK6Z5Vd9VkXkykPidRqmLHU2wI+MNyfUMnUCBw3pQ==", + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.3.tgz", + "integrity": "sha512-J93s59tGvSFvAPWVUtEgxqPI0CHayTx1Z8poj1/4UJAquHGPIruWRMurkRldiNbgBiaQ4OOt15rHZbFfU6u05A==", "dev": true, "requires": { "markdown-it": "10.0.0" } }, "markdownlint-cli": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.22.0.tgz", - "integrity": "sha512-qRg6tK5dXWqkaFvEstz9YSQal1ECMgofrSZgdBOaPWG8cD50pk8Hs0ZpBCJ6SCHPKF71pCdtuSL2u82sIx2XWA==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.1.tgz", + "integrity": "sha512-UARWuPILksAcVLTosUv1F1tLognNYQ/qjLRIgWwQAYqdl3QQrTPurU/X9Z2jrdAJYlOim868QsufxjYJpH0K7Q==", "dev": true, "requires": { "commander": "~2.9.0", @@ -6249,9 +6576,10 @@ "jsonc-parser": "~2.2.0", "lodash.differencewith": "~4.5.0", "lodash.flatten": "~4.4.0", - "markdownlint": "~0.19.0", - "markdownlint-rule-helpers": "~0.7.0", + "markdownlint": "~0.20.3", + "markdownlint-rule-helpers": "~0.10.0", "minimatch": "~3.0.4", + "minimist": "~1.2.5", "rc": "~1.2.7" }, "dependencies": { @@ -6271,23 +6599,23 @@ "dev": true }, "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz", + "integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA==", "dev": true } } }, "markdownlint-rule-helpers": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.7.0.tgz", - "integrity": "sha512-xZByWJNBaCMHo7nYPv/5aO8Jt68YcMvyouFXhuXmJzbqCsQy8rfCj0kYcv22kdK5PwAgMdbHg0hyTdURbUZtJw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.10.0.tgz", + "integrity": "sha512-0e8VUTjNdQwS7hTyNan9oOLsy4a7KEsXo3fxKMDRFRk6Jn+pLB3iKZ3mj/m6ECrlOUCxPYYmgOmmyk3bSdbIvw==", "dev": true }, "marked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.0.0.tgz", - "integrity": "sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.0.tgz", + "integrity": "sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==", "dev": true }, "marked-terminal": { @@ -6493,9 +6821,9 @@ } }, "mocha": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", - "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", + "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -6511,7 +6839,7 @@ "js-yaml": "3.13.1", "log-symbols": "3.0.0", "minimatch": "3.0.4", - "mkdirp": "0.5.3", + "mkdirp": "0.5.5", "ms": "2.1.1", "node-environment-flags": "1.0.6", "object.assign": "4.1.0", @@ -6524,6 +6852,12 @@ "yargs-unparser": "1.6.0" }, "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", @@ -6601,13 +6935,13 @@ "path-exists": "^3.0.0" } }, - "mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "minimist": "^1.2.5" + "chalk": "^2.4.2" } }, "ms": { @@ -6732,14 +7066,14 @@ "dev": true }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz", + "integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==" }, "moment-timezone": { - "version": "0.5.28", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", - "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", "requires": { "moment": ">= 2.9.0" } @@ -6756,11 +7090,12 @@ "dev": true }, "mysql2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-1.7.0.tgz", - "integrity": "sha512-xTWWQPjP5rcrceZQ7CSTKR/4XIDeH/cRkNH/uzvVGQ7W5c7EJ0dXeJUusk7OKhIoHj7uFKUxDVSCfLIl+jluog==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.1.0.tgz", + "integrity": "sha512-9kGVyi930rG2KaHrz3sHwtc6K+GY9d8wWk1XRSYxQiunvGcn4DwuZxOwmK11ftuhhwrYDwGx9Ta4VBwznJn36A==", "dev": true, "requires": { + "cardinal": "^2.1.1", "denque": "^1.4.1", "generate-function": "^2.3.1", "iconv-lite": "^0.5.0", @@ -6828,9 +7163,9 @@ "dev": true }, "needle": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", - "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", + "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", "dev": true, "requires": { "debug": "^3.2.6", @@ -6945,6 +7280,21 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } } } }, @@ -10567,6 +10917,26 @@ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "npm-packlist": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", @@ -10578,6 +10948,40 @@ "npm-normalize-package-bin": "^1.0.1" } }, + "npm-registry-client": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.6.0.tgz", + "integrity": "sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==", + "dev": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -10934,23 +11338,12 @@ } }, "p-props": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-props/-/p-props-3.1.0.tgz", - "integrity": "sha512-N9mPfJfnApIlyCmF6H2KKccOsW9a1um2aIZWh/dU6flpJ5uU3fYDPVYfjOPLTbLVnUn+lcTqtlxxCVylM3mYsw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-props/-/p-props-4.0.0.tgz", + "integrity": "sha512-3iKFbPdoPG7Ne3cMA53JnjPsTMaIzE9gxKZnvKJJivTAeqLEZPBu6zfi6DYq9AsH1nYycWmo3sWCNI8Kz6T2Zg==", "dev": true, "requires": { - "p-map": "^3.0.0" - }, - "dependencies": { - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - } + "p-map": "^4.0.0" } }, "p-reduce": { @@ -11002,6 +11395,15 @@ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", "dev": true }, + "pad": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", + "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", + "dev": true, + "requires": { + "wcwidth": "^1.0.1" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11105,16 +11507,16 @@ "dev": true }, "pg": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.18.2.tgz", - "integrity": "sha512-Mvt0dGYMwvEADNKy5PMQGlzPudKcKKzJds/VbOeZJpb6f/pI3mmoXX0JksPgI3l3JPP/2Apq7F36O63J7mgveA==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.2.1.tgz", + "integrity": "sha512-DKzffhpkWRr9jx7vKxA+ur79KG+SKw+PdjMb1IRhMiKI9zqYUGczwFprqy+5Veh/DCcFs1Y6V8lRLN5I1DlleQ==", "dev": true, "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", - "pg-connection-string": "0.1.3", - "pg-packet-stream": "^1.1.0", - "pg-pool": "^2.0.10", + "pg-connection-string": "^2.2.3", + "pg-pool": "^3.2.1", + "pg-protocol": "^1.2.4", "pg-types": "^2.1.0", "pgpass": "1.x", "semver": "4.3.2" @@ -11129,9 +11531,9 @@ } }, "pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.3.tgz", + "integrity": "sha512-I/KCSQGmOrZx6sMHXkOs2MjddrYcqpza3Dtsy0AjIgBr/bZiPJRK9WhABXN1Uy1UDazRbi9gZEzO2sAhL5EqiQ==", "dev": true }, "pg-hstore": { @@ -11149,16 +11551,16 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", "dev": true }, - "pg-packet-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz", - "integrity": "sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg==", + "pg-pool": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.1.tgz", + "integrity": "sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA==", "dev": true }, - "pg-pool": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.10.tgz", - "integrity": "sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==", + "pg-protocol": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.4.tgz", + "integrity": "sha512-/8L/G+vW/VhWjTGXpGh8XVkXOFx1ZDY+Yuz//Ab8CfjInzFkreI+fDG3WjCeSra7fIZwAFxzbGptNbm8xSXenw==", "dev": true }, "pg-types": { @@ -11603,6 +12005,14 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "require-directory": { @@ -11971,9 +12381,9 @@ "dev": true }, "sequelize-pool": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-3.1.0.tgz", - "integrity": "sha512-zhOVPYVKo61dQgGewyNOgH9mWIjdU6eglCQUenNFpAww/BMlj2Ci8paOKC6Mdt0yJl2HX08H37N4GzqP7Nsmcw==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-5.0.0.tgz", + "integrity": "sha512-T/i80WCCzF3GcTDhc5LCLwahWkUDvmolrqMBtW8Ny5nK+hwhPmt8Tvbsosia9wGpSN/WN1bXmU1ard6+GVWbOA==" }, "set-blocking": { "version": "2.0.0", @@ -12091,6 +12501,12 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -12186,14 +12602,13 @@ "dev": true }, "sqlite3": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", - "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", + "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", "dev": true, "requires": { "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0", - "request": "^2.87.0" + "node-pre-gyp": "^0.11.0" } }, "sqlstring": { @@ -12219,6 +12634,15 @@ "tweetnacl": "~0.14.0" } }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, "stack-chain": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", @@ -12248,13 +12672,25 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + } } }, "string.prototype.trimend": { @@ -12320,12 +12756,12 @@ } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -12394,12 +12830,6 @@ } } }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -12460,37 +12890,67 @@ "dev": true }, "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" } }, "tedious": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tedious/-/tedious-6.0.0.tgz", - "integrity": "sha512-+M+mWg/D0a6DEynpl3JHNUqc3w9blSYGN+f+gs7jUfZsdnVYzcDPDzrKV0rjfaM1P22/bKPZ5Lm/2oDHo6/olQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-8.3.0.tgz", + "integrity": "sha512-v46Q9SRVgz6IolyPdlsxQtfm9q/sqDs+y4aRFK0ET1iKitbpzCCQRHb6rnVcR1FLnLR0Y7AgcqnWUoMPUXz9HA==", "dev": true, "requires": { - "adal-node": "^0.1.22", - "big-number": "1.0.0", - "bl": "^2.2.0", - "depd": "^1.1.2", - "iconv-lite": "^0.4.23", + "@azure/ms-rest-nodeauth": "2.0.2", + "@js-joda/core": "^2.0.0", + "bl": "^3.0.0", + "depd": "^2.0.0", + "iconv-lite": "^0.5.0", + "jsbi": "^3.1.1", "native-duplexpair": "^1.0.0", "punycode": "^2.1.0", - "readable-stream": "^3.1.1", + "readable-stream": "^3.6.0", "sprintf-js": "^1.1.2" }, "dependencies": { + "bl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", + "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "dev": true, + "requires": { + "readable-stream": "^3.0.1" + } + }, + "iconv-lite": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", + "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -12621,6 +13081,12 @@ "is-negated-glob": "^1.0.0" } }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -12747,6 +13213,12 @@ "tslib": "^1.8.1" } }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -12783,6 +13255,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -12793,9 +13271,9 @@ } }, "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", + "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", "dev": true }, "uc.micro": { @@ -12882,9 +13360,9 @@ "dev": true }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", + "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==" }, "v8-compile-cache": { "version": "2.1.0", @@ -12902,6 +13380,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, "validator": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", @@ -12990,6 +13477,15 @@ "vinyl": "^2.0.0" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", @@ -13074,43 +13570,6 @@ "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, "wrappy": { @@ -13147,6 +13606,22 @@ "dev": true, "optional": true }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + }, "xmldom": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", diff --git a/package.json b/package.json index 8e1623fe518f..ced54cbe92d7 100644 --- a/package.json +++ b/package.json @@ -34,30 +34,30 @@ "dottie": "^2.0.0", "inflection": "1.12.0", "lodash": "^4.17.15", - "moment": "^2.24.0", - "moment-timezone": "^0.5.21", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", "retry-as-promised": "^3.2.0", "semver": "^7.3.2", - "sequelize-pool": "^3.1.0", + "sequelize-pool": "^5.0.0", "toposort-class": "^1.0.1", - "uuid": "^3.4.0", + "uuid": "^8.1.0", "validator": "^10.11.0", "wkx": "^0.5.0" }, "devDependencies": { "@commitlint/cli": "^8.3.5", "@commitlint/config-angular": "^8.3.4", - "@types/node": "^12.12.37", + "@types/node": "^12.12.42", "@types/validator": "^10.11.0", - "acorn": "^7.1.1", + "acorn": "^7.2.0", "chai": "^4.x", "chai-as-promised": "^7.x", - "chai-datetime": "^1.x", - "cheerio": "^1.0.0-rc.2", + "chai-datetime": "^1.6.0", + "cheerio": "^1.0.0-rc.3", "cls-hooked": "^4.2.2", "cross-env": "^7.0.2", "delay": "^4.3.0", - "dtslint": "^3.4.2", + "dtslint": "^3.6.4", "env-cmd": "^10.1.0", "esdoc": "^1.1.0", "esdoc-ecmascript-proposal-plugin": "^1.0.0", @@ -66,29 +66,29 @@ "eslint": "^6.8.0", "eslint-plugin-jsdoc": "^20.4.0", "eslint-plugin-mocha": "^6.2.2", - "fs-jetpack": "^2.2.3", + "fs-jetpack": "^2.4.0", "husky": "^4.2.5", "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", - "lint-staged": "^10.1.7", + "lint-staged": "^10.2.6", "mariadb": "^2.3.1", - "markdownlint-cli": "^0.22.0", - "marked": "^1.0.0", - "mocha": "^7.1.1", - "mysql2": "^1.6.5", + "markdownlint-cli": "^0.23.1", + "marked": "^1.1.0", + "mocha": "^7.1.2", + "mysql2": "^2.1.0", "nyc": "^15.0.0", "p-map": "^4.0.0", - "p-props": "^3.1.0", + "p-props": "^4.0.0", "p-timeout": "^3.2.0", - "pg": "^7.8.1", + "pg": "^8.2.1", "pg-hstore": "^2.x", "rimraf": "^3.0.2", "semantic-release": "^17.0.7", "sinon": "^9.0.2", "sinon-chai": "^3.3.0", - "sqlite3": "^4.1.1", - "tedious": "6.0.0", - "typescript": "^3.6.3" + "sqlite3": "^4.2.0", + "tedious": "8.3.0", + "typescript": "^3.9.3" }, "keywords": [ "mysql", diff --git a/scripts/appveyor-setup.ps1 b/scripts/appveyor-setup.ps1 index 412a7ef57823..80093463bcc6 100644 --- a/scripts/appveyor-setup.ps1 +++ b/scripts/appveyor-setup.ps1 @@ -29,6 +29,7 @@ $config = @{ database = "sequelize_test" dialectOptions = @{ options = @{ + encrypt = $false requestTimeout = 25000 cryptoCredentialsDetails = @{ ciphers = "RC4-MD5" diff --git a/test/unit/dialects/mssql/connection-manager.test.js b/test/unit/dialects/mssql/connection-manager.test.js index 726831e594c9..f9ec050586a9 100644 --- a/test/unit/dialects/mssql/connection-manager.test.js +++ b/test/unit/dialects/mssql/connection-manager.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Support = require('../../support'), dialect = Support.getTestDialect(), - tedious = require('tedious'), sinon = require('sinon'); if (dialect === 'mssql') { @@ -29,8 +28,13 @@ if (dialect === 'mssql') { this.config.password, this.config ); - - this.connectionStub = sinon.stub(tedious, 'Connection'); + this.Connection = {}; + const self = this; + this.connectionStub = sinon.stub(this.instance.connectionManager, 'lib').value({ + Connection: function FakeConnection() { + return self.Connection; + } + }); }); afterEach(function() { @@ -38,7 +42,7 @@ if (dialect === 'mssql') { }); it('connectionManager._connect() does not delete `domain` from config.dialectOptions', async function() { - this.connectionStub.returns({ + this.Connection = { STATE: {}, state: '', once(event, cb) { @@ -50,7 +54,7 @@ if (dialect === 'mssql') { }, removeListener: () => {}, on: () => {} - }); + }; expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); await this.instance.dialect.connectionManager._connect(this.config); @@ -58,7 +62,7 @@ if (dialect === 'mssql') { }); it('connectionManager._connect() should reject if end was called and connect was not', async function() { - this.connectionStub.returns({ + this.Connection = { STATE: {}, state: '', once(event, cb) { @@ -70,7 +74,7 @@ if (dialect === 'mssql') { }, removeListener: () => {}, on: () => {} - }); + }; try { await this.instance.dialect.connectionManager._connect(this.config); @@ -83,7 +87,7 @@ if (dialect === 'mssql') { it('connectionManager._connect() should call connect if state is initialized', async function() { const connectStub = sinon.stub(); const INITIALIZED = { name: 'INITIALIZED' }; - this.connectionStub.returns({ + this.Connection = { STATE: { INITIALIZED }, state: INITIALIZED, connect: connectStub, @@ -96,7 +100,7 @@ if (dialect === 'mssql') { }, removeListener: () => {}, on: () => {} - }); + }; await this.instance.dialect.connectionManager._connect(this.config); expect(connectStub.called).to.equal(true); From 8244a34eaa41d40c2170eec6c9b6cf5ff4378e38 Mon Sep 17 00:00:00 2001 From: Simon Schick Date: Sat, 23 May 2020 08:50:24 -0700 Subject: [PATCH 162/414] docs: asyncify (#12297) --- lib/associations/belongs-to-many.js | 13 +- lib/model.js | 6 +- lib/sequelize.js | 37 ++-- lib/transaction.js | 22 +- types/lib/model.d.ts | 29 ++- types/lib/query-interface.d.ts | 15 +- types/lib/sequelize.d.ts | 51 ++--- types/lib/transaction.d.ts | 13 +- types/test/connection.ts | 22 +- types/test/e2e/docs-example.ts | 8 +- types/test/query-interface.ts | 303 ++++++++++++++-------------- types/test/sequelize.ts | 32 ++- types/test/upsert.ts | 14 +- types/test/where.ts | 33 ++- 14 files changed, 284 insertions(+), 314 deletions(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 14ed1015eb76..27f58701b052 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -30,9 +30,8 @@ const Op = require('../operators'); * All methods allow you to pass either a persisted instance, its primary key, or a mixture: * * ```js - * Project.create({ id: 11 }).then(project => { - * user.addProjects([project, 12]); - * }); + * const project = await Project.create({ id: 11 }); + * await user.addProjects([project, 12]); * ``` * * If you want to set several target instances, but with different attributes you have to set the attributes on the instance, using a property with the name of the through model: @@ -46,10 +45,10 @@ const Op = require('../operators'); * * Similarly, when fetching through a join table with custom attributes, these attributes will be available as an object with the name of the through model. * ```js - * user.getProjects().then(projects => { - * let p1 = projects[0] - * p1.UserProjects.started // Is this project started yet? - * }) + * const projects = await user.getProjects(); + * const p1 = projects[0]; + * p1.UserProjects.started // Is this project started yet? + * }) * ``` * * In the API reference below, add the name of the association to the method, e.g. for `User.belongsToMany(Project)` the getter will be `user.getProjects()`. diff --git a/lib/model.js b/lib/model.js index 3efd0ffc10e1..e4dcabc37cf7 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2027,13 +2027,11 @@ class Model { * Find all the rows matching your query, within a specified offset / limit, and get the total number of rows matching your query. This is very useful for paging * * @example - * Model.findAndCountAll({ + * const result = await Model.findAndCountAll({ * where: ..., * limit: 12, * offset: 12 - * }).then(result => { - * ... - * }) + * }); * * # In the above example, `result.rows` will contain rows 13 through 24, while `result.count` will return the total number of rows that matched your query. * diff --git a/lib/sequelize.js b/lib/sequelize.js index 27afcd41b9ef..48d5a5ee6000 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -467,13 +467,9 @@ class Sequelize { * If you are running a type of query where you don't need the metadata, for example a `SELECT` query, you can pass in a query type to make sequelize format the results: * * ```js - * sequelize.query('SELECT...').then(([results, metadata]) => { - * // Raw query - use then plus array spread - * }); + * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring * - * sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }).then(results => { - * // SELECT query - use then - * }) + * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring * ``` * * @param {string} sql @@ -1024,25 +1020,28 @@ class Sequelize { * If you have [CLS](https://github.com/Jeff-Lewis/cls-hooked) enabled, the transaction will automatically be passed to any query that runs within the callback * * @example - * sequelize.transaction().then(transaction => { - * return User.findOne(..., {transaction}) - * .then(user => user.update(..., {transaction})) - * .then(() => transaction.commit()) - * .catch(() => transaction.rollback()); - * }) + * + * try { + * const transaction = await sequelize.transaction(); + * const user = await User.findOne(..., { transaction }); + * await user.update(..., { transaction }); + * await transaction.commit(); + * } catch { + * await transaction.rollback() + * } * * @example * - * sequelize.transaction(transaction => { // Note that we use a callback rather than a promise.then() - * return User.findOne(..., {transaction}) - * .then(user => user.update(..., {transaction})) - * }).then(() => { + * try { + * await sequelize.transaction(transaction => { // Note that we pass a callback rather than awaiting the call with no arguments + * const user = await User.findOne(..., {transaction}); + * await user.update(..., {transaction}); + * }); * // Committed - * }).catch(err => { + * } catch(err) { * // Rolled back * console.error(err); - * }); - * + * } * @example * * const cls = require('cls-hooked'); diff --git a/lib/transaction.js b/lib/transaction.js index dc3f060f22b5..4a24cf537735 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -199,13 +199,14 @@ class Transaction { * Pass in the desired level as the first argument: * * @example - * return sequelize.transaction({type: Sequelize.Transaction.TYPES.EXCLUSIVE}, transaction => { - * // your transactions - * }).then(result => { + * try { + * await sequelize.transaction({ type: Sequelize.Transaction.TYPES.EXCLUSIVE }, transaction => { + * // your transactions + * }); * // transaction has been committed. Do something after the commit if required. - * }).catch(err => { + * } catch(err) { * // do something with the err. - * }); + * } * * @property DEFERRED * @property IMMEDIATE @@ -226,13 +227,14 @@ class Transaction { * Pass in the desired level as the first argument: * * @example - * return sequelize.transaction({isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE}, transaction => { - * // your transactions - * }).then(result => { + * try { + * const result = await sequelize.transaction({isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE}, transaction => { + * // your transactions + * }); * // transaction has been committed. Do something after the commit if required. - * }).catch(err => { + * } catch(err) { * // do something with the err. - * }); + * } * * @property READ_UNCOMMITTED * @property READ_COMMITTED diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index efca8bd72db8..e59363e7ac40 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1830,17 +1830,14 @@ export abstract class Model extends Hooks { * rows matching your query. This is very usefull for paging * * ```js - * Model.findAndCountAll({ + * const { rows, count } = await Model.findAndCountAll({ * where: ..., * limit: 12, * offset: 12 - * }).then(result => { - * ... - * }) + * }); * ``` - * In the above example, `result.rows` will contain rows 13 through 24, while `result.count` will return - * the - * total number of rows that matched your query. + * In the above example, `rows` will contain rows 13 through 24, while `count` will return + * the total number of rows that matched your query. * * When you add includes, only those which are required (either because they have a where clause, or * because @@ -1922,7 +1919,7 @@ export abstract class Model extends Hooks { /** * Find a row that matches the query, or build (but don't save) the row if none is found. - * The successfull result of the promise will be (instance, initialized) - Make sure to use `.then(([...]))` + * The successful result of the promise will be [instance, initialized] - Make sure to use destructuring such as `const [instance, wasBuilt] = ...` */ public static findOrBuild( this: { new(): M } & typeof Model, @@ -1931,7 +1928,7 @@ export abstract class Model extends Hooks { /** * Find a row that matches the query, or build and save the row if none is found - * The successful result of the promise will be (instance, created) - Make sure to use `.then(([...]))` + * The successful result of the promise will be [instance, created] - Make sure to use destructuring such as `const [instance, wasCreated] = ...` * * If no transaction is passed in the `options` object, a new transaction will be created internally, to * prevent the race condition where a matching row is created by another connection after the find but @@ -2446,10 +2443,9 @@ export abstract class Model extends Hooks { * Similarily, when fetching through a join table with custom attributes, these attributes will be * available as an object with the name of the through model. * ```js - * user.getProjects().then(projects => { - * const p1 = projects[0] - * p1.userprojects.started // Is this project started yet? - * }) + * const projects = await user.getProjects(); + * const p1 = projects[0]; + * p1.UserProjects.started // Is this project started yet? * ``` * * @param target The model that will be associated with hasOne relationship @@ -2498,10 +2494,9 @@ export abstract class Model extends Hooks { * Similarily, when fetching through a join table with custom attributes, these attributes will be * available as an object with the name of the through model. * ```js - * user.getProjects().then(projects => { - * const p1 = projects[0] - * p1.userprojects.started // Is this project started yet? - * }) + * const porjects = await user.getProjects(); + * const p1 = projects[0]; + * p1.userprojects.started // Is this project started yet? * ``` * * @param target The model that will be associated with hasOne relationship diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 79801f75f832..13cb915df7b0 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -249,6 +249,19 @@ export interface FunctionParam { direction?: string; } +export interface ColumnDescription { + type: string; + allowNull: boolean; + defaultValue: string; + primaryKey: boolean; + autoIncrement: boolean; + comment: string | null; +} + +export interface ColumnsDescription { + [key: string]: ColumnDescription; +} + /** * The interface that Sequelize uses to talk to all databases. * @@ -352,7 +365,7 @@ export class QueryInterface { public describeTable( tableName: string | { schema?: string; tableName?: string }, options?: string | { schema?: string; schemaDelimiter?: string } & Logging - ): Promise; + ): Promise; /** * Adds a new column to a table diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 22f1c660ce43..14244284bb61 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -22,7 +22,7 @@ import { Hookable, } from './model'; import { ModelManager } from './model-manager'; -import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType } from './query-interface'; +import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface'; import QueryTypes = require('./query-types'); import { Transaction, TransactionOptions } from './transaction'; import { Cast, Col, Fn, Json, Literal, Where } from './utils'; @@ -1177,19 +1177,15 @@ export class Sequelize extends Hooks { * Execute a query on the DB, optionally bypassing all the Sequelize goodness. * * By default, the function will return two arguments: an array of results, and a metadata object, - * containing number of affected rows etc. Use `.then(([...]))` to access the results. + * containing number of affected rows etc. Use `const [results, meta] = await ...` to access the results. * * If you are running a type of query where you don't need the metadata, for example a `SELECT` query, you * can pass in a query type to make sequelize format the results: * * ```js - * sequelize.query('SELECT...').then(([results, metadata]) { - * // Raw query - use spread - * }); + * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring * - * sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }).then(results => { - * // SELECT query - use then - * }) + * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring * ``` * * @param sql @@ -1202,16 +1198,7 @@ export class Sequelize extends Hooks { public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise<{ - [key: string]: { - type: string; - allowNull: boolean; - defaultValue: string; - primaryKey: boolean; - autoIncrement: boolean; - comment: string | null; - } - }>; + public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query( sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithModel @@ -1325,12 +1312,14 @@ export class Sequelize extends Hooks { * in order for the query to happen under that transaction * * ```js - * sequelize.transaction().then(t => { - * return User.findOne(..., { transaction: t}).then(user => { - * return user.update(..., { transaction: t}); - * }) - * .then(t.commit.bind(t)) - * .catch(t.rollback.bind(t)); + * try { + * const transaction = await sequelize.transaction(); + * const user = await User.findOne(..., { transaction }); + * await user.update(..., { transaction }); + * await transaction.commit(); + * } catch(err) { + * await transaction.rollback(); + * } * }) * ``` * @@ -1338,16 +1327,16 @@ export class Sequelize extends Hooks { * supported: * * ```js - * sequelize.transaction(t => { // Note that we use a callback rather than a promise.then() - * return User.findOne(..., { transaction: t}).then(user => { - * return user.update(..., { transaction: t}); + * try { + * await sequelize.transaction(transaction => { // Note that we pass a callback rather than awaiting the call with no arguments + * const user = await User.findOne(..., {transaction}); + * await user.update(..., {transaction}); * }); - * }).then(() => { - * // Commited - * }).catch(err => { + * // Committed + * } catch(err) { * // Rolled back * console.error(err); - * }); + * } * ``` * * If you have [CLS](https://github.com/Jeff-Lewis/cls-hooked) enabled, the transaction diff --git a/types/lib/transaction.d.ts b/types/lib/transaction.d.ts index 5fc40a3c6491..fabbf117dfea 100644 --- a/types/lib/transaction.d.ts +++ b/types/lib/transaction.d.ts @@ -59,15 +59,14 @@ export namespace Transaction { * Pass in the desired level as the first argument: * * ```js - * return sequelize.transaction({isolationLevel: Sequelize.Transaction.SERIALIZABLE}, transaction => { - * - * // your transactions - * - * }).then(result => { + * try { + * await sequelize.transaction({isolationLevel: Sequelize.Transaction.SERIALIZABLE}, transaction => { + * // your transactions + * }); * // transaction has been committed. Do something after the commit if required. - * }).catch(err => { + * } catch(err) { * // do something with the err. - * }); + * } * ``` */ enum ISOLATION_LEVELS { diff --git a/types/test/connection.ts b/types/test/connection.ts index 20d0354ddbb4..3c282068f74a 100644 --- a/types/test/connection.ts +++ b/types/test/connection.ts @@ -7,23 +7,19 @@ sequelize.afterBulkSync((options: SyncOptions) => { console.log('synced'); }); -sequelize +async function test() { + const rows: unknown[] = await sequelize .query('SELECT * FROM `test`', { type: QueryTypes.SELECT, - }) - .then(rows => { - rows.forEach(row => { - console.log(row); - }); }); + const [autoIncrementId, affectedRows] = await sequelize + .query('INSERT into test set test=1', { + type: QueryTypes.INSERT, + }); +} + + -sequelize -.query('INSERT into test set test=1', { - type: QueryTypes.INSERT, -}) -.then(([aiId, affected]) => { - console.log(aiId, affected); -}); sequelize.transaction(async transaction => { const rows = await sequelize diff --git a/types/test/e2e/docs-example.ts b/types/test/e2e/docs-example.ts index 9619ffdcc238..f5abd97dd88e 100644 --- a/types/test/e2e/docs-example.ts +++ b/types/test/e2e/docs-example.ts @@ -167,11 +167,9 @@ const MyDefineModel = sequelize.define('MyDefineModel', { } }); -function stuffTwo() { - MyDefineModel.findByPk(1, { +async function stuffTwo() { + const myModel = await MyDefineModel.findByPk(1, { rejectOnEmpty: true, - }) - .then(myModel => { - console.log(myModel.id); }); + console.log(myModel.id); } diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index 515976410346..3a6387bfbd8c 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -4,72 +4,70 @@ import { QueryInterface } from 'sequelize/lib/query-interface'; declare let queryInterface: QueryInterface; -queryInterface.createTable( - 'nameOfTheNewTable', - { - attr1: DataTypes.STRING, - attr2: DataTypes.INTEGER, - attr3: { - allowNull: false, - defaultValue: false, - type: DataTypes.BOOLEAN, - }, - // foreign key usage - attr4: { - onDelete: 'cascade', - onUpdate: 'cascade', - references: { - key: 'id', - model: 'another_table_name', +async function test() { + await queryInterface.createTable( + 'nameOfTheNewTable', + { + attr1: DataTypes.STRING, + attr2: DataTypes.INTEGER, + attr3: { + allowNull: false, + defaultValue: false, + type: DataTypes.BOOLEAN, + }, + // foreign key usage + attr4: { + onDelete: 'cascade', + onUpdate: 'cascade', + references: { + key: 'id', + model: 'another_table_name', + }, + type: DataTypes.INTEGER, + }, + createdAt: { + type: DataTypes.DATE, + }, + id: { + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER, + }, + updatedAt: { + type: DataTypes.DATE, }, - type: DataTypes.INTEGER, - }, - createdAt: { - type: DataTypes.DATE, - }, - id: { - autoIncrement: true, - primaryKey: true, - type: DataTypes.INTEGER, - }, - updatedAt: { - type: DataTypes.DATE, }, - }, - { - charset: 'latin1', // default: null - collate: 'latin1_general_ci', - engine: 'MYISAM', // default: 'InnoDB' - uniqueKeys: { - test: { - customIndex: true, - fields: ['attr2', 'attr3'], + { + charset: 'latin1', // default: null + collate: 'latin1_general_ci', + engine: 'MYISAM', // default: 'InnoDB' + uniqueKeys: { + test: { + customIndex: true, + fields: ['attr2', 'attr3'], + } } } - } -); + ); -queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.dropTable('nameOfTheExistingTable'); -queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); + await queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); -const bulkInsertRes: Promise = queryInterface.bulkInsert({ tableName: 'foo', as: 'bar', name: 'as' }, [{}], {}); + const bulkInsertRes: Promise = queryInterface.bulkInsert({ tableName: 'foo', as: 'bar', name: 'as' }, [{}], {}); -queryInterface.bulkUpdate({ tableName: 'foo', delimiter: 'bar', as: 'baz', name: 'quz' }, {}, {}); + await queryInterface.bulkUpdate({ tableName: 'foo', delimiter: 'bar', as: 'baz', name: 'quz' }, {}, {}); -queryInterface.dropTrigger({ tableName: 'foo', as: 'bar', name: 'baz' }, 'foo', {}); + await queryInterface.dropTrigger({ tableName: 'foo', as: 'bar', name: 'baz' }, 'foo', {}); -queryInterface.quoteTable({ tableName: 'foo', delimiter: 'bar' }); + await queryInterface.quoteTable({ tableName: 'foo', delimiter: 'bar' }); -queryInterface.dropAllTables(); + await queryInterface.dropAllTables(); -queryInterface.renameTable('Person', 'User'); + await queryInterface.renameTable('Person', 'User'); -queryInterface.showAllTables().then(tableNames => { - // do nothing -}); + const tableNames: string[] = await queryInterface.showAllTables(); -queryInterface.describeTable('Person').then(attributes => { /* attributes will be something like: @@ -86,115 +84,116 @@ queryInterface.describeTable('Person').then(attributes => { } } */ -}); - -queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', DataTypes.STRING); + const attributes: object = await queryInterface.describeTable('Person'); -// or + await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', DataTypes.STRING); -queryInterface.addColumn( - { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, - 'nameOfTheNewAttribute', - DataTypes.STRING -); + // or -// or + await queryInterface.addColumn( + { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, + 'nameOfTheNewAttribute', + DataTypes.STRING + ); -queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', { - allowNull: false, - type: DataTypes.STRING, -}); + // or -queryInterface.removeColumn('Person', 'signature'); - -// or + await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', { + allowNull: false, + type: DataTypes.STRING, + }); -queryInterface.removeColumn({ tableName: 'Person', schema: 'nameOfSchema' }, 'signature'); + await queryInterface.removeColumn('Person', 'signature'); -queryInterface.changeColumn('nameOfAnExistingTable', 'nameOfAnExistingAttribute', { - allowNull: false, - defaultValue: 0.0, - type: DataTypes.FLOAT, -}); + // or -// or + await queryInterface.removeColumn({ tableName: 'Person', schema: 'nameOfSchema' }, 'signature'); -queryInterface.changeColumn( - { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, - 'nameOfAnExistingAttribute', - { + await queryInterface.changeColumn('nameOfAnExistingTable', 'nameOfAnExistingAttribute', { allowNull: false, defaultValue: 0.0, type: DataTypes.FLOAT, - } -); - -queryInterface.renameColumn('Person', 'signature', 'sig'); - -// This example will create the index person_firstname_lastname -queryInterface.addIndex('Person', ['firstname', 'lastname']); - -// This example will create a unique index with the name SuperDuperIndex using the optional 'options' field. -// Possible options: -// - indexName: The name of the index. Default is __ -// - parser: For FULLTEXT columns set your parser -// - indexType: Set a type for the index, e.g. BTREE. See the documentation of the used dialect -// - logging: A function that receives the sql query, e.g. console.log -queryInterface.addIndex('Person', ['firstname', 'lastname'], { - name: 'SuperDuperIndex', - type: 'UNIQUE', -}); - -queryInterface.addIndex('Foo', { - name: 'foo_a', - fields: [ - { name: 'foo_b', order: 'DESC' }, - 'foo_c', - { name: 'foo_d', order: 'ASC', collate: 'foobar', length: 42 } - ], -}); - -queryInterface.addIndex('Foo', { - name: 'foo_b_lower', - fields: [ - fn('lower', col('foo_b')) - ], -}); - -queryInterface.addIndex('Foo', { - name: 'foo_c_lower', - fields: [ - literal('LOWER(foo_c)') - ] -}) - -queryInterface.removeIndex('Person', 'SuperDuperIndex'); - -// or - -queryInterface.removeIndex('Person', ['firstname', 'lastname']); - -queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', { - name: 'firstnamexlastname', - fields: ['firstname', 'lastname'], - type: 'unique', - transaction: trx, -})) - -queryInterface.removeConstraint('Person', 'firstnamexlastname'); - -queryInterface.select(null, 'Person', { - where: { - a: 1, - }, -}); - -queryInterface.delete(null, 'Person', { - where: { - a: 1, - }, -}); - -queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, Model, {}); - -queryInterface.insert(null, 'test', {}); + }); + + // or + + await queryInterface.changeColumn( + { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, + 'nameOfAnExistingAttribute', + { + allowNull: false, + defaultValue: 0.0, + type: DataTypes.FLOAT, + } + ); + + await queryInterface.renameColumn('Person', 'signature', 'sig'); + + // This example will create the index person_firstname_lastname + await queryInterface.addIndex('Person', ['firstname', 'lastname']); + + // This example will create a unique index with the name SuperDuperIndex using the optional 'options' field. + // Possible options: + // - indexName: The name of the index. Default is __ + // - parser: For FULLTEXT columns set your parser + // - indexType: Set a type for the index, e.g. BTREE. See the documentation of the used dialect + // - logging: A function that receives the sql query, e.g. console.log + await queryInterface.addIndex('Person', ['firstname', 'lastname'], { + name: 'SuperDuperIndex', + type: 'UNIQUE', + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_a', + fields: [ + { name: 'foo_b', order: 'DESC' }, + 'foo_c', + { name: 'foo_d', order: 'ASC', collate: 'foobar', length: 42 } + ], + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_b_lower', + fields: [ + fn('lower', col('foo_b')) + ], + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_c_lower', + fields: [ + literal('LOWER(foo_c)') + ] + }) + + await queryInterface.removeIndex('Person', 'SuperDuperIndex'); + + // or + + await queryInterface.removeIndex('Person', ['firstname', 'lastname']); + + await queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', { + name: 'firstnamexlastname', + fields: ['firstname', 'lastname'], + type: 'unique', + transaction: trx, + })) + + await queryInterface.removeConstraint('Person', 'firstnamexlastname'); + + await queryInterface.select(null, 'Person', { + where: { + a: 1, + }, + }); + + await queryInterface.delete(null, 'Person', { + where: { + a: 1, + }, + }); + + await queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, Model, {}); + + await queryInterface.insert(null, 'test', {}); +} diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts index d17b68c837dc..0ae09acaf8a7 100644 --- a/types/test/sequelize.ts +++ b/types/test/sequelize.ts @@ -60,25 +60,17 @@ const myModel: typeof Model1 = sequelize.models.asd; myModel.hasOne(Model2) myModel.findAll(); -sequelize.query('SELECT * FROM `user`', { type: QueryTypes.RAW }).then(result => { - const data = result[0]; - const arraysOnly = (a: any[]) => a; - arraysOnly(data); -}); +async function test() { + const [results, meta]: [unknown[], unknown] = await sequelize.query('SELECT * FROM `user`', { type: QueryTypes.RAW }); -sequelize - .query<{ count: number }>("SELECT COUNT(1) as count FROM `user`", { - type: QueryTypes.SELECT, - plain: true - }) - .then(result => { - result.count.toExponential(); // is a number! - }); + const res2: { count: number } = await sequelize + .query<{ count: number }>("SELECT COUNT(1) as count FROM `user`", { + type: QueryTypes.SELECT, + plain: true + }); -sequelize - .query("SELECT COUNT(1) as count FROM `user`", { - plain: true - }) - .then(result => { - console.log(result.count); - }); + const res3: { [key: string]: unknown; } = await sequelize + .query("SELECT COUNT(1) as count FROM `user`", { + plain: true + }) +} diff --git a/types/test/upsert.ts b/types/test/upsert.ts index efc6f81b6a06..cd1845a7a92a 100644 --- a/types/test/upsert.ts +++ b/types/test/upsert.ts @@ -6,8 +6,8 @@ class TestModel extends Model { TestModel.init({}, {sequelize}) -sequelize.transaction(trx => { - TestModel.upsert({}, { +sequelize.transaction(async trx => { + const res: [TestModel, boolean] = await TestModel.upsert({}, { benchmark: true, fields: ['testField'], hooks: true, @@ -16,9 +16,9 @@ sequelize.transaction(trx => { searchPath: 'DEFAULT', transaction: trx, validate: true, - }).then((res: [ TestModel, boolean ]) => {}); + }); - TestModel.upsert({}, { + let created: boolean = await TestModel.upsert({}, { benchmark: true, fields: ['testField'], hooks: true, @@ -27,9 +27,9 @@ sequelize.transaction(trx => { searchPath: 'DEFAULT', transaction: trx, validate: true, - }).then((created: boolean) => {}); + }); - return TestModel.upsert({}, { + created = await TestModel.upsert({}, { benchmark: true, fields: ['testField'], hooks: true, @@ -37,5 +37,5 @@ sequelize.transaction(trx => { searchPath: 'DEFAULT', transaction: trx, validate: true, - }).then((created: boolean) => {}); + }); }) diff --git a/types/test/where.ts b/types/test/where.ts index 1c33e9d9b2d5..16f904ba60a2 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -164,31 +164,22 @@ MyModel.update({ hi: 1 }, { where }); // From https://sequelize.org/master/en/v4/docs/models-usage/ -// find multiple entries -MyModel.findAll().then(projects => { - // projects will be an array of all MyModel instances -}); +async function test() { + // find multiple entries + let projects: MyModel[] = await MyModel.findAll(); -// search for specific attributes - hash usage -MyModel.findAll({ where: { name: 'A MyModel', enabled: true } }).then(projects => { - // projects will be an array of MyModel instances with the specified name -}); + // search for specific attributes - hash usage + projects = await MyModel.findAll({ where: { name: 'A MyModel', enabled: true } }) -// search within a specific range -MyModel.findAll({ where: { id: [1, 2, 3] } }).then(projects => { - // projects will be an array of MyModels having the id 1, 2 or 3 - // this is actually doing an IN query -}); + // search within a specific range + projects = await MyModel.findAll({ where: { id: [1, 2, 3] } }); -// locks -MyModel.findAll({ lock: Transaction.LOCK.KEY_SHARE }).then(projects => { - // noop -}); + // locks + projects = await MyModel.findAll({ lock: Transaction.LOCK.KEY_SHARE }); -// locks on model -MyModel.findAll({ lock: { level: Transaction.LOCK.KEY_SHARE, of: MyModel} }).then(projects => { - // noop -}); + // locks on model + projects = await MyModel.findAll({ lock: { level: Transaction.LOCK.KEY_SHARE, of: MyModel} }); +} MyModel.findAll({ where: { From 4d9165b6cc11517a023e10eb30e27094dc0f46e1 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 24 May 2020 11:26:21 +0530 Subject: [PATCH 163/414] feat(postgres): native upsert (#12301) --- lib/dialects/abstract/index.js | 1 - lib/dialects/abstract/query-generator.js | 42 ++-- lib/dialects/abstract/query-interface.js | 84 +++----- lib/dialects/mariadb/query.js | 7 +- lib/dialects/mssql/query-interface.js | 42 +++- lib/dialects/mssql/query.js | 6 +- lib/dialects/mysql/query-generator.js | 11 - lib/dialects/mysql/query-interface.js | 22 +- lib/dialects/mysql/query.js | 5 +- lib/dialects/postgres/query-generator.js | 20 -- lib/dialects/postgres/query-interface.js | 7 - lib/dialects/postgres/query.js | 12 +- lib/dialects/sqlite/query-generator.js | 15 -- lib/dialects/sqlite/query.js | 7 +- lib/model.js | 29 ++- test/config/config.js | 1 + test/integration/model/upsert.test.js | 198 ++++++++++-------- .../dialects/postgres/query-generator.test.js | 23 -- types/lib/model.d.ts | 26 +-- types/test/upsert.ts | 6 +- 20 files changed, 260 insertions(+), 304 deletions(-) diff --git a/lib/dialects/abstract/index.js b/lib/dialects/abstract/index.js index 3dbdc4666844..57d681fac84b 100644 --- a/lib/dialects/abstract/index.js +++ b/lib/dialects/abstract/index.js @@ -7,7 +7,6 @@ AbstractDialect.prototype.supports = { 'DEFAULT VALUES': false, 'VALUES ()': false, 'LIMIT ON UPDATE': false, - 'ON DUPLICATE KEY': true, 'ORDER NULLS': false, 'UNION': true, 'UNION ALL': true, diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 8b603fab6ab6..9900d8f3b4db 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -3,7 +3,6 @@ const util = require('util'); const _ = require('lodash'); const uuidv4 = require('uuid').v4; -const semver = require('semver'); const Utils = require('../../utils'); const deprecations = require('../../utils/deprecations'); @@ -179,6 +178,20 @@ class QueryGenerator { } } + let onDuplicateKeyUpdate = ''; + + if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) { + if (this._dialect.supports.inserts.updateOnDuplicate == ' ON CONFLICT DO UPDATE SET') { // postgres / sqlite + // If no conflict target columns were specified, use the primary key names from options.upsertKeys + const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr)); + const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`); + onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`; + } else { + const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`); + onDuplicateKeyUpdate += `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`; + } + } + const replacements = { ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : '', onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : '', @@ -188,8 +201,8 @@ class QueryGenerator { tmpTable }; - valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${replacements.onConflictDoNothing}${valueQuery}`; - emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${replacements.onConflictDoNothing}${emptyQuery}`; + valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${valueQuery}`; + emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${emptyQuery}`; // Mostly for internal use, so we expect the user to know what he's doing! // pg_temp functions are private per connection, so we never risk this function interfering with another one. @@ -200,31 +213,16 @@ class QueryGenerator { returningModelAttributes.push('*'); } - if (semver.gte(this.sequelize.options.databaseVersion, '9.2.0')) { - // >= 9.2 - Use a UUID but prefix with 'func_' (numbers first not allowed) - const delimiter = `$func_${uuidv4().replace(/-/g, '')}$`; - const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(', (testfunc.response).')}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`; + const delimiter = `$func_${uuidv4().replace(/-/g, '')}$`; + const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(', (testfunc.response).')}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`; - options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; - valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter - } BEGIN ${valueQuery} RETURNING * INTO response; EXCEPTION ${options.exception} END ${delimiter} LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`; - } else { - const selectQuery = `SELECT ${returningModelAttributes.join(', ')} FROM pg_temp.testfunc();`; - - options.exception = 'WHEN unique_violation THEN NULL;'; - valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc() RETURNS SETOF ${quotedTable} AS $body$ BEGIN RETURN QUERY ${valueQuery - } RETURNING *; EXCEPTION ${options.exception} END; $body$ LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`; - } + options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; + valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter} BEGIN ${valueQuery} RETURNING * INTO response; EXCEPTION ${options.exception} END ${delimiter} LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`; } else { valueQuery += returningFragment; emptyQuery += returningFragment; } - if (this._dialect.supports['ON DUPLICATE KEY'] && options.onDuplicate) { - valueQuery += ` ON DUPLICATE KEY ${options.onDuplicate}`; - emptyQuery += ` ON DUPLICATE KEY ${options.onDuplicate}`; - } - query = `${replacements.attributes.length ? valueQuery : emptyQuery};`; if (identityWrapperRequired && this._dialect.supports.autoIncrement.identityInsert) { query = `SET IDENTITY_INSERT ${quotedTable} ON; ${query} SET IDENTITY_INSERT ${quotedTable} OFF;`; diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 0124662c490f..2b156b06d8f6 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -6,7 +6,6 @@ const Utils = require('../../utils'); const DataTypes = require('../../data-types'); const Transaction = require('../../transaction'); const QueryTypes = require('../../query-types'); -const Op = require('../../operators'); /** * The interface that Sequelize uses to talk to all databases @@ -744,72 +743,51 @@ class QueryInterface { * @param {string} tableName table to upsert on * @param {object} insertValues values to be inserted, mapped to field name * @param {object} updateValues values to be updated, mapped to field name - * @param {object} where various conditions - * @param {Model} model Model to upsert on + * @param {object} where where conditions, which can be used for UPDATE part when INSERT fails * @param {object} options query options * * @returns {Promise} Resolves an array with */ - async upsert(tableName, insertValues, updateValues, where, model, options) { - const wheres = []; - const attributes = Object.keys(insertValues); - let indexes = []; - let indexFields; - + async upsert(tableName, insertValues, updateValues, where, options) { options = { ...options }; - if (!Utils.isWhereEmpty(where)) { - wheres.push(where); - } + const model = options.model; + const primaryKeys = Object.values(model.primaryKeys).map(item => item.field); + const uniqueKeys = Object.values(model.uniqueKeys).filter(c => c.fields.length >= 1).map(c => c.fields); + const indexKeys = Object.values(model._indexes).filter(c => c.unique && c.fields.length >= 1).map(c => c.fields); - // Lets combine unique keys and indexes into one - indexes = _.map(model.uniqueKeys, value => { - return value.fields; - }); - - model._indexes.forEach(value => { - if (value.unique) { - // fields in the index may both the strings or objects with an attribute property - lets sanitize that - indexFields = value.fields.map(field => { - if (_.isPlainObject(field)) { - return field.attribute; - } - return field; - }); - indexes.push(indexFields); + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + options.upsertKeys = []; + + // For fields in updateValues, try to find a constraint or unique index + // that includes given field. Only first matching upsert key is used. + for (const field of options.updateOnDuplicate) { + const uniqueKey = uniqueKeys.find(fields => fields.includes(field)); + if (uniqueKey) { + options.upsertKeys = uniqueKey; + break; } - }); - for (const index of indexes) { - if (_.intersection(attributes, index).length === index.length) { - where = {}; - for (const field of index) { - where[field] = insertValues[field]; - } - wheres.push(where); + const indexKey = indexKeys.find(fields => fields.includes(field)); + if (indexKey) { + options.upsertKeys = indexKey; + break; } } - where = { [Op.or]: wheres }; - - options.type = QueryTypes.UPSERT; - options.raw = true; + // Always use PK, if no constraint available OR update data contains PK + if ( + options.upsertKeys.length === 0 + || _.intersection(options.updateOnDuplicate, primaryKeys).length + ) { + options.upsertKeys = primaryKeys; + } - const sql = this.queryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); - const result = await this.sequelize.query(sql, options); - return this._convertUpsertResult(result, model); - } + options.upsertKeys = _.uniq(options.upsertKeys); - /** - * Converts raw upsert result to API contract. - * - * @param {object} result - * @param {Model} model - * @protected - */ - // eslint-disable-next-line no-unused-vars - _convertUpsertResult(result, model) { - return [result, undefined]; + const sql = this.queryGenerator.insertQuery(tableName, insertValues, model.rawAttributes, options); + return await this.sequelize.query(sql, options); } /** diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index 35c2993d8c1b..8e2fad0815dd 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -92,10 +92,12 @@ class Query extends AbstractQuery { formatResults(data) { let result = this.instance; - if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() - || this.isUpsertQuery()) { + if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery()) { return data.affectedRows; } + if (this.isUpsertQuery()) { + return [null, data.affectedRows === 1]; + } if (this.isInsertQuery(data)) { this.handleInsertQuery(data); @@ -116,6 +118,7 @@ class Query extends AbstractQuery { } return [result, data.affectedRows]; } + return [data[this.getInsertIdField()], data.affectedRows]; } } diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index 3c5eee411ff9..3d91a9796dc3 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -1,5 +1,10 @@ 'use strict'; +const _ = require('lodash'); + +const Utils = require('../../utils'); +const QueryTypes = require('../../query-types'); +const Op = require('../../operators'); const { QueryInterface } = require('../abstract/query-interface'); /** @@ -42,11 +47,38 @@ class MSSqlQueryInterface extends QueryInterface { /** * @override */ - _convertUpsertResult(result, model) { - return [ - result.$action === 'INSERT', - result[model.primaryKeyField] - ]; + async upsert(tableName, insertValues, updateValues, where, options) { + const model = options.model; + const wheres = []; + + options = { ...options }; + + if (!Utils.isWhereEmpty(where)) { + wheres.push(where); + } + + // Lets combine unique keys and indexes into one + let indexes = Object.values(model.uniqueKeys).map(item => item.fields); + indexes = indexes.concat(Object.values(model._indexes).filter(item => item.unique).map(item => item.fields)); + + const attributes = Object.keys(insertValues); + for (const index of indexes) { + if (_.intersection(attributes, index).length === index.length) { + where = {}; + for (const field of index) { + where[field] = insertValues[field]; + } + wheres.push(where); + } + } + + where = { [Op.or]: wheres }; + + options.type = QueryTypes.UPSERT; + options.raw = true; + + const sql = this.queryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); + return await this.sequelize.query(sql, options); } } diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index ea88cacfb6dc..2d4f6dd6b279 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -206,9 +206,6 @@ class Query extends AbstractQuery { if (this.isShowIndexesQuery()) { return this.handleShowIndexesQuery(data); } - if (this.isUpsertQuery()) { - return data[0]; - } if (this.isCallQuery()) { return data[0]; } @@ -224,6 +221,9 @@ class Query extends AbstractQuery { if (this.isForeignKeysQuery()) { return data; } + if (this.isUpsertQuery()) { + return [result, data[0].$action === 'INSERT']; + } if (this.isInsertQuery() || this.isUpdateQuery()) { return [result, rowCount]; } diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index fe8f02cb5703..06d7e3c1b51b 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -293,17 +293,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { return value; } - upsertQuery(tableName, insertValues, updateValues, where, model, options) { - options.onDuplicate = 'UPDATE '; - - options.onDuplicate += Object.keys(updateValues).map(key => { - key = this.quoteIdentifier(key); - return `${key}=VALUES(${key})`; - }).join(', '); - - return this.insertQuery(tableName, insertValues, model.rawAttributes, options); - } - truncateTableQuery(tableName) { return `TRUNCATE ${this.quoteTable(tableName)}`; } diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 5e2bcc922de0..bcdec9d2e5a7 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -2,6 +2,7 @@ const sequelizeErrors = require('../../errors'); const { QueryInterface } = require('../abstract/query-interface'); +const QueryTypes = require('../../query-types'); /** * The interface that Sequelize uses to talk with MySQL/MariaDB database @@ -37,6 +38,20 @@ class MySQLQueryInterface extends QueryInterface { ); } + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + options = { ...options }; + + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + + const model = options.model; + const sql = this.queryGenerator.insertQuery(tableName, insertValues, model.rawAttributes, options); + return await this.sequelize.query(sql, options); + } + /** * @override */ @@ -69,13 +84,6 @@ class MySQLQueryInterface extends QueryInterface { return await this.sequelize.query(query, options); } - - /** - * @override - */ - _convertUpsertResult(result) { - return [result === 1, undefined]; - } } exports.MySQLQueryInterface = MySQLQueryInterface; diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index 7e8b73aff708..208c7067df94 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -138,7 +138,7 @@ class Query extends AbstractQuery { if (this.isCallQuery()) { return data[0]; } - if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) { + if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery()) { return data.affectedRows; } if (this.isVersionQuery()) { @@ -147,6 +147,9 @@ class Query extends AbstractQuery { if (this.isForeignKeysQuery()) { return data; } + if (this.isUpsertQuery()) { + return [result, data.affectedRows === 1]; + } if (this.isInsertQuery() || this.isUpdateQuery()) { return [result, data.affectedRows]; } diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 18678c740344..d08e01abbd07 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -343,26 +343,6 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return this.fn(fnName, tableName, parameters, body, returns, language); } - upsertQuery(tableName, insertValues, updateValues, where, model, options) { - const primaryField = this.quoteIdentifier(model.primaryKeyField); - - const upsertOptions = { ...options, bindParam: false, returning: ['*'] }; - const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); - const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); - const returningRegex = /RETURNING \*(?![\s\S]*RETURNING \*)/; - - insert.query = insert.query.replace(returningRegex, `RETURNING ${primaryField} INTO primary_key`); - update.query = update.query.replace(returningRegex, `RETURNING ${primaryField} INTO primary_key`); - - return this.exceptionFn( - 'sequelize_upsert', - tableName, - 'OUT created boolean, OUT primary_key text', - `${insert.query} created := true;`, - `${update.query}; created := false` - ); - } - truncateTableQuery(tableName, options = {}) { return [ `TRUNCATE ${this.quoteTable(tableName)}`, diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 1beb1068bf6d..5aa0c0fde84d 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -241,13 +241,6 @@ class PostgresQueryInterface extends QueryInterface { await Promise.all(promises); } - - /** - * @override - */ - _convertUpsertResult(result) { - return [result.created, result.primary_key]; - } } exports.PostgresQueryInterface = PostgresQueryInterface; diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 50feaaadf195..acb5732a8246 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -262,10 +262,7 @@ class Query extends AbstractQuery { if (QueryTypes.BULKDELETE === this.options.type) { return parseInt(rowCount, 10); } - if (this.isUpsertQuery()) { - return rows[0]; - } - if (this.isInsertQuery() || this.isUpdateQuery()) { + if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) { if (this.instance && this.instance.dataValues) { for (const key in rows[0]) { if (Object.prototype.hasOwnProperty.call(rows[0], key)) { @@ -278,6 +275,13 @@ class Query extends AbstractQuery { } } + if (this.isUpsertQuery()) { + return [ + this.instance, + null + ]; + } + return [ this.instance || rows && (this.options.plain && rows[0] || rows) || undefined, rowCount diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 0a78f9807034..9d9691fe700e 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -179,21 +179,6 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { return 'SELECT name FROM `sqlite_master` WHERE type=\'table\' and name!=\'sqlite_sequence\';'; } - upsertQuery(tableName, insertValues, updateValues, where, model, options) { - options.ignoreDuplicates = true; - - const bind = []; - const bindParam = this.bindParam(bind); - - const upsertOptions = { ...options, bindParam }; - const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); - const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); - - const query = `${insert.query} ${update.query}`; - - return { query, bind }; - } - updateQuery(tableName, attrValueHash, where, options, attributes) { options = options || {}; _.defaults(options, this.options); diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 685509a0f34a..cfb5de7ea6c7 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -201,15 +201,15 @@ class Query extends AbstractQuery { if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].includes(this.options.type)) { return metaData.changes; } - if (this.options.type === QueryTypes.UPSERT) { - return undefined; - } if (this.options.type === QueryTypes.VERSION) { return results[0].version; } if (this.options.type === QueryTypes.RAW) { return [results, metaData]; } + if (this.isUpsertQuery()) { + return [result, null]; + } if (this.isUpdateQuery() || this.isInsertQuery()) { return [result, metaData.changes]; } @@ -229,7 +229,6 @@ class Query extends AbstractQuery { } else { complete = this._logQuery(sql, debug, parameters); } - return new Promise((resolve, reject) => conn.serialize(async () => { const columnTypes = {}; diff --git a/lib/model.js b/lib/model.js index e4dcabc37cf7..92d287edbfff 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2395,40 +2395,42 @@ class Model { * * **Implementation details:** * - * * MySQL - Implemented as a single query `INSERT values ON DUPLICATE KEY UPDATE values` - * * PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN unique_constraint UPDATE - * * SQLite - Implemented as two queries `INSERT; UPDATE`. This means that the update is executed regardless of whether the row already existed or not + * * MySQL - Implemented with ON DUPLICATE KEY UPDATE` + * * PostgreSQL - Implemented with ON CONFLICT DO UPDATE. If update data contains PK field, then PK is selected as the default conflict key. Otherwise first unique constraint/index will be selected, which can satisfy conflict key requirements. + * * SQLite - Implemented with ON CONFLICT DO UPDATE * * MSSQL - Implemented as a single query using `MERGE` and `WHEN (NOT) MATCHED THEN` - * **Note** that SQLite returns undefined for created, no matter if the row was created or updated. This is because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know whether the row was inserted or not. + * + * **Note** that Postgres/SQLite returns null for created, no matter if the row was created or updated * * @param {object} values hash of values to upsert * @param {object} [options] upsert options * @param {boolean} [options.validate=true] Run validations before the row is inserted * @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all changed fields * @param {boolean} [options.hooks=true] Run before / after upsert hooks? - * @param {boolean} [options.returning=false] If true, fetches back auto generated values (Postgres only) + * @param {boolean} [options.returning=true] If true, fetches back auto generated values * @param {Transaction} [options.transaction] Transaction to run query under * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * - * @returns {Promise} Returns a boolean indicating whether the row was created or updated. For MySQL/MariaDB, it returns `true` when inserted and `false` when updated. For Postgres/MSSQL with `options.returning` true, it returns record and created boolean with signature ``. + * @returns {Promise} returns record and whether row was created or updated as boolean. For Postgres/SQLite dialects boolean value is always null`. */ static async upsert(values, options) { options = { hooks: true, - returning: false, + returning: true, validate: true, ...Utils.cloneDeep(options) }; - options.model = this; - const createdAtAttr = this._timestampAttributes.createdAt; const updatedAtAttr = this._timestampAttributes.updatedAt; const hasPrimary = this.primaryKeyField in values || this.primaryKeyAttribute in values; const instance = this.build(values); + options.model = this; + options.instance = instance; + const changed = Array.from(instance._changed); if (!options.fields) { options.fields = changed; @@ -2463,14 +2465,7 @@ class Model { if (options.hooks) { await this.runHooks('beforeUpsert', values, options); } - const [created, primaryKey] = await this.queryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); - let result; - if (options.returning === true && primaryKey) { - const record = await this.findByPk(primaryKey, options); - result = [record, created]; - } else { - result = created; - } + const result = await this.queryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), options); if (options.hooks) { await this.runHooks('afterUpsert', result, options); diff --git a/test/config/config.js b/test/config/config.js index 0ad16363b8f5..181206e12c5d 100644 --- a/test/config/config.js +++ b/test/config/config.js @@ -31,6 +31,7 @@ module.exports = { port: env.SEQ_MSSQL_PORT || env.SEQ_PORT || 1433, dialectOptions: { options: { + encrypt: false, requestTimeout: 60000 } }, diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index fbbe6a8d8e89..2f22c0dee1f9 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -60,19 +60,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.upserts) { describe('upsert', () => { it('works with upsert on id', async function() { - const created0 = await this.User.upsert({ id: 42, username: 'john' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await this.User.upsert({ id: 42, username: 'john' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { - expect(created0).to.be.ok; + expect(created0).to.be.true; } this.clock.tick(1000); - const created = await this.User.upsert({ id: 42, username: 'doe' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.User.upsert({ id: 42, username: 'doe' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user = await this.User.findByPk(42); @@ -82,19 +82,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('works with upsert on a composite key', async function() { - const created0 = await this.User.upsert({ foo: 'baz', bar: 19, username: 'john' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await this.User.upsert({ foo: 'baz', bar: 19, username: 'john' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { - expect(created0).to.be.ok; + expect(created0).to.be.true; } this.clock.tick(1000); - const created = await this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user = await this.User.findOne({ where: { foo: 'baz', bar: 19 } }); @@ -112,14 +112,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4 }, - name: { type: Sequelize.STRING } }); await User.sync({ force: true }); - await User.upsert({ name: 'John Doe' }); }); @@ -144,21 +142,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.upsert({ a: 'a', b: 'a', username: 'curt' }) ]); - if (dialect === 'sqlite') { - expect(created1).to.be.undefined; - expect(created2).to.be.undefined; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created1[1]).to.be.null; + expect(created2[1]).to.be.null; } else { - expect(created1).to.be.ok; - expect(created2).to.be.ok; + expect(created1[1]).to.be.true; + expect(created2[1]).to.be.true; } this.clock.tick(1000); // Update the first one - const created = await User.upsert({ a: 'a', b: 'b', username: 'doe' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await User.upsert({ a: 'a', b: 'b', username: 'doe' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user1 = await User.findOne({ where: { a: 'a', b: 'b' } }); @@ -199,28 +197,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { const options = { validate: false }; await User.sync({ force: true }); - const created = await User.upsert({ id: 1, email: 'notanemail' }, options); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await User.upsert({ id: 1, email: 'notanemail' }, options); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).to.be.ok; + expect(created).to.be.true; } }); it('works with BLOBs', async function() { - const created0 = await this.User.upsert({ id: 42, username: 'john', blob: Buffer.from('kaj') }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await this.User.upsert({ id: 42, username: 'john', blob: Buffer.from('kaj') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } this.clock.tick(1000); - const created = await this.User.upsert({ id: 42, username: 'doe', blob: Buffer.from('andrea') }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.User.upsert({ id: 42, username: 'doe', blob: Buffer.from('andrea') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user = await this.User.findByPk(42); @@ -231,18 +229,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('works with .field', async function() { - const created0 = await this.User.upsert({ id: 42, baz: 'foo' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await this.User.upsert({ id: 42, baz: 'foo' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } - const created = await this.User.upsert({ id: 42, baz: 'oof' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.User.upsert({ id: 42, baz: 'oof' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user = await this.User.findByPk(42); @@ -250,19 +248,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('works with primary key using .field', async function() { - const created0 = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'first' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'first' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } this.clock.tick(1000); - const created = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'second' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'second' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const instance = await this.ModelWithFieldPK.findOne({ where: { userId: 42 } }); @@ -270,19 +268,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); it('works with database functions', async function() { - const created0 = await this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1') }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } this.clock.tick(1000); - const created = await this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user = await this.User.findByPk(42); expect(user.createdAt).to.be.ok; @@ -322,9 +320,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { it('does not update when setting current values', async function() { await this.User.create({ id: 42, username: 'john' }); const user = await this.User.findByPk(42); - const created = await this.User.upsert({ id: user.id, username: user.username }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await this.User.upsert({ id: user.id, username: user.username }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { // After set node-mysql flags = '-FOUND_ROWS' / foundRows=false // result from upsert should be false when upsert a row to its current value @@ -349,18 +347,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); const clock = sinon.useFakeTimers(); await User.sync({ force: true }); - const created0 = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } clock.tick(1000); - const created = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } clock.tick(1000); const user = await User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); @@ -385,17 +383,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); await User.sync({ force: true }); - const created0 = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } - const created = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { - expect(created).not.to.be.ok; + expect(created).to.be.false; } const user = await User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); expect(user.createdAt).to.be.ok; @@ -415,15 +413,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); await User.sync({ force: true }); - const created0 = await User.upsert({ name: 'user1', address: 'address', city: 'City' }); - if (dialect === 'sqlite') { - expect(created0).to.be.undefined; + const [, created0] = await User.upsert({ name: 'user1', address: 'address', city: 'City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; } else { expect(created0).to.be.ok; } - const created = await User.upsert({ name: 'user1', address: 'address', city: 'New City' }); - if (dialect === 'sqlite') { - expect(created).to.be.undefined; + const [, created] = await User.upsert({ name: 'user1', address: 'address', city: 'New City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; } else { expect(created).not.to.be.ok; } @@ -498,12 +496,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { const [user0, created0] = await this.User.upsert({ id: 42, username: 'john' }, { returning: true }); expect(user0.get('id')).to.equal(42); expect(user0.get('username')).to.equal('john'); - expect(created0).to.be.true; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } const [user, created] = await this.User.upsert({ id: 42, username: 'doe' }, { returning: true }); expect(user.get('id')).to.equal(42); expect(user.get('username')).to.equal('doe'); - expect(created).to.be.false; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } }); it('works for table with custom primary key field', async function() { @@ -523,12 +529,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { const [user0, created0] = await User.upsert({ id: 42, username: 'john' }, { returning: true }); expect(user0.get('id')).to.equal(42); expect(user0.get('username')).to.equal('john'); - expect(created0).to.be.true; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } const [user, created] = await User.upsert({ id: 42, username: 'doe' }, { returning: true }); expect(user.get('id')).to.equal(42); expect(user.get('username')).to.equal('doe'); - expect(created).to.be.false; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } }); it('works for non incrementing primaryKey', async function() { @@ -547,12 +561,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { const [user0, created0] = await User.upsert({ id: 'surya', username: 'john' }, { returning: true }); expect(user0.get('id')).to.equal('surya'); expect(user0.get('username')).to.equal('john'); - expect(created0).to.be.true; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } const [user, created] = await User.upsert({ id: 'surya', username: 'doe' }, { returning: true }); expect(user.get('id')).to.equal('surya'); expect(user.get('username')).to.equal('doe'); - expect(created).to.be.false; + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } }); }); } diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js index fe5b92310ed7..75443cf06e13 100644 --- a/test/unit/dialects/postgres/query-generator.test.js +++ b/test/unit/dialects/postgres/query-generator.test.js @@ -1094,29 +1094,6 @@ if (dialect.startsWith('postgres')) { } ], - upsertQuery: [ - { - arguments: [ - 'myTable', - { name: 'foo' }, - { name: 'foo' }, - { id: 2 }, - { primaryKeyField: 'id' } - ], - expectation: 'CREATE OR REPLACE FUNCTION pg_temp.sequelize_upsert(OUT created boolean, OUT primary_key text) AS $func$ BEGIN INSERT INTO "myTable" ("name") VALUES (\'foo\') RETURNING "id" INTO primary_key; created := true; EXCEPTION WHEN unique_violation THEN UPDATE "myTable" SET "name"=\'foo\' WHERE "id" = 2 RETURNING "id" INTO primary_key; created := false; END; $func$ LANGUAGE plpgsql; SELECT * FROM pg_temp.sequelize_upsert();' - }, - { - arguments: [ - 'myTable', - { name: 'RETURNING *', json: '{"foo":"RETURNING *"}' }, - { name: 'RETURNING *', json: '{"foo":"RETURNING *"}' }, - { id: 2 }, - { primaryKeyField: 'id' } - ], - expectation: 'CREATE OR REPLACE FUNCTION pg_temp.sequelize_upsert(OUT created boolean, OUT primary_key text) AS $func$ BEGIN INSERT INTO "myTable" ("name","json") VALUES (\'RETURNING *\',\'{"foo":"RETURNING *"}\') RETURNING "id" INTO primary_key; created := true; EXCEPTION WHEN unique_violation THEN UPDATE "myTable" SET "name"=\'RETURNING *\',"json"=\'{"foo":"RETURNING *"}\' WHERE "id" = 2 RETURNING "id" INTO primary_key; created := false; END; $func$ LANGUAGE plpgsql; SELECT * FROM pg_temp.sequelize_upsert();' - } - ], - removeIndexQuery: [ { arguments: ['User', 'user_foo_bar'], diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index e59363e7ac40..f58feff20556 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -705,7 +705,7 @@ export interface UpsertOptions extends Logging, Transactionable, SearchPathable, fields?: string[]; /** - * Return the affected rows (only for postgres) + * Return the affected rows */ returning?: boolean; @@ -1959,28 +1959,18 @@ export abstract class Model extends Hooks { * * **Implementation details:** * - * * MySQL - Implemented as a single query `INSERT values ON DUPLICATE KEY UPDATE values` - * * PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN - * unique_constraint UPDATE - * * SQLite - Implemented as two queries `INSERT; UPDATE`. This means that the update is executed - * regardless - * of whether the row already existed or not + * * MySQL - Implemented with ON DUPLICATE KEY UPDATE + * * PostgreSQL - Implemented with ON CONFLICT DO UPDATE + * * SQLite - Implemented with ON CONFLICT DO UPDATE + * * MSSQL - Implemented with MERGE statement * - * **Note** that SQLite returns undefined for created, no matter if the row was created or updated. This is - * because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know - * whether the row was inserted or not. + * **Note** that PostgreSQL/SQLite returns null for created, no matter if the row was created or updated. */ public static upsert( this: { new(): M } & typeof Model, values: object, - options?: UpsertOptions & { returning?: false | undefined } - ): Promise; - - public static upsert( - this: { new(): M } & typeof Model, - values: object, - options?: UpsertOptions & { returning: true } - ): Promise<[M, boolean]>; + options?: UpsertOptions + ): Promise<[M, boolean | null]>; /** * Create and insert multiple instances in bulk. diff --git a/types/test/upsert.ts b/types/test/upsert.ts index cd1845a7a92a..3f0523d183ca 100644 --- a/types/test/upsert.ts +++ b/types/test/upsert.ts @@ -7,7 +7,7 @@ class TestModel extends Model { TestModel.init({}, {sequelize}) sequelize.transaction(async trx => { - const res: [TestModel, boolean] = await TestModel.upsert({}, { + const res1: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, fields: ['testField'], hooks: true, @@ -18,7 +18,7 @@ sequelize.transaction(async trx => { validate: true, }); - let created: boolean = await TestModel.upsert({}, { + const res2: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, fields: ['testField'], hooks: true, @@ -29,7 +29,7 @@ sequelize.transaction(async trx => { validate: true, }); - created = await TestModel.upsert({}, { + const res3: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, fields: ['testField'], hooks: true, From 0769aea374ef104ba38bbc767e903d0b6f90b2c9 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 24 May 2020 11:54:12 +0530 Subject: [PATCH 164/414] refactor: cleanup query generators (#12304) --- lib/dialects/mssql/query-generator.js | 2 -- lib/dialects/mysql/query-generator.js | 1 - lib/dialects/postgres/query-generator.js | 38 ++++++------------------ 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 4c1b20bc4bf8..211a411875d4 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -336,8 +336,6 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { const allAttributes = []; const allQueries = []; - - let needIdentityInsertWrapper = false, outputFragment = ''; diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index 06d7e3c1b51b..a9ccf5d9a29e 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -147,7 +147,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { ]); } - describeTableQuery(tableName, schema, schemaDelimiter) { const table = this.quoteTable( this.addSchema({ diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index d08e01abbd07..0325c2fa5150 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -335,14 +335,6 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}(${parameters}) ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`; } - exceptionFn(fnName, tableName, parameters, main, then, when, returns, language) { - when = when || 'unique_violation'; - - const body = `${main} EXCEPTION WHEN ${when} THEN ${then};`; - - return this.fn(fnName, tableName, parameters, body, returns, language); - } - truncateTableQuery(tableName, options = {}) { return [ `TRUNCATE ${this.quoteTable(tableName)}`, @@ -593,7 +585,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { const decodedEventType = this.decodeTriggerEventType(eventType); const eventSpec = this.expandTriggerEventSpec(fireOnSpec); const expandedOptions = this.expandOptions(optionsArray); - const paramList = this.expandFunctionParamList(functionParams); + const paramList = this._expandFunctionParamList(functionParams); return `CREATE ${this.triggerEventTypeIsConstraint(eventType)}TRIGGER ${this.quoteIdentifier(triggerName)} ${decodedEventType} ${ eventSpec} ON ${this.quoteTable(tableName)}${expandedOptions ? ` ${expandedOptions}` : ''} EXECUTE PROCEDURE ${functionName}(${paramList});`; @@ -610,8 +602,8 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { createFunction(functionName, params, returnType, language, body, optionsArray, options) { if (!functionName || !returnType || !language || !body) throw new Error('createFunction missing some parameters. Did you pass functionName, returnType, language and body?'); - const paramList = this.expandFunctionParamList(params); - const variableList = options && options.variables ? this.expandFunctionVariableList(options.variables) : ''; + const paramList = this._expandFunctionParamList(params); + const variableList = options && options.variables ? this._expandFunctionVariableList(options.variables) : ''; const expandedOptionsArray = this.expandOptions(optionsArray); const statement = options && options.force ? 'CREATE OR REPLACE FUNCTION' : 'CREATE FUNCTION'; @@ -622,34 +614,22 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { dropFunction(functionName, params) { if (!functionName) throw new Error('requires functionName'); // RESTRICT is (currently, as of 9.2) default but we'll be explicit - const paramList = this.expandFunctionParamList(params); + const paramList = this._expandFunctionParamList(params); return `DROP FUNCTION ${functionName}(${paramList}) RESTRICT;`; } renameFunction(oldFunctionName, params, newFunctionName) { - const paramList = this.expandFunctionParamList(params); + const paramList = this._expandFunctionParamList(params); return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${newFunctionName};`; } - databaseConnectionUri(config) { - let uri = `${config.protocol}://${config.user}:${config.password}@${config.host}`; - if (config.port) { - uri += `:${config.port}`; - } - uri += `/${config.database}`; - if (config.ssl) { - uri += `?ssl=${config.ssl}`; - } - return uri; - } - pgEscapeAndQuote(val) { return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'")); } - expandFunctionParamList(params) { + _expandFunctionParamList(params) { if (params === undefined || !Array.isArray(params)) { - throw new Error('expandFunctionParamList: function parameters array required, including an empty one for no arguments'); + throw new Error('_expandFunctionParamList: function parameters array required, including an empty one for no arguments'); } const paramList = []; @@ -671,9 +651,9 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return paramList.join(', '); } - expandFunctionVariableList(variables) { + _expandFunctionVariableList(variables) { if (!Array.isArray(variables)) { - throw new Error('expandFunctionVariableList: function variables must be an array'); + throw new Error('_expandFunctionVariableList: function variables must be an array'); } const variableDefinitions = []; variables.forEach(variable => { From a2dcfa07fd3115fd6e1f08c79cee76ce61c63f30 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 24 May 2020 15:00:55 +0530 Subject: [PATCH 165/414] fix(query): ensure correct return signature for QueryTypes.RAW (#12305) --- lib/dialects/mssql/query.js | 33 +- .../dialects/mysql/warning.test.js | 1 + .../query-interface/createTable.test.js | 10 +- test/integration/sequelize.test.js | 590 ---------------- test/integration/sequelize/query.test.js | 627 ++++++++++++++++++ 5 files changed, 644 insertions(+), 617 deletions(-) create mode 100644 test/integration/sequelize/query.test.js diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 2d4f6dd6b279..4d860ca56b98 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -150,29 +150,15 @@ class Query extends AbstractQuery { * ]) */ formatResults(data, rowCount) { - let result = this.instance; if (this.isInsertQuery(data)) { this.handleInsertQuery(data); - - if (!this.instance) { - if (this.options.plain) { - // NOTE: super contrived. This just passes the newly added query-interface - // test returning only the PK. There isn't a way in MSSQL to identify - // that a given return value is the PK, and we have no schema information - // because there was no calling Model. - const record = data[0]; - result = record[Object.keys(record)[0]]; - } else { - result = data; - } - } + return [this.instance || data, rowCount]; } - if (this.isShowTablesQuery()) { return this.handleShowTablesQuery(data); } if (this.isDescribeQuery()) { - result = {}; + const result = {}; for (const _result of data) { if (_result.Default) { _result.Default = _result.Default.replace("('", '').replace("')", '').replace(/'/g, ''); @@ -197,8 +183,8 @@ class Query extends AbstractQuery { result[_result.Name].type += `(${_result.Length})`; } } - } + return result; } if (this.isSelectQuery()) { return this.handleSelectQuery(data); @@ -222,20 +208,19 @@ class Query extends AbstractQuery { return data; } if (this.isUpsertQuery()) { - return [result, data[0].$action === 'INSERT']; + this.handleInsertQuery(data); + return [this.instance || data, data[0].$action === 'INSERT']; } - if (this.isInsertQuery() || this.isUpdateQuery()) { - return [result, rowCount]; + if (this.isUpdateQuery()) { + return [this.instance || data, rowCount]; } if (this.isShowConstraintsQuery()) { return this.handleShowConstraintsQuery(data); } if (this.isRawQuery()) { - // MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta - return [data, data]; + return [data, rowCount]; } - - return result; + return data; } handleShowTablesQuery(results) { diff --git a/test/integration/dialects/mysql/warning.test.js b/test/integration/dialects/mysql/warning.test.js index ef0f6b85e4d9..9b1a63a97da8 100644 --- a/test/integration/dialects/mysql/warning.test.js +++ b/test/integration/dialects/mysql/warning.test.js @@ -33,6 +33,7 @@ describe(Support.getTestDialectTeaser('Warning'), () => { // last log is warning message expect(logger.args[logger.args.length - 1][0]).to.be.match(/^MySQL Warnings \(default\):.*/m); + logger.restore(); }); }); } diff --git a/test/integration/query-interface/createTable.test.js b/test/integration/query-interface/createTable.test.js index 7f40a2faa8df..31f2af637138 100644 --- a/test/integration/query-interface/createTable.test.js +++ b/test/integration/query-interface/createTable.test.js @@ -16,7 +16,6 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { await Support.dropTestSchemas(this.sequelize); }); - // FIXME: These tests should make assertions against the created table using describeTable describe('createTable', () => { it('should create a auto increment primary key', async function() { await this.queryInterface.createTable('TableWithPK', { @@ -27,8 +26,13 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }); - const [response] = await this.queryInterface.insert(null, 'TableWithPK', {}, { raw: true, returning: true, plain: true }); - expect(response.table_id || typeof response !== 'object' && response).to.be.ok; + const result = await this.queryInterface.describeTable('TableWithPK'); + + if (dialect === 'mssql' || dialect === 'mysql' || dialect === 'mariadb') { + expect(result.table_id.autoIncrement).to.be.true; + } else if (dialect === 'postgres') { + expect(result.table_id.defaultValue).to.equal('nextval("TableWithPK_table_id_seq"::regclass)'); + } }); it('should create unique constraint with uniqueKeys', async function() { diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index cb50a36c27f1..a80110a684d3 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -7,7 +7,6 @@ const dialect = Support.getTestDialect(); const _ = require('lodash'); const Sequelize = require('../../index'); const config = require('../config/config'); -const moment = require('moment'); const Transaction = require('../../lib/transaction'); const sinon = require('sinon'); const current = Support.sequelize; @@ -221,595 +220,6 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); }); - describe('query', () => { - afterEach(function() { - this.sequelize.options.quoteIdentifiers = true; - console.log.restore && console.log.restore(); - }); - - beforeEach(async function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - unique: true - }, - emailAddress: { - type: DataTypes.STRING, - field: 'email_address' - } - }); - - this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ - qq('createdAt') }, ${qq('updatedAt') - }) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; - - await this.User.sync({ force: true }); - }); - - it('executes a query the internal way', async function() { - await this.sequelize.query(this.insertQuery, { raw: true }); - }); - - it('executes a query if only the sql is passed', async function() { - await this.sequelize.query(this.insertQuery); - }); - - it('executes a query if a placeholder value is an array', async function() { - await this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + - `${qq('createdAt')}, ${qq('updatedAt')}) VALUES ?;`, { - replacements: [[ - ['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'], - ['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'] - ]] - }); - - const rows = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: this.sequelize.QueryTypes.SELECT - }); - - expect(rows).to.be.lengthOf(2); - expect(rows[0].username).to.be.equal('john'); - expect(rows[1].username).to.be.equal('michael'); - }); - - describe('retry', () => { - it('properly bind parameters on extra retries', async function() { - const payload = { - username: 'test', - createdAt: '2010-10-10 00:00:00', - updatedAt: '2010-10-10 00:00:00' - }; - - const spy = sinon.spy(); - - await this.User.create(payload); - - await expect(this.sequelize.query(` - INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt); - `, { - bind: payload, - logging: spy, - retry: { - max: 3, - match: [ - /Validation/ - ] - } - })).to.be.rejectedWith(Sequelize.UniqueConstraintError); - - expect(spy.callCount).to.eql(3); - }); - }); - - describe('logging', () => { - it('executes a query with global benchmarking option and custom logger', async () => { - const logger = sinon.spy(); - const sequelize = Support.createSequelizeInstance({ - logging: logger, - benchmark: true - }); - - await sequelize.query('select 1;'); - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); - - it('executes a query with benchmarking option and custom logger', async function() { - const logger = sinon.spy(); - - await this.sequelize.query('select 1;', { - logging: logger, - benchmark: true - }); - - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); - describe('log sql when set logQueryParameters', () => { - beforeEach(async function() { - this.sequelize = Support.createSequelizeInstance({ - benchmark: true, - logQueryParameters: true - }); - this.User = this.sequelize.define('User', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - username: { - type: DataTypes.STRING - }, - emailAddress: { - type: DataTypes.STRING - } - }, { - timestamps: false - }); - - await this.User.sync({ force: true }); - }); - it('add parameters in log sql', async function() { - let createSql, updateSql; - - const user = await this.User.create({ - username: 'john', - emailAddress: 'john@gmail.com' - }, { - logging: s =>{ - createSql = s; - } - }); - - user.username = 'li'; - - await user.save({ - logging: s =>{ - updateSql = s; - } - }); - - expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); - expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); - }); - - it('add parameters in log sql when use bind value', async function() { - let logSql; - const typeCast = dialect === 'postgres' ? '::text' : ''; - await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }); - expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); - }); - }); - - }); - - it('executes select queries correctly', async function() { - await this.sequelize.query(this.insertQuery); - const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`); - expect(users.map(u => { return u.username; })).to.include('john'); - }); - - it('executes select queries correctly when quoteIdentifiers is false', async function() { - const seq = Object.create(this.sequelize); - - seq.options.quoteIdentifiers = false; - await seq.query(this.insertQuery); - const [users] = await seq.query(`select * from ${qq(this.User.tableName)}`); - expect(users.map(u => { return u.username; })).to.include('john'); - }); - - it('executes select query with dot notation results', async function() { - await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); - await this.sequelize.query(this.insertQuery); - const [users] = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); - expect(users).to.deep.equal([{ 'user.username': 'john' }]); - }); - - it('executes select query with dot notation results and nest it', async function() { - await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); - await this.sequelize.query(this.insertQuery); - const users = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); - expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); - }); - - if (dialect === 'mysql') { - it('executes stored procedures', async function() { - await this.sequelize.query(this.insertQuery); - await this.sequelize.query('DROP PROCEDURE IF EXISTS foo'); - - await this.sequelize.query( - `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` - ); - - const users = await this.sequelize.query('CALL foo()'); - expect(users.map(u => { return u.username; })).to.include('john'); - }); - } else { - console.log('FIXME: I want to be supported in this dialect as well :-('); - } - - it('uses the passed model', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User - }); - - expect(users[0]).to.be.instanceof(this.User); - }); - - it('maps the field names to attributes based on the passed model', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User, - mapToModel: true - }); - - expect(users[0].emailAddress).to.be.equal('john@gmail.com'); - }); - - it('arbitrarily map the field names', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'userName', email_address: 'email' } - }); - - expect(users[0].userName).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); - }); - - it('keeps field names that are mapped to the same name', async function() { - await this.sequelize.query(this.insertQuery); - - const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'username', email_address: 'email' } - }); - - expect(users[0].username).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); - }); - - it('reject if `values` and `options.replacements` are both passed', async function() { - await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `options.bind` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `sql.bind` and `options.bind` cannot be set at the same time'); - }); - - it('reject if `options.replacements` and `options.bind` are both passed', async function() { - await this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `sql.values` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `options.replacements`` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `options.bind` and `sql.replacements` are both passed', async function() { - await this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('properly adds and escapes replacement value', async function() { - let logSql; - const number = 1, - date = new Date(), - string = 't\'e"st', - boolean = true, - buffer = Buffer.from('t\'e"st'); - - date.setMilliseconds(0); - - const result = await this.sequelize.query({ - query: 'select ? as number, ? as date,? as string,? as boolean,? as buffer', - values: [number, date, string, boolean, buffer] - }, { - type: this.sequelize.QueryTypes.SELECT, - logging(s) { - logSql = s; - } - }); - - const res = result[0] || {}; - res.date = res.date && new Date(res.date); - res.boolean = res.boolean && true; - if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { - res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); - } - expect(res).to.deep.equal({ - number, - date, - string, - boolean, - buffer - }); - expect(logSql).to.not.include('?'); - }); - - it('it allows to pass custom class instances', async function() { - let logSql; - class SQLStatement { - constructor() { - this.values = [1, 2]; - } - get query() { - return 'select ? as foo, ? as bar'; - } - } - const result = await this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); - - it('uses properties `query` and `values` if query is tagged', async function() { - let logSql; - const result = await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); - - it('uses properties `query` and `bind` if query is tagged', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - } else if (dialect === 'mssql') { - expect(logSql).to.include('@0'); - expect(logSql).to.include('@1'); - } else if (dialect === 'mysql') { - expect(logSql.match(/\?/g).length).to.equal(2); - } - }); - - it('dot separated attributes when doing a raw query without nest', async function() { - const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', - sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - - await expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); - }); - - it('destructs dot separated attributes when doing a raw query using nest', async function() { - const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', - sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - - const result = await this.sequelize.query(sql, { raw: true, nest: true }); - expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); - }); - - it('replaces token with the passed array', async function() { - const result = await this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - }); - - it('replaces named parameters with the passed object', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); - }); - - it('replaces named parameters with the passed object and ignore those which does not qualify', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); - }); - - it('replaces named parameters with the passed object using the same key twice', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - }); - - it('replaces named parameters with the passed object having a null property', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: null }]); - }); - - it('reject when key is missing in the passed object', async function() { - await this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed number', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed empty object', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed string', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed date', async function() { - await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('binds token with the passed array', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }); - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); - - it('binds named parameters with the passed object', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - } - if (dialect === 'sqlite') { - expect(logSql).to.include('$one'); - } - }); - - it('binds named parameters with the passed object using the same key twice', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - expect(logSql).to.not.include('$3'); - } - }); - - it('binds named parameters with the passed object having a null property', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); - }); - - it('binds named parameters array handles escaped $$', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - const result = await this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); - - it('binds named parameters object handles escaped $$', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - const result = await this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }); - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); - }); - - it('escape where has $ on the middle of characters', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - const result = await this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }); - expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); - }); - - if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { - it('does not improperly escape arrays of strings bound to named parameters', async function() { - const result = await this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }); - expect(result[0]).to.deep.equal([{ foo: '"string"' }]); - }); - } - - it('reject when binds passed with object and numeric $1 is also present', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - - await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when binds passed as array and $alpha is also present', async function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - - await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is $0 with the passed array', async function() { - await this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is $01 with the passed array', async function() { - await this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is missing in the passed array', async function() { - await this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is missing in the passed object', async function() { - await this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed number for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed empty object for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed string for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed date for bind', async function() { - await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('handles AS in conjunction with functions just fine', async function() { - let datetime = dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'; - if (dialect === 'mssql') { - datetime = 'GETDATE()'; - } - - const [result] = await this.sequelize.query(`SELECT ${datetime} AS t`); - expect(moment(result[0].t).isValid()).to.be.true; - }); - - if (Support.getTestDialect() === 'postgres') { - it('replaces named parameters with the passed object and ignores casts', async function() { - await expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); - }); - - it('supports WITH queries', async function() { - await expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) - .to.eventually.deep.equal([{ 'sum': '5050' }]); - }); - } - - if (Support.getTestDialect() === 'sqlite') { - it('binds array parameters for upsert are replaced. $$ unescapes only once', async function() { - let logSql; - await this.sequelize.query('select $1 as foo, $2 as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: [1, 2], logging(s) { logSql = s; } }); - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); - - it('binds named parameters for upsert are replaced. $$ unescapes only once', async function() { - let logSql; - await this.sequelize.query('select $one as foo, $two as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); - } - - }); - describe('set', () => { it('should be configurable with global functions', function() { const defaultSetterMethod = sinon.spy(), diff --git a/test/integration/sequelize/query.test.js b/test/integration/sequelize/query.test.js new file mode 100644 index 000000000000..6ed8b346ed6d --- /dev/null +++ b/test/integration/sequelize/query.test.js @@ -0,0 +1,627 @@ +'use strict'; + +const { expect } = require('chai'); +const Support = require('../support'); +const Sequelize = Support.Sequelize; +const DataTypes = Support.Sequelize.DataTypes; +const dialect = Support.getTestDialect(); +const sinon = require('sinon'); +const moment = require('moment'); + +const qq = str => { + if (dialect === 'postgres' || dialect === 'mssql') { + return `"${str}"`; + } + if (dialect === 'mysql' || dialect === 'mariadb' || dialect === 'sqlite') { + return `\`${str}\``; + } + return str; +}; + +describe(Support.getTestDialectTeaser('Sequelize'), () => { + describe('query', () => { + afterEach(function() { + this.sequelize.options.quoteIdentifiers = true; + console.log.restore && console.log.restore(); + }); + + beforeEach(async function() { + this.User = this.sequelize.define('User', { + username: { + type: Sequelize.STRING, + unique: true + }, + emailAddress: { + type: Sequelize.STRING, + field: 'email_address' + } + }); + + this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ + qq('createdAt') }, ${qq('updatedAt') + }) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; + + await this.User.sync({ force: true }); + }); + + it('executes a query the internal way', async function() { + await this.sequelize.query(this.insertQuery, { raw: true }); + }); + + it('executes a query if only the sql is passed', async function() { + await this.sequelize.query(this.insertQuery); + }); + + it('executes a query if a placeholder value is an array', async function() { + await this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + + `${qq('createdAt')}, ${qq('updatedAt')}) VALUES ?;`, { + replacements: [[ + ['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'], + ['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'] + ]] + }); + + const rows = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: this.sequelize.QueryTypes.SELECT + }); + + expect(rows).to.be.lengthOf(2); + expect(rows[0].username).to.be.equal('john'); + expect(rows[1].username).to.be.equal('michael'); + }); + + describe('QueryTypes', () => { + it('RAW', async function() { + await this.sequelize.query(this.insertQuery, { + type: Sequelize.QueryTypes.RAW + }); + + const [rows, count] = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: Sequelize.QueryTypes.RAW + }); + + expect(rows).to.be.an.instanceof(Array); + expect(count).to.be.ok; + }); + }); + + describe('retry', () => { + it('properly bind parameters on extra retries', async function() { + const payload = { + username: 'test', + createdAt: '2010-10-10 00:00:00', + updatedAt: '2010-10-10 00:00:00' + }; + + const spy = sinon.spy(); + + await this.User.create(payload); + + await expect(this.sequelize.query(` + INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt); + `, { + bind: payload, + logging: spy, + retry: { + max: 3, + match: [ + /Validation/ + ] + } + })).to.be.rejectedWith(Sequelize.UniqueConstraintError); + + expect(spy.callCount).to.eql(3); + }); + }); + + describe('logging', () => { + it('executes a query with global benchmarking option and custom logger', async () => { + const logger = sinon.spy(); + const sequelize = Support.createSequelizeInstance({ + logging: logger, + benchmark: true + }); + + await sequelize.query('select 1;'); + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); + expect(typeof logger.args[0][1] === 'number').to.be.true; + }); + + it('executes a query with benchmarking option and custom logger', async function() { + const logger = sinon.spy(); + + await this.sequelize.query('select 1;', { + logging: logger, + benchmark: true + }); + + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); + expect(typeof logger.args[0][1] === 'number').to.be.true; + }); + + describe('with logQueryParameters', () => { + beforeEach(async function() { + this.sequelize = Support.createSequelizeInstance({ + benchmark: true, + logQueryParameters: true + }); + this.User = this.sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + username: { + type: DataTypes.STRING + }, + emailAddress: { + type: DataTypes.STRING + } + }, { + timestamps: false + }); + + await this.User.sync({ force: true }); + }); + + it('add parameters in log sql', async function() { + let createSql, updateSql; + + const user = await this.User.create({ + username: 'john', + emailAddress: 'john@gmail.com' + }, { + logging: s =>{ + createSql = s; + } + }); + + user.username = 'li'; + + await user.save({ + logging: s =>{ + updateSql = s; + } + }); + + expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); + expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); + }); + + it('add parameters in log sql when use bind value', async function() { + let logSql; + const typeCast = dialect === 'postgres' ? '::text' : ''; + await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }); + expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); + }); + }); + }); + + it('executes select queries correctly', async function() { + await this.sequelize.query(this.insertQuery); + const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`); + expect(users.map(u => { return u.username; })).to.include('john'); + }); + + it('executes select queries correctly when quoteIdentifiers is false', async function() { + const seq = Object.create(this.sequelize); + + seq.options.quoteIdentifiers = false; + await seq.query(this.insertQuery); + const [users] = await seq.query(`select * from ${qq(this.User.tableName)}`); + expect(users.map(u => { return u.username; })).to.include('john'); + }); + + it('executes select query with dot notation results', async function() { + await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); + await this.sequelize.query(this.insertQuery); + const [users] = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); + expect(users).to.deep.equal([{ 'user.username': 'john' }]); + }); + + it('executes select query with dot notation results and nest it', async function() { + await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); + await this.sequelize.query(this.insertQuery); + const users = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); + expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); + }); + + if (dialect === 'mysql') { + it('executes stored procedures', async function() { + await this.sequelize.query(this.insertQuery); + await this.sequelize.query('DROP PROCEDURE IF EXISTS foo'); + + await this.sequelize.query( + `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` + ); + + const users = await this.sequelize.query('CALL foo()'); + expect(users.map(u => { return u.username; })).to.include('john'); + }); + } else { + console.log('FIXME: I want to be supported in this dialect as well :-('); + } + + it('uses the passed model', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + model: this.User + }); + + expect(users[0]).to.be.instanceof(this.User); + }); + + it('maps the field names to attributes based on the passed model', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + model: this.User, + mapToModel: true + }); + + expect(users[0].emailAddress).to.be.equal('john@gmail.com'); + }); + + it('arbitrarily map the field names', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: 'SELECT', + fieldMap: { username: 'userName', email_address: 'email' } + }); + + expect(users[0].userName).to.be.equal('john'); + expect(users[0].email).to.be.equal('john@gmail.com'); + }); + + it('keeps field names that are mapped to the same name', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: 'SELECT', + fieldMap: { username: 'username', email_address: 'email' } + }); + + expect(users[0].username).to.be.equal('john'); + expect(users[0].email).to.be.equal('john@gmail.com'); + }); + + describe('rejections', () => { + it('reject if `values` and `options.replacements` are both passed', async function() { + await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); + }); + + it('reject if `sql.bind` and `options.bind` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `sql.bind` and `options.bind` cannot be set at the same time'); + }); + + it('reject if `options.replacements` and `options.bind` are both passed', async function() { + await this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject if `sql.bind` and `sql.values` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject if `sql.bind` and `options.replacements`` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject if `options.bind` and `sql.replacements` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject when key is missing in the passed object', async function() { + await this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed number', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed empty object', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed string', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed date', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject when binds passed with object and numeric $1 is also present', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + + await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when binds passed as array and $alpha is also present', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + + await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is $0 with the passed array', async function() { + await this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is $01 with the passed array', async function() { + await this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is missing in the passed array', async function() { + await this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is missing in the passed object', async function() { + await this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed number for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed empty object for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed string for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed date for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + }); + + it('properly adds and escapes replacement value', async function() { + let logSql; + const number = 1, + date = new Date(), + string = 't\'e"st', + boolean = true, + buffer = Buffer.from('t\'e"st'); + + date.setMilliseconds(0); + + const result = await this.sequelize.query({ + query: 'select ? as number, ? as date,? as string,? as boolean,? as buffer', + values: [number, date, string, boolean, buffer] + }, { + type: this.sequelize.QueryTypes.SELECT, + logging(s) { + logSql = s; + } + }); + + const res = result[0] || {}; + res.date = res.date && new Date(res.date); + res.boolean = res.boolean && true; + if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { + res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); + } + expect(res).to.deep.equal({ + number, + date, + string, + boolean, + buffer + }); + expect(logSql).to.not.include('?'); + }); + + it('it allows to pass custom class instances', async function() { + let logSql; + class SQLStatement { + constructor() { + this.values = [1, 2]; + } + get query() { + return 'select ? as foo, ? as bar'; + } + } + const result = await this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + expect(logSql).to.not.include('?'); + }); + + it('uses properties `query` and `values` if query is tagged', async function() { + let logSql; + const result = await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + expect(logSql).to.not.include('?'); + }); + + it('uses properties `query` and `bind` if query is tagged', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + expect(logSql).to.include('$2'); + } else if (dialect === 'mssql') { + expect(logSql).to.include('@0'); + expect(logSql).to.include('@1'); + } else if (dialect === 'mysql') { + expect(logSql.match(/\?/g).length).to.equal(2); + } + }); + + it('dot separated attributes when doing a raw query without nest', async function() { + const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', + sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; + + await expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); + }); + + it('destructs dot separated attributes when doing a raw query using nest', async function() { + const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', + sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; + + const result = await this.sequelize.query(sql, { raw: true, nest: true }); + expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); + }); + + it('replaces token with the passed array', async function() { + const result = await this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + }); + + it('replaces named parameters with the passed object', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); + }); + + it('replaces named parameters with the passed object and ignore those which does not qualify', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); + }); + + it('replaces named parameters with the passed object using the same key twice', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); + }); + + it('replaces named parameters with the passed object having a null property', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: null }]); + }); + + it('binds token with the passed array', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + } + }); + + it('binds named parameters with the passed object', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres') { + expect(logSql).to.include('$1'); + } + if (dialect === 'sqlite') { + expect(logSql).to.include('$one'); + } + }); + + it('binds named parameters with the passed object using the same key twice', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); + if (dialect === 'postgres') { + expect(logSql).to.include('$1'); + expect(logSql).to.include('$2'); + expect(logSql).to.not.include('$3'); + } + }); + + it('binds named parameters with the passed object having a null property', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); + }); + + it('binds named parameters array handles escaped $$', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + } + }); + + it('binds named parameters object handles escaped $$', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + const result = await this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); + }); + + it('escape where has $ on the middle of characters', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + const result = await this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }); + expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); + }); + + if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { + it('does not improperly escape arrays of strings bound to named parameters', async function() { + const result = await this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }); + expect(result[0]).to.deep.equal([{ foo: '"string"' }]); + }); + } + + it('handles AS in conjunction with functions just fine', async function() { + let datetime = dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'; + if (dialect === 'mssql') { + datetime = 'GETDATE()'; + } + + const [result] = await this.sequelize.query(`SELECT ${datetime} AS t`); + expect(moment(result[0].t).isValid()).to.be.true; + }); + + if (Support.getTestDialect() === 'postgres') { + it('replaces named parameters with the passed object and ignores casts', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); + }); + + it('supports WITH queries', async function() { + await expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) + .to.eventually.deep.equal([{ 'sum': '5050' }]); + }); + } + + if (Support.getTestDialect() === 'sqlite') { + it('binds array parameters for upsert are replaced. $$ unescapes only once', async function() { + let logSql; + await this.sequelize.query('select $1 as foo, $2 as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: [1, 2], logging(s) { logSql = s; } }); + // sqlite.exec does not return a result + expect(logSql).to.not.include('$one'); + expect(logSql).to.include('\'$$\''); + }); + + it('binds named parameters for upsert are replaced. $$ unescapes only once', async function() { + let logSql; + await this.sequelize.query('select $one as foo, $two as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + // sqlite.exec does not return a result + expect(logSql).to.not.include('$one'); + expect(logSql).to.include('\'$$\''); + }); + } + }); +}); From 6d87cc583da1250e3f7b968f215b7f736c22347e Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 24 May 2020 17:21:48 +0530 Subject: [PATCH 166/414] docs(associations): belongs to many create with through table --- .../advanced-many-to-many.md | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/docs/manual/advanced-association-concepts/advanced-many-to-many.md b/docs/manual/advanced-association-concepts/advanced-many-to-many.md index af155955f8fb..2998ff6ac554 100644 --- a/docs/manual/advanced-association-concepts/advanced-many-to-many.md +++ b/docs/manual/advanced-association-concepts/advanced-many-to-many.md @@ -52,7 +52,7 @@ const amidala = User.create({ username: 'p4dm3', points: 1000 }); const queen = Profile.create({ name: 'Queen' }); await amidala.addProfile(queen, { through: { selfGranted: false } }); const result = await User.findOne({ - where: { username: 'p4dm3' } + where: { username: 'p4dm3' }, include: Profile }); console.log(result); @@ -79,6 +79,53 @@ Output: } ``` +You can create all relationship in single `create` call too. + +Example: + +```js +const amidala = await User.create({ + username: 'p4dm3', + points: 1000, + profiles: [{ + name: 'Queen', + User_Profile: { + selfGranted: true + } + }] +}, { + include: Profile +}); + +const result = await User.findOne({ + where: { username: 'p4dm3' }, + include: Profile +}); + +console.log(result); +``` + +Output: + +```json +{ + "id": 1, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 1, + "name": "Queen", + "User_Profile": { + "selfGranted": true, + "userId": 1, + "profileId": 1 + } + } + ] +} +``` + You probably noticed that the `User_Profiles` table does not have an `id` field. As mentioned above, it has a composite unique key instead. The name of this composite unique key is chosen automatically by Sequelize but can be customized with the `uniqueKey` option: ```js @@ -617,4 +664,4 @@ Found game: "Winter Showdown" So this is how we can achieve a *many-to-many-to-many* relationship between three models in Sequelize, by taking advantage of the Super Many-to-Many relationship technique! -This idea can be applied recursively for even more complex, *many-to-many-to-...-to-many* relationships (although at some point queries might become slow). \ No newline at end of file +This idea can be applied recursively for even more complex, *many-to-many-to-...-to-many* relationships (although at some point queries might become slow). From 59b8a7bfa018b94ccfa6e30e1040de91d1e3d3dd Mon Sep 17 00:00:00 2001 From: Vyacheslav Aristov Date: Tue, 26 May 2020 17:01:13 +0300 Subject: [PATCH 167/414] fix(include): check if attributes specified for included through model (#12316) --- lib/model.js | 4 ++- test/integration/include/findAll.test.js | 44 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 92d287edbfff..a4799f63d06b 100644 --- a/lib/model.js +++ b/lib/model.js @@ -604,7 +604,9 @@ class Model { // pseudo include just needed the attribute logic, return if (include._pseudo) { - include.attributes = Object.keys(include.model.tableAttributes); + if (!include.attributes) { + include.attributes = Object.keys(include.model.tableAttributes); + } return Utils.mapFinderOptions(include, include.model); } diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index 25d6a0214bc5..bdbc58178f21 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -1835,6 +1835,50 @@ describe(Support.getTestDialectTeaser('Include'), () => { expect(parseInt(post.get('commentCount'), 10)).to.equal(3); }); + it('should ignore include with attributes: [] and through: { attributes: [] } (used for aggregates)', async function() { + const User = this.sequelize.define('User', { + name: DataTypes.STRING + }); + const Project = this.sequelize.define('Project', { + title: DataTypes.STRING + }); + + User.belongsToMany(Project, { as: 'projects', through: 'UserProject' }); + Project.belongsToMany(User, { as: 'users', through: 'UserProject' }); + + await this.sequelize.sync({ force: true }); + + await User.create({ + name: Math.random().toString(), + projects: [ + { title: Math.random().toString() }, + { title: Math.random().toString() }, + { title: Math.random().toString() } + ] + }, { + include: [User.associations.projects] + }); + + const users = await User.findAll({ + attributes: [ + [this.sequelize.fn('COUNT', this.sequelize.col('projects.id')), 'projectsCount'] + ], + include: { + association: User.associations.projects, + attributes: [], + through: { attributes: [] } + }, + group: ['User.id'] + }); + + expect(users.length).to.equal(1); + + const user = users[0]; + + expect(user.projects).not.to.be.ok; + expect(parseInt(user.get('projectsCount'), 10)).to.equal(3); + }); + it('should not add primary key when including and aggregating with raw: true', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING From 1b867291b9f0580a187ef34e83540e9753443d5e Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Mon, 1 Jun 2020 07:53:25 +0300 Subject: [PATCH 168/414] docs: responsive (#12308) --- docs/css/style.css | 123 ++++++++++++++++++++++++++++++++- docs/scripts/.eslintrc | 5 ++ docs/scripts/menu-groups.js | 26 +++++++ docs/transforms/menu-groups.js | 7 ++ docs/transforms/meta-tags.js | 5 ++ 5 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 docs/scripts/.eslintrc create mode 100644 docs/scripts/menu-groups.js create mode 100644 docs/transforms/meta-tags.js diff --git a/docs/css/style.css b/docs/css/style.css index 99187d3ab6c3..c99916d7bb00 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -21,10 +21,126 @@ div.sequelize { max-width: 300px; } -.navigation { - margin-top: 40px !important; +.layout-container { + display: flex; + flex-wrap: wrap; + height: 100vh; + overflow: hidden; +} + +.layout-container .navigation { + position: initial; + margin: 0; + padding: 0 0.25em; + flex-grow: 1; + flex-shrink: 1; + max-width: 18em; + height: calc(100% - 4.6em - 40px); + overflow: auto; +} + +.layout-container header { + position: initial; + flex-basis: 100%; + display: flex; +} + +.layout-container header .search-box { + position: initial; + flex-grow: 1; + flex-shrink: 1; + text-align: right; + order: 1; + margin-top: 0.75em; + padding-bottom: 0; +} + +.search-box>span { + display:block; + width: 100%; +} + +.search-box.active .search-input { + width: calc(100% - 29px); + max-width: 300px; +} + +.search-result { + right: 0; +} + +.content { + position: initial; + margin: 0; + flex-grow: 1; + flex-basis: 50%; + height: calc(100% - 4.6em - 40px); + overflow: auto; padding-top: 0; - height: calc(100% - 40px); +} + +.navigation .hamburger { + display: none; + background-color: #eee; + width: 2.3em; + border: none; + padding: 0.25em; + cursor: pointer; + margin: 0.5em 0.25em; +} + +.navigation .hamburger .line { + display: block; + width: 100%; + height: 0.25em; + background-color: #666; + margin: 0.3em 0; + pointer-events: none; +} + +.footer { + flex-basis: 100%; + margin-top: 1em; + padding: 1em 0; + height: 1.6em; +} + +code { + overflow: auto; +} + +@media only screen and (max-width: 660px) { + .layout-container .navigation { + width: auto; + height: auto; + max-width: 100%; + position: absolute; + background-color: #fff; + top: 40px; + z-index: 1; + box-shadow: 1px 2px 4px #aaa; + } + + .layout-container .navigation.open { + height: calc(100% - 40px); + } + + .layout-container .navigation .hamburger { + display: inline-block; + } + + .layout-container .navigation>div { + display: none; + } + + .layout-container .navigation.open>div { + display: block; + } + + .footer { + margin-left: 0; + margin-right: 0; + } } .manual-toc a:hover { @@ -43,6 +159,7 @@ div.sequelize { } .api-reference-link { + white-space: nowrap; font-weight: bold; padding: 0 20px; } diff --git a/docs/scripts/.eslintrc b/docs/scripts/.eslintrc new file mode 100644 index 000000000000..b99c1118dd5e --- /dev/null +++ b/docs/scripts/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "browser": true + } +} diff --git a/docs/scripts/menu-groups.js b/docs/scripts/menu-groups.js new file mode 100644 index 000000000000..bec7591e5589 --- /dev/null +++ b/docs/scripts/menu-groups.js @@ -0,0 +1,26 @@ +'use strict'; + +(() => { + function toggleNavigationBar() { + const navigationElements = document.getElementsByClassName('navigation'); + for (let i = 0; i < navigationElements.length; ++i) { + const navigationElement = navigationElements[i]; + navigationElement.classList.toggle('open'); + } + } + + // Hamburger button - toggles the navigation bar + const hamburger = document.getElementById('navigationHamburger'); + hamburger.addEventListener('click', () => { + toggleNavigationBar(); + }); + + // Each link in the navigation bar - closes the navigation bar + const navigationLinks = document.querySelectorAll('.navigation a'); + for (let i = 0; i < navigationLinks.length; ++i) { + const linkElement = navigationLinks[i]; + linkElement.addEventListener('click', () => { + toggleNavigationBar(); + }); + } +})(); diff --git a/docs/transforms/menu-groups.js b/docs/transforms/menu-groups.js index 3d3667b57c65..d8a40b5d5b3c 100644 --- a/docs/transforms/menu-groups.js +++ b/docs/transforms/menu-groups.js @@ -1,5 +1,7 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const _ = require('lodash'); const manualGroups = require('./../manual-groups.json'); @@ -18,6 +20,11 @@ function isLinkToHiddenManual(link) { } module.exports = function transform($, filePath) { + // The three s are used to draw the menu button icon + $('nav.navigation').prepend($('')); + const menuGroupsScripts = fs.readFileSync(path.join(__dirname, '..', 'scripts', 'menu-groups.js'), 'utf8'); + $('body').append($(``)); + const sidebarManualDivs = $('nav div.manual-toc-root div[data-ice=manual]'); $(sidebarManualDivs.get(0)).before(` diff --git a/docs/transforms/meta-tags.js b/docs/transforms/meta-tags.js new file mode 100644 index 000000000000..62bb58e10404 --- /dev/null +++ b/docs/transforms/meta-tags.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function transform($) { + $('head').append(''); +}; From 65a9e1eae8ad724eeca44051fa56b41fab0ded2f Mon Sep 17 00:00:00 2001 From: Alexander Mochalin Date: Tue, 2 Jun 2020 13:37:36 +0500 Subject: [PATCH 169/414] fix(types): add Association into OrderItem type (#12332) --- types/lib/model.d.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index f58feff20556..bf506b214de7 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -451,7 +451,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { subQuery?: boolean; } -type OrderItemModel = typeof Model | { model: typeof Model; as: string } | string +type OrderItemAssociation = Association | typeof Model | { model: typeof Model; as: string } | string type OrderItemColumn = string | Col | Fn | Literal export type OrderItem = | string @@ -459,14 +459,14 @@ export type OrderItem = | Col | Literal | [OrderItemColumn, string] - | [OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemColumn, string] - | [OrderItemModel, OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemModel, OrderItemColumn, string] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn, string] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] export type Order = string | Fn | Col | Literal | OrderItem[]; /** From 2bf7f7bb3a4c6087da652053f06b3f36ac182e5a Mon Sep 17 00:00:00 2001 From: Sebastian Di Luzio Date: Fri, 5 Jun 2020 06:54:29 +0200 Subject: [PATCH 170/414] fix(typings): add support for optional values in "where" clauses (#12337) --- types/lib/model.d.ts | 6 ++++-- types/test/where.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index bf506b214de7..c0004b7bdf3f 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -339,8 +339,10 @@ export type WhereValue = | OrOperator | AndOperator | WhereGeometryOptions - | (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] - + | (string | number | Buffer | WhereAttributeHash)[] // implicit [Op.or] + // allow optional values in where object types + // Sequelize will still throw when a value in the object has the value undefined + | undefined; /** * A hash of attributes to describe your search. */ diff --git a/types/test/where.ts b/types/test/where.ts index 16f904ba60a2..370c0e1656fb 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -25,6 +25,15 @@ where = { date: new Date() }; +// Optional values +let whereWithOptionals: { needed: number; optional?: number } = { needed: 2 }; +where = whereWithOptionals; + +// Misusing optional values (typings allow this, sequelize will throw an error during runtime) +// This might be solved by updates to typescript itself (https://github.com/microsoft/TypeScript/issues/13195) +whereWithOptionals = { needed: 2, optional: undefined }; +where = whereWithOptionals; + // Operators const and: AndOperator = { From f9e660f01cd87cef494cb18d9258a4f2c7ad426b Mon Sep 17 00:00:00 2001 From: Sushant Date: Fri, 5 Jun 2020 11:46:51 +0530 Subject: [PATCH 171/414] docs: update feature request template --- .github/ISSUE_TEMPLATE/feature_request.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c1960c62f846..a250df30acee 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- -## Issue Description +## Feature Description ### Is your feature request related to a problem? Please describe. + A clear and concise description of what the problem is. Example: I'm always frustrated when [...] ### Describe the solution you'd like + A clear and concise description of what you want to happen. How can the requested feature be used to approach the problem it's supposed to solve? ```js @@ -26,24 +27,27 @@ A clear and concise description of what you want to happen. How can the requeste ``` ### Why should this be in Sequelize + Short explanation why this should be part of Sequelize rather than a separate package. ### Describe alternatives/workarounds you've considered + A clear and concise description of any alternative solutions or features you've considered. If any workaround exists to the best of your knowledge, include it here. ### Additional context + Add any other context or screenshots about the feature request here. -## Issue Template Checklist +## Feature Request Checklist -### Is this issue dialect-specific? +### Is this feature dialect-specific? - [ ] No. This issue is relevant to Sequelize as a whole. - [ ] Yes. This issue only applies to the following dialect(s): XXX, YYY, ZZZ -### Would you be willing to resolve this issue by submitting a Pull Request? +### Would you be willing to implement this feature by submitting a Pull Request? From 7afd589938ca01996f40d12d8fff1a93360bff19 Mon Sep 17 00:00:00 2001 From: Sushant Date: Fri, 5 Jun 2020 12:10:47 +0530 Subject: [PATCH 172/414] docs(sequelize): omitNull only works for CREATE/UPDATE queries --- lib/sequelize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index 48d5a5ee6000..38ecfe1ef776 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -146,7 +146,7 @@ class Sequelize { * @param {boolean} [options.standardConformingStrings=true] The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. WARNING: Setting this to false may expose vulnerabilities and is not recommended! * @param {Function} [options.logging=console.log] A function that gets executed every time Sequelize would log something. Function may receive multiple parameters but only first one is printed by `console.log`. To print all values use `(...msg) => console.log(msg)` * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {boolean} [options.omitNull=false] A flag that defines if null values should be passed to SQL queries or not. + * @param {boolean} [options.omitNull=false] A flag that defines if null values should be passed as values to CREATE/UPDATE SQL queries or not. * @param {boolean} [options.native=false] A flag that defines if native library shall be used or not. Currently only has an effect for postgres * @param {boolean} [options.replication=false] Use read / write replication. To enable replication, pass an object, with two properties, read and write. Write should be an object (a single server for handling writes), and read an array of object (several servers to handle reads). Each read/write server can have the following properties: `host`, `port`, `username`, `password`, `database` * @param {object} [options.pool] sequelize connection pool configuration From f3671912c3201b318b7aa2564bf814e896fade8e Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Sat, 6 Jun 2020 08:54:38 +0300 Subject: [PATCH 173/414] fix(query-generator): do not generate GROUP BY clause if options.group is empty (#12343) --- lib/dialects/abstract/query-generator.js | 4 +- test/unit/sql/group.test.js | 54 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/unit/sql/group.test.js diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 9900d8f3b4db..97e372bbec48 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1333,9 +1333,9 @@ class QueryGenerator { if (options.group) { options.group = Array.isArray(options.group) ? options.group.map(t => this.aliasGrouping(t, model, mainTable.as, options)).join(', ') : this.aliasGrouping(options.group, model, mainTable.as, options); - if (subQuery) { + if (subQuery && options.group) { subQueryItems.push(` GROUP BY ${options.group}`); - } else { + } else if (options.group) { mainQueryItems.push(` GROUP BY ${options.group}`); } } diff --git a/test/unit/sql/group.test.js b/test/unit/sql/group.test.js new file mode 100644 index 000000000000..4059c6477350 --- /dev/null +++ b/test/unit/sql/group.test.js @@ -0,0 +1,54 @@ +'use strict'; + +const Support = require('../support'), + DataTypes = require('../../../lib/data-types'), + util = require('util'), + expectsql = Support.expectsql, + current = Support.sequelize, + sql = current.dialect.queryGenerator; + + +describe(Support.getTestDialectTeaser('SQL'), () => { + describe('group', () => { + const testsql = function(options, expectation) { + const model = options.model; + + it(util.inspect(options, { depth: 2 }), () => { + return expectsql( + sql.selectQuery( + options.table || model && model.getTableName(), + options, + options.model + ), + expectation + ); + }); + }; + + const User = Support.sequelize.define('User', { + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false + } + }); + + testsql({ + model: User, + group: ['name'] + }, { + default: 'SELECT * FROM `Users` AS `User` GROUP BY `name`;', + postgres: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + mssql: 'SELECT * FROM [Users] AS [User] GROUP BY [name];' + }); + + testsql({ + model: User, + group: [] + }, { + default: 'SELECT * FROM `Users` AS `User`;', + postgres: 'SELECT * FROM "Users" AS "User";', + mssql: 'SELECT * FROM [Users] AS [User];' + }); + }); +}); From 72925cf70ce50ce9a004de0dd7e3aa83f4321c41 Mon Sep 17 00:00:00 2001 From: Marquitos Date: Sun, 7 Jun 2020 06:28:44 +0200 Subject: [PATCH 174/414] fix: add missing fields to 'FindOrCreateType' (#12338) --- types/lib/model.d.ts | 7 +++---- types/test/model.ts | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index c0004b7bdf3f..6c6b61ca0952 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -685,12 +685,11 @@ export interface Hookable { /** * Options for Model.findOrCreate method */ -export interface FindOrCreateOptions extends Logging, Transactionable { +export interface FindOrCreateOptions extends Filterable, Logging, Transactionable { /** - * A hash of search attributes. + * The fields to insert / update. Defaults to all fields */ - where: WhereOptions; - + fields?: string[]; /** * Default values to use if building a new instance */ diff --git a/types/test/model.ts b/types/test/model.ts index c08f4a1d8329..439eeeee514f 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -98,6 +98,20 @@ UserModel.findCreateFind({ } }) +/** + * Tests for findOrCreate() type. + */ + + UserModel.findOrCreate({ + fields: [ "jane.doe" ], + where: { + username: "jane.doe" + }, + defaults: { + username: "jane.doe" + } +}) + /** * Test for primaryKeyAttributes. */ @@ -116,12 +130,12 @@ someInstance.getOthers({ joinTableAttributes: { include: [ 'id' ] } }) -/** +/** * Test for through options in creating a BelongsToMany association */ class Film extends Model {} -class Actor extends Model {} +class Actor extends Model {} Film.belongsToMany(Actor, { through: { @@ -135,4 +149,4 @@ Actor.belongsToMany(Film, { model: 'FilmActors', paranoid: true } -}) \ No newline at end of file +}) From ed2d7a90c7966576403a4beb8d17634758925685 Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Mon, 8 Jun 2020 01:52:19 -0300 Subject: [PATCH 175/414] fix(model.destroy): return 0 with truncate (#12281) --- lib/dialects/mssql/query.js | 2 +- lib/dialects/postgres/query.js | 3 +-- test/integration/model.test.js | 9 +++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 4d860ca56b98..86f0c74af5e7 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -199,7 +199,7 @@ class Query extends AbstractQuery { return rowCount; } if (this.isBulkDeleteQuery()) { - return data[0] && data[0].AFFECTEDROWS; + return data[0] ? data[0].AFFECTEDROWS : 0; } if (this.isVersionQuery()) { return data[0].version; diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index acb5732a8246..e4e4135fe6d1 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -97,7 +97,7 @@ class Query extends AbstractQuery { (count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, 0 ) - : queryResult.rowCount; + : queryResult.rowCount || 0; if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) { rows = rows @@ -396,7 +396,6 @@ class Query extends AbstractQuery { } } - module.exports = Query; module.exports.Query = Query; module.exports.default = Query; diff --git a/test/integration/model.test.js b/test/integration/model.test.js index c6bff55502a4..c52b228ac107 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -1238,6 +1238,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(await User.findAll()).to.have.lengthOf(0); }); + it('`truncate` option returns a number', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + const affectedRows = await User.destroy({ truncate: true }); + expect(await User.findAll()).to.have.lengthOf(0); + expect(affectedRows).to.be.a('number'); + }); + it('throws an error if no where clause is given', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); await this.sequelize.sync({ force: true }); From 95f7fb5ce37439e796cd9920d3fda5bc0bbd9bc9 Mon Sep 17 00:00:00 2001 From: Over Martinez Date: Mon, 8 Jun 2020 04:28:19 -0400 Subject: [PATCH 176/414] fix(mssql): empty order array generates invalid FETCH statement (#12261) --- lib/dialects/mssql/query-generator.js | 4 ++-- test/unit/sql/offset-limit.test.js | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 211a411875d4..f1f9ca941be3 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -913,9 +913,9 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } if (options.limit || options.offset) { - if (!options.order || options.include && !orders.subQueryOrder.length) { + if (!options.order || !options.order.length || options.include && !orders.subQueryOrder.length) { const tablePkFragment = `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`; - if (!options.order) { + if (!options.order || !options.order.length) { fragment += ` ORDER BY ${tablePkFragment}`; } else { const orderFieldNames = _.map(options.order, order => order[0]); diff --git a/test/unit/sql/offset-limit.test.js b/test/unit/sql/offset-limit.test.js index 1a9a20a416f8..5b5fc3afb36e 100644 --- a/test/unit/sql/offset-limit.test.js +++ b/test/unit/sql/offset-limit.test.js @@ -79,5 +79,14 @@ describe(Support.getTestDialectTeaser('SQL'), () => { mysql: " LIMIT '\\';DELETE FROM user', 10", mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY" }); + + testsql({ + limit: 10, + order: [], // When the order is an empty array, one is automagically prepended + model: { primaryKeyField: 'id', name: 'tableRef' } + }, { + default: ' LIMIT 10', + mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' + }); }); }); From b71cd05b1f733af9e5572a5d788741c2ec66f08b Mon Sep 17 00:00:00 2001 From: Mikxail Date: Tue, 16 Jun 2020 07:29:00 +0300 Subject: [PATCH 177/414] fix(query): preserve cls context for logger (#12328) --- lib/dialects/mssql/query.js | 63 ++++++++++++++++++------------------ lib/dialects/mysql/query.js | 43 ++++++++++++------------ test/integration/cls.test.js | 33 ++++++++++++++++++- 3 files changed, 84 insertions(+), 55 deletions(-) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 86f0c74af5e7..bc45d0d650cf 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -44,48 +44,29 @@ class Query extends AbstractQuery { return paramType; } - _run(connection, sql, parameters) { + async _run(connection, sql, parameters) { this.sql = sql; const { options } = this; const complete = this._logQuery(sql, debug, parameters); - return new Promise((resolve, reject) => { - const handleTransaction = err => { - if (err) { - err.sql = sql; - err.parameters = parameters; - reject(this.formatError(err)); - return; - } - resolve(this.formatResults()); - }; + const query = new Promise((resolve, reject) => { // TRANSACTION SUPPORT if (sql.startsWith('BEGIN TRANSACTION')) { - return connection.beginTransaction(handleTransaction, options.transaction.name, connection.lib.ISOLATION_LEVEL[options.isolationLevel]); + return connection.beginTransaction(error => error ? reject(error) : resolve([]), options.transaction.name, connection.lib.ISOLATION_LEVEL[options.isolationLevel]); } if (sql.startsWith('COMMIT TRANSACTION')) { - return connection.commitTransaction(handleTransaction); + return connection.commitTransaction(error => error ? reject(error) : resolve([])); } if (sql.startsWith('ROLLBACK TRANSACTION')) { - return connection.rollbackTransaction(handleTransaction, options.transaction.name); + return connection.rollbackTransaction(error => error ? reject(error) : resolve([]), options.transaction.name); } if (sql.startsWith('SAVE TRANSACTION')) { - return connection.saveTransaction(handleTransaction, options.transaction.name); + return connection.saveTransaction(error => error ? reject(error) : resolve([]), options.transaction.name); } - const results = []; - const request = new connection.lib.Request(sql, (err, rowCount) => { - - complete(); - if (err) { - err.sql = sql; - err.parameters = parameters; - reject(this.formatError(err)); - } else { - resolve(this.formatResults(results, rowCount)); - } - }); + const rows = []; + const request = new connection.lib.Request(sql, (err, rowCount) => err ? reject(err) : resolve([rows, rowCount])); if (parameters) { _.forOwn(parameters, (value, key) => { @@ -95,6 +76,27 @@ class Query extends AbstractQuery { } request.on('row', columns => { + rows.push(columns); + }); + + connection.execSql(request); + }); + + let rows, rowCount; + + try { + [rows, rowCount] = await query; + } catch (err) { + err.sql = sql; + err.parameters = parameters; + + throw this.formatError(err); + } + + complete(); + + if (Array.isArray(rows)) { + rows = rows.map(columns => { const row = {}; for (const column of columns) { const typeid = column.metadata.type.id; @@ -106,12 +108,11 @@ class Query extends AbstractQuery { } row[column.metadata.colName] = value; } - - results.push(row); + return row; }); + } - connection.execSql(request); - }); + return this.formatResults(rows, rowCount); } run(sql, parameters) { diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index 208c7067df94..a6e3dbc89951 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -35,30 +35,27 @@ class Query extends AbstractQuery { const complete = this._logQuery(sql, debug, parameters); - const results = await new Promise((resolve, reject) => { - const handler = (err, results) => { - complete(); - - if (err) { - // MySQL automatically rolls-back transactions in the event of a deadlock - if (options.transaction && err.errno === 1213) { - options.transaction.finished = 'rollback'; - } - err.sql = sql; - err.parameters = parameters; - - reject(this.formatError(err)); - } else { - resolve(results); - } - }; - if (parameters) { - debug('parameters(%j)', parameters); - connection.execute(sql, parameters, handler).setMaxListeners(100); - } else { - connection.query({ sql }, handler).setMaxListeners(100); + const query = parameters && parameters.length + ? new Promise((resolve, reject) => connection.execute(sql, parameters, (error, result) => error ? reject(error) : resolve(result)).setMaxListeners(100)) + : new Promise((resolve, reject) => connection.query({ sql }, (error, result) => error ? reject(error) : resolve(result)).setMaxListeners(100)); + + let results; + + try { + results = await query; + } catch (err) { + // MySQL automatically rolls-back transactions in the event of a deadlock + if (options.transaction && err.errno === 1213) { + options.transaction.finished = 'rollback'; } - }); + err.sql = sql; + err.parameters = parameters; + + throw this.formatError(err); + } + + complete(); + // Log warnings if we've got them. if (showWarnings && results && results.warningStatus > 0) { await this.logWarnings(results); diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js index 2a96f4969a0d..746d43996842 100644 --- a/test/integration/cls.test.js +++ b/test/integration/cls.test.js @@ -6,7 +6,8 @@ const chai = require('chai'), Sequelize = Support.Sequelize, cls = require('cls-hooked'), current = Support.sequelize, - delay = require('delay'); + delay = require('delay'), + sinon = require('sinon'); if (current.dialect.supports.transactions) { describe(Support.getTestDialectTeaser('CLS (Async hooks)'), () => { @@ -146,5 +147,35 @@ if (current.dialect.supports.transactions) { } ); }); + + it('custom logging with benchmarking has correct CLS context', async function() { + const logger = sinon.spy(() => { + return this.ns.get('value'); + }); + const sequelize = Support.createSequelizeInstance({ + logging: logger, + benchmark: true + }); + + const result = this.ns.runPromise(async () => { + this.ns.set('value', 1); + await delay(500); + return sequelize.query('select 1;'); + }); + + await this.ns.runPromise(() => { + this.ns.set('value', 2); + return sequelize.query('select 2;'); + }); + + await result; + + expect(logger.calledTwice).to.be.true; + expect(logger.firstCall.args[0]).to.be.match(/Executed \((\d*|default)\): select 2/); + expect(logger.firstCall.returnValue).to.be.equal(2); + expect(logger.secondCall.args[0]).to.be.match(/Executed \((\d*|default)\): select 1/); + expect(logger.secondCall.returnValue).to.be.equal(1); + + }); }); } From 4914367c8d4c909530136aea7af43f515313d2fd Mon Sep 17 00:00:00 2001 From: Jose Miguel Colella Date: Wed, 17 Jun 2020 00:11:57 -0400 Subject: [PATCH 178/414] fix(types): add clientMinMessages to Options interface (#12375) --- types/lib/sequelize.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 14244284bb61..7cd58aa544a2 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -353,6 +353,14 @@ export interface Options extends Logging { */ standardConformingStrings?: boolean; + /** + * The PostgreSQL `client_min_messages` session parameter. + * Set to `false` to not override the database's default. + * + * @default 'warning' + */ + clientMinMessages?: string | boolean; + /** * Sets global permanent hooks. */ From e80501d85615455d854bae770309cb7f8eca1afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Uhl=C3=AD=C5=99?= Date: Wed, 17 Jun 2020 06:14:46 +0200 Subject: [PATCH 179/414] fix(types): transactionType in Options (#12377) --- types/lib/sequelize.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 7cd58aa544a2..e5388a8d47ad 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -327,6 +327,13 @@ export interface Options extends Logging { */ isolationLevel?: string; + /** + * Set the default transaction type. See Sequelize.Transaction.TYPES for possible options. Sqlite only. + * + * @default 'DEFERRED' + */ + transactionType?: Transaction.TYPES; + /** * Run built in type validators on insert and update, e.g. validate that arguments passed to integer * fields are integer-like. From 5611ef0bdb9b481355346afaedc564b153fdbde4 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sat, 20 Jun 2020 14:34:50 +0530 Subject: [PATCH 180/414] build: update dependencies (#12395) --- CONTACT.md | 7 -- package-lock.json | 291 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 3 files changed, 277 insertions(+), 23 deletions(-) diff --git a/CONTACT.md b/CONTACT.md index 8aaf39521ef4..9b60d3d84330 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -7,10 +7,3 @@ You can use the information below to contact maintainers directly. We will try t - **Jan Aagaard Meier** janzeh@gmail.com - **Sushant Dhiman** sushantdhiman@outlook.com - -### Via Slack - -Maintainer's usernames for [Sequelize Slack Channel](https://sequelize.slack.com) - -- **Jan Aagaard Meier** @janmeier -- **Sushant Dhiman** @sushantdhiman diff --git a/package-lock.json b/package-lock.json index 852f7bbe57dd..83ff957a15d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1251,6 +1251,12 @@ "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==", "dev": true }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, "@types/node": { "version": "12.12.42", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.42.tgz", @@ -2628,13 +2634,10 @@ } }, "dargs": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", - "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true }, "dashdash": { "version": "1.14.1", @@ -4458,16 +4461,256 @@ } }, "git-raw-commits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.3.tgz", - "integrity": "sha512-SoSsFL5lnixVzctGEi2uykjA7B5I0AhO9x6kdzvGRHbxsa6JSEgrgy1esRKsfOKE1cgyOJ/KDR2Trxu157sb8w==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.7.tgz", + "integrity": "sha512-SkwrTqrDxw8y0G1uGJ9Zw13F7qu3LF8V4BifyDeiJCxSnjRGZD9SaoMiMqUvvXMXh6S3sOQ1DsBN7L2fMUZW/g==", "dev": true, "requires": { - "dargs": "^4.0.1", + "dargs": "^7.0.0", "lodash.template": "^4.0.2", - "meow": "^5.0.0", + "meow": "^7.0.0", "split2": "^2.0.0", "through2": "^3.0.0" + }, + "dependencies": { + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "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" + } + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "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" + } + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, + "meow": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + } + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + } + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.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" + } + }, + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "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 + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "dev": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + } } }, "glob": { @@ -4624,6 +4867,12 @@ "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5762,6 +6011,12 @@ "safe-buffer": "^5.0.1" } }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", @@ -6767,6 +7022,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -12381,9 +12642,9 @@ "dev": true }, "sequelize-pool": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-5.0.0.tgz", - "integrity": "sha512-T/i80WCCzF3GcTDhc5LCLwahWkUDvmolrqMBtW8Ny5nK+hwhPmt8Tvbsosia9wGpSN/WN1bXmU1ard6+GVWbOA==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.0.0.tgz", + "integrity": "sha512-D/VfOX2Z+6JTWqM73lhcqMXp1X4CeqRNVMlndvbOMtyjFAZ2kYzH7rGFGFrLO1r+RZQdc/h+3zQL4nd3cclNLg==" }, "set-blocking": { "version": "2.0.0", diff --git a/package.json b/package.json index ced54cbe92d7..c21cc41e2c44 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "moment-timezone": "^0.5.31", "retry-as-promised": "^3.2.0", "semver": "^7.3.2", - "sequelize-pool": "^5.0.0", + "sequelize-pool": "^6.0.0", "toposort-class": "^1.0.1", "uuid": "^8.1.0", "validator": "^10.11.0", From e33d2bdc74167a00c4ff12ba9825a8a0cfc72bc9 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 21 Jun 2020 10:33:00 +0530 Subject: [PATCH 181/414] fix(reload): include default scope (#12399) --- lib/model.js | 2 +- test/integration/instance/reload.test.js | 31 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index a4799f63d06b..f148563ba8fa 100644 --- a/lib/model.js +++ b/lib/model.js @@ -4043,7 +4043,7 @@ class Model { options = Utils.defaults({ where: this.where() }, options, { - include: this._options.include || null + include: this._options.include || undefined }); const reloaded = await this.constructor.findOne(options); diff --git a/test/integration/instance/reload.test.js b/test/integration/instance/reload.test.js index 89ed2b666e1c..8b9566d82140 100644 --- a/test/integration/instance/reload.test.js +++ b/test/integration/instance/reload.test.js @@ -301,5 +301,36 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const leTeam = await leTeam0.reload(); expect(leTeam.Players).to.have.length(1); }); + + it('should inject default scope when reloading', async function() { + const Bar = this.sequelize.define('Bar', { + name: DataTypes.TEXT + }); + + const Foo = this.sequelize.define('Foo', { + name: DataTypes.TEXT + }, { + defaultScope: { + include: [{ model: Bar }] + } + }); + + Bar.belongsTo(Foo); + Foo.hasMany(Bar); + + await this.sequelize.sync(); + + const foo = await Foo.create({ name: 'foo' }); + await foo.createBar({ name: 'bar' }); + const fooFromFind = await Foo.findByPk(foo.id); + + expect(fooFromFind.Bars).to.be.ok; + expect(fooFromFind.Bars[0].name).to.equal('bar'); + + await foo.reload(); + + expect(foo.Bars).to.be.ok; + expect(foo.Bars[0].name).to.equal('bar'); + }); }); }); From c6e41928c116d406bf03a7eeccf489a29c800030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Weislinger?= <2735603+closingin@users.noreply.github.com> Date: Tue, 23 Jun 2020 06:00:05 +0200 Subject: [PATCH 182/414] fix(postgres): parse enums correctly when describing a table (#12409) --- lib/dialects/postgres/query-generator.js | 2 +- .../dialects/postgres/query-generator.test.js | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js index 0325c2fa5150..f5d10c949298 100644 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -798,7 +798,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return []; } - matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '')); + matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/g, '')); return matches.slice(0, -1); } diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js index 75443cf06e13..e7b085d6db16 100644 --- a/test/unit/dialects/postgres/query-generator.test.js +++ b/test/unit/dialects/postgres/query-generator.test.js @@ -1265,5 +1265,45 @@ if (dialect.startsWith('postgres')) { }); }); }); + + describe('fromArray()', () => { + beforeEach(function() { + this.queryGenerator = new QueryGenerator({ + sequelize: this.sequelize, + _dialect: this.sequelize.dialect + }); + }); + + const tests = [ + { + title: 'should convert an enum with no quoted strings to an array', + arguments: '{foo,bar,foobar}', + expectation: ['foo', 'bar', 'foobar'] + }, { + title: 'should convert an enum starting with a quoted string to an array', + arguments: '{"foo bar",foo,bar}', + expectation: ['foo bar', 'foo', 'bar'] + }, { + title: 'should convert an enum ending with a quoted string to an array', + arguments: '{foo,bar,"foo bar"}', + expectation: ['foo', 'bar', 'foo bar'] + }, { + title: 'should convert an enum with a quoted string in the middle to an array', + arguments: '{foo,"foo bar",bar}', + expectation: ['foo', 'foo bar', 'bar'] + }, { + title: 'should convert an enum full of quoted strings to an array', + arguments: '{"foo bar","foo bar","foo bar"}', + expectation: ['foo bar', 'foo bar', 'foo bar'] + } + ]; + + _.each(tests, test => { + it(test.title, function() { + const convertedText = this.queryGenerator.fromArray(test.arguments); + expect(convertedText).to.deep.equal(test.expectation); + }); + }); + }); }); } From 663261bf0ee122ba7581ea9df5cba141435395fc Mon Sep 17 00:00:00 2001 From: Davide Mauri Date: Tue, 23 Jun 2020 11:27:39 +0200 Subject: [PATCH 183/414] feat(sequelize): allow passing dialectOptions.options from url (#12404) --- lib/sequelize.js | 14 ++++++++++++-- test/unit/configuration.test.js | 6 ++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index 38ecfe1ef776..21709ca15d8a 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -217,10 +217,20 @@ class Sequelize { options.host = urlParts.query.host; } - if (options.dialectOptions) + if (options.dialectOptions) { Object.assign(options.dialectOptions, urlParts.query); - else + } else { options.dialectOptions = urlParts.query; + if (urlParts.query.options) { + try { + const o = JSON.parse(urlParts.query.options); + options.dialectOptions.options = o; + } catch (e) { + // Nothing to do, string is not a valid JSON + // an thus does not need any further processing + } + } + } } } else { // new Sequelize(database, username, password, { ... options }) diff --git a/test/unit/configuration.test.js b/test/unit/configuration.test.js index b2882c698ec5..0cade77cc358 100644 --- a/test/unit/configuration.test.js +++ b/test/unit/configuration.test.js @@ -180,6 +180,12 @@ describe('Sequelize', () => { expect(dialectOptions.ssl).to.equal('true'); }); + it('should handle JSON options', () => { + const sequelizeWithOptions = new Sequelize('mysql://example.com:9821/dbname?options={"encrypt":true}&anotherOption=1'); + expect(sequelizeWithOptions.options.dialectOptions.options.encrypt).to.be.true; + expect(sequelizeWithOptions.options.dialectOptions.anotherOption).to.equal('1'); + }); + it('should use query string host if specified', () => { const sequelize = new Sequelize('mysql://localhost:9821/dbname?host=example.com'); From 0ca8d727366452c4c51ac0767d0929a31e0518dd Mon Sep 17 00:00:00 2001 From: Sushant Date: Wed, 24 Jun 2020 12:20:47 +0530 Subject: [PATCH 184/414] docs: prepare for v6 release (#12416) --- .travis.yml | 13 ++---- CONTRIBUTING.DOCS.md | 6 +-- CONTRIBUTING.md | 55 +++++++++++++---------- Dockerfile | 2 +- README.md | 18 ++++---- SECURITY.md | 1 + docs/manual/other-topics/upgrade-to-v6.md | 27 +++++++++++ package.json | 10 +++++ 8 files changed, 86 insertions(+), 46 deletions(-) diff --git a/.travis.yml b/.travis.yml index bf4979cc2266..f49b366cae80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ language: node_js branches: only: - master - - /^greenkeeper/.*$/ except: - /^v\d+\.\d+\.\d+$/ @@ -37,7 +36,7 @@ before_script: script: - |- - if [ "$COVERAGE" = true ]; then npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info; else npm run test; fi + npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info jobs: include: @@ -75,14 +74,8 @@ jobs: - stage: release node_js: '10' script: - - npm run lint-docs #change after v6 released - before_deploy: - - npm run docs - deploy: - provider: surge - project: ./esdoc/ - domain: docs.sequelizejs.com - skip_cleanup: true + - npm run lint + #- npm run semantic-release stages: - lint diff --git a/CONTRIBUTING.DOCS.md b/CONTRIBUTING.DOCS.md index 586d036cc5c5..844993d92094 100644 --- a/CONTRIBUTING.DOCS.md +++ b/CONTRIBUTING.DOCS.md @@ -2,10 +2,10 @@ The sequelize documentation is divided in two parts: -* Tutorials, guides, and example based documentation are written in Markdown -* The API reference is generated automatically from source code comments with [ESDoc](http://esdoc.org) (which uses [JSDoc](http://usejsdoc.org) syntax). +- Tutorials, guides, and example based documentation are written in Markdown +- The API reference is generated automatically from source code comments with [ESDoc](http://esdoc.org) (which uses [JSDoc](http://usejsdoc.org) syntax). -The whole documentation is rendered using ESDoc and continuously deployed to [Surge](http://surge.sh). The output is produced in the `esdoc` folder. +The whole documentation is rendered using ESDoc and continuously deployed to Github Pages at https://sequelize.org. The output is produced in the `esdoc` folder. The tutorials, written in markdown, are located in the `docs` folder. ESDoc is configured to find them in the `"manual"` field of `.esdoc.json`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb1ef24ce34a..dc1de04960dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,37 +1,40 @@ -_Please note!_ The github issue tracker should only be used for feature requests and bugs with a clear description of the issue and the expected behaviour (see below). All questions belong on [Slack](https://sequelize.slack.com), [StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) or [Google groups](https://groups.google.com/forum/#!forum/sequelize). +_Please note!_ The github issue tracker should only be used for feature requests and bugs with a clear description of the issue and the expected behaviour (see below). All questions belong on [Slack](https://sequelize.slack.com) & [StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js). # Issues + Issues are always very welcome - after all, they are a big part of making sequelize better. However, there are a couple of things you can do to make the lives of the developers _much, much_ easier: ### Tell us: -* What you are doing? - * Post a _minimal_ code sample that reproduces the issue, including models and associations - * What do you expect to happen? - * What is actually happening? -* Which dialect you are using (postgres, mysql etc)? -* Which sequelize version you are using? +- What you are doing? + - Post a _minimal_ code sample that reproduces the issue, including models and associations + - What do you expect to happen? + - What is actually happening? +- Which dialect you are using (postgres, mysql etc)? +- Which sequelize version you are using? When you post code, please use [Github flavored markdown](https://help.github.com/articles/github-flavored-markdown), in order to get proper syntax highlighting! If you can even provide a pull request with a failing unit test, we will love you long time! Plus your issue will likely be fixed much faster. # Pull requests + We're glad to get pull request if any functionality is missing or something is buggy. However, there are a couple of things you can do to make life easier for the maintainers: -* Explain the issue that your PR is solving - or link to an existing issue -* Make sure that all existing tests pass -* Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md#coding-guidelines) -* Add some tests for your new functionality or a test exhibiting the bug you are solving. Ideally all new tests should not pass _without_ your changes. +- Explain the issue that your PR is solving - or link to an existing issue +- Make sure that all existing tests pass +- Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md#coding-guidelines) +- Add some tests for your new functionality or a test exhibiting the bug you are solving. Ideally all new tests should not pass _without_ your changes. - Use [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) in all new tests. Specifically this means: - don't use `EventEmitter`, `QueryChainer` or the `success`, `done` and `error` events - don't use a done callback in your test, just return the promise chain. - Small bugfixes and direct backports to the 4.x branch are accepted without tests. -* If you are adding to / changing the public API, remember to add API docs, in the form of [JSDoc style](http://usejsdoc.org/about-getting-started.html) comments. See [section 4a](#4a-check-the-documentation) for the specifics. +- If you are adding to / changing the public API, remember to add API docs, in the form of [JSDoc style](http://usejsdoc.org/about-getting-started.html) comments. See [section 4a](#4a-check-the-documentation) for the specifics. Interested? Coolio! Here is how to get started: ### 1. Prepare your environment + Here comes a little surprise: You need [Node.JS](http://nodejs.org). ### 2. Install the dependencies @@ -72,6 +75,7 @@ You may need to specify credentials using the environment variables `SEQ_PG_USER For Postgres you may also need to install the `postgresql-postgis` package (an optional component of some Postgres distributions, e.g. Ubuntu). The package will be named something like: `postgresql--postgis-`, e.g. `postgresql-9.5-postgis-2.2`. You should be able to find the exact package name on a Debian/Ubuntu system by running the command: `apt-cache search -- -postgis`. Create the following extensions in the test database: + ``` CREATE EXTENSION postgis; CREATE EXTENSION hstore; @@ -92,20 +96,23 @@ $ docker-compose up postgres-95 mysql-57 mssql ``` > **_NOTE:_** If you get the following output: ->``` ->... ->Creating mysql-57 ... error > ->ERROR: for mysql-57 Cannot create container for service mysql-57: b'create .: volume name is too short, names should be at least two alphanumeric characters' +> ``` +> ... +> Creating mysql-57 ... error +> +> ERROR: for mysql-57 Cannot create container for service mysql-57: b'create .: volume name is too short, names should be at least two alphanumeric characters' +> +> ERROR: for mysql-57 Cannot create container for service mysql-57: b'create .: volume name is too short, names should be at least two alphanumeric characters' +> ERROR: Encountered errors while bringing up the project. +> ``` +> +> You need to set the variables `MARIADB_ENTRYPOINT` and `MYSQLDB_ENTRYPOINT` accordingly: > ->ERROR: for mysql-57 Cannot create container for service mysql-57: b'create .: volume name is too short, names should be at least two alphanumeric characters' ->ERROR: Encountered errors while bringing up the project. ->``` ->You need to set the variables `MARIADB_ENTRYPOINT` and `MYSQLDB_ENTRYPOINT` accordingly: ->```sh ->$ export MARIADB_ENTRYPOINT="$PATH_TO_PROJECT/test/config/mariadb" ->$ export MYSQLDB_ENTRYPOINT="$PATH_TO_PROJECT/test/config/mysql" ->``` +> ```sh +> $ export MARIADB_ENTRYPOINT="$PATH_TO_PROJECT/test/config/mariadb" +> $ export MYSQLDB_ENTRYPOINT="$PATH_TO_PROJECT/test/config/mysql" +> ``` **MSSQL:** Please run `npm run setup-mssql` to create the test database. diff --git a/Dockerfile b/Dockerfile index 4543daff8c4b..9cff06d2fa9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6 +FROM node:10 RUN apt-get install libpq-dev diff --git a/README.md b/README.md index 4a21d9e30b42..9d2152a0f0ed 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,14 @@ Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite an New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.org/master). You might also be interested in the [API Reference](https://sequelize.org/master/identifiers). -### v6-beta Release +### v6 Release -[![npm version](https://badgen.net/npm/v/sequelize/next)](https://www.npmjs.com/package/sequelize) - -`v6-beta` is now available. You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/other-topics/upgrade-to-v6.md). +You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/other-topics/upgrade-to-v6.md). ## Installation ```sh -$ npm i sequelize # This will install v5 -$ npm i sequelize@next # This will install v6-beta +$ npm i sequelize # This will install v6 # And one of the following: $ npm i pg pg-hstore # Postgres @@ -33,19 +30,23 @@ $ npm i tedious # Microsoft SQL Server ``` ## Documentation -- [v6-beta Documentation](https://sequelize.org/master) + +- [v6 Documentation](https://sequelize.org/master) - [v5/v4/v3 Documentation](https://sequelize.org) - [Contributing](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md) ## Responsible disclosure + If you have security issues to report, please refer to our [Responsible Disclosure Policy](https://github.com/sequelize/sequelize/blob/master/SECURITY.md) for more details. ## Resources + - [Changelog](https://github.com/sequelize/sequelize/releases) -- [Slack](http://sequelize-slack.herokuapp.com/) +- [Slack Inviter](http://sequelize-slack.herokuapp.com/) - [Stack Overflow](https://stackoverflow.com/questions/tagged/sequelize.js) ### Tools + - [CLI](https://github.com/sequelize/cli) - [With TypeScript](https://sequelize.org/master/manual/typescript.html) - [Enhanced TypeScript with decorators](https://github.com/RobinBuschmann/sequelize-typescript) @@ -54,5 +55,6 @@ If you have security issues to report, please refer to our [Responsible Disclosu - [Plugins](https://sequelize.org/master/manual/resources.html) ### Translations + - [English](https://sequelize.org/master) (OFFICIAL) - [中文文档](https://github.com/demopark/sequelize-docs-Zh-CN) (UNOFFICIAL) diff --git a/SECURITY.md b/SECURITY.md index c7de02d699e1..f204f8e8e29e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,6 +6,7 @@ The following table describes the versions of this project that are currently su | Version | Supported | | ------- | ------------------ | +| 6.x | :heavy_check_mark: | | 5.x | :heavy_check_mark: | ## Responsible disclosure policy diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index 0321088f0246..e6e722d6c519 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -66,6 +66,33 @@ This method now only takes 2 parameters, `tableName` and `options`. Previously t ## Changelog +### 6.0.0-beta.7 + +- docs(associations): belongs to many create with through table +- docs(query-interface): fix broken links [#12272](https://github.com/sequelize/sequelize/pull/12272) +- docs(sequelize): omitNull only works for CREATE/UPDATE queries +- docs: asyncify [#12297](https://github.com/sequelize/sequelize/pull/12297) +- docs: responsive [#12308](https://github.com/sequelize/sequelize/pull/12308) +- docs: update feature request template +- feat(postgres): native upsert [#12301](https://github.com/sequelize/sequelize/pull/12301) +- feat(sequelize): allow passing dialectOptions.options from url [#12404](https://github.com/sequelize/sequelize/pull/12404) +- fix(include): check if attributes specified for included through model [#12316](https://github.com/sequelize/sequelize/pull/12316) +- fix(model.destroy): return 0 with truncate [#12281](https://github.com/sequelize/sequelize/pull/12281) +- fix(mssql): empty order array generates invalid FETCH statement [#12261](https://github.com/sequelize/sequelize/pull/12261) +- fix(postgres): parse enums correctly when describing a table [#12409](https://github.com/sequelize/sequelize/pull/12409) +- fix(query): ensure correct return signature for QueryTypes.RAW [#12305](https://github.com/sequelize/sequelize/pull/12305) +- fix(query): preserve cls context for logger [#12328](https://github.com/sequelize/sequelize/pull/12328) +- fix(query-generator): do not generate GROUP BY clause if options.group is empty [#12343](https://github.com/sequelize/sequelize/pull/12343) +- fix(reload): include default scope [#12399](https://github.com/sequelize/sequelize/pull/12399) +- fix(types): add Association into OrderItem type [#12332](https://github.com/sequelize/sequelize/pull/12332) +- fix(types): add clientMinMessages to Options interface [#12375](https://github.com/sequelize/sequelize/pull/12375) +- fix(types): transactionType in Options [#12377](https://github.com/sequelize/sequelize/pull/12377) +- fix(types): add support for optional values in "where" clauses [#12337](https://github.com/sequelize/sequelize/pull/12337) +- fix(types): add missing fields to 'FindOrCreateType' [#12338](https://github.com/sequelize/sequelize/pull/12338) +- fix: add missing sql and parameters properties to some query errors [#12299](https://github.com/sequelize/sequelize/pull/12299) +- fix: remove custom inspect [#12262](https://github.com/sequelize/sequelize/pull/12262) +- refactor: cleanup query generators [#12304](https://github.com/sequelize/sequelize/pull/12304) + ### 6.0.0-beta.6 - docs(add-constraint): options.fields support diff --git a/package.json b/package.json index c21cc41e2c44..c91968e0b349 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,16 @@ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, + "release": { + "branch": "master", + "verifyConditions": [ + "@semantic-release/npm", + "@semantic-release/github" + ] + }, + "publishConfig": { + "tag": "latest" + }, "scripts": { "lint": "eslint lib test --quiet", "lint-docs": "markdownlint docs", From 6b32821214ccd33d4e35633acd3e1db7749a8e76 Mon Sep 17 00:00:00 2001 From: Sushant Date: Wed, 24 Jun 2020 12:22:17 +0530 Subject: [PATCH 185/414] 6.0.0-beta.7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83ff957a15d8..c17b2e716287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sequelize", - "version": "6.0.0-beta.6", + "version": "6.0.0-beta.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c91968e0b349..ce6616aed9ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sequelize", "description": "Multi dialect ORM for Node.JS", - "version": "6.0.0-beta.6", + "version": "6.0.0-beta.7", "maintainers": [ "Sascha Depold ", "Jan Aagaard Meier ", From 901bceb444080be6eda864b4c733fa23edf1595e Mon Sep 17 00:00:00 2001 From: Sushant Date: Wed, 24 Jun 2020 13:10:27 +0530 Subject: [PATCH 186/414] 6.1.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c17b2e716287..ba8011cc7225 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sequelize", - "version": "6.0.0-beta.7", + "version": "6.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ce6616aed9ae..190a8793c142 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sequelize", "description": "Multi dialect ORM for Node.JS", - "version": "6.0.0-beta.7", + "version": "6.1.0", "maintainers": [ "Sascha Depold ", "Jan Aagaard Meier ", From ddde7dcfda84dc818d145275e5e3192ebca4a2e6 Mon Sep 17 00:00:00 2001 From: Sushant Date: Wed, 24 Jun 2020 13:41:31 +0530 Subject: [PATCH 187/414] docs: update upgrade guide --- .travis.yml | 3 +- docs/manual/other-topics/upgrade-to-v6.md | 45 ++++++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index f49b366cae80..79894ac7f5f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,8 +74,7 @@ jobs: - stage: release node_js: '10' script: - - npm run lint - #- npm run semantic-release + - npm run semantic-release stages: - lint diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index e6e722d6c519..54b9239e477c 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -13,11 +13,11 @@ Sequelize v6 will only support Node 10 and up [#10821](https://github.com/sequel You should now use [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) package for CLS support. ```js - const cls = require('cls-hooked'); - const namespace = cls.createNamespace('....'); - const Sequelize = require('sequelize'); +const cls = require("cls-hooked"); +const namespace = cls.createNamespace("...."); +const Sequelize = require("sequelize"); - Sequelize.useCLS(namespace); +Sequelize.useCLS(namespace); ``` ### Database Engine Support @@ -28,7 +28,7 @@ We have updated our minimum supported database engine versions. Using older data - Bluebird has been removed. Internally all methods are now using async/await. Public API now returns native promises. Thanks to [Andy Edwards](https://github.com/jedwards1211) for this refactor work. - `Sequelize.Promise` is no longer available. -- `sequelize.import` method has been removed. +- `sequelize.import` method has been removed. CLI users should update to `sequelize-cli@6`. ### Model @@ -41,23 +41,40 @@ Option `returning: true` will no longer return attributes that are not defined i This method now tests for equality with [`_.isEqual`](https://lodash.com/docs/4.17.15#isEqual) and is now deep aware for JSON objects. Modifying a nested value for a JSON object won't mark it as changed (since it is still the same object). ```js - const instance = await MyModel.findOne(); +const instance = await MyModel.findOne(); - instance.myJsonField.someProperty = 12345; // Changed from something else to 12345 - console.log(instance.changed()); // false +instance.myJsonField.someProperty = 12345; // Changed from something else to 12345 +console.log(instance.changed()); // false - await instance.save(); // this will not save anything +await instance.save(); // this will not save anything - instance.changed('myJsonField', true); - console.log(instance.changed()); // ['myJsonField'] +instance.changed("myJsonField", true); +console.log(instance.changed()); // ['myJsonField'] - await instance.save(); // will save +await instance.save(); // will save ``` #### `Model.bulkCreate()` This method now throws `Sequelize.AggregateError` instead of `Bluebird.AggregateError`. All errors are now exposed as `errors` key. +#### `Model.upsert()` + +Native upsert is now supported for all dialects. + +```js +const [instance, created] = await MyModel.upsert({}); +``` + +Signature for this method has been changed to `Promise`. First index contains upserted `instance`, second index contains a boolean (or `null`) indicating if record was created or updated. For SQLite/Postgres, `created` value will always be `null`. + +- MySQL - Implemented with ON DUPLICATE KEY UPDATE +- PostgreSQL - Implemented with ON CONFLICT DO UPDATE +- SQLite - Implemented with ON CONFLICT DO UPDATE +- MSSQL - Implemented with MERGE statement + +_Note for Postgres users:_ If upsert payload contains PK field, then PK will be used as the conflict target. Otherwise first unique constraint will be selected as the conflict key. + ### QueryInterface #### `addConstraint` @@ -75,7 +92,7 @@ This method now only takes 2 parameters, `tableName` and `options`. Previously t - docs: responsive [#12308](https://github.com/sequelize/sequelize/pull/12308) - docs: update feature request template - feat(postgres): native upsert [#12301](https://github.com/sequelize/sequelize/pull/12301) -- feat(sequelize): allow passing dialectOptions.options from url [#12404](https://github.com/sequelize/sequelize/pull/12404) +- feat(sequelize): allow passing dialectOptions.options from url [#12404](https://github.com/sequelize/sequelize/pull/12404) - fix(include): check if attributes specified for included through model [#12316](https://github.com/sequelize/sequelize/pull/12316) - fix(model.destroy): return 0 with truncate [#12281](https://github.com/sequelize/sequelize/pull/12281) - fix(mssql): empty order array generates invalid FETCH statement [#12261](https://github.com/sequelize/sequelize/pull/12261) @@ -129,7 +146,7 @@ This method now only takes 2 parameters, `tableName` and `options`. Previously t - fix(mssql): use uppercase for engine table and columns [#12212](https://github.com/sequelize/sequelize/pull/12212) - fix(pool): show deprecation when engine is not supported [#12218](https://github.com/sequelize/sequelize/pull/12218) - fix(postgres): addColumn support ARRAY(ENUM) [#12259](https://github.com/sequelize/sequelize/pull/12259) -- fix(query): do not bind $ used within a whole-word [#12250](https://github.com/sequelize/sequelize/pull/12250) +- fix(query): do not bind \$ used within a whole-word [#12250](https://github.com/sequelize/sequelize/pull/12250) - fix(query-generator): handle literal for substring based operators [#12210](https://github.com/sequelize/sequelize/pull/12210) - fix(query-interface): allow passing null for query interface insert [#11931](https://github.com/sequelize/sequelize/pull/11931) - fix(query-interface): allow sequelize.fn and sequelize.literal in fields of IndexesOptions [#12224](https://github.com/sequelize/sequelize/pull/12224) From 77b5fe6f4a08eae4c1cc7393e525174f7009edde Mon Sep 17 00:00:00 2001 From: Sushant Date: Wed, 24 Jun 2020 14:21:19 +0530 Subject: [PATCH 188/414] docs: add mention for QueryInterface breaking changes --- docs/manual/other-topics/upgrade-to-v6.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index 54b9239e477c..35a3ee688ab3 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -29,6 +29,7 @@ We have updated our minimum supported database engine versions. Using older data - Bluebird has been removed. Internally all methods are now using async/await. Public API now returns native promises. Thanks to [Andy Edwards](https://github.com/jedwards1211) for this refactor work. - `Sequelize.Promise` is no longer available. - `sequelize.import` method has been removed. CLI users should update to `sequelize-cli@6`. +- All instances of QueryInterface and QueryGenerator have been renamed to their lowerCamelCase variants eg. queryInterface and queryGenerator when used as property names on Model and Dialect, the class names remain the same. ### Model From e36212c8731634e64dc4a805c64d2aa11b756492 Mon Sep 17 00:00:00 2001 From: Shahar Hadas Date: Fri, 26 Jun 2020 08:03:02 +0300 Subject: [PATCH 189/414] fix(mssql): returning data for bulkUpdate (#12413) --- lib/dialects/mssql/query.js | 4 ++++ test/integration/model/update.test.js | 19 +++++++++++++++++++ types/lib/model.d.ts | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index bc45d0d650cf..81cb22dddaf4 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -197,6 +197,10 @@ class Query extends AbstractQuery { return data[0]; } if (this.isBulkUpdateQuery()) { + if (this.options.returning) { + return this.handleSelectQuery(data); + } + return rowCount; } if (this.isBulkDeleteQuery()) { diff --git a/test/integration/model/update.test.js b/test/integration/model/update.test.js index 91fbb3f42fac..fda69409baff 100644 --- a/test/integration/model/update.test.js +++ b/test/integration/model/update.test.js @@ -134,6 +134,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); } + if (_.get(current.dialect.supports, 'returnValues.output')) { + it('should output the updated record', async function() { + const account = await this.Account.create({ ownerId: 2 }); + + const [, accounts] = await this.Account.update({ name: 'FooBar' }, { + where: { + id: account.get('id') + }, + returning: true + }); + + const firstAcc = accounts[0]; + expect(firstAcc.name).to.be.equal('FooBar'); + + await firstAcc.reload(); + expect(firstAcc.ownerId, 'Reloaded as output update only return primary key and changed fields').to.be.equal(2); + }); + } + if (current.dialect.supports['LIMIT ON UPDATE']) { it('should only update one row', async function() { await this.Account.create({ diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 6c6b61ca0952..b2e132e326a1 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2010,7 +2010,7 @@ export abstract class Model extends Hooks { /** * Update multiple instances that match the where options. The promise returns an array with one or two * elements. The first element is always the number of affected rows, while the second element is the actual - * affected rows (only supported in postgres with `options.returning` true.) + * affected rows (only supported in postgres and mssql with `options.returning` true.) */ public static update( this: { new(): M } & typeof Model, From 871157bc07ccdbf2445581b8becd297ede887c22 Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Fri, 26 Jun 2020 01:19:17 -0700 Subject: [PATCH 190/414] feat(types): added optional stricter typing for Model attributes (#12405) --- docs/manual/other-topics/typescript.md | 74 ++- types/index.d.ts | 6 + types/lib/associations/belongs-to-many.d.ts | 24 +- types/lib/associations/belongs-to.d.ts | 7 +- types/lib/associations/has-many.d.ts | 16 +- types/lib/associations/has-one.d.ts | 6 +- types/lib/hooks.d.ts | 135 +++-- types/lib/model.d.ts | 608 ++++++++++++-------- types/lib/query-interface.d.ts | 47 +- types/lib/sequelize.d.ts | 159 ++--- types/lib/utils.d.ts | 15 +- types/test/define.ts | 58 +- types/test/e2e/docs-example.ts | 2 +- types/test/errors.ts | 2 +- types/test/findOne.ts | 7 + types/test/models/User.ts | 24 +- types/test/models/UserGroup.ts | 2 + types/test/sequelize.ts | 6 +- types/test/transaction.ts | 2 +- types/test/where.ts | 8 +- 20 files changed, 778 insertions(+), 430 deletions(-) create mode 100644 types/test/findOne.ts diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index ef557aecd483..ce45acf854ea 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -19,7 +19,21 @@ Example of a minimal TypeScript project: import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize'; import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize'; -class User extends Model { +// These are the minimum attributes needed to create a User +interface UserCreationAttributes { + name: string; + preferredName: string | null; +} + +// These are all the attributes in the User model +interface UserAttributes extends UserCreationAttributes { + id: number; +} + +// You can choose to omit the `UserAttributes` and `UserCreationAttributes` +// generic types to simplify your types. This will come at the cost of making +// typechecking slightly less strict. +class User extends Model implements UserAttributes { public id!: number; // Note that the `null assertion` `!` is required in strict mode. public name!: string; public preferredName!: string | null; // for nullable fields @@ -49,7 +63,16 @@ class User extends Model { const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); -class Project extends Model { +interface ProjectAttributes { + ownerId: number; + name: string; +} + +interface ProjectAttributes extends ProjectCreationAttributes { + id: number; +} + +class Project extends Model implements ProjectAttributes { public id!: number; public ownerId!: number; public name!: string; @@ -58,7 +81,12 @@ class Project extends Model { public readonly updatedAt!: Date; } -class Address extends Model { +interface AddressAttributes { + userId: number; + address: string; +} + +class Address extends Model implements AddressAttributes { public userId!: number; public address!: string; @@ -149,21 +177,45 @@ async function stuff() { ## Usage of `sequelize.define` -TypeScript doesn't know how to generate a `class` definition when we use the `sequelize.define` method to define a Model. Therefore, we need to do some manual work and declare an interface and a type, and eventually cast the result of `.define` to the _static_ type. +In Sequelize versions before v5, the default way of defining a model involved using `sequelize.define`. It's still possible to define models with that, and you can also add typings to these models using interfaces. ```ts -// We need to declare an interface for our model that is basically what our class would be -interface MyModel extends Model { +// We recommend you declare an interface for the attributes, for stricter typechecking +interface MyModelAttributes { readonly id: number; + name: string; +} + +interface MyModelCreationAttributes extends Optional {} + +// We need to declare an interface for our model that is basically what our class would be +interface MyModel extends Model, MyModelAttributes {} + +const MyDefineModel = sequelize.define('MyDefineModel', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + } +}); + +async function stuffTwo() { + const myModel = await MyDefineModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(myModel.id); } +``` + +If you're comfortable with somewhat less strict typing for the attributes on a model, you can save some code by defining the Instance to just extend `Model` without any attributes in the generic types. -// Need to declare the static model so `findOne` etc. use correct types. -type MyModelStatic = typeof Model & { - new (values?: object, options?: BuildOptions): MyModel; +```ts +// We need to declare an interface for our model that is basically what our class would be +interface MyModel extends Model { + readonly id: number; + name: string; } -// TS can't derive a proper class definition from a `.define` call, therefor we need to cast here. -const MyDefineModel = sequelize.define('MyDefineModel', { +const MyDefineModel = sequelize.define('MyDefineModel', { id: { primaryKey: true, type: DataTypes.INTEGER.UNSIGNED, diff --git a/types/index.d.ts b/types/index.d.ts index 6bd346a8af13..d604421b9b65 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -17,3 +17,9 @@ export { BaseError as Error } from './lib/errors'; export { useInflection } from './lib/utils'; export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable }; export { Validator as validator } from './lib/utils/validator-extras'; + +/** + * Type helper for making certain fields of an object optional. This is helpful + * for creating the `CreationAttributes` from your `Attributes` for a Model. + */ +export type Optional = Omit & Partial>; diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index 2b642d62043f..9aa9e14e807a 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -104,7 +104,7 @@ export class BelongsToMany ext * The options for the getAssociations mixin of the belongsToMany association. * @see BelongsToManyGetAssociationsMixin */ -export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions { +export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions { /** * A list of the attributes from the join table that you want to select. */ @@ -149,9 +149,9 @@ export type BelongsToManyGetAssociationsMixin = ( * @see BelongsToManySetAssociationsMixin */ export interface BelongsToManySetAssociationsMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, + extends FindOptions, + BulkCreateOptions, + InstanceUpdateOptions, InstanceDestroyOptions { through?: JoinTableAttributes; } @@ -191,9 +191,9 @@ export type BelongsToManySetAssociationsMixin = ( * @see BelongsToManyAddAssociationsMixin */ export interface BelongsToManyAddAssociationsMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, + extends FindOptions, + BulkCreateOptions, + InstanceUpdateOptions, InstanceDestroyOptions { through?: JoinTableAttributes; } @@ -233,9 +233,9 @@ export type BelongsToManyAddAssociationsMixin = ( * @see BelongsToManyAddAssociationMixin */ export interface BelongsToManyAddAssociationMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, + extends FindOptions, + BulkCreateOptions, + InstanceUpdateOptions, InstanceDestroyOptions { through?: JoinTableAttributes; } @@ -274,7 +274,7 @@ export type BelongsToManyAddAssociationMixin = ( * The options for the createAssociation mixin of the belongsToMany association. * @see BelongsToManyCreateAssociationMixin */ -export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions { +export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions { through?: JoinTableAttributes; } /** @@ -455,7 +455,7 @@ export type BelongsToManyHasAssociationsMixin = ( * The options for the countAssociations mixin of the belongsToMany association. * @see BelongsToManyCountAssociationsMixin */ -export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable { +export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable { /** * Apply a scope on the related model, or remove its default scope by passing false. */ diff --git a/types/lib/associations/belongs-to.d.ts b/types/lib/associations/belongs-to.d.ts index a88f33221703..17754ac93775 100644 --- a/types/lib/associations/belongs-to.d.ts +++ b/types/lib/associations/belongs-to.d.ts @@ -30,7 +30,7 @@ export class BelongsTo extends * The options for the getAssociation mixin of the belongsTo association. * @see BelongsToGetAssociationMixin */ -export interface BelongsToGetAssociationMixinOptions extends FindOptions { +export interface BelongsToGetAssociationMixinOptions extends FindOptions { /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -61,7 +61,7 @@ export type BelongsToGetAssociationMixin = (options?: BelongsToGetAssoci * The options for the setAssociation mixin of the belongsTo association. * @see BelongsToSetAssociationMixin */ -export interface BelongsToSetAssociationMixinOptions extends SaveOptions { +export interface BelongsToSetAssociationMixinOptions extends SaveOptions { /** * Skip saving this after setting the foreign key if false. */ @@ -95,7 +95,8 @@ export type BelongsToSetAssociationMixin = ( * The options for the createAssociation mixin of the belongsTo association. * @see BelongsToCreateAssociationMixin */ -export interface BelongsToCreateAssociationMixinOptions extends CreateOptions, BelongsToSetAssociationMixinOptions {} +export interface BelongsToCreateAssociationMixinOptions + extends CreateOptions, BelongsToSetAssociationMixinOptions {} /** * The createAssociation mixin applied to models with belongsTo. diff --git a/types/lib/associations/has-many.d.ts b/types/lib/associations/has-many.d.ts index bbe68276d509..802ad45a7181 100644 --- a/types/lib/associations/has-many.d.ts +++ b/types/lib/associations/has-many.d.ts @@ -36,7 +36,7 @@ export class HasMany extends A * The options for the getAssociations mixin of the hasMany association. * @see HasManyGetAssociationsMixin */ -export interface HasManyGetAssociationsMixinOptions extends FindOptions { +export interface HasManyGetAssociationsMixinOptions extends FindOptions { /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -74,7 +74,7 @@ export type HasManyGetAssociationsMixin = (options?: HasManyGetAssociati * The options for the setAssociations mixin of the hasMany association. * @see HasManySetAssociationsMixin */ -export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} +export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} /** * The setAssociations mixin applied to models with hasMany. @@ -110,7 +110,7 @@ export type HasManySetAssociationsMixin = ( * The options for the addAssociations mixin of the hasMany association. * @see HasManyAddAssociationsMixin */ -export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} +export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} /** * The addAssociations mixin applied to models with hasMany. @@ -146,7 +146,7 @@ export type HasManyAddAssociationsMixin = ( * The options for the addAssociation mixin of the hasMany association. * @see HasManyAddAssociationMixin */ -export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} +export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} /** * The addAssociation mixin applied to models with hasMany. @@ -182,7 +182,7 @@ export type HasManyAddAssociationMixin = ( * The options for the createAssociation mixin of the hasMany association. * @see HasManyCreateAssociationMixin */ -export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} +export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} /** * The createAssociation mixin applied to models with hasMany. @@ -218,7 +218,7 @@ export type HasManyCreateAssociationMixin = ( * The options for the removeAssociation mixin of the hasMany association. * @see HasManyRemoveAssociationMixin */ -export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} +export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} /** * The removeAssociation mixin applied to models with hasMany. @@ -254,7 +254,7 @@ export type HasManyRemoveAssociationMixin = ( * The options for the removeAssociations mixin of the hasMany association. * @see HasManyRemoveAssociationsMixin */ -export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} +export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} /** * The removeAssociations mixin applied to models with hasMany. @@ -362,7 +362,7 @@ export type HasManyHasAssociationsMixin = ( * The options for the countAssociations mixin of the hasMany association. * @see HasManyCountAssociationsMixin */ -export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable { +export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable { /** * Apply a scope on the related model, or remove its default scope by passing false. */ diff --git a/types/lib/associations/has-one.d.ts b/types/lib/associations/has-one.d.ts index 07c846f36bab..7b41fc05196e 100644 --- a/types/lib/associations/has-one.d.ts +++ b/types/lib/associations/has-one.d.ts @@ -28,7 +28,7 @@ export class HasOne extends As * The options for the getAssociation mixin of the hasOne association. * @see HasOneGetAssociationMixin */ -export interface HasOneGetAssociationMixinOptions extends FindOptions { +export interface HasOneGetAssociationMixinOptions extends FindOptions { /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -59,7 +59,7 @@ export type HasOneGetAssociationMixin = (options?: HasOneGetAssociationM * The options for the setAssociation mixin of the hasOne association. * @see HasOneSetAssociationMixin */ -export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions { +export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions { /** * Skip saving this after setting the foreign key if false. */ @@ -93,7 +93,7 @@ export type HasOneSetAssociationMixin = ( * The options for the createAssociation mixin of the hasOne association. * @see HasOneCreateAssociationMixin */ -export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} +export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} /** * The createAssociation mixin applied to models with hasOne. diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index bdd9261b5418..228de7f9efde 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -21,40 +21,50 @@ export type HookReturn = Promise | void; * Options for Model.init. We mostly duplicate the Hooks here, since there is no way to combine the two * interfaces. */ -export interface ModelHooks { +export interface ModelHooks { beforeValidate(instance: M, options: ValidationOptions): HookReturn; afterValidate(instance: M, options: ValidationOptions): HookReturn; - beforeCreate(attributes: M, options: CreateOptions): HookReturn; - afterCreate(attributes: M, options: CreateOptions): HookReturn; + beforeCreate(attributes: M, options: CreateOptions): HookReturn; + afterCreate(attributes: M, options: CreateOptions): HookReturn; beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; beforeRestore(instance: M, options: InstanceRestoreOptions): HookReturn; afterRestore(instance: M, options: InstanceRestoreOptions): HookReturn; - beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; - afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; - beforeSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; - afterSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; - beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; - afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; - beforeBulkDestroy(options: DestroyOptions): HookReturn; - afterBulkDestroy(options: DestroyOptions): HookReturn; - beforeBulkRestore(options: RestoreOptions): HookReturn; - afterBulkRestore(options: RestoreOptions): HookReturn; - beforeBulkUpdate(options: UpdateOptions): HookReturn; - afterBulkUpdate(options: UpdateOptions): HookReturn; - beforeFind(options: FindOptions): HookReturn; - beforeCount(options: CountOptions): HookReturn; - beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; - beforeFindAfterOptions(options: FindOptions): HookReturn; - afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn; + beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; + afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; + beforeSave( + instance: M, + options: InstanceUpdateOptions | CreateOptions + ): HookReturn; + afterSave( + instance: M, + options: InstanceUpdateOptions | CreateOptions + ): HookReturn; + beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; + afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; + beforeBulkDestroy(options: DestroyOptions): HookReturn; + afterBulkDestroy(options: DestroyOptions): HookReturn; + beforeBulkRestore(options: RestoreOptions): HookReturn; + afterBulkRestore(options: RestoreOptions): HookReturn; + beforeBulkUpdate(options: UpdateOptions): HookReturn; + afterBulkUpdate(options: UpdateOptions): HookReturn; + beforeFind(options: FindOptions): HookReturn; + beforeCount(options: CountOptions): HookReturn; + beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; + beforeFindAfterOptions(options: FindOptions): HookReturn; + afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn; beforeSync(options: SyncOptions): HookReturn; afterSync(options: SyncOptions): HookReturn; beforeBulkSync(options: SyncOptions): HookReturn; afterBulkSync(options: SyncOptions): HookReturn; } -export interface SequelizeHooks extends ModelHooks { - beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; +export interface SequelizeHooks< + M extends Model = Model, + TAttributes = any, + TCreationAttributes = TAttributes +> extends ModelHooks { + beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; afterDefine(model: typeof Model): void; beforeInit(config: Config, options: Options): void; afterInit(sequelize: Sequelize): void; @@ -67,33 +77,72 @@ export interface SequelizeHooks extends ModelHooks { /** * Virtual class for deduplication */ -export class Hooks { +export class Hooks< + M extends Model = Model, + TModelAttributes extends {} = any, + TCreationAttributes extends {} = TModelAttributes +> { + /** + * A dummy variable that doesn't exist on the real object. This exists so + * Typescript can infer the type of the attributes in static functions. Don't + * try to access this! + */ + _model: M; + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + */ + _attributes: TModelAttributes; + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + */ + _creationAttributes: TCreationAttributes; + /** * Add a hook to the model * * @param name Provide a name for the hook function. It can be used to remove the hook later or to order * hooks based on some sort of priority system in the future. */ - public static addHook( + public static addHook< + H extends Hooks, + K extends keyof SequelizeHooks + >( + this: HooksStatic, hookType: K, name: string, - fn: SequelizeHooks[K] - ): C; - public static addHook( + fn: SequelizeHooks[K] + ): HooksCtor; + public static addHook< + H extends Hooks, + K extends keyof SequelizeHooks + >( + this: HooksStatic, hookType: K, - fn: SequelizeHooks[K] - ): C; + fn: SequelizeHooks[K] + ): HooksCtor; /** * Remove hook from the model */ - public static removeHook(hookType: K, name: string): C; + public static removeHook( + this: HooksStatic, + hookType: keyof SequelizeHooks, + name: string, + ): HooksCtor; /** * Check whether the mode has any hooks of this type */ - public static hasHook(hookType: K): boolean; - public static hasHooks(hookType: K): boolean; + public static hasHook( + this: HooksStatic, + hookType: keyof SequelizeHooks, + ): boolean; + public static hasHooks( + this: HooksStatic, + hookType: keyof SequelizeHooks, + ): boolean; /** * Add a hook to the model @@ -101,16 +150,28 @@ export class Hooks { * @param name Provide a name for the hook function. It can be used to remove the hook later or to order * hooks based on some sort of priority system in the future. */ - public addHook(hookType: K, name: string, fn: SequelizeHooks[K]): this; - public addHook(hookType: K, fn: SequelizeHooks[K]): this; + public addHook>( + hookType: K, + name: string, + fn: SequelizeHooks[K] + ): this; + public addHook>( + hookType: K, fn: SequelizeHooks[K]): this; /** * Remove hook from the model */ - public removeHook(hookType: K, name: string): this; + public removeHook>( + hookType: K, + name: string + ): this; /** * Check whether the mode has any hooks of this type */ - public hasHook(hookType: K): boolean; - public hasHooks(hookType: K): boolean; + public hasHook>(hookType: K): boolean; + public hasHooks>(hookType: K): boolean; } + +export type HooksCtor = typeof Hooks & { new(): H }; + +export type HooksStatic = { new(): H }; diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index b2e132e326a1..be93a4381cfe 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -45,11 +45,11 @@ export interface SearchPathable { searchPath?: string; } -export interface Filterable { +export interface Filterable { /** * Attribute has to be matched for rows to be selected for the given action. */ - where?: WhereOptions; + where?: WhereOptions; } export interface Projectable { @@ -109,7 +109,13 @@ export interface ScopeOptions { /** * The type accepted by every `where` option */ -export type WhereOptions = WhereAttributeHash | AndOperator | OrOperator | Literal | Fn | Where; +export type WhereOptions = + | WhereAttributeHash + | AndOperator + | OrOperator + | Literal + | Fn + | Where; /** * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` @@ -304,13 +310,13 @@ export interface WhereOperators { } /** Example: `[Op.or]: [{a: 5}, {a: 6}]` becomes `(a = 5 OR a = 6)` */ -export interface OrOperator { - [Op.or]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; +export interface OrOperator { + [Op.or]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; } /** Example: `[Op.and]: {a: 5}` becomes `AND (a = 5)` */ -export interface AndOperator { - [Op.and]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; +export interface AndOperator { + [Op.and]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; } /** @@ -325,7 +331,7 @@ export interface WhereGeometryOptions { * Used for the right hand side of WhereAttributeHash. * WhereAttributeHash is in there for JSON columns. */ -export type WhereValue = +export type WhereValue = | string // literal value | number // literal value | boolean // literal value @@ -333,20 +339,18 @@ export type WhereValue = | Buffer // literal value | null | WhereOperators - | WhereAttributeHash // for JSON columns + | WhereAttributeHash // for JSON columns | Col // reference another column | Fn - | OrOperator - | AndOperator + | OrOperator + | AndOperator | WhereGeometryOptions - | (string | number | Buffer | WhereAttributeHash)[] // implicit [Op.or] - // allow optional values in where object types - // Sequelize will still throw when a value in the object has the value undefined - | undefined; + | (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] + /** * A hash of attributes to describe your search. */ -export interface WhereAttributeHash { +export type WhereAttributeHash = { /** * Possible key values: * - A simple attribute name @@ -358,12 +362,12 @@ export interface WhereAttributeHash { * } * } */ - [field: string]: WhereValue | WhereOptions; + [field in keyof TAttributes]?: WhereValue | WhereOptions; } /** * Through options for Include Options */ -export interface IncludeThroughOptions extends Filterable, Projectable { +export interface IncludeThroughOptions extends Filterable, Projectable { /** * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / * `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural @@ -379,7 +383,7 @@ export type Includeable = typeof Model | Association | IncludeOptions | { all: t /** * Complex include options */ -export interface IncludeOptions extends Filterable, Projectable, Paranoid { +export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * Mark the include as duplicating, will prevent a subquery from being used. */ @@ -403,13 +407,13 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * Custom `on` clause, overrides default. */ - on?: WhereOptions; + on?: WhereOptions; /** * Note that this converts the eager load to an inner join, * unless you explicitly set `required: false` */ - where?: WhereOptions; + where?: WhereOptions; /** * If true, converts to an inner join, which means that the parent model will only be loaded if it has any @@ -453,7 +457,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { subQuery?: boolean; } -type OrderItemAssociation = Association | typeof Model | { model: typeof Model; as: string } | string +type OrderItemAssociation = Association | ModelStatic | { model: ModelStatic; as: string } | string type OrderItemColumn = string | Col | Fn | Literal export type OrderItem = | string @@ -507,7 +511,9 @@ type Omit = Pick> * * A hash of options to describe the scope of the search */ -export interface FindOptions extends QueryOptions, Filterable, Projectable, Paranoid, IndexHintable { +export interface FindOptions + extends QueryOptions, Filterable, Projectable, Paranoid, IndexHintable +{ /** * A list of associations to eagerly load using a left join (a single association is also supported). Supported is either * `{ include: Model1 }`, `{ include: [ Model1, Model2, ...]}`, `{ include: [{ model: Model1, as: 'Alias' }]}` or @@ -547,7 +553,7 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para */ lock?: | LOCK - | { level: LOCK; of: typeof Model } + | { level: LOCK; of: ModelStatic } | boolean; /** * Skip locked rows. Only supported in Postgres. @@ -562,7 +568,7 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para /** * Select group rows after groups and aggregates are computed. */ - having?: WhereOptions; + having?: WhereOptions; /** * Use sub queries (internal) @@ -570,7 +576,7 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para subQuery?: boolean; } -export interface NonNullFindOptions extends FindOptions { +export interface NonNullFindOptions extends FindOptions { /** * Throw if nothing was found. */ @@ -580,7 +586,9 @@ export interface NonNullFindOptions extends FindOptions { /** * Options for Model.count method */ -export interface CountOptions extends Logging, Transactionable, Filterable, Projectable, Paranoid, Poolable { +export interface CountOptions + extends Logging, Transactionable, Filterable, Projectable, Paranoid, Poolable +{ /** * Include options. See `find` for details */ @@ -607,7 +615,7 @@ export interface CountOptions extends Logging, Transactionable, Filterable, Proj /** * Options for Model.count when GROUP BY is used */ -export interface CountWithOptions extends CountOptions { +export interface CountWithOptions extends CountOptions { /** * GROUP BY in sql * Used in conjunction with `attributes`. @@ -616,7 +624,7 @@ export interface CountWithOptions extends CountOptions { group: GroupOption; } -export interface FindAndCountOptions extends CountOptions, FindOptions { } +export interface FindAndCountOptions extends CountOptions, FindOptions { } /** * Options for Model.build method @@ -652,11 +660,11 @@ export interface Silent { /** * Options for Model.create method */ -export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable, Hookable { +export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable, Hookable { /** * If set, only columns matching those in fields will be saved */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * On Duplicate @@ -685,28 +693,30 @@ export interface Hookable { /** * Options for Model.findOrCreate method */ -export interface FindOrCreateOptions extends Filterable, Logging, Transactionable { +export interface FindOrCreateOptions + extends FindOptions +{ /** * The fields to insert / update. Defaults to all fields */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * Default values to use if building a new instance */ - defaults?: object; + defaults?: TCreationAttributes; } /** * Options for Model.upsert method */ -export interface UpsertOptions extends Logging, Transactionable, SearchPathable, Hookable { +export interface UpsertOptions extends Logging, Transactionable, SearchPathable, Hookable { /** * The fields to insert / update. Defaults to all fields */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** - * Return the affected rows + * Return the affected rows (only for postgres) */ returning?: boolean; @@ -719,11 +729,11 @@ export interface UpsertOptions extends Logging, Transactionable, SearchPathable, /** * Options for Model.bulkCreate method */ -export interface BulkCreateOptions extends Logging, Transactionable, Hookable { +export interface BulkCreateOptions extends Logging, Transactionable, Hookable { /** * Fields to insert (defaults to all fields) */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -748,7 +758,7 @@ export interface BulkCreateOptions extends Logging, Transactionable, Hookable { * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. */ - updateOnDuplicate?: string[]; + updateOnDuplicate?: (keyof TAttributes)[]; /** * Include options. See `find` for details @@ -758,13 +768,13 @@ export interface BulkCreateOptions extends Logging, Transactionable, Hookable { /** * Return all columns or only the specified columns for the affected rows (only for postgres) */ - returning?: boolean | string[]; + returning?: boolean | (keyof TAttributes)[]; } /** * The options passed to Model.destroy in addition to truncate */ -export interface TruncateOptions extends Logging, Transactionable, Filterable, Hookable { +export interface TruncateOptions extends Logging, Transactionable, Filterable, Hookable { /** * Only used in conjuction with TRUNCATE. Truncates all tables that have foreign-key references to the * named table, or to any tables added to the group due to CASCADE. @@ -799,7 +809,7 @@ export interface TruncateOptions extends Logging, Transactionable, Filterable, H /** * Options used for Model.destroy */ -export interface DestroyOptions extends TruncateOptions { +export interface DestroyOptions extends TruncateOptions { /** * If set to true, dialects that support it will use TRUNCATE instead of DELETE FROM. If a table is * truncated the where and limit options are ignored @@ -810,7 +820,7 @@ export interface DestroyOptions extends TruncateOptions { /** * Options for Model.restore */ -export interface RestoreOptions extends Logging, Transactionable, Filterable, Hookable { +export interface RestoreOptions extends Logging, Transactionable, Filterable, Hookable { /** * If set to true, restore will find all records within the where parameter and will execute before / after @@ -827,16 +837,16 @@ export interface RestoreOptions extends Logging, Transactionable, Filterable, Ho /** * Options used for Model.update */ -export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hookable { +export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hookable { /** * Options to describe the scope of the search. */ - where: WhereOptions; + where: WhereOptions; /** * Fields to update (defaults to all fields) */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -880,7 +890,9 @@ export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hooka /** * Options used for Model.aggregate */ -export interface AggregateOptions extends QueryOptions, Filterable, Paranoid { +export interface AggregateOptions + extends QueryOptions, Filterable, Paranoid +{ /** * The type of the result. If `field` is a field in this Model, the default will be the type of that field, * otherwise defaults to float. @@ -898,12 +910,13 @@ export interface AggregateOptions extends QueryOpt /** * Options used for Instance.increment method */ -export interface IncrementDecrementOptions extends Logging, Transactionable, Silent, SearchPathable, Filterable { } +export interface IncrementDecrementOptions + extends Logging, Transactionable, Silent, SearchPathable, Filterable { } /** * Options used for Instance.increment method */ -export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptions { +export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptions { /** * The number to increment by * @@ -930,7 +943,8 @@ export interface InstanceDestroyOptions extends Logging, Transactionable { /** * Options used for Instance.update method */ -export interface InstanceUpdateOptions extends SaveOptions, SetOptions, Filterable { } +export interface InstanceUpdateOptions extends + SaveOptions, SetOptions, Filterable { } /** * Options used for Instance.set method @@ -950,12 +964,12 @@ export interface SetOptions { /** * Options used for Instance.save method */ -export interface SaveOptions extends Logging, Transactionable, Silent { +export interface SaveOptions extends Logging, Transactionable, Silent { /** * An optional array of strings, representing database columns. If fields is provided, only those columns * will be validated and saved. */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -1191,11 +1205,11 @@ export interface ModelSetterOptions { /** * Interface for Define Scope Options */ -export interface ModelScopeOptions { +export interface ModelScopeOptions { /** * Name of the scope and it's query */ - [scopeName: string]: FindOptions | ((...args: any[]) => FindOptions); + [scopeName: string]: FindOptions | ((...args: any[]) => FindOptions); } /** @@ -1338,11 +1352,11 @@ export interface ModelAttributeColumnOptions extends Co /** * Interface for Attributes provided for a column */ -export interface ModelAttributes { +export type ModelAttributes = { /** * The description of a database column */ - [name: string]: DataType | ModelAttributeColumnOptions; + [name in keyof TCreationAttributes]: DataType | ModelAttributeColumnOptions; } /** @@ -1358,13 +1372,13 @@ export interface ModelOptions { * Define the default search scope to use for this model. Scopes have the same form as the options passed to * find / findAll. */ - defaultScope?: FindOptions; + defaultScope?: FindOptions; /** * More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about * how scopes are defined, and what you can do with them */ - scopes?: ModelScopeOptions; + scopes?: ModelScopeOptions; /** * Don't persits null values. This means that all columns with null values will not be saved. @@ -1464,7 +1478,7 @@ export interface ModelOptions { * See Hooks for more information about hook * functions and their signatures. Each property can either be a function, or an array of functions. */ - hooks?: Partial>; + hooks?: Partial>; /** * An object of model wide validations. Validations have access to all model values via `this`. If the @@ -1514,7 +1528,31 @@ export interface AddScopeOptions { override: boolean; } -export abstract class Model extends Hooks { +export abstract class Model + extends Hooks, TModelAttributes, TCreationAttributes> +{ + /** + * A dummy variable that doesn't exist on the real object. This exists so + * Typescript can infer the type of the attributes in static functions. Don't + * try to access this! + * + * Before using these, I'd tried typing out the functions without them, but + * Typescript fails to infer `TAttributes` in signatures like the below. + * + * ```ts + * public static findOne, TAttributes>( + * this: { new(): M }, + * options: NonNullFindOptions + * ): Promise; + * ``` + */ + _attributes: TModelAttributes; + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + */ + _creationAttributes: TCreationAttributes; + /** The name of the database table */ public static readonly tableName: string; @@ -1593,7 +1631,10 @@ export abstract class Model extends Hooks { * @param options These options are merged with the default define options provided to the Sequelize constructor * @return Return the initialized model */ - public static init(this: ModelCtor, attributes: ModelAttributes, options: InitOptions): Model; + public static init( + this: ModelStatic, + attributes: ModelAttributes, options: InitOptions + ): Model; /** * Remove attribute from model definition @@ -1606,7 +1647,7 @@ export abstract class Model extends Hooks { * Sync this Model to the DB, that is create the table. Upon success, the callback will be called with the * model instance (this) */ - public static sync(options?: SyncOptions): Promise; + public static sync(options?: SyncOptions): Promise; /** * Drop the table represented by this Model @@ -1625,7 +1666,7 @@ export abstract class Model extends Hooks { * @param options */ public static schema( - this: { new(): M } & typeof Model, + this: ModelStatic, schema: string, options?: SchemaOptions ): { new(): M } & typeof Model; @@ -1694,10 +1735,10 @@ export abstract class Model extends Hooks { * @return Model A reference to the model, with the scope(s) applied. Calling scope again on the returned * model will clear the previous scope. */ - public static scope( - this: M, - options?: string | ScopeOptions | (string | ScopeOptions)[] | WhereAttributeHash - ): M; + public static scope( + this: ModelStatic, + options?: string | ScopeOptions | (string | ScopeOptions)[] | WhereAttributeHash + ): ModelCtor; /** * Add a new scope to the model @@ -1707,8 +1748,18 @@ export abstract class Model extends Hooks { * error if a scope with that name already exists. Pass `override: true` in the options * object to silence this error. */ - public static addScope(name: string, scope: FindOptions, options?: AddScopeOptions): void; - public static addScope(name: string, scope: (...args: any[]) => FindOptions, options?: AddScopeOptions): void; + public static addScope( + this: ModelStatic, + name: string, + scope: FindOptions, + options?: AddScopeOptions + ): void; + public static addScope( + this: ModelStatic, + name: string, + scope: (...args: any[]) => FindOptions, + options?: AddScopeOptions + ): void; /** * Search for multiple instances. @@ -1772,30 +1823,35 @@ export abstract class Model extends Hooks { * * @see {Sequelize#query} */ - public static findAll(this: { new(): M } & typeof Model, options?: FindOptions): Promise; + public static findAll( + this: ModelStatic, + options?: FindOptions): Promise; /** * Search for a single instance by its primary key. This applies LIMIT 1, so the listener will * always be called with a single instance. */ public static findByPk( - this: { new (): M } & typeof Model, + this: ModelStatic, identifier: Identifier, - options: Omit + options: Omit, 'where'> ): Promise; public static findByPk( - this: { new (): M } & typeof Model, + this: ModelStatic, identifier?: Identifier, - options?: Omit + options?: Omit, 'where'> ): Promise; /** * Search for a single instance. Returns the first instance found, or null if none can be found. */ - public static findOne(this: { new (): M } & typeof Model, options: NonNullFindOptions): Promise; public static findOne( - this: { new(): M } & typeof Model, - options?: FindOptions + this: ModelStatic, + options: NonNullFindOptions + ): Promise; + public static findOne( + this: ModelStatic, + options?: FindOptions ): Promise; /** @@ -1807,38 +1863,47 @@ export abstract class Model extends Hooks { * @return Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in * which case the complete data result is returned. */ - public static aggregate( - this: { new(): M } & typeof Model, - field: keyof M, + public static aggregate( + this: ModelStatic, + field: keyof M['_attributes'] | '*', aggregateFunction: string, - options?: AggregateOptions + options?: AggregateOptions ): Promise; /** * Count number of records if group by is used */ - public static count(options: CountWithOptions): Promise<{ [key: string]: number }>; + public static count( + this: ModelStatic, + options: CountWithOptions + ): Promise<{ [key: string]: number }>; /** * Count the number of records matching the provided where clause. * * If you provide an `include` option, the number of matching associations will be counted instead. */ - public static count(options?: CountOptions): Promise; + public static count( + this: ModelStatic, + options?: CountOptions + ): Promise; /** * Find all the rows matching your query, within a specified offset / limit, and get the total number of * rows matching your query. This is very usefull for paging * * ```js - * const { rows, count } = await Model.findAndCountAll({ + * Model.findAndCountAll({ * where: ..., * limit: 12, * offset: 12 - * }); + * }).then(result => { + * ... + * }) * ``` - * In the above example, `rows` will contain rows 13 through 24, while `count` will return - * the total number of rows that matched your query. + * In the above example, `result.rows` will contain rows 13 through 24, while `result.count` will return + * the + * total number of rows that matched your query. * * When you add includes, only those which are required (either because they have a where clause, or * because @@ -1859,43 +1924,43 @@ export abstract class Model extends Hooks { * profiles will be counted */ public static findAndCountAll( - this: { new(): M } & typeof Model, - options?: FindAndCountOptions + this: ModelStatic, + options?: FindAndCountOptions ): Promise<{ rows: M[]; count: number }>; /** * Find the maximum value of field */ - public static max( - this: { new(): M } & typeof Model, - field: keyof M, - options?: AggregateOptions + public static max( + this: ModelStatic, + field: keyof M['_attributes'], + options?: AggregateOptions ): Promise; /** * Find the minimum value of field */ - public static min( - this: { new(): M } & typeof Model, - field: keyof M, - options?: AggregateOptions + public static min( + this: ModelStatic, + field: keyof M['_attributes'], + options?: AggregateOptions ): Promise; /** * Find the sum of field */ - public static sum( - this: { new(): M } & typeof Model, - field: keyof M, - options?: AggregateOptions + public static sum( + this: ModelStatic, + field: keyof M['_attributes'], + options?: AggregateOptions ): Promise; /** * Builds a new model instance. Values is an object of key value pairs, must be defined but can be empty. */ public static build( - this: { new(): M } & typeof Model, - record?: object, + this: ModelStatic, + record?: M['_creationAttributes'], options?: BuildOptions ): M; @@ -1903,8 +1968,8 @@ export abstract class Model extends Hooks { * Undocumented bulkBuild */ public static bulkBuild( - this: { new(): M } & typeof Model, - records: object[], + this: ModelStatic, + records: (M['_creationAttributes'])[], options?: BuildOptions ): M[]; @@ -1912,24 +1977,28 @@ export abstract class Model extends Hooks { * Builds a new model instance and calls save on it. */ public static create( - this: { new(): M } & typeof Model, - values?: object, - options?: CreateOptions + this: ModelStatic, + values?: M['_creationAttributes'], + options?: CreateOptions ): Promise; - public static create(values: object, options: CreateOptions & { returning: false }): Promise; + public static create( + this: ModelStatic, + values: M['_creationAttributes'], + options: CreateOptions & { returning: false } + ): Promise; /** * Find a row that matches the query, or build (but don't save) the row if none is found. - * The successful result of the promise will be [instance, initialized] - Make sure to use destructuring such as `const [instance, wasBuilt] = ...` + * The successfull result of the promise will be (instance, initialized) - Make sure to use `.then(([...]))` */ public static findOrBuild( - this: { new(): M } & typeof Model, - options: FindOrCreateOptions + this: ModelStatic, + options: FindOrCreateOptions ): Promise<[M, boolean]>; /** * Find a row that matches the query, or build and save the row if none is found - * The successful result of the promise will be [instance, created] - Make sure to use destructuring such as `const [instance, wasCreated] = ...` + * The successful result of the promise will be (instance, created) - Make sure to use `.then(([...]))` * * If no transaction is passed in the `options` object, a new transaction will be created internally, to * prevent the race condition where a matching row is created by another connection after the find but @@ -1939,8 +2008,8 @@ export abstract class Model extends Hooks { * will be created instead, and any unique constraint violation will be handled internally. */ public static findOrCreate( - this: { new(): M } & typeof Model, - options: FindOrCreateOptions + this: ModelStatic, + options: FindOrCreateOptions ): Promise<[M, boolean]>; /** @@ -1948,8 +2017,8 @@ export abstract class Model extends Hooks { * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again */ public static findCreateFind( - this: { new(): M } & typeof Model, - options: FindOrCreateOptions + this: ModelStatic, + options: FindOrCreateOptions ): Promise<[M, boolean]>; /** @@ -1960,17 +2029,21 @@ export abstract class Model extends Hooks { * * **Implementation details:** * - * * MySQL - Implemented with ON DUPLICATE KEY UPDATE - * * PostgreSQL - Implemented with ON CONFLICT DO UPDATE - * * SQLite - Implemented with ON CONFLICT DO UPDATE - * * MSSQL - Implemented with MERGE statement + * * MySQL - Implemented as a single query `INSERT values ON DUPLICATE KEY UPDATE values` + * * PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN + * unique_constraint UPDATE + * * SQLite - Implemented as two queries `INSERT; UPDATE`. This means that the update is executed + * regardless + * of whether the row already existed or not * - * **Note** that PostgreSQL/SQLite returns null for created, no matter if the row was created or updated. + * **Note** that SQLite returns null for created, no matter if the row was created or updated. This is + * because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know + * whether the row was inserted or not. */ public static upsert( - this: { new(): M } & typeof Model, - values: object, - options?: UpsertOptions + this: ModelStatic, + values: M['_creationAttributes'], + options?: UpsertOptions ): Promise<[M, boolean | null]>; /** @@ -1985,27 +2058,36 @@ export abstract class Model extends Hooks { * @param records List of objects (key/value pairs) to create instances from */ public static bulkCreate( - this: { new(): M } & typeof Model, - records: object[], - options?: BulkCreateOptions + this: ModelStatic, + records: (M['_creationAttributes'])[], + options?: BulkCreateOptions ): Promise; /** * Truncate all instances of the model. This is a convenient method for Model.destroy({ truncate: true }). */ - public static truncate(options?: TruncateOptions): Promise; + public static truncate( + this: ModelStatic, + options?: TruncateOptions + ): Promise; /** * Delete multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. * * @return Promise The number of destroyed rows */ - public static destroy(options?: DestroyOptions): Promise; + public static destroy( + this: ModelStatic, + options?: DestroyOptions + ): Promise; /** * Restore multiple instances if `paranoid` is enabled. */ - public static restore(options?: RestoreOptions): Promise; + public static restore( + this: ModelStatic, + options?: RestoreOptions + ): Promise; /** * Update multiple instances that match the where options. The promise returns an array with one or two @@ -2013,36 +2095,36 @@ export abstract class Model extends Hooks { * affected rows (only supported in postgres and mssql with `options.returning` true.) */ public static update( - this: { new(): M } & typeof Model, - values: object, - options: UpdateOptions + this: ModelStatic, + values: Partial, + options: UpdateOptions ): Promise<[number, M[]]>; /** * Increments a single field. */ - public static increment( - this: { new(): M }, - field: K, - options: IncrementDecrementOptionsWithBy + public static increment( + this: ModelStatic, + field: keyof M['_attributes'], + options: IncrementDecrementOptionsWithBy ): Promise; /** * Increments multiple fields by the same value. */ - public static increment( - this: { new(): M }, - fields: K[], - options: IncrementDecrementOptionsWithBy + public static increment( + this: ModelStatic, + fields: (keyof M['_attributes'])[], + options: IncrementDecrementOptionsWithBy ): Promise; /** * Increments multiple fields by different values. */ - public static increment( - this: { new(): M }, - fields: { [key in K]?: number }, - options: IncrementDecrementOptions + public static increment( + this: ModelStatic, + fields: { [key in keyof M['_attributes']]?: number }, + options: IncrementDecrementOptions ): Promise; /** @@ -2063,12 +2145,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeValidate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; public static beforeValidate( - this: { new(): M } & typeof Model, + this: ModelStatic, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; @@ -2079,12 +2161,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterValidate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; public static afterValidate( - this: { new(): M } & typeof Model, + this: ModelStatic, fn: (instance: M, options: ValidationOptions) => HookReturn ): void; @@ -2095,13 +2177,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with attributes, options */ public static beforeCreate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (attributes: M, options: CreateOptions) => HookReturn + fn: (instance: M, options: CreateOptions) => HookReturn ): void; public static beforeCreate( - this: { new(): M } & typeof Model, - fn: (attributes: M, options: CreateOptions) => HookReturn + this: ModelStatic, + fn: (instance: M, options: CreateOptions) => HookReturn ): void; /** @@ -2111,13 +2193,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with attributes, options */ public static afterCreate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (attributes: M, options: CreateOptions) => HookReturn + fn: (instance: M, options: CreateOptions) => HookReturn ): void; public static afterCreate( - this: { new(): M } & typeof Model, - fn: (attributes: M, options: CreateOptions) => HookReturn + this: ModelStatic, + fn: (instance: M, options: CreateOptions) => HookReturn ): void; /** @@ -2127,12 +2209,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeDestroy( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; public static beforeDestroy( - this: { new(): M } & typeof Model, + this: ModelStatic, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; @@ -2143,12 +2225,12 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterDestroy( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; public static afterDestroy( - this: { new(): M } & typeof Model, + this: ModelStatic, fn: (instance: M, options: InstanceDestroyOptions) => HookReturn ): void; @@ -2159,13 +2241,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeUpdate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instance: M, options: UpdateOptions) => HookReturn + fn: (instance: M, options: UpdateOptions) => HookReturn ): void; public static beforeUpdate( - this: { new(): M } & typeof Model, - fn: (instance: M, options: UpdateOptions) => HookReturn + this: ModelStatic, + fn: (instance: M, options: UpdateOptions) => HookReturn ): void; /** @@ -2175,13 +2257,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterUpdate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instance: M, options: UpdateOptions) => HookReturn + fn: (instance: M, options: UpdateOptions) => HookReturn ): void; public static afterUpdate( - this: { new(): M } & typeof Model, - fn: (instance: M, options: UpdateOptions) => HookReturn + this: ModelStatic, + fn: (instance: M, options: UpdateOptions) => HookReturn ): void; /** @@ -2191,13 +2273,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static beforeSave( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; public static beforeSave( - this: { new(): M } & typeof Model, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + this: ModelStatic, + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; /** @@ -2207,13 +2289,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance, options */ public static afterSave( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; public static afterSave( - this: { new(): M } & typeof Model, - fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + this: ModelStatic, + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn ): void; /** @@ -2223,13 +2305,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instances, options */ public static beforeBulkCreate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; public static beforeBulkCreate( - this: { new(): M } & typeof Model, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + this: ModelStatic, + fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; /** @@ -2239,13 +2321,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instances, options */ public static afterBulkCreate( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; public static afterBulkCreate( - this: { new(): M } & typeof Model, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + this: ModelStatic, + fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; /** @@ -2254,8 +2336,13 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => HookReturn): void; - public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => HookReturn): void; + public static beforeBulkDestroy( + this: ModelStatic, + name: string, fn: (options: BulkCreateOptions) => HookReturn): void; + public static beforeBulkDestroy( + this: ModelStatic, + fn: (options: BulkCreateOptions) => HookReturn + ): void; /** * A hook that is run after destroying instances in bulk @@ -2263,8 +2350,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => HookReturn): void; - public static afterBulkDestroy(fn: (options: DestroyOptions) => HookReturn): void; + public static afterBulkDestroy( + this: ModelStatic, + name: string, fn: (options: DestroyOptions) => HookReturn + ): void; + public static afterBulkDestroy( + this: ModelStatic, + fn: (options: DestroyOptions) => HookReturn + ): void; /** * A hook that is run after updating instances in bulk @@ -2272,8 +2365,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => HookReturn): void; - public static beforeBulkUpdate(fn: (options: UpdateOptions) => HookReturn): void; + public static beforeBulkUpdate( + this: ModelStatic, + name: string, fn: (options: UpdateOptions) => HookReturn + ): void; + public static beforeBulkUpdate( + this: ModelStatic, + fn: (options: UpdateOptions) => HookReturn + ): void; /** * A hook that is run after updating instances in bulk @@ -2281,8 +2380,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => HookReturn): void; - public static afterBulkUpdate(fn: (options: UpdateOptions) => HookReturn): void; + public static afterBulkUpdate( + this: ModelStatic, + name: string, fn: (options: UpdateOptions) => HookReturn + ): void; + public static afterBulkUpdate( + this: ModelStatic, + fn: (options: UpdateOptions) => HookReturn + ): void; /** * A hook that is run before a find (select) query @@ -2290,8 +2395,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeFind(name: string, fn: (options: FindOptions) => HookReturn): void; - public static beforeFind(fn: (options: FindOptions) => HookReturn): void; + public static beforeFind( + this: ModelStatic, + name: string, fn: (options: FindOptions) => HookReturn + ): void; + public static beforeFind( + this: ModelStatic, + fn: (options: FindOptions) => HookReturn + ): void; /** * A hook that is run before a count query @@ -2299,8 +2410,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeCount(name: string, fn: (options: CountOptions) => HookReturn): void; - public static beforeCount(fn: (options: CountOptions) => HookReturn): void; + public static beforeCount( + this: ModelStatic, + name: string, fn: (options: CountOptions) => HookReturn + ): void; + public static beforeCount( + this: ModelStatic, + fn: (options: CountOptions) => HookReturn + ): void; /** * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded @@ -2308,8 +2425,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => HookReturn): void; - public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => HookReturn): void; + public static beforeFindAfterExpandIncludeAll( + this: ModelStatic, + name: string, fn: (options: FindOptions) => HookReturn + ): void; + public static beforeFindAfterExpandIncludeAll( + this: ModelStatic, + fn: (options: FindOptions) => HookReturn + ): void; /** * A hook that is run before a find (select) query, after all option parsing is complete @@ -2317,8 +2440,14 @@ export abstract class Model extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => HookReturn): void; - public static beforeFindAfterOptions(fn: (options: FindOptions) => void): HookReturn; + public static beforeFindAfterOptions( + this: ModelStatic, + name: string, fn: (options: FindOptions) => HookReturn + ): void; + public static beforeFindAfterOptions( + this: ModelStatic, + fn: (options: FindOptions) => void + ): HookReturn; /** * A hook that is run after a find (select) query @@ -2327,13 +2456,13 @@ export abstract class Model extends Hooks { * @param fn A callback function that is called with instance(s), options */ public static afterFind( - this: { new(): M } & typeof Model, + this: ModelStatic, name: string, - fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn + fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn ): void; public static afterFind( - this: { new(): M } & typeof Model, - fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn + this: ModelStatic, + fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn ): void; /** @@ -2374,7 +2503,7 @@ export abstract class Model extends Hooks { * @param options Options for the association */ public static hasOne( - this: ModelCtor, target: ModelCtor, options?: HasOneOptions + this: ModelStatic, target: ModelStatic, options?: HasOneOptions ): HasOne; /** @@ -2387,7 +2516,7 @@ export abstract class Model extends Hooks { * @param options Options for the association */ public static belongsTo( - this: ModelCtor, target: ModelCtor, options?: BelongsToOptions + this: ModelStatic, target: ModelStatic, options?: BelongsToOptions ): BelongsTo; /** @@ -2434,16 +2563,17 @@ export abstract class Model extends Hooks { * Similarily, when fetching through a join table with custom attributes, these attributes will be * available as an object with the name of the through model. * ```js - * const projects = await user.getProjects(); - * const p1 = projects[0]; - * p1.UserProjects.started // Is this project started yet? + * user.getProjects().then(projects => { + * const p1 = projects[0] + * p1.userprojects.started // Is this project started yet? + * }) * ``` * * @param target The model that will be associated with hasOne relationship * @param options Options for the association */ public static hasMany( - this: ModelCtor, target: ModelCtor, options?: HasManyOptions + this: ModelStatic, target: ModelStatic, options?: HasManyOptions ): HasMany; /** @@ -2485,9 +2615,10 @@ export abstract class Model extends Hooks { * Similarily, when fetching through a join table with custom attributes, these attributes will be * available as an object with the name of the through model. * ```js - * const porjects = await user.getProjects(); - * const p1 = projects[0]; - * p1.userprojects.started // Is this project started yet? + * user.getProjects().then(projects => { + * const p1 = projects[0] + * p1.userprojects.started // Is this project started yet? + * }) * ``` * * @param target The model that will be associated with hasOne relationship @@ -2495,7 +2626,7 @@ export abstract class Model extends Hooks { * */ public static belongsToMany( - this: ModelCtor, target: ModelCtor, options: BelongsToManyOptions + this: ModelStatic, target: ModelStatic, options: BelongsToManyOptions ): BelongsToMany; /** @@ -2512,7 +2643,7 @@ export abstract class Model extends Hooks { * Builds a new model instance. * @param values an object of key value pairs */ - constructor(values?: object, options?: BuildOptions); + constructor(values?: TCreationAttributes, options?: BuildOptions); /** * Get an object representing the query for this instance, use with `options.where` @@ -2522,12 +2653,12 @@ export abstract class Model extends Hooks { /** * Get the value of the underlying data value */ - public getDataValue(key: K): this[K]; + public getDataValue(key: K): TModelAttributes[K]; /** * Update the underlying data value */ - public setDataValue(key: K, value: this[K]): void; + public setDataValue(key: K, value: TModelAttributes[K]): void; /** * If no key is given, returns all values of the instance, also invoking virtual getters. @@ -2537,7 +2668,7 @@ export abstract class Model extends Hooks { * * @param options.plain If set to true, included instances will be returned as plain objects */ - public get(options?: { plain?: boolean; clone?: boolean }): object; + public get(options?: { plain?: boolean; clone?: boolean }): TModelAttributes; public get(key: K, options?: { plain?: boolean; clone?: boolean }): this[K]; public get(key: string, options?: { plain?: boolean; clone?: boolean }): unknown; @@ -2565,10 +2696,10 @@ export abstract class Model extends Hooks { * @param options.raw If set to true, field and virtual setters will be ignored * @param options.reset Clear all previously set data values */ - public set(key: K, value: this[K], options?: SetOptions): this; - public set(keys: Partial, options?: SetOptions): this; - public setAttributes(key: K, value: this[K], options?: SetOptions): this; - public setAttributes(keys: object, options?: SetOptions): this; + public set(key: K, value: TModelAttributes[K], options?: SetOptions): this; + public set(keys: Partial, options?: SetOptions): this; + public setAttributes(key: K, value: TModelAttributes[K], options?: SetOptions): this; + public setAttributes(keys: Partial, options?: SetOptions): this; /** * If changed is called with a string it will return a boolean indicating whether the value of that key in @@ -2598,7 +2729,7 @@ export abstract class Model extends Hooks { * * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. */ - public save(options?: SaveOptions): Promise; + public save(options?: SaveOptions): Promise; /** * Refresh the current instance in-place, i.e. update the object with current data from the DB and return @@ -2606,7 +2737,7 @@ export abstract class Model extends Hooks { * return a new instance. With this method, all references to the Instance are updated with the new data * and no new objects are created. */ - public reload(options?: FindOptions): Promise; + public reload(options?: FindOptions): Promise; /** * Validate the attribute of this instance according to validation rules set in the model definition. @@ -2621,8 +2752,8 @@ export abstract class Model extends Hooks { /** * This is the same as calling `set` and then calling `save`. */ - public update(key: K, value: this[K], options?: InstanceUpdateOptions): Promise; - public update(keys: object, options?: InstanceUpdateOptions): Promise; + public update(key: K, value: this[K], options?: InstanceUpdateOptions): Promise; + public update(keys: object, options?: InstanceUpdateOptions): Promise; /** * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will @@ -2655,9 +2786,9 @@ export abstract class Model extends Hooks { * If an array is provided, the same is true for each column. * If and object is provided, each column is incremented by the value given. */ - public increment( - fields: K | K[] | Partial, - options?: IncrementDecrementOptionsWithBy + public increment( + fields: K | K[] | Partial, + options?: IncrementDecrementOptionsWithBy ): Promise; /** @@ -2680,9 +2811,9 @@ export abstract class Model extends Hooks { * If an array is provided, the same is true for each column. * If and object is provided, each column is decremented by the value given */ - public decrement( - fields: K | K[] | Partial, - options?: IncrementDecrementOptionsWithBy + public decrement( + fields: K | K[] | Partial, + options?: IncrementDecrementOptionsWithBy ): Promise; /** @@ -2713,6 +2844,11 @@ export abstract class Model extends Hooks { export type ModelType = typeof Model; -export type ModelCtor = { new(): M } & ModelType; +// Do not switch the order of `typeof Model` and `{ new(): M }`. For +// instances created by `sequelize.define` to typecheck well, `typeof Model` +// must come first for unknown reasons. +export type ModelCtor = typeof Model & { new(): M }; + +export type ModelStatic = { new(): M }; export default Model; diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 13cb915df7b0..7e7e01019d07 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -1,5 +1,15 @@ import { DataType } from './data-types'; -import { Logging, Model, ModelAttributeColumnOptions, ModelAttributes, Transactionable, WhereOptions, Filterable, Poolable } from './model'; +import { + Logging, + Model, + ModelAttributeColumnOptions, + ModelAttributes, + Transactionable, + WhereOptions, + Filterable, + Poolable, + ModelCtor, ModelStatic +} from './model'; import QueryTypes = require('./query-types'); import { Sequelize, RetryOptions } from './sequelize'; import { Transaction } from './transaction'; @@ -71,15 +81,15 @@ export interface QueryOptions extends Logging, Transactionable, Poolable { fieldMap?: FieldMap; } -export interface QueryOptionsWithWhere extends QueryOptions, Filterable { +export interface QueryOptionsWithWhere extends QueryOptions, Filterable { } -export interface QueryOptionsWithModel extends QueryOptions { +export interface QueryOptionsWithModel extends QueryOptions { /** * A sequelize model used to build the returned model instances (used to be called callee) */ - model: typeof Model; + model: ModelStatic; } export interface QueryOptionsWithType extends QueryOptions { @@ -189,7 +199,7 @@ export interface IndexesOptions { /** * Optional where parameter for index. Can be used to limit the index to certain rows. */ - where?: WhereOptions; + where?: WhereOptions; /** * Prefix to append to the index name. @@ -215,7 +225,7 @@ export interface AddDefaultConstraintOptions extends BaseConstraintOptions { export interface AddCheckConstraintOptions extends BaseConstraintOptions { type: 'check'; - where?: WhereOptions; + where?: WhereOptions; } export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { @@ -321,9 +331,9 @@ export class QueryInterface { * @param attributes Hash of attributes, key is attribute name, value is data type * @param options Table options. */ - public createTable( + public createTable( tableName: string | { schema?: string; tableName?: string }, - attributes: ModelAttributes, + attributes: ModelAttributes, options?: QueryInterfaceCreateTableOptions ): Promise; @@ -490,11 +500,11 @@ export class QueryInterface { /** * Updates a row */ - public update( - instance: Model, + public update( + instance: M, tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions ): Promise; @@ -504,7 +514,7 @@ export class QueryInterface { public bulkUpdate( tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions, attributes?: string[] | string ): Promise; @@ -512,14 +522,19 @@ export class QueryInterface { /** * Deletes a row */ - public delete(instance: Model | null, tableName: TableName, identifier: WhereOptions, options?: QueryOptions): Promise; + public delete( + instance: Model | null, + tableName: TableName, + identifier: WhereOptions, + options?: QueryOptions + ): Promise; /** * Deletes multiple rows at once */ public bulkDelete( tableName: TableName, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions, model?: typeof Model ): Promise; @@ -532,11 +547,11 @@ export class QueryInterface { /** * Increments a row value */ - public increment( + public increment( instance: Model, tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions ): Promise; diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index e5388a8d47ad..673ac03b4478 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -371,7 +371,7 @@ export interface Options extends Logging { /** * Sets global permanent hooks. */ - hooks?: Partial; + hooks?: Partial>; /** * Set to `true` to automatically minify aliases generated by sequelize. @@ -517,8 +517,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with attributes, options */ - public static beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public static beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + public static beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public static beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; /** * A hook that is run after creating a single instance @@ -526,8 +526,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with attributes, options */ - public static afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public static afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + public static afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public static afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; /** * A hook that is run before destroying a single instance @@ -553,8 +553,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instance, options */ - public static beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public static beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + public static beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public static beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; /** * A hook that is run after updating a single instance @@ -562,8 +562,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instance, options */ - public static afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public static afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + public static afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public static afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; /** * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` @@ -571,8 +571,11 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instance, options */ - public static beforeSave(name: string, fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - public static beforeSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; + public static beforeSave( + name: string, + fn: (instance: Model, options: UpdateOptions | CreateOptions) => void + ): void; + public static beforeSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; /** * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` @@ -580,8 +583,13 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instance, options */ - public static afterSave(name: string, fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; - public static afterSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; + public static afterSave( + name: string, + fn: (instance: Model, options: UpdateOptions | CreateOptions) => void + ): void; + public static afterSave( + fn: (instance: Model, options: UpdateOptions | CreateOptions) => void + ): void; /** * A hook that is run before creating instances in bulk @@ -589,8 +597,11 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instances, options */ - public static beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public static beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public static beforeBulkCreate( + name: string, + fn: (instances: Model[], options: BulkCreateOptions) => void + ): void; + public static beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; /** * A hook that is run after creating instances in bulk @@ -598,8 +609,10 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instances, options */ - public static afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public static afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public static afterBulkCreate( + name: string, fn: (instances: Model[], options: BulkCreateOptions) => void + ): void; + public static afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; /** * A hook that is run before destroying instances in bulk @@ -607,8 +620,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; - public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; + public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; + public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; /** * A hook that is run after destroying instances in bulk @@ -616,8 +629,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; - public static afterBulkDestroy(fn: (options: DestroyOptions) => void): void; + public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; + public static afterBulkDestroy(fn: (options: DestroyOptions) => void): void; /** * A hook that is run after updating instances in bulk @@ -625,8 +638,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public static beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; + public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public static beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; /** * A hook that is run after updating instances in bulk @@ -634,8 +647,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public static afterBulkUpdate(fn: (options: UpdateOptions) => void): void; + public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public static afterBulkUpdate(fn: (options: UpdateOptions) => void): void; /** * A hook that is run before a find (select) query @@ -643,8 +656,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeFind(name: string, fn: (options: FindOptions) => void): void; - public static beforeFind(fn: (options: FindOptions) => void): void; + public static beforeFind(name: string, fn: (options: FindOptions) => void): void; + public static beforeFind(fn: (options: FindOptions) => void): void; /** * A hook that is run before a connection is established @@ -688,8 +701,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; - public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; + public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; + public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; /** * A hook that is run before a find (select) query, after all option parsing is complete @@ -697,8 +710,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; - public static beforeFindAfterOptions(fn: (options: FindOptions) => void): void; + public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; + public static beforeFindAfterOptions(fn: (options: FindOptions) => void): void; /** * A hook that is run after a find (select) query @@ -708,10 +721,10 @@ export class Sequelize extends Hooks { */ public static afterFind( name: string, - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void + fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void ): void; public static afterFind( - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void + fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void ): void; /** @@ -722,10 +735,10 @@ export class Sequelize extends Hooks { */ public static beforeDefine( name: string, - fn: (attributes: ModelAttributes, options: ModelOptions) => void + fn: (attributes: ModelAttributes, options: ModelOptions) => void ): void; public static beforeDefine( - fn: (attributes: ModelAttributes, options: ModelOptions) => void + fn: (attributes: ModelAttributes, options: ModelOptions) => void ): void; /** @@ -877,8 +890,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with attributes, options */ - public beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + public beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; /** * A hook that is run after creating a single instance @@ -886,8 +899,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with attributes, options */ - public afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; - public afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + public afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; /** * A hook that is run before destroying a single instance @@ -913,8 +926,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instance, options */ - public beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + public beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; /** * A hook that is run after updating a single instance @@ -922,8 +935,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instance, options */ - public afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; - public afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + public afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; /** * A hook that is run before creating instances in bulk @@ -931,8 +944,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instances, options */ - public beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; /** * A hook that is run after creating instances in bulk @@ -940,8 +953,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with instances, options */ - public afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; - public afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; /** * A hook that is run before destroying instances in bulk @@ -949,8 +962,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; - public beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; + public beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; + public beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; /** * A hook that is run after destroying instances in bulk @@ -958,8 +971,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; - public afterBulkDestroy(fn: (options: DestroyOptions) => void): void; + public afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; + public afterBulkDestroy(fn: (options: DestroyOptions) => void): void; /** * A hook that is run after updating instances in bulk @@ -967,8 +980,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; + public beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; /** * A hook that is run after updating instances in bulk @@ -976,8 +989,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; - public afterBulkUpdate(fn: (options: UpdateOptions) => void): void; + public afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public afterBulkUpdate(fn: (options: UpdateOptions) => void): void; /** * A hook that is run before a find (select) query @@ -985,8 +998,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public beforeFind(name: string, fn: (options: FindOptions) => void): void; - public beforeFind(fn: (options: FindOptions) => void): void; + public beforeFind(name: string, fn: (options: FindOptions) => void): void; + public beforeFind(fn: (options: FindOptions) => void): void; /** * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded @@ -994,8 +1007,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; - public beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; + public beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; + public beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; /** * A hook that is run before a find (select) query, after all option parsing is complete @@ -1003,8 +1016,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; - public beforeFindAfterOptions(fn: (options: FindOptions) => void): void; + public beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; + public beforeFindAfterOptions(fn: (options: FindOptions) => void): void; /** * A hook that is run after a find (select) query @@ -1014,9 +1027,9 @@ export class Sequelize extends Hooks { */ public afterFind( name: string, - fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void + fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void ): void; - public afterFind(fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void): void; + public afterFind(fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void): void; /** * A hook that is run before a define call @@ -1024,8 +1037,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with attributes, options */ - public beforeDefine(name: string, fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; - public beforeDefine(fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; + public beforeDefine(name: string, fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; + public beforeDefine(fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; /** * A hook that is run after a define call @@ -1151,7 +1164,11 @@ export class Sequelize extends Hooks { * @param options These options are merged with the default define options provided to the Sequelize * constructor */ - public define(modelName: string, attributes: ModelAttributes, options?: ModelOptions): typeof Model; + public define( + modelName: string, + attributes: ModelAttributes, + options?: ModelOptions + ): ModelCtor; /** * Fetch a Model which is already defined @@ -1216,7 +1233,7 @@ export class Sequelize extends Hooks { public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query( sql: string | { query: string; values: unknown[] }, - options: QueryOptionsWithModel + options: QueryOptionsWithModel ): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType & { plain: true }): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; @@ -1305,7 +1322,7 @@ export class Sequelize extends Hooks { * * @param [options] The options passed to Model.destroy in addition to truncate */ - public truncate(options?: DestroyOptions): Promise; + public truncate(options?: DestroyOptions): Promise; /** * Drop all tables defined through this sequelize instance. This is done by calling Model.drop on each model @@ -1435,14 +1452,14 @@ export function literal(val: string): Literal; * * @param args Each argument will be joined by AND */ -export function and(...args: (WhereOperators | WhereAttributeHash | Where)[]): AndOperator; +export function and(...args: (WhereOperators | WhereAttributeHash | Where)[]): AndOperator; /** * An OR query * * @param args Each argument will be joined by OR */ -export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): OrOperator; +export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): OrOperator; /** * Creates an object representing nested where conditions for postgres's json data-type. @@ -1455,7 +1472,7 @@ export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): Or export function json(conditionsOrPath: string | object, value?: string | number | boolean): Json; export type AttributeType = Fn | Col | Literal | ModelAttributeColumnOptions | string; -export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string | symbol | null; +export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string | symbol | null; /** * A way of specifying attr = condition. diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts index 468162fad0a3..b7b5df81df17 100644 --- a/types/lib/utils.d.ts +++ b/types/lib/utils.d.ts @@ -1,5 +1,5 @@ import { DataType } from './data-types'; -import { Model, WhereOptions } from './model'; +import { Model, ModelCtor, WhereOptions } from './model'; export type Primitive = 'string' | 'number' | 'boolean'; @@ -24,16 +24,21 @@ export function formatNamedParameters(sql: string, parameters: { }, dialect: string): string; export function cloneDeep(obj: T, fn?: (el: unknown) => unknown): T; -export interface OptionsForMapping { +export interface OptionsForMapping { attributes?: string[]; - where?: WhereOptions; + where?: WhereOptions; } /** Expand and normalize finder options */ -export function mapFinderOptions(options: T, model: typeof Model): T; +export function mapFinderOptions>( + options: T, + model: ModelCtor +): T; /* Used to map field names in attributes and where conditions */ -export function mapOptionFieldNames(options: T, model: typeof Model): T; +export function mapOptionFieldNames>( + options: T, model: ModelCtor +): T; export function mapWhereFieldNames(attributes: object, model: typeof Model): object; /** Used to map field names in values */ diff --git a/types/test/define.ts b/types/test/define.ts index c3f39da11e11..2c67bb78bf1d 100644 --- a/types/test/define.ts +++ b/types/test/define.ts @@ -1,32 +1,56 @@ -import { DataTypes, Model } from 'sequelize'; +import { BuildOptions, DataTypes, Model } from 'sequelize'; import { sequelize } from './connection'; // I really wouldn't recommend this, but if you want you can still use define() and interfaces -interface User extends Model { - id: number; - username: string; - firstName: string; - lastName: string; - createdAt: Date; - updatedAt: Date; +interface UserAttributes { + id: number; + username: string; + firstName: string; + lastName: string; + createdAt: Date; + updatedAt: Date; } -type UserModel = { - new (): User - customStaticMethod(): unknown -} & typeof Model; +interface UserCreationAttributes extends Partial {} -const User = sequelize.define('User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UserModel; +interface UserModel extends Model, UserAttributes {} -async function test() { - User.customStaticMethod(); +const User = sequelize.define( + 'User', { firstName: DataTypes.STRING }, { tableName: 'users' }); - const user: User = new User(); +async function test() { + const user: UserModel = new User() as UserModel; - const user2: User = (await User.findOne()) as User; + const user2: UserModel | null = await User.findOne(); + if (!user2) return; user2.firstName = 'John'; await user2.save(); } + +// The below doesn't define Attribute types, but should still work +interface UntypedUserModel extends Model, UserAttributes {} + +type UntypedUserModelStatic = typeof Model & { + new (values?: keyof any, options?: BuildOptions): UntypedUserModel; + customStaticMethod(): unknown; +} +const UntypedUser = sequelize.define( + 'User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UntypedUserModelStatic; + +UntypedUser.customStaticMethod = () => {}; + +async function testUntyped() { + UntypedUser.customStaticMethod(); + + const user: UntypedUserModel = new UntypedUser() as UntypedUserModel; + + const user2: UntypedUserModel | null = await UntypedUser.findOne(); + if (!user2) return; + + user2.firstName = 'John'; + + await user2.save(); +} diff --git a/types/test/e2e/docs-example.ts b/types/test/e2e/docs-example.ts index f5abd97dd88e..0840e5a6a0f7 100644 --- a/types/test/e2e/docs-example.ts +++ b/types/test/e2e/docs-example.ts @@ -1,6 +1,6 @@ // This file is used as example. -import {BuildOptions, DataTypes, Model, Sequelize} from 'sequelize'; +import { BuildOptions, DataTypes, Model, Sequelize } from 'sequelize'; import { Association, HasManyAddAssociationMixin, diff --git a/types/test/errors.ts b/types/test/errors.ts index 89f9d6375e7b..c1de7ab5a46c 100644 --- a/types/test/errors.ts +++ b/types/test/errors.ts @@ -5,7 +5,7 @@ import { OptimisticLockError } from '../lib/errors'; async function test() { try { - await User.create({ username: 'john_doe' }); + await User.create({ username: 'john_doe', firstName: 'John' }); } catch (e) { if (e instanceof UniqueConstraintError) { throw new Error((e as UniqueConstraintError).sql); diff --git a/types/test/findOne.ts b/types/test/findOne.ts new file mode 100644 index 000000000000..3b3f4e675aa0 --- /dev/null +++ b/types/test/findOne.ts @@ -0,0 +1,7 @@ +import { User } from "./models/User"; + +User.findOne({ where: { firstName: 'John' }}); + +// The below line should be an error if uncommented, thanks to the new +// TAttributes-based typechecking +// User.findOne({ where: { blah: 'blah2' }}); diff --git a/types/test/models/User.ts b/types/test/models/User.ts index 99170ac61c22..152a5a89a1b2 100644 --- a/types/test/models/User.ts +++ b/types/test/models/User.ts @@ -7,11 +7,26 @@ import { FindOptions, Model, ModelCtor, - Op + Op, + Optional } from 'sequelize'; import { sequelize } from '../connection'; -export class User extends Model { +export interface UserAttributes { + id: number; + username: string; + firstName: string; + lastName: string; + groupId: number; +} + +/** + * In this case, we make every single field optional. In real cases, only + * fields that have default/autoincrement values should be made optional. + */ +export interface UserCreationAttributes extends Optional {} + +export class User extends Model implements UserAttributes { public static associations: { group: BelongsTo; }; @@ -20,11 +35,11 @@ export class User extends Model { public username!: string; public firstName!: string; public lastName!: string; + public groupId!: number; public createdAt!: Date; public updatedAt!: Date; // mixins for association (optional) - public groupId!: number; public group?: UserGroup; public getGroup!: BelongsToGetAssociationMixin; public setGroup!: BelongsToSetAssociationMixin; @@ -36,6 +51,7 @@ User.init( firstName: DataTypes.STRING, lastName: DataTypes.STRING, username: DataTypes.STRING, + groupId: DataTypes.NUMBER, }, { version: true, @@ -86,7 +102,7 @@ User.afterFind((users, options) => { }); // TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly -User.addHook('beforeFind', 'test', (options: FindOptions) => { +User.addHook('beforeFind', 'test', (options: FindOptions) => { return undefined; }); diff --git a/types/test/models/UserGroup.ts b/types/test/models/UserGroup.ts index 498d4e75875d..66cc046fbc3a 100644 --- a/types/test/models/UserGroup.ts +++ b/types/test/models/UserGroup.ts @@ -14,6 +14,8 @@ import { } from 'sequelize'; import { sequelize } from '../connection'; +// This class doesn't extend the generic Model, but should still +// function just fine, with a bit less safe type-checking export class UserGroup extends Model { public static associations: { users: HasMany diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts index 0ae09acaf8a7..2b5af1c5dfb4 100644 --- a/types/test/sequelize.ts +++ b/types/test/sequelize.ts @@ -1,4 +1,4 @@ -import { Config, Sequelize, Model, QueryTypes } from 'sequelize'; +import { Config, Sequelize, Model, QueryTypes, ModelCtor } from 'sequelize'; import { Fn } from '../lib/utils'; Sequelize.useCLS({ @@ -6,7 +6,7 @@ Sequelize.useCLS({ export const sequelize = new Sequelize({ hooks: { - afterConnect: (connection, config: Config) => { + afterConnect: (connection: unknown, config: Config) => { // noop } }, @@ -56,7 +56,7 @@ const rnd: Fn = sequelize.random(); class Model1 extends Model{} class Model2 extends Model{} -const myModel: typeof Model1 = sequelize.models.asd; +const myModel: ModelCtor = sequelize.models.asd; myModel.hasOne(Model2) myModel.findAll(); diff --git a/types/test/transaction.ts b/types/test/transaction.ts index e4aeaacf9fdc..96bdb14574fd 100644 --- a/types/test/transaction.ts +++ b/types/test/transaction.ts @@ -8,7 +8,7 @@ async function trans() { transaction.afterCommit(() => console.log('transaction complete')); User.create( { - data: 123, + firstName: 'John', }, { transaction, diff --git a/types/test/where.ts b/types/test/where.ts index 370c0e1656fb..b268212b1019 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -25,7 +25,7 @@ where = { date: new Date() }; -// Optional values +// Optional values let whereWithOptionals: { needed: number; optional?: number } = { needed: 2 }; where = whereWithOptionals; @@ -39,10 +39,16 @@ where = whereWithOptionals; const and: AndOperator = { [Op.and]: { a: 5 }, // AND (a = 5) }; +const typedAnd: AndOperator<{ a: number }> = { + [Op.and]: { a: 5 }, // AND (a = 5) +}; const or: OrOperator = { [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) }; +const typedOr: OrOperator<{ a: number }> = { + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) +}; let operators: WhereOperators = { [Op.gt]: 6, // > 6 From 962ed3ca66725e8eef2773e958d8c67a9ed5ce9a Mon Sep 17 00:00:00 2001 From: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Date: Fri, 26 Jun 2020 10:20:09 +0200 Subject: [PATCH 191/414] fix(types): references support for string (#12407) --- types/lib/model.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index be93a4381cfe..8297da2257b4 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1293,9 +1293,9 @@ export interface ModelAttributeColumnOptions extends Co comment?: string; /** - * An object with reference configurations + * An object with reference configurations or the column name as string */ - references?: ModelAttributeColumnReferencesOptions; + references?: string | ModelAttributeColumnReferencesOptions; /** * What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or From aeb318aeaacbc471690d116bf127a059512f21f9 Mon Sep 17 00:00:00 2001 From: Shahar Hadas Date: Sat, 27 Jun 2020 07:57:13 +0300 Subject: [PATCH 192/414] fix(mssql): insert/upsert operations do not return all fields (#12433) --- lib/dialects/mssql/query.js | 13 +++++++++++++ test/integration/model/create.test.js | 17 +++++++++++++++++ test/integration/model/upsert.test.js | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index 81cb22dddaf4..e95aac6f6f66 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -380,6 +380,19 @@ class Query extends AbstractQuery { id = id || autoIncrementAttributeAlias && results && results[0][autoIncrementAttributeAlias]; this.instance[autoIncrementAttribute] = id; + + if (this.instance.dataValues) { + for (const key in results[0]) { + if (Object.prototype.hasOwnProperty.call(results[0], key)) { + const record = results[0][key]; + + const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + + this.instance.dataValues[attr && attr.fieldName || key] = record; + } + } + } + } } } diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 81ded27d43a9..ee7007f116e3 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -1420,4 +1420,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(spy.called).to.be.ok; }); + + if (current.dialect.supports.returnValues) { + it('should return default value set by the database (create)', async function() { + + const User = this.sequelize.define('User', { + name: DataTypes.STRING, + code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } + }); + + await User.sync({ force: true }); + + const user = await User.create({ name: 'FooBar' }); + + expect(user.name).to.be.equal('FooBar'); + expect(user.code).to.be.equal(2020); + }); + } }); diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index 2f22c0dee1f9..7187b5f7aaf9 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -577,6 +577,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); }); + + it('should return default value set by the database (upsert)', async function() { + const User = this.sequelize.define('User', { + name: { type: DataTypes.STRING, primaryKey: true }, + code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } + }); + + await User.sync({ force: true }); + + const [user, created] = await User.upsert({ name: 'Test default value' }, { returning: true }); + + expect(user.name).to.be.equal('Test default value'); + expect(user.code).to.be.equal(2020); + + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.true; + } + }); } }); } From 54737510dfc8de3c1df163d7f881e7dafb05b8ea Mon Sep 17 00:00:00 2001 From: Martin Zagora Date: Sat, 27 Jun 2020 07:01:28 +0200 Subject: [PATCH 193/414] docs(typescript): error in the name of interface (#12430) --- docs/manual/other-topics/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index ce45acf854ea..936babfd9a12 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -63,7 +63,7 @@ class User extends Model implements User const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); -interface ProjectAttributes { +interface ProjectCreationAttributes { ownerId: number; name: string; } From 9c446f9c96dcf00147a296b09263a73b9b26b03e Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Sat, 27 Jun 2020 04:14:56 -0700 Subject: [PATCH 194/414] fix(types): fixed types for model.init and sequelize.define; update docs (#12435) --- docs/manual/other-topics/typescript.md | 284 ++++++++++++------ types/lib/model.d.ts | 2 +- types/lib/sequelize.d.ts | 4 +- types/test/define.ts | 42 ++- types/test/models/User.ts | 10 +- types/test/typescriptDocs/Define.ts | 38 +++ .../test/typescriptDocs/DefineNoAttributes.ts | 30 ++ types/test/typescriptDocs/ModelInit.ts | 179 +++++++++++ .../typescriptDocs/ModelInitNoAttributes.ts | 47 +++ 9 files changed, 530 insertions(+), 106 deletions(-) create mode 100644 types/test/typescriptDocs/Define.ts create mode 100644 types/test/typescriptDocs/DefineNoAttributes.ts create mode 100644 types/test/typescriptDocs/ModelInit.ts create mode 100644 types/test/typescriptDocs/ModelInitNoAttributes.ts diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index 936babfd9a12..717e4db91d7b 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -13,27 +13,42 @@ In order to avoid installation bloat for non TS users, you must install the foll ## Usage -Example of a minimal TypeScript project: +Example of a minimal TypeScript project with strict type-checking for attributes. + + ```ts -import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize'; -import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize'; +import { + Sequelize, + Model, + DataTypes, + HasManyGetAssociationsMixin, + HasManyAddAssociationMixin, + HasManyHasAssociationMixin, + Association, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + Optional, +} from 'sequelize'; -// These are the minimum attributes needed to create a User -interface UserCreationAttributes { - name: string; - preferredName: string | null; -} +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); // These are all the attributes in the User model -interface UserAttributes extends UserCreationAttributes { +interface UserAttributes { id: number; + name: string; + preferredName: string | null; } -// You can choose to omit the `UserAttributes` and `UserCreationAttributes` -// generic types to simplify your types. This will come at the cost of making -// typechecking slightly less strict. -class User extends Model implements UserAttributes { +// Some attributes are optional in `User.build` and `User.create` calls +interface UserCreationAttributes extends Optional {} + +class User extends Model + implements UserAttributes { public id!: number; // Note that the `null assertion` `!` is required in strict mode. public name!: string; public preferredName!: string | null; // for nullable fields @@ -45,7 +60,6 @@ class User extends Model implements User // Since TS cannot determine model association at compile time // we have to declare them here purely virtually // these will not exist until `Model.init` was called. - public getProjects!: HasManyGetAssociationsMixin; // Note the null assertions! public addProject!: HasManyAddAssociationMixin; public hasProject!: HasManyHasAssociationMixin; @@ -61,18 +75,16 @@ class User extends Model implements User }; } -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); - -interface ProjectCreationAttributes { +interface ProjectAttributes { + id: number; ownerId: number; name: string; } -interface ProjectAttributes extends ProjectCreationAttributes { - id: number; -} +interface ProjectCreationAttributes extends Optional {} -class Project extends Model implements ProjectAttributes { +class Project extends Model + implements ProjectAttributes { public id!: number; public ownerId!: number; public name!: string; @@ -86,6 +98,8 @@ interface AddressAttributes { address: string; } +// You can write `extends Model` instead, +// but that will do the exact same thing as below class Address extends Model implements AddressAttributes { public userId!: number; public address!: string; @@ -94,68 +108,77 @@ class Address extends Model implements AddressAttributes { public readonly updatedAt!: Date; } -Project.init({ - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true, +Project.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + ownerId: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, }, - ownerId: { - type: DataTypes.INTEGER.UNSIGNED, - allowNull: false, + { + sequelize, + tableName: 'projects', }, - name: { - type: new DataTypes.STRING(128), - allowNull: false, - } -}, { - sequelize, - tableName: 'projects', -}); - -User.init({ - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true, +); + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, }, - name: { - type: new DataTypes.STRING(128), - allowNull: false, + { + tableName: 'users', + sequelize, // passing the `sequelize` instance is required }, - preferredName: { - type: new DataTypes.STRING(128), - allowNull: true - } -}, { - tableName: 'users', - sequelize: sequelize, // passing the `sequelize` instance is required -}); - -Address.init({ - userId: { - type: DataTypes.INTEGER.UNSIGNED, +); + +Address.init( + { + userId: { + type: DataTypes.INTEGER.UNSIGNED, + }, + address: { + type: new DataTypes.STRING(128), + allowNull: false, + }, }, - address: { - type: new DataTypes.STRING(128), - allowNull: false, - } -}, { - tableName: 'address', - sequelize: sequelize, // passing the `sequelize` instance is required -}); + { + tableName: 'address', + sequelize, // passing the `sequelize` instance is required + }, +); // Here we associate which actually populates out pre-declared `association` static and other methods. User.hasMany(Project, { sourceKey: 'id', foreignKey: 'ownerId', - as: 'projects' // this determines the name in `associations`! + as: 'projects', // this determines the name in `associations`! }); -Address.belongsTo(User, {targetKey: 'id'}); -User.hasOne(Address,{sourceKey: 'id'}); +Address.belongsTo(User, { targetKey: 'id' }); +User.hasOne(Address, { sourceKey: 'id' }); -async function stuff() { +async function doStuffWithUser() { const newUser = await User.create({ name: 'Johnny', preferredName: 'John', @@ -170,8 +193,66 @@ async function stuff() { include: [User.associations.projects], rejectOnEmpty: true, // Specifying true here removes `null` from the return type! }); - console.log(ourUser.projects![0].name); // Note the `!` null assertion since TS can't know if we included - // the model or not + + // Note the `!` null assertion since TS can't know if we included + // the model or not + console.log(ourUser.projects![0].name); +} +``` + +### Usage without strict types for attributes + +The typings for Sequelize v5 allowed you to define models without specifying types for the attributes. This is still possible for backwards compatibility and for cases where you feel strict typing for attributes isn't worth it. + + + +```ts +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +class User extends Model { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields +} + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: 'users', + sequelize, // passing the `sequelize` instance is required + }, +); + +async function doStuffWithUserModel() { + const newUser = await User.create({ + name: 'Johnny', + preferredName: 'John', + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const foundUser = await User.findOne({ where: { name: 'Johnny' } }); + if (foundUser === null) return; + console.log(foundUser.name); } ``` @@ -179,53 +260,82 @@ async function stuff() { In Sequelize versions before v5, the default way of defining a model involved using `sequelize.define`. It's still possible to define models with that, and you can also add typings to these models using interfaces. + + ```ts +import { Sequelize, Model, DataTypes, Optional } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + // We recommend you declare an interface for the attributes, for stricter typechecking -interface MyModelAttributes { - readonly id: number; +interface UserAttributes { + id: number; name: string; } -interface MyModelCreationAttributes extends Optional {} +// Some fields are optional when calling UserModel.create() or UserModel.build() +interface UserCreationAttributes extends Optional {} // We need to declare an interface for our model that is basically what our class would be -interface MyModel extends Model, MyModelAttributes {} +interface UserInstance + extends Model, + UserAttributes {} -const MyDefineModel = sequelize.define('MyDefineModel', { +const UserModel = sequelize.define('User', { id: { primaryKey: true, type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, } }); -async function stuffTwo() { - const myModel = await MyDefineModel.findByPk(1, { +async function doStuff() { + const instance = await UserModel.findByPk(1, { rejectOnEmpty: true, }); - console.log(myModel.id); + console.log(instance.id); } ``` If you're comfortable with somewhat less strict typing for the attributes on a model, you can save some code by defining the Instance to just extend `Model` without any attributes in the generic types. + + ```ts +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + // We need to declare an interface for our model that is basically what our class would be -interface MyModel extends Model { - readonly id: number; +interface UserInstance extends Model { + id: number; name: string; } -const MyDefineModel = sequelize.define('MyDefineModel', { +const UserModel = sequelize.define('User', { id: { primaryKey: true, type: DataTypes.INTEGER.UNSIGNED, - } + }, + name: { + type: DataTypes.STRING, + }, }); -async function stuffTwo() { - const myModel = await MyDefineModel.findByPk(1, { +async function doStuff() { + const instance = await UserModel.findByPk(1, { rejectOnEmpty: true, }); - console.log(myModel.id); + console.log(instance.id); } ``` diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 8297da2257b4..af72733e2eb7 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1633,7 +1633,7 @@ export abstract class Model( this: ModelStatic, - attributes: ModelAttributes, options: InitOptions + attributes: ModelAttributes, options: InitOptions ): Model; /** diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 673ac03b4478..e1e7c446d407 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -772,7 +772,7 @@ export class Sequelize extends Hooks { * A hook that is run before sequelize.sync call * @param fn A callback function that is called with options passed to sequelize.sync */ - public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeBulkSync(dname: string, fn: (options: SyncOptions) => HookReturn): void; public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; /** @@ -1164,7 +1164,7 @@ export class Sequelize extends Hooks { * @param options These options are merged with the default define options provided to the Sequelize * constructor */ - public define( + public define( modelName: string, attributes: ModelAttributes, options?: ModelOptions diff --git a/types/test/define.ts b/types/test/define.ts index 2c67bb78bf1d..50bc194ed74d 100644 --- a/types/test/define.ts +++ b/types/test/define.ts @@ -1,4 +1,4 @@ -import { BuildOptions, DataTypes, Model } from 'sequelize'; +import { BuildOptions, DataTypes, Model, Optional } from 'sequelize'; import { sequelize } from './connection'; // I really wouldn't recommend this, but if you want you can still use define() and interfaces @@ -8,26 +8,34 @@ interface UserAttributes { username: string; firstName: string; lastName: string; - createdAt: Date; - updatedAt: Date; } -interface UserCreationAttributes extends Partial {} +interface UserCreationAttributes extends Optional {} -interface UserModel extends Model, UserAttributes {} +interface UserModel + extends Model, + UserAttributes {} const User = sequelize.define( - 'User', { firstName: DataTypes.STRING }, { tableName: 'users' }); + 'User', + { + id: { type: DataTypes.NUMBER, primaryKey: true }, + username: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + }, + { tableName: 'users' }, +); async function test() { - const user: UserModel = new User() as UserModel; + const user: UserModel = new User() as UserModel; - const user2: UserModel | null = await User.findOne(); - if (!user2) return; + const user2: UserModel | null = await User.findOne(); + if (!user2) return; - user2.firstName = 'John'; + user2.firstName = 'John'; - await user2.save(); + await user2.save(); } // The below doesn't define Attribute types, but should still work @@ -36,9 +44,17 @@ interface UntypedUserModel extends Model, UserAttributes {} type UntypedUserModelStatic = typeof Model & { new (values?: keyof any, options?: BuildOptions): UntypedUserModel; customStaticMethod(): unknown; -} +}; const UntypedUser = sequelize.define( - 'User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UntypedUserModelStatic; + 'User', + { + id: { type: DataTypes.NUMBER, primaryKey: true }, + username: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + }, + { tableName: 'users' }, +) as UntypedUserModelStatic; UntypedUser.customStaticMethod = () => {}; diff --git a/types/test/models/User.ts b/types/test/models/User.ts index 152a5a89a1b2..b29534cb3669 100644 --- a/types/test/models/User.ts +++ b/types/test/models/User.ts @@ -21,8 +21,8 @@ export interface UserAttributes { } /** - * In this case, we make every single field optional. In real cases, only - * fields that have default/autoincrement values should be made optional. + * In this case, we make most fields optional. In real cases, + * only fields that have default/autoincrement values should be made optional. */ export interface UserCreationAttributes extends Optional {} @@ -48,6 +48,10 @@ export class User extends Model implemen User.init( { + id: { + type: DataTypes.NUMBER, + primaryKey: true, + }, firstName: DataTypes.STRING, lastName: DataTypes.STRING, username: DataTypes.STRING, @@ -62,7 +66,7 @@ User.init( }, setterMethods: { b(val: string) { - (this).username = val; + this.username = val; }, }, scopes: { diff --git a/types/test/typescriptDocs/Define.ts b/types/test/typescriptDocs/Define.ts new file mode 100644 index 000000000000..9e222311053d --- /dev/null +++ b/types/test/typescriptDocs/Define.ts @@ -0,0 +1,38 @@ +/** + * Keep this file in sync with the code in the "Usage of `sequelize.define`" + * section in typescript.md + */ +import { Sequelize, Model, DataTypes, Optional } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// We recommend you declare an interface for the attributes, for stricter typechecking +interface UserAttributes { + id: number; + name: string; +} + +// Some fields are optional when calling UserModel.create() or UserModel.build() +interface UserCreationAttributes extends Optional {} + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance + extends Model, + UserAttributes {} + +const UserModel = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + } +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} diff --git a/types/test/typescriptDocs/DefineNoAttributes.ts b/types/test/typescriptDocs/DefineNoAttributes.ts new file mode 100644 index 000000000000..ac2d70069a13 --- /dev/null +++ b/types/test/typescriptDocs/DefineNoAttributes.ts @@ -0,0 +1,30 @@ +/** + * Keep this file in sync with the code in the "Usage of `sequelize.define`" + * that doesn't have attribute types in typescript.md + */ +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance extends Model { + id: number; + name: string; +} + +const UserModel = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + }, +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} diff --git a/types/test/typescriptDocs/ModelInit.ts b/types/test/typescriptDocs/ModelInit.ts new file mode 100644 index 000000000000..1cfaf4230d4f --- /dev/null +++ b/types/test/typescriptDocs/ModelInit.ts @@ -0,0 +1,179 @@ +/** + * Keep this file in sync with the code in the "Usage" section in typescript.md + */ +import { + Sequelize, + Model, + DataTypes, + HasManyGetAssociationsMixin, + HasManyAddAssociationMixin, + HasManyHasAssociationMixin, + Association, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + Optional, +} from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// These are all the attributes in the User model +interface UserAttributes { + id: number; + name: string; + preferredName: string | null; +} + +// Some attributes are optional in `User.build` and `User.create` calls +interface UserCreationAttributes extends Optional {} + +class User extends Model + implements UserAttributes { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields + + // timestamps! + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + // Since TS cannot determine model association at compile time + // we have to declare them here purely virtually + // these will not exist until `Model.init` was called. + public getProjects!: HasManyGetAssociationsMixin; // Note the null assertions! + public addProject!: HasManyAddAssociationMixin; + public hasProject!: HasManyHasAssociationMixin; + public countProjects!: HasManyCountAssociationsMixin; + public createProject!: HasManyCreateAssociationMixin; + + // You can also pre-declare possible inclusions, these will only be populated if you + // actively include a relation. + public readonly projects?: Project[]; // Note this is optional since it's only populated when explicitly requested in code + + public static associations: { + projects: Association; + }; +} + +interface ProjectAttributes { + id: number; + ownerId: number; + name: string; +} + +interface ProjectCreationAttributes extends Optional {} + +class Project extends Model + implements ProjectAttributes { + public id!: number; + public ownerId!: number; + public name!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +interface AddressAttributes { + userId: number; + address: string; +} + +// You can write `extends Model` instead, +// but that will do the exact same thing as below +class Address extends Model implements AddressAttributes { + public userId!: number; + public address!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +Project.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + ownerId: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + }, + { + sequelize, + tableName: 'projects', + }, +); + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: 'users', + sequelize, // passing the `sequelize` instance is required + }, +); + +Address.init( + { + userId: { + type: DataTypes.INTEGER.UNSIGNED, + }, + address: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + }, + { + tableName: 'address', + sequelize, // passing the `sequelize` instance is required + }, +); + +// Here we associate which actually populates out pre-declared `association` static and other methods. +User.hasMany(Project, { + sourceKey: 'id', + foreignKey: 'ownerId', + as: 'projects', // this determines the name in `associations`! +}); + +Address.belongsTo(User, { targetKey: 'id' }); +User.hasOne(Address, { sourceKey: 'id' }); + +async function doStuffWithUser() { + const newUser = await User.create({ + name: 'Johnny', + preferredName: 'John', + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const project = await newUser.createProject({ + name: 'first!', + }); + + const ourUser = await User.findByPk(1, { + include: [User.associations.projects], + rejectOnEmpty: true, // Specifying true here removes `null` from the return type! + }); + + // Note the `!` null assertion since TS can't know if we included + // the model or not + console.log(ourUser.projects![0].name); +} diff --git a/types/test/typescriptDocs/ModelInitNoAttributes.ts b/types/test/typescriptDocs/ModelInitNoAttributes.ts new file mode 100644 index 000000000000..b53ea86fc12a --- /dev/null +++ b/types/test/typescriptDocs/ModelInitNoAttributes.ts @@ -0,0 +1,47 @@ +/** + * Keep this file in sync with the code in the "Usage without strict types for + * attributes" section in typescript.md + */ +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +class User extends Model { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields +} + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: 'users', + sequelize, // passing the `sequelize` instance is required + }, +); + +async function doStuffWithUserModel() { + const newUser = await User.create({ + name: 'Johnny', + preferredName: 'John', + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const foundUser = await User.findOne({ where: { name: 'Johnny' } }); + if (foundUser === null) return; + console.log(foundUser.name); +} From 2de3377dab13959c4073515ce29287de14d4221e Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Sun, 28 Jun 2020 00:22:19 -0400 Subject: [PATCH 195/414] fix(sqlite): describeTable now returns unique constraint and references (#12420) This will allow changeColumn/renameColumn/removeColumn to preserve constraints and references --- lib/dialects/sqlite/query-generator.js | 3 +- lib/dialects/sqlite/query-interface.js | 67 +++++++++- test/integration/model.test.js | 12 +- test/integration/model/sync.test.js | 9 +- .../query-interface/changeColumn.test.js | 120 ++++++++++++++++++ .../dialects/sqlite/query-generator.test.js | 2 +- 6 files changed, 206 insertions(+), 7 deletions(-) diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 9d9691fe700e..8997bb6a0e9a 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -418,7 +418,8 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { ).join(', '); const attributeNamesExport = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', '); - return `${this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE') + // Temporary tables don't support foreign keys, so creating a temporary table will not allow foreign keys to be preserved + return `${this.createTableQuery(backupTableName, attributes) }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNamesImport} FROM ${quotedTableName};` + `DROP TABLE ${quotedTableName};${ this.createTableQuery(tableName, attributes) diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index 8f31bd793ad2..ca2b68653d0f 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -4,6 +4,7 @@ const sequelizeErrors = require('../../errors'); const QueryTypes = require('../../query-types'); const { QueryInterface } = require('../abstract/query-interface'); const { cloneDeep } = require('../../utils'); +const _ = require('lodash'); /** * The interface that Sequelize uses to talk with SQLite database @@ -39,7 +40,7 @@ class SQLiteQueryInterface extends QueryInterface { options = options || {}; const fields = await this.describeTable(tableName, options); - fields[attributeName] = this.normalizeAttribute(dataTypeOrOptions); + Object.assign(fields[attributeName], this.normalizeAttribute(dataTypeOrOptions)); const sql = this.queryGenerator.removeColumnQuery(tableName, fields); const subQueries = sql.split(';').filter(q => q !== ''); @@ -168,6 +169,70 @@ class SQLiteQueryInterface extends QueryInterface { await this._dropAllTables(tableNames, skip, options); await this.sequelize.query('PRAGMA foreign_keys = ON', options); } + + /** + * @override + */ + async describeTable(tableName, options) { + let schema = null; + let schemaDelimiter = null; + + if (typeof options === 'string') { + schema = options; + } else if (typeof options === 'object' && options !== null) { + schema = options.schema || null; + schemaDelimiter = options.schemaDelimiter || null; + } + + if (typeof tableName === 'object' && tableName !== null) { + schema = tableName.schema; + tableName = tableName.tableName; + } + + const sql = this.queryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); + options = { ...options, type: QueryTypes.DESCRIBE }; + const sqlIndexes = this.queryGenerator.showIndexesQuery(tableName); + + try { + const data = await this.sequelize.query(sql, options); + /* + * If no data is returned from the query, then the table name may be wrong. + * Query generators that use information_schema for retrieving table info will just return an empty result set, + * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql). + */ + if (_.isEmpty(data)) { + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + } + + const indexes = await this.sequelize.query(sqlIndexes, options); + for (const prop in data) { + data[prop].unique = false; + } + for (const index of indexes) { + for (const field of index.fields) { + if (index.unique !== undefined) { + data[field.attribute].unique = index.unique; + } + } + } + + const foreignKeys = await this.getForeignKeyReferencesForTable(tableName, options); + for (const foreignKey of foreignKeys) { + data[foreignKey.columnName].references = { + model: foreignKey.referencedTableName, + key: foreignKey.referencedColumnName + }; + } + + return data; + } catch (e) { + if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') { + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + } + + throw e; + } + } } exports.SQLiteQueryInterface = SQLiteQueryInterface; diff --git a/test/integration/model.test.js b/test/integration/model.test.js index c52b228ac107..444d277fb92a 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -2014,7 +2014,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { let table = await this.sequelize.queryInterface.describeTable('Publics', { logging(sql) { - if (['sqlite', 'mysql', 'mssql', 'mariadb'].includes(dialect)) { + if (dialect === 'sqlite' && sql.includes('TABLE_INFO')) { + test++; + expect(sql).to.not.contain('special'); + } + else if (['mysql', 'mssql', 'mariadb'].includes(dialect)) { test++; expect(sql).to.not.contain('special'); } @@ -2029,7 +2033,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { table = await this.sequelize.queryInterface.describeTable('Publics', { schema: 'special', logging(sql) { - if (['sqlite', 'mysql', 'mssql', 'mariadb'].includes(dialect)) { + if (dialect === 'sqlite' && sql.includes('TABLE_INFO')) { + test++; + expect(sql).to.contain('special'); + } + else if (['mysql', 'mssql', 'mariadb'].includes(dialect)) { test++; expect(sql).to.contain('special'); } diff --git a/test/integration/model/sync.test.js b/test/integration/model/sync.test.js index 630fd9baaa73..c615efc78595 100644 --- a/test/integration/model/sync.test.js +++ b/test/integration/model/sync.test.js @@ -215,12 +215,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); if (dialect === 'sqlite') { // SQLite doesn't treat primary key as index - expect(results).to.have.length(4); + // However it does create an extra "autoindex", except primary == false + expect(results).to.have.length(4 + 1); } else { expect(results).to.have.length(4 + 1); expect(results.filter(r => r.primary)).to.have.length(1); } + if (dialect === 'sqlite') { + expect(results.filter(r => r.name === 'sqlite_autoindex_testSyncs_1')).to.have.length(1); + } expect(results.filter(r => r.name === 'another_index_email_mobile')).to.have.length(1); expect(results.filter(r => r.name === 'another_index_phone_mobile')).to.have.length(1); expect(results.filter(r => r.name === 'another_index_email')).to.have.length(1); @@ -254,7 +258,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); if (dialect === 'sqlite') { // SQLite doesn't treat primary key as index - expect(results).to.have.length(4); + // However it does create an extra "autoindex", except primary == false + expect(results).to.have.length(4 + 1); } else { expect(results).to.have.length(4 + 1); expect(results.filter(r => r.primary)).to.have.length(1); diff --git a/test/integration/query-interface/changeColumn.test.js b/test/integration/query-interface/changeColumn.test.js index 82817a08b1b1..7bc12b77a2d8 100644 --- a/test/integration/query-interface/changeColumn.test.js +++ b/test/integration/query-interface/changeColumn.test.js @@ -233,5 +233,125 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { }); }); } + + if (dialect === 'sqlite') { + it('should not remove unique constraints when adding or modifying columns', async function() { + await this.queryInterface.createTable({ + tableName: 'Foos' + }, { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + name: { + allowNull: false, + unique: true, + type: DataTypes.STRING + }, + email: { + allowNull: false, + unique: true, + type: DataTypes.STRING + } + }); + + await this.queryInterface.addColumn('Foos', 'phone', { + type: DataTypes.STRING, + defaultValue: null, + allowNull: true + }); + + let table = await this.queryInterface.describeTable({ + tableName: 'Foos' + }); + expect(table.phone.allowNull).to.equal(true, '(1) phone column should allow null values'); + expect(table.phone.defaultValue).to.equal(null, '(1) phone column should have a default value of null'); + expect(table.email.unique).to.equal(true, '(1) email column should remain unique'); + expect(table.name.unique).to.equal(true, '(1) name column should remain unique'); + + await this.queryInterface.changeColumn('Foos', 'email', { + type: DataTypes.STRING, + allowNull: true + }); + + table = await this.queryInterface.describeTable({ + tableName: 'Foos' + }); + expect(table.email.allowNull).to.equal(true, '(2) email column should allow null values'); + expect(table.email.unique).to.equal(true, '(2) email column should remain unique'); + expect(table.name.unique).to.equal(true, '(2) name column should remain unique'); + }); + + it('should add unique constraints to 2 columns and keep allowNull', async function() { + await this.queryInterface.createTable({ + tableName: 'Foos' + }, { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + name: { + allowNull: false, + type: DataTypes.STRING + }, + email: { + allowNull: true, + type: DataTypes.STRING + } + }); + + await this.queryInterface.changeColumn('Foos', 'name', { + type: DataTypes.STRING, + unique: true + }); + await this.queryInterface.changeColumn('Foos', 'email', { + type: DataTypes.STRING, + unique: true + }); + + const table = await this.queryInterface.describeTable({ + tableName: 'Foos' + }); + expect(table.name.allowNull).to.equal(false); + expect(table.name.unique).to.equal(true); + expect(table.email.allowNull).to.equal(true); + expect(table.email.unique).to.equal(true); + }); + + it('should not remove foreign keys when adding or modifying columns', async function() { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), + User = this.sequelize.define('User', { username: DataTypes.STRING }); + + User.hasOne(Task); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + await this.queryInterface.addColumn('Tasks', 'bar', DataTypes.INTEGER); + let refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1, 'should keep foreign key after adding column'); + expect(refs[0].columnName).to.equal('UserId'); + expect(refs[0].referencedTableName).to.equal('Users'); + expect(refs[0].referencedColumnName).to.equal('id'); + + await this.queryInterface.changeColumn('Tasks', 'bar', DataTypes.STRING); + refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1, 'should keep foreign key after changing column'); + expect(refs[0].columnName).to.equal('UserId'); + expect(refs[0].referencedTableName).to.equal('Users'); + expect(refs[0].referencedColumnName).to.equal('id'); + + await this.queryInterface.renameColumn('Tasks', 'bar', 'foo'); + refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1, 'should keep foreign key after renaming column'); + expect(refs[0].columnName).to.equal('UserId'); + expect(refs[0].referencedTableName).to.equal('Users'); + expect(refs[0].referencedColumnName).to.equal('id'); + }); + } }); }); diff --git a/test/unit/dialects/sqlite/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js index 062352d78b08..a3dd0dd51440 100644 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ b/test/unit/dialects/sqlite/query-generator.test.js @@ -607,7 +607,7 @@ if (dialect === 'sqlite') { title: 'Properly quotes column names', arguments: ['myTable', 'foo', 'commit', { commit: 'VARCHAR(255)', bar: 'VARCHAR(255)' }], expectation: - 'CREATE TEMPORARY TABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + + 'CREATE TABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + 'INSERT INTO `myTable_backup` SELECT `foo` AS `commit`, `bar` FROM `myTable`;' + 'DROP TABLE `myTable`;' + 'CREATE TABLE IF NOT EXISTS `myTable` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + From 44784fca277320dd5a2373d6b26db4146d053c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Csord=C3=A1s?= Date: Sun, 28 Jun 2020 11:01:59 +0200 Subject: [PATCH 196/414] docs: fix typo in the associations docs (#12438) --- docs/manual/core-concepts/assocs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md index 495c809eb656..194f0236d4b7 100644 --- a/docs/manual/core-concepts/assocs.md +++ b/docs/manual/core-concepts/assocs.md @@ -287,8 +287,8 @@ const ActorMovies = sequelize.define('ActorMovies', { } } }); -Movie.belongsToMany(Actor, { through: 'ActorMovies' }); -Actor.belongsToMany(Movie, { through: 'ActorMovies' }); +Movie.belongsToMany(Actor, { through: ActorMovies }); +Actor.belongsToMany(Movie, { through: ActorMovies }); ``` The above yields the following SQL in PostgreSQL, which is equivalent to the one shown above: From eca352414afe4cd7baed08ede4f2c5589f8e77e1 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 28 Jun 2020 14:52:48 +0530 Subject: [PATCH 197/414] build: esdoc build can't parse comments --- docs/manual/other-topics/typescript.md | 87 +++++++++++--------------- 1 file changed, 37 insertions(+), 50 deletions(-) diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index 717e4db91d7b..9edca2c58023 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -15,11 +15,7 @@ In order to avoid installation bloat for non TS users, you must install the foll Example of a minimal TypeScript project with strict type-checking for attributes. - +**NOTE:** Keep the following code in sync with `typescriptDocs/ModelInit.ts` to ensure it typechecks correctly. ```ts import { @@ -33,9 +29,9 @@ import { HasManyCountAssociationsMixin, HasManyCreateAssociationMixin, Optional, -} from 'sequelize'; +} from "sequelize"; -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); // These are all the attributes in the User model interface UserAttributes { @@ -45,7 +41,7 @@ interface UserAttributes { } // Some attributes are optional in `User.build` and `User.create` calls -interface UserCreationAttributes extends Optional {} +interface UserCreationAttributes extends Optional {} class User extends Model implements UserAttributes { @@ -81,7 +77,7 @@ interface ProjectAttributes { name: string; } -interface ProjectCreationAttributes extends Optional {} +interface ProjectCreationAttributes extends Optional {} class Project extends Model implements ProjectAttributes { @@ -126,8 +122,8 @@ Project.init( }, { sequelize, - tableName: 'projects', - }, + tableName: "projects", + } ); User.init( @@ -147,9 +143,9 @@ User.init( }, }, { - tableName: 'users', + tableName: "users", sequelize, // passing the `sequelize` instance is required - }, + } ); Address.init( @@ -163,30 +159,30 @@ Address.init( }, }, { - tableName: 'address', + tableName: "address", sequelize, // passing the `sequelize` instance is required - }, + } ); // Here we associate which actually populates out pre-declared `association` static and other methods. User.hasMany(Project, { - sourceKey: 'id', - foreignKey: 'ownerId', - as: 'projects', // this determines the name in `associations`! + sourceKey: "id", + foreignKey: "ownerId", + as: "projects", // this determines the name in `associations`! }); -Address.belongsTo(User, { targetKey: 'id' }); -User.hasOne(Address, { sourceKey: 'id' }); +Address.belongsTo(User, { targetKey: "id" }); +User.hasOne(Address, { sourceKey: "id" }); async function doStuffWithUser() { const newUser = await User.create({ - name: 'Johnny', - preferredName: 'John', + name: "Johnny", + preferredName: "John", }); console.log(newUser.id, newUser.name, newUser.preferredName); const project = await newUser.createProject({ - name: 'first!', + name: "first!", }); const ourUser = await User.findByPk(1, { @@ -204,16 +200,13 @@ async function doStuffWithUser() { The typings for Sequelize v5 allowed you to define models without specifying types for the attributes. This is still possible for backwards compatibility and for cases where you feel strict typing for attributes isn't worth it. - ```ts -import { Sequelize, Model, DataTypes } from 'sequelize'; +import { Sequelize, Model, DataTypes } from "sequelize"; -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); class User extends Model { public id!: number; // Note that the `null assertion` `!` is required in strict mode. @@ -238,19 +231,19 @@ User.init( }, }, { - tableName: 'users', + tableName: "users", sequelize, // passing the `sequelize` instance is required - }, + } ); async function doStuffWithUserModel() { const newUser = await User.create({ - name: 'Johnny', - preferredName: 'John', + name: "Johnny", + preferredName: "John", }); console.log(newUser.id, newUser.name, newUser.preferredName); - const foundUser = await User.findOne({ where: { name: 'Johnny' } }); + const foundUser = await User.findOne({ where: { name: "Johnny" } }); if (foundUser === null) return; console.log(foundUser.name); } @@ -260,16 +253,13 @@ async function doStuffWithUserModel() { In Sequelize versions before v5, the default way of defining a model involved using `sequelize.define`. It's still possible to define models with that, and you can also add typings to these models using interfaces. - ```ts -import { Sequelize, Model, DataTypes, Optional } from 'sequelize'; +import { Sequelize, Model, DataTypes, Optional } from "sequelize"; -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); // We recommend you declare an interface for the attributes, for stricter typechecking interface UserAttributes { @@ -278,21 +268,21 @@ interface UserAttributes { } // Some fields are optional when calling UserModel.create() or UserModel.build() -interface UserCreationAttributes extends Optional {} +interface UserCreationAttributes extends Optional {} // We need to declare an interface for our model that is basically what our class would be interface UserInstance extends Model, UserAttributes {} -const UserModel = sequelize.define('User', { +const UserModel = sequelize.define("User", { id: { primaryKey: true, type: DataTypes.INTEGER.UNSIGNED, }, name: { type: DataTypes.STRING, - } + }, }); async function doStuff() { @@ -305,16 +295,13 @@ async function doStuff() { If you're comfortable with somewhat less strict typing for the attributes on a model, you can save some code by defining the Instance to just extend `Model` without any attributes in the generic types. - ```ts -import { Sequelize, Model, DataTypes } from 'sequelize'; +import { Sequelize, Model, DataTypes } from "sequelize"; -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); // We need to declare an interface for our model that is basically what our class would be interface UserInstance extends Model { @@ -322,7 +309,7 @@ interface UserInstance extends Model { name: string; } -const UserModel = sequelize.define('User', { +const UserModel = sequelize.define("User", { id: { primaryKey: true, type: DataTypes.INTEGER.UNSIGNED, From d346a08864ad1587e3ca91d9366415ab8ac92337 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 28 Jun 2020 14:54:22 +0530 Subject: [PATCH 198/414] build: appveyor fix --- appveyor.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index efe08259bce5..05c59d450795 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,6 +15,7 @@ environment: install: - ps: Install-Product node $env:NODE_VERSION x64 - npm ci + - npm i --save-dev codecov build: off @@ -25,12 +26,7 @@ test_script: - 'IF "%COVERAGE%" == "true" (npm run cover) ELSE (npm test)' after_test: - - ps: | - $env:PATH = 'C:\Program Files\Git\usr\bin;' + $env:PATH - if (Test-Path env:\COVERAGE) { - Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh - bash codecov.sh -f "coverage\lcov.info" - } + - npx codecov branches: only: From 9358a1325939ccb5d29e4006fb4e923785d9fc61 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 28 Jun 2020 14:57:42 +0530 Subject: [PATCH 199/414] build: enable ci for v6 --- .travis.yml | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 79894ac7f5f6..f90a16436c06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ language: node_js branches: only: - master + - v6 except: - /^v\d+\.\d+\.\d+$/ @@ -80,4 +81,4 @@ stages: - lint - test - name: release - if: branch = master AND type = push AND fork = false + if: branch = v6 AND type = push AND fork = false diff --git a/package.json b/package.json index 190a8793c142..55547ef04941 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ } }, "release": { - "branch": "master", + "branch": "v6", "verifyConditions": [ "@semantic-release/npm", "@semantic-release/github" From 90e60b171b0fec7137defaf620bd44778e50626c Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 28 Jun 2020 14:58:38 +0530 Subject: [PATCH 200/414] build: enable appveyor ci for v6 --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 05c59d450795..703a7b8a3ae5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,4 +31,5 @@ after_test: branches: only: - master + - v6 - /^greenkeeper/.*$/ From 945af927db60e964eba7e344665ed0594ee63231 Mon Sep 17 00:00:00 2001 From: Sushant Date: Sun, 28 Jun 2020 15:16:18 +0530 Subject: [PATCH 201/414] build: use release.branches --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55547ef04941..41a363893168 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ } }, "release": { - "branch": "v6", + "branches": ["v6"], "verifyConditions": [ "@semantic-release/npm", "@semantic-release/github" From 9a45509e3f4b23b957a195190dd989ea33cae8d8 Mon Sep 17 00:00:00 2001 From: Chris Northwood Date: Wed, 1 Jul 2020 06:56:32 +0100 Subject: [PATCH 202/414] fix(types): update SaveOptions type to include Hookable (#12441) (#12444) --- types/lib/model.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index af72733e2eb7..5c73f1bbb8a0 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -964,7 +964,7 @@ export interface SetOptions { /** * Options used for Instance.save method */ -export interface SaveOptions extends Logging, Transactionable, Silent { +export interface SaveOptions extends Logging, Transactionable, Silent, Hookable { /** * An optional array of strings, representing database columns. If fields is provided, only those columns * will be validated and saved. From 4d82907d56ef28f6ddd5a29c16bac7a64271ff91 Mon Sep 17 00:00:00 2001 From: Patrick Carnahan Date: Sat, 4 Jul 2020 00:29:04 -0400 Subject: [PATCH 203/414] fix(mssql): upsert query with falsey values (#12453) (#12459) --- lib/dialects/mssql/query-generator.js | 2 +- .../dialects/mssql/query-generator.test.js | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index f1f9ca941be3..1c0f71367d53 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -465,7 +465,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { * Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row */ for (const key in clause) { - if (!clause[key]) { + if (typeof clause[key] === 'undefined' || clause[key] == null) { valid = false; break; } diff --git a/test/unit/dialects/mssql/query-generator.test.js b/test/unit/dialects/mssql/query-generator.test.js index 39fbbd62ac8b..525b7ff99da4 100644 --- a/test/unit/dialects/mssql/query-generator.test.js +++ b/test/unit/dialects/mssql/query-generator.test.js @@ -3,6 +3,8 @@ const Support = require('../../support'); const expectsql = Support.expectsql; const current = Support.sequelize; +const DataTypes = require('../../../../lib/data-types'); +const Op = require('../../../../lib/operators'); const TableHints = require('../../../../lib/table-hints'); const QueryGenerator = require('../../../../lib/dialects/mssql/query-generator'); @@ -21,6 +23,56 @@ if (current.dialect.name === 'mssql') { }); }); + it('upsertQuery with falsey values', function() { + const testTable = this.sequelize.define( + 'test_table', + { + Name: { + type: DataTypes.STRING, + primaryKey: true + }, + Age: { + type: DataTypes.INTEGER + }, + IsOnline: { + type: DataTypes.BOOLEAN, + primaryKey: true + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + const insertValues = { + Name: 'Charlie', + Age: 24, + IsOnline: false + }; + + const updateValues = { + Age: 24 + }; + + const whereValues = [ + { + Name: 'Charlie', + IsOnline: false + } + ]; + + const where = { + [Op.or]: whereValues + }; + + // the main purpose of this test is to validate this does not throw + expectsql(this.queryGenerator.upsertQuery('test_table', updateValues, insertValues, where, testTable), { + mssql: + "MERGE INTO [test_table] WITH(HOLDLOCK) AS [test_table_target] USING (VALUES(24)) AS [test_table_source]([Age]) ON [test_table_target].[Name] = [test_table_source].[Name] AND [test_table_target].[IsOnline] = [test_table_source].[IsOnline] WHEN MATCHED THEN UPDATE SET [test_table_target].[Name] = N'Charlie', [test_table_target].[Age] = 24, [test_table_target].[IsOnline] = 0 WHEN NOT MATCHED THEN INSERT ([Age]) VALUES(24) OUTPUT $action, INSERTED.*;" + }); + }); + it('createDatabaseQuery with collate', function() { expectsql(this.queryGenerator.createDatabaseQuery('myDatabase', { collate: 'Latin1_General_CS_AS_KS_WS' }), { mssql: "IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'myDatabase' ) BEGIN CREATE DATABASE [myDatabase] COLLATE N'Latin1_General_CS_AS_KS_WS'; END;" From 5cabcbc87fbc12d234fbc1d67632a86e6d975f39 Mon Sep 17 00:00:00 2001 From: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Date: Sat, 4 Jul 2020 05:31:58 +0100 Subject: [PATCH 204/414] feat(types): Add ModelDefined type as syntactic sugar (#12445) --- docs/manual/other-topics/typescript.md | 39 +++++++++++++- types/lib/model.d.ts | 2 + types/test/typescriptDocs/ModelInit.ts | 74 +++++++++++++++++++------- 3 files changed, 96 insertions(+), 19 deletions(-) diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md index 9edca2c58023..836963e20de6 100644 --- a/docs/manual/other-topics/typescript.md +++ b/docs/manual/other-topics/typescript.md @@ -15,12 +15,13 @@ In order to avoid installation bloat for non TS users, you must install the foll Example of a minimal TypeScript project with strict type-checking for attributes. -**NOTE:** Keep the following code in sync with `typescriptDocs/ModelInit.ts` to ensure it typechecks correctly. +**NOTE:** Keep the following code in sync with `/types/test/typescriptDocs/ModelInit.ts` to ensure it typechecks correctly. ```ts import { Sequelize, Model, + ModelDefined, DataTypes, HasManyGetAssociationsMixin, HasManyAddAssociationMixin, @@ -104,6 +105,16 @@ class Address extends Model implements AddressAttributes { public readonly updatedAt!: Date; } +// You can also define modules in a functional way +interface NoteAttributes { + id: number; + title: string; + content: string; +} + +// You can also set multiple attributes optional at once +interface NoteCreationAttributes extends Optional {}; + Project.init( { id: { @@ -164,6 +175,32 @@ Address.init( } ); +// And with a functional approach defining a module looks like this +const Note: ModelDefined< + NoteAttributes, + NoteCreationAttributes +> = sequelize.define( + 'Note', + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + title: { + type: new DataTypes.STRING(64), + defaultValue: 'Unnamed Note', + }, + content: { + type: new DataTypes.STRING(4096), + allowNull: false, + }, + }, + { + tableName: 'notes', + } +); + // Here we associate which actually populates out pre-declared `association` static and other methods. User.hasMany(Project, { sourceKey: "id", diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 5c73f1bbb8a0..cad5ff3eb066 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2849,6 +2849,8 @@ export type ModelType = typeof Model; // must come first for unknown reasons. export type ModelCtor = typeof Model & { new(): M }; +export type ModelDefined = ModelCtor>; + export type ModelStatic = { new(): M }; export default Model; diff --git a/types/test/typescriptDocs/ModelInit.ts b/types/test/typescriptDocs/ModelInit.ts index 1cfaf4230d4f..163cb8ec88ee 100644 --- a/types/test/typescriptDocs/ModelInit.ts +++ b/types/test/typescriptDocs/ModelInit.ts @@ -4,6 +4,7 @@ import { Sequelize, Model, + ModelDefined, DataTypes, HasManyGetAssociationsMixin, HasManyAddAssociationMixin, @@ -12,9 +13,9 @@ import { HasManyCountAssociationsMixin, HasManyCreateAssociationMixin, Optional, -} from 'sequelize'; +} from "sequelize"; -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); // These are all the attributes in the User model interface UserAttributes { @@ -24,7 +25,7 @@ interface UserAttributes { } // Some attributes are optional in `User.build` and `User.create` calls -interface UserCreationAttributes extends Optional {} +interface UserCreationAttributes extends Optional {} class User extends Model implements UserAttributes { @@ -60,7 +61,7 @@ interface ProjectAttributes { name: string; } -interface ProjectCreationAttributes extends Optional {} +interface ProjectCreationAttributes extends Optional {} class Project extends Model implements ProjectAttributes { @@ -87,6 +88,17 @@ class Address extends Model implements AddressAttributes { public readonly updatedAt!: Date; } +// You can also define modules in a functional way +interface NoteAttributes { + id: number; + title: string; + content: string; +} + +// You can also set multiple attributes optional at once +interface NoteCreationAttributes + extends Optional {} + Project.init( { id: { @@ -105,8 +117,8 @@ Project.init( }, { sequelize, - tableName: 'projects', - }, + tableName: "projects", + } ); User.init( @@ -126,9 +138,9 @@ User.init( }, }, { - tableName: 'users', + tableName: "users", sequelize, // passing the `sequelize` instance is required - }, + } ); Address.init( @@ -142,30 +154,56 @@ Address.init( }, }, { - tableName: 'address', + tableName: "address", sequelize, // passing the `sequelize` instance is required + } +); + +// And with a functional approach defining a module looks like this +const Note: ModelDefined< + NoteAttributes, + NoteCreationAttributes +> = sequelize.define( + "Note", + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + title: { + type: new DataTypes.STRING(64), + defaultValue: "Unnamed Note", + }, + content: { + type: new DataTypes.STRING(4096), + allowNull: false, + }, }, + { + tableName: "notes", + } ); // Here we associate which actually populates out pre-declared `association` static and other methods. User.hasMany(Project, { - sourceKey: 'id', - foreignKey: 'ownerId', - as: 'projects', // this determines the name in `associations`! + sourceKey: "id", + foreignKey: "ownerId", + as: "projects", // this determines the name in `associations`! }); -Address.belongsTo(User, { targetKey: 'id' }); -User.hasOne(Address, { sourceKey: 'id' }); +Address.belongsTo(User, { targetKey: "id" }); +User.hasOne(Address, { sourceKey: "id" }); async function doStuffWithUser() { const newUser = await User.create({ - name: 'Johnny', - preferredName: 'John', + name: "Johnny", + preferredName: "John", }); console.log(newUser.id, newUser.name, newUser.preferredName); const project = await newUser.createProject({ - name: 'first!', + name: "first!", }); const ourUser = await User.findByPk(1, { @@ -176,4 +214,4 @@ async function doStuffWithUser() { // Note the `!` null assertion since TS can't know if we included // the model or not console.log(ourUser.projects![0].name); -} +} \ No newline at end of file From 631f555cc92b9629a8c7678e330e3492cdee31c7 Mon Sep 17 00:00:00 2001 From: Tommy Odom Date: Fri, 10 Jul 2020 00:53:34 -0400 Subject: [PATCH 205/414] fix: use lodash _baseIsNative directly instead of _.isNative (#12358) (#12475) (#12480) --- lib/utils.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 4299619bac0e..0ce6466ea304 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,6 +3,7 @@ const DataTypes = require('./data-types'); const SqlString = require('./sql-string'); const _ = require('lodash'); +const baseIsNative = require('lodash/_baseIsNative'); const uuidv1 = require('uuid').v1; const uuidv4 = require('uuid').v4; const operators = require('./operators'); @@ -51,7 +52,9 @@ function mergeDefaults(a, b) { return _.mergeWith(a, b, (objectValue, sourceValue) => { // If it's an object, let _ handle it this time, we will be called again for each property if (!_.isPlainObject(objectValue) && objectValue !== undefined) { - if (_.isFunction(objectValue) && _.isNative(objectValue)) { + // _.isNative includes a check for core-js and throws an error if present. + // Depending on _baseIsNative bypasses the core-js check. + if (_.isFunction(objectValue) && baseIsNative(objectValue)) { return sourceValue || objectValue; } return objectValue; From 45d9c2260387894b3d20cb4ca25845df944974dd Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Fri, 10 Jul 2020 02:47:53 -0300 Subject: [PATCH 206/414] docs: add unique: false to belongs_to_many (#12481) --- docs/manual/core-concepts/assocs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md index 194f0236d4b7..b511d7c62f80 100644 --- a/docs/manual/core-concepts/assocs.md +++ b/docs/manual/core-concepts/assocs.md @@ -308,7 +308,7 @@ CREATE TABLE IF NOT EXISTS "ActorMovies" ( Unlike One-To-One and One-To-Many relationships, the defaults for both `ON UPDATE` and `ON DELETE` are `CASCADE` for Many-To-Many relationships. -Belongs-To-Many creates a unique key when primary key is not present on through model. This unique key name can be overridden using **uniqueKey** option. +Belongs-To-Many creates a unique key on through model. This unique key name can be overridden using **uniqueKey** option. To prevent creating this unique key, use the ***unique: false*** option. ```js Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' }) From 96f4166afc86dc773ac0886bf10d82d446cfa0e1 Mon Sep 17 00:00:00 2001 From: Ariel Barabas <810664+knoid@users.noreply.github.com> Date: Sat, 11 Jul 2020 01:50:53 -0300 Subject: [PATCH 207/414] fix(upsert): set isNewRecord to false when upserting (#12447) (#12485) --- lib/dialects/mariadb/query.js | 2 +- lib/model.js | 3 +++ test/integration/instance.test.js | 6 ++++++ test/unit/model/upsert.test.js | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index 8e2fad0815dd..b89575575a96 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -96,7 +96,7 @@ class Query extends AbstractQuery { return data.affectedRows; } if (this.isUpsertQuery()) { - return [null, data.affectedRows === 1]; + return [result, data.affectedRows === 1]; } if (this.isInsertQuery(data)) { this.handleInsertQuery(data); diff --git a/lib/model.js b/lib/model.js index f148563ba8fa..4b79d70460a3 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2469,6 +2469,9 @@ class Model { } const result = await this.queryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), options); + const [record] = result; + record.isNewRecord = false; + if (options.hooks) { await this.runHooks('afterUpsert', result, options); return result; diff --git a/test/integration/instance.test.js b/test/integration/instance.test.js index 099b783b0b08..d7d16c7dba4b 100644 --- a/test/integration/instance.test.js +++ b/test/integration/instance.test.js @@ -85,6 +85,12 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(user.isNewRecord).to.not.be.ok; }); + it('returns false for upserted objects', async function() { + // adding id here so MSSQL doesn't fail. It needs a primary key to upsert + const [user] = await this.User.upsert({ id: 2, username: 'user' }); + expect(user.isNewRecord).to.not.be.ok; + }); + it('returns false for objects found by find method', async function() { await this.User.create({ username: 'user' }); const user = await this.User.create({ username: 'user' }); diff --git a/test/unit/model/upsert.test.js b/test/unit/model/upsert.test.js index 63db06413b4b..85e42bbd8784 100644 --- a/test/unit/model/upsert.test.js +++ b/test/unit/model/upsert.test.js @@ -43,7 +43,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.query = sinon.stub(current, 'query').resolves(); - this.stub = sinon.stub(current.getQueryInterface(), 'upsert').resolves([true, undefined]); + this.stub = sinon.stub(current.getQueryInterface(), 'upsert').resolves([this.User.build(), true]); }); afterEach(function() { From ec2af0d926cda598c938c7fd4c799e6e2331f9e7 Mon Sep 17 00:00:00 2001 From: Daan Roet Date: Sat, 11 Jul 2020 16:13:44 +0200 Subject: [PATCH 208/414] fix: mark database drivers as optional peer dependencies (#12484) --- package.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/package.json b/package.json index 41a363893168..f3d838e8d425 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,26 @@ "tedious": "8.3.0", "typescript": "^3.9.3" }, + "peerDependenciesMeta": { + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + }, "keywords": [ "mysql", "mariadb", From 4b2e76749930c04826e8d8f24e105f1290f6be77 Mon Sep 17 00:00:00 2001 From: Juarez Lustosa Date: Fri, 17 Jul 2020 01:05:52 -0300 Subject: [PATCH 209/414] doc: Add install tsc on configuration (#12510) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc1de04960dc..34b4ed63f3f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,6 +44,7 @@ Just "cd" into sequelize directory and run `npm ci`, see an example below: ```sh $ cd path/to/sequelize $ npm ci +$ npm run tsc ``` ### 3. Database From b0e6f03975c4b4b90303e8b04033354059142593 Mon Sep 17 00:00:00 2001 From: Hyunyoung Cho Date: Sun, 2 Aug 2020 05:22:26 +0900 Subject: [PATCH 210/414] docs(advanced-many-to-many): add missing `await` (#12533) --- .../advanced-association-concepts/advanced-many-to-many.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/advanced-association-concepts/advanced-many-to-many.md b/docs/manual/advanced-association-concepts/advanced-many-to-many.md index 2998ff6ac554..371f66201f8c 100644 --- a/docs/manual/advanced-association-concepts/advanced-many-to-many.md +++ b/docs/manual/advanced-association-concepts/advanced-many-to-many.md @@ -48,8 +48,8 @@ With this, we can now track an extra information at the through table, namely th Example: ```js -const amidala = User.create({ username: 'p4dm3', points: 1000 }); -const queen = Profile.create({ name: 'Queen' }); +const amidala = await User.create({ username: 'p4dm3', points: 1000 }); +const queen = await Profile.create({ name: 'Queen' }); await amidala.addProfile(queen, { through: { selfGranted: false } }); const result = await User.findOne({ where: { username: 'p4dm3' }, From 45ec1a22c3a48891a156e2c1b2b5374a1bba6f35 Mon Sep 17 00:00:00 2001 From: Bagus Budi Cahyono Date: Sun, 2 Aug 2020 03:09:33 +0700 Subject: [PATCH 211/414] docs(naming-strategies): fix user model name user (#12576) --- docs/manual/other-topics/naming-strategies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/other-topics/naming-strategies.md b/docs/manual/other-topics/naming-strategies.md index f97edd004fcd..9daf8b1a3567 100644 --- a/docs/manual/other-topics/naming-strategies.md +++ b/docs/manual/other-topics/naming-strategies.md @@ -5,7 +5,7 @@ Sequelize provides the `underscored` option for a model. When `true`, this option will set the `field` option on all attributes to the [snake_case](https://en.wikipedia.org/wiki/Snake_case) version of its name. This also applies to foreign keys automatically generated by associations and other automatically generated fields. Example: ```js -const User = sequelize.define('task', { username: Sequelize.STRING }, { +const User = sequelize.define('user', { username: Sequelize.STRING }, { underscored: true }); const Task = sequelize.define('task', { title: Sequelize.STRING }, { From 62f19114b9b71f3240452ab9c1d38c6cb0dce702 Mon Sep 17 00:00:00 2001 From: Valetek <34715110+Valetek@users.noreply.github.com> Date: Sat, 1 Aug 2020 22:21:03 +0200 Subject: [PATCH 212/414] docs(migrations): add example for conditional unique index (#12578) Co-authored-by: Valentin Almeida--Lemaire Co-authored-by: Pedro Augusto de Paula Barbosa --- docs/manual/other-topics/migrations.md | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/migrations.md b/docs/manual/other-topics/migrations.md index 73d350788210..879c4fb1f0cc 100644 --- a/docs/manual/other-topics/migrations.md +++ b/docs/manual/other-topics/migrations.md @@ -330,6 +330,34 @@ module.exports = { }; ``` +The next example is of a migration that creates an unique index composed of multiple fields with a condition, which allows a relation to exist multiple times but only one can satisfy the condition: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + queryInterface.createTable('Person', { + name: Sequelize.DataTypes.STRING, + bool: { + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false + } + }).then((queryInterface, Sequelize) => { + queryInterface.addIndex( + 'Person', + ['name', 'bool'], + { + indicesType: 'UNIQUE', + where: { bool : 'true' }, + } + ); + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Person'); + } +} +``` + ### The `.sequelizerc` file This is a special configuration file. It lets you specify the following options that you would usually pass as arguments to CLI: @@ -534,4 +562,4 @@ npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/databas ### Programmatic usage -Sequelize has a sister library called [umzug](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks. \ No newline at end of file +Sequelize has a sister library called [umzug](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks. From 6c99ba01ba0f793d138cc98fef00df8e524ba5b2 Mon Sep 17 00:00:00 2001 From: Fernando Maia <2966576+fernandomaia@users.noreply.github.com> Date: Sat, 1 Aug 2020 17:24:10 -0300 Subject: [PATCH 213/414] docs(raw-queries): update broken link (#12542) --- docs/manual/core-concepts/raw-queries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/raw-queries.md b/docs/manual/core-concepts/raw-queries.md index eb41d2637b6b..8172d2bfddb3 100644 --- a/docs/manual/core-concepts/raw-queries.md +++ b/docs/manual/core-concepts/raw-queries.md @@ -17,7 +17,7 @@ const users = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes. // We didn't need to destructure the result here - the results were returned directly ``` -Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/lib/query-types.js). +Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/src/query-types.ts). A second option is the model. If you pass a model the returned data will be instances of that model. From 490db410e33241158f7d8a7cdeffbf3766af731c Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 1 Aug 2020 20:27:22 -0300 Subject: [PATCH 214/414] fix(model): handle `true` timestamp fields correctly (#12580) (#12581) Co-authored-by: Pedro Augusto de Paula Barbosa Co-authored-by: Vishal Sood --- lib/model.js | 18 ++++++++++++--- test/integration/model.test.js | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/model.js b/lib/model.js index 4b79d70460a3..772230ad6239 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1022,16 +1022,28 @@ class Model { // setup names of timestamp attributes if (this.options.timestamps) { + for (const key of ['createdAt', 'updatedAt', 'deletedAt']) { + if (!['undefined', 'string', 'boolean'].includes(typeof this.options[key])) { + throw new Error(`Value for "${key}" option must be a string or a boolean, got ${typeof this.options[key]}`); + } + if (this.options[key] === '') { + throw new Error(`Value for "${key}" option cannot be an empty string`); + } + } + if (this.options.createdAt !== false) { - this._timestampAttributes.createdAt = this.options.createdAt || 'createdAt'; + this._timestampAttributes.createdAt = + typeof this.options.createdAt === 'string' ? this.options.createdAt : 'createdAt'; this._readOnlyAttributes.add(this._timestampAttributes.createdAt); } if (this.options.updatedAt !== false) { - this._timestampAttributes.updatedAt = this.options.updatedAt || 'updatedAt'; + this._timestampAttributes.updatedAt = + typeof this.options.updatedAt === 'string' ? this.options.updatedAt : 'updatedAt'; this._readOnlyAttributes.add(this._timestampAttributes.updatedAt); } if (this.options.paranoid && this.options.deletedAt !== false) { - this._timestampAttributes.deletedAt = this.options.deletedAt || 'deletedAt'; + this._timestampAttributes.deletedAt = + typeof this.options.deletedAt === 'string' ? this.options.deletedAt : 'deletedAt'; this._readOnlyAttributes.add(this._timestampAttributes.deletedAt); } } diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 444d277fb92a..477a3c7dbc66 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -146,6 +146,46 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(defaultFunction.callCount).to.equal(2); }); + it('should throw `TypeError` when value for updatedAt, createdAt, or deletedAt is neither string nor boolean', async function() { + const modelName = 'UserCol'; + const attributes = { aNumber: Sequelize.INTEGER }; + + expect(() => { + this.sequelize.define(modelName, attributes, { timestamps: true, updatedAt: {} }); + }).to.throw(Error, 'Value for "updatedAt" option must be a string or a boolean, got object'); + expect(() => { + this.sequelize.define(modelName, attributes, { timestamps: true, createdAt: 100 }); + }).to.throw(Error, 'Value for "createdAt" option must be a string or a boolean, got number'); + expect(() => { + this.sequelize.define(modelName, attributes, { timestamps: true, deletedAt: () => {} }); + }).to.throw(Error, 'Value for "deletedAt" option must be a string or a boolean, got function'); + }); + + it('should allow me to use `true` as a value for updatedAt, createdAt, and deletedAt fields', async function() { + const UserTable = this.sequelize.define( + 'UserCol', + { + aNumber: Sequelize.INTEGER + }, + { + timestamps: true, + updatedAt: true, + createdAt: true, + deletedAt: true, + paranoid: true + } + ); + + await UserTable.sync({ force: true }); + const user = await UserTable.create({ aNumber: 4 }); + expect(user['true']).to.not.exist; + expect(user.updatedAt).to.exist; + expect(user.createdAt).to.exist; + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAt).to.exist; + }); + it('should allow me to override updatedAt, createdAt, and deletedAt fields', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER From 933b3f62640e218587d34ec141029b2416ff7845 Mon Sep 17 00:00:00 2001 From: Benoit MERIAUX Date: Tue, 1 Sep 2020 19:19:11 +0200 Subject: [PATCH 215/414] fix(truncate): fix missing `await` in truncate all models with cascade (#12664) --- lib/sequelize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index 21709ca15d8a..05604cddee56 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -819,7 +819,7 @@ class Sequelize { }, { reverse: false }); if (options && options.cascade) { - for (const model of models) model.truncate(options); + for (const model of models) await model.truncate(options); } else { await Promise.all(models.map(model => model.truncate(options))); } From 6addca90933dca10ca0b52b02d135037565ccbba Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 8 Sep 2020 08:04:55 -0400 Subject: [PATCH 216/414] docs(hooks.md): fix broken url (#12677) --- docs/manual/other-topics/hooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/other-topics/hooks.md b/docs/manual/other-topics/hooks.md index 9dd422232df0..71791719bb4f 100644 --- a/docs/manual/other-topics/hooks.md +++ b/docs/manual/other-topics/hooks.md @@ -6,7 +6,7 @@ Hooks (also known as lifecycle events), are functions which are called before an ## Available hooks -Sequelize provides a lot of hooks. The full list can be found in directly in the [source code - lib/hooks.js](https://github.com/sequelize/sequelize/blob/master/lib/hooks.js#L7). +Sequelize provides a lot of hooks. The full list can be found in directly in the [source code - lib/hooks.js](https://github.com/sequelize/sequelize/blob/v6/lib/hooks.js#L7). ## Hooks firing order @@ -382,4 +382,4 @@ It is very important to recognize that sequelize may make use of transactions in * If a transaction was used, then `{ transaction: options.transaction }` will ensure it is used again; * Otherwise, `{ transaction: options.transaction }` will be equivalent to `{ transaction: undefined }`, which won't use a transaction (which is ok). -This way your hooks will always behave correctly. \ No newline at end of file +This way your hooks will always behave correctly. From a329bb76e9efa1f4c0f24f8be7c7ddda0a464ae8 Mon Sep 17 00:00:00 2001 From: Stanley Ume Date: Fri, 2 Oct 2020 17:46:23 +0100 Subject: [PATCH 217/414] docs(model-basics.md): fix typo (#12735) --- docs/manual/core-concepts/model-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md index 7cf8ce161a14..a413b81fa614 100644 --- a/docs/manual/core-concepts/model-basics.md +++ b/docs/manual/core-concepts/model-basics.md @@ -167,7 +167,7 @@ console.log("All tables dropped!"); ### Database safety check -As shown above, the `sync` and `drop` operations are destructive. Sequelize acceps a `match` option as an additional safety check, which receives a RegExp: +As shown above, the `sync` and `drop` operations are destructive. Sequelize accepts a `match` option as an additional safety check, which receives a RegExp: ```js // This will run .sync() only if database name ends with '_test' From c3ec6c507f4b7115455834bda052806d29a5132c Mon Sep 17 00:00:00 2001 From: "Hiroki.Ihoriya" <38400669+ia17011@users.noreply.github.com> Date: Sat, 24 Oct 2020 04:01:57 +0900 Subject: [PATCH 218/414] docs(model-querying-basics): add missing comma (#12786) --- docs/manual/core-concepts/model-querying-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index c738993c50d4..71adc7657853 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -77,7 +77,7 @@ You can use [`sequelize.fn`](../class/lib/sequelize.js~Sequelize.html#static-met Model.findAll({ attributes: [ 'foo', - [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'] + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'], 'bar' ] }); From aa6345be1551f247ab72e4a8a2cfeab48eb95320 Mon Sep 17 00:00:00 2001 From: Ajaz Ur Rehman Date: Mon, 16 Nov 2020 03:07:24 +0530 Subject: [PATCH 219/414] docs(transactions.md): add missing `async` declaration (#12838) Co-authored-by: Pedro Augusto de Paula Barbosa --- docs/manual/other-topics/transactions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/other-topics/transactions.md b/docs/manual/other-topics/transactions.md index 8a862fef0c11..e24f6d216781 100644 --- a/docs/manual/other-topics/transactions.md +++ b/docs/manual/other-topics/transactions.md @@ -98,7 +98,7 @@ Note that `t.commit()` and `t.rollback()` were not called directly (which is cor When using the managed transaction you should *never* commit or rollback the transaction manually. If all queries are successful (in the sense of not throwing any error), but you still want to rollback the transaction, you should throw an error yourself: ```js -await sequelize.transaction(t => { +await sequelize.transaction(async t => { const user = await User.create({ firstName: 'Abraham', lastName: 'Lincoln' From e4144677584e5a9080cf59c4eafe4301069ad868 Mon Sep 17 00:00:00 2001 From: papb Date: Thu, 3 Dec 2020 23:29:33 -0300 Subject: [PATCH 220/414] build: update dependencies --- package-lock.json | 2327 ++++++++++++++++++++++++++------------------- package.json | 10 +- 2 files changed, 1376 insertions(+), 961 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba8011cc7225..c5f6d63a7c76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -330,6 +330,14 @@ "meow": "5.0.0", "resolve-from": "5.0.0", "resolve-global": "1.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/config-angular": { @@ -354,6 +362,14 @@ "dev": true, "requires": { "lodash": "4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/execute-rule": { @@ -399,6 +415,14 @@ "@commitlint/rules": "^8.3.4", "babel-runtime": "^6.23.0", "lodash": "4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/load": { @@ -414,6 +438,14 @@ "cosmiconfig": "^5.2.0", "lodash": "4.17.15", "resolve-from": "^5.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/message": { @@ -455,6 +487,14 @@ "lodash": "4.17.15", "resolve-from": "^5.0.0", "resolve-global": "^1.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "@commitlint/rules": { @@ -711,131 +751,127 @@ } }, "@octokit/auth-token": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.0.tgz", - "integrity": "sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.4.tgz", + "integrity": "sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q==", "dev": true, "requires": { - "@octokit/types": "^2.0.0" + "@octokit/types": "^6.0.0" } }, "@octokit/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-2.5.0.tgz", - "integrity": "sha512-uvzmkemQrBgD8xuGbjhxzJN1darJk9L2cS+M99cHrDG2jlSVpxNJVhoV86cXdYBqdHCc9Z995uLCczaaHIYA6Q==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.4.tgz", + "integrity": "sha512-d9dTsqdePBqOn7aGkyRFe7pQpCXdibSJ5SFnrTr0axevObZrpz3qkWm7t/NjYv5a66z6vhfteriaq4FRz3e0Qg==", "dev": true, "requires": { - "@octokit/auth-token": "^2.4.0", - "@octokit/graphql": "^4.3.1", - "@octokit/request": "^5.4.0", - "@octokit/types": "^2.0.0", + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.4.12", + "@octokit/types": "^6.0.3", "before-after-hook": "^2.1.0", - "universal-user-agent": "^5.0.0" + "universal-user-agent": "^6.0.0" } }, "@octokit/endpoint": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.1.tgz", - "integrity": "sha512-pOPHaSz57SFT/m3R5P8MUu4wLPszokn5pXcB/pzavLTQf2jbU+6iayTvzaY6/BiotuRS0qyEUkx3QglT4U958A==", + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.10.tgz", + "integrity": "sha512-9+Xef8nT7OKZglfkOMm7IL6VwxXUQyR7DUSU0LH/F7VNqs8vyd7es5pTfz9E7DwUIx7R3pGscxu1EBhYljyu7Q==", "dev": true, "requires": { - "@octokit/types": "^2.11.1", - "is-plain-object": "^3.0.0", - "universal-user-agent": "^5.0.0" + "@octokit/types": "^6.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" } }, "@octokit/graphql": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.3.1.tgz", - "integrity": "sha512-hCdTjfvrK+ilU2keAdqNBWOk+gm1kai1ZcdjRfB30oA3/T6n53UVJb7w0L5cR3/rhU91xT3HSqCd+qbvH06yxA==", + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.8.tgz", + "integrity": "sha512-WnCtNXWOrupfPJgXe+vSmprZJUr0VIu14G58PMlkWGj3cH+KLZEfKMmbUQ6C3Wwx6fdhzVW1CD5RTnBdUHxhhA==", "dev": true, "requires": { "@octokit/request": "^5.3.0", - "@octokit/types": "^2.0.0", - "universal-user-agent": "^4.0.0" - }, - "dependencies": { - "universal-user-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", - "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", - "dev": true, - "requires": { - "os-name": "^3.1.0" - } - } + "@octokit/types": "^6.0.0", + "universal-user-agent": "^6.0.0" } }, + "@octokit/openapi-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.0.tgz", + "integrity": "sha512-J4bfM7lf8oZvEAdpS71oTvC1ofKxfEZgU5vKVwzZKi4QPiL82udjpseJwxPid9Pu2FNmyRQOX4iEj6W1iOSnPw==", + "dev": true + }, "@octokit/plugin-paginate-rest": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.0.tgz", - "integrity": "sha512-KoNxC3PLNar8UJwR+1VMQOw2IoOrrFdo5YOiDKnBhpVbKpw+zkBKNMNKwM44UWL25Vkn0Sl3nYIEGKY+gW5ebw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.6.2.tgz", + "integrity": "sha512-3Dy7/YZAwdOaRpGQoNHPeT0VU1fYLpIUdPyvR37IyFLgd6XSij4j9V/xN/+eSjF2KKvmfIulEh9LF1tRPjIiDA==", "dev": true, "requires": { - "@octokit/types": "^2.12.1" + "@octokit/types": "^6.0.1" } }, "@octokit/plugin-request-log": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz", - "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz", + "integrity": "sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==", "dev": true }, "@octokit/plugin-rest-endpoint-methods": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.8.0.tgz", - "integrity": "sha512-LUkTgZ53adPFC/Hw6mxvAtShUtGy3zbpcfCAJMWAN7SvsStV4p6TK7TocSv0Aak4TNmDLhbShTagGhpgz9mhYw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.3.1.tgz", + "integrity": "sha512-gKnD7zjja2Ne2YJniQhcmVFnJ4vkIVjBeMDQaV4fVJkLniZUmm8WZ2GRF7HkueI4kT0B6sfDK02TYwskRMG3dQ==", "dev": true, "requires": { - "@octokit/types": "^2.12.1", + "@octokit/types": "^6.0.3", "deprecation": "^2.3.1" } }, "@octokit/request": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.2.tgz", - "integrity": "sha512-zKdnGuQ2TQ2vFk9VU8awFT4+EYf92Z/v3OlzRaSh4RIP0H6cvW1BFPXq4XYvNez+TPQjqN+0uSkCYnMFFhcFrw==", + "version": "5.4.12", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.12.tgz", + "integrity": "sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.0.0", - "@octokit/types": "^2.11.1", + "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", - "is-plain-object": "^3.0.0", - "node-fetch": "^2.3.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", "once": "^1.4.0", - "universal-user-agent": "^5.0.0" + "universal-user-agent": "^6.0.0" } }, "@octokit/request-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.0.tgz", - "integrity": "sha512-rtYicB4Absc60rUv74Rjpzek84UbVHGHJRu4fNVlZ1mCcyUPPuzFfG9Rn6sjHrd95DEsmjSt1Axlc699ZlbDkw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.4.tgz", + "integrity": "sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA==", "dev": true, "requires": { - "@octokit/types": "^2.0.0", + "@octokit/types": "^6.0.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "@octokit/rest": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.6.0.tgz", - "integrity": "sha512-knh+4hPBA26AMXflFRupTPT3u9NcQmQzeBJl4Gcuf14Gn7dUh6Loc1ICWF0Pz18A6ElFZQt+wB9tFINSruIa+g==", + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.11.tgz", + "integrity": "sha512-qOF1/9qsyuyKVGpOdcfE0XJXoKBM3x/j2cAMTGCVeeaR+nZZ60JY1fPTjadelDgKVwHFfghqeX/li+X1YYGENg==", "dev": true, "requires": { - "@octokit/core": "^2.4.3", - "@octokit/plugin-paginate-rest": "^2.2.0", - "@octokit/plugin-request-log": "^1.0.0", - "@octokit/plugin-rest-endpoint-methods": "3.8.0" + "@octokit/core": "^3.2.3", + "@octokit/plugin-paginate-rest": "^2.6.2", + "@octokit/plugin-request-log": "^1.0.2", + "@octokit/plugin-rest-endpoint-methods": "4.3.1" } }, "@octokit/types": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.12.2.tgz", - "integrity": "sha512-1GHLI/Jll3j6F0GbYyZPFTcHZMGjAiRfkTEoRUyaVVk2IWbDdwEiClAJvXzfXCDayuGSNCqAUH8lpjZtqW9GDw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.0.tgz", + "integrity": "sha512-bMWBmg77MQTiRkOVyf50qK3QECWOEy43rLy/6fTWZ4HEwAhNfqzMcjiBDZAowkILwTrFvzE1CpP6gD0MuPHS+A==", "dev": true, "requires": { + "@octokit/openapi-types": "^2.0.0", "@types/node": ">= 8" } }, @@ -863,15 +899,40 @@ "micromatch": "^4.0.2" }, "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, "conventional-changelog-angular": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.6.tgz", - "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", + "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true } } }, @@ -882,12 +943,12 @@ "dev": true }, "@semantic-release/github": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.0.5.tgz", - "integrity": "sha512-1nJCMeomspRIXKiFO3VXtkUMbIBEreYLFNBdWoLjvlUNcEK0/pEbupEZJA3XHfJuSzv43u3OLpPhF/JBrMuv+A==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.2.0.tgz", + "integrity": "sha512-tMRnWiiWb43whRHvbDGXq4DGEbKRi56glDpXDJZit4PIiwDPX7Kx3QzmwRtDOcG+8lcpGjpdPabYZ9NBxoI2mw==", "dev": true, "requires": { - "@octokit/rest": "^17.0.0", + "@octokit/rest": "^18.0.0", "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "bottleneck": "^2.18.1", @@ -906,9 +967,9 @@ }, "dependencies": { "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -918,13 +979,21 @@ } }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } } }, "universalify": { @@ -936,47 +1005,58 @@ } }, "@semantic-release/npm": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.5.tgz", - "integrity": "sha512-D+oEmsx9aHE1q806NFQwSC9KdBO8ri/VO99eEz0wWbX2jyLqVyWr7t0IjKC8aSnkkQswg/4KN/ZjfF6iz1XOpw==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.9.tgz", + "integrity": "sha512-VsmmQF3/n7mDbm6AGL0yPD3QNTGsHdinBtkyyerN1eLgvhdGJ/vEeAvmDMARiuf5Ev9cFeCheF0wLyUZNlAkeA==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", - "execa": "^4.0.0", + "execa": "^5.0.0", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "nerf-dart": "^1.0.0", "normalize-url": "^5.0.0", - "npm": "^6.10.3", + "npm": "^6.14.8", "rc": "^1.2.8", "read-pkg": "^5.0.0", "registry-auth-token": "^4.0.0", "semver": "^7.1.2", - "tempy": "^0.5.0" + "tempy": "^1.0.0" }, "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "execa": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", - "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", @@ -986,13 +1066,16 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true }, "is-stream": { "version": "2.0.0", @@ -1001,13 +1084,21 @@ "dev": true }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } } }, "npm-run-path": { @@ -1019,15 +1110,24 @@ "path-key": "^3.0.0" } }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -1075,16 +1175,35 @@ "read-pkg-up": "^7.0.0" }, "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, "conventional-changelog-angular": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.6.tgz", - "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", + "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" } }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1096,14 +1215,20 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" } }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1138,14 +1263,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -1387,9 +1512,9 @@ } }, "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { "debug": "4" @@ -2259,44 +2384,6 @@ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -2462,117 +2549,719 @@ } }, "conventional-changelog-writer": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz", - "integrity": "sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.18.tgz", + "integrity": "sha512-mAQDCKyB9HsE8Ko5cCM1Jn1AWxXPYV0v8dFPabZRkvsiWUul2YyAqbIaoMKF88Zf2ffnOPSvKhboLf3fnjo5/A==", "dev": true, "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.2", + "compare-func": "^2.0.0", + "conventional-commits-filter": "^2.0.7", "dateformat": "^3.0.0", - "handlebars": "^4.4.0", + "handlebars": "^4.7.6", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.15", - "meow": "^5.0.0", + "meow": "^8.0.0", "semver": "^6.0.0", "split": "^1.0.0", - "through2": "^3.0.0" + "through2": "^4.0.0" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true - } - } - }, - "conventional-commits-filter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz", - "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==", - "dev": true, - "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz", - "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==", - "dev": true, - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.1", - "lodash": "^4.17.15", - "meow": "^5.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0", - "trim-off-newlines": "^1.0.0" - } - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "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": { + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "compare-func": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" } }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } - } - }, - "cross-env": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", - "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.1" - } - }, - "cross-spawn": { + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "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" + } + }, + "hosted-git-info": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "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" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, + "meow": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", + "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.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" + } + }, + "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": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "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 + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "dev": true + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, + "conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz", + "integrity": "sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==", + "dev": true, + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^2.0.0", + "through2": "^4.0.0", + "trim-off-newlines": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "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" + } + }, + "hosted-git-info": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "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" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, + "meow": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", + "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + } + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, + "normalize-package-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", + "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "dev": true, + "requires": { + "hosted-git-info": "^3.0.6", + "resolve": "^1.17.0", + "semver": "^7.3.2", + "validate-npm-package-license": "^3.0.1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.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" + } + }, + "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": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "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 + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + } + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "dev": true + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "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" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "cross-env": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-spawn": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", @@ -2762,6 +3451,30 @@ "object-keys": "^1.0.12" } }, + "del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "dev": true, + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } + } + }, "delay": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", @@ -2895,73 +3608,34 @@ "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, "dts-critic": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.2.3.tgz", - "integrity": "sha512-CErYGgQiloLH0PZ/vrLH5+WgpPbHiOj77qFF+6pGuGtlQzb43oFUSS9Qetr4y9fAg2ZOG9ZvGp7h+jhh0kkbAg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.3.4.tgz", + "integrity": "sha512-OjLTrSBCFbi1tDAiOXcP7G20W3HI3eIzkpSpLwvH7oDFZYdqFCMe9lsNhMZFXqsNcSTpRg3+PBS4fF27+h1qew==", "dev": true, "requires": { - "@definitelytyped/header-parser": "0.0.34", + "@definitelytyped/header-parser": "^0.0.64", "command-exists": "^1.2.8", "rimraf": "^3.0.2", "semver": "^6.2.0", "tmp": "^0.2.1", - "typescript": "^3.9.2", - "yargs": "^12.0.5" + "yargs": "^15.3.1" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "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==", + "@definitelytyped/header-parser": { + "version": "0.0.64", + "resolved": "https://registry.npmjs.org/@definitelytyped/header-parser/-/header-parser-0.0.64.tgz", + "integrity": "sha512-vwh6ojw0PYptEiAogAdKNrwYy2Dv0zZj6M5bjKAoF7/xOmY5B/QluJnNmgF7Q+fxLG2vVxYDbkc26r04xYIZQA==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "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" + "@definitelytyped/typescript-versions": "^0.0.64", + "@types/parsimmon": "^1.10.1", + "parsimmon": "^1.13.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==", + "@definitelytyped/typescript-versions": { + "version": "0.0.64", + "resolved": "https://registry.npmjs.org/@definitelytyped/typescript-versions/-/typescript-versions-0.0.64.tgz", + "integrity": "sha512-h+RvQPzvC/yVtZ/FqttXoIac/X1htXrmuNbvmQP+RiVonGunKq7S8ona5tm7ckiheur6VvtKQGe+zQIDF3sErQ==", "dev": true }, "semver": { @@ -2970,25 +3644,6 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -2997,36 +3652,6 @@ "requires": { "rimraf": "^3.0.0" } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -3152,9 +3777,9 @@ }, "dependencies": { "execa": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", - "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -3169,9 +3794,9 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -3915,81 +4540,15 @@ }, "estraverse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "extend": { "version": "3.0.2", @@ -4021,9 +4580,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", - "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -4047,9 +4606,9 @@ "dev": true }, "fastq": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz", - "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", + "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -4383,12 +4942,6 @@ "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", "dev": true }, - "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 - }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -4407,15 +4960,6 @@ "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", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -4791,9 +5335,9 @@ "dev": true }, "globby": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", - "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -4805,9 +5349,9 @@ }, "dependencies": { "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } } @@ -5433,12 +5977,6 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -5533,6 +6071,18 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true + }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -5540,13 +6090,10 @@ "dev": true }, "is-plain-object": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", - "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", - "dev": true, - "requires": { - "isobject": "^4.0.0" - } + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true }, "is-promise": { "version": "2.1.0", @@ -5584,12 +6131,6 @@ "is-unc-path": "^1.0.0" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -5653,12 +6194,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "dev": true - }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -5903,6 +6438,12 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -6035,15 +6576,6 @@ "readable-stream": "^2.0.5" } }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "lcov-result-merger": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-3.1.0.tgz", @@ -6368,9 +6900,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -6723,12 +7255,6 @@ "yallist": "^3.0.2" } }, - "macos-release": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", - "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==", - "dev": true - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6746,15 +7272,6 @@ } } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "map-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", @@ -6888,19 +7405,18 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6929,9 +7445,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6945,17 +7461,6 @@ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "meow": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", @@ -6980,9 +7485,9 @@ "dev": true }, "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, "micromatch": { @@ -6996,9 +7501,9 @@ } }, "mime": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", - "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", "dev": true }, "mime-db": { @@ -7446,9 +7951,9 @@ } }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "nerf-dart": { @@ -7504,9 +8009,9 @@ } }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", "dev": true }, "node-pre-gyp": { @@ -7608,9 +8113,9 @@ } }, "normalize-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-5.0.0.tgz", - "integrity": "sha512-bAEm2fx8Dq/a35Z6PIRkkBBJvR56BbEJvhpNtvCZ4W9FyORSna77fn+xtYFjqk5JpBS+fMnAOG/wFgkQBmB7hw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-5.3.0.tgz", + "integrity": "sha512-9/nOVLYYe/dO/eJeQUNaGUF4m4Z5E7cb9oNTKabH+bNf19mqj60txTcveQxL0GlcWLXCxkOu2/LwL8oW0idIDA==", "dev": true }, "now-and-later": { @@ -7623,9 +8128,9 @@ } }, "npm": { - "version": "6.14.4", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.4.tgz", - "integrity": "sha512-B8UDDbWvdkW6RgXFn8/h2cHJP/u/FPa4HWeGzW23aNEBARN3QPrRaHqPIZW2NSN3fW649gtgUDNZpaRs0zTMPw==", + "version": "6.14.9", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.9.tgz", + "integrity": "sha512-yHi1+i9LyAZF1gAmgyYtVk+HdABlLy94PMIDoK1TRKWvmFQAt5z3bodqVwKvzY0s6dLqQPVsRLiwhJfNtiHeCg==", "dev": true, "requires": { "JSONStream": "^1.3.5", @@ -7634,7 +8139,7 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.7", + "bin-links": "^1.1.8", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", @@ -7655,9 +8160,9 @@ "find-npm-prefix": "^1.0.2", "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.3.0", + "gentle-fs": "^2.3.1", "glob": "^7.1.6", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.4", "has-unicode": "~2.0.1", "hosted-git-info": "^2.8.8", "iferr": "^1.0.2", @@ -7670,14 +8175,14 @@ "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", "lazy-property": "~1.0.0", - "libcipm": "^4.0.7", + "libcipm": "^4.0.8", "libnpm": "^3.0.1", "libnpmaccess": "^3.0.2", "libnpmhook": "^5.0.3", "libnpmorg": "^1.0.1", "libnpmsearch": "^2.0.2", "libnpmteam": "^1.0.2", - "libnpx": "^10.2.2", + "libnpx": "^10.2.4", "lock-verify": "^2.1.0", "lockfile": "^1.0.4", "lodash._baseindexof": "*", @@ -7692,23 +8197,23 @@ "lodash.uniq": "~4.5.0", "lodash.without": "~4.4.0", "lru-cache": "^5.1.1", - "meant": "~1.0.1", + "meant": "^1.0.2", "mississippi": "^3.0.0", - "mkdirp": "^0.5.4", + "mkdirp": "^0.5.5", "move-concurrently": "^1.0.1", "node-gyp": "^5.1.0", - "nopt": "~4.0.1", + "nopt": "^4.0.3", "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.2", + "npm-audit-report": "^1.3.3", "npm-cache-filename": "~1.0.2", "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.4", + "npm-lifecycle": "^3.1.5", "npm-package-arg": "^6.1.1", "npm-packlist": "^1.4.8", "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.4", - "npm-registry-fetch": "^4.0.3", - "npm-user-validate": "~1.0.0", + "npm-registry-fetch": "^4.0.7", + "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "once": "~1.4.0", "opener": "^1.5.1", @@ -7783,17 +8288,6 @@ "humanize-ms": "^1.2.1" } }, - "ajv": { - "version": "5.5.2", - "bundled": true, - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "ansi-align": { "version": "2.0.0", "bundled": true, @@ -7916,7 +8410,7 @@ } }, "bin-links": { - "version": "1.1.7", + "version": "1.1.8", "bundled": true, "dev": true, "requires": { @@ -8071,26 +8565,41 @@ } }, "cliui": { - "version": "4.1.0", + "version": "5.0.0", "bundled": true, "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", "bundled": true, "dev": true }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "strip-ansi": { - "version": "4.0.0", + "version": "5.2.0", "bundled": true, "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -8109,11 +8618,6 @@ "mkdirp": "~0.5.0" } }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true - }, "code-point-at": { "version": "1.1.0", "bundled": true, @@ -8205,11 +8709,11 @@ } }, "configstore": { - "version": "3.1.2", + "version": "3.1.5", "bundled": true, "dev": true, "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", "make-dir": "^1.0.0", "unique-string": "^1.0.0", @@ -8385,7 +8889,7 @@ } }, "dot-prop": { - "version": "4.2.0", + "version": "4.2.1", "bundled": true, "dev": true, "requires": { @@ -8452,6 +8956,11 @@ "bundled": true, "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "bundled": true, + "dev": true + }, "encoding": { "version": "0.1.12", "bundled": true, @@ -8557,11 +9066,6 @@ "bundled": true, "dev": true }, - "fast-deep-equal": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, "fast-json-stable-stringify": { "version": "2.0.0", "bundled": true, @@ -8577,14 +9081,6 @@ "bundled": true, "dev": true }, - "find-up": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, "flush-write-stream": { "version": "1.0.3", "bundled": true, @@ -8783,7 +9279,7 @@ "dev": true }, "gentle-fs": { - "version": "2.3.0", + "version": "2.3.1", "bundled": true, "dev": true, "requires": { @@ -8813,7 +9309,7 @@ } }, "get-caller-file": { - "version": "1.0.3", + "version": "2.0.5", "bundled": true, "dev": true }, @@ -8880,7 +9376,7 @@ } }, "graceful-fs": { - "version": "4.2.3", + "version": "4.2.4", "bundled": true, "dev": true }, @@ -8890,12 +9386,35 @@ "dev": true }, "har-validator": { - "version": "5.1.0", + "version": "5.1.5", "bundled": true, "dev": true, "requires": { - "ajv": "^5.3.0", + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "bundled": true, + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true, + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "bundled": true, + "dev": true + } } }, "has": { @@ -9037,11 +9556,6 @@ "validate-npm-package-name": "^3.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, "ip": { "version": "1.1.5", "bundled": true, @@ -9187,11 +9701,6 @@ "bundled": true, "dev": true }, - "json-schema-traverse": { - "version": "0.3.1", - "bundled": true, - "dev": true - }, "json-stringify-safe": { "version": "5.0.1", "bundled": true, @@ -9226,16 +9735,8 @@ "bundled": true, "dev": true }, - "lcid": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "libcipm": { - "version": "4.0.7", + "version": "4.0.8", "bundled": true, "dev": true, "requires": { @@ -9245,7 +9746,7 @@ "find-npm-prefix": "^1.0.2", "graceful-fs": "^4.1.11", "ini": "^1.3.5", - "lock-verify": "^2.0.2", + "lock-verify": "^2.1.0", "mkdirp": "^0.5.1", "npm-lifecycle": "^3.0.0", "npm-logical-tree": "^1.2.1", @@ -9404,7 +9905,7 @@ } }, "libnpx": { - "version": "10.2.2", + "version": "10.2.4", "bundled": true, "dev": true, "requires": { @@ -9415,16 +9916,7 @@ "update-notifier": "^2.3.0", "which": "^1.3.0", "y18n": "^4.0.0", - "yargs": "^11.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "yargs": "^14.2.3" } }, "lock-verify": { @@ -9555,36 +10047,11 @@ "ssri": "^6.0.0" } }, - "map-age-cleaner": { - "version": "0.1.3", - "bundled": true, - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "meant": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true, "dev": true }, - "mem": { - "version": "4.3.0", - "bundled": true, - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "bundled": true, - "dev": true - } - } - }, "mime-db": { "version": "1.35.0", "bundled": true, @@ -9606,6 +10073,11 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + }, "minizlib": { "version": "1.3.3", "bundled": true, @@ -9643,7 +10115,7 @@ } }, "mkdirp": { - "version": "0.5.4", + "version": "0.5.5", "bundled": true, "dev": true, "requires": { @@ -9687,11 +10159,6 @@ "bundled": true, "dev": true }, - "nice-try": { - "version": "1.0.5", - "bundled": true, - "dev": true - }, "node-fetch-npm": { "version": "2.0.2", "bundled": true, @@ -9721,7 +10188,7 @@ } }, "nopt": { - "version": "4.0.1", + "version": "4.0.3", "bundled": true, "dev": true, "requires": { @@ -9751,7 +10218,7 @@ } }, "npm-audit-report": { - "version": "1.3.2", + "version": "1.3.3", "bundled": true, "dev": true, "requires": { @@ -9781,7 +10248,7 @@ } }, "npm-lifecycle": { - "version": "3.1.4", + "version": "3.1.5", "bundled": true, "dev": true, "requires": { @@ -9847,7 +10314,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.3", + "version": "4.0.7", "bundled": true, "dev": true, "requires": { @@ -9861,7 +10328,7 @@ }, "dependencies": { "safe-buffer": { - "version": "5.2.0", + "version": "5.2.1", "bundled": true, "dev": true } @@ -9876,7 +10343,7 @@ } }, "npm-user-validate": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true, "dev": true }, @@ -9916,112 +10383,43 @@ "bundled": true, "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.1", - "bundled": true, - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "bundled": true, - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "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" - } - } - } - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, - "p-defer": { - "version": "1.0.0", + "once": { + "version": "1.4.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "wrappy": "1" + } }, - "p-finally": { - "version": "1.0.0", + "opener": { + "version": "1.5.1", "bundled": true, "dev": true }, - "p-is-promise": { - "version": "2.1.0", + "os-homedir": { + "version": "1.0.2", "bundled": true, "dev": true }, - "p-limit": { - "version": "1.2.0", + "os-tmpdir": { + "version": "1.0.2", "bundled": true, - "dev": true, - "requires": { - "p-try": "^1.0.0" - } + "dev": true }, - "p-locate": { - "version": "2.0.0", + "osenv": { + "version": "0.1.5", "bundled": true, "dev": true, "requires": { - "p-limit": "^1.1.0" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, - "p-try": { + "p-finally": { "version": "1.0.0", "bundled": true, "dev": true @@ -10290,13 +10688,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true - } } }, "read": { @@ -10422,7 +10813,7 @@ "dev": true }, "require-main-filename": { - "version": "1.0.1", + "version": "2.0.0", "bundled": true, "dev": true }, @@ -10621,7 +11012,7 @@ } }, "spdx-license-ids": { - "version": "3.0.3", + "version": "3.0.5", "bundled": true, "dev": true }, @@ -10956,6 +11347,21 @@ "xdg-basedir": "^3.0.0" } }, + "uri-js": { + "version": "4.4.0", + "bundled": true, + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "bundled": true, + "dev": true + } + } + }, "url-parse-lax": { "version": "1.0.0", "bundled": true, @@ -11072,22 +11478,41 @@ } }, "wrap-ansi": { - "version": "2.1.0", + "version": "5.1.0", "bundled": true, "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, "string-width": { - "version": "1.0.2", + "version": "3.1.0", "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } } } @@ -11128,37 +11553,105 @@ "dev": true }, "yargs": { - "version": "11.1.1", + "version": "14.2.3", "bundled": true, "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" }, "dependencies": { - "y18n": { - "version": "3.2.1", + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", "bundled": true, "dev": true + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "yargs-parser": { - "version": "9.0.2", + "version": "15.0.1", "bundled": true, "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "bundled": true, + "dev": true + } } } } @@ -11243,23 +11736,6 @@ } } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - } - } - }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -11493,27 +11969,6 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-name": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", - "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", - "dev": true, - "requires": { - "macos-release": "^2.2.0", - "windows-release": "^3.1.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -11530,16 +11985,10 @@ "os-tmpdir": "^1.0.0" } }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, "p-each-series": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", - "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "dev": true }, "p-filter": { @@ -11559,18 +12008,6 @@ } } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -11624,13 +12061,10 @@ } }, "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.0.0.tgz", + "integrity": "sha512-G02ZnMbmDL84Aue/AApFAyP7gaK7PFU2v9xFqUp10y9g1CJ05OVpQjylwYNNCwyVW051rpU8F450Zi2fEV8hVw==", + "dev": true }, "p-try": { "version": "1.0.0", @@ -12169,9 +12603,9 @@ "dev": true }, "registry-auth-token": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", - "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", "dev": true, "requires": { "rc": "^1.2.8" @@ -12282,12 +12716,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -12370,9 +12798,9 @@ } }, "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", "dev": true }, "rxjs": { @@ -12403,9 +12831,9 @@ "dev": true }, "semantic-release": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.0.7.tgz", - "integrity": "sha512-F6FzJI1yiGavzCTXir4yPthK/iozZPJ4myUYndiHhSHbmOcCSJ2m7V+P6sFwVpDpQKQp1Q31M68sTJ/Q/27Bow==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.3.0.tgz", + "integrity": "sha512-enhDayMZRP4nWcWAMBFHHB7THRaIcRdUAZv3lxd65pXs2ttzay7IeCvRRrGayRWExtnY0ulwRz5Ycp88Dv/UeQ==", "dev": true, "requires": { "@semantic-release/commit-analyzer": "^8.0.0", @@ -12452,9 +12880,9 @@ } }, "execa": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz", - "integrity": "sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -12479,21 +12907,21 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" } }, "hosted-git-info": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", - "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", + "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", "dev": true, "requires": { - "lru-cache": "^5.1.1" + "lru-cache": "^6.0.0" } }, "is-stream": { @@ -12511,6 +12939,15 @@ "p-locate": "^4.1.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -12545,14 +12982,14 @@ "dev": true }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", + "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, @@ -12598,6 +13035,12 @@ "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -13031,12 +13474,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -13081,9 +13518,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13195,9 +13632,9 @@ }, "dependencies": { "bl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.0.tgz", - "integrity": "sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz", + "integrity": "sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==", "dev": true, "requires": { "readable-stream": "^3.0.1" @@ -13238,14 +13675,15 @@ "dev": true }, "tempy": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.5.0.tgz", - "integrity": "sha512-VEY96x7gbIRfsxqsafy2l5yVxxp3PhwAGoWMyC2D2Zt5DmEv+2tGiPOrquNRpf21hhGnKLVEsuqleqiZmKG/qw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.0.tgz", + "integrity": "sha512-eLXG5B1G0mRPHmgH2WydPl5v4jH35qEn3y/rA/aahKhIa91Pn119SsU7n7v/433gtT9ONzC8ISvNHIh2JSTm0w==", "dev": true, "requires": { + "del": "^6.0.0", "is-stream": "^2.0.0", "temp-dir": "^2.0.0", - "type-fest": "^0.12.0", + "type-fest": "^0.16.0", "unique-string": "^2.0.0" }, "dependencies": { @@ -13256,9 +13694,9 @@ "dev": true }, "type-fest": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", - "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", "dev": true } } @@ -13544,14 +13982,11 @@ "dev": true }, "uglify-js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", - "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz", + "integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==", "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.3" - } + "optional": true }, "unc-path-regex": { "version": "0.1.2", @@ -13585,13 +14020,10 @@ } }, "universal-user-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", - "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", - "dev": true, - "requires": { - "os-name": "^3.1.0" - } + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true }, "universalify": { "version": "0.1.2", @@ -13794,15 +14226,6 @@ "string-width": "^1.0.2 || 2" } }, - "windows-release": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz", - "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==", - "dev": true, - "requires": { - "execa": "^1.0.0" - } - }, "wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", @@ -13823,16 +14246,6 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f3d838e8d425..c2d5abb5cac0 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "debug": "^4.1.1", "dottie": "^2.0.0", "inflection": "1.12.0", - "lodash": "^4.17.15", + "lodash": "^4.17.20", "moment": "^2.26.0", "moment-timezone": "^0.5.31", "retry-as-promised": "^3.2.0", @@ -79,11 +79,11 @@ "nyc": "^15.0.0", "p-map": "^4.0.0", "p-props": "^4.0.0", - "p-timeout": "^3.2.0", + "p-timeout": "^4.0.0", "pg": "^8.2.1", "pg-hstore": "^2.x", "rimraf": "^3.0.2", - "semantic-release": "^17.0.7", + "semantic-release": "^17.3.0", "sinon": "^9.0.2", "sinon-chai": "^3.3.0", "sqlite3": "^4.2.0", @@ -139,7 +139,9 @@ } }, "release": { - "branches": ["v6"], + "branches": [ + "v6" + ], "verifyConditions": [ "@semantic-release/npm", "@semantic-release/github" From 0eb93af6743a02c746ab69e9702c5fc59806b699 Mon Sep 17 00:00:00 2001 From: papb Date: Thu, 3 Dec 2020 23:34:50 -0300 Subject: [PATCH 221/414] test: improve 'running queries' detection (backport of #12885) --- test/integration/support.js | 58 ++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/test/integration/support.js b/test/integration/support.js index 5ca88076d8a5..44d68aa1b044 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -1,8 +1,15 @@ 'use strict'; +// Store local references to `setTimeout` and `clearTimeout` asap, so that we can use them within `p-timeout`, +// avoiding to be affected unintentionally by `sinon.useFakeTimers()` called by the tests themselves. +const { setTimeout, clearTimeout } = global; + +const pTimeout = require('p-timeout'); const Support = require('../support'); -const runningQueries = new Set(); +const CLEANUP_TIMEOUT = Number.parseInt(process.env.SEQ_TEST_CLEANUP_TIMEOUT, 10) || 10000; + +let runningQueries = new Set(); before(function() { this.sequelize.addHook('beforeQuery', (options, query) => { @@ -17,14 +24,49 @@ beforeEach(async function() { await Support.clearDatabase(this.sequelize); }); -afterEach(function() { - if (runningQueries.size === 0) { - return; +afterEach(async function() { + // Note: recall that throwing an error from a `beforeEach` or `afterEach` hook in Mocha causes the entire test suite to abort. + + let runningQueriesProblem; + + if (runningQueries.size > 0) { + runningQueriesProblem = `Expected 0 queries running after this test, but there are still ${ + runningQueries.size + } queries running in the database (or, at least, the \`afterQuery\` Sequelize hook did not fire for them):\n\n${ + // prettier-ignore + [...runningQueries].map(query => ` ${query.uuid}: ${query.sql}`).join('\n') + }`; + } + + runningQueries = new Set(); + + try { + await pTimeout( + Support.clearDatabase(this.sequelize), + CLEANUP_TIMEOUT, + `Could not clear database after this test in less than ${CLEANUP_TIMEOUT}ms. This test crashed the DB, and testing cannot continue. Aborting.`, + { customTimers: { setTimeout, clearTimeout } } + ); + } catch (error) { + let message = error.message; + if (runningQueriesProblem) { + message += `\n\n Also, ${runningQueriesProblem}`; + } + message += `\n\n Full test name:\n ${this.currentTest.fullTitle()}`; + + // Throw, aborting the entire Mocha execution + throw new Error(message); + } + + if (runningQueriesProblem) { + if (this.test.ctx.currentTest.state === 'passed') { + // `this.test.error` is an obscure Mocha API that allows failing a test from the `afterEach` hook + // This is better than throwing because throwing would cause the entire Mocha execution to abort + this.test.error(new Error(`This test passed, but ${runningQueriesProblem}`)); + } else { + console.log(` ${runningQueriesProblem}`); + } } - let msg = `Expected 0 running queries. ${runningQueries.size} queries still running in ${this.currentTest.fullTitle()}\n`; - msg += 'Queries:\n\n'; - msg += [...runningQueries].map(query => `${query.uuid}: ${query.sql}`).join('\n'); - throw new Error(msg); }); module.exports = Support; From 2a79df36b9e807bb4b282bd528ef82d6a672745f Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Fri, 4 Dec 2020 00:32:21 -0300 Subject: [PATCH 222/414] test: begin transition to GitHub Actions (#12887) --- .github/workflows/ci.yml | 149 +++++++++++++++++++++++++ test/config/mssql.json | 17 +++ test/integration/configuration.test.js | 7 +- test/integration/transaction.test.js | 13 ++- 4 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 test/config/mssql.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..1eea9d5cdb69 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,149 @@ +name: CI +on: [push, pull_request] + +env: + SEQ_DB: sequelize_test + SEQ_USER: sequelize_test + SEQ_PW: sequelize_test + +jobs: + lint: + name: Lint code and docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm install + - run: npm run lint + - run: npm run lint-docs + test-typings: + name: TS Typings + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm install + - run: npm run test-typings + test-sqlite: + name: SQLite + runs-on: ubuntu-latest + env: + DIALECT: sqlite + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm install + - name: Unit Tests + run: npm run test-unit + - name: Integration Tests + run: npm run test-integration + test-postgres: + strategy: + fail-fast: false + matrix: + postgres-version: [9.5, 10] # Does not work with 12 + native: [true, false] + name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }} + runs-on: ubuntu-latest + services: + postgres: + image: sushantdhiman/postgres:${{ matrix.postgres-version }} + env: + POSTGRES_USER: sequelize_test + POSTGRES_DB: sequelize_test + POSTGRES_PASSWORD: sequelize_test + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + env: + DIALECT: ${{ matrix.native && 'postgres-native' || 'postgres' }} + steps: + - run: PGPASSWORD=sequelize_test psql -h localhost -p 5432 -U sequelize_test sequelize_test -c '\l' + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm install + - run: npm install pg-native + if: matrix.native + - name: Unit Tests + run: npm run test-unit + - name: Integration Tests + run: npm run test-integration + test-mysql-mariadb: + strategy: + fail-fast: false + matrix: + include: + - name: MySQL 5.7 + image: mysql:5.7 + dialect: mysql + - name: MariaDB 10.3 + image: mariadb:10.3 + dialect: mariadb + name: ${{ matrix.name }} + runs-on: ubuntu-latest + services: + mysql: + image: ${{ matrix.image }} + env: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin -usequelize_test -psequelize_test status" --health-interval 10s --health-timeout 5s --health-retries 5 --tmpfs /var/lib/mysql:rw + env: + DIALECT: ${{ matrix.dialect }} + steps: + - run: mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm install + - name: Unit Tests + run: npm run test-unit + - name: Integration Tests + run: npm run test-integration + test-mssql: + strategy: + fail-fast: false + matrix: + mssql-version: [2017, 2019] + name: MSSQL ${{ matrix.mssql-version }} + runs-on: ubuntu-latest + services: + mssql: + image: mcr.microsoft.com/mssql/server:${{ matrix.mssql-version }}-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: Password12! + ports: + - 1433:1433 + options: >- + --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Password12!" -l 30 -Q \"SELECT 1\" || exit 1" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 10 + env: + DIALECT: mssql + steps: + - run: /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Password12!" -Q "CREATE DATABASE sequelize_test; ALTER DATABASE sequelize_test SET READ_COMMITTED_SNAPSHOT ON;" + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm install + - name: Unit Tests + run: npm run test-unit + - name: Integration Tests + run: npm run test-integration diff --git a/test/config/mssql.json b/test/config/mssql.json new file mode 100644 index 000000000000..04564f5e789b --- /dev/null +++ b/test/config/mssql.json @@ -0,0 +1,17 @@ +{ + "host": "localhost", + "username": "SA", + "password": "Password12!", + "port": 1433, + "database": "sequelize_test", + "dialectOptions": { + "options": { + "encrypt": false, + "requestTimeout": 25000 + } + }, + "pool": { + "max": 5, + "idle": 3000 + } +} diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js index aa71c77920e2..96f8bafd66da 100644 --- a/test/integration/configuration.test.js +++ b/test/integration/configuration.test.js @@ -20,8 +20,8 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { it('when we don\'t have the correct server details', async () => { const options = { logging: false, - host: '0.0.0.1', - port: config[dialect].port, + host: 'localhost', + port: 19999, // Wrong port dialect }; @@ -47,8 +47,7 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { it('when we don\'t have the correct login information', async () => { if (dialect === 'mssql') { - // NOTE: Travis seems to be having trouble with this test against the - // AWS instance. Works perfectly fine on a local setup. + // TODO: GitHub Actions seems to be having trouble with this test. Works perfectly fine on a local setup. expect(true).to.be.true; return; } diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index b5215c257cc6..68b32239a939 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -578,14 +578,21 @@ if (current.dialect.supports.transactions) { const transaction = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }); await User.findAll( { transaction } ); - await Promise.all([// Update should not succeed before transaction has committed + await Promise.all([ + // Update should not succeed before transaction has committed User.update({ username: 'joe' }, { where: { username: 'jan' } - }).then(() => expect(transactionSpy).to.have.been.called ), delay(2000) + }).then(() => { + expect(transactionSpy).to.have.been.called; + expect(transaction.finished).to.equal('commit'); + }), + + delay(4000) + .then(transactionSpy) .then(() => transaction.commit()) - .then(transactionSpy)]); + ]); }); } From 72f31f6b1daba9b3933c75c663f0f0e003a9653a Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 19:45:33 -0300 Subject: [PATCH 223/414] chore: birth of the 'main' branch Note: this is an empty commit just to clearly mark this point in history. From 062dbee66483f8c9050114a4842fd3423aea7c3c Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 19:54:24 -0300 Subject: [PATCH 224/414] docs: add myself to contact info --- CONTACT.md | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTACT.md b/CONTACT.md index 9b60d3d84330..0cae62d734e9 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -5,5 +5,6 @@ You can use the information below to contact maintainers directly. We will try t ### Via Email +- **Pedro Augusto de Paula Barbosa** papb1996@gmail.com - **Jan Aagaard Meier** janzeh@gmail.com - **Sushant Dhiman** sushantdhiman@outlook.com diff --git a/package.json b/package.json index c2d5abb5cac0..1e591dd052e7 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "Jan Aagaard Meier ", "Daniel Durante ", "Mick Hansen ", - "Sushant Dhiman " + "Sushant Dhiman ", + "Pedro Augusto de Paula Barbosa " ], "repository": { "type": "git", From aea376daf77325cc0e5251417b20a501bf90b33d Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 20:13:55 -0300 Subject: [PATCH 225/414] ci: drop travis and appveyor --- .github/workflows/ci.yml | 12 ++++++ .travis.yml | 84 ---------------------------------------- appveyor.yml | 35 ----------------- 3 files changed, 12 insertions(+), 119 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eea9d5cdb69..5bf5ce4c5a8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,3 +147,15 @@ jobs: run: npm run test-unit - name: Integration Tests run: npm run test-integration + release: + name: Release + runs-on: ubuntu-latest + needs: [lint, test-typings, test-sqlite, test-postgres, test-mysql-mariadb, test-mssql] + if: github.event.push + steps: + # - uses: actions/checkout@v2 + # - uses: actions/setup-node@v1 + # with: + # node-version: 12.x + # - run: npm run semantic-release + - run: echo 'Auto-release would happen!' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f90a16436c06..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,84 +0,0 @@ -sudo: true -dist: trusty - -language: node_js - -branches: - only: - - master - - v6 - except: - - /^v\d+\.\d+\.\d+$/ - -cache: npm - -install: - - npm ci - - |- - if [ "$DIALECT" = "postgres-native" ]; then npm install pg-native; fi - -env: - global: - - SEQ_DB=sequelize_test - - SEQ_USER=sequelize_test - - SEQ_PW=sequelize_test - - SEQ_HOST=127.0.0.1 - - COVERAGE=true - -before_script: - # setup docker - - if [ $MARIADB_VER ]; then export MARIADB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mariadb; fi - - if [ $MYSQL_VER ]; then export MYSQLDB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mysql; fi - - if [ $POSTGRES_VER ] || [ $MARIADB_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MARIADB_VER} ${MYSQL_VER}; fi - - wait_for() { docker run --net sequelize_default jwilder/dockerize -timeout 2m -wait "$1"; } - - if [ $POSTGRES_VER ]; then wait_for tcp://${POSTGRES_VER}:5432; fi - - if [ $MARIADB_VER ]; then wait_for tcp://${MARIADB_VER}:3306; fi - - if [ $MYSQL_VER ]; then wait_for tcp://${MYSQL_VER}:3306; fi - -script: - - |- - npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info - -jobs: - include: - - stage: lint - node_js: '10' - script: - - npm run lint - - npm run lint-docs - - npm run test-typings - - stage: test - node_js: '10' - env: DIALECT=sqlite - - stage: test - node_js: '10' - sudo: required - env: MARIADB_VER=mariadb-103 SEQ_MARIADB_PORT=8960 DIALECT=mariadb - - stage: test - node_js: '10' - sudo: required - env: MYSQL_VER=mysql-57 SEQ_MYSQL_PORT=8980 DIALECT=mysql - - stage: test - node_js: '10' - sudo: required - env: POSTGRES_VER=postgres-10 SEQ_PG_PORT=8991 DIALECT=postgres - - stage: test - node_js: '10' - sudo: required - env: POSTGRES_VER=postgres-10 SEQ_PG_PORT=8991 SEQ_PG_MINIFY_ALIASES=1 DIALECT=postgres - script: - - npm run test-integration - - stage: test - node_js: '10' - sudo: required - env: POSTGRES_VER=postgres-95 SEQ_PG_PORT=8990 DIALECT=postgres-native - - stage: release - node_js: '10' - script: - - npm run semantic-release - -stages: - - lint - - test - - name: release - if: branch = v6 AND type = push AND fork = false diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 703a7b8a3ae5..000000000000 --- a/appveyor.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: '{build}' - -platform: - - x64 - -services: - - mssql2017 - -shallow_clone: true - -environment: - matrix: - - { NODE_VERSION: 10, DIALECT: mssql, COVERAGE: true } - -install: - - ps: Install-Product node $env:NODE_VERSION x64 - - npm ci - - npm i --save-dev codecov - -build: off - -before_test: - - ps: . .\scripts\appveyor-setup.ps1 - -test_script: - - 'IF "%COVERAGE%" == "true" (npm run cover) ELSE (npm test)' - -after_test: - - npx codecov - -branches: - only: - - master - - v6 - - /^greenkeeper/.*$/ From c453b451c1e7706c378f4e8a6c07ac8d261f3479 Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 20:16:23 -0300 Subject: [PATCH 226/414] docs: fix engine.md Note: Due to a mistake MariaDB support was listed as 10.1 and above, but that should be 10.3 and above. --- ENGINE.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ENGINE.md b/ENGINE.md index e13552ec0809..57f2d36e1066 100644 --- a/ENGINE.md +++ b/ENGINE.md @@ -1,10 +1,10 @@ # Database Engine Support -## v6-beta -| Engine | Minimum supported version | +## v6 +| Engine | Minimum supported version | | :------------: | :------------: | -| Postgre | [9.5 ](https://www.postgresql.org/docs/9.5/ ) | -| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | -| MariaDB | [10.1](https://mariadb.com/kb/en/changes-improvements-in-mariadb-101/) | -| Microsoft SQL | `12.0.2000` | -| SQLite | [3.0](https://www.sqlite.org/version3.html) +| PostgreSQL | [9.5](https://www.postgresql.org/docs/9.5/ ) | +| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | +| MariaDB | [10.3](https://mariadb.com/kb/en/changes-improvements-in-mariadb-103/) | +| Microsoft SQL Server | `12.0.2000` | +| SQLite | [3.0](https://www.sqlite.org/version3.html) | From 22d35953da85869cf742d223cd49498c99027d0c Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 20:25:16 -0300 Subject: [PATCH 227/414] build: stop using package-lock.json for now --- .npmrc | 1 + package-lock.json | 14681 -------------------------------------------- 2 files changed, 1 insertion(+), 14681 deletions(-) create mode 100644 .npmrc delete mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000000..43c97e719a5a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index c5f6d63a7c76..000000000000 --- a/package-lock.json +++ /dev/null @@ -1,14681 +0,0 @@ -{ - "name": "sequelize", - "version": "6.1.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@azure/ms-rest-azure-env": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz", - "integrity": "sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA==", - "dev": true - }, - "@azure/ms-rest-js": { - "version": "1.8.15", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-1.8.15.tgz", - "integrity": "sha512-kIB71V3DcrA4iysBbOsYcxd4WWlOE7OFtCUYNfflPODM0lbIR23A236QeTn5iAeYwcHmMjR/TAKp5KQQh/WqoQ==", - "dev": true, - "requires": { - "@types/tunnel": "0.0.0", - "axios": "^0.19.0", - "form-data": "^2.3.2", - "tough-cookie": "^2.4.3", - "tslib": "^1.9.2", - "tunnel": "0.0.6", - "uuid": "^3.2.1", - "xml2js": "^0.4.19" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "@azure/ms-rest-nodeauth": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-2.0.2.tgz", - "integrity": "sha512-KmNNICOxt3EwViAJI3iu2VH8t8BQg5J2rSAyO4IUYLF9ZwlyYsP419pdvl4NBUhluAP2cgN7dfD2V6E6NOMZlQ==", - "dev": true, - "requires": { - "@azure/ms-rest-azure-env": "^1.1.2", - "@azure/ms-rest-js": "^1.8.7", - "adal-node": "^0.1.28" - } - }, - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", - "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", - "dev": true, - "requires": { - "@babel/types": "^7.9.5", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", - "dev": true - }, - "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" - } - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==", - "dev": true - }, - "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - } - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - } - }, - "@babel/traverse": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", - "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.5", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", - "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@commitlint/cli": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-8.3.5.tgz", - "integrity": "sha512-6+L0vbw55UEdht71pgWOE55SRgb+8OHcEwGDB234VlIBFGK9P2QOBU7MHiYJ5cjdjCQ0rReNrGjOHmJ99jwf0w==", - "dev": true, - "requires": { - "@commitlint/format": "^8.3.4", - "@commitlint/lint": "^8.3.5", - "@commitlint/load": "^8.3.5", - "@commitlint/read": "^8.3.4", - "babel-polyfill": "6.26.0", - "chalk": "2.4.2", - "get-stdin": "7.0.0", - "lodash": "4.17.15", - "meow": "5.0.0", - "resolve-from": "5.0.0", - "resolve-global": "1.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@commitlint/config-angular": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/config-angular/-/config-angular-8.3.4.tgz", - "integrity": "sha512-mFg1Yj2xFDBJJyltGP3RLqZOk89HuNK1ttOcRA9lsTjTVVu4MrNv5sQ1LkW645xn4Vy9zgxlB0CrR4LXEg5QpQ==", - "dev": true, - "requires": { - "@commitlint/config-angular-type-enum": "^8.3.4" - } - }, - "@commitlint/config-angular-type-enum": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/config-angular-type-enum/-/config-angular-type-enum-8.3.4.tgz", - "integrity": "sha512-V8DJ9G3vd8/g55euhwqNPv9gycqbNGfJsswpga7RP0tetzcekkXeE9fXnFBJklqzXrjwUmP8Of5eEsGmbt39IQ==", - "dev": true - }, - "@commitlint/ensure": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-8.3.4.tgz", - "integrity": "sha512-8NW77VxviLhD16O3EUd02lApMFnrHexq10YS4F4NftNoErKbKaJ0YYedktk2boKrtNRf/gQHY/Qf65edPx4ipw==", - "dev": true, - "requires": { - "lodash": "4.17.15" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@commitlint/execute-rule": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-8.3.4.tgz", - "integrity": "sha512-f4HigYjeIBn9f7OuNv5zh2y5vWaAhNFrfeul8CRJDy82l3Y+09lxOTGxfF3uMXKrZq4LmuK6qvvRCZ8mUrVvzQ==", - "dev": true - }, - "@commitlint/format": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-8.3.4.tgz", - "integrity": "sha512-809wlQ/ND6CLZON+w2Rb3YM2TLNDfU2xyyqpZeqzf2reJNpySMSUAeaO/fNDJSOKIsOsR3bI01rGu6hv28k+Nw==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "@commitlint/is-ignored": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-8.3.5.tgz", - "integrity": "sha512-Zo+8a6gJLFDTqyNRx53wQi/XTiz8mncvmWf/4oRG+6WRcBfjSSHY7KPVj5Y6UaLy2EgZ0WQ2Tt6RdTDeQiQplA==", - "dev": true, - "requires": { - "semver": "6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@commitlint/lint": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-8.3.5.tgz", - "integrity": "sha512-02AkI0a6PU6rzqUvuDkSi6rDQ2hUgkq9GpmdJqfai5bDbxx2939mK4ZO+7apbIh4H6Pae7EpYi7ffxuJgm+3hQ==", - "dev": true, - "requires": { - "@commitlint/is-ignored": "^8.3.5", - "@commitlint/parse": "^8.3.4", - "@commitlint/rules": "^8.3.4", - "babel-runtime": "^6.23.0", - "lodash": "4.17.15" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@commitlint/load": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-8.3.5.tgz", - "integrity": "sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw==", - "dev": true, - "requires": { - "@commitlint/execute-rule": "^8.3.4", - "@commitlint/resolve-extends": "^8.3.5", - "babel-runtime": "^6.23.0", - "chalk": "2.4.2", - "cosmiconfig": "^5.2.0", - "lodash": "4.17.15", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@commitlint/message": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-8.3.4.tgz", - "integrity": "sha512-nEj5tknoOKXqBsaQtCtgPcsAaf5VCg3+fWhss4Vmtq40633xLq0irkdDdMEsYIx8rGR0XPBTukqzln9kAWCkcA==", - "dev": true - }, - "@commitlint/parse": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-8.3.4.tgz", - "integrity": "sha512-b3uQvpUQWC20EBfKSfMRnyx5Wc4Cn778bVeVOFErF/cXQK725L1bYFvPnEjQO/GT8yGVzq2wtLaoEqjm1NJ/Bw==", - "dev": true, - "requires": { - "conventional-changelog-angular": "^1.3.3", - "conventional-commits-parser": "^3.0.0", - "lodash": "^4.17.11" - } - }, - "@commitlint/read": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-8.3.4.tgz", - "integrity": "sha512-FKv1kHPrvcAG5j+OSbd41IWexsbLhfIXpxVC/YwQZO+FR0EHmygxQNYs66r+GnhD1EfYJYM4WQIqd5bJRx6OIw==", - "dev": true, - "requires": { - "@commitlint/top-level": "^8.3.4", - "@marionebl/sander": "^0.6.0", - "babel-runtime": "^6.23.0", - "git-raw-commits": "^2.0.0" - } - }, - "@commitlint/resolve-extends": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-8.3.5.tgz", - "integrity": "sha512-nHhFAK29qiXNe6oH6uG5wqBnCR+BQnxlBW/q5fjtxIaQALgfoNLHwLS9exzbIRFqwJckpR6yMCfgMbmbAOtklQ==", - "dev": true, - "requires": { - "import-fresh": "^3.0.0", - "lodash": "4.17.15", - "resolve-from": "^5.0.0", - "resolve-global": "^1.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@commitlint/rules": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-8.3.4.tgz", - "integrity": "sha512-xuC9dlqD5xgAoDFgnbs578cJySvwOSkMLQyZADb1xD5n7BNcUJfP8WjT9W1Aw8K3Wf8+Ym/ysr9FZHXInLeaRg==", - "dev": true, - "requires": { - "@commitlint/ensure": "^8.3.4", - "@commitlint/message": "^8.3.4", - "@commitlint/to-lines": "^8.3.4", - "babel-runtime": "^6.23.0" - } - }, - "@commitlint/to-lines": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-8.3.4.tgz", - "integrity": "sha512-5AvcdwRsMIVq0lrzXTwpbbG5fKRTWcHkhn/hCXJJ9pm1JidsnidS1y0RGkb3O50TEHGewhXwNoavxW9VToscUA==", - "dev": true - }, - "@commitlint/top-level": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-8.3.4.tgz", - "integrity": "sha512-nOaeLBbAqSZNpKgEtO6NAxmui1G8ZvLG+0wb4rvv6mWhPDzK1GNZkCd8FUZPahCoJ1iHDoatw7F8BbJLg4nDjg==", - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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 - }, - "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 - } - } - }, - "@definitelytyped/header-parser": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@definitelytyped/header-parser/-/header-parser-0.0.34.tgz", - "integrity": "sha512-/yTifMAhYKB8SFH3pSlAQmcBzrk7UyqpEz9/vJKaMKdzRpJrxmc1zWMP+hwJtJTVCjAK+Ul4m3i1GZQrTZfymw==", - "dev": true, - "requires": { - "@definitelytyped/typescript-versions": "^0.0.34", - "@types/parsimmon": "^1.10.1", - "parsimmon": "^1.13.0" - } - }, - "@definitelytyped/typescript-versions": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@definitelytyped/typescript-versions/-/typescript-versions-0.0.34.tgz", - "integrity": "sha512-7IqWcbHKYbfY8Lt7AigXDa29cbz3gynzBHMjwMUCeLnex8D682M6OW8uBLouvVHCr+YENL58tQB3dn0Zos8mFQ==", - "dev": true - }, - "@definitelytyped/utils": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@definitelytyped/utils/-/utils-0.0.34.tgz", - "integrity": "sha512-C1mlA9ixRfv7PPmO99hJZ8Ii2roKY+7GoIwBPh5uYSF6WfABoVlzyWX3LsF6fy1MwpZbempC+r81iDm2QeYTsw==", - "dev": true, - "requires": { - "@definitelytyped/typescript-versions": "^0.0.34", - "@types/node": "^12.12.29", - "charm": "^1.0.2", - "fs-extra": "^8.1.0", - "fstream": "^1.0.12", - "npm-registry-client": "^8.6.0", - "tar": "^2.2.2", - "tar-stream": "1.6.2" - }, - "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - } - } - }, - "@istanbuljs/load-nyc-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", - "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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 - }, - "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 - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true - }, - "@js-joda/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-2.0.0.tgz", - "integrity": "sha512-OWm/xa9O9e4ugzNHoRT3IsXZZYfaV6Ia1aRwctOmCQ2GYWMnhKBzMC1WomqCh/oGxEZKNtPy5xv5//VIAOgMqw==", - "dev": true - }, - "@marionebl/sander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@marionebl/sander/-/sander-0.6.1.tgz", - "integrity": "sha1-GViWWHTyS8Ub5Ih1/rUNZC/EH3s=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@octokit/auth-token": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.4.tgz", - "integrity": "sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.0" - } - }, - "@octokit/core": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.4.tgz", - "integrity": "sha512-d9dTsqdePBqOn7aGkyRFe7pQpCXdibSJ5SFnrTr0axevObZrpz3qkWm7t/NjYv5a66z6vhfteriaq4FRz3e0Qg==", - "dev": true, - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.4.12", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.1.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/endpoint": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.10.tgz", - "integrity": "sha512-9+Xef8nT7OKZglfkOMm7IL6VwxXUQyR7DUSU0LH/F7VNqs8vyd7es5pTfz9E7DwUIx7R3pGscxu1EBhYljyu7Q==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.0", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/graphql": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.8.tgz", - "integrity": "sha512-WnCtNXWOrupfPJgXe+vSmprZJUr0VIu14G58PMlkWGj3cH+KLZEfKMmbUQ6C3Wwx6fdhzVW1CD5RTnBdUHxhhA==", - "dev": true, - "requires": { - "@octokit/request": "^5.3.0", - "@octokit/types": "^6.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/openapi-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.0.tgz", - "integrity": "sha512-J4bfM7lf8oZvEAdpS71oTvC1ofKxfEZgU5vKVwzZKi4QPiL82udjpseJwxPid9Pu2FNmyRQOX4iEj6W1iOSnPw==", - "dev": true - }, - "@octokit/plugin-paginate-rest": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.6.2.tgz", - "integrity": "sha512-3Dy7/YZAwdOaRpGQoNHPeT0VU1fYLpIUdPyvR37IyFLgd6XSij4j9V/xN/+eSjF2KKvmfIulEh9LF1tRPjIiDA==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.1" - } - }, - "@octokit/plugin-request-log": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz", - "integrity": "sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==", - "dev": true - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.3.1.tgz", - "integrity": "sha512-gKnD7zjja2Ne2YJniQhcmVFnJ4vkIVjBeMDQaV4fVJkLniZUmm8WZ2GRF7HkueI4kT0B6sfDK02TYwskRMG3dQ==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.3.1" - } - }, - "@octokit/request": { - "version": "5.4.12", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.12.tgz", - "integrity": "sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg==", - "dev": true, - "requires": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.0.0", - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", - "once": "^1.4.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.4.tgz", - "integrity": "sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA==", - "dev": true, - "requires": { - "@octokit/types": "^6.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@octokit/rest": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.11.tgz", - "integrity": "sha512-qOF1/9qsyuyKVGpOdcfE0XJXoKBM3x/j2cAMTGCVeeaR+nZZ60JY1fPTjadelDgKVwHFfghqeX/li+X1YYGENg==", - "dev": true, - "requires": { - "@octokit/core": "^3.2.3", - "@octokit/plugin-paginate-rest": "^2.6.2", - "@octokit/plugin-request-log": "^1.0.2", - "@octokit/plugin-rest-endpoint-methods": "4.3.1" - } - }, - "@octokit/types": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.0.tgz", - "integrity": "sha512-bMWBmg77MQTiRkOVyf50qK3QECWOEy43rLy/6fTWZ4HEwAhNfqzMcjiBDZAowkILwTrFvzE1CpP6gD0MuPHS+A==", - "dev": true, - "requires": { - "@octokit/openapi-types": "^2.0.0", - "@types/node": ">= 8" - } - }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", - "dev": true, - "requires": { - "any-observable": "^0.3.0" - } - }, - "@semantic-release/commit-analyzer": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz", - "integrity": "sha512-5bJma/oB7B4MtwUkZC2Bf7O1MHfi4gWe4mA+MIQ3lsEV0b422Bvl1z5HRpplDnMLHH3EXMoRdEng6Ds5wUqA3A==", - "dev": true, - "requires": { - "conventional-changelog-angular": "^5.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.0.7", - "debug": "^4.0.0", - "import-from": "^3.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.2" - }, - "dependencies": { - "compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "conventional-changelog-angular": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", - "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - } - } - }, - "@semantic-release/error": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", - "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", - "dev": true - }, - "@semantic-release/github": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.2.0.tgz", - "integrity": "sha512-tMRnWiiWb43whRHvbDGXq4DGEbKRi56glDpXDJZit4PIiwDPX7Kx3QzmwRtDOcG+8lcpGjpdPabYZ9NBxoI2mw==", - "dev": true, - "requires": { - "@octokit/rest": "^18.0.0", - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "bottleneck": "^2.18.1", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "fs-extra": "^9.0.0", - "globby": "^11.0.0", - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "issue-parser": "^6.0.0", - "lodash": "^4.17.4", - "mime": "^2.4.3", - "p-filter": "^2.0.0", - "p-retry": "^4.0.0", - "url-join": "^4.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true - } - } - }, - "@semantic-release/npm": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.9.tgz", - "integrity": "sha512-VsmmQF3/n7mDbm6AGL0yPD3QNTGsHdinBtkyyerN1eLgvhdGJ/vEeAvmDMARiuf5Ev9cFeCheF0wLyUZNlAkeA==", - "dev": true, - "requires": { - "@semantic-release/error": "^2.2.0", - "aggregate-error": "^3.0.0", - "execa": "^5.0.0", - "fs-extra": "^9.0.0", - "lodash": "^4.17.15", - "nerf-dart": "^1.0.0", - "normalize-url": "^5.0.0", - "npm": "^6.14.8", - "rc": "^1.2.8", - "read-pkg": "^5.0.0", - "registry-auth-token": "^4.0.0", - "semver": "^7.1.2", - "tempy": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - } - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true - } - } - }, - "@semantic-release/release-notes-generator": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.1.tgz", - "integrity": "sha512-bOoTiH6SiiR0x2uywSNR7uZcRDl22IpZhj+Q5Bn0v+98MFtOMhCxFhbrKQjhbYoZw7vps1mvMRmFkp/g6R9cvQ==", - "dev": true, - "requires": { - "conventional-changelog-angular": "^5.0.0", - "conventional-changelog-writer": "^4.0.0", - "conventional-commits-filter": "^2.0.0", - "conventional-commits-parser": "^3.0.0", - "debug": "^4.0.0", - "get-stream": "^5.0.0", - "import-from": "^3.0.0", - "into-stream": "^5.0.0", - "lodash": "^4.17.4", - "read-pkg-up": "^7.0.0" - }, - "dependencies": { - "compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "conventional-changelog-angular": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", - "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "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" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "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.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } - } - } - }, - "@sinonjs/commons": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz", - "integrity": "sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, - "@sinonjs/samsam": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", - "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/geojson": { - "version": "7946.0.7", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz", - "integrity": "sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==", - "dev": true - }, - "@types/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", - "dev": true - }, - "@types/node": { - "version": "12.12.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.42.tgz", - "integrity": "sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg==" - }, - "@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 - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "@types/parsimmon": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@types/parsimmon/-/parsimmon-1.10.2.tgz", - "integrity": "sha512-WVugAiBoLsmay9IPrLJoMnmLTP0cWPbc4w5c5suTevyhaJW9TWGyPbkFraNUk5YULf8vQ5C/3NBEQcIs6XfTcg==", - "dev": true - }, - "@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "@types/tunnel": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.0.tgz", - "integrity": "sha512-FGDp0iBRiBdPjOgjJmn1NH0KDLN+Z8fRmo+9J7XGBhubq1DPrGrbmG4UTlGzrpbCpesMqD0sWkzi27EYkOMHyg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/validator": { - "version": "10.11.3", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz", - "integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w==", - "dev": true - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abab": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", - "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", - "dev": true, - "optional": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", - "dev": true - }, - "acorn-globals": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", - "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", - "dev": true, - "optional": true, - "requires": { - "acorn": "^2.1.0" - }, - "dependencies": { - "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true - } - } - }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "adal-node": { - "version": "0.1.28", - "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz", - "integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=", - "dev": true, - "requires": { - "@types/node": "^8.0.47", - "async": ">=0.6.0", - "date-utils": "*", - "jws": "3.x.x", - "request": ">= 2.52.0", - "underscore": ">= 1.3.1", - "uuid": "^3.1.0", - "xmldom": ">= 0.1.x", - "xpath.js": "~1.1.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.61", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz", - "integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - } - } - }, - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", - "dev": true - }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", - "dev": true, - "requires": { - "buffer-equal": "^1.0.0" - } - }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "argv-formatter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", - "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "dev": true - }, - "async-hook-jl": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", - "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", - "dev": true, - "requires": { - "stack-chain": "^1.3.7" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true - }, - "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "dev": true, - "requires": { - "follow-redirects": "1.5.10" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "before-after-hook": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", - "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", - "dev": true - }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "dev": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "dev": true, - "requires": { - "inherits": "~2.0.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", - "dev": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true - }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - } - }, - "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", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - } - }, - "cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", - "dev": true, - "requires": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - } - }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "requires": { - "check-error": "^1.0.2" - } - }, - "chai-datetime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/chai-datetime/-/chai-datetime-1.6.0.tgz", - "integrity": "sha512-/KGUjV0c/dyiBsY+WSMrYRNoOejzHFWTBx2GJ5XoAKtLfVP6qGVEkgmPlNpZpiyZZ/x0evkZ0CZ4O63G5J/psw==", - "dev": true, - "requires": { - "chai": ">1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "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.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "charm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-1.0.2.tgz", - "integrity": "sha1-it02cVOm2aWBMxBSxAkJkdqZXjU=", - "dev": true, - "requires": { - "inherits": "^2.0.1" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - } - }, - "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "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 - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, - "requires": { - "colors": "1.0.3" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "cls-hooked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", - "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", - "dev": true, - "requires": { - "async-hook-jl": "^1.7.6", - "emitter-listener": "^1.0.1", - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-logger": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/color-logger/-/color-logger-0.0.6.tgz", - "integrity": "sha1-5WJF7ymCJlcRDHy3WpzXhstp7Rs=", - "dev": true - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "comment-parser": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.7.2.tgz", - "integrity": "sha512-4Rjb1FnxtOcv9qsfuaNuVsmmVn4ooVoBHzYfyKteiXwIU84PClyGA5jASoFMwPV93+FPh9spwueXauxFJZkGAg==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "compare-func": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", - "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", - "dev": true, - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^3.0.0" - } - }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "conventional-changelog-angular": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz", - "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", - "dev": true, - "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" - } - }, - "conventional-changelog-writer": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.18.tgz", - "integrity": "sha512-mAQDCKyB9HsE8Ko5cCM1Jn1AWxXPYV0v8dFPabZRkvsiWUul2YyAqbIaoMKF88Zf2ffnOPSvKhboLf3fnjo5/A==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.6", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "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" - } - }, - "hosted-git-info": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", - "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "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" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", - "dev": true - }, - "meow": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", - "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "normalize-package-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", - "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", - "dev": true, - "requires": { - "hosted-git-info": "^3.0.6", - "resolve": "^1.17.0", - "semver": "^7.3.2", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "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 - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "requires": { - "readable-stream": "3" - } - }, - "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", - "dev": true - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - } - } - }, - "conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", - "dev": true, - "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz", - "integrity": "sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==", - "dev": true, - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^2.0.0", - "through2": "^4.0.0", - "trim-off-newlines": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "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" - } - }, - "hosted-git-info": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", - "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "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" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", - "dev": true - }, - "meow": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", - "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - } - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "normalize-package-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", - "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", - "dev": true, - "requires": { - "hosted-git-info": "^3.0.6", - "resolve": "^1.17.0", - "semver": "^7.3.2", - "validate-npm-package-license": "^3.0.1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "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 - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "requires": { - "readable-stream": "3" - } - }, - "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", - "dev": true - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - } - } - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "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" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } - } - }, - "cross-env": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", - "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.1" - } - }, - "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "optional": true - }, - "cssstyle": { - "version": "0.2.37", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", - "dev": true, - "optional": true, - "requires": { - "cssom": "0.3.x" - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-utils": { - "version": "1.2.21", - "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", - "integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q=", - "dev": true - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - } - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "requires": { - "strip-bom": "^4.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - } - } - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - } - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "del": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", - "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", - "dev": true, - "requires": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } - } - }, - "delay": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", - "integrity": "sha512-Lwaf3zVFDMBop1yDuFZ19F9WyGcZcGacsbdlZtWjQmM50tOcMntm1njF/Nb/Vjij3KaSvCF+sEYGKrrjObu2NA==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, - "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "dev": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", - "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "dottie": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", - "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" - }, - "dts-critic": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.3.4.tgz", - "integrity": "sha512-OjLTrSBCFbi1tDAiOXcP7G20W3HI3eIzkpSpLwvH7oDFZYdqFCMe9lsNhMZFXqsNcSTpRg3+PBS4fF27+h1qew==", - "dev": true, - "requires": { - "@definitelytyped/header-parser": "^0.0.64", - "command-exists": "^1.2.8", - "rimraf": "^3.0.2", - "semver": "^6.2.0", - "tmp": "^0.2.1", - "yargs": "^15.3.1" - }, - "dependencies": { - "@definitelytyped/header-parser": { - "version": "0.0.64", - "resolved": "https://registry.npmjs.org/@definitelytyped/header-parser/-/header-parser-0.0.64.tgz", - "integrity": "sha512-vwh6ojw0PYptEiAogAdKNrwYy2Dv0zZj6M5bjKAoF7/xOmY5B/QluJnNmgF7Q+fxLG2vVxYDbkc26r04xYIZQA==", - "dev": true, - "requires": { - "@definitelytyped/typescript-versions": "^0.0.64", - "@types/parsimmon": "^1.10.1", - "parsimmon": "^1.13.0" - } - }, - "@definitelytyped/typescript-versions": { - "version": "0.0.64", - "resolved": "https://registry.npmjs.org/@definitelytyped/typescript-versions/-/typescript-versions-0.0.64.tgz", - "integrity": "sha512-h+RvQPzvC/yVtZ/FqttXoIac/X1htXrmuNbvmQP+RiVonGunKq7S8ona5tm7ckiheur6VvtKQGe+zQIDF3sErQ==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - } - } - }, - "dtslint": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-3.6.4.tgz", - "integrity": "sha512-D9qwC0N945ge+CENZ1Dtm6I72fc8dAgQpyAde1XoiChR+mk8dC9uzToJNORe7SI0P3dSSULofwjsg7rzQ/iplw==", - "dev": true, - "requires": { - "@definitelytyped/header-parser": "0.0.34", - "@definitelytyped/typescript-versions": "0.0.34", - "@definitelytyped/utils": "0.0.34", - "dts-critic": "^3.2.3", - "fs-extra": "^6.0.1", - "json-stable-stringify": "^1.0.1", - "strip-json-comments": "^2.0.1", - "tslint": "5.14.0", - "typescript": "^4.0.0-dev.20200523", - "yargs": "^15.1.0" - }, - "dependencies": { - "typescript": { - "version": "4.0.0-dev.20200523", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.0-dev.20200523.tgz", - "integrity": "sha512-BMe4bsXbdGABN84olraexKpJ7bnJlSPhcfCUDLEEeBXtD554FG75Aad5TWOgrmwgXYvlcpLrh9oYA18AO33JmA==", - "dev": true - } - } - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "elegant-spinner": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-2.0.0.tgz", - "integrity": "sha512-5YRYHhvhYzV/FC4AiMdeSIg3jAYGq9xFvbhZMpPlJoBsfYgrw2DSCYeXfat6tYBu45PWiyRr3+flaCPPmviPaA==", - "dev": true - }, - "emitter-listener": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", - "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", - "dev": true, - "requires": { - "shimmer": "^1.2.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", - "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", - "dev": true, - "requires": { - "ansi-colors": "^3.2.1" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "env-ci": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.2.tgz", - "integrity": "sha512-Xc41mKvjouTXD3Oy9AqySz1IeyvJvHZ20Twf5ZLYbNpPPIuCnL/qHCmNlD01LoNy0JTunw9HPYVptD19Ac7Mbw==", - "dev": true, - "requires": { - "execa": "^4.0.0", - "java-properties": "^1.0.0" - }, - "dependencies": { - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - } - } - }, - "env-cmd": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", - "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", - "dev": true, - "requires": { - "commander": "^4.0.0", - "cross-spawn": "^7.0.0" - }, - "dependencies": { - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - } - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "dev": true, - "optional": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "esdoc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esdoc/-/esdoc-1.1.0.tgz", - "integrity": "sha512-vsUcp52XJkOWg9m1vDYplGZN2iDzvmjDL5M/Mp8qkoDG3p2s0yIQCIjKR5wfPBaM3eV14a6zhQNYiNTCVzPnxA==", - "dev": true, - "requires": { - "babel-generator": "6.26.1", - "babel-traverse": "6.26.0", - "babylon": "6.18.0", - "cheerio": "1.0.0-rc.2", - "color-logger": "0.0.6", - "escape-html": "1.0.3", - "fs-extra": "5.0.0", - "ice-cap": "0.0.4", - "marked": "0.3.19", - "minimist": "1.2.0", - "taffydb": "2.7.3" - }, - "dependencies": { - "cheerio": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", - "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - } - }, - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "esdoc-accessor-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-accessor-plugin/-/esdoc-accessor-plugin-1.0.0.tgz", - "integrity": "sha1-eRukhy5sQDUVznSbE0jW8Ck62es=", - "dev": true - }, - "esdoc-brand-plugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esdoc-brand-plugin/-/esdoc-brand-plugin-1.0.1.tgz", - "integrity": "sha512-Yv9j3M7qk5PSLmSeD6MbPsfIsEf8K43EdH8qZpE/GZwnJCRVmDPrZJ1cLDj/fPu6P35YqgcEaJK4E2NL/CKA7g==", - "dev": true, - "requires": { - "cheerio": "0.22.0" - }, - "dependencies": { - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - } - } - } - }, - "esdoc-coverage-plugin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esdoc-coverage-plugin/-/esdoc-coverage-plugin-1.1.0.tgz", - "integrity": "sha1-OGmGnNf4eJH5cmJXh2laKZrs5Fw=", - "dev": true - }, - "esdoc-ecmascript-proposal-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-ecmascript-proposal-plugin/-/esdoc-ecmascript-proposal-plugin-1.0.0.tgz", - "integrity": "sha1-OQ3FZWuoooMOOdujVw15E43y/9k=", - "dev": true - }, - "esdoc-external-ecmascript-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-external-ecmascript-plugin/-/esdoc-external-ecmascript-plugin-1.0.0.tgz", - "integrity": "sha1-ePVl1KDFGFrGMVJhTc4f4ahmiNs=", - "dev": true, - "requires": { - "fs-extra": "1.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - } - } - }, - "esdoc-inject-style-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-inject-style-plugin/-/esdoc-inject-style-plugin-1.0.0.tgz", - "integrity": "sha1-oTWXNou5+4nDZeBmSVyvl6Tey7E=", - "dev": true, - "requires": { - "cheerio": "0.22.0", - "fs-extra": "1.0.0" - }, - "dependencies": { - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - } - }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - } - } - }, - "esdoc-integrate-manual-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-integrate-manual-plugin/-/esdoc-integrate-manual-plugin-1.0.0.tgz", - "integrity": "sha1-GFSmqhwIEDXXyMUeO91PtlqkcRw=", - "dev": true - }, - "esdoc-integrate-test-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-integrate-test-plugin/-/esdoc-integrate-test-plugin-1.0.0.tgz", - "integrity": "sha1-4tDQAJD38MNeXS8sAzMnp55T5Ak=", - "dev": true - }, - "esdoc-lint-plugin": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/esdoc-lint-plugin/-/esdoc-lint-plugin-1.0.2.tgz", - "integrity": "sha512-24AYqD2WbZI9We02I7/6dzAa7yUliRTFUaJCZAcYJMQicJT5gUrNFVaI8XmWEN/mhF3szIn1uZBNWeLul4CmNw==", - "dev": true - }, - "esdoc-publish-html-plugin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/esdoc-publish-html-plugin/-/esdoc-publish-html-plugin-1.1.2.tgz", - "integrity": "sha512-hG1fZmTcEp3P/Hv/qKiMdG1qSp8MjnVZMMkxL5P5ry7I2sX0HQ4P9lt2lms+90Lt0r340HHhSuVx107UL7dphg==", - "dev": true, - "requires": { - "babel-generator": "6.11.4", - "cheerio": "0.22.0", - "escape-html": "1.0.3", - "fs-extra": "1.0.0", - "ice-cap": "0.0.4", - "marked": "0.3.19", - "taffydb": "2.7.2" - }, - "dependencies": { - "babel-generator": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.11.4.tgz", - "integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=", - "dev": true, - "requires": { - "babel-messages": "^6.8.0", - "babel-runtime": "^6.9.0", - "babel-types": "^6.10.2", - "detect-indent": "^3.0.1", - "lodash": "^4.2.0", - "source-map": "^0.5.0" - } - }, - "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - } - }, - "detect-indent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz", - "integrity": "sha1-ncXl3bzu+DJXZLlFGwK8bVQIT3U=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "minimist": "^1.1.0", - "repeating": "^1.1.0" - } - }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true - }, - "repeating": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", - "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "taffydb": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.7.2.tgz", - "integrity": "sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg=", - "dev": true - } - } - }, - "esdoc-standard-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-standard-plugin/-/esdoc-standard-plugin-1.0.0.tgz", - "integrity": "sha1-ZhIBysfvhokkkCRG/awVJyU8XU0=", - "dev": true, - "requires": { - "esdoc-accessor-plugin": "^1.0.0", - "esdoc-brand-plugin": "^1.0.0", - "esdoc-coverage-plugin": "^1.0.0", - "esdoc-external-ecmascript-plugin": "^1.0.0", - "esdoc-integrate-manual-plugin": "^1.0.0", - "esdoc-integrate-test-plugin": "^1.0.0", - "esdoc-lint-plugin": "^1.0.0", - "esdoc-publish-html-plugin": "^1.0.0", - "esdoc-type-inference-plugin": "^1.0.0", - "esdoc-undocumented-identifier-plugin": "^1.0.0", - "esdoc-unexported-identifier-plugin": "^1.0.0" - } - }, - "esdoc-type-inference-plugin": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/esdoc-type-inference-plugin/-/esdoc-type-inference-plugin-1.0.2.tgz", - "integrity": "sha512-tMIcEHNe1uhUGA7lT1UTWc9hs2dzthnTgmqXpmeUhurk7fL2tinvoH+IVvG/sLROzwOGZQS9zW/F9KWnpMzLIQ==", - "dev": true - }, - "esdoc-undocumented-identifier-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-undocumented-identifier-plugin/-/esdoc-undocumented-identifier-plugin-1.0.0.tgz", - "integrity": "sha1-guBdNxwy0ShxFA8dXIHsmf2cwsg=", - "dev": true - }, - "esdoc-unexported-identifier-plugin": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esdoc-unexported-identifier-plugin/-/esdoc-unexported-identifier-plugin-1.0.0.tgz", - "integrity": "sha1-H5h0xqfCvr+a05fDzrdcnGnaurE=", - "dev": true - }, - "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-plugin-jsdoc": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-20.4.0.tgz", - "integrity": "sha512-c/fnEpwWLFeQn+A7pb1qLOdyhovpqGCWCeUv1wtzFNL5G+xedl9wHUnXtp3b1sGHolVimi9DxKVTuhK/snXoOw==", - "dev": true, - "requires": { - "comment-parser": "^0.7.2", - "debug": "^4.1.1", - "jsdoctypeparser": "^6.1.0", - "lodash": "^4.17.15", - "object.entries-ponyfill": "^1.0.1", - "regextras": "^0.7.0", - "semver": "^6.3.0", - "spdx-expression-parse": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-plugin-mocha": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz", - "integrity": "sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg==", - "dev": true, - "requires": { - "eslint-utils": "^2.0.0", - "ramda": "^0.27.0" - }, - "dependencies": { - "eslint-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", - "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - } - } - }, - "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", - "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "find-versions": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", - "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", - "dev": true, - "requires": { - "semver-regex": "^2.0.0" - } - }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - } - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dev": true, - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fromentries": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", - "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", - "dev": true - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-jetpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/fs-jetpack/-/fs-jetpack-2.4.0.tgz", - "integrity": "sha512-S/o9Dd7K9A7gicVU32eT8G0kHcmSu0rCVdP79P0MWInKFb8XpTc8Syhoo66k9no+HDshtlh4pUJTws8X+8fdFQ==", - "dev": true, - "requires": { - "minimatch": "^3.0.2", - "rimraf": "^2.6.3" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "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 - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "git-log-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", - "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", - "dev": true, - "requires": { - "argv-formatter": "~1.0.0", - "spawn-error-forwarder": "~1.0.0", - "split2": "~1.0.0", - "stream-combiner2": "~1.1.1", - "through2": "~2.0.0", - "traverse": "~0.6.6" - }, - "dependencies": { - "split2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", - "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", - "dev": true, - "requires": { - "through2": "~2.0.0" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "git-raw-commits": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.7.tgz", - "integrity": "sha512-SkwrTqrDxw8y0G1uGJ9Zw13F7qu3LF8V4BifyDeiJCxSnjRGZD9SaoMiMqUvvXMXh6S3sOQ1DsBN7L2fMUZW/g==", - "dev": true, - "requires": { - "dargs": "^7.0.0", - "lodash.template": "^4.0.2", - "meow": "^7.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0" - }, - "dependencies": { - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true - }, - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - }, - "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" - } - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "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" - } - }, - "map-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", - "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", - "dev": true - }, - "meow": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", - "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "arrify": "^2.0.1", - "camelcase": "^6.0.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - } - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "dependencies": { - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - } - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "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 - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", - "dev": true - }, - "type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } - } - } - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "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" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, - "requires": { - "ini": "^1.3.4" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true - }, - "hasha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", - "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - } - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hook-std": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", - "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "husky": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", - "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^6.0.0", - "find-versions": "^3.2.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^4.2.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "ice-cap": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/ice-cap/-/ice-cap-0.0.4.tgz", - "integrity": "sha1-im0xq0ysjUtW3k+pRt8zUlYbbhg=", - "dev": true, - "requires": { - "cheerio": "0.20.0", - "color-logger": "0.0.3" - }, - "dependencies": { - "cheerio": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.20.0.tgz", - "integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "~3.8.1", - "jsdom": "^7.0.2", - "lodash": "^4.1.0" - } - }, - "color-logger": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/color-logger/-/color-logger-0.0.3.tgz", - "integrity": "sha1-2bIt0dlz4Waxi/MT+fSBu6TfIBg=", - "dev": true - }, - "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", - "dev": true, - "requires": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" - }, - "dependencies": { - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", - "dev": true - } - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "into-stream": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", - "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", - "dev": true, - "requires": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - }, - "dependencies": { - "p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", - "dev": true - } - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "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-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "requires": { - "is-unc-path": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", - "dev": true, - "requires": { - "text-extensions": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "issue-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", - "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", - "dev": true, - "requires": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - } - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "requires": { - "append-transform": "^2.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", - "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" - }, - "dependencies": { - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "java-properties": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", - "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", - "dev": true - }, - "js-combinatorics": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/js-combinatorics/-/js-combinatorics-0.5.5.tgz", - "integrity": "sha512-WglFY9EQvwndNhuJLxxyjnC16649lfZly/G3M3zgQMwcWlJDJ0Jn9niPWeYjnLXwWOEycYVxR2Tk98WLeFkrcw==", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbi": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.2.tgz", - "integrity": "sha512-5nDXo1X9QVaXK/Cpb5VECV9ss1QPbjUuk1qSruHB1PK/g39Sd414K4nci99ElFDZv0vzxDEnKn3o49/Tn9Yagw==", - "dev": true - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdoctypeparser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-6.1.0.tgz", - "integrity": "sha512-UCQBZ3xCUBv/PLfwKAJhp6jmGOSLFNKzrotXGNgbKhWvz27wPsCsVeP7gIcHPElQw2agBmynAitXqhxR58XAmA==", - "dev": true - }, - "jsdom": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz", - "integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=", - "dev": true, - "optional": true, - "requires": { - "abab": "^1.0.0", - "acorn": "^2.4.0", - "acorn-globals": "^1.0.4", - "cssom": ">= 0.3.0 < 0.4.0", - "cssstyle": ">= 0.2.29 < 0.3.0", - "escodegen": "^1.6.1", - "nwmatcher": ">= 1.3.7 < 2.0.0", - "parse5": "^1.5.1", - "request": "^2.55.0", - "sax": "^1.1.4", - "symbol-tree": ">= 3.1.0 < 4.0.0", - "tough-cookie": "^2.2.0", - "webidl-conversions": "^2.0.0", - "whatwg-url-compat": "~0.6.5", - "xml-name-validator": ">= 2.0.1 < 3.0.0" - }, - "dependencies": { - "acorn": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", - "dev": true, - "optional": true - }, - "parse5": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", - "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", - "dev": true, - "optional": true - } - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsonc-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", - "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", - "dev": true - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dev": true, - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dev": true, - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - } - }, - "lcov-result-merger": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-3.1.0.tgz", - "integrity": "sha512-vGXaMNGZRr4cYvW+xMVg+rg7qd5DX9SbGXl+0S3k85+gRZVK4K7UvxPWzKb/qiMwe+4bx3EOrW2o4mbdb1WnsA==", - "dev": true, - "requires": { - "through2": "^2.0.3", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.2" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "requires": { - "flush-write-stream": "^1.0.2" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "lint-staged": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.6.tgz", - "integrity": "sha512-2oEBWyPZHkdyjKcIv2U6ay80Q52ZMlZZrUnfsV0WTVcgzPlt3o2t5UFy2v8ETUTsIDZ0xSJVnffWCgD3LF6xTQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "cli-truncate": "2.1.0", - "commander": "^5.1.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "execa": "^4.0.1", - "listr2": "^2.0.2", - "log-symbols": "^4.0.0", - "micromatch": "^4.0.2", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "execa": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", - "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "listr2": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.0.4.tgz", - "integrity": "sha512-oJaAcplPsa72rKW0eg4P4LbEJjhH+UO2I8uqR/I2wzHrVg16ohSfUy0SlcHS21zfYXxtsUpL8YXGHjyfWMR0cg==", - "dev": true, - "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", - "chalk": "^4.0.0", - "cli-cursor": "^3.1.0", - "cli-truncate": "^2.1.0", - "elegant-spinner": "^2.0.0", - "enquirer": "^2.3.5", - "figures": "^3.2.0", - "indent-string": "^4.0.0", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "pad": "^3.2.0", - "rxjs": "^6.5.5", - "through": "^2.3.8", - "uuid": "^7.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true - } - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=", - "dev": true - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=", - "dev": true - }, - "lodash.capitalize": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", - "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", - "dev": true - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true - }, - "lodash.differencewith": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz", - "integrity": "sha1-uvr7yRi1UVTheRdqALsK76rIVLc=", - "dev": true - }, - "lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", - "dev": true - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=", - "dev": true - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=", - "dev": true - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", - "dev": true - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", - "dev": true - }, - "lodash.uniqby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", - "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", - "dev": true - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", - "dev": true - }, - "mariadb": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-2.3.1.tgz", - "integrity": "sha512-suv+ygoiS+tQSKmxgzJsGV9R+USN8g6Ql+GuMo9k7alD6FxOT/lwebLHy63/7yPZfVtlyAitK1tPd7ZoFhN/Sg==", - "dev": true, - "requires": { - "@types/geojson": "^7946.0.7", - "@types/node": ">=8.0.0", - "denque": "^1.4.1", - "iconv-lite": "^0.5.1", - "long": "^4.0.0", - "moment-timezone": "^0.5.27" - }, - "dependencies": { - "iconv-lite": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", - "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "dependencies": { - "entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", - "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", - "dev": true - } - } - }, - "markdownlint": { - "version": "0.20.3", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.3.tgz", - "integrity": "sha512-J93s59tGvSFvAPWVUtEgxqPI0CHayTx1Z8poj1/4UJAquHGPIruWRMurkRldiNbgBiaQ4OOt15rHZbFfU6u05A==", - "dev": true, - "requires": { - "markdown-it": "10.0.0" - } - }, - "markdownlint-cli": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.1.tgz", - "integrity": "sha512-UARWuPILksAcVLTosUv1F1tLognNYQ/qjLRIgWwQAYqdl3QQrTPurU/X9Z2jrdAJYlOim868QsufxjYJpH0K7Q==", - "dev": true, - "requires": { - "commander": "~2.9.0", - "deep-extend": "~0.5.1", - "get-stdin": "~5.0.1", - "glob": "~7.1.2", - "ignore": "~5.1.4", - "js-yaml": "~3.13.1", - "jsonc-parser": "~2.2.0", - "lodash.differencewith": "~4.5.0", - "lodash.flatten": "~4.4.0", - "markdownlint": "~0.20.3", - "markdownlint-rule-helpers": "~0.10.0", - "minimatch": "~3.0.4", - "minimist": "~1.2.5", - "rc": "~1.2.7" - }, - "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", - "dev": true - }, - "ignore": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz", - "integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA==", - "dev": true - } - } - }, - "markdownlint-rule-helpers": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.10.0.tgz", - "integrity": "sha512-0e8VUTjNdQwS7hTyNan9oOLsy4a7KEsXo3fxKMDRFRk6Jn+pLB3iKZ3mj/m6ECrlOUCxPYYmgOmmyk3bSdbIvw==", - "dev": true - }, - "marked": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.0.tgz", - "integrity": "sha512-EkE7RW6KcXfMHy2PA7Jg0YJE1l8UPEZE8k45tylzmZM30/r1M1MUXWQfJlrSbsTeh7m/XTwHbWUENvAJZpp1YA==", - "dev": true - }, - "marked-terminal": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.0.tgz", - "integrity": "sha512-5KllfAOW02WS6hLRQ7cNvGOxvKW1BKuXELH4EtbWfyWgxQhROoMxEvuQ/3fTgkNjledR0J48F4HbapvYp1zWkQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.1", - "cardinal": "^2.1.1", - "chalk": "^4.0.0", - "cli-table": "^0.3.1", - "node-emoji": "^1.10.0", - "supports-hyperlinks": "^2.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" - } - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "dependencies": { - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "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" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "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" - } - }, - "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" - } - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "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 - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "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.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true - }, - "moment": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz", - "integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==" - }, - "moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "requires": { - "moment": ">= 2.9.0" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "mysql2": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.1.0.tgz", - "integrity": "sha512-9kGVyi930rG2KaHrz3sHwtc6K+GY9d8wWk1XRSYxQiunvGcn4DwuZxOwmK11ftuhhwrYDwGx9Ta4VBwznJn36A==", - "dev": true, - "requires": { - "cardinal": "^2.1.1", - "denque": "^1.4.1", - "generate-function": "^2.3.1", - "iconv-lite": "^0.5.0", - "long": "^4.0.0", - "lru-cache": "^5.1.1", - "named-placeholders": "^1.1.2", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.1" - }, - "dependencies": { - "iconv-lite": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", - "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "named-placeholders": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", - "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", - "dev": true, - "requires": { - "lru-cache": "^4.1.3" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "dev": true - }, - "native-duplexpair": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", - "integrity": "sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "needle": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", - "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", - "dev": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nerf-dart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", - "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "nise": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz", - "integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - } - }, - "node-emoji": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", - "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", - "dev": true, - "requires": { - "lodash.toarray": "^4.4.0" - } - }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true - }, - "node-pre-gyp": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", - "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", - "dev": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - } - } - }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "requires": { - "process-on-spawn": "^1.0.0" - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "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" - } - }, - "normalize-url": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-5.3.0.tgz", - "integrity": "sha512-9/nOVLYYe/dO/eJeQUNaGUF4m4Z5E7cb9oNTKabH+bNf19mqj60txTcveQxL0GlcWLXCxkOu2/LwL8oW0idIDA==", - "dev": true - }, - "now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "requires": { - "once": "^1.3.2" - } - }, - "npm": { - "version": "6.14.9", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.9.tgz", - "integrity": "sha512-yHi1+i9LyAZF1gAmgyYtVk+HdABlLy94PMIDoK1TRKWvmFQAt5z3bodqVwKvzY0s6dLqQPVsRLiwhJfNtiHeCg==", - "dev": true, - "requires": { - "JSONStream": "^1.3.5", - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "aproba": "^2.0.0", - "archy": "~1.0.0", - "bin-links": "^1.1.8", - "bluebird": "^3.5.5", - "byte-size": "^5.0.1", - "cacache": "^12.0.3", - "call-limit": "^1.1.1", - "chownr": "^1.1.4", - "ci-info": "^2.0.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.5.1", - "cmd-shim": "^3.0.3", - "columnify": "~1.5.4", - "config-chain": "^1.1.12", - "debuglog": "*", - "detect-indent": "~5.0.0", - "detect-newline": "^2.1.0", - "dezalgo": "~1.0.3", - "editor": "~1.0.0", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "fs-vacuum": "~1.2.10", - "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.3.1", - "glob": "^7.1.6", - "graceful-fs": "^4.2.4", - "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.8", - "iferr": "^1.0.2", - "imurmurhash": "*", - "infer-owner": "^1.0.4", - "inflight": "~1.0.6", - "inherits": "^2.0.4", - "ini": "^1.3.5", - "init-package-json": "^1.10.3", - "is-cidr": "^3.0.0", - "json-parse-better-errors": "^1.0.2", - "lazy-property": "~1.0.0", - "libcipm": "^4.0.8", - "libnpm": "^3.0.1", - "libnpmaccess": "^3.0.2", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "libnpx": "^10.2.4", - "lock-verify": "^2.1.0", - "lockfile": "^1.0.4", - "lodash._baseindexof": "*", - "lodash._baseuniq": "~4.6.0", - "lodash._bindcallback": "*", - "lodash._cacheindexof": "*", - "lodash._createcache": "*", - "lodash._getnative": "*", - "lodash.clonedeep": "~4.5.0", - "lodash.restparam": "*", - "lodash.union": "~4.6.0", - "lodash.uniq": "~4.5.0", - "lodash.without": "~4.4.0", - "lru-cache": "^5.1.1", - "meant": "^1.0.2", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.5", - "move-concurrently": "^1.0.1", - "node-gyp": "^5.1.0", - "nopt": "^4.0.3", - "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.3", - "npm-cache-filename": "~1.0.2", - "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.5", - "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.8", - "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.4", - "npm-registry-fetch": "^4.0.7", - "npm-user-validate": "^1.0.1", - "npmlog": "~4.1.2", - "once": "~1.4.0", - "opener": "^1.5.1", - "osenv": "^0.1.5", - "pacote": "^9.5.12", - "path-is-inside": "~1.0.2", - "promise-inflight": "~1.0.1", - "qrcode-terminal": "^0.12.0", - "query-string": "^6.8.2", - "qw": "~1.0.1", - "read": "~1.0.7", - "read-cmd-shim": "^1.0.5", - "read-installed": "~4.0.3", - "read-package-json": "^2.1.1", - "read-package-tree": "^5.3.1", - "readable-stream": "^3.6.0", - "readdir-scoped-modules": "^1.1.0", - "request": "^2.88.0", - "retry": "^0.12.0", - "rimraf": "^2.7.1", - "safe-buffer": "^5.1.2", - "semver": "^5.7.1", - "sha": "^3.0.0", - "slide": "~1.1.6", - "sorted-object": "~2.0.1", - "sorted-union-stream": "~2.1.3", - "ssri": "^6.0.1", - "stringify-package": "^1.0.1", - "tar": "^4.4.13", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "uid-number": "0.0.6", - "umask": "~1.1.0", - "unique-filename": "^1.1.1", - "unpipe": "~1.0.0", - "update-notifier": "^2.5.0", - "uuid": "^3.3.3", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^1.3.1", - "worker-farm": "^1.7.0", - "write-file-atomic": "^2.4.3" - }, - "dependencies": { - "JSONStream": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "bundled": true, - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "agentkeepalive": { - "version": "3.5.2", - "bundled": true, - "dev": true, - "requires": { - "humanize-ms": "^1.2.1" - } - }, - "ansi-align": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^2.0.0" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "bundled": true, - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "ansicolors": { - "version": "0.3.2", - "bundled": true, - "dev": true - }, - "ansistyles": { - "version": "0.1.3", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "archy": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "asap": { - "version": "2.0.6", - "bundled": true, - "dev": true - }, - "asn1": { - "version": "0.2.4", - "bundled": true, - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "bundled": true, - "dev": true - }, - "aws4": { - "version": "1.8.0", - "bundled": true, - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bin-links": { - "version": "1.1.8", - "bundled": true, - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "cmd-shim": "^3.0.0", - "gentle-fs": "^2.3.0", - "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0", - "write-file-atomic": "^2.3.0" - } - }, - "bluebird": { - "version": "3.5.5", - "bundled": true, - "dev": true - }, - "boxen": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-from": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "builtins": { - "version": "1.0.3", - "bundled": true, - "dev": true - }, - "byline": { - "version": "5.0.0", - "bundled": true, - "dev": true - }, - "byte-size": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "cacache": { - "version": "12.0.3", - "bundled": true, - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "call-limit": { - "version": "1.1.1", - "bundled": true, - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, - "capture-stack-trace": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true - }, - "chalk": { - "version": "2.4.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "cidr-regex": { - "version": "2.0.10", - "bundled": true, - "dev": true, - "requires": { - "ip-regex": "^2.1.0" - } - }, - "cli-boxes": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "cli-columns": { - "version": "3.1.2", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" - } - }, - "cli-table3": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - } - }, - "cliui": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "bundled": true, - "dev": true - }, - "cmd-shim": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "color-convert": { - "version": "1.9.1", - "bundled": true, - "dev": true, - "requires": { - "color-name": "^1.1.1" - } - }, - "color-name": { - "version": "1.1.3", - "bundled": true, - "dev": true - }, - "colors": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true - }, - "columnify": { - "version": "1.5.4", - "bundled": true, - "dev": true, - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - } - }, - "combined-stream": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "bundled": true, - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "config-chain": { - "version": "1.1.12", - "bundled": true, - "dev": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "configstore": { - "version": "3.1.5", - "bundled": true, - "dev": true, - "requires": { - "dot-prop": "^4.2.1", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "iferr": { - "version": "0.1.5", - "bundled": true, - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "create-error-class": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "bundled": true, - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "bundled": true, - "dev": true - } - } - }, - "crypto-random-string": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "cyclist": { - "version": "0.2.2", - "bundled": true, - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true - } - } - }, - "debuglog": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "decamelize": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "bundled": true, - "dev": true - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true - }, - "defaults": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "detect-indent": { - "version": "5.0.0", - "bundled": true, - "dev": true - }, - "detect-newline": { - "version": "2.1.0", - "bundled": true, - "dev": true - }, - "dezalgo": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "dot-prop": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "dotenv": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "duplexer3": { - "version": "0.1.4", - "bundled": true, - "dev": true - }, - "duplexify": { - "version": "3.6.0", - "bundled": true, - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "editor": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "bundled": true, - "dev": true - }, - "encoding": { - "version": "0.1.12", - "bundled": true, - "dev": true, - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "end-of-stream": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "env-paths": { - "version": "2.2.0", - "bundled": true, - "dev": true - }, - "err-code": { - "version": "1.1.2", - "bundled": true, - "dev": true - }, - "errno": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.12.0", - "bundled": true, - "dev": true, - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-promise": { - "version": "4.2.8", - "bundled": true, - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "bundled": true, - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "bundled": true, - "dev": true - }, - "execa": { - "version": "0.7.0", - "bundled": true, - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "bundled": true, - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "bundled": true, - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "bundled": true, - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "figgy-pudding": { - "version": "3.5.1", - "bundled": true, - "dev": true - }, - "find-npm-prefix": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "flush-write-stream": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true - }, - "form-data": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, - "from2": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^2.6.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "fs-vacuum": { - "version": "1.2.10", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - }, - "dependencies": { - "iferr": { - "version": "0.1.5", - "bundled": true, - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "bundled": true, - "dev": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "genfun": { - "version": "5.0.0", - "bundled": true, - "dev": true - }, - "gentle-fs": { - "version": "2.3.1", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.1.2", - "chownr": "^1.1.2", - "cmd-shim": "^3.0.3", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "infer-owner": "^1.0.4", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "iferr": { - "version": "0.1.5", - "bundled": true, - "dev": true - } - } - }, - "get-caller-file": { - "version": "2.0.5", - "bundled": true, - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "requires": { - "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-dirs": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "requires": { - "ini": "^1.3.4" - } - }, - "got": { - "version": "6.7.1", - "bundled": true, - "dev": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "bundled": true, - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.2.4", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "bundled": true, - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "bundled": true, - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "bundled": true, - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "bundled": true, - "dev": true - } - } - }, - "has": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "hosted-git-info": { - "version": "2.8.8", - "bundled": true, - "dev": true - }, - "http-cache-semantics": { - "version": "3.8.1", - "bundled": true, - "dev": true - }, - "http-proxy-agent": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - } - }, - "http-signature": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "humanize-ms": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.23", - "bundled": true, - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "iferr": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-lazy": { - "version": "2.1.0", - "bundled": true, - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "bundled": true, - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "bundled": true, - "dev": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true - }, - "init-package-json": { - "version": "1.10.3", - "bundled": true, - "dev": true, - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - } - }, - "ip": { - "version": "1.1.5", - "bundled": true, - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "bundled": true, - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "bundled": true, - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "requires": { - "ci-info": "^1.5.0" - }, - "dependencies": { - "ci-info": { - "version": "1.6.0", - "bundled": true, - "dev": true - } - } - }, - "is-cidr": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "cidr-regex": "^2.0.10" - } - }, - "is-date-object": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "bundled": true, - "dev": true, - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, - "is-npm": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "is-path-inside": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-redirect": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "bundled": true, - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-retry-allowed": { - "version": "1.2.0", - "bundled": true, - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isexe": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "bundled": true, - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "latest-version": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "package-json": "^4.0.0" - } - }, - "lazy-property": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "libcipm": { - "version": "4.0.8", - "bundled": true, - "dev": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "ini": "^1.3.5", - "lock-verify": "^2.1.0", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^9.1.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" - } - }, - "libnpm": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.3", - "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.2", - "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmpublish": "^1.1.2", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "lock-verify": "^2.0.2", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", - "npmlog": "^4.1.2", - "pacote": "^9.5.3", - "read-package-json": "^2.0.13", - "stringify-package": "^1.0.0" - } - }, - "libnpmaccess": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmconfig": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "find-up": "^3.0.0", - "ini": "^1.3.5" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "bundled": true, - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "bundled": true, - "dev": true - } - } - }, - "libnpmhook": { - "version": "5.0.3", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmorg": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmpublish": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" - } - }, - "libnpmsearch": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmteam": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpx": { - "version": "10.2.4", - "bundled": true, - "dev": true, - "requires": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^14.2.3" - } - }, - "lock-verify": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "npm-package-arg": "^6.1.0", - "semver": "^5.4.1" - } - }, - "lockfile": { - "version": "1.0.4", - "bundled": true, - "dev": true, - "requires": { - "signal-exit": "^3.0.2" - } - }, - "lodash._baseindexof": { - "version": "3.1.0", - "bundled": true, - "dev": true - }, - "lodash._baseuniq": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "lodash._bindcallback": { - "version": "3.0.1", - "bundled": true, - "dev": true - }, - "lodash._cacheindexof": { - "version": "3.0.2", - "bundled": true, - "dev": true - }, - "lodash._createcache": { - "version": "3.1.2", - "bundled": true, - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0" - } - }, - "lodash._createset": { - "version": "4.0.3", - "bundled": true, - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "bundled": true, - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "bundled": true, - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "bundled": true, - "dev": true - }, - "lodash.restparam": { - "version": "3.6.1", - "bundled": true, - "dev": true - }, - "lodash.union": { - "version": "4.6.0", - "bundled": true, - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "bundled": true, - "dev": true - }, - "lodash.without": { - "version": "4.4.0", - "bundled": true, - "dev": true - }, - "lowercase-keys": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "bundled": true, - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "make-fetch-happen": { - "version": "5.0.2", - "bundled": true, - "dev": true, - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - }, - "meant": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "mime-db": { - "version": "1.35.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.19", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "~1.35.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "requires": { - "minipass": "^2.9.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "mississippi": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mkdirp": { - "version": "0.5.5", - "bundled": true, - "dev": true, - "requires": { - "minimist": "^1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true - } - } - }, - "move-concurrently": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true - } - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "bundled": true, - "dev": true - }, - "node-fetch-npm": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" - } - }, - "node-gyp": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.2", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.1.2", - "request": "^2.88.0", - "rimraf": "^2.6.3", - "semver": "^5.7.1", - "tar": "^4.4.12", - "which": "^1.3.1" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "bundled": true, - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "resolve": { - "version": "1.10.0", - "bundled": true, - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "npm-audit-report": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "requires": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-cache-filename": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "npm-install-checks": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "semver": "^2.3.0 || 3.x || 4 || 5" - } - }, - "npm-lifecycle": { - "version": "3.1.5", - "bundled": true, - "dev": true, - "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.15", - "node-gyp": "^5.0.2", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" - } - }, - "npm-logical-tree": { - "version": "1.2.1", - "bundled": true, - "dev": true - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "npm-package-arg": { - "version": "6.1.1", - "bundled": true, - "dev": true, - "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "dev": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-pick-manifest": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" - } - }, - "npm-profile": { - "version": "4.0.4", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.1.2 || 2", - "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^4.0.0" - } - }, - "npm-registry-fetch": { - "version": "4.0.7", - "bundled": true, - "dev": true, - "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "bundled": true, - "dev": true - } - } - }, - "npm-run-path": { - "version": "2.0.2", - "bundled": true, - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npm-user-validate": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true - }, - "object-keys": { - "version": "1.0.12", - "bundled": true, - "dev": true - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.1", - "bundled": true, - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "package-json": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "pacote": { - "version": "9.5.12", - "bundled": true, - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-normalize-package-bin": "^1.0.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^3.0.0", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.10", - "unique-filename": "^1.1.1", - "which": "^1.3.1" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "parallel-transform": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "path-exists": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "path-key": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "bundled": true, - "dev": true - }, - "pify": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "bundled": true, - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "promise-retry": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "bundled": true, - "dev": true - } - } - }, - "promzard": { - "version": "0.3.0", - "bundled": true, - "dev": true, - "requires": { - "read": "1" - } - }, - "proto-list": { - "version": "1.2.4", - "bundled": true, - "dev": true - }, - "protoduck": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "requires": { - "genfun": "^5.0.0" - } - }, - "prr": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "psl": { - "version": "1.1.29", - "bundled": true, - "dev": true - }, - "pump": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "bundled": true, - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true - }, - "qrcode-terminal": { - "version": "0.12.0", - "bundled": true, - "dev": true - }, - "qs": { - "version": "6.5.2", - "bundled": true, - "dev": true - }, - "query-string": { - "version": "6.8.2", - "bundled": true, - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, - "qw": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "read": { - "version": "1.0.7", - "bundled": true, - "dev": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-cmd-shim": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "read-installed": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "graceful-fs": "^4.1.2", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - } - }, - "read-package-json": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "read-package-tree": { - "version": "5.3.1", - "bundled": true, - "dev": true, - "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "bundled": true, - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdir-scoped-modules": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "registry-auth-token": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "rc": "^1.0.1" - } - }, - "request": { - "version": "2.88.0", - "bundled": true, - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "retry": { - "version": "0.12.0", - "bundled": true, - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-queue": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "requires": { - "aproba": "^1.1.1" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true - }, - "semver-diff": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "semver": "^5.0.3" - } - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "sha": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true - }, - "slide": { - "version": "1.1.6", - "bundled": true, - "dev": true - }, - "smart-buffer": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, - "socks": { - "version": "2.3.3", - "bundled": true, - "dev": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "4.0.2", - "bundled": true, - "dev": true, - "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "dependencies": { - "agent-base": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - } - } - }, - "sorted-object": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "sorted-union-stream": { - "version": "2.1.3", - "bundled": true, - "dev": true, - "requires": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" - }, - "dependencies": { - "from2": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" - } - }, - "isarray": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "bundled": true, - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "bundled": true, - "dev": true - } - } - }, - "spdx-correct": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "bundled": true, - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "bundled": true, - "dev": true - }, - "split-on-first": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "sshpk": { - "version": "1.14.2", - "bundled": true, - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "6.0.1", - "bundled": true, - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "stream-each": { - "version": "1.2.2", - "bundled": true, - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-iterate": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-shift": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "strict-uri-encode": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "bundled": true, - "dev": true - } - } - }, - "stringify-package": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "bundled": true, - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "term-size": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "execa": "^0.7.0" - } - }, - "text-table": { - "version": "0.2.0", - "bundled": true, - "dev": true - }, - "through": { - "version": "2.3.8", - "bundled": true, - "dev": true - }, - "through2": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "requires": { - "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": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timed-out": { - "version": "4.0.1", - "bundled": true, - "dev": true - }, - "tiny-relative-date": { - "version": "1.3.0", - "bundled": true, - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "bundled": true, - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "typedarray": { - "version": "0.0.6", - "bundled": true, - "dev": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true - }, - "umask": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "unique-filename": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "unique-string": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "crypto-random-string": "^1.0.0" - } - }, - "unpipe": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "unzip-response": { - "version": "2.0.1", - "bundled": true, - "dev": true - }, - "update-notifier": { - "version": "2.5.0", - "bundled": true, - "dev": true, - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "uri-js": { - "version": "4.4.0", - "bundled": true, - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "bundled": true, - "dev": true - } - } - }, - "url-parse-lax": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "util-extend": { - "version": "1.0.3", - "bundled": true, - "dev": true - }, - "util-promisify": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "uuid": { - "version": "3.3.3", - "bundled": true, - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "builtins": "^1.0.3" - } - }, - "verror": { - "version": "1.10.0", - "bundled": true, - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "wcwidth": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "which": { - "version": "1.3.1", - "bundled": true, - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^1.0.2" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "widest-line": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "string-width": "^2.1.1" - } - }, - "worker-farm": { - "version": "1.7.0", - "bundled": true, - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "bundled": true, - "dev": true - }, - "xtend": { - "version": "4.0.1", - "bundled": true, - "dev": true - }, - "y18n": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true - }, - "yargs": { - "version": "14.2.3", - "bundled": true, - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "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": "^15.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, - "find-up": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "bundled": true, - "dev": true - }, - "string-width": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "15.0.1", - "bundled": true, - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "bundled": true, - "dev": true - } - } - } - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true - }, - "npm-package-arg": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", - "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", - "dev": true, - "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "dev": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-registry-client": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.6.0.tgz", - "integrity": "sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==", - "dev": true, - "requires": { - "concat-stream": "^1.5.2", - "graceful-fs": "^4.1.6", - "normalize-package-data": "~1.0.1 || ^2.0.0", - "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "npmlog": "2 || ^3.1.0 || ^4.0.0", - "once": "^1.3.3", - "request": "^2.74.0", - "retry": "^0.10.0", - "safe-buffer": "^5.1.1", - "semver": "2 >=2.2.1 || 3.x || 4 || 5", - "slide": "^1.1.3", - "ssri": "^5.2.4" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nwmatcher": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", - "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", - "dev": true, - "optional": true - }, - "nyc": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.1.tgz", - "integrity": "sha512-n0MBXYBYRqa67IVt62qW1r/d9UH/Qtr7SF1w/nQLJ9KxvWF6b2xCHImRAixHN9tnMMYHC2P14uo6KddNGwMgGg==", - "dev": true, - "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.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 - }, - "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 - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.entries-ponyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.entries-ponyfill/-/object.entries-ponyfill-1.0.1.tgz", - "integrity": "sha1-Kavfd8v70mVm3RqiTp2I9lQz0lY=", - "dev": true - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "opencollective-postinstall": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", - "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", - "dev": true - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "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", - "word-wrap": "~1.2.3" - } - }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", - "dev": true, - "requires": { - "p-map": "^2.0.0" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - } - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-props": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-props/-/p-props-4.0.0.tgz", - "integrity": "sha512-3iKFbPdoPG7Ne3cMA53JnjPsTMaIzE9gxKZnvKJJivTAeqLEZPBu6zfi6DYq9AsH1nYycWmo3sWCNI8Kz6T2Zg==", - "dev": true, - "requires": { - "p-map": "^4.0.0" - } - }, - "p-reduce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", - "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", - "dev": true - }, - "p-retry": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.2.0.tgz", - "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", - "dev": true, - "requires": { - "@types/retry": "^0.12.0", - "retry": "^0.12.0" - } - }, - "p-timeout": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.0.0.tgz", - "integrity": "sha512-G02ZnMbmDL84Aue/AApFAyP7gaK7PFU2v9xFqUp10y9g1CJ05OVpQjylwYNNCwyVW051rpU8F450Zi2fEV8hVw==", - "dev": true - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", - "dev": true - }, - "pad": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", - "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", - "dev": true, - "requires": { - "wcwidth": "^1.0.1" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^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" - } - }, - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "parsimmon": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.13.0.tgz", - "integrity": "sha512-5UIrOCW+gjbILkjKPgTgmq8LKf8TT3Iy7kN2VD7OtQ81facKn8B4gG1X94jWqXYZsxG2KbJhrv/Yq/5H6BQn7A==", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pg": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.2.1.tgz", - "integrity": "sha512-DKzffhpkWRr9jx7vKxA+ur79KG+SKw+PdjMb1IRhMiKI9zqYUGczwFprqy+5Veh/DCcFs1Y6V8lRLN5I1DlleQ==", - "dev": true, - "requires": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.2.3", - "pg-pool": "^3.2.1", - "pg-protocol": "^1.2.4", - "pg-types": "^2.1.0", - "pgpass": "1.x", - "semver": "4.3.2" - }, - "dependencies": { - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=", - "dev": true - } - } - }, - "pg-connection-string": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.3.tgz", - "integrity": "sha512-I/KCSQGmOrZx6sMHXkOs2MjddrYcqpza3Dtsy0AjIgBr/bZiPJRK9WhABXN1Uy1UDazRbi9gZEzO2sAhL5EqiQ==", - "dev": true - }, - "pg-hstore": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.3.tgz", - "integrity": "sha512-qpeTpdkguFgfdoidtfeTho1Q1zPVPbtMHgs8eQ+Aan05iLmIs3Z3oo5DOZRclPGoQ4i68I1kCtQSJSa7i0ZVYg==", - "dev": true, - "requires": { - "underscore": "^1.7.0" - } - }, - "pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "dev": true - }, - "pg-pool": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.1.tgz", - "integrity": "sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA==", - "dev": true - }, - "pg-protocol": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.4.tgz", - "integrity": "sha512-/8L/G+vW/VhWjTGXpGh8XVkXOFx1ZDY+Yuz//Ab8CfjInzFkreI+fDG3WjCeSra7fIZwAFxzbGptNbm8xSXenw==", - "dev": true - }, - "pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dev": true, - "requires": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - } - }, - "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", - "dev": true, - "requires": { - "split": "^1.0.0" - } - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" - } - }, - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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 - }, - "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 - } - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, - "postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "dev": true - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", - "dev": true - }, - "postgres-date": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz", - "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==", - "dev": true - }, - "postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dev": true, - "requires": { - "xtend": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "requires": { - "fromentries": "^1.2.0" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", - "dev": true - }, - "ramda": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", - "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", - "dev": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - } - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "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" - } - }, - "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.4" - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", - "dev": true, - "requires": { - "esprima": "~4.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "regextras": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.7.0.tgz", - "integrity": "sha512-ds+fL+Vhl918gbAUb0k2gVKbTZLsg84Re3DI6p85Et0U0tYME3hyW4nMK8Px4dtDaBA2qNjvG5uWyW7eK5gfmw==", - "dev": true - }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - } - }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", - "dev": true, - "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~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 - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve-global": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", - "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", - "dev": true, - "requires": { - "global-dirs": "^0.1.1" - } - }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "requires": { - "value-or-function": "^3.0.0" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "retry-as-promised": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", - "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", - "requires": { - "any-promise": "^1.3.0" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", - "dev": true - }, - "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semantic-release": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.3.0.tgz", - "integrity": "sha512-enhDayMZRP4nWcWAMBFHHB7THRaIcRdUAZv3lxd65pXs2ttzay7IeCvRRrGayRWExtnY0ulwRz5Ycp88Dv/UeQ==", - "dev": true, - "requires": { - "@semantic-release/commit-analyzer": "^8.0.0", - "@semantic-release/error": "^2.2.0", - "@semantic-release/github": "^7.0.0", - "@semantic-release/npm": "^7.0.0", - "@semantic-release/release-notes-generator": "^9.0.0", - "aggregate-error": "^3.0.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.0.0", - "env-ci": "^5.0.0", - "execa": "^4.0.0", - "figures": "^3.0.0", - "find-versions": "^3.0.0", - "get-stream": "^5.0.0", - "git-log-parser": "^1.2.0", - "hook-std": "^2.0.0", - "hosted-git-info": "^3.0.0", - "lodash": "^4.17.15", - "marked": "^1.0.0", - "marked-terminal": "^4.0.0", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "p-reduce": "^2.0.0", - "read-pkg-up": "^7.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.3.2", - "semver-diff": "^3.1.1", - "signale": "^1.2.1", - "yargs": "^15.0.1" - }, - "dependencies": { - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - } - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "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" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "hosted-git-info": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", - "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "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" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "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 - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" - }, - "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 - }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "semver-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", - "dev": true - }, - "seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=", - "dev": true - }, - "sequelize-pool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-6.0.0.tgz", - "integrity": "sha512-D/VfOX2Z+6JTWqM73lhcqMXp1X4CeqRNVMlndvbOMtyjFAZ2kYzH7rGFGFrLO1r+RZQdc/h+3zQL4nd3cclNLg==" - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "signale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", - "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", - "dev": true, - "requires": { - "chalk": "^2.3.2", - "figures": "^2.0.0", - "pkg-conf": "^2.1.0" - }, - "dependencies": { - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - } - } - }, - "sinon": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", - "integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.2", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.0.3", - "diff": "^4.0.2", - "nise": "^4.0.1", - "supports-color": "^7.1.0" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "sinon-chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", - "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", - "dev": true - }, - "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", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "spawn-error-forwarder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", - "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", - "dev": true - }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - } - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - }, - "split2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", - "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", - "dev": true, - "requires": { - "through2": "^2.0.2" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sqlite3": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz", - "integrity": "sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg==", - "dev": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0" - } - }, - "sqlstring": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", - "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "stack-chain": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", - "integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=", - "dev": true - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - } - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "optional": true - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "taffydb": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.7.3.tgz", - "integrity": "sha1-KtNxaWKUmPylvIQkMJbTzeDsOjQ=", - "dev": true - }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", - "dev": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - } - }, - "tedious": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/tedious/-/tedious-8.3.0.tgz", - "integrity": "sha512-v46Q9SRVgz6IolyPdlsxQtfm9q/sqDs+y4aRFK0ET1iKitbpzCCQRHb6rnVcR1FLnLR0Y7AgcqnWUoMPUXz9HA==", - "dev": true, - "requires": { - "@azure/ms-rest-nodeauth": "2.0.2", - "@js-joda/core": "^2.0.0", - "bl": "^3.0.0", - "depd": "^2.0.0", - "iconv-lite": "^0.5.0", - "jsbi": "^3.1.1", - "native-duplexpair": "^1.0.0", - "punycode": "^2.1.0", - "readable-stream": "^3.6.0", - "sprintf-js": "^1.1.2" - }, - "dependencies": { - "bl": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-3.0.1.tgz", - "integrity": "sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ==", - "dev": true, - "requires": { - "readable-stream": "^3.0.1" - } - }, - "iconv-lite": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", - "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - } - } - }, - "temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true - }, - "tempy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.0.tgz", - "integrity": "sha512-eLXG5B1G0mRPHmgH2WydPl5v4jH35qEn3y/rA/aahKhIa91Pn119SsU7n7v/433gtT9ONzC8ISvNHIh2JSTm0w==", - "dev": true, - "requires": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true - } - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } - }, - "through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - } - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", - "dev": true, - "requires": { - "through2": "^2.0.3" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true, - "optional": true - }, - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", - "dev": true - }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", - "dev": true - }, - "trim-off-newlines": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", - "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", - "dev": true - }, - "tslint": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.14.0.tgz", - "integrity": "sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ==", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", - "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "uglify-js": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz", - "integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==", - "dev": true, - "optional": true - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", - "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", - "dev": true - }, - "unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", - "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==" - }, - "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, - "requires": { - "builtins": "^1.0.3" - } - }, - "validator": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", - "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" - }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - }, - "vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - } - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "webidl-conversions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", - "integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=", - "dev": true, - "optional": true - }, - "whatwg-url-compat": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz", - "integrity": "sha1-AImBEa9om7CXVBzVpFymyHmERb8=", - "dev": true, - "optional": true, - "requires": { - "tr46": "~0.0.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wkx": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", - "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", - "requires": { - "@types/node": "*" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xml-name-validator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", - "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", - "dev": true, - "optional": true - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true - }, - "xmldom": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz", - "integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g==", - "dev": true - }, - "xpath.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz", - "integrity": "sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yaml": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz", - "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "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" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "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-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.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" - } - }, - "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 - }, - "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 - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "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" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "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.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "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 - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "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.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - } - } -} From dbeaa5c7795f6b33fb566cb17a132b6377eedf5c Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 20:25:33 -0300 Subject: [PATCH 228/414] refactor: minor cleanup --- .editorconfig | 9 ------- .gitignore | 8 +----- package.json | 10 ++++---- scripts/appveyor-setup.ps1 | 51 -------------------------------------- scripts/mocha-bootload | 0 5 files changed, 6 insertions(+), 72 deletions(-) delete mode 100644 scripts/appveyor-setup.ps1 delete mode 100644 scripts/mocha-bootload diff --git a/.editorconfig b/.editorconfig index bc048d8c822c..85e074827ad8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,18 +6,9 @@ root = true [*] -# Change these settings to your own preference indent_style = space indent_size = 2 - -# We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false - -[Makefile] -indent_style = tabs diff --git a/.gitignore b/.gitignore index 0483107ca2ef..b47ee9c01b3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,9 @@ *.swp .idea .DS_STORE -npm-debug.log +npm-debug.log* *~ -test/dialects/sqlite/test.sqlite -test/sqlite/test.sqlite test.sqlite -docs/api/tmp.md -ssce.js -sscce.js *.sublime* yarn.lock @@ -17,7 +12,6 @@ coverage-* coverage test/tmp/* test/binary/tmp/* -site .vscode/ esdoc node_modules diff --git a/package.json b/package.json index 1e591dd052e7..4ebe1b7975fe 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "test-docker-integration": "env-cmd $npm_package_options_env_cmd npm run test-integration", "docs": "rimraf esdoc && esdoc -c docs/esdoc-config.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER && node docs/run-docs-transforms.js && node docs/redirects/create-redirects.js && rimraf esdoc/file esdoc/source.html", "teaser": "node scripts/teaser", - "test-unit": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.js\"", + "test-unit": "mocha --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.js\"", "test-unit-mariadb": "cross-env DIALECT=mariadb npm run test-unit", "test-unit-mysql": "cross-env DIALECT=mysql npm run test-unit", "test-unit-postgres": "cross-env DIALECT=postgres npm run test-unit", @@ -168,7 +168,7 @@ "test-unit-sqlite": "cross-env DIALECT=sqlite npm run test-unit", "test-unit-mssql": "cross-env DIALECT=mssql npm run test-unit", "test-unit-all": "npm run test-unit-mariadb && npm run test-unit-mysql && npm run test-unit-postgres && npm run test-unit-postgres-native && npm run test-unit-mssql && npm run test-unit-sqlite", - "test-integration": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/integration/**/*.test.js\"", + "test-integration": "mocha --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/integration/**/*.test.js\"", "test-integration-mariadb": "cross-env DIALECT=mariadb npm run test-integration", "test-integration-mysql": "cross-env DIALECT=mysql npm run test-integration", "test-integration-postgres": "cross-env DIALECT=postgres npm run test-integration", @@ -187,8 +187,8 @@ "test-all": "npm run test-mariadb && npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql", "test-typings": "tsc -b types/tsconfig.json && dtslint --expectOnly types/test", "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", - "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha --require scripts/mocha-bootload -t 30000 --exit \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", - "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha --require scripts/mocha-bootload -t 30000 --exit \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", + "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha -t 30000 --exit \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", + "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha -t 30000 --exit \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", "merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"", "sscce": "env-cmd $npm_package_options_env_cmd node sscce.js", "sscce-mariadb": "cross-env DIALECT=mariadb npm run sscce", @@ -197,6 +197,6 @@ "sscce-sqlite": "cross-env DIALECT=sqlite npm run sscce", "sscce-mssql": "cross-env DIALECT=mssql npm run sscce", "setup-mssql": "env-cmd $npm_package_options_env_cmd ./scripts/setup-mssql", - "semantic-release": "semantic-release" + "//semantic-release": "semantic-release" } } diff --git a/scripts/appveyor-setup.ps1 b/scripts/appveyor-setup.ps1 deleted file mode 100644 index 80093463bcc6..000000000000 --- a/scripts/appveyor-setup.ps1 +++ /dev/null @@ -1,51 +0,0 @@ - -Set-Service sqlbrowser -StartupType auto -Start-Service sqlbrowser - -[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null -[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null - -$serverName = $env:COMPUTERNAME -$instanceName = 'SQL2017' -$smo = 'Microsoft.SqlServer.Management.Smo.' -$wmi = new-object ($smo + 'Wmi.ManagedComputer') - -# Enable TCP/IP -$uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']" -$Tcp = $wmi.GetSmoObject($uri) -$Tcp.IsEnabled = $true -$TCP.alter() - -Start-Service "MSSQL`$$instanceName" - -$ipall = $wmi.GetSmoObject("ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']/IPAddress[@Name='IPAll']") -$port = $ipall.IPAddressProperties.Item("TcpPort").Value - -$config = @{ - host = "localhost" - username = "sa" - password = "Password12!" - port = $port - database = "sequelize_test" - dialectOptions = @{ - options = @{ - encrypt = $false - requestTimeout = 25000 - cryptoCredentialsDetails = @{ - ciphers = "RC4-MD5" - } - } - } - pool = @{ - max = 5 - idle = 3000 - } -} - -$json = $config | ConvertTo-Json -Depth 3 - -# Create sequelize_test database -sqlcmd -S "(local)" -U "sa" -P "Password12!" -d "master" -Q "CREATE DATABASE [sequelize_test]; ALTER DATABASE [sequelize_test] SET READ_COMMITTED_SNAPSHOT ON;" - -# cannot use Out-File because it outputs a BOM -[IO.File]::WriteAllLines((Join-Path $pwd "test\config\mssql.json"), $json) diff --git a/scripts/mocha-bootload b/scripts/mocha-bootload deleted file mode 100644 index e69de29bb2d1..000000000000 From d034a8f746eb3ec5f5aafd29fbfda0f24bcf658c Mon Sep 17 00:00:00 2001 From: papb Date: Fri, 15 Jan 2021 20:30:35 -0300 Subject: [PATCH 229/414] build: fix version in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ebe1b7975fe..b460363d3ab6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sequelize", "description": "Multi dialect ORM for Node.JS", - "version": "6.1.0", + "version": "0.0.0-development", "maintainers": [ "Sascha Depold ", "Jan Aagaard Meier ", From 0ca876e29eec6fc1d90b1e3dea5d6de4e6fa65ca Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 10:14:03 -0300 Subject: [PATCH 230/414] build: update dev-dependencies --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b460363d3ab6..88e06a719048 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,11 @@ "wkx": "^0.5.0" }, "devDependencies": { - "@commitlint/cli": "^8.3.5", - "@commitlint/config-angular": "^8.3.4", + "@commitlint/cli": "^11.0.0", + "@commitlint/config-angular": "^11.0.0", "@types/node": "^12.12.42", "@types/validator": "^10.11.0", - "acorn": "^7.2.0", + "acorn": "^8.0.4", "chai": "^4.x", "chai-as-promised": "^7.x", "chai-datetime": "^1.6.0", @@ -67,13 +67,13 @@ "eslint": "^6.8.0", "eslint-plugin-jsdoc": "^20.4.0", "eslint-plugin-mocha": "^6.2.2", - "fs-jetpack": "^2.4.0", + "fs-jetpack": "^4.1.0", "husky": "^4.2.5", "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", "lint-staged": "^10.2.6", "mariadb": "^2.3.1", - "markdownlint-cli": "^0.23.1", + "markdownlint-cli": "^0.26.0", "marked": "^1.1.0", "mocha": "^7.1.2", "mysql2": "^2.1.0", @@ -89,7 +89,7 @@ "sinon-chai": "^3.3.0", "sqlite3": "^4.2.0", "tedious": "8.3.0", - "typescript": "^3.9.3" + "typescript": "^4.1.3" }, "peerDependenciesMeta": { "pg": { From d399de7e3a76f9959d2e9c337b6443b6dbe1174e Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 13:40:13 -0300 Subject: [PATCH 231/414] ci: re-add pg minify-aliases tests --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bf5ce4c5a8e..2b4062d4a317 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,9 @@ jobs: fail-fast: false matrix: postgres-version: [9.5, 10] # Does not work with 12 + minify-aliases: [true, false] native: [true, false] - name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }} + name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }}${{ matrix.minify-aliases && ' (minified aliases)' || '' }} runs-on: ubuntu-latest services: postgres: @@ -63,6 +64,7 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: DIALECT: ${{ matrix.native && 'postgres-native' || 'postgres' }} + SEQ_PG_MINIFY_ALIASES: ${{ matrix.minify-aliases && '1' || '' }} steps: - run: PGPASSWORD=sequelize_test psql -h localhost -p 5432 -U sequelize_test sequelize_test -c '\l' - uses: actions/checkout@v2 @@ -74,6 +76,7 @@ jobs: if: matrix.native - name: Unit Tests run: npm run test-unit + if: ${{ !matrix.minify-aliases }} - name: Integration Tests run: npm run test-integration test-mysql-mariadb: From 9ecebef5f29747091e784bf41341e9b9d7a3e671 Mon Sep 17 00:00:00 2001 From: Ricky Curtice Date: Thu, 2 Jul 2020 22:45:01 -0700 Subject: [PATCH 232/414] feat(query-interface): support composite foreign keys (#12456) Note: this commit was cherry-picked from a55c154 --- lib/dialects/abstract/query-generator.js | 8 ++++-- lib/dialects/abstract/query-interface.js | 34 +++++++++++++++++------- test/unit/sql/add-constraint.test.js | 19 +++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 97e372bbec48..e07784e3ee80 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -664,11 +664,15 @@ class QueryGenerator { break; case 'FOREIGN KEY': const references = options.references; - if (!references || !references.table || !references.field) { + if (!references || !references.table || !(references.field || references.fields)) { throw new Error('references object with table and field must be specified'); } constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_${references.table}_fk`); - const referencesSnippet = `${this.quoteTable(references.table)} (${this.quoteIdentifier(references.field)})`; + const quotedReferences = + typeof references.field !== 'undefined' + ? this.quoteIdentifier(references.field) + : references.fields.map(f => this.quoteIdentifier(f)).join(', '); + const referencesSnippet = `${this.quoteTable(references.table)} (${quotedReferences})`; constraintSnippet = `CONSTRAINT ${constraintName} `; constraintSnippet += `FOREIGN KEY (${fieldsSqlQuotedString}) REFERENCES ${referencesSnippet}`; if (options.onUpdate) { diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 2b156b06d8f6..3f00e909f4e5 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -679,16 +679,30 @@ class QueryInterface { * onUpdate: 'cascade' * }); * - * @param {string} tableName Table name where you want to add a constraint - * @param {object} options An object to define the constraint name, type etc - * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) - * @param {Array} options.fields Array of column names to apply the constraint over - * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names - * @param {string} [options.defaultValue] The value for the default constraint - * @param {object} [options.where] Where clause/expression for the CHECK constraint - * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint - * @param {string} [options.references.table] Target table name - * @param {string} [options.references.field] Target column name + * @example + * queryInterface.addConstraint('TableName', { + * fields: ['source_column_name', 'other_source_column_name'], + * type: 'foreign key', + * name: 'custom_fkey_constraint_name', + * references: { //Required field + * table: 'target_table_name', + * fields: ['target_column_name', 'other_target_column_name'] + * }, + * onDelete: 'cascade', + * onUpdate: 'cascade' + * }); + * + * @param {string} tableName Table name where you want to add a constraint + * @param {object} options An object to define the constraint name, type etc + * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) + * @param {Array} options.fields Array of column names to apply the constraint over + * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names + * @param {string} [options.defaultValue] The value for the default constraint + * @param {object} [options.where] Where clause/expression for the CHECK constraint + * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint + * @param {string} [options.references.table] Target table name + * @param {string} [options.references.field] Target column name + * @param {string} [options.references.fields] Target column names for a composite primary key. Must match the order of fields in options.fields. * * @returns {Promise} */ diff --git a/test/unit/sql/add-constraint.test.js b/test/unit/sql/add-constraint.test.js index 3466062d0828..239d8d7052e2 100644 --- a/test/unit/sql/add-constraint.test.js +++ b/test/unit/sql/add-constraint.test.js @@ -157,6 +157,25 @@ if (current.dialect.supports.constraints.addConstraint) { }); }); + it('supports composite keys', () => { + expectsql( + sql.addConstraintQuery('myTable', { + type: 'foreign key', + fields: ['myColumn', 'anotherColumn'], + references: { + table: 'myOtherTable', + fields: ['id1', 'id2'] + }, + onUpdate: 'cascade', + onDelete: 'cascade' + }), + { + default: + 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_anotherColumn_myOtherTable_fk] FOREIGN KEY ([myColumn], [anotherColumn]) REFERENCES [myOtherTable] ([id1], [id2]) ON UPDATE CASCADE ON DELETE CASCADE;' + } + ); + }); + it('uses onDelete, onUpdate', () => { expectsql(sql.addConstraintQuery('myTable', { type: 'foreign key', From afb247cd2546f3132537de75c698e1028e026109 Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 14:18:16 -0300 Subject: [PATCH 233/414] docs: update contact.md See a4290a4 and febc083 --- CONTACT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTACT.md b/CONTACT.md index 0cae62d734e9..0f7c3b636c8d 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -7,4 +7,4 @@ You can use the information below to contact maintainers directly. We will try t - **Pedro Augusto de Paula Barbosa** papb1996@gmail.com - **Jan Aagaard Meier** janzeh@gmail.com -- **Sushant Dhiman** sushantdhiman@outlook.com +- **Sascha Depold** sascha@depold.com From fd5749b431a1edc71b03a74c1c09b72fc57fa87a Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Fri, 11 Dec 2020 07:07:31 +0100 Subject: [PATCH 234/414] docs: looking for core maintainers! Note: cherry-picked (with minor edits) from 6554992 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9d2152a0f0ed..b7f8de0186ba 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.or You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/other-topics/upgrade-to-v6.md). +## Looking for maintainers +Due to personal reasons a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy recently. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: + +We are looking for more core maintainers who are interested in finishing the current TypeScript migration, extending the documentation for v6, organizing issues, reviewing PRs, streamlining the overall code base and planning the future roadmap. + +If that sounds interesting to you, please reach out to us on [Slack](https://sequelize.slack.com/). We are looking forward to meet you! + ## Installation ```sh From 268c067f340283465d173f9d34833f4311049e6e Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 14:47:44 -0300 Subject: [PATCH 235/414] docs: fix ci badges in readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index b7f8de0186ba..18dc3ebfd419 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Sequelize [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) -[![Travis Build Status](https://badgen.net/travis/sequelize/sequelize?icon=travis)](https://travis-ci.org/sequelize/sequelize) -[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/9l1ypgwsp5ij46m3/branch/master?svg=true)](https://ci.appveyor.com/project/sushantdhiman/sequelize/branch/master) +[![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) [![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize/master?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) From c7ab77e51998f8e76c9dcdaec893e714fcc1616c Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 15:09:27 -0300 Subject: [PATCH 236/414] docs: fix ci badges in docs/index.md --- docs/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index f62f464fc096..430eccc20ad0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,8 +6,7 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) -[![Travis Build Status](https://badgen.net/travis/sequelize/sequelize?icon=travis)](https://travis-ci.org/sequelize/sequelize) -[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/9l1ypgwsp5ij46m3/branch/master?svg=true)](https://ci.appveyor.com/project/sushantdhiman/sequelize/branch/master) +[![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) [![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) [![Last commit](https://badgen.net/github/last-commit/sequelize/sequelize)](https://github.com/sequelize/sequelize) From 2249ded161755ed6d36bbfd044d00e51bb3f559e Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 15:15:35 -0300 Subject: [PATCH 237/414] fix(types): remove part forgotten in #12175 --- types/lib/sequelize.d.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index e1e7c446d407..44976d55bc91 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1184,27 +1184,6 @@ export class Sequelize extends Hooks { */ public isDefined(modelName: string): boolean; - /** - * Imports a model defined in another file - * - * Imported models are cached, so multiple calls to import with the same path will not load the file - * multiple times - * - * See https://github.com/sequelize/sequelize/blob/master/examples/using-multiple-model-files/Task.js for a - * short example of how to define your models in separate files so that they can be imported by - * sequelize.import - * - * @param path The path to the file that holds the model you want to import. If the part is relative, it - * will be resolved relatively to the calling file - * - * @param defineFunction An optional function that provides model definitions. Useful if you do not - * want to use the module root as the define function - */ - public import( - path: string, - defineFunction?: (sequelize: Sequelize, dataTypes: typeof DataTypes) => T - ): T; - /** * Execute a query on the DB, optionally bypassing all the Sequelize goodness. * From 9634338d8f9a07ff151f70a91793b37547e2c23e Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 15:16:17 -0300 Subject: [PATCH 238/414] chore: rename 'master' branch to 'main' --- .github/PULL_REQUEST_TEMPLATE.md | 4 +- CONTRIBUTING.md | 12 +- README.md | 8 +- docs/index.md | 2 +- docs/manual/core-concepts/model-basics.md | 872 +++++++++--------- docs/manual/core-concepts/raw-queries.md | 2 +- docs/manual/other-topics/legal.md | 2 +- docs/manual/other-topics/migrations.md | 2 +- docs/manual/other-topics/read-replication.md | 58 +- docs/manual/other-topics/upgrade-to-v6.md | 2 +- lib/dialects/mssql/data-types.js | 2 +- lib/dialects/postgres/connection-manager.js | 2 +- lib/utils/deprecations.js | 2 +- .../abstract/connection-manager.test.js | 6 +- 14 files changed, 488 insertions(+), 488 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c1659ed024fa..91d8c314aaa4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34b4ed63f3f1..f4aa0fee41c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ We're glad to get pull request if any functionality is missing or something is b - Explain the issue that your PR is solving - or link to an existing issue - Make sure that all existing tests pass -- Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md#coding-guidelines) +- Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#coding-guidelines) - Add some tests for your new functionality or a test exhibiting the bug you are solving. Ideally all new tests should not pass _without_ your changes. - Use [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) in all new tests. Specifically this means: - don't use `EventEmitter`, `QueryChainer` or the `success`, `done` and `error` events @@ -153,18 +153,18 @@ Then push and send your pull request. Happy hacking and thank you for contributi # Coding guidelines -Have a look at our [.eslintrc.json](https://github.com/sequelize/sequelize/blob/master/.eslintrc.json) file for the specifics. As part of the test process, all files will be linted, and your PR will **not** be accepted if it does not pass linting. +Have a look at our [.eslintrc.json](https://github.com/sequelize/sequelize/blob/main/.eslintrc.json) file for the specifics. As part of the test process, all files will be linted, and your PR will **not** be accepted if it does not pass linting. # Contributing to the documentation -For contribution guidelines for the documentation, see [CONTRIBUTING.DOCS.md](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.DOCS.md). +For contribution guidelines for the documentation, see [CONTRIBUTING.DOCS.md](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.DOCS.md). # Publishing a release (For Maintainers) -1. Ensure that latest build on master is green -2. Ensure your local code is up to date (`git pull origin master`) +1. Ensure that latest build on the main branch is green +2. Ensure your local code is up to date (`git pull origin main`) 3. `npm version patch|minor|major` (see [Semantic Versioning](http://semver.org)) 4. Update changelog to match version number, commit changelog -5. `git push --tags origin master` +5. `git push --tags origin main` 6. `npm publish .` 7. Copy changelog for version to release notes for version on github diff --git a/README.md b/README.md index 18dc3ebfd419..9b50e17ce361 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) [![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize/master?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) +[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize/main?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) @@ -13,7 +13,7 @@ New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.or ### v6 Release -You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/master/docs/manual/other-topics/upgrade-to-v6.md). +You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). ## Looking for maintainers Due to personal reasons a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy recently. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: @@ -39,11 +39,11 @@ $ npm i tedious # Microsoft SQL Server - [v6 Documentation](https://sequelize.org/master) - [v5/v4/v3 Documentation](https://sequelize.org) -- [Contributing](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md) +- [Contributing](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) ## Responsible disclosure -If you have security issues to report, please refer to our [Responsible Disclosure Policy](https://github.com/sequelize/sequelize/blob/master/SECURITY.md) for more details. +If you have security issues to report, please refer to our [Responsible Disclosure Policy](https://github.com/sequelize/sequelize/blob/main/SECURITY.md) for more details. ## Resources diff --git a/docs/index.md b/docs/index.md index 430eccc20ad0..5266b16723c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,7 +14,7 @@ [![GitHub stars](https://badgen.net/github/stars/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![Slack Status](http://sequelize-slack.herokuapp.com/badge.svg)](http://sequelize-slack.herokuapp.com/) [![node](https://badgen.net/npm/node/sequelize)](https://www.npmjs.com/package/sequelize) -[![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/master/LICENSE) +[![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/main/LICENSE) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) Sequelize is a promise-based Node.js [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md index a413b81fa614..99b2c209a64b 100644 --- a/docs/manual/core-concepts/model-basics.md +++ b/docs/manual/core-concepts/model-basics.md @@ -1,436 +1,436 @@ -# Model Basics - -In this tutorial you will learn what models are in Sequelize and how to use them. - -## Concept - -Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends [Model](../class/lib/model.js~Model.html). - -The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types). - -A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as `User`) while tables have pluralized names (such as `Users`), although this is fully configurable. - -## Model Definition - -Models can be defined in two equivalent ways in Sequelize: - -* Calling [`sequelize.define(modelName, attributes, options)`](../class/lib/sequelize.js~Sequelize.html#instance-method-define) -* Extending [Model](../class/lib/model.js~Model.html) and calling [`init(attributes, options)`](../class/lib/model.js~Model.html#static-method-init) - -After a model is defined, it is available within `sequelize.models` by its model name. - -To learn with an example, we will consider that we want to create a model to represent users, which have a `firstName` and a `lastName`. We want our model to be called `User`, and the table it represents is called `Users` in the database. - -Both ways to define this model are shown below. After being defined, we can access our model with `sequelize.models.User`. - -### Using [`sequelize.define`](../class/lib/sequelize.js~Sequelize.html#instance-method-define): - -```js -const { Sequelize, DataTypes } = require('sequelize'); -const sequelize = new Sequelize('sqlite::memory:'); - -const User = sequelize.define('User', { - // Model attributes are defined here - firstName: { - type: DataTypes.STRING, - allowNull: false - }, - lastName: { - type: DataTypes.STRING - // allowNull defaults to true - } -}, { - // Other model options go here -}); - -// `sequelize.define` also returns the model -console.log(User === sequelize.models.User); // true -``` - -### Extending [Model](../class/lib/model.js~Model.html) - -```js -const { Sequelize, DataTypes, Model } = require('sequelize'); -const sequelize = new Sequelize('sqlite::memory'); - -class User extends Model {} - -User.init({ - // Model attributes are defined here - firstName: { - type: DataTypes.STRING, - allowNull: false - }, - lastName: { - type: DataTypes.STRING - // allowNull defaults to true - } -}, { - // Other model options go here - sequelize, // We need to pass the connection instance - modelName: 'User' // We need to choose the model name -}); - -// the defined model is the class itself -console.log(User === sequelize.models.User); // true -``` - -Internally, `sequelize.define` calls `Model.init`, so both approaches are essentially equivalent. - -## Table name inference - -Observe that, in both methods above, the table name (`Users`) was never explicitly defined. However, the model name was given (`User`). - -By default, when the table name is not given, Sequelize automatically pluralizes the model name and uses that as the table name. This pluralization is done under the hood by a library called [inflection](https://www.npmjs.com/package/inflection), so that irregular plurals (such as `person -> people`) are computed correctly. - -Of course, this behavior is easily configurable. - -### Enforcing the table name to be equal to the model name - -You can stop the auto-pluralization performed by Sequelize using the `freezeTableName: true` option. This way, Sequelize will infer the table name to be equal to the model name, without any modifications: - -```js -sequelize.define('User', { - // ... (attributes) -}, { - freezeTableName: true -}); -``` - -The example above will create a model named `User` pointing to a table also named `User`. - -This behavior can also be defined globally for the sequelize instance, when it is created: - -```js -const sequelize = new Sequelize('sqlite::memory:', { - define: { - freezeTableName: true - } -}); -``` - -This way, all tables will use the same name as the model name. - -### Providing the table name directly - -You can simply tell Sequelize the name of the table directly as well: - -```js -sequelize.define('User', { - // ... (attributes) -}, { - tableName: 'Employees' -}); -``` - -## Model synchronization - -When you define a model, you're telling Sequelize a few things about its table in the database. However, what if the table actually doesn't even exist in the database? What if it exists, but it has different columns, less columns, or any other difference? - -This is where model synchronization comes in. A model can be synchronized with the database by calling [`model.sync(options)`](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-sync), an asynchronous function (that returns a Promise). With this call, Sequelize will automatically perform an SQL query to the database. Note that this changes only the table in the database, not the model in the JavaScript side. - -* `User.sync()` - This creates the table if it doesn't exist (and does nothing if it already exists) -* `User.sync({ force: true })` - This creates the table, dropping it first if it already existed -* `User.sync({ alter: true })` - This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model. - -Example: - -```js -await User.sync({ force: true }); -console.log("The table for the User model was just (re)created!"); -``` - -### Synchronizing all models at once - -You can use [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync) to automatically synchronize all models. Example: - -```js -await sequelize.sync({ force: true }); -console.log("All models were synchronized successfully."); -``` - -### Dropping tables - -To drop the table related to a model: - -```js -await User.drop(); -console.log("User table dropped!"); -``` - -To drop all tables: - -```js -await sequelize.drop(); -console.log("All tables dropped!"); -``` - -### Database safety check - -As shown above, the `sync` and `drop` operations are destructive. Sequelize accepts a `match` option as an additional safety check, which receives a RegExp: - -```js -// This will run .sync() only if database name ends with '_test' -sequelize.sync({ force: true, match: /_test$/ }); -``` - -### Synchronization in production - -As shown above, `sync({ force: true })` and `sync({ alter: true })` can be destructive operations. Therefore, they are not recommended for production-level software. Instead, synchronization should be done with the advanced concept of [Migrations](migrations.html), with the help of the [Sequelize CLI](https://github.com/sequelize/cli). - -## Timestamps - -By default, Sequelize automatically adds the fields `createdAt` and `updatedAt` to every model, using the data type `DataTypes.DATE`. Those fields are automatically managed as well - whenever you use Sequelize to create or update something, those fields will be set correctly. The `createdAt` field will contain the timestamp representing the moment of creation, and the `updatedAt` will contain the timestamp of the latest update. - -**Note:** This is done in the Sequelize level (i.e. not done with *SQL triggers*). This means that direct SQL queries (for example queries performed without Sequelize by any other means) will not cause these fields to be updated automatically. - -This behavior can be disabled for a model with the `timestamps: false` option: - -```js -sequelize.define('User', { - // ... (attributes) -}, { - timestamps: false -}); -``` - -It is also possible to enable only one of `createdAt`/`updatedAt`, and to provide a custom name for these columns: - -```js -class Foo extends Model {} -Foo.init({ /* attributes */ }, { - sequelize, - - // don't forget to enable timestamps! - timestamps: true, - - // I don't want createdAt - createdAt: false, - - // I want updatedAt to actually be called updateTimestamp - updatedAt: 'updateTimestamp' -}); -``` - -## Column declaration shorthand syntax - -If the only thing being specified about a column is its data type, the syntax can be shortened: - -```js -// This: -sequelize.define('User', { - name: { - type: DataTypes.STRING - } -}); - -// Can be simplified to: -sequelize.define('User', { name: DataTypes.STRING }); -``` - -## Default Values - -By default, Sequelize assumes that the default value of a column is `NULL`. This behavior can be changed by passing a specific `defaultValue` to the column definition: - -```js -sequelize.define('User', { - name: { - type: DataTypes.STRING, - defaultValue: "John Doe" - } -}); -``` - -Some special values, such as `Sequelize.NOW`, are also accepted: - -```js -sequelize.define('Foo', { - bar: { - type: DataTypes.DATETIME, - defaultValue: Sequelize.NOW - // This way, the current date/time will be used to populate this column (at the moment of insertion) - } -}); -``` - -## Data Types - -Every column you define in your model must have a data type. Sequelize provides [a lot of built-in data types](https://github.com/sequelize/sequelize/blob/master/lib/data-types.js). To access a built-in data type, you must import `DataTypes`: - -```js -const { DataTypes } = require("sequelize"); // Import the built-in data types -``` - -### Strings - -```js -DataTypes.STRING // VARCHAR(255) -DataTypes.STRING(1234) // VARCHAR(1234) -DataTypes.STRING.BINARY // VARCHAR BINARY -DataTypes.TEXT // TEXT -DataTypes.TEXT('tiny') // TINYTEXT -DataTypes.CITEXT // CITEXT PostgreSQL and SQLite only. -``` - -### Boolean - -```js -DataTypes.BOOLEAN // TINYINT(1) -``` - -### Numbers - -```js -DataTypes.INTEGER // INTEGER -DataTypes.BIGINT // BIGINT -DataTypes.BIGINT(11) // BIGINT(11) - -DataTypes.FLOAT // FLOAT -DataTypes.FLOAT(11) // FLOAT(11) -DataTypes.FLOAT(11, 10) // FLOAT(11,10) - -DataTypes.REAL // REAL PostgreSQL only. -DataTypes.REAL(11) // REAL(11) PostgreSQL only. -DataTypes.REAL(11, 12) // REAL(11,12) PostgreSQL only. - -DataTypes.DOUBLE // DOUBLE -DataTypes.DOUBLE(11) // DOUBLE(11) -DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) - -DataTypes.DECIMAL // DECIMAL -DataTypes.DECIMAL(10, 2) // DECIMAL(10,2) -``` - -#### Unsigned & Zerofill integers - MySQL/MariaDB only - -In MySQL and MariaDB, the data types `INTEGER`, `BIGINT`, `FLOAT` and `DOUBLE` can be set as unsigned or zerofill (or both), as follows: - -```js -DataTypes.INTEGER.UNSIGNED -DataTypes.INTEGER.ZEROFILL -DataTypes.INTEGER.UNSIGNED.ZEROFILL -// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER -// Same for BIGINT, FLOAT and DOUBLE -``` - -### Dates - -```js -DataTypes.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres -DataTypes.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision -DataTypes.DATEONLY // DATE without time -``` - -### UUIDs - -For UUIDs, use `DataTypes.UUID`. It becomes the `UUID` data type for PostgreSQL and SQLite, and `CHAR(36)` for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use `Sequelize.UUIDV1` or `Sequelize.UUIDV4` as the default value: - -```js -{ - type: DataTypes.UUID, - defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1 -} -``` - -### Others - -There are other data types, covered in a [separate guide](other-data-types.html). - -## Column Options - -When defining a column, apart from specifying the `type` of the column, and the `allowNull` and `defaultValue` options mentioned above, there are a lot more options that can be used. Some examples are below. - -```js -const { Model, DataTypes, Deferrable } = require("sequelize"); - -class Foo extends Model {} -Foo.init({ - // instantiating will automatically set the flag to true if not set - flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, - - // default values for dates => current time - myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, - - // setting allowNull to false will add NOT NULL to the column, which means an error will be - // thrown from the DB when the query is executed if the column is null. If you want to check that a value - // is not null before querying the DB, look at the validations section below. - title: { type: DataTypes.STRING, allowNull: false }, - - // Creating two objects with the same value will throw an error. The unique property can be either a - // boolean, or a string. If you provide the same string for multiple columns, they will form a - // composite unique key. - uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' }, - uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' }, - - // The unique property is simply a shorthand to create a unique constraint. - someUnique: { type: DataTypes.STRING, unique: true }, - - // Go on reading for further information about primary keys - identifier: { type: DataTypes.STRING, primaryKey: true }, - - // autoIncrement can be used to create auto_incrementing integer columns - incrementMe: { type: DataTypes.INTEGER, autoIncrement: true }, - - // You can specify a custom column name via the 'field' attribute: - fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' }, - - // It is possible to create foreign keys: - bar_id: { - type: DataTypes.INTEGER, - - references: { - // This is a reference to another model - model: Bar, - - // This is the column name of the referenced model - key: 'id', - - // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type. - deferrable: Deferrable.INITIALLY_IMMEDIATE - // Options: - // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints - // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction - // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction - } - }, - - // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL - commentMe: { - type: DataTypes.INTEGER, - comment: 'This is a column name that has a comment' - } -}, { - sequelize, - modelName: 'foo', - - // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options: - indexes: [{ unique: true, fields: ['someUnique'] }] -}); -``` - -## Taking advantage of Models being classes - -The Sequelize models are [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). You can very easily add custom instance or class level methods. - -```js -class User extends Model { - static classLevelMethod() { - return 'foo'; - } - instanceLevelMethod() { - return 'bar'; - } - getFullname() { - return [this.firstname, this.lastname].join(' '); - } -} -User.init({ - firstname: Sequelize.TEXT, - lastname: Sequelize.TEXT -}, { sequelize }); - -console.log(User.classLevelMethod()); // 'foo' -const user = User.build({ firstname: 'Jane', lastname: 'Doe' }); -console.log(user.instanceLevelMethod()); // 'bar' -console.log(user.getFullname()); // 'Jane Doe' -``` \ No newline at end of file +# Model Basics + +In this tutorial you will learn what models are in Sequelize and how to use them. + +## Concept + +Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends [Model](../class/lib/model.js~Model.html). + +The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types). + +A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as `User`) while tables have pluralized names (such as `Users`), although this is fully configurable. + +## Model Definition + +Models can be defined in two equivalent ways in Sequelize: + +* Calling [`sequelize.define(modelName, attributes, options)`](../class/lib/sequelize.js~Sequelize.html#instance-method-define) +* Extending [Model](../class/lib/model.js~Model.html) and calling [`init(attributes, options)`](../class/lib/model.js~Model.html#static-method-init) + +After a model is defined, it is available within `sequelize.models` by its model name. + +To learn with an example, we will consider that we want to create a model to represent users, which have a `firstName` and a `lastName`. We want our model to be called `User`, and the table it represents is called `Users` in the database. + +Both ways to define this model are shown below. After being defined, we can access our model with `sequelize.models.User`. + +### Using [`sequelize.define`](../class/lib/sequelize.js~Sequelize.html#instance-method-define): + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:'); + +const User = sequelize.define('User', { + // Model attributes are defined here + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING + // allowNull defaults to true + } +}, { + // Other model options go here +}); + +// `sequelize.define` also returns the model +console.log(User === sequelize.models.User); // true +``` + +### Extending [Model](../class/lib/model.js~Model.html) + +```js +const { Sequelize, DataTypes, Model } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory'); + +class User extends Model {} + +User.init({ + // Model attributes are defined here + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING + // allowNull defaults to true + } +}, { + // Other model options go here + sequelize, // We need to pass the connection instance + modelName: 'User' // We need to choose the model name +}); + +// the defined model is the class itself +console.log(User === sequelize.models.User); // true +``` + +Internally, `sequelize.define` calls `Model.init`, so both approaches are essentially equivalent. + +## Table name inference + +Observe that, in both methods above, the table name (`Users`) was never explicitly defined. However, the model name was given (`User`). + +By default, when the table name is not given, Sequelize automatically pluralizes the model name and uses that as the table name. This pluralization is done under the hood by a library called [inflection](https://www.npmjs.com/package/inflection), so that irregular plurals (such as `person -> people`) are computed correctly. + +Of course, this behavior is easily configurable. + +### Enforcing the table name to be equal to the model name + +You can stop the auto-pluralization performed by Sequelize using the `freezeTableName: true` option. This way, Sequelize will infer the table name to be equal to the model name, without any modifications: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + freezeTableName: true +}); +``` + +The example above will create a model named `User` pointing to a table also named `User`. + +This behavior can also be defined globally for the sequelize instance, when it is created: + +```js +const sequelize = new Sequelize('sqlite::memory:', { + define: { + freezeTableName: true + } +}); +``` + +This way, all tables will use the same name as the model name. + +### Providing the table name directly + +You can simply tell Sequelize the name of the table directly as well: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + tableName: 'Employees' +}); +``` + +## Model synchronization + +When you define a model, you're telling Sequelize a few things about its table in the database. However, what if the table actually doesn't even exist in the database? What if it exists, but it has different columns, less columns, or any other difference? + +This is where model synchronization comes in. A model can be synchronized with the database by calling [`model.sync(options)`](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-sync), an asynchronous function (that returns a Promise). With this call, Sequelize will automatically perform an SQL query to the database. Note that this changes only the table in the database, not the model in the JavaScript side. + +* `User.sync()` - This creates the table if it doesn't exist (and does nothing if it already exists) +* `User.sync({ force: true })` - This creates the table, dropping it first if it already existed +* `User.sync({ alter: true })` - This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model. + +Example: + +```js +await User.sync({ force: true }); +console.log("The table for the User model was just (re)created!"); +``` + +### Synchronizing all models at once + +You can use [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync) to automatically synchronize all models. Example: + +```js +await sequelize.sync({ force: true }); +console.log("All models were synchronized successfully."); +``` + +### Dropping tables + +To drop the table related to a model: + +```js +await User.drop(); +console.log("User table dropped!"); +``` + +To drop all tables: + +```js +await sequelize.drop(); +console.log("All tables dropped!"); +``` + +### Database safety check + +As shown above, the `sync` and `drop` operations are destructive. Sequelize accepts a `match` option as an additional safety check, which receives a RegExp: + +```js +// This will run .sync() only if database name ends with '_test' +sequelize.sync({ force: true, match: /_test$/ }); +``` + +### Synchronization in production + +As shown above, `sync({ force: true })` and `sync({ alter: true })` can be destructive operations. Therefore, they are not recommended for production-level software. Instead, synchronization should be done with the advanced concept of [Migrations](migrations.html), with the help of the [Sequelize CLI](https://github.com/sequelize/cli). + +## Timestamps + +By default, Sequelize automatically adds the fields `createdAt` and `updatedAt` to every model, using the data type `DataTypes.DATE`. Those fields are automatically managed as well - whenever you use Sequelize to create or update something, those fields will be set correctly. The `createdAt` field will contain the timestamp representing the moment of creation, and the `updatedAt` will contain the timestamp of the latest update. + +**Note:** This is done in the Sequelize level (i.e. not done with *SQL triggers*). This means that direct SQL queries (for example queries performed without Sequelize by any other means) will not cause these fields to be updated automatically. + +This behavior can be disabled for a model with the `timestamps: false` option: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + timestamps: false +}); +``` + +It is also possible to enable only one of `createdAt`/`updatedAt`, and to provide a custom name for these columns: + +```js +class Foo extends Model {} +Foo.init({ /* attributes */ }, { + sequelize, + + // don't forget to enable timestamps! + timestamps: true, + + // I don't want createdAt + createdAt: false, + + // I want updatedAt to actually be called updateTimestamp + updatedAt: 'updateTimestamp' +}); +``` + +## Column declaration shorthand syntax + +If the only thing being specified about a column is its data type, the syntax can be shortened: + +```js +// This: +sequelize.define('User', { + name: { + type: DataTypes.STRING + } +}); + +// Can be simplified to: +sequelize.define('User', { name: DataTypes.STRING }); +``` + +## Default Values + +By default, Sequelize assumes that the default value of a column is `NULL`. This behavior can be changed by passing a specific `defaultValue` to the column definition: + +```js +sequelize.define('User', { + name: { + type: DataTypes.STRING, + defaultValue: "John Doe" + } +}); +``` + +Some special values, such as `Sequelize.NOW`, are also accepted: + +```js +sequelize.define('Foo', { + bar: { + type: DataTypes.DATETIME, + defaultValue: Sequelize.NOW + // This way, the current date/time will be used to populate this column (at the moment of insertion) + } +}); +``` + +## Data Types + +Every column you define in your model must have a data type. Sequelize provides [a lot of built-in data types](https://github.com/sequelize/sequelize/blob/main/lib/data-types.js). To access a built-in data type, you must import `DataTypes`: + +```js +const { DataTypes } = require("sequelize"); // Import the built-in data types +``` + +### Strings + +```js +DataTypes.STRING // VARCHAR(255) +DataTypes.STRING(1234) // VARCHAR(1234) +DataTypes.STRING.BINARY // VARCHAR BINARY +DataTypes.TEXT // TEXT +DataTypes.TEXT('tiny') // TINYTEXT +DataTypes.CITEXT // CITEXT PostgreSQL and SQLite only. +``` + +### Boolean + +```js +DataTypes.BOOLEAN // TINYINT(1) +``` + +### Numbers + +```js +DataTypes.INTEGER // INTEGER +DataTypes.BIGINT // BIGINT +DataTypes.BIGINT(11) // BIGINT(11) + +DataTypes.FLOAT // FLOAT +DataTypes.FLOAT(11) // FLOAT(11) +DataTypes.FLOAT(11, 10) // FLOAT(11,10) + +DataTypes.REAL // REAL PostgreSQL only. +DataTypes.REAL(11) // REAL(11) PostgreSQL only. +DataTypes.REAL(11, 12) // REAL(11,12) PostgreSQL only. + +DataTypes.DOUBLE // DOUBLE +DataTypes.DOUBLE(11) // DOUBLE(11) +DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) + +DataTypes.DECIMAL // DECIMAL +DataTypes.DECIMAL(10, 2) // DECIMAL(10,2) +``` + +#### Unsigned & Zerofill integers - MySQL/MariaDB only + +In MySQL and MariaDB, the data types `INTEGER`, `BIGINT`, `FLOAT` and `DOUBLE` can be set as unsigned or zerofill (or both), as follows: + +```js +DataTypes.INTEGER.UNSIGNED +DataTypes.INTEGER.ZEROFILL +DataTypes.INTEGER.UNSIGNED.ZEROFILL +// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER +// Same for BIGINT, FLOAT and DOUBLE +``` + +### Dates + +```js +DataTypes.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres +DataTypes.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision +DataTypes.DATEONLY // DATE without time +``` + +### UUIDs + +For UUIDs, use `DataTypes.UUID`. It becomes the `UUID` data type for PostgreSQL and SQLite, and `CHAR(36)` for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use `Sequelize.UUIDV1` or `Sequelize.UUIDV4` as the default value: + +```js +{ + type: DataTypes.UUID, + defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1 +} +``` + +### Others + +There are other data types, covered in a [separate guide](other-data-types.html). + +## Column Options + +When defining a column, apart from specifying the `type` of the column, and the `allowNull` and `defaultValue` options mentioned above, there are a lot more options that can be used. Some examples are below. + +```js +const { Model, DataTypes, Deferrable } = require("sequelize"); + +class Foo extends Model {} +Foo.init({ + // instantiating will automatically set the flag to true if not set + flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, + + // default values for dates => current time + myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + + // setting allowNull to false will add NOT NULL to the column, which means an error will be + // thrown from the DB when the query is executed if the column is null. If you want to check that a value + // is not null before querying the DB, look at the validations section below. + title: { type: DataTypes.STRING, allowNull: false }, + + // Creating two objects with the same value will throw an error. The unique property can be either a + // boolean, or a string. If you provide the same string for multiple columns, they will form a + // composite unique key. + uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' }, + uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' }, + + // The unique property is simply a shorthand to create a unique constraint. + someUnique: { type: DataTypes.STRING, unique: true }, + + // Go on reading for further information about primary keys + identifier: { type: DataTypes.STRING, primaryKey: true }, + + // autoIncrement can be used to create auto_incrementing integer columns + incrementMe: { type: DataTypes.INTEGER, autoIncrement: true }, + + // You can specify a custom column name via the 'field' attribute: + fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' }, + + // It is possible to create foreign keys: + bar_id: { + type: DataTypes.INTEGER, + + references: { + // This is a reference to another model + model: Bar, + + // This is the column name of the referenced model + key: 'id', + + // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type. + deferrable: Deferrable.INITIALLY_IMMEDIATE + // Options: + // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints + // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction + // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction + } + }, + + // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL + commentMe: { + type: DataTypes.INTEGER, + comment: 'This is a column name that has a comment' + } +}, { + sequelize, + modelName: 'foo', + + // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options: + indexes: [{ unique: true, fields: ['someUnique'] }] +}); +``` + +## Taking advantage of Models being classes + +The Sequelize models are [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). You can very easily add custom instance or class level methods. + +```js +class User extends Model { + static classLevelMethod() { + return 'foo'; + } + instanceLevelMethod() { + return 'bar'; + } + getFullname() { + return [this.firstname, this.lastname].join(' '); + } +} +User.init({ + firstname: Sequelize.TEXT, + lastname: Sequelize.TEXT +}, { sequelize }); + +console.log(User.classLevelMethod()); // 'foo' +const user = User.build({ firstname: 'Jane', lastname: 'Doe' }); +console.log(user.instanceLevelMethod()); // 'bar' +console.log(user.getFullname()); // 'Jane Doe' +``` diff --git a/docs/manual/core-concepts/raw-queries.md b/docs/manual/core-concepts/raw-queries.md index 8172d2bfddb3..ff18dffcf8d6 100644 --- a/docs/manual/core-concepts/raw-queries.md +++ b/docs/manual/core-concepts/raw-queries.md @@ -17,7 +17,7 @@ const users = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes. // We didn't need to destructure the result here - the results were returned directly ``` -Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/src/query-types.ts). +Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/main/src/query-types.ts). A second option is the model. If you pass a model the returned data will be instances of that model. diff --git a/docs/manual/other-topics/legal.md b/docs/manual/other-topics/legal.md index a78f37cab681..0d8d4b428b2a 100644 --- a/docs/manual/other-topics/legal.md +++ b/docs/manual/other-topics/legal.md @@ -2,7 +2,7 @@ ## License -Sequelize library is distributed with MIT license. You can find original license [here.](https://github.com/sequelize/sequelize/blob/master/LICENSE) +Sequelize library is distributed with MIT license. You can find original license [here.](https://github.com/sequelize/sequelize/blob/main/LICENSE) ```text MIT License diff --git a/docs/manual/other-topics/migrations.md b/docs/manual/other-topics/migrations.md index 879c4fb1f0cc..9a36c8c180c0 100644 --- a/docs/manual/other-topics/migrations.md +++ b/docs/manual/other-topics/migrations.md @@ -454,7 +454,7 @@ module.exports = { dialectOptions: { bigNumberStrings: true, ssl: { - ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') + ca: fs.readFileSync(__dirname + '/mysql-ca-main.crt') } } } diff --git a/docs/manual/other-topics/read-replication.md b/docs/manual/other-topics/read-replication.md index 2fe8fdd90b71..95b7a67d9e57 100644 --- a/docs/manual/other-topics/read-replication.md +++ b/docs/manual/other-topics/read-replication.md @@ -1,29 +1,29 @@ -# Read Replication - -Sequelize supports [read replication](https://en.wikipedia.org/wiki/Replication_%28computing%29#Database_replication), i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the write master, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). - -```js -const sequelize = new Sequelize('database', null, null, { - dialect: 'mysql', - port: 3306 - replication: { - read: [ - { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, - { host: '9.9.9.9', username: 'read-2-username', password: process.env.READ_DB_2_PW } - ], - write: { host: '1.1.1.1', username: 'write-username', password: process.env.WRITE_DB_PW } - }, - pool: { // If you want to override the options used for the read/write pool you can do so here - max: 20, - idle: 30000 - }, -}) -``` - -If you have any general settings that apply to all replicas you do not need to provide them for each instance. In the code above, database name and port is propagated to all replicas. The same will happen for user and password, if you leave them out for any of the replicas. Each replica has the following options:`host`,`port`,`username`,`password`,`database`. - -Sequelize uses a pool to manage connections to your replicas. Internally Sequelize will maintain two pools created using `pool` configuration. - -If you want to modify these, you can pass pool as an options when instantiating Sequelize, as shown above. - -Each `write` or `useMaster: true` query will use write pool. For `SELECT` read pool will be used. Read replica are switched using a basic round robin scheduling. \ No newline at end of file +# Read Replication + +Sequelize supports [read replication](https://en.wikipedia.org/wiki/Replication_%28computing%29#Database_replication), i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the main writer, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). + +```js +const sequelize = new Sequelize('database', null, null, { + dialect: 'mysql', + port: 3306 + replication: { + read: [ + { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, + { host: '9.9.9.9', username: 'read-2-username', password: process.env.READ_DB_2_PW } + ], + write: { host: '1.1.1.1', username: 'write-username', password: process.env.WRITE_DB_PW } + }, + pool: { // If you want to override the options used for the read/write pool you can do so here + max: 20, + idle: 30000 + }, +}) +``` + +If you have any general settings that apply to all replicas you do not need to provide them for each instance. In the code above, database name and port is propagated to all replicas. The same will happen for user and password, if you leave them out for any of the replicas. Each replica has the following options:`host`,`port`,`username`,`password`,`database`. + +Sequelize uses a pool to manage connections to your replicas. Internally Sequelize will maintain two pools created using `pool` configuration. + +If you want to modify these, you can pass pool as an options when instantiating Sequelize, as shown above. + +Each `write` or `useMaster: true` query will use write pool. For `SELECT` read pool will be used. Read replica are switched using a basic round robin scheduling. diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md index 35a3ee688ab3..fd047d5cd694 100644 --- a/docs/manual/other-topics/upgrade-to-v6.md +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -22,7 +22,7 @@ Sequelize.useCLS(namespace); ### Database Engine Support -We have updated our minimum supported database engine versions. Using older database engine will show `SEQUELIZE0006` deprecation warning. Please check [ENGINE.md](https://github.com/sequelize/sequelize/blob/master/ENGINE.md) for version table. +We have updated our minimum supported database engine versions. Using older database engine will show `SEQUELIZE0006` deprecation warning. Please check [ENGINE.md](https://github.com/sequelize/sequelize/blob/main/ENGINE.md) for version table. ### Sequelize diff --git a/lib/dialects/mssql/data-types.js b/lib/dialects/mssql/data-types.js index 9194051a072f..4aeb6a495890 100644 --- a/lib/dialects/mssql/data-types.js +++ b/lib/dialects/mssql/data-types.js @@ -24,7 +24,7 @@ module.exports = BaseTypes => { /** * types: [hex, ...] * - * @see hex here https://github.com/tediousjs/tedious/blob/master/src/data-type.js + * @see hex here https://github.com/tediousjs/tedious/blob/master/src/data-type.ts */ BaseTypes.DATE.types.mssql = [43]; diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index d42f0d54b24f..0cf2f793743d 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -99,7 +99,7 @@ class ConnectionManager extends AbstractConnectionManager { // see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME] 'application_name', // choose the SSL mode with the PGSSLMODE environment variable - // object format: [https://github.com/brianc/node-postgres/blob/master/lib/connection.js#L79] + // object format: [https://github.com/brianc/node-postgres/blob/ee19e74ffa6309c9c5e8e01746261a8f651661f8/lib/connection.js#L79] // see also [http://www.postgresql.org/docs/9.3/static/libpq-ssl.html] 'ssl', // In addition to the values accepted by the corresponding server, diff --git a/lib/utils/deprecations.js b/lib/utils/deprecations.js index 10e231720a85..ac9d62216c71 100644 --- a/lib/utils/deprecations.js +++ b/lib/utils/deprecations.js @@ -9,4 +9,4 @@ exports.noTrueLogging = deprecate(noop, 'The logging-option should be either a f exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://sequelize.org/master/manual/querying.html#operators', 'SEQUELIZE0003'); exports.noBoolOperatorAliases = deprecate(noop, 'A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.', 'SEQUELIZE0004'); exports.noDoubleNestedGroup = deprecate(noop, 'Passing a double nested nested array to `group` is unsupported and will be removed in v6.', 'SEQUELIZE0005'); -exports.unsupportedEngine = deprecate(noop, 'This database engine version is not supported, please update your database server. More information https://github.com/sequelize/sequelize/blob/master/ENGINE.md', 'SEQUELIZE0006'); +exports.unsupportedEngine = deprecate(noop, 'This database engine version is not supported, please update your database server. More information https://github.com/sequelize/sequelize/blob/main/ENGINE.md', 'SEQUELIZE0006'); diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index 1c4ce5faf95c..05214e9d8608 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -130,12 +130,12 @@ describe(Support.getTestDialectTeaser('Connection Manager'), () => { it('should allow forced reads from the write pool', async () => { - const master = { ...poolEntry }; - master.host = 'the-boss'; + const main = { ...poolEntry }; + main.host = 'the-boss'; const options = { replication: { - write: master, + write: main, read: [{ ...poolEntry }] } }; From 914279aa0dffa1f6ca40ab3d9f86ea5fa5e7d561 Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 18:57:47 -0300 Subject: [PATCH 239/414] test(types): refactor adding `expect-type` Closes #11829 --- package.json | 4 +- types/test/connection.ts | 30 ++++++------ types/test/count.ts | 17 +++++-- types/test/data-types.ts | 56 +++++++++++----------- types/test/define.ts | 29 ++++++------ types/test/errors.ts | 63 +++---------------------- types/test/{findById.ts => findByPk.ts} | 0 types/test/findOne.ts | 7 ++- types/test/index.d.ts | 3 -- types/test/model.ts | 21 +++++---- 10 files changed, 93 insertions(+), 137 deletions(-) rename types/test/{findById.ts => findByPk.ts} (100%) delete mode 100644 types/test/index.d.ts diff --git a/package.json b/package.json index 88e06a719048..bf7c549b3e5c 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "cls-hooked": "^4.2.2", "cross-env": "^7.0.2", "delay": "^4.3.0", - "dtslint": "^3.6.4", "env-cmd": "^10.1.0", "esdoc": "^1.1.0", "esdoc-ecmascript-proposal-plugin": "^1.0.0", @@ -67,6 +66,7 @@ "eslint": "^6.8.0", "eslint-plugin-jsdoc": "^20.4.0", "eslint-plugin-mocha": "^6.2.2", + "expect-type": "^0.11.0", "fs-jetpack": "^4.1.0", "husky": "^4.2.5", "js-combinatorics": "^0.5.5", @@ -185,7 +185,7 @@ "test-postgresn": "npm run test-postgres-native", "test-mssql": "cross-env DIALECT=mssql npm test", "test-all": "npm run test-mariadb && npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql", - "test-typings": "tsc -b types/tsconfig.json && dtslint --expectOnly types/test", + "test-typings": "tsc -b types/tsconfig.json && tsc -b types/test/tsconfig.json", "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha -t 30000 --exit \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha -t 30000 --exit \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", diff --git a/types/test/connection.ts b/types/test/connection.ts index 3c282068f74a..5c2006fb053a 100644 --- a/types/test/connection.ts +++ b/types/test/connection.ts @@ -1,3 +1,4 @@ +import { expectTypeOf } from "expect-type"; import { QueryTypes, Sequelize, SyncOptions } from 'sequelize'; import { User } from 'models/User'; @@ -8,22 +9,18 @@ sequelize.afterBulkSync((options: SyncOptions) => { }); async function test() { - const rows: unknown[] = await sequelize - .query('SELECT * FROM `test`', { - type: QueryTypes.SELECT, - }); - const [autoIncrementId, affectedRows] = await sequelize - .query('INSERT into test set test=1', { - type: QueryTypes.INSERT, - }); -} - - + expectTypeOf( + await sequelize.query('SELECT * FROM `test`', { type: QueryTypes.SELECT }) + ).toEqualTypeOf(); + expectTypeOf( + await sequelize.query('INSERT into test set test=1', { type: QueryTypes.INSERT }) + ).toEqualTypeOf<[number, number]>(); +} sequelize.transaction(async transaction => { - const rows = await sequelize - .query('SELECT * FROM `user`', { + expectTypeOf( + await sequelize.query('SELECT * FROM `user`', { retry: { max: 123, }, @@ -31,12 +28,15 @@ sequelize.transaction(async transaction => { transaction, logging: true, }) + ).toEqualTypeOf(); }); -sequelize.query('SELECT * FROM `user` WHERE status = $1', +sequelize.query( + 'SELECT * FROM `user` WHERE status = $1', { bind: ['active'], type: QueryTypes.SELECT } ); -sequelize.query('SELECT * FROM `user` WHERE status = $status', +sequelize.query( + 'SELECT * FROM `user` WHERE status = $status', { bind: { status: 'active' }, type: QueryTypes.SELECT } ); diff --git a/types/test/count.ts b/types/test/count.ts index 0892c3a7e402..57607f4e1f14 100644 --- a/types/test/count.ts +++ b/types/test/count.ts @@ -1,9 +1,16 @@ +import { expectTypeOf } from "expect-type"; import { Model, Op } from 'sequelize'; class MyModel extends Model {} -const grouped: Promise<{ [key: string]: number }> = MyModel.count({ group: 'tag' }); -const counted: Promise = MyModel.count(); -const countedDistinct: Promise = MyModel.count({ col: 'tag', distinct: true }); - -const countedDistinctOnReader: Promise = MyModel.count({ where: { updatedAt: { [Op.gte]: new Date() } }, useMaster: false }) +expectTypeOf(MyModel.count()).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ group: 'tag' })).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ col: 'tag', distinct: true })).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ + where: { + updatedAt: { + [Op.gte]: new Date() + } + }, + useMaster: false +})).toEqualTypeOf>(); diff --git a/types/test/data-types.ts b/types/test/data-types.ts index fe991f8e7a3c..01d463f2135b 100644 --- a/types/test/data-types.ts +++ b/types/test/data-types.ts @@ -1,32 +1,34 @@ -import { INTEGER, IntegerDataType, TINYINT } from 'sequelize'; -import { SmallIntegerDataType, SMALLINT, MEDIUMINT, MediumIntegerDataType, BigIntDataType, BIGINT } from '../lib/data-types'; +import { expectTypeOf } from 'expect-type'; +import { DataTypes } from 'sequelize'; -let tinyint: IntegerDataType; -tinyint = TINYINT(); -tinyint = new TINYINT(); -tinyint = TINYINT.UNSIGNED.ZEROFILL(); -tinyint = new TINYINT.UNSIGNED.ZEROFILL(); +const { TINYINT, SMALLINT, MEDIUMINT, BIGINT, INTEGER } = DataTypes; -let smallint: SmallIntegerDataType; -smallint = SMALLINT(); -smallint = new SMALLINT(); -smallint = SMALLINT.UNSIGNED.ZEROFILL(); -smallint = new SMALLINT.UNSIGNED.ZEROFILL(); +// TINYINT +expectTypeOf(TINYINT()).toEqualTypeOf(); +expectTypeOf(new TINYINT()).toEqualTypeOf(); +expectTypeOf(TINYINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new TINYINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -let mediumint: MediumIntegerDataType; -mediumint = MEDIUMINT(); -mediumint = new MEDIUMINT(); -mediumint = MEDIUMINT.UNSIGNED.ZEROFILL(); -mediumint = new MEDIUMINT.UNSIGNED.ZEROFILL(); +// SMALLINT +expectTypeOf(SMALLINT()).toEqualTypeOf(); +expectTypeOf(new SMALLINT()).toEqualTypeOf(); +expectTypeOf(SMALLINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new SMALLINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -let int: IntegerDataType; -int = INTEGER(); -int = new INTEGER(); -int = INTEGER.UNSIGNED.ZEROFILL(); -int = new INTEGER.UNSIGNED.ZEROFILL(); +// MEDIUMINT +expectTypeOf(MEDIUMINT()).toEqualTypeOf(); +expectTypeOf(new MEDIUMINT()).toEqualTypeOf(); +expectTypeOf(MEDIUMINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new MEDIUMINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -let bigint: BigIntDataType; -bigint = BIGINT(); -bigint = new BIGINT(); -bigint = BIGINT.UNSIGNED.ZEROFILL(); -bigint = new BIGINT.UNSIGNED.ZEROFILL(); +// BIGINT +expectTypeOf(BIGINT()).toEqualTypeOf(); +expectTypeOf(new BIGINT()).toEqualTypeOf(); +expectTypeOf(BIGINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new BIGINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); + +// INTEGER +expectTypeOf(INTEGER()).toEqualTypeOf(); +expectTypeOf(new INTEGER()).toEqualTypeOf(); +expectTypeOf(INTEGER.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new INTEGER.UNSIGNED.ZEROFILL()).toEqualTypeOf(); diff --git a/types/test/define.ts b/types/test/define.ts index 50bc194ed74d..c4dc10e1e48f 100644 --- a/types/test/define.ts +++ b/types/test/define.ts @@ -1,3 +1,4 @@ +import { expectTypeOf } from 'expect-type'; import { BuildOptions, DataTypes, Model, Optional } from 'sequelize'; import { sequelize } from './connection'; @@ -12,9 +13,7 @@ interface UserAttributes { interface UserCreationAttributes extends Optional {} -interface UserModel - extends Model, - UserAttributes {} +interface UserModel extends Model, UserAttributes {} const User = sequelize.define( 'User', @@ -28,14 +27,14 @@ const User = sequelize.define( ); async function test() { - const user: UserModel = new User() as UserModel; + expectTypeOf().toMatchTypeOf(new User()); - const user2: UserModel | null = await User.findOne(); - if (!user2) return; + const user = await User.findOne(); + expectTypeOf(user).toEqualTypeOf(); - user2.firstName = 'John'; - - await user2.save(); + if (!user) return; + user.firstName = 'John'; + await user.save(); } // The below doesn't define Attribute types, but should still work @@ -61,12 +60,12 @@ UntypedUser.customStaticMethod = () => {}; async function testUntyped() { UntypedUser.customStaticMethod(); - const user: UntypedUserModel = new UntypedUser() as UntypedUserModel; - - const user2: UntypedUserModel | null = await UntypedUser.findOne(); - if (!user2) return; + expectTypeOf().toMatchTypeOf(new UntypedUser()); - user2.firstName = 'John'; + const user = await UntypedUser.findOne(); + expectTypeOf(user).toEqualTypeOf(); - await user2.save(); + if (!user) return; + user.firstName = 'John'; + await user.save(); } diff --git a/types/test/errors.ts b/types/test/errors.ts index c1de7ab5a46c..0a938a37f264 100644 --- a/types/test/errors.ts +++ b/types/test/errors.ts @@ -1,58 +1,9 @@ -// Error === BaseError -import { BaseError, EmptyResultError, Error, UniqueConstraintError } from 'sequelize'; -import { User } from './models/User'; +import { expectTypeOf } from "expect-type"; +import { BaseError, EmptyResultError, Error as AliasedBaseError, UniqueConstraintError } from 'sequelize'; import { OptimisticLockError } from '../lib/errors'; -async function test() { - try { - await User.create({ username: 'john_doe', firstName: 'John' }); - } catch (e) { - if (e instanceof UniqueConstraintError) { - throw new Error((e as UniqueConstraintError).sql); - } - } - - try { - await User.findOne({ - rejectOnEmpty: true, - where: { - username: 'something_that_doesnt_exist', - }, - }); - } catch (e) { - if (!(e instanceof EmptyResultError)) { - throw new Error('should return emptyresulterror'); - } - } - - - class CustomError extends Error {} - - try { - await User.findOne({ - rejectOnEmpty: new CustomError('User does not exist'), - where: { - username: 'something_that_doesnt_exist', - }, - }); - } catch (e) { - if (!(e instanceof CustomError)) { - throw new Error('should return CustomError'); - } - if (e.message !== 'User does not exist') { - throw new Error('should return CustomError with the proper message') - } - } - - try { - const user: User | null = await User.findByPk(1); - if (user != null) { - user.username = 'foo'; - user.save(); - } - } catch (e) { - if (!(e instanceof OptimisticLockError)) { - throw new Error('should return OptimisticLockError'); - } - } -} +expectTypeOf().toEqualTypeOf(); +expectTypeOf().toHaveProperty('sql').toBeString(); +expectTypeOf().toMatchTypeOf(); +expectTypeOf().toMatchTypeOf(); +expectTypeOf().toMatchTypeOf(); diff --git a/types/test/findById.ts b/types/test/findByPk.ts similarity index 100% rename from types/test/findById.ts rename to types/test/findByPk.ts diff --git a/types/test/findOne.ts b/types/test/findOne.ts index 3b3f4e675aa0..1a71a0647d9f 100644 --- a/types/test/findOne.ts +++ b/types/test/findOne.ts @@ -1,7 +1,6 @@ import { User } from "./models/User"; -User.findOne({ where: { firstName: 'John' }}); +User.findOne({ where: { firstName: 'John' } }); -// The below line should be an error if uncommented, thanks to the new -// TAttributes-based typechecking -// User.findOne({ where: { blah: 'blah2' }}); +// @ts-expect-error +User.findOne({ where: { blah: 'blah2' } }); diff --git a/types/test/index.d.ts b/types/test/index.d.ts deleted file mode 100644 index c6b77f4ff9b4..000000000000 --- a/types/test/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Controls typescript version for testing the types - -// TypeScript Version: 3.6 diff --git a/types/test/model.ts b/types/test/model.ts index 439eeeee514f..4036eb1a530a 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -1,5 +1,7 @@ +import { expectTypeOf } from "expect-type"; import { Association, BelongsToManyGetAssociationsMixin, DataTypes, HasOne, Model, Sequelize } from 'sequelize'; +expectTypeOf().toMatchTypeOf(); class MyModel extends Model { public num!: number; public static associations: { @@ -12,10 +14,9 @@ class MyModel extends Model { class OtherModel extends Model {} -const assoc: Association = MyModel.associations.other; - const Instance: MyModel = new MyModel({ int: 10 }); -const num: number = Instance.get('num'); + +expectTypeOf(Instance.get('num')).toEqualTypeOf(); MyModel.findOne({ include: [ @@ -102,7 +103,7 @@ UserModel.findCreateFind({ * Tests for findOrCreate() type. */ - UserModel.findOrCreate({ +UserModel.findOrCreate({ fields: [ "jane.doe" ], where: { username: "jane.doe" @@ -122,13 +123,13 @@ TestModel.primaryKeyAttributes; * Test for joinTableAttributes on BelongsToManyGetAssociationsMixin */ class SomeModel extends Model { - public getOthers!: BelongsToManyGetAssociationsMixin + public getOthers!: BelongsToManyGetAssociationsMixin } -const someInstance = new SomeModel() +const someInstance = new SomeModel(); someInstance.getOthers({ - joinTableAttributes: { include: [ 'id' ] } -}) + joinTableAttributes: { include: ['id'] } +}); /** * Test for through options in creating a BelongsToMany association @@ -142,11 +143,11 @@ Film.belongsToMany(Actor, { model: 'FilmActors', paranoid: true } -}) +}); Actor.belongsToMany(Film, { through: { model: 'FilmActors', paranoid: true } -}) +}); From 287607a03ad5dc511fbd65c48ec72e397224c1ff Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 18:58:26 -0300 Subject: [PATCH 240/414] fix(types): better support for readonly arrays --- types/lib/hooks.d.ts | 6 +- types/lib/model.d.ts | 164 +++++++-------- types/test/hooks.ts | 62 +++--- types/test/where.ts | 470 +++++++++++++++++++------------------------ 4 files changed, 317 insertions(+), 385 deletions(-) diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index 228de7f9efde..e3ea9f76955a 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -40,8 +40,8 @@ export interface ModelHooks { instance: M, options: InstanceUpdateOptions | CreateOptions ): HookReturn; - beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; - afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; + beforeBulkCreate(instances: readonly M[], options: BulkCreateOptions): HookReturn; + afterBulkCreate(instances: readonly M[], options: BulkCreateOptions): HookReturn; beforeBulkDestroy(options: DestroyOptions): HookReturn; afterBulkDestroy(options: DestroyOptions): HookReturn; beforeBulkRestore(options: RestoreOptions): HookReturn; @@ -52,7 +52,7 @@ export interface ModelHooks { beforeCount(options: CountOptions): HookReturn; beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; beforeFindAfterOptions(options: FindOptions): HookReturn; - afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn; + afterFind(instancesOrInstance: readonly M[] | M | null, options: FindOptions): HookReturn; beforeSync(options: SyncOptions): HookReturn; afterSync(options: SyncOptions): HookReturn; beforeBulkSync(options: SyncOptions): HookReturn; diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index cad5ff3eb066..29c3f16c86cf 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -70,7 +70,7 @@ export interface Paranoid { paranoid?: boolean; } -export type GroupOption = string | Fn | Col | (string | Fn | Col)[]; +export type GroupOption = string | Fn | Col | readonly (string | Fn | Col)[]; /** * Options to pass to Model on drop @@ -103,7 +103,7 @@ export interface ScopeOptions { * any arguments, or an array, where the first element is the name of the method, and consecutive elements * are arguments to that method. Pass null to remove all scopes, including the default. */ - method: string | [string, ...unknown[]]; + method: string | readonly [string, ...unknown[]]; } /** @@ -123,15 +123,15 @@ export type WhereOptions = * _PG only_ */ export interface AnyOperator { - [Op.any]: (string | number)[]; + [Op.any]: readonly (string | number)[]; } /** Undocumented? */ export interface AllOperator { - [Op.all]: (string | number | Date | Literal)[]; + [Op.all]: readonly (string | number | Date | Literal)[]; } -export type Rangable = [number, number] | [Date, Date] | Literal; +export type Rangable = readonly [number, number] | readonly [Date, Date] | Literal; /** * Operators that can be used in WhereOptions @@ -144,7 +144,7 @@ export interface WhereOperators { * * _PG only_ */ - [Op.any]?: (string | number | Literal)[] | Literal; + [Op.any]?: readonly (string | number | Literal)[] | Literal; /** Example: `[Op.gte]: 6,` becomes `>= 6` */ [Op.gte]?: number | string | Date | Literal; @@ -165,10 +165,10 @@ export interface WhereOperators { [Op.between]?: Rangable; /** Example: `[Op.in]: [1, 2],` becomes `IN [1, 2]` */ - [Op.in]?: (string | number | Literal)[] | Literal; + [Op.in]?: readonly (string | number | Literal)[] | Literal; /** Example: `[Op.notIn]: [1, 2],` becomes `NOT IN [1, 2]` */ - [Op.notIn]?: (string | number | Literal)[] | Literal; + [Op.notIn]?: readonly (string | number | Literal)[] | Literal; /** * Examples: @@ -205,14 +205,14 @@ export interface WhereOperators { * * Example: `[Op.contains]: [1, 2]` becomes `@> [1, 2]` */ - [Op.contains]?: (string | number)[] | Rangable; + [Op.contains]?: readonly (string | number)[] | Rangable; /** * PG array contained by operator * * Example: `[Op.contained]: [1, 2]` becomes `<@ [1, 2]` */ - [Op.contained]?: (string | number)[] | Rangable; + [Op.contained]?: readonly (string | number)[] | Rangable; /** Example: `[Op.gt]: 6,` becomes `> 6` */ [Op.gt]?: number | string | Date | Literal; @@ -311,12 +311,12 @@ export interface WhereOperators { /** Example: `[Op.or]: [{a: 5}, {a: 6}]` becomes `(a = 5 OR a = 6)` */ export interface OrOperator { - [Op.or]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; + [Op.or]: WhereOptions | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; } /** Example: `[Op.and]: {a: 5}` becomes `AND (a = 5)` */ export interface AndOperator { - [Op.and]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; + [Op.and]: WhereOptions | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; } /** @@ -324,7 +324,7 @@ export interface AndOperator { */ export interface WhereGeometryOptions { type: string; - coordinates: (number[] | number)[]; + coordinates: readonly (number[] | number)[]; } /** @@ -345,7 +345,7 @@ export type WhereValue = | OrOperator | AndOperator | WhereGeometryOptions - | (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] + | readonly (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] /** * A hash of attributes to describe your search. @@ -444,7 +444,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * Load further nested related models */ - include?: Includeable[]; + include?: readonly Includeable[]; /** * Order include. Only available when setting `separate` to true. @@ -464,44 +464,44 @@ export type OrderItem = | Fn | Col | Literal - | [OrderItemColumn, string] - | [OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemColumn, string] - | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] -export type Order = string | Fn | Col | Literal | OrderItem[]; + | readonly [OrderItemColumn, string] + | readonly [OrderItemAssociation, OrderItemColumn] + | readonly [OrderItemAssociation, OrderItemColumn, string] + | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] +export type Order = string | Fn | Col | Literal | readonly OrderItem[]; /** * Please note if this is used the aliased property will not be available on the model instance * as a property but only via `instance.get('alias')`. */ -export type ProjectionAlias = [string | Literal | Fn, string]; +export type ProjectionAlias = readonly [string | Literal | Fn, string]; export type FindAttributeOptions = - | (string | ProjectionAlias)[] + | readonly (string | ProjectionAlias)[] | { - exclude: string[]; - include?: (string | ProjectionAlias)[]; + exclude: readonly string[]; + include?: readonly (string | ProjectionAlias)[]; } | { - exclude?: string[]; - include: (string | ProjectionAlias)[]; + exclude?: readonly string[]; + include: readonly (string | ProjectionAlias)[]; }; export interface IndexHint { type: IndexHints; - values: string[]; + values: readonly string[]; } export interface IndexHintable { /** * MySQL only. */ - indexHints?: IndexHint[]; + indexHints?: readonly IndexHint[]; } type Omit = Pick> @@ -521,7 +521,7 @@ export interface FindOptions * If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in * the as attribute when eager loading Y). */ - include?: Includeable | Includeable[]; + include?: Includeable | readonly Includeable[]; /** * Specifies an ordering. If a string is provided, it will be escaped. Using an array, you can provide @@ -592,7 +592,7 @@ export interface CountOptions /** * Include options. See `find` for details */ - include?: Includeable | Includeable[]; + include?: Includeable | readonly Includeable[]; /** * Apply COUNT(DISTINCT(col)) @@ -645,7 +645,7 @@ export interface BuildOptions { * * TODO: See set */ - include?: Includeable | Includeable[]; + include?: Includeable | readonly Includeable[]; } export interface Silent { @@ -664,7 +664,7 @@ export interface CreateOptions extends BuildOptions, Logging, /** * If set, only columns matching those in fields will be saved */ - fields?: (keyof TAttributes)[]; + fields?: readonly (keyof TAttributes)[]; /** * On Duplicate @@ -699,7 +699,7 @@ export interface FindOrCreateOptions extends Logging, Transactionab /** * The fields to insert / update. Defaults to all fields */ - fields?: (keyof TAttributes)[]; + fields?: readonly (keyof TAttributes)[]; /** * Return the affected rows (only for postgres) @@ -733,7 +733,7 @@ export interface BulkCreateOptions extends Logging, Transacti /** * Fields to insert (defaults to all fields) */ - fields?: (keyof TAttributes)[]; + fields?: readonly (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -758,17 +758,17 @@ export interface BulkCreateOptions extends Logging, Transacti * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. */ - updateOnDuplicate?: (keyof TAttributes)[]; + updateOnDuplicate?: readonly (keyof TAttributes)[]; /** * Include options. See `find` for details */ - include?: Includeable | Includeable[]; + include?: Includeable | readonly Includeable[]; /** * Return all columns or only the specified columns for the affected rows (only for postgres) */ - returning?: boolean | (keyof TAttributes)[]; + returning?: boolean | readonly (keyof TAttributes)[]; } /** @@ -846,7 +846,7 @@ export interface UpdateOptions extends Logging, Transactionab /** * Fields to update (defaults to all fields) */ - fields?: (keyof TAttributes)[]; + fields?: readonly (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -969,7 +969,7 @@ export interface SaveOptions extends Logging, Transactionable * An optional array of strings, representing database columns. If fields is provided, only those columns * will be validated and saved. */ - fields?: (keyof TAttributes)[]; + fields?: readonly (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -990,15 +990,15 @@ export interface SaveOptions extends Logging, Transactionable */ export interface ModelValidateOptions { /** - * is: ["^[a-z]+$",'i'] // will only allow letters - * is: /^[a-z]+[Op./i] // same as the previous example using real RegExp + * - `{ is: ['^[a-z]+$','i'] }` will only allow letters + * - `{ is: /^[a-z]+$/i }` also only allows letters */ - is?: string | (string | RegExp)[] | RegExp | { msg: string; args: string | (string | RegExp)[] | RegExp }; + is?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; /** - * not: ["[a-z]",'i'] // will not allow letters + * - `{ not: ['[a-z]','i'] }` will not allow letters */ - not?: string | (string | RegExp)[] | RegExp | { msg: string; args: string | (string | RegExp)[] | RegExp }; + not?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; /** * checks for email format (foo@bar.com) @@ -1093,22 +1093,22 @@ export interface ModelValidateOptions { /** * check the value is not one of these */ - notIn?: string[][] | { msg: string; args: string[][] }; + notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; /** * check the value is one of these */ - isIn?: string[][] | { msg: string; args: string[][] }; + isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; /** * don't allow specific substrings */ - notContains?: string[] | string | { msg: string; args: string[] | string }; + notContains?: readonly string[] | string | { msg: string; args: readonly string[] | string }; /** * only allow values with length between 2 and 10 */ - len?: [number, number] | { msg: string; args: [number, number] }; + len?: readonly [number, number] | { msg: string; args: readonly [number, number] }; /** * only allow uuids @@ -1133,12 +1133,12 @@ export interface ModelValidateOptions { /** * only allow values */ - max?: number | { msg: string; args: [number] }; + max?: number | { msg: string; args: readonly [number] }; /** * only allow values >= 23 */ - min?: number | { msg: string; args: [number] }; + min?: number | { msg: string; args: readonly [number] }; /** * only allow arrays @@ -1150,20 +1150,10 @@ export interface ModelValidateOptions { */ isCreditCard?: boolean | { msg: string; args: boolean }; + // TODO: Enforce 'rest' indexes to have type `(value: unknown) => boolean` + // Blocked by: https://github.com/microsoft/TypeScript/issues/7765 /** - * custom validations are also possible - * - * Implementation notes : - * - * We can't enforce any other method to be a function, so : - * - * ```typescript - * [name: string] : ( value : unknown ) => boolean; - * ``` - * - * doesn't work in combination with the properties above - * - * @see https://github.com/Microsoft/TypeScript/issues/1889 + * Custom validations are also possible */ [name: string]: unknown; } @@ -1209,7 +1199,7 @@ export interface ModelScopeOptions { /** * Name of the scope and it's query */ - [scopeName: string]: FindOptions | ((...args: any[]) => FindOptions); + [scopeName: string]: FindOptions | ((...args: readonly any[]) => FindOptions); } /** @@ -1334,7 +1324,7 @@ export interface ModelAttributeColumnOptions extends Co * }, { sequelize }) * ``` */ - values?: string[]; + values?: readonly string[]; /** * Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying @@ -1426,7 +1416,7 @@ export interface ModelOptions { /** * Indexes for the provided database table */ - indexes?: ModelIndexesOptions[]; + indexes?: readonly ModelIndexesOptions[]; /** * Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps @@ -1564,7 +1554,7 @@ export abstract class Model( this: ModelStatic, - options?: string | ScopeOptions | (string | ScopeOptions)[] | WhereAttributeHash + options?: string | ScopeOptions | readonly (string | ScopeOptions)[] | WhereAttributeHash ): ModelCtor; /** @@ -1757,7 +1747,7 @@ export abstract class Model( this: ModelStatic, name: string, - scope: (...args: any[]) => FindOptions, + scope: (...args: readonly any[]) => FindOptions, options?: AddScopeOptions ): void; @@ -1969,7 +1959,7 @@ export abstract class Model( this: ModelStatic, - records: (M['_creationAttributes'])[], + records: ReadonlyArray, options?: BuildOptions ): M[]; @@ -2059,7 +2049,7 @@ export abstract class Model( this: ModelStatic, - records: (M['_creationAttributes'])[], + records: ReadonlyArray, options?: BulkCreateOptions ): Promise; @@ -2114,7 +2104,7 @@ export abstract class Model( this: ModelStatic, - fields: (keyof M['_attributes'])[], + fields: ReadonlyArray, options: IncrementDecrementOptionsWithBy ): Promise; @@ -2307,11 +2297,11 @@ export abstract class Model( this: ModelStatic, name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn ): void; public static beforeBulkCreate( this: ModelStatic, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn ): void; /** @@ -2323,11 +2313,11 @@ export abstract class Model( this: ModelStatic, name: string, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn ): void; public static afterBulkCreate( this: ModelStatic, - fn: (instances: M[], options: BulkCreateOptions) => HookReturn + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn ): void; /** @@ -2458,11 +2448,11 @@ export abstract class Model( this: ModelStatic, name: string, - fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn + fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions) => HookReturn ): void; public static afterFind( this: ModelStatic, - fn: (instancesOrInstance: M[] | M | null, options: FindOptions) => HookReturn + fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions) => HookReturn ): void; /** @@ -2787,7 +2777,7 @@ export abstract class Model( - fields: K | K[] | Partial, + fields: K | readonly K[] | Partial, options?: IncrementDecrementOptionsWithBy ): Promise; @@ -2812,7 +2802,7 @@ export abstract class Model( - fields: K | K[] | Partial, + fields: K | readonly K[] | Partial, options?: IncrementDecrementOptionsWithBy ): Promise; @@ -2824,7 +2814,7 @@ export abstract class Model {}); -Sequelize.afterSave((t: TestModel, options: SaveOptions) => {}); -Sequelize.afterFind((t: TestModel[] | TestModel | null, options: FindOptions) => {}); -Sequelize.afterFind('namedAfterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {}); +class TestModel extends Model {} /* * covers types/lib/hooks.d.ts */ -export const sequelize = new Sequelize('uri', { - hooks: { - beforeSave (m: Model, options: SaveOptions) {}, - afterSave (m: Model, options: SaveOptions) {}, - afterFind (m: Model[] | Model | null, options: FindOptions) {}, - } -}); - -class TestModel extends Model { -} - const hooks: Partial = { - beforeSave(t: TestModel, options: SaveOptions) { }, - afterSave(t: TestModel, options: SaveOptions) { }, - afterFind(t: TestModel | TestModel[] | null, options: FindOptions) { }, + beforeSave(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? + }, + afterSave(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? + }, + afterFind(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + } }; -TestModel.init({}, {sequelize, hooks }) +export const sequelize = new Sequelize('uri', { hooks }); +TestModel.init({}, { sequelize, hooks }); -TestModel.addHook('beforeSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.addHook('afterSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.addHook('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { }); +TestModel.addHook('beforeSave', hooks.beforeSave!); +TestModel.addHook('afterSave', hooks.afterSave!); +TestModel.addHook('afterFind', hooks.afterFind!); /* * covers types/lib/model.d.ts */ -TestModel.beforeSave((t: TestModel, options: SaveOptions) => { }); -TestModel.afterSave((t: TestModel, options: SaveOptions) => { }); -TestModel.afterFind((t: TestModel | TestModel[] | null, options: FindOptions) => { }); +TestModel.beforeSave(hooks.beforeSave!); +TestModel.afterSave(hooks.afterSave!); +TestModel.afterFind(hooks.afterFind!); + +/* + * covers types/lib/sequelize.d.ts + */ + +Sequelize.beforeSave(hooks.beforeSave!); +Sequelize.afterSave(hooks.afterSave!); +Sequelize.afterFind(hooks.afterFind!); +Sequelize.afterFind('namedAfterFind', hooks.afterFind!); diff --git a/types/test/where.ts b/types/test/where.ts index b268212b1019..5b4565248e90 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -1,19 +1,14 @@ +import { expectTypeOf } from "expect-type"; import { AndOperator, fn, Model, Op, OrOperator, Sequelize, WhereOperators, WhereOptions, literal, where as whereFn } from 'sequelize'; import Transaction from '../lib/transaction'; class MyModel extends Model { - public hi!: number; + public hi!: number; } -let where: WhereOptions; +// Simple options -// From https://sequelize.org/master/en/v4/docs/querying/ - -/** - * Literal values - * @see WhereValue - */ -where = { +expectTypeOf({ string: 'foo', strings: ['foo'], number: 1, @@ -23,127 +18,95 @@ where = { buffers: [Buffer.alloc(0)], null: null, date: new Date() -}; +}).toMatchTypeOf(); // Optional values -let whereWithOptionals: { needed: number; optional?: number } = { needed: 2 }; -where = whereWithOptionals; +expectTypeOf<{ needed: number; optional?: number }>().toMatchTypeOf(); // Misusing optional values (typings allow this, sequelize will throw an error during runtime) // This might be solved by updates to typescript itself (https://github.com/microsoft/TypeScript/issues/13195) -whereWithOptionals = { needed: 2, optional: undefined }; -where = whereWithOptionals; +// expectTypeOf({ needed: 2, optional: undefined }).not.toMatchTypeOf(); // Operators -const and: AndOperator = { - [Op.and]: { a: 5 }, // AND (a = 5) -}; -const typedAnd: AndOperator<{ a: number }> = { - [Op.and]: { a: 5 }, // AND (a = 5) -}; - -const or: OrOperator = { - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) -}; -const typedOr: OrOperator<{ a: number }> = { - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) -}; - -let operators: WhereOperators = { - [Op.gt]: 6, // > 6 - [Op.gte]: 6, // >= 6 - [Op.lt]: 10, // < 10 - [Op.lte]: 10, // <= 10 - [Op.ne]: 20, // != 20 - [Op.not]: true, // IS NOT TRUE - [Op.between]: [6, 10], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.startsWith]: 'hat', - [Op.endsWith]: 'hat', - [Op.substring]: 'hat', - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) - [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) - [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) - [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) - [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) -}; - -operators = { - [Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike -}; - -// Combinations - -MyModel.findOne({ where: or }); -MyModel.findOne({ where: and }); - -where = Sequelize.and(); - -where = Sequelize.or(); - -where = { [Op.and]: [] }; - -where = { - rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }), -}; - -where = { - rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }), -}; - -where = { - rank: { - [Op.or]: { - [Op.lt]: 1000, - [Op.eq]: null, - }, - }, -}; -// rank < 1000 OR rank IS NULL - -where = { +expectTypeOf({ + [Op.and]: { a: 5 }, // AND (a = 5) +}).toMatchTypeOf(); +expectTypeOf({ + [Op.and]: { a: 5 }, // AND (a = 5) +}).toMatchTypeOf>(); + +expectTypeOf({ + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) +}).toMatchTypeOf(); +expectTypeOf({ + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) +}).toMatchTypeOf>(); + +expectTypeOf({ + [Op.gt]: 6, // > 6 + [Op.gte]: 6, // >= 6 + [Op.lt]: 10, // < 10 + [Op.lte]: 10, // <= 10 + [Op.ne]: 20, // != 20 + [Op.not]: true, // IS NOT TRUE + [Op.between]: [6, 10], // BETWEEN 6 AND 10 + [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 + [Op.in]: [1, 2], // IN [1, 2] + [Op.notIn]: [1, 2], // NOT IN [1, 2] + [Op.like]: '%hat', // LIKE '%hat' + [Op.notLike]: '%hat', // NOT LIKE '%hat' + [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) + [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) + [Op.startsWith]: 'hat', + [Op.endsWith]: 'hat', + [Op.substring]: 'hat', + [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) + [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) + [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) + [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) + [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) + [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) + [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) +} as const).toMatchTypeOf(); + +expectTypeOf({ + [Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] + [Op.iLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] + [Op.notLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] + [Op.notILike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] +}).toMatchTypeOf(); + +// Complex where options via combinations + +expectTypeOf([ + { [Op.or]: [{ a: 5 }, { a: 6 }] }, + Sequelize.and(), + Sequelize.or(), + { [Op.and]: [] }, + { rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }) }, + { rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }) }, + { rank: { [Op.or]: { [Op.lt]: 1000, [Op.eq]: null } } }, + { createdAt: { - [Op.lt]: new Date(), - [Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000), - }, -}; -// createdAt < [timestamp] AND createdAt > [timestamp] - -where = { + [Op.lt]: new Date(), + [Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000), + } + }, + { [Op.or]: [ - { - title: { - [Op.like]: 'Boat%', - }, - }, - { - description: { - [Op.like]: '%boat%', - }, - }, - ], -}; -// title LIKE 'Boat%' OR description LIKE '%boat%' - -// Containment - -where = { + { title: { [Op.like]: 'Boat%' } }, + { description: { [Op.like]: '%boat%' } } + ] + }, + { meta: { - [Op.contains]: { - site: { - url: 'http://google.com', - }, - }, + [Op.contains]: { + site: { + url: 'https://sequelize.org/' + } + } }, meta2: { [Op.contains]: ['stringValue1', 'stringValue2', 'stringValue3'] @@ -151,31 +114,88 @@ where = { meta3: { [Op.contains]: [1, 2, 3, 4] }, -}; + }, + { + name: 'a project', + [Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }] + }, + { + id: { + [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }] + }, + name: 'a project' + }, + { + id: { + [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }] + }, + name: 'a project' + }, + { + name: 'a project', + type: { + [Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }], + }, + }, + { + name: 'a project', + [Op.not]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], + }, + { + meta: { + video: { + url: { + [Op.ne]: null, + }, + }, + }, + }, + { + 'meta.audio.length': { + [Op.gt]: 20, + }, + }, + { + [Op.and]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], + }, + { + [Op.gt]: fn('NOW'), + }, + whereFn('test', { [Op.gt]: new Date() }), + literal('true'), + fn('LOWER', 'asd'), + { [Op.lt]: Sequelize.literal('SOME_STRING') } +]).toMatchTypeOf(); // Relations / Associations // Find all projects with a least one task where task.state === project.task MyModel.findAll({ - include: [ - { - model: MyModel, - where: { state: Sequelize.col('project.state') }, - }, - ], + include: [ + { + model: MyModel, + where: { state: Sequelize.col('project.state') }, + }, + ], }); -MyModel.findOne({ +{ + const where: WhereOptions = 0 as any; + MyModel.findOne({ include: [ - { - include: [{ model: MyModel, where }], - model: MyModel, - where, - }, + { + include: [{ model: MyModel, where }], + model: MyModel, + where, + }, ], where, -}); -MyModel.destroy({ where }); -MyModel.update({ hi: 1 }, { where }); + }); + MyModel.destroy({ where }); + MyModel.update({ hi: 1 }, { where }); + + // Where as having option + MyModel.findAll({ having: where }); +} // From https://sequelize.org/master/en/v4/docs/models-usage/ @@ -197,141 +217,61 @@ async function test() { } MyModel.findAll({ - where: { - id: { - // casting here to check a missing operator is not accepted as field name - [Op.and]: { a: 5 }, // AND (a = 5) - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) - [Op.gt]: 6, // id > 6 - [Op.gte]: 6, // id >= 6 - [Op.lt]: 10, // id < 10 - [Op.lte]: 10, // id <= 10 - [Op.ne]: 20, // id != 20 - [Op.between]: [6, 10] || [new Date(), new Date()], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) - [Op.adjacent]: [1, 2], - [Op.strictLeft]: [1, 2], - [Op.strictRight]: [1, 2], - [Op.noExtendLeft]: [1, 2], - [Op.noExtendRight]: [1, 2], - [Op.values]: [1, 2], - } as WhereOperators, - status: { - [Op.not]: false, // status NOT FALSE - }, - }, -}); - -// Complex filtering / NOT queries - -where = { - name: 'a project', - [Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }], -}; - -where = { + where: { id: { - [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }], - }, - name: 'a project', -}; - -where = { - name: 'a project', - type: { - [Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }], - }, -}; - -// [Op.not] example: -where = { - name: 'a project', - [Op.not]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], -}; - -// JSONB - -// Nested object - -where = { - meta: { - video: { - url: { - [Op.ne]: null, - }, - }, + // casting here to check a missing operator is not accepted as field name + [Op.and]: { a: 5 }, // AND (a = 5) + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) + [Op.gt]: 6, // id > 6 + [Op.gte]: 6, // id >= 6 + [Op.lt]: 10, // id < 10 + [Op.lte]: 10, // id <= 10 + [Op.ne]: 20, // id != 20 + [Op.between]: [6, 10] || [new Date(), new Date()], // BETWEEN 6 AND 10 + [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 + [Op.in]: [1, 2], // IN [1, 2] + [Op.notIn]: [1, 2], // NOT IN [1, 2] + [Op.like]: '%hat', // LIKE '%hat' + [Op.notLike]: '%hat', // NOT LIKE '%hat' + [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) + [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) + [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) + [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) + [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) + [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + [Op.adjacent]: [1, 2], + [Op.strictLeft]: [1, 2], + [Op.strictRight]: [1, 2], + [Op.noExtendLeft]: [1, 2], + [Op.noExtendRight]: [1, 2], + [Op.values]: [1, 2], + } as WhereOperators, + status: { + [Op.not]: false, // status NOT FALSE }, -}; - -// Nested key -where = { - 'meta.audio.length': { - [Op.gt]: 20, - }, -}; - -// Operator symbols -where = { - [Op.and]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], -}; - -// Fn as value -where = { - [Op.gt]: fn('NOW'), -}; - -where = whereFn('test', { - [Op.gt]: new Date(), + }, }); -// Literal as where -where = literal('true') - -where = fn('LOWER', 'asd') - -MyModel.findAll({ - where: literal('true') -}) - -// Where as having option -MyModel.findAll({ - having: where -}); - -where = { - [Op.lt]: Sequelize.literal('SOME_STRING') -} - Sequelize.where( - Sequelize.cast(Sequelize.col('SOME_COL'), 'INTEGER'), { - [Op.lt]: Sequelize.literal('LIT'), - [Op.any]: Sequelize.literal('LIT'), - [Op.gte]: Sequelize.literal('LIT'), - [Op.lt]: Sequelize.literal('LIT'), - [Op.lte]: Sequelize.literal('LIT'), - [Op.ne]: Sequelize.literal('LIT'), - [Op.not]: Sequelize.literal('LIT'), - [Op.in]: Sequelize.literal('LIT'), - [Op.notIn]: Sequelize.literal('LIT'), - [Op.like]: Sequelize.literal('LIT'), - [Op.notLike]: Sequelize.literal('LIT'), - [Op.iLike]: Sequelize.literal('LIT'), - [Op.overlap]: Sequelize.literal('LIT'), - [Op.contains]: Sequelize.literal('LIT'), - [Op.contained]: Sequelize.literal('LIT'), - [Op.gt]: Sequelize.literal('LIT'), - [Op.notILike]: Sequelize.literal('LIT'), - } + Sequelize.cast(Sequelize.col('SOME_COL'), 'INTEGER'), { + [Op.lt]: Sequelize.literal('LIT'), + [Op.any]: Sequelize.literal('LIT'), + [Op.gte]: Sequelize.literal('LIT'), + [Op.lt]: Sequelize.literal('LIT'), + [Op.lte]: Sequelize.literal('LIT'), + [Op.ne]: Sequelize.literal('LIT'), + [Op.not]: Sequelize.literal('LIT'), + [Op.in]: Sequelize.literal('LIT'), + [Op.notIn]: Sequelize.literal('LIT'), + [Op.like]: Sequelize.literal('LIT'), + [Op.notLike]: Sequelize.literal('LIT'), + [Op.iLike]: Sequelize.literal('LIT'), + [Op.overlap]: Sequelize.literal('LIT'), + [Op.contains]: Sequelize.literal('LIT'), + [Op.contained]: Sequelize.literal('LIT'), + [Op.gt]: Sequelize.literal('LIT'), + [Op.notILike]: Sequelize.literal('LIT'), + } ) Sequelize.where(Sequelize.col("ABS"), Op.is, null); From bb6acd30bef8c9216365cfa4a41b123c29b4bcd5 Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 19:10:22 -0300 Subject: [PATCH 241/414] ci: test typings in several ts versions --- .github/workflows/ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b4062d4a317..e95766d82799 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,11 @@ jobs: - run: npm run lint - run: npm run lint-docs test-typings: - name: TS Typings + strategy: + fail-fast: false + matrix: + ts-version: [3.6, 3.7, 3.8, 3.9, 4.0, 4.1] + name: TS Typings (${{ matrix.ts-version }}) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -27,6 +31,7 @@ jobs: with: node-version: 12.x - run: npm install + - run: npm install --save-dev typescript@~${{ matrix.ts-version }} - run: npm run test-typings test-sqlite: name: SQLite From 6375960038693042c9caff114a183f90bac42537 Mon Sep 17 00:00:00 2001 From: papb Date: Sat, 16 Jan 2021 19:27:01 -0300 Subject: [PATCH 242/414] ci: do not test on TS 3.6, 3.7 and 3.8 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e95766d82799..4d896335afc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - ts-version: [3.6, 3.7, 3.8, 3.9, 4.0, 4.1] + ts-version: [3.9, 4.0, 4.1] name: TS Typings (${{ matrix.ts-version }}) runs-on: ubuntu-latest steps: From 3ca53d8654ef7e16a75e51a29ed8a29f96b23fe9 Mon Sep 17 00:00:00 2001 From: papb Date: Sun, 17 Jan 2021 19:37:53 -0300 Subject: [PATCH 243/414] ci: fix condition for release job --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d896335afc6..fc6da2eca3df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,7 +159,7 @@ jobs: name: Release runs-on: ubuntu-latest needs: [lint, test-typings, test-sqlite, test-postgres, test-mysql-mariadb, test-mssql] - if: github.event.push + if: github.event_name == 'push' && github.ref == 'refs/heads/v6' steps: # - uses: actions/checkout@v2 # - uses: actions/setup-node@v1 From defa2f027bb1459542110fe51cffb0a6d2913510 Mon Sep 17 00:00:00 2001 From: papb Date: Sun, 17 Jan 2021 20:34:37 -0300 Subject: [PATCH 244/414] ci: re-enable semantic-release for v6 --- .github/workflows/ci.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc6da2eca3df..74d6f16a9e3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,10 +160,11 @@ jobs: runs-on: ubuntu-latest needs: [lint, test-typings, test-sqlite, test-postgres, test-mysql-mariadb, test-mssql] if: github.event_name == 'push' && github.ref == 'refs/heads/v6' + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} steps: - # - uses: actions/checkout@v2 - # - uses: actions/setup-node@v1 - # with: - # node-version: 12.x - # - run: npm run semantic-release - - run: echo 'Auto-release would happen!' + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm run semantic-release From 598d58863fc276db713758488d622c5ee7f2a8b7 Mon Sep 17 00:00:00 2001 From: papb Date: Sun, 17 Jan 2021 20:57:16 -0300 Subject: [PATCH 245/414] ci: test on Node.js 10 and 12 --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74d6f16a9e3a..e700580a9dc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,11 @@ jobs: - run: npm install --save-dev typescript@~${{ matrix.ts-version }} - run: npm run test-typings test-sqlite: - name: SQLite + strategy: + fail-fast: false + matrix: + node-version: [10, 12] + name: SQLite (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest env: DIALECT: sqlite @@ -42,7 +46,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: ${{ matrix.node-version }} - run: npm install - name: Unit Tests run: npm run test-unit @@ -52,10 +56,11 @@ jobs: strategy: fail-fast: false matrix: + node-version: [10, 12] postgres-version: [9.5, 10] # Does not work with 12 minify-aliases: [true, false] native: [true, false] - name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }}${{ matrix.minify-aliases && ' (minified aliases)' || '' }} + name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }} (Node ${{ matrix.node-version }})${{ matrix.minify-aliases && ' (minified aliases)' || '' }} runs-on: ubuntu-latest services: postgres: @@ -75,7 +80,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: ${{ matrix.node-version }} - run: npm install - run: npm install pg-native if: matrix.native @@ -92,10 +97,20 @@ jobs: - name: MySQL 5.7 image: mysql:5.7 dialect: mysql + node-version: 10 + - name: MySQL 5.7 + image: mysql:5.7 + dialect: mysql + node-version: 12 + - name: MariaDB 10.3 + image: mariadb:10.3 + dialect: mariadb + node-version: 10 - name: MariaDB 10.3 image: mariadb:10.3 dialect: mariadb - name: ${{ matrix.name }} + node-version: 12 + name: ${{ matrix.name }} (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest services: mysql: @@ -115,7 +130,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: ${{ matrix.node-version }} - run: npm install - name: Unit Tests run: npm run test-unit @@ -125,8 +140,9 @@ jobs: strategy: fail-fast: false matrix: + node-version: [10, 12] mssql-version: [2017, 2019] - name: MSSQL ${{ matrix.mssql-version }} + name: MSSQL ${{ matrix.mssql-version }} (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest services: mssql: @@ -149,7 +165,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: ${{ matrix.node-version }} - run: npm install - name: Unit Tests run: npm run test-unit From c8ca9b22494c3f5380ba30b6c934180dc02c5aca Mon Sep 17 00:00:00 2001 From: papb Date: Sun, 17 Jan 2021 23:13:42 -0300 Subject: [PATCH 246/414] ci: simplify mssql configuration --- .github/workflows/ci.yml | 2 ++ test/config/config.js | 21 +++++++-------------- test/config/mssql.json | 17 ----------------- 3 files changed, 9 insertions(+), 31 deletions(-) delete mode 100644 test/config/mssql.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e700580a9dc3..831ee190168a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,6 +160,8 @@ jobs: --health-retries 10 env: DIALECT: mssql + SEQ_USER: SA + SEQ_PW: Password12! steps: - run: /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Password12!" -Q "CREATE DATABASE sequelize_test; ALTER DATABASE sequelize_test SET READ_COMMITTED_SNAPSHOT ON;" - uses: actions/checkout@v2 diff --git a/test/config/config.js b/test/config/config.js index 181206e12c5d..4f422d88d015 100644 --- a/test/config/config.js +++ b/test/config/config.js @@ -1,13 +1,6 @@ 'use strict'; -const fs = require('fs'); -let mssqlConfig; -try { - mssqlConfig = JSON.parse(fs.readFileSync(`${__dirname}/mssql.json`, 'utf8')); -} catch (e) { - // ignore -} -const env = process.env; +const { env } = process; module.exports = { username: env.SEQ_USER || 'root', @@ -23,16 +16,16 @@ module.exports = { return parseInt(Math.random() * 999, 10); }, - mssql: mssqlConfig || { - database: env.SEQ_MSSQL_DB || env.SEQ_DB || 'sequelize_test', - username: env.SEQ_MSSQL_USER || env.SEQ_USER || 'sequelize', - password: env.SEQ_MSSQL_PW || env.SEQ_PW || 'nEGkLma26gXVHFUAHJxcmsrK', - host: env.SEQ_MSSQL_HOST || env.SEQ_HOST || '127.0.0.1', + mssql: { + host: env.SEQ_MSSQL_HOST || env.SEQ_HOST || 'localhost', + username: env.SEQ_MSSQL_USER || env.SEQ_USER || 'SA', + password: env.SEQ_MSSQL_PW || env.SEQ_PW || 'Password12!', port: env.SEQ_MSSQL_PORT || env.SEQ_PORT || 1433, + database: env.SEQ_MSSQL_DB || env.SEQ_DB || 'sequelize_test', dialectOptions: { options: { encrypt: false, - requestTimeout: 60000 + requestTimeout: 25000 } }, pool: { diff --git a/test/config/mssql.json b/test/config/mssql.json deleted file mode 100644 index 04564f5e789b..000000000000 --- a/test/config/mssql.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "host": "localhost", - "username": "SA", - "password": "Password12!", - "port": 1433, - "database": "sequelize_test", - "dialectOptions": { - "options": { - "encrypt": false, - "requestTimeout": 25000 - } - }, - "pool": { - "max": 5, - "idle": 3000 - } -} From 2608cf150498c2315a548eac771d4d45c730318b Mon Sep 17 00:00:00 2001 From: papb Date: Sun, 17 Jan 2021 23:28:06 -0300 Subject: [PATCH 247/414] ci(typings): fix tests for TS typings in TS 4.0 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 831ee190168a..d05cdcf97328 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - ts-version: [3.9, 4.0, 4.1] + ts-version: ['3.9', '4.0', '4.1'] name: TS Typings (${{ matrix.ts-version }}) runs-on: ubuntu-latest steps: From dc60533cb98b6870307dbb80631f5cc924c8c8d6 Mon Sep 17 00:00:00 2001 From: papb Date: Sun, 17 Jan 2021 23:44:12 -0300 Subject: [PATCH 248/414] docs: update index and readme --- README.md | 13 ++++++++----- docs/index.md | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9b50e17ce361..157bcb743006 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,24 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) [![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize/main?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) + [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. Sequelize follows [Semantic Versioning](http://semver.org). +Sequelize is a promise-based [Node.js](https://nodejs.org/en/about/) [ORM tool](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. + +Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 and above. New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.org/master). You might also be interested in the [API Reference](https://sequelize.org/master/identifiers). ### v6 Release -You can find detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). +You can find the detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). + +## Note: Looking for maintainers! -## Looking for maintainers -Due to personal reasons a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy recently. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: +Due to various personal reasons, a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy recently. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: We are looking for more core maintainers who are interested in finishing the current TypeScript migration, extending the documentation for v6, organizing issues, reviewing PRs, streamlining the overall code base and planning the future roadmap. diff --git a/docs/index.md b/docs/index.md index 5266b16723c6..79bf6dc78615 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) [![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) + [![Last commit](https://badgen.net/github/last-commit/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![GitHub stars](https://badgen.net/github/stars/sequelize/sequelize)](https://github.com/sequelize/sequelize) @@ -17,7 +17,7 @@ [![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/main/LICENSE) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -Sequelize is a promise-based Node.js [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. +Sequelize is a promise-based [Node.js](https://nodejs.org/en/about/) [ORM tool](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 and above. From 33b17e01cb220522444e826f220a195493c273dc Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 18 Jan 2021 13:17:27 -0300 Subject: [PATCH 249/414] ci: really enable semantic-release Fixed what was forgotten in defa2f0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf7c549b3e5c..56987cebb78f 100644 --- a/package.json +++ b/package.json @@ -197,6 +197,6 @@ "sscce-sqlite": "cross-env DIALECT=sqlite npm run sscce", "sscce-mssql": "cross-env DIALECT=mssql npm run sscce", "setup-mssql": "env-cmd $npm_package_options_env_cmd ./scripts/setup-mssql", - "//semantic-release": "semantic-release" + "semantic-release": "semantic-release" } } From 8e6f5881c03d7ef78c38dfa83f21414569380ae5 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 18 Jan 2021 13:37:27 -0300 Subject: [PATCH 250/414] ci: fix release job --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d05cdcf97328..9526a30635a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -185,4 +185,5 @@ jobs: - uses: actions/setup-node@v1 with: node-version: 12.x + - run: npm install - run: npm run semantic-release From 4dc0334d1736e52ff26852640ea2189cca9e07e5 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 18 Jan 2021 14:13:13 -0300 Subject: [PATCH 251/414] ci: try to resolve flaky pg test --- test/integration/transaction.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 68b32239a939..b5cfd7677e31 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -124,8 +124,11 @@ if (current.dialect.supports.transactions) { return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }, async t => { + await delay(1000); await SumSumSum.sum('value', { transaction: t }); + await delay(1000); await SumSumSum.create({ value: -val }, { transaction: t }); + await delay(1000); }); }; From fbf3a4ccb3e3ed3eb9631da01d3109c240ab3d26 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 18 Jan 2021 15:02:22 -0300 Subject: [PATCH 252/414] ci: fix release job again --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9526a30635a5..4277bebe901a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,6 +179,7 @@ jobs: needs: [lint, test-typings, test-sqlite, test-postgres, test-mysql-mariadb, test-mssql] if: github.event_name == 'push' && github.ref == 'refs/heads/v6' env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} steps: - uses: actions/checkout@v2 From 83be13fa1dc73fb37508fe3eab638267003f8618 Mon Sep 17 00:00:00 2001 From: papb Date: Tue, 19 Jan 2021 21:10:47 -0300 Subject: [PATCH 253/414] test(transaction): try to fix flaky postgres test --- test/integration/transaction.test.js | 90 ++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index b5cfd7677e31..68f37f62c51e 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -110,39 +110,79 @@ if (current.dialect.supports.transactions) { expect(hook).to.not.have.been.called; }); - //Promise rejection test is specific to postgres if (dialect === 'postgres') { - it('do not rollback if already committed', async function() { - const SumSumSum = this.sequelize.define('transaction', { - value: { - type: Support.Sequelize.DECIMAL(10, 3), - field: 'value' - } - }); - - const transTest = val => { - return this.sequelize.transaction({ - isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE - }, async t => { - await delay(1000); - await SumSumSum.sum('value', { transaction: t }); - await delay(1000); - await SumSumSum.create({ value: -val }, { transaction: t }); - await delay(1000); + // See #3689, #3726 and #6972 (https://github.com/sequelize/sequelize/pull/6972/files#diff-533eac602d424db379c3d72af5089e9345fd9d3bbe0a26344503c22a0a5764f7L75) + it('does not try to rollback a transaction that failed upon committing with SERIALIZABLE isolation level (#3689)', async function() { + // See https://wiki.postgresql.org/wiki/SSI + + const Dots = this.sequelize.define('dots', { color: Sequelize.STRING }); + await Dots.sync({ force: true }); + + const initialData = [ + { color: 'red' }, + { color: 'green' }, + { color: 'green' }, + { color: 'red' }, + { color: 'green' }, + { color: 'red' }, + { color: 'green' }, + { color: 'green' }, + { color: 'green' }, + { color: 'red' }, + { color: 'red' }, + { color: 'red' }, + { color: 'green' }, + { color: 'red' }, + { color: 'red' }, + { color: 'red' }, + { color: 'green' }, + { color: 'red' } + ]; + + await Dots.bulkCreate(initialData); + + const isolationLevel = Transaction.ISOLATION_LEVELS.SERIALIZABLE; + + let firstTransactionGotNearCommit = false; + let secondTransactionGotNearCommit = false; + + const firstTransaction = async () => { + await this.sequelize.transaction({ isolationLevel }, async t => { + await Dots.update({ color: 'red' }, { + where: { color: 'green' }, + transaction: t + }); + await delay(1500); + firstTransactionGotNearCommit = true; }); }; - await SumSumSum.sync({ force: true }); + const secondTransaction = async () => { + await delay(500); + await this.sequelize.transaction({ isolationLevel }, async t => { + await Dots.update({ color: 'green' }, { + where: { color: 'red' }, + transaction: t + }); + + // Sanity check - in this test we want this line to be reached before the + // first transaction gets to commit + expect(firstTransactionGotNearCommit).to.be.false; + + secondTransactionGotNearCommit = true; + }); + }; await expect( - Promise.all([ - transTest(80), - transTest(80) - ]) + Promise.all([firstTransaction(), secondTransaction()]) ).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'); - // one row was created, another was rejected due to rollback - expect(await SumSumSum.count()).to.equal(1); + expect(firstTransactionGotNearCommit).to.be.true; + expect(secondTransactionGotNearCommit).to.be.true; + + // Only the second transaction worked + expect(await Dots.count({ where: { color: 'red' } })).to.equal(0); + expect(await Dots.count({ where: { color: 'green' } })).to.equal(initialData.length); }); } From c77b1f3a6c4840e4e846042c9c330dba2408b86c Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Mon, 25 Jan 2021 05:36:18 -0800 Subject: [PATCH 254/414] fix(mysql, mariadb): release connection on deadlocks (#12841) --- lib/dialects/mariadb/query.js | 19 +++- lib/sequelize.js | 16 ++- lib/transaction.js | 24 +++-- test/integration/transaction.test.js | 140 +++++++++++++++++---------- 4 files changed, 132 insertions(+), 67 deletions(-) diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index b89575575a96..f3bd5d6fc9eb 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -54,8 +54,25 @@ class Query extends AbstractQuery { await this.logWarnings(results); } } catch (err) { - // MariaDB automatically rolls-back transactions in the event of a deadlock + // MariaDB automatically rolls-back transactions in the event of a + // deadlock. + // + // Even though we shouldn't need to do this, we initiate a manual + // rollback. Without the rollback, the next transaction using the + // connection seems to retain properties of the previous transaction + // (e.g. isolation level) and not work as expected. + // + // For example (in our tests), a follow-up READ_COMMITTED transaction + // doesn't work as expected unless we explicitly rollback the + // transaction: it would fail to read a value inserted outside of that + // transaction. if (options.transaction && err.errno === 1213) { + try { + await options.transaction.rollback(); + } catch (err) { + // Ignore errors - since MariaDB automatically rolled back, we're + // not that worried about this redundant rollback failing. + } options.transaction.finished = 'rollback'; } diff --git a/lib/sequelize.js b/lib/sequelize.js index 05604cddee56..f10c66bb5590 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -1091,12 +1091,15 @@ class Sequelize { await transaction.commit(); return await result; } catch (err) { - if (!transaction.finished) { - try { + try { + if (!transaction.finished) { await transaction.rollback(); - } catch (err0) { - // ignore + } else { + // release the connection, even if we don't need to rollback + await transaction.cleanup(); } + } catch (err0) { + // ignore } throw err; } @@ -1104,7 +1107,10 @@ class Sequelize { } /** - * Use CLS with Sequelize. + * Use CLS (Continuation Local Storage) with Sequelize. With Continuation + * Local Storage, all queries within the transaction callback will + * automatically receive the transaction object. + * * CLS namespace provided is stored as `Sequelize._cls` * * @param {object} ns CLS namespace diff --git a/lib/transaction.js b/lib/transaction.js index 4a24cf537735..f8e32f18df92 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -56,15 +56,11 @@ class Transaction { throw new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`); } - this._clearCls(); - try { return await this.sequelize.getQueryInterface().commitTransaction(this, this.options); } finally { this.finished = 'commit'; - if (!this.parent) { - this.cleanup(); - } + this.cleanup(); for (const hook of this._afterCommitHooks) { await hook.apply(this, [this]); } @@ -85,20 +81,23 @@ class Transaction { throw new Error('Transaction cannot be rolled back because it never started'); } - this._clearCls(); - try { return await this .sequelize .getQueryInterface() .rollbackTransaction(this, this.options); } finally { - if (!this.parent) { - this.cleanup(); - } + this.cleanup(); } } + /** + * Called to acquire a connection to use and set the correct options on the connection. + * We should ensure all of the environment that's set up is cleaned up in `cleanup()` below. + * + * @param {boolean} useCLS Defaults to true: Use CLS (Continuation Local Storage) with Sequelize. With CLS, all queries within the transaction callback will automatically receive the transaction object. + * @returns {Promise} + */ async prepareEnvironment(useCLS) { let connectionPromise; @@ -162,6 +161,11 @@ class Transaction { } cleanup() { + // Don't release the connection if there's a parent transaction or + // if we've already cleaned up + if (this.parent || this.connection.uuid === undefined) return; + + this._clearCls(); const res = this.sequelize.connectionManager.releaseConnection(this.connection); this.connection.uuid = undefined; return res; diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 68f37f62c51e..fce1e74a8468 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -424,70 +424,105 @@ if (current.dialect.supports.transactions) { if (dialect === 'mysql' || dialect === 'mariadb') { describe('deadlock handling', () => { - it('should treat deadlocked transaction as rollback', async function() { - const Task = this.sequelize.define('task', { + // Create the `Task` table and ensure it's initialized with 2 rows + const getAndInitializeTaskModel = async sequelize => { + const Task = sequelize.define('task', { id: { type: Sequelize.INTEGER, primaryKey: true } }); - // This gets called twice simultaneously, and we expect at least one of the calls to encounter a - // deadlock (which effectively rolls back the active transaction). - // We only expect createTask() to insert rows if a transaction is active. If deadlocks are handled - // properly, it should only execute a query if we're actually inside a real transaction. If it does - // execute a query, we expect the newly-created rows to be destroyed when we forcibly rollback by - // throwing an error. - // tl;dr; This test is designed to ensure that this function never inserts and commits a new row. - const update = async (from, to) => this.sequelize.transaction(async transaction => { - try { + await sequelize.sync({ force: true }); + await Task.create({ id: 0 }); + await Task.create({ id: 1 }); + return Task; + }; + + // Lock the row with id of `from`, and then try to update the row + // with id of `to` + const update = async (sequelize, Task, from, to) => { + await sequelize + .transaction(async transaction => { try { - await Task.findAll({ - where: { - id: { - [Sequelize.Op.eq]: from + try { + await Task.findAll({ + where: { id: { [Sequelize.Op.eq]: from } }, + lock: transaction.LOCK.UPDATE, + transaction + }); + + await delay(10); + + await Task.update( + { id: to }, + { + where: { id: { [Sequelize.Op.ne]: to } }, + lock: transaction.LOCK.UPDATE, + transaction } - }, - lock: 'UPDATE', - transaction - }); + ); + } catch (e) { + console.log(e.message); + } - await delay(10); - - await Task.update({ id: to }, { - where: { - id: { - [Sequelize.Op.ne]: to - } - }, - lock: transaction.LOCK.UPDATE, - transaction - }); + await Task.create({ id: 2 }, { transaction }); } catch (e) { console.log(e.message); } - await Task.create({ id: 2 }, { transaction }); - } catch (e) { - console.log(e.message); - } - - throw new Error('Rollback!'); - }).catch(() => {}); + throw new Error('Rollback!'); + }) + .catch(() => {}); + }; - await this.sequelize.sync({ force: true }); - await Task.create({ id: 0 }); - await Task.create({ id: 1 }); + it('should treat deadlocked transaction as rollback', async function() { + const Task = await getAndInitializeTaskModel(this.sequelize); - await Promise.all([ - update(1, 0), - update(0, 1) - ]); + // This gets called twice simultaneously, and we expect at least one of the calls to encounter a + // deadlock (which effectively rolls back the active transaction). + // We only expect createTask() to insert rows if a transaction is active. If deadlocks are handled + // properly, it should only execute a query if we're actually inside a real transaction. If it does + // execute a query, we expect the newly-created rows to be destroyed when we forcibly rollback by + // throwing an error. + // tl;dr; This test is designed to ensure that this function never inserts and commits a new row. + await Promise.all([update(this.sequelize, Task, 1, 0), update(this.sequelize, Task, 0, 1)]); const count = await Task.count(); // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); }); + + it('should release the connection for a deadlocked transaction', async function() { + const Task = await getAndInitializeTaskModel(this.sequelize); + + // 1 of 2 queries should deadlock and be rolled back by InnoDB + this.sinon.spy(this.sequelize.connectionManager, 'releaseConnection'); + await Promise.all([update(this.sequelize, Task, 1, 0), update(this.sequelize, Task, 0, 1)]); + + // Verify that both of the connections were released + expect(this.sequelize.connectionManager.releaseConnection.callCount).to.equal(2); + + // Verify that a follow-up READ_COMMITTED works as expected. + // For unknown reasons, we need to explicitly rollback on MariaDB, + // even though the transaction should've automatically been rolled + // back. + // Otherwise, this READ_COMMITTED doesn't work as expected. + const User = this.sequelize.define('user', { + username: Support.Sequelize.STRING + }); + await this.sequelize.sync({ force: true }); + await this.sequelize.transaction( + { isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, + async transaction => { + const users0 = await User.findAll({ transaction }); + expect(users0).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + expect(users).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction + } + ); + }); }); } @@ -576,13 +611,16 @@ if (current.dialect.supports.transactions) { await expect( this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, async transaction => { - const users0 = await User.findAll({ transaction }); - await expect( users0 ).to.have.lengthOf(0); - await User.create({ username: 'jan' }); // Create a User outside of the transaction - const users = await User.findAll({ transaction }); - return expect( users ).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction - }); + return this.sequelize.transaction( + { isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, + async transaction => { + const users0 = await User.findAll({ transaction }); + expect(users0).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + expect(users).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction + } + ); }) ).to.eventually.be.fulfilled; }); From e5b8929d14282c1a2017f5bb4baf3f4721311219 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Mon, 25 Jan 2021 10:49:16 -0300 Subject: [PATCH 255/414] fix(types): allow changing values on before hooks (#12970) --- types/lib/hooks.d.ts | 2 +- types/lib/model.d.ts | 70 +++++++-------- types/test/hooks.ts | 103 +++++++++++++---------- types/test/type-helpers/deep-writable.ts | 48 +++++++++++ 4 files changed, 142 insertions(+), 81 deletions(-) create mode 100644 types/test/type-helpers/deep-writable.ts diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index e3ea9f76955a..66881456e953 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -40,7 +40,7 @@ export interface ModelHooks { instance: M, options: InstanceUpdateOptions | CreateOptions ): HookReturn; - beforeBulkCreate(instances: readonly M[], options: BulkCreateOptions): HookReturn; + beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; afterBulkCreate(instances: readonly M[], options: BulkCreateOptions): HookReturn; beforeBulkDestroy(options: DestroyOptions): HookReturn; afterBulkDestroy(options: DestroyOptions): HookReturn; diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 29c3f16c86cf..71a6c499acd1 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -70,7 +70,7 @@ export interface Paranoid { paranoid?: boolean; } -export type GroupOption = string | Fn | Col | readonly (string | Fn | Col)[]; +export type GroupOption = string | Fn | Col | (string | Fn | Col)[]; /** * Options to pass to Model on drop @@ -126,7 +126,7 @@ export interface AnyOperator { [Op.any]: readonly (string | number)[]; } -/** Undocumented? */ +/** TODO: Undocumented? */ export interface AllOperator { [Op.all]: readonly (string | number | Date | Literal)[]; } @@ -444,7 +444,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * Load further nested related models */ - include?: readonly Includeable[]; + include?: Includeable[]; /** * Order include. Only available when setting `separate` to true. @@ -464,16 +464,16 @@ export type OrderItem = | Fn | Col | Literal - | readonly [OrderItemColumn, string] - | readonly [OrderItemAssociation, OrderItemColumn] - | readonly [OrderItemAssociation, OrderItemColumn, string] - | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] - | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] - | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] - | readonly [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] -export type Order = string | Fn | Col | Literal | readonly OrderItem[]; + | [OrderItemColumn, string] + | [OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] +export type Order = string | Fn | Col | Literal | OrderItem[]; /** * Please note if this is used the aliased property will not be available on the model instance @@ -482,26 +482,26 @@ export type Order = string | Fn | Col | Literal | readonly OrderItem[]; export type ProjectionAlias = readonly [string | Literal | Fn, string]; export type FindAttributeOptions = - | readonly (string | ProjectionAlias)[] + | (string | ProjectionAlias)[] | { - exclude: readonly string[]; - include?: readonly (string | ProjectionAlias)[]; + exclude: string[]; + include?: (string | ProjectionAlias)[]; } | { - exclude?: readonly string[]; - include: readonly (string | ProjectionAlias)[]; + exclude?: string[]; + include: (string | ProjectionAlias)[]; }; export interface IndexHint { type: IndexHints; - values: readonly string[]; + values: string[]; } export interface IndexHintable { /** * MySQL only. */ - indexHints?: readonly IndexHint[]; + indexHints?: IndexHint[]; } type Omit = Pick> @@ -521,7 +521,7 @@ export interface FindOptions * If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in * the as attribute when eager loading Y). */ - include?: Includeable | readonly Includeable[]; + include?: Includeable | Includeable[]; /** * Specifies an ordering. If a string is provided, it will be escaped. Using an array, you can provide @@ -592,7 +592,7 @@ export interface CountOptions /** * Include options. See `find` for details */ - include?: Includeable | readonly Includeable[]; + include?: Includeable | Includeable[]; /** * Apply COUNT(DISTINCT(col)) @@ -645,7 +645,7 @@ export interface BuildOptions { * * TODO: See set */ - include?: Includeable | readonly Includeable[]; + include?: Includeable | Includeable[]; } export interface Silent { @@ -664,7 +664,7 @@ export interface CreateOptions extends BuildOptions, Logging, /** * If set, only columns matching those in fields will be saved */ - fields?: readonly (keyof TAttributes)[]; + fields?: (keyof TAttributes)[]; /** * On Duplicate @@ -699,7 +699,7 @@ export interface FindOrCreateOptions extends Logging, Transactionab /** * The fields to insert / update. Defaults to all fields */ - fields?: readonly (keyof TAttributes)[]; + fields?: (keyof TAttributes)[]; /** * Return the affected rows (only for postgres) @@ -733,7 +733,7 @@ export interface BulkCreateOptions extends Logging, Transacti /** * Fields to insert (defaults to all fields) */ - fields?: readonly (keyof TAttributes)[]; + fields?: (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -758,17 +758,17 @@ export interface BulkCreateOptions extends Logging, Transacti * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. */ - updateOnDuplicate?: readonly (keyof TAttributes)[]; + updateOnDuplicate?: (keyof TAttributes)[]; /** * Include options. See `find` for details */ - include?: Includeable | readonly Includeable[]; + include?: Includeable | Includeable[]; /** * Return all columns or only the specified columns for the affected rows (only for postgres) */ - returning?: boolean | readonly (keyof TAttributes)[]; + returning?: boolean | (keyof TAttributes)[]; } /** @@ -846,7 +846,7 @@ export interface UpdateOptions extends Logging, Transactionab /** * Fields to update (defaults to all fields) */ - fields?: readonly (keyof TAttributes)[]; + fields?: (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -969,7 +969,7 @@ export interface SaveOptions extends Logging, Transactionable * An optional array of strings, representing database columns. If fields is provided, only those columns * will be validated and saved. */ - fields?: readonly (keyof TAttributes)[]; + fields?: (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -2297,11 +2297,11 @@ export abstract class Model( this: ModelStatic, name: string, - fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn + fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; public static beforeBulkCreate( this: ModelStatic, - fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn + fn: (instances: M[], options: BulkCreateOptions) => HookReturn ): void; /** @@ -2812,7 +2812,7 @@ export abstract class Model = { - beforeSave(m, options) { - expectTypeOf(m).toEqualTypeOf(); - expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? - }, - afterSave(m, options) { - expectTypeOf(m).toEqualTypeOf(); - expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? - }, - afterFind(m, options) { - expectTypeOf(m).toEqualTypeOf(); - expectTypeOf(options).toEqualTypeOf(); - } -}; - -export const sequelize = new Sequelize('uri', { hooks }); -TestModel.init({}, { sequelize, hooks }); - -TestModel.addHook('beforeSave', hooks.beforeSave!); -TestModel.addHook('afterSave', hooks.afterSave!); -TestModel.addHook('afterFind', hooks.afterFind!); - -/* - * covers types/lib/model.d.ts - */ - -TestModel.beforeSave(hooks.beforeSave!); -TestModel.afterSave(hooks.afterSave!); -TestModel.afterFind(hooks.afterFind!); - -/* - * covers types/lib/sequelize.d.ts - */ - -Sequelize.beforeSave(hooks.beforeSave!); -Sequelize.afterSave(hooks.afterSave!); -Sequelize.afterFind(hooks.afterFind!); -Sequelize.afterFind('namedAfterFind', hooks.afterFind!); +{ + class TestModel extends Model {} + + const hooks: Partial = { + beforeSave(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? + }, + afterSave(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? + }, + afterFind(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + } + }; + + const sequelize = new Sequelize('uri', { hooks }); + TestModel.init({}, { sequelize, hooks }); + + TestModel.addHook('beforeSave', hooks.beforeSave!); + TestModel.addHook('afterSave', hooks.afterSave!); + TestModel.addHook('afterFind', hooks.afterFind!); + + TestModel.beforeSave(hooks.beforeSave!); + TestModel.afterSave(hooks.afterSave!); + TestModel.afterFind(hooks.afterFind!); + + Sequelize.beforeSave(hooks.beforeSave!); + Sequelize.afterSave(hooks.afterSave!); + Sequelize.afterFind(hooks.afterFind!); + Sequelize.afterFind('namedAfterFind', hooks.afterFind!); +} + +// #12959 +{ + const hooks: ModelHooks = 0 as any; + + hooks.beforeValidate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeCreate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeDestroy = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeRestore = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeUpdate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeSave = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkCreate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkDestroy = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkRestore = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkUpdate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeFind = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeCount = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeFindAfterExpandIncludeAll = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeFindAfterOptions = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; +} diff --git a/types/test/type-helpers/deep-writable.ts b/types/test/type-helpers/deep-writable.ts new file mode 100644 index 000000000000..19d34e5db1eb --- /dev/null +++ b/types/test/type-helpers/deep-writable.ts @@ -0,0 +1,48 @@ +/** + * Adapted from krzkaczor/ts-essentials + * + * https://github.com/krzkaczor/ts-essentials/blob/v7.1.0/lib/types.ts#L165 + * + * Thank you! + */ + +import { Model, Sequelize, ModelCtor, ModelType, ModelDefined, ModelStatic } from "sequelize"; + +type Builtin = string | number | boolean | bigint | symbol | undefined | null | Function | Date | Error | RegExp; +type SequelizeBasic = Builtin | Sequelize | Model | ModelCtor | ModelType | ModelDefined | ModelStatic; + +// type ToMutableArrayIfNeeded = T extends readonly any[] +// ? { -readonly [K in keyof T]: ToMutableArrayIfNeeded } +// : T; + +type NoReadonlyArraysDeep = T extends SequelizeBasic + ? T + : T extends readonly any[] + ? { -readonly [K in keyof T]: NoReadonlyArraysDeep } + : T extends Record + ? { [K in keyof T]: NoReadonlyArraysDeep } + : T; + +type ShallowWritable = T extends Record ? { -readonly [K in keyof T]: T[K] } : T; + +export type SemiDeepWritable = ShallowWritable>; + +export type DeepWritable = T extends SequelizeBasic + ? T + : T extends Map + ? Map, DeepWritable> + : T extends ReadonlyMap + ? Map, DeepWritable> + : T extends WeakMap + ? WeakMap, DeepWritable> + : T extends Set + ? Set> + : T extends ReadonlySet + ? Set> + : T extends WeakSet + ? WeakSet> + : T extends Promise + ? Promise> + : T extends {} + ? { -readonly [K in keyof T]: DeepWritable } + : T; From 2fe980e2bc3f495ed1ccdc9ee2debb112cd3ddd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20B=C3=BCschel?= <13087421+tobiasbueschel@users.noreply.github.com> Date: Mon, 25 Jan 2021 21:59:13 +0800 Subject: [PATCH 256/414] fix(types): typo in sequelize.js and sequelize.d.ts (#12975) --- lib/sequelize.js | 2 +- types/lib/sequelize.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index f10c66bb5590..b074beef1710 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -167,7 +167,7 @@ class Sequelize { * @param {object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. * @param {object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. * @param {boolean} [options.minifyAliases=false] A flag that defines if aliases should be minified (mostly useful to avoid Postgres alias character limit of 64) - * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind patameters in log. + * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind parameters in log. */ constructor(database, username, password, options) { let config; diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 44976d55bc91..f03d916025f5 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -382,7 +382,7 @@ export interface Options extends Logging { minifyAliases?: boolean; /** - * Set to `true` to show bind patameters in log. + * Set to `true` to show bind parameters in log. * * @default false */ From 416a7ceef35c9841668005ee0d133c733188ffd8 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 25 Jan 2021 13:08:24 -0300 Subject: [PATCH 257/414] test(model): fix flaky timestamp test --- test/integration/model/create.test.js | 35 ++++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index ee7007f116e3..00e1ebfee742 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -9,6 +9,7 @@ const chai = require('chai'), dialect = Support.getTestDialect(), Op = Sequelize.Op, _ = require('lodash'), + delay = require('delay'), assert = require('assert'), current = Support.sequelize, pTimeout = require('p-timeout'); @@ -724,12 +725,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); await this.sequelize.sync({ force: true }); - const user = await User.create({}); - expect(user).to.be.ok; - expect(user.created_time).to.be.ok; - expect(user.updated_time).to.be.ok; - expect(user.created_time.getMilliseconds()).not.to.equal(0); - expect(user.updated_time.getMilliseconds()).not.to.equal(0); + + const user1 = await User.create({}); + await delay(10); + const user2 = await User.create({}); + + for (const user in [user1, user2]) { + expect(user).to.be.ok; + expect(user.created_time).to.be.ok; + expect(user.updated_time).to.be.ok; + } + + // Timestamps should have milliseconds. However, there is a small chance that + // it really is 0 for one of them, by coincidence. So we check twice with two + // users created almost at the same time. + expect([ + user1.created_time.getMilliseconds(), + user2.created_time.getMilliseconds() + ]).not.to.deep.equal([0, 0]); + expect([ + user1.updated_time.getMilliseconds(), + user2.updated_time.getMilliseconds() + ]).not.to.deep.equal([0, 0]); }); it('works with custom timestamps and underscored', async function() { @@ -1423,7 +1440,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.returnValues) { it('should return default value set by the database (create)', async function() { - + const User = this.sequelize.define('User', { name: DataTypes.STRING, code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } @@ -1432,9 +1449,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { await User.sync({ force: true }); const user = await User.create({ name: 'FooBar' }); - + expect(user.name).to.be.equal('FooBar'); expect(user.code).to.be.equal(2020); }); - } + } }); From 9013836fc9a3596d778fb4134abb8b16fa492125 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 25 Jan 2021 13:43:04 -0300 Subject: [PATCH 258/414] test(model): fix flaky timestamp test (2) Fixes mistake introduced in 416a7ce --- test/integration/model/create.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 00e1ebfee742..53bc8f3bd150 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -730,7 +730,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { await delay(10); const user2 = await User.create({}); - for (const user in [user1, user2]) { + for (const user of [user1, user2]) { expect(user).to.be.ok; expect(user.created_time).to.be.ok; expect(user.updated_time).to.be.ok; From e45df29dd8a65fcb9d11b654e43f8553924b0d8d Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Wed, 27 Jan 2021 00:37:49 -0300 Subject: [PATCH 259/414] feat(postgres): add TSVECTOR datatype and `@@` operator (#12955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jano Kacer Co-authored-by: Sébastien BRAMILLE <2752200+oktapodia@users.noreply.github.com> --- docs/manual/core-concepts/model-basics.md | 1 + .../core-concepts/model-querying-basics.md | 1 + lib/data-types.js | 18 ++++++++++++- .../abstract/query-generator/operators.js | 3 ++- lib/dialects/postgres/data-types.js | 1 + lib/dialects/postgres/index.js | 1 + lib/operators.js | 3 ++- test/integration/data-types.test.js | 12 +++++++++ test/integration/model/create.test.js | 25 +++++++++++++++++++ test/integration/model/findOne.test.js | 16 ++++++++++++ test/unit/sql/where.test.js | 14 +++++++++++ 11 files changed, 92 insertions(+), 3 deletions(-) diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md index 99b2c209a64b..db4075280dec 100644 --- a/docs/manual/core-concepts/model-basics.md +++ b/docs/manual/core-concepts/model-basics.md @@ -270,6 +270,7 @@ DataTypes.STRING.BINARY // VARCHAR BINARY DataTypes.TEXT // TEXT DataTypes.TEXT('tiny') // TINYTEXT DataTypes.CITEXT // CITEXT PostgreSQL and SQLite only. +DataTypes.TSVECTOR // TSVECTOR PostgreSQL only. ``` ### Boolean diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index 71adc7657853..c10cf75d10d6 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -261,6 +261,7 @@ Post.findAll({ [Op.notIRegexp]: '^[h|a|t]', // !~* '^[h|a|t]' (PG only) [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // match text search for strings 'fat' and 'rat' (PG only) // In Postgres, Op.like/Op.iLike/Op.notLike can be combined to Op.any: [Op.like]: { [Op.any]: ['cat', 'hat'] } // LIKE ANY ARRAY['cat', 'hat'] diff --git a/lib/data-types.js b/lib/data-types.js index 655162fe2008..36e520f5f2f7 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -940,6 +940,21 @@ class MACADDR extends ABSTRACT { } } +/** + * The TSVECTOR type stores text search vectors. + * + * Only available for Postgres + * + */ +class TSVECTOR extends ABSTRACT { + validate(value) { + if (typeof value !== 'string') { + throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value)); + } + return true; + } +} + /** * A convenience class holding commonly used data types. The data types are used when defining a new model using `Sequelize.define`, like this: * ```js @@ -1023,7 +1038,8 @@ const DataTypes = module.exports = { CIDR, INET, MACADDR, - CITEXT + CITEXT, + TSVECTOR }; _.each(DataTypes, (dataType, name) => { diff --git a/lib/dialects/abstract/query-generator/operators.js b/lib/dialects/abstract/query-generator/operators.js index baef21654810..91d3caa2bcc0 100644 --- a/lib/dialects/abstract/query-generator/operators.js +++ b/lib/dialects/abstract/query-generator/operators.js @@ -42,7 +42,8 @@ const OperatorHelpers = { [Op.and]: ' AND ', [Op.or]: ' OR ', [Op.col]: 'COL', - [Op.placeholder]: '$$PLACEHOLDER$$' + [Op.placeholder]: '$$PLACEHOLDER$$', + [Op.match]: '@@' }, OperatorsAliasMap: {}, diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js index 70411f7a95c8..17eaa70a1a65 100644 --- a/lib/dialects/postgres/data-types.js +++ b/lib/dialects/postgres/data-types.js @@ -36,6 +36,7 @@ module.exports = BaseTypes => { BaseTypes.CIDR.types.postgres = ['cidr']; BaseTypes.INET.types.postgres = ['inet']; BaseTypes.MACADDR.types.postgres = ['macaddr']; + BaseTypes.TSVECTOR.types.postgres = ['tsvector']; BaseTypes.JSON.types.postgres = ['json']; BaseTypes.JSONB.types.postgres = ['jsonb']; BaseTypes.TIME.types.postgres = ['time']; diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index d813c19cae96..3d23f6ed6c6d 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -57,6 +57,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototy JSON: true, JSONB: true, HSTORE: true, + TSVECTOR: true, deferrableConstraints: true, searchPath: true }); diff --git a/lib/operators.js b/lib/operators.js index a5b55f9b377a..7e8bb7cf9cdb 100644 --- a/lib/operators.js +++ b/lib/operators.js @@ -84,7 +84,8 @@ const Op = { values: Symbol.for('values'), col: Symbol.for('col'), placeholder: Symbol.for('placeholder'), - join: Symbol.for('join') + join: Symbol.for('join'), + match: Symbol.for('match') }; module.exports = Op; diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js index b18aa58d4072..ddd8e3a5b52d 100644 --- a/test/integration/data-types.test.js +++ b/test/integration/data-types.test.js @@ -362,6 +362,18 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { }); + if (current.dialect.supports.TSVECTOR) { + it('calls parse and stringify for TSVECTOR', async () => { + const Type = new Sequelize.TSVECTOR(); + + if (['postgres'].includes(dialect)) { + await testSuccess(Type, 'swagger'); + } else { + testFailure(Type); + } + }); + } + it('calls parse and stringify for ENUM', async () => { const Type = new Sequelize.ENUM('hat', 'cat'); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index 53bc8f3bd150..d495ceb7a933 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -1007,6 +1007,31 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); } + if (dialect === 'postgres') { + it('allows the creation of a TSVECTOR field', async function() { + const User = this.sequelize.define('UserWithTSVECTOR', { + name: Sequelize.TSVECTOR + }); + + await User.sync({ force: true }); + await User.create({ name: 'John Doe' }); + }); + + it('TSVECTOR only allow string', async function() { + const User = this.sequelize.define('UserWithTSVECTOR', { + username: { type: Sequelize.TSVECTOR } + }); + + try { + await User.sync({ force: true }); + await User.create({ username: 42 }); + } catch (err) { + if (!(err instanceof Sequelize.ValidationError)) throw err; + expect(err).to.be.ok; + } + }); + } + if (current.dialect.supports.index.functionBased) { it("doesn't allow duplicated records with unique function based indexes", async function() { const User = this.sequelize.define('UserWithUniqueUsernameFunctionIndex', { diff --git a/test/integration/model/findOne.test.js b/test/integration/model/findOne.test.js index a2b6d38fa023..07351f9a9da6 100644 --- a/test/integration/model/findOne.test.js +++ b/test/integration/model/findOne.test.js @@ -273,6 +273,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.username).to.equal('longUserNAME'); }); } + + if (dialect === 'postgres') { + it('should allow case-sensitive find on TSVECTOR type', async function() { + const User = this.sequelize.define('UserWithCaseInsensitiveName', { + username: Sequelize.TSVECTOR + }); + + await User.sync({ force: true }); + await User.create({ username: 'longUserNAME' }); + const user = await User.findOne({ + where: { username: 'longUserNAME' } + }); + expect(user).to.exist; + expect(user.username).to.equal("'longUserNAME'"); + }); + } }); describe('eager loading', () => { diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js index 98ee3d3c8f8f..779d7828c85b 100644 --- a/test/unit/sql/where.test.js +++ b/test/unit/sql/where.test.js @@ -1206,6 +1206,20 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } } + if (current.dialect.supports.TSVESCTOR) { + describe('Op.match', () => { + testsql( + 'username', + { + [Op.match]: Support.sequelize.fn('to_tsvector', 'swagger') + }, + { + postgres: "[username] @@ to_tsvector('swagger')" + } + ); + }); + } + describe('fn', () => { it('{name: this.sequelize.fn(\'LOWER\', \'DERP\')}', function() { expectsql(sql.whereQuery({ name: this.sequelize.fn('LOWER', 'DERP') }), { From 4063c2ab627ad57919d5b45cc7755f077a69fa5e Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Wed, 27 Jan 2021 00:49:45 -0300 Subject: [PATCH 260/414] refactor(query): similar code for mysql and mariadb (#12981) --- lib/dialects/mariadb/query.js | 50 ++++++++++++++---------------- lib/dialects/mysql/query.js | 57 +++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index f3bd5d6fc9eb..d1dd48394e01 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -19,15 +19,14 @@ class Query extends AbstractQuery { static formatBindParameters(sql, values, dialect) { const bindParam = []; - const replacementFunc = (match, key, val) => { - if (val[key] !== undefined) { - bindParam.push(val[key]); + const replacementFunc = (match, key, values_) => { + if (values_[key] !== undefined) { + bindParam.push(values_[key]); return '?'; } return undefined; }; - sql = AbstractQuery.formatBindParameters(sql, values, dialect, - replacementFunc)[0]; + sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; return [sql, bindParam.length > 0 ? bindParam : undefined]; } @@ -35,14 +34,14 @@ class Query extends AbstractQuery { this.sql = sql; const { connection, options } = this; - const showWarnings = this.sequelize.options.showWarnings - || options.showWarnings; + const showWarnings = this.sequelize.options.showWarnings || options.showWarnings; const complete = this._logQuery(sql, debug, parameters); if (parameters) { debug('parameters(%j)', parameters); } + let results; try { @@ -120,13 +119,14 @@ class Query extends AbstractQuery { if (!this.instance) { // handle bulkCreate AI primary key - if (this.model + if ( + this.model && this.model.autoIncrementAttribute && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute && this.model.rawAttributes[this.model.primaryKeyAttribute] ) { - //ONLY TRUE IF @auto_increment_increment is set to 1 !! - //Doesn't work with GALERA => each node will reserve increment (x for first server, x+1 for next node ... + // ONLY TRUE IF @auto_increment_increment is set to 1 !! + // Doesn't work with GALERA => each node will reserve increment (x for first server, x+1 for next node...) const startId = data[this.getInsertIdField()]; result = new Array(data.affectedRows); const pkField = this.model.rawAttributes[this.model.primaryKeyAttribute].field; @@ -195,7 +195,7 @@ class Query extends AbstractQuery { for (const _field of Object.keys(this.model.fieldRawAttributesMap)) { const modelField = this.model.fieldRawAttributesMap[_field]; if (modelField.type instanceof DataTypes.JSON) { - //value is return as String, no JSON + // Value is returned as String, not JSON rows = rows.map(row => { row[modelField.fieldName] = row[modelField.fieldName] ? JSON.parse( row[modelField.fieldName]) : null; @@ -211,12 +211,10 @@ class Query extends AbstractQuery { async logWarnings(results) { const warningResults = await this.run('SHOW WARNINGS'); - const warningMessage = `MariaDB Warnings (${this.connection.uuid - || 'default'}): `; + const warningMessage = `MariaDB Warnings (${this.connection.uuid || 'default'}): `; const messages = []; for (const _warningRow of warningResults) { - if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] - !== 'function') { + if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') { continue; } for (const _warningResult of _warningRow) { @@ -224,8 +222,7 @@ class Query extends AbstractQuery { messages.push(_warningResult.Message); } else { for (const _objectKey of _warningResult.keys()) { - messages.push( - [_objectKey, _warningResult[_objectKey]].join(': ')); + messages.push([_objectKey, _warningResult[_objectKey]].join(': ')); } } } @@ -250,9 +247,7 @@ class Query extends AbstractQuery { const uniqueKey = this.model && this.model.uniqueKeys[fieldKey]; if (uniqueKey) { - if (uniqueKey.msg) { - message = uniqueKey.msg; - } + if (uniqueKey.msg) message = uniqueKey.msg; fields = _.zipObject(uniqueKey.fields, values); } else { fields[fieldKey] = fieldVal; @@ -270,24 +265,23 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError( - { message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); } case ER_ROW_IS_REFERENCED: case ER_NO_REFERENCED_ROW: { // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) const match = err.message.match( - /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/); + /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/ + ); const quoteChar = match ? match[1] : '`'; - const fields = match ? match[3].split( - new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; + const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; + return new sequelizeErrors.ForeignKeyConstraintError({ - reltype: err.errno === 1451 ? 'parent' : 'child', + reltype: err.errno === ER_ROW_IS_REFERENCED ? 'parent' : 'child', table: match ? match[4] : undefined, fields, - value: fields && fields.length && this.instance - && this.instance[fields[0]] || undefined, + value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined, index: match ? match[2] : undefined, parent: err }); diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index a6e3dbc89951..41ea95ef29d8 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -5,8 +5,11 @@ const sequelizeErrors = require('../../errors'); const _ = require('lodash'); const { logger } = require('../../utils/logger'); -const debug = logger.debugContext('sql:mysql'); +const ER_DUP_ENTRY = 1062; +const ER_ROW_IS_REFERENCED = 1451; +const ER_NO_REFERENCED_ROW = 1452; +const debug = logger.debugContext('sql:mysql'); class Query extends AbstractQuery { constructor(connection, sequelize, options) { @@ -15,9 +18,9 @@ class Query extends AbstractQuery { static formatBindParameters(sql, values, dialect) { const bindParam = []; - const replacementFunc = (match, key, values) => { - if (values[key] !== undefined) { - bindParam.push(values[key]); + const replacementFunc = (match, key, values_) => { + if (values_[key] !== undefined) { + bindParam.push(values_[key]); return '?'; } return undefined; @@ -30,37 +33,46 @@ class Query extends AbstractQuery { this.sql = sql; const { connection, options } = this; - //do we need benchmark for this query execution const showWarnings = this.sequelize.options.showWarnings || options.showWarnings; const complete = this._logQuery(sql, debug, parameters); - const query = parameters && parameters.length - ? new Promise((resolve, reject) => connection.execute(sql, parameters, (error, result) => error ? reject(error) : resolve(result)).setMaxListeners(100)) - : new Promise((resolve, reject) => connection.query({ sql }, (error, result) => error ? reject(error) : resolve(result)).setMaxListeners(100)); + if (parameters) { + debug('parameters(%j)', parameters); + } let results; try { - results = await query; + if (parameters && parameters.length) { + results = await new Promise((resolve, reject) => { + connection + .execute(sql, parameters, (error, result) => error ? reject(error) : resolve(result)) + .setMaxListeners(100); + }); + } else { + results = await new Promise((resolve, reject) => { + connection + .query({ sql }, (error, result) => error ? reject(error) : resolve(result)) + .setMaxListeners(100); + }); + } } catch (err) { // MySQL automatically rolls-back transactions in the event of a deadlock if (options.transaction && err.errno === 1213) { options.transaction.finished = 'rollback'; } + err.sql = sql; err.parameters = parameters; - throw this.formatError(err); } complete(); - // Log warnings if we've got them. if (showWarnings && results && results.warningStatus > 0) { await this.logWarnings(results); } - // Return formatted results... return this.formatResults(results); } @@ -88,7 +100,7 @@ class Query extends AbstractQuery { this.handleInsertQuery(data); if (!this.instance) { - // handle bulkCreate AI primiary key + // handle bulkCreate AI primary key if ( data.constructor.name === 'ResultSetHeader' && this.model @@ -123,7 +135,8 @@ class Query extends AbstractQuery { allowNull: _result.Null === 'YES', defaultValue: _result.Default, primaryKey: _result.Key === 'PRI', - autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') && _result.Extra.toLowerCase() === 'auto_increment', + autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') + && _result.Extra.toLowerCase() === 'auto_increment', comment: _result.Comment ? _result.Comment : null }; } @@ -166,7 +179,9 @@ class Query extends AbstractQuery { const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `; const messages = []; for (const _warningRow of warningResults) { - if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') continue; + if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') { + continue; + } for (const _warningResult of _warningRow) { if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { messages.push(_warningResult.Message); @@ -187,7 +202,7 @@ class Query extends AbstractQuery { const errCode = err.errno || err.code; switch (errCode) { - case 1062: { + case ER_DUP_ENTRY: { const match = err.message.match(/Duplicate entry '([\s\S]*)' for key '?((.|\s)*?)'?$/); let fields = {}; let message = 'Validation error'; @@ -218,15 +233,17 @@ class Query extends AbstractQuery { return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); } - case 1451: - case 1452: { + case ER_ROW_IS_REFERENCED: + case ER_NO_REFERENCED_ROW: { // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) - const match = err.message.match(/CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/); + const match = err.message.match( + /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/ + ); const quoteChar = match ? match[1] : '`'; const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; return new sequelizeErrors.ForeignKeyConstraintError({ - reltype: String(errCode) === '1451' ? 'parent' : 'child', + reltype: String(errCode) === String(ER_ROW_IS_REFERENCED) ? 'parent' : 'child', table: match ? match[4] : undefined, fields, value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined, From 29901187d9560e7d51ae1f9b5f411cf0c5d8994a Mon Sep 17 00:00:00 2001 From: Shivam Kalra Date: Thu, 4 Mar 2021 20:29:38 +0530 Subject: [PATCH 261/414] docs(geometry): update link (#13067 #12937) Co-authored-by: john gravois --- lib/data-types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data-types.js b/lib/data-types.js index 36e520f5f2f7..e96e334180c7 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -794,7 +794,7 @@ class ARRAY extends ABSTRACT { * In PostGIS, the GeoJSON is parsed using the PostGIS function `ST_GeomFromGeoJSON`. * In MySQL it is parsed using the function `GeomFromText`. * - * Therefore, one can just follow the [GeoJSON spec](http://geojson.org/geojson-spec.html) for handling geometry objects. See the following examples: + * Therefore, one can just follow the [GeoJSON spec](https://tools.ietf.org/html/rfc7946) for handling geometry objects. See the following examples: * * @example * DataTypes.GEOMETRY From 801caa36e1263c3c9cb306c3139e2aae3ede05d4 Mon Sep 17 00:00:00 2001 From: Shivam Kalra Date: Wed, 10 Mar 2021 08:25:20 +0530 Subject: [PATCH 262/414] docs(getters-setters-virtuals): fix typos (#13074 #12811 #12655) Co-authored-by: sliterok Co-authored-by: William Gurzoni --- docs/manual/core-concepts/getters-setters-virtuals.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/manual/core-concepts/getters-setters-virtuals.md b/docs/manual/core-concepts/getters-setters-virtuals.md index b171a02e156b..c280dc3ae344 100644 --- a/docs/manual/core-concepts/getters-setters-virtuals.md +++ b/docs/manual/core-concepts/getters-setters-virtuals.md @@ -15,7 +15,7 @@ const User = sequelize.define('user', { username: { type: DataTypes.STRING, get() { - const rawValue = this.getDataValue(username); + const rawValue = this.getDataValue('username'); return rawValue ? rawValue.toUpperCase() : null; } } @@ -27,10 +27,10 @@ This getter, just like a standard JavaScript getter, is called automatically whe ```js const user = User.build({ username: 'SuperUser123' }); console.log(user.username); // 'SUPERUSER123' -console.log(user.getDataValue(username)); // 'SuperUser123' +console.log(user.getDataValue('username')); // 'SuperUser123' ``` -Note that, although `SUPERUSER123` was logged above, the value truly stored in the database is still `SuperUser123`. We used `this.getDataValue(username)` to obtain this value, and converted it to uppercase. +Note that, although `SUPERUSER123` was logged above, the value truly stored in the database is still `SuperUser123`. We used `this.getDataValue('username')` to obtain this value, and converted it to uppercase. Had we tried to use `this.username` in the getter instead, we would have gotten an infinite loop! This is why Sequelize provides the `getDataValue` method. @@ -55,7 +55,7 @@ const User = sequelize.define('user', { ```js const user = User.build({ username: 'someone', password: 'NotSo§tr0ngP4$SW0RD!' }); console.log(user.password); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' -console.log(user.getDataValue(password)); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' +console.log(user.getDataValue('password')); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' ``` Observe that Sequelize called the setter automatically, before even sending data to the database. The only data the database ever saw was the already hashed value. From 76568950e13539205140a755df6fb638f29ef4a8 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Wed, 10 Mar 2021 00:34:44 -0300 Subject: [PATCH 263/414] test(dropEnum): minor refactor (#13084) --- test/integration/query-interface/dropEnum.test.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/test/integration/query-interface/dropEnum.test.js b/test/integration/query-interface/dropEnum.test.js index 595d347162c1..136e30331a5c 100644 --- a/test/integration/query-interface/dropEnum.test.js +++ b/test/integration/query-interface/dropEnum.test.js @@ -19,18 +19,9 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { describe('dropEnum', () => { beforeEach(async function() { await this.queryInterface.createTable('menus', { - structuretype: { - type: DataTypes.ENUM('menus', 'submenu', 'routine'), - allowNull: true - }, - sequence: { - type: DataTypes.INTEGER, - allowNull: true - }, - name: { - type: DataTypes.STRING, - allowNull: true - } + structuretype: DataTypes.ENUM('menus', 'submenu', 'routine'), + sequence: DataTypes.INTEGER, + name: DataTypes.STRING }); }); From 365c23afa36d8cf9cbdd2213dc3f7fa907d914c4 Mon Sep 17 00:00:00 2001 From: Thomas Hoppe Date: Thu, 11 Mar 2021 14:06:53 +0100 Subject: [PATCH 264/414] chore: fix docker-compose volume declaration (#13089) --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 31ea58d455e4..cdda5c535eb4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: MYSQL_USER: sequelize_test MYSQL_PASSWORD: sequelize_test volumes: - - $MARIADB_ENTRYPOINT:/docker-entrypoint-initdb.d + - $MARIADB_ENTRYPOINT/:/docker-entrypoint-initdb.d ports: - "8960:3306" container_name: mariadb-103 @@ -67,7 +67,7 @@ services: MYSQL_USER: sequelize_test MYSQL_PASSWORD: sequelize_test volumes: - - $MYSQLDB_ENTRYPOINT:/docker-entrypoint-initdb.d + - $MYSQLDB_ENTRYPOINT/:/docker-entrypoint-initdb.d ports: - "8980:3306" container_name: mysql-57 From ced4dc785f24ab1be167d75e50289c1316053e20 Mon Sep 17 00:00:00 2001 From: Wong Yong Jie Date: Sat, 13 Mar 2021 13:23:54 +0800 Subject: [PATCH 265/414] fix(types): allow transaction to be `null` (#13093) --- types/lib/model.d.ts | 2 +- types/test/transaction.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 71a6c499acd1..c296a146c179 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -35,7 +35,7 @@ export interface Transactionable { /** * Transaction to run query under */ - transaction?: Transaction; + transaction?: Transaction | null; } export interface SearchPathable { diff --git a/types/test/transaction.ts b/types/test/transaction.ts index 96bdb14574fd..b77cf12e4eaf 100644 --- a/types/test/transaction.ts +++ b/types/test/transaction.ts @@ -78,3 +78,9 @@ async function nestedTransact() { }); await tr.commit(); } + +async function excludeFromTransaction() { + await sequelize.transaction(async t => + await sequelize.query('SELECT 1', { transaction: null }) + ); +} From 6388507ebd916efe6adf3e6a94a4c5ce235dee2a Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sun, 14 Mar 2021 02:52:50 -0300 Subject: [PATCH 266/414] fix(mysql): release connection on deadlocks (#13102) * test(mysql, mariadb): improve transaction tests - Greatly improve test for `SELECT ... LOCK IN SHARE MODE` - Greatly improve test for deadlock handling * fix(mysql): release connection on deadlocks This is a follow-up for a problem not covered by #12841. * refactor(mariadb): `query.js` similar to mysql's * Update comments with a reference to this PR --- lib/dialects/mariadb/query.js | 36 +--- lib/dialects/mysql/query.js | 25 ++- package.json | 1 + test/integration/replication.test.js | 4 +- test/integration/transaction.test.js | 288 ++++++++++++++++++++++----- test/support.js | 16 +- 6 files changed, 281 insertions(+), 89 deletions(-) diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index d1dd48394e01..2f78761d91bc 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -7,6 +7,7 @@ const DataTypes = require('../../data-types'); const { logger } = require('../../utils/logger'); const ER_DUP_ENTRY = 1062; +const ER_DEADLOCK = 1213; const ER_ROW_IS_REFERENCED = 1451; const ER_NO_REFERENCED_ROW = 1452; @@ -46,40 +47,25 @@ class Query extends AbstractQuery { try { results = await connection.query(this.sql, parameters); - complete(); - - // Log warnings if we've got them. - if (showWarnings && results && results.warningStatus > 0) { - await this.logWarnings(results); - } - } catch (err) { - // MariaDB automatically rolls-back transactions in the event of a - // deadlock. - // - // Even though we shouldn't need to do this, we initiate a manual - // rollback. Without the rollback, the next transaction using the - // connection seems to retain properties of the previous transaction - // (e.g. isolation level) and not work as expected. - // - // For example (in our tests), a follow-up READ_COMMITTED transaction - // doesn't work as expected unless we explicitly rollback the - // transaction: it would fail to read a value inserted outside of that - // transaction. - if (options.transaction && err.errno === 1213) { + } catch (error) { + if (options.transaction && error.errno === ER_DEADLOCK) { + // MariaDB automatically rolls-back transactions in the event of a deadlock. + // However, we still initiate a manual rollback to ensure the connection gets released - see #13102. try { await options.transaction.rollback(); - } catch (err) { + } catch (error_) { // Ignore errors - since MariaDB automatically rolled back, we're // not that worried about this redundant rollback failing. } + options.transaction.finished = 'rollback'; } + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error); + } finally { complete(); - - err.sql = sql; - err.parameters = parameters; - throw this.formatError(err); } if (showWarnings && results && results.warningStatus > 0) { diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index 41ea95ef29d8..a0745a799bcc 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -6,6 +6,7 @@ const _ = require('lodash'); const { logger } = require('../../utils/logger'); const ER_DUP_ENTRY = 1062; +const ER_DEADLOCK = 1213; const ER_ROW_IS_REFERENCED = 1451; const ER_NO_REFERENCED_ROW = 1452; @@ -57,19 +58,27 @@ class Query extends AbstractQuery { .setMaxListeners(100); }); } - } catch (err) { - // MySQL automatically rolls-back transactions in the event of a deadlock - if (options.transaction && err.errno === 1213) { + } catch (error) { + if (options.transaction && error.errno === ER_DEADLOCK) { + // MySQL automatically rolls-back transactions in the event of a deadlock. + // However, we still initiate a manual rollback to ensure the connection gets released - see #13102. + try { + await options.transaction.rollback(); + } catch (error_) { + // Ignore errors - since MySQL automatically rolled back, we're + // not that worried about this redundant rollback failing. + } + options.transaction.finished = 'rollback'; } - err.sql = sql; - err.parameters = parameters; - throw this.formatError(err); + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error); + } finally { + complete(); } - complete(); - if (showWarnings && results && results.warningStatus > 0) { await this.logWarnings(results); } diff --git a/package.json b/package.json index 56987cebb78f..6d9004d3b9c1 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "nyc": "^15.0.0", "p-map": "^4.0.0", "p-props": "^4.0.0", + "p-settle": "^4.1.1", "p-timeout": "^4.0.0", "pg": "^8.2.1", "pg-hstore": "^2.x", diff --git a/test/integration/replication.test.js b/test/integration/replication.test.js index 65c1ba483351..1ba98bb12912 100644 --- a/test/integration/replication.test.js +++ b/test/integration/replication.test.js @@ -18,8 +18,8 @@ describe(Support.getTestDialectTeaser('Replication'), () => { this.sequelize = Support.getSequelizeInstance(null, null, null, { replication: { - write: Support.getConnectionOptions(), - read: [Support.getConnectionOptions()] + write: Support.getConnectionOptionsWithoutPool(), + read: [Support.getConnectionOptionsWithoutPool()] } }); diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index fce1e74a8468..d4605dca3caf 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -1,15 +1,14 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - dialect = Support.getTestDialect(), - Sequelize = require('../../index'), - QueryTypes = require('../../lib/query-types'), - Transaction = require('../../lib/transaction'), - sinon = require('sinon'), - current = Support.sequelize, - delay = require('delay'); +const chai = require('chai'); +const expect = chai.expect; +const Support = require('./support'); +const dialect = Support.getTestDialect(); +const { Sequelize, QueryTypes, DataTypes, Transaction } = require('../../index'); +const sinon = require('sinon'); +const current = Support.sequelize; +const delay = require('delay'); +const pSettle = require('p-settle'); if (current.dialect.supports.transactions) { @@ -493,7 +492,7 @@ if (current.dialect.supports.transactions) { expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); }); - it('should release the connection for a deadlocked transaction', async function() { + it('should release the connection for a deadlocked transaction (1/2)', async function() { const Task = await getAndInitializeTaskModel(this.sequelize); // 1 of 2 queries should deadlock and be rolled back by InnoDB @@ -523,6 +522,127 @@ if (current.dialect.supports.transactions) { } ); }); + + it('should release the connection for a deadlocked transaction (2/2)', async function() { + const verifyDeadlock = async () => { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN + }, { timestamps: false }); + + await this.sequelize.sync({ force: true }); + const { id } = await User.create({ username: 'jan' }); + + // First, we start a transaction T1 and perform a SELECT with it using the `LOCK.SHARE` mode (setting a shared mode lock on the row). + // This will cause other sessions to be able to read the row but not modify it. + // So, if another transaction tries to update those same rows, it will wait until T1 commits (or rolls back). + // https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html + const t1 = await this.sequelize.transaction(); + const t1Jan = await User.findByPk(id, { lock: t1.LOCK.SHARE, transaction: t1 }); + + // Then we start another transaction T2 and see that it can indeed read the same row. + const t2 = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }); + const t2Jan = await User.findByPk(id, { transaction: t2 }); + + // Then, we want to see that an attempt to update that row from T2 will be queued until T1 commits. + // However, before commiting T1 we will also perform an update via T1 on the same rows. + // This should cause T2 to notice that it can't function anymore, so it detects a deadlock and automatically rolls itself back (and throws an error). + // Meanwhile, T1 should still be ok. + const executionOrder = []; + const [t2AttemptData, t1AttemptData] = await pSettle([ + (async () => { + try { + executionOrder.push('Begin attempt to update via T2'); + await t2Jan.update({ awesome: false }, { transaction: t2 }); + executionOrder.push('Done updating via T2'); // Shouldn't happen + } catch (error) { + executionOrder.push('Failed to update via T2'); + throw error; + } + + await delay(30); + + try { + // We shouldn't reach this point, but if we do, let's at least commit the transaction + // to avoid forever occupying one connection of the pool with a pending transaction. + executionOrder.push('Attempting to commit T2'); + await t2.commit(); + executionOrder.push('Done committing T2'); + } catch { + executionOrder.push('Failed to commit T2'); + } + })(), + (async () => { + await delay(100); + + try { + executionOrder.push('Begin attempt to update via T1'); + await t1Jan.update({ awesome: true }, { transaction: t1 }); + executionOrder.push('Done updating via T1'); + } catch (error) { + executionOrder.push('Failed to update via T1'); // Shouldn't happen + throw error; + } + + await delay(150); + + try { + executionOrder.push('Attempting to commit T1'); + await t1.commit(); + executionOrder.push('Done committing T1'); + } catch { + executionOrder.push('Failed to commit T1'); // Shouldn't happen + } + })() + ]); + + expect(t1AttemptData.isFulfilled).to.be.true; + expect(t2AttemptData.isRejected).to.be.true; + expect(t2AttemptData.reason.message).to.include('Deadlock found when trying to get lock; try restarting transaction'); + expect(t1.finished).to.equal('commit'); + expect(t2.finished).to.equal('rollback'); + + const expectedExecutionOrder = [ + 'Begin attempt to update via T2', + 'Begin attempt to update via T1', // 100ms after + 'Done updating via T1', // right after + 'Failed to update via T2', // right after + 'Attempting to commit T1', // 150ms after + 'Done committing T1' // right after + ]; + + // The order things happen in the database must be the one shown above. However, sometimes it can happen that + // the calls in the JavaScript event loop that are communicating with the database do not match exactly this order. + // In particular, it is possible that the JS event loop logs `'Failed to update via T2'` before logging `'Done updating via T1'`, + // even though the database updated T1 first (and then rushed to declare a deadlock for T2). + + const anotherAcceptableExecutionOrderFromJSPerspective = [ + 'Begin attempt to update via T2', + 'Begin attempt to update via T1', // 100ms after + 'Failed to update via T2', // right after + 'Done updating via T1', // right after + 'Attempting to commit T1', // 150ms after + 'Done committing T1' // right after + ]; + + const executionOrderOk = Support.isDeepEqualToOneOf( + executionOrder, + [ + expectedExecutionOrder, + anotherAcceptableExecutionOrderFromJSPerspective + ] + ); + + if (!executionOrderOk) { + throw new Error(`Unexpected execution order: ${executionOrder.join(' > ')}`); + } + }; + + for (let i = 0; i < 3 * Support.getPoolMax(); i++) { + await verifyDeadlock(); + await delay(10); + } + }); }); } @@ -916,55 +1036,123 @@ if (current.dialect.supports.transactions) { }); } - it('supports for share', async function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }, { timestamps: false }); + it('supports for share (i.e. `SELECT ... LOCK IN SHARE MODE`)', async function() { + const verifySelectLockInShareMode = async () => { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN + }, { timestamps: false }); - const t1CommitSpy = sinon.spy(); - const t2FindSpy = sinon.spy(); - const t2UpdateSpy = sinon.spy(); + await this.sequelize.sync({ force: true }); + const { id } = await User.create({ username: 'jan' }); - await this.sequelize.sync({ force: true }); - const user = await User.create({ username: 'jan' }); + // First, we start a transaction T1 and perform a SELECT with it using the `LOCK.SHARE` mode (setting a shared mode lock on the row). + // This will cause other sessions to be able to read the row but not modify it. + // So, if another transaction tries to update those same rows, it will wait until T1 commits (or rolls back). + // https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html + const t1 = await this.sequelize.transaction(); + await User.findByPk(id, { lock: t1.LOCK.SHARE, transaction: t1 }); - const t1 = await this.sequelize.transaction(); - const t1Jan = await User.findByPk(user.id, { - lock: t1.LOCK.SHARE, - transaction: t1 - }); + // Then we start another transaction T2 and see that it can indeed read the same row. + const t2 = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }); + const t2Jan = await User.findByPk(id, { transaction: t2 }); - const t2 = await this.sequelize.transaction({ - isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED - }); + // Then, we want to see that an attempt to update that row from T2 will be queued until T1 commits. + const executionOrder = []; + const [t2AttemptData, t1AttemptData] = await pSettle([ + (async () => { + try { + executionOrder.push('Begin attempt to update via T2'); + await t2Jan.update({ awesome: false }, { transaction: t2 }); + executionOrder.push('Done updating via T2'); + } catch (error) { + executionOrder.push('Failed to update via T2'); // Shouldn't happen + throw error; + } - await Promise.all([ - (async () => { - const t2Jan = await User.findByPk(user.id, { - transaction: t2 - }); + await delay(30); - t2FindSpy(); + try { + executionOrder.push('Attempting to commit T2'); + await t2.commit(); + executionOrder.push('Done committing T2'); + } catch { + executionOrder.push('Failed to commit T2'); // Shouldn't happen + } + })(), + (async () => { + await delay(100); - await t2Jan.update({ awesome: false }, { transaction: t2 }); - t2UpdateSpy(); + try { + executionOrder.push('Begin attempt to read via T1'); + await User.findAll({ transaction: t1 }); + executionOrder.push('Done reading via T1'); + } catch (error) { + executionOrder.push('Failed to read via T1'); // Shouldn't happen + throw error; + } - await t2.commit(); - })(), - (async () => { - await t1Jan.update({ awesome: true }, { transaction: t1 }); - await delay(2000); - t1CommitSpy(); - await t1.commit(); - })() - ]); + await delay(150); - // (t2) find call should have returned before (t1) commit - expect(t2FindSpy).to.have.been.calledBefore(t1CommitSpy); + try { + executionOrder.push('Attempting to commit T1'); + await t1.commit(); + executionOrder.push('Done committing T1'); + } catch { + executionOrder.push('Failed to commit T1'); // Shouldn't happen + } + })() + ]); + + expect(t1AttemptData.isFulfilled).to.be.true; + expect(t2AttemptData.isFulfilled).to.be.true; + expect(t1.finished).to.equal('commit'); + expect(t2.finished).to.equal('commit'); + + const expectedExecutionOrder = [ + 'Begin attempt to update via T2', + 'Begin attempt to read via T1', // 100ms after + 'Done reading via T1', // right after + 'Attempting to commit T1', // 150ms after + 'Done committing T1', // right after + 'Done updating via T2', // right after + 'Attempting to commit T2', // 30ms after + 'Done committing T2' // right after + ]; + + // The order things happen in the database must be the one shown above. However, sometimes it can happen that + // the calls in the JavaScript event loop that are communicating with the database do not match exactly this order. + // In particular, it is possible that the JS event loop logs `'Done updating via T2'` before logging `'Done committing T1'`, + // even though the database committed T1 first (and then rushed to complete the pending update query from T2). + + const anotherAcceptableExecutionOrderFromJSPerspective = [ + 'Begin attempt to update via T2', + 'Begin attempt to read via T1', // 100ms after + 'Done reading via T1', // right after + 'Attempting to commit T1', // 150ms after + 'Done updating via T2', // right after + 'Done committing T1', // right after + 'Attempting to commit T2', // 30ms after + 'Done committing T2' // right after + ]; + + const executionOrderOk = Support.isDeepEqualToOneOf( + executionOrder, + [ + expectedExecutionOrder, + anotherAcceptableExecutionOrderFromJSPerspective + ] + ); - // But (t2) update call should not happen before (t1) commit - expect(t2UpdateSpy).to.have.been.calledAfter(t1CommitSpy); + if (!executionOrderOk) { + throw new Error(`Unexpected execution order: ${executionOrder.join(' > ')}`); + } + }; + + for (let i = 0; i < 3 * Support.getPoolMax(); i++) { + await verifySelectLockInShareMode(); + await delay(10); + } }); }); } diff --git a/test/support.js b/test/support.js index 2fe0f36f402e..c2d721df9079 100644 --- a/test/support.js +++ b/test/support.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); +const { isDeepStrictEqual } = require('util'); const _ = require('lodash'); const Sequelize = require('../index'); const Config = require('./config/config'); @@ -119,11 +120,10 @@ const Support = { return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions); }, - getConnectionOptions() { - const config = Config[this.getTestDialect()]; - + getConnectionOptionsWithoutPool() { + // Do not break existing config object - shallow clone before `delete config.pool` + const config = { ...Config[this.getTestDialect()] }; delete config.pool; - return config; }, @@ -207,6 +207,10 @@ const Support = { return `[${dialect.toUpperCase()}] ${moduleName}`; }, + getPoolMax() { + return Config[this.getTestDialect()].pool.max; + }, + expectsql(query, assertions) { const expectations = assertions.query || assertions; let expectation = expectations[Support.sequelize.dialect.name]; @@ -234,6 +238,10 @@ const Support = { const bind = assertions.bind[Support.sequelize.dialect.name] || assertions.bind['default'] || assertions.bind; expect(query.bind).to.deep.equal(bind); } + }, + + isDeepEqualToOneOf(actual, expectedOptions) { + return expectedOptions.some(expected => isDeepStrictEqual(actual, expected)); } }; From 48225948d199d7099a9c8db2f11487bfff13fb17 Mon Sep 17 00:00:00 2001 From: Augusto Amaral Pereira Date: Sun, 21 Mar 2021 13:57:56 -0300 Subject: [PATCH 267/414] docs(dialect-specific-things): fix typo (#13081) --- docs/manual/other-topics/dialect-specific-things.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/other-topics/dialect-specific-things.md b/docs/manual/other-topics/dialect-specific-things.md index 5950bcf1fe01..d7c5e9427609 100644 --- a/docs/manual/other-topics/dialect-specific-things.md +++ b/docs/manual/other-topics/dialect-specific-things.md @@ -83,7 +83,7 @@ You can provide custom options to it using `dialectOptions.options` in the Seque ```js const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'postgres', + dialect: 'mssql', dialectOptions: { // Observe the need for this nested `options` field for MSSQL options: { @@ -215,4 +215,4 @@ Person.init({ /* attributes */ }, { }) ``` -The comment will be set when calling `sync()`. \ No newline at end of file +The comment will be set when calling `sync()`. From f98bd7ed0e0d2bafcb593ccb1755f88c9ae18f37 Mon Sep 17 00:00:00 2001 From: Hugo Denizart Date: Sun, 21 Mar 2021 18:17:31 +0100 Subject: [PATCH 268/414] feat(add-constraint): add deferrable option (#13096) Co-authored-by: Pedro Augusto de Paula Barbosa --- lib/dialects/abstract/query-generator.js | 5 + lib/dialects/abstract/query-interface.js | 1 + test/integration/sequelize/deferrable.test.js | 188 +++++++++++------- types/lib/query-interface.d.ts | 4 + 4 files changed, 126 insertions(+), 72 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index e07784e3ee80..4d8d1f434104 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -684,6 +684,11 @@ class QueryGenerator { break; default: throw new Error(`${options.type} is invalid.`); } + + if (options.deferrable && ['UNIQUE', 'PRIMARY KEY', 'FOREIGN KEY'].includes(options.type.toUpperCase())) { + constraintSnippet += ` ${this.deferConstraintsQuery(options)}`; + } + return constraintSnippet; } diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 3f00e909f4e5..274453c8d0ff 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -703,6 +703,7 @@ class QueryInterface { * @param {string} [options.references.table] Target table name * @param {string} [options.references.field] Target column name * @param {string} [options.references.fields] Target column names for a composite primary key. Must match the order of fields in options.fields. + * @param {string} [options.deferrable] Sets the constraint to be deferred or immediately checked. See Sequelize.Deferrable. PostgreSQL Only * * @returns {Promise} */ diff --git a/test/integration/sequelize/deferrable.test.js b/test/integration/sequelize/deferrable.test.js index 11c85c0ae540..e0f81b365148 100644 --- a/test/integration/sequelize/deferrable.test.js +++ b/test/integration/sequelize/deferrable.test.js @@ -13,92 +13,136 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) { describe(Support.getTestDialectTeaser('Sequelize'), () => { describe('Deferrable', () => { - beforeEach(function() { - this.run = async function(deferrable, options) { - options = options || {}; - - const taskTableName = options.taskTableName || `tasks_${config.rand()}`; - const transactionOptions = { deferrable: Sequelize.Deferrable.SET_DEFERRED, ...options }; - const userTableName = `users_${config.rand()}`; - - const User = this.sequelize.define( - 'User', { name: Sequelize.STRING }, { tableName: userTableName } - ); - - const Task = this.sequelize.define( - 'Task', { - title: Sequelize.STRING, - user_id: { - allowNull: false, - type: Sequelize.INTEGER, - references: { - model: userTableName, - key: 'id', - deferrable - } - } - }, { - tableName: taskTableName - } - ); - - await User.sync({ force: true }); - await Task.sync({ force: true }); - - return this.sequelize.transaction(transactionOptions, async t => { - const task0 = await Task - .create({ title: 'a task', user_id: -1 }, { transaction: t }); + const describeDeferrableTest = (title, defineModels) => { + describe(title, () => { + beforeEach(function() { + this.run = async function(deferrable, options) { + options = options || {}; + + const taskTableName = options.taskTableName || `tasks_${config.rand()}`; + const transactionOptions = { deferrable: Sequelize.Deferrable.SET_DEFERRED, ...options }; + const userTableName = `users_${config.rand()}`; + + const { Task, User } = await defineModels({ sequelize: this.sequelize, userTableName, deferrable, taskTableName }); + + return this.sequelize.transaction(transactionOptions, async t => { + const task0 = await Task + .create({ title: 'a task', user_id: -1 }, { transaction: t }); + + const [task, user] = await Promise.all([task0, User.create({}, { transaction: t })]); + task.user_id = user.id; + return task.save({ transaction: t }); + }); + }; + }); - const [task, user] = await Promise.all([task0, User.create({}, { transaction: t })]); - task.user_id = user.id; - return task.save({ transaction: t }); + describe('NOT', () => { + it('does not allow the violation of the foreign key constraint', async function() { + await expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + }); }); - }; - }); - describe('NOT', () => { - it('does not allow the violation of the foreign key constraint', async function() { - await expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); - }); + describe('INITIALLY_IMMEDIATE', () => { + it('allows the violation of the foreign key constraint if the transaction is deferred', async function() { + const task = await this + .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE); - describe('INITIALLY_IMMEDIATE', () => { - it('allows the violation of the foreign key constraint if the transaction is deferred', async function() { - const task = await this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE); + expect(task.title).to.equal('a task'); + expect(task.user_id).to.equal(1); + }); - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); - }); + it('does not allow the violation of the foreign key constraint if the transaction is not deffered', async function() { + await expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { + deferrable: undefined + })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + }); - it('does not allow the violation of the foreign key constraint if the transaction is not deffered', async function() { - await expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { - deferrable: undefined - })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); + it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', async function() { + const taskTableName = `tasks_${config.rand()}`; - it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', async function() { - const taskTableName = `tasks_${config.rand()}`; + const task = await this + .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { + deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]), + taskTableName + }); - const task = await this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { - deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]), - taskTableName + expect(task.title).to.equal('a task'); + expect(task.user_id).to.equal(1); }); + }); + + describe('INITIALLY_DEFERRED', () => { + it('allows the violation of the foreign key constraint', async function() { + const task = await this + .run(Sequelize.Deferrable.INITIALLY_DEFERRED); - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); + expect(task.title).to.equal('a task'); + expect(task.user_id).to.equal(1); + }); + }); }); - }); + }; + + describeDeferrableTest('set in define', async ({ sequelize, userTableName, deferrable, taskTableName }) => { + const User = sequelize.define( + 'User', { name: Sequelize.STRING }, { tableName: userTableName } + ); + + const Task = sequelize.define( + 'Task', { + title: Sequelize.STRING, + user_id: { + allowNull: false, + type: Sequelize.INTEGER, + references: { + model: userTableName, + key: 'id', + deferrable + } + } + }, { + tableName: taskTableName + } + ); + + await User.sync({ force: true }); + await Task.sync({ force: true }); - describe('INITIALLY_DEFERRED', () => { - it('allows the violation of the foreign key constraint', async function() { - const task = await this - .run(Sequelize.Deferrable.INITIALLY_DEFERRED); + return { Task, User }; + }); - expect(task.title).to.equal('a task'); - expect(task.user_id).to.equal(1); + describeDeferrableTest('set in addConstraint', async ({ sequelize, userTableName, deferrable, taskTableName }) => { + const User = sequelize.define( + 'User', { name: Sequelize.STRING }, { tableName: userTableName } + ); + + const Task = sequelize.define( + 'Task', { + title: Sequelize.STRING, + user_id: { + allowNull: false, + type: Sequelize.INTEGER + } + }, { + tableName: taskTableName + } + ); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + await sequelize.getQueryInterface().addConstraint(taskTableName, { + fields: ['user_id'], + type: 'foreign key', + name: `${taskTableName}_user_id_fkey`, + deferrable, + references: { + table: userTableName, + field: 'id' + } }); + + return { Task, User }; }); }); }); diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 7e7e01019d07..8155a62f97f5 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -15,6 +15,7 @@ import { Sequelize, RetryOptions } from './sequelize'; import { Transaction } from './transaction'; import { SetRequired } from './../type-helpers/set-required'; import { Fn, Literal } from './utils'; +import { Deferrable } from './deferrable'; type BindOrReplacements = { [key: string]: unknown } | unknown[]; type FieldMap = { [key: string]: string }; @@ -216,6 +217,7 @@ export interface BaseConstraintOptions { export interface AddUniqueConstraintOptions extends BaseConstraintOptions { type: 'unique'; + deferrable?: Deferrable; } export interface AddDefaultConstraintOptions extends BaseConstraintOptions { @@ -230,6 +232,7 @@ export interface AddCheckConstraintOptions extends BaseConstraintOptions { export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { type: 'primary key'; + deferrable?: Deferrable; } export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { @@ -240,6 +243,7 @@ export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { }; onDelete: string; onUpdate: string; + deferrable?: Deferrable; } export type AddConstraintOptions = From 4ec88a6732eb38269aac3f76a8cb5769d61c7879 Mon Sep 17 00:00:00 2001 From: Hugo Denizart Date: Sun, 21 Mar 2021 18:33:08 +0100 Subject: [PATCH 269/414] docs(deferrable): clarify api docs (#13120) Co-authored-by: Pedro Augusto de Paula Barbosa --- lib/deferrable.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/deferrable.js b/lib/deferrable.js index 84dce479d4b2..a2379b533da6 100644 --- a/lib/deferrable.js +++ b/lib/deferrable.js @@ -87,17 +87,19 @@ class SET_IMMEDIATE extends ABSTRACT { * }); * ``` * - * @property INITIALLY_DEFERRED Defer constraints checks to the end of transactions. - * @property INITIALLY_IMMEDIATE Trigger the constraint checks immediately - * @property NOT Set the constraints to not deferred. This is the default in PostgreSQL and it make it impossible to dynamically defer the constraints within a transaction. - * @property SET_DEFERRED - * @property SET_IMMEDIATE + * @property INITIALLY_DEFERRED Use when declaring a constraint. Allow and enable by default this constraint's checks to be deferred at the end of transactions. + * @property INITIALLY_IMMEDIATE Use when declaring a constraint. Allow the constraint's checks to be deferred at the end of transactions. + * @property NOT Use when declaring a constraint. Set the constraint to not deferred. This is the default in PostgreSQL and makes it impossible to dynamically defer the constraints within a transaction. + * @property SET_DEFERRED Use when declaring a transaction. Defer the deferrable checks involved in this transaction at commit. + * @property SET_IMMEDIATE Use when declaring a transaction. Execute the deferrable checks involved in this transaction immediately. */ -const Deferrable = module.exports = { // eslint-disable-line +const Deferrable = { INITIALLY_DEFERRED: classToInvokable(INITIALLY_DEFERRED), INITIALLY_IMMEDIATE: classToInvokable(INITIALLY_IMMEDIATE), NOT: classToInvokable(NOT), SET_DEFERRED: classToInvokable(SET_DEFERRED), SET_IMMEDIATE: classToInvokable(SET_IMMEDIATE) }; + +module.exports = Deferrable; From 3fd64cbdfb882b1fb14124b72de62791b2c9c4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=ED=99=98?= Date: Mon, 22 Mar 2021 02:38:20 +0900 Subject: [PATCH 270/414] fix(types): allow `sequelize.col` in `attributes` (#13105) --- types/lib/model.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index c296a146c179..ed6114090df4 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -479,7 +479,7 @@ export type Order = string | Fn | Col | Literal | OrderItem[]; * Please note if this is used the aliased property will not be available on the model instance * as a property but only via `instance.get('alias')`. */ -export type ProjectionAlias = readonly [string | Literal | Fn, string]; +export type ProjectionAlias = readonly [string | Literal | Fn | Col, string]; export type FindAttributeOptions = | (string | ProjectionAlias)[] From 9cb4d7f37b3caa707e31574db8a81feb2bd801c0 Mon Sep 17 00:00:00 2001 From: Vladlen Date: Mon, 22 Mar 2021 04:43:16 +1100 Subject: [PATCH 271/414] fix(types): decapitalize queryGenerator property (#13126) --- types/lib/query-interface.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 8155a62f97f5..9f721c5c802d 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -288,7 +288,7 @@ export class QueryInterface { * * We don't have a definition for the QueryGenerator, because I doubt it is commonly in use separately. */ - public QueryGenerator: unknown; + public queryGenerator: unknown; /** * Returns the current sequelize instance. From 466e361ed495f44efa8aa98aa83a5385c7735d74 Mon Sep 17 00:00:00 2001 From: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Date: Sun, 21 Mar 2021 18:47:10 +0100 Subject: [PATCH 272/414] fix(types): fix `Model#previous` type (#13106) --- types/lib/model.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index ed6114090df4..f3f883005dfa 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2708,7 +2708,7 @@ export abstract class Model(key: K): this[K]; + public previous(key: K): TCreationAttributes[K] | undefined; /** * Validates this instance, and if the validation passes, persists it to the database. From e35a9bf58b3eb52f3c4d0e2650dc671182f83bff Mon Sep 17 00:00:00 2001 From: Lorhan Sohaky Date: Sun, 21 Mar 2021 15:27:24 -0300 Subject: [PATCH 273/414] fix(types): fix `ValidationErrorItem` types (#13108) Co-authored-by: papb --- lib/errors/validation-error.js | 34 +++++++++++++------------- types/lib/errors.d.ts | 44 +++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 185a5d76c835..4bb4c9817bc6 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -55,18 +55,18 @@ class ValidationError extends BaseError { */ class ValidationErrorItem { /** - * Creates new validation error item + * Creates a new ValidationError item. Instances of this class are included in the `ValidationError.errors` property. * - * @param {string} message An error message - * @param {string} type The type/origin of the validation error - * @param {string} path The field that triggered the validation error - * @param {string} value The value that generated the error - * @param {object} [inst] the DAO instance that caused the validation error - * @param {object} [validatorKey] a validation "key", used for identification + * @param {string} [message] An error message + * @param {string} [type] The type/origin of the validation error + * @param {string} [path] The field that triggered the validation error + * @param {string} [value] The value that generated the error + * @param {Model} [instance] the DAO instance that caused the validation error + * @param {string} [validatorKey] a validation "key", used for identification * @param {string} [fnName] property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable - * @param {string} [fnArgs] parameters used with the BUILT-IN validator function, if applicable + * @param {Array} [fnArgs] parameters used with the BUILT-IN validator function, if applicable */ - constructor(message, type, path, value, inst, validatorKey, fnName, fnArgs) { + constructor(message, type, path, value, instance, validatorKey, fnName, fnArgs) { /** * An error message * @@ -77,21 +77,21 @@ class ValidationErrorItem { /** * The type/origin of the validation error * - * @type {string} + * @type {string | null} */ this.type = null; /** * The field that triggered the validation error * - * @type {string} + * @type {string | null} */ this.path = path || null; /** * The value that generated the error * - * @type {string} + * @type {string | null} */ this.value = value !== undefined ? value : null; @@ -100,28 +100,28 @@ class ValidationErrorItem { /** * The DAO instance that caused the validation error * - * @type {Model} + * @type {Model | null} */ - this.instance = inst || null; + this.instance = instance || null; /** * A validation "key", used for identification * - * @type {string} + * @type {string | null} */ this.validatorKey = validatorKey || null; /** * Property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable * - * @type {string} + * @type {string | null} */ this.validatorName = fnName || null; /** * Parameters used with the BUILT-IN validator function, if applicable * - * @type {string} + * @type {Array} */ this.validatorArgs = fnArgs || []; diff --git a/types/lib/errors.d.ts b/types/lib/errors.d.ts index d8e2a7c599e9..a575486bec62 100644 --- a/types/lib/errors.d.ts +++ b/types/lib/errors.d.ts @@ -1,3 +1,5 @@ +import Model from "./model"; + /** * The Base Error all Sequelize Errors inherit from. */ @@ -33,29 +35,53 @@ export class ValidationError extends BaseError { export class ValidationErrorItem { /** An error message */ - public readonly message: string; + public readonly message: string; - /** The type of the validation error */ - public readonly type: string; + /** The type/origin of the validation error */ + public readonly type: string | null; /** The field that triggered the validation error */ - public readonly path: string; + public readonly path: string | null; /** The value that generated the error */ - public readonly value: string; + public readonly value: string | null; + + /** The DAO instance that caused the validation error */ + public readonly instance: Model | null; + + /** A validation "key", used for identification */ + public readonly validatorKey: string | null; + + /** Property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable */ + public readonly validatorName: string | null; + + /** Parameters used with the BUILT-IN validator function, if applicable */ + public readonly validatorArgs: unknown[]; public readonly original: Error; /** - * Validation Error Item - * Instances of this class are included in the `ValidationError.errors` property. + * Creates a new ValidationError item. Instances of this class are included in the `ValidationError.errors` property. * * @param message An error message - * @param type The type of the validation error + * @param type The type/origin of the validation error * @param path The field that triggered the validation error * @param value The value that generated the error + * @param instance the DAO instance that caused the validation error + * @param validatorKey a validation "key", used for identification + * @param fnName property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable + * @param fnArgs parameters used with the BUILT-IN validator function, if applicable */ - constructor(message?: string, type?: string, path?: string, value?: string); + constructor( + message?: string, + type?: string, + path?: string, + value?: string, + instance?: object, + validatorKey?: string, + fnName?: string, + fnArgs?: unknown[] + ); } export interface CommonErrorProperties { From 88925077eba182814af2ec8f6fa6674ae1ee5b15 Mon Sep 17 00:00:00 2001 From: Tobin Brown Date: Sun, 21 Mar 2021 13:28:46 -0500 Subject: [PATCH 274/414] fix(types): allow bigints in `WhereValue` (#13028) Co-authored-by: Pedro Augusto de Paula Barbosa --- types/lib/model.d.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index f3f883005dfa..808fbfc25e39 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -332,11 +332,12 @@ export interface WhereGeometryOptions { * WhereAttributeHash is in there for JSON columns. */ export type WhereValue = - | string // literal value - | number // literal value - | boolean // literal value - | Date // literal value - | Buffer // literal value + | string + | number + | bigint + | boolean + | Date + | Buffer | null | WhereOperators | WhereAttributeHash // for JSON columns From de5f21dce6e3324f370d578a29b3ec94632e7b5d Mon Sep 17 00:00:00 2001 From: KapitanOczywisty <44417092+KapitanOczywisty@users.noreply.github.com> Date: Mon, 22 Mar 2021 01:50:05 +0100 Subject: [PATCH 275/414] fix(types): models with attributes couldn't be used in some cases (#13010) --- types/lib/associations/belongs-to-many.d.ts | 5 ++- types/lib/hooks.d.ts | 3 +- types/lib/model-manager.d.ts | 6 +-- types/lib/model.d.ts | 12 +++--- types/lib/query-interface.d.ts | 10 ++--- types/lib/sequelize.d.ts | 9 +++-- types/lib/utils.d.ts | 6 +-- types/test/attributes.ts | 44 +++++++++++++++++++++ types/test/query-interface.ts | 4 +- 9 files changed, 74 insertions(+), 25 deletions(-) create mode 100644 types/test/attributes.ts diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index 9aa9e14e807a..b04a728af6ee 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -8,6 +8,7 @@ import { InstanceUpdateOptions, Model, ModelCtor, + ModelType, Transactionable, WhereOptions, } from '../model'; @@ -21,7 +22,7 @@ export interface ThroughOptions { * The model used to join both sides of the N:M association. * Can be a string if you want the model to be generated by sequelize. */ - model: typeof Model | string; + model: ModelType | string; /** * If true the generated join table will be paranoid @@ -59,7 +60,7 @@ export interface BelongsToManyOptions extends ManyToManyOptions { * The name of the table that is used to join source and target in n:m associations. Can also be a * sequelize model if you want to define the junction table yourself and add extra attributes to it. */ - through: typeof Model | string | ThroughOptions; + through: ModelType | string | ThroughOptions; /** * The name of the foreign key in the join table (representing the target model) or an object representing diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index 66881456e953..d19f93065f55 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -1,3 +1,4 @@ +import { ModelType } from '../index'; import { ValidationOptions } from './instance-validator'; import Model, { BulkCreateOptions, @@ -65,7 +66,7 @@ export interface SequelizeHooks< TCreationAttributes = TAttributes > extends ModelHooks { beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; - afterDefine(model: typeof Model): void; + afterDefine(model: ModelType): void; beforeInit(config: Config, options: Options): void; afterInit(sequelize: Sequelize): void; beforeConnect(config: Config): HookReturn; diff --git a/types/lib/model-manager.d.ts b/types/lib/model-manager.d.ts index 29eac4961375..41e72dae6636 100644 --- a/types/lib/model-manager.d.ts +++ b/types/lib/model-manager.d.ts @@ -1,4 +1,4 @@ -import { Model } from './model'; +import { Model, ModelType } from './model'; import { Sequelize } from './sequelize'; export class ModelManager { @@ -7,8 +7,8 @@ export class ModelManager { public all: typeof Model[]; constructor(sequelize: Sequelize); - public addModel(model: T): T; - public removeModel(model: typeof Model): void; + public addModel(model: T): T; + public removeModel(model: ModelType): void; public getModel(against: unknown, options?: { attribute?: string }): typeof Model; } diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 808fbfc25e39..5d6046a739e4 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -379,7 +379,7 @@ export interface IncludeThroughOptions extends Filterable, Projectable { /** * Options for eager-loading associated models, also allowing for all associations to be loaded at once */ -export type Includeable = typeof Model | Association | IncludeOptions | { all: true, nested?: true } | string; +export type Includeable = ModelType | Association | IncludeOptions | { all: true, nested?: true } | string; /** * Complex include options @@ -392,7 +392,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * The model you want to eagerly load */ - model?: typeof Model; + model?: ModelType; /** * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / @@ -1232,7 +1232,7 @@ export interface ModelAttributeColumnReferencesOptions { /** * If this column references another table, provide it here as a Model, or a string */ - model?: string | typeof Model; + model?: string | ModelType; /** * The column of the foreign table that this column references @@ -1660,7 +1660,7 @@ export abstract class Model, schema: string, options?: SchemaOptions - ): { new(): M } & typeof Model; + ): ModelCtor; /** * Get the tablename of the model, taking schema into account. The method will return The name as a string @@ -2127,7 +2127,7 @@ export abstract class Model(this: M): M; + public static unscoped(this: M): M; /** * A hook that is run before validation @@ -2833,7 +2833,7 @@ export abstract class Model = new () => Model; // Do not switch the order of `typeof Model` and `{ new(): M }`. For // instances created by `sequelize.define` to typecheck well, `typeof Model` diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 9f721c5c802d..0faa892c8bb1 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -8,7 +8,7 @@ import { WhereOptions, Filterable, Poolable, - ModelCtor, ModelStatic + ModelCtor, ModelStatic, ModelType } from './model'; import QueryTypes = require('./query-types'); import { Sequelize, RetryOptions } from './sequelize'; @@ -487,7 +487,7 @@ export class QueryInterface { insertValues: object, updateValues: object, where: object, - model: typeof Model, + model: ModelType, options?: QueryOptions ): Promise; @@ -540,13 +540,13 @@ export class QueryInterface { tableName: TableName, identifier: WhereOptions, options?: QueryOptions, - model?: typeof Model + model?: ModelType ): Promise; /** * Returns selected rows */ - public select(model: typeof Model | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise; + public select(model: ModelType | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise; /** * Increments a row value @@ -566,7 +566,7 @@ export class QueryInterface { tableName: TableName, options: QueryOptionsWithWhere, attributeSelector: string | string[], - model?: typeof Model + model?: ModelType ): Promise; /** diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index f03d916025f5..b88d5ec4e5d7 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -20,6 +20,7 @@ import { WhereOperators, ModelCtor, Hookable, + ModelType, } from './model'; import { ModelManager } from './model-manager'; import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface'; @@ -747,8 +748,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with factory */ - public static afterDefine(name: string, fn: (model: typeof Model) => void): void; - public static afterDefine(fn: (model: typeof Model) => void): void; + public static afterDefine(name: string, fn: (model: ModelType) => void): void; + public static afterDefine(fn: (model: ModelType) => void): void; /** * A hook that is run before Sequelize() call @@ -1046,8 +1047,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with factory */ - public afterDefine(name: string, fn: (model: typeof Model) => void): void; - public afterDefine(fn: (model: typeof Model) => void): void; + public afterDefine(name: string, fn: (model: ModelType) => void): void; + public afterDefine(fn: (model: ModelType) => void): void; /** * A hook that is run before Sequelize() call diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts index b7b5df81df17..b06f12cb16dd 100644 --- a/types/lib/utils.d.ts +++ b/types/lib/utils.d.ts @@ -1,5 +1,5 @@ import { DataType } from './data-types'; -import { Model, ModelCtor, WhereOptions } from './model'; +import { Model, ModelCtor, ModelType, WhereOptions } from './model'; export type Primitive = 'string' | 'number' | 'boolean'; @@ -40,9 +40,9 @@ export function mapOptionFieldNames ): T; -export function mapWhereFieldNames(attributes: object, model: typeof Model): object; +export function mapWhereFieldNames(attributes: object, model: ModelType): object; /** Used to map field names in values */ -export function mapValueFieldNames(dataValues: object, fields: string[], model: typeof Model): object; +export function mapValueFieldNames(dataValues: object, fields: string[], model: ModelType): object; export function isColString(value: string): boolean; export function canTreatArrayAsAnd(arr: unknown[]): boolean; diff --git a/types/test/attributes.ts b/types/test/attributes.ts new file mode 100644 index 000000000000..2c754923debd --- /dev/null +++ b/types/test/attributes.ts @@ -0,0 +1,44 @@ +import { Model } from "sequelize/lib/model"; + +interface UserCreationAttributes { + name: string; +} + +interface UserAttributes extends UserCreationAttributes { + id: number; +} + +class User + extends Model + implements UserAttributes { + public id!: number; + public name!: string; + + public readonly projects?: Project[]; + public readonly address?: Address; +} + +interface ProjectCreationAttributes { + ownerId: number; + name: string; +} + +interface ProjectAttributes extends ProjectCreationAttributes { + id: number; +} + +class Project + extends Model + implements ProjectAttributes { + public id!: number; + public ownerId!: number; + public name!: string; +} + +class Address extends Model { + public userId!: number; + public address!: string; +} + +// both models should be accepted in include +User.findAll({ include: [Project, Address] }); diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index 3a6387bfbd8c..e24486db7791 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -193,7 +193,9 @@ async function test() { }, }); - await queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, Model, {}); + class TestModel extends Model {} + + await queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, TestModel, {}); await queryInterface.insert(null, 'test', {}); } From c8c76d4312f1def0b3f84a213539270ea118367e Mon Sep 17 00:00:00 2001 From: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Date: Mon, 22 Mar 2021 02:04:11 +0100 Subject: [PATCH 276/414] test(types): add tests for `.previous` and `.set` (#13129) --- types/test/define.ts | 4 ++-- types/test/model.ts | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/types/test/define.ts b/types/test/define.ts index c4dc10e1e48f..54d422c88747 100644 --- a/types/test/define.ts +++ b/types/test/define.ts @@ -27,7 +27,7 @@ const User = sequelize.define( ); async function test() { - expectTypeOf().toMatchTypeOf(new User()); + expectTypeOf().toMatchTypeOf(User.build()); const user = await User.findOne(); expectTypeOf(user).toEqualTypeOf(); @@ -60,7 +60,7 @@ UntypedUser.customStaticMethod = () => {}; async function testUntyped() { UntypedUser.customStaticMethod(); - expectTypeOf().toMatchTypeOf(new UntypedUser()); + expectTypeOf().toMatchTypeOf(UntypedUser.build()); const user = await UntypedUser.findOne(); expectTypeOf(user).toEqualTypeOf(); diff --git a/types/test/model.ts b/types/test/model.ts index 4036eb1a530a..0bc5486ca352 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -1,5 +1,6 @@ import { expectTypeOf } from "expect-type"; -import { Association, BelongsToManyGetAssociationsMixin, DataTypes, HasOne, Model, Sequelize } from 'sequelize'; +import { Association, BelongsToManyGetAssociationsMixin, DataTypes, HasOne, Model, Optional, Sequelize } from 'sequelize'; +import { ModelDefined } from '../lib/model'; expectTypeOf().toMatchTypeOf(); class MyModel extends Model { @@ -151,3 +152,35 @@ Actor.belongsToMany(Film, { paranoid: true } }); + +interface ModelAttributes { + id: number; + name: string; +} + +interface CreationAttributes extends Optional {} + +const ModelWithAttributes: ModelDefined< + ModelAttributes, + CreationAttributes +> = sequelize.define('efs', { + name: DataTypes.STRING +}); + +const modelWithAttributes = ModelWithAttributes.build(); + +/** + * Tests for set() type + */ +expectTypeOf(modelWithAttributes.set).toBeFunction(); +expectTypeOf(modelWithAttributes.set).parameter(0).toEqualTypeOf>(); + +/** + * Tests for previous() type + */ +expectTypeOf(modelWithAttributes.previous).toBeFunction(); +expectTypeOf(modelWithAttributes.previous).toBeCallableWith('name'); +expectTypeOf(modelWithAttributes.previous).parameter(0).toEqualTypeOf(); +expectTypeOf(modelWithAttributes.previous).parameter(0).not.toEqualTypeOf<'unreferencedAttribute'>(); +expectTypeOf(modelWithAttributes.previous).returns.toEqualTypeOf(); +expectTypeOf(modelWithAttributes.previous('name')).toEqualTypeOf(); From ac39f8ac753d2d445b115b8c79dc47e24fd112a5 Mon Sep 17 00:00:00 2001 From: Amin Mahrami Date: Mon, 22 Mar 2021 02:13:36 +0100 Subject: [PATCH 277/414] fix(types): remove `string` from `Order` type (#13057) --- types/lib/model.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 5d6046a739e4..f364ab04e40d 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -474,7 +474,7 @@ export type OrderItem = | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] -export type Order = string | Fn | Col | Literal | OrderItem[]; +export type Order = Fn | Col | Literal | OrderItem[]; /** * Please note if this is used the aliased property will not be available on the model instance From 3e9441bc8b05bb10a2aaa4924ed48ec83b660a4c Mon Sep 17 00:00:00 2001 From: Sahil Rajput Date: Mon, 22 Mar 2021 06:44:24 +0530 Subject: [PATCH 278/414] docs(model-querying-basics): clarify example (#12999) --- docs/manual/core-concepts/model-querying-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index c10cf75d10d6..42e89792fbe3 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -18,7 +18,7 @@ console.log("Jane's auto-generated ID:", jane.id); The [`Model.create()`](../class/lib/model.js~Model.html#static-method-create) method is a shorthand for building an unsaved instance with [`Model.build()`](../class/lib/model.js~Model.html#static-method-build) and saving the instance with [`instance.save()`](../class/lib/model.js~Model.html#instance-method-save). -It is also possible to define which attributes can be set in the `create` method. This can be especially useful if you create database entries based on a form which can be filled by a user. Using that would, for example, allow you to restrict the `User` model to set only an username and an address but not an admin flag: +It is also possible to define which attributes can be set in the `create` method. This can be especially useful if you create database entries based on a form which can be filled by a user. Using that would, for example, allow you to restrict the `User` model to set only an username but not an admin flag (i.e., `isAdmin`): ```js const user = await User.create({ From 023e1d9aefe10d3c708f1580a979fb2b754bd0d3 Mon Sep 17 00:00:00 2001 From: Khinenw Date: Mon, 22 Mar 2021 11:42:35 +0900 Subject: [PATCH 279/414] fix(sqlite): retrieve primary key on upsert (#12991) Co-authored-by: Pedro Augusto de Paula Barbosa --- lib/dialects/sqlite/query.js | 27 ++++------------- test/integration/model/upsert.test.js | 38 ++++++++++++------------ test/integration/sequelize/query.test.js | 18 ----------- 3 files changed, 25 insertions(+), 58 deletions(-) diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index cfb5de7ea6c7..4047ef4792f1 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -74,7 +74,7 @@ class Query extends AbstractQuery { let result = this.instance; // add the inserted row id to the instance - if (this.isInsertQuery(results, metaData)) { + if (this.isInsertQuery(results, metaData) || this.isUpsertQuery()) { this.handleInsertQuery(results, metaData); if (!this.instance) { // handle bulkCreate AI primary key @@ -220,15 +220,7 @@ class Query extends AbstractQuery { const conn = this.connection; this.sql = sql; const method = this.getDatabaseMethod(); - let complete; - if (method === 'exec') { - // exec does not support bind parameter - sql = AbstractQuery.formatBindParameters(sql, this.options.bind, this.options.dialect || 'sqlite', { skipUnescape: true })[0]; - this.sql = sql; - complete = this._logQuery(sql, debug); - } else { - complete = this._logQuery(sql, debug, parameters); - } + const complete = this._logQuery(sql, debug, parameters); return new Promise((resolve, reject) => conn.serialize(async () => { const columnTypes = {}; @@ -250,13 +242,9 @@ class Query extends AbstractQuery { } } - if (method === 'exec') { - // exec does not support bind parameter - conn[method](sql, afterExecute); - } else { - if (!parameters) parameters = []; - conn[method](sql, parameters, afterExecute); - } + if (!parameters) parameters = []; + conn[method](sql, parameters, afterExecute); + return null; }; @@ -437,10 +425,7 @@ class Query extends AbstractQuery { } getDatabaseMethod() { - if (this.isUpsertQuery()) { - return 'exec'; // Needed to run multiple queries in one - } - if (this.isInsertQuery() || this.isUpdateQuery() || this.isBulkUpdateQuery() || this.sql.toLowerCase().includes('CREATE TEMPORARY TABLE'.toLowerCase()) || this.options.type === QueryTypes.BULKDELETE) { + if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery() || this.isBulkUpdateQuery() || this.sql.toLowerCase().includes('CREATE TEMPORARY TABLE'.toLowerCase()) || this.options.type === QueryTypes.BULKDELETE) { return 'run'; } return 'all'; diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index 7187b5f7aaf9..2a67cb18e6c7 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -491,7 +491,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { } if (current.dialect.supports.returnValues) { - describe('with returning option', () => { + describe('returns values', () => { it('works with upsert on id', async function() { const [user0, created0] = await this.User.upsert({ id: 42, username: 'john' }, { returning: true }); expect(user0.get('id')).to.equal(42); @@ -576,26 +576,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(created).to.be.false; } }); - }); - it('should return default value set by the database (upsert)', async function() { - const User = this.sequelize.define('User', { - name: { type: DataTypes.STRING, primaryKey: true }, - code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } + it('should return default value set by the database (upsert)', async function() { + const User = this.sequelize.define('User', { + name: { type: DataTypes.STRING, primaryKey: true }, + code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } + }); + + await User.sync({ force: true }); + + const [user, created] = await User.upsert({ name: 'Test default value' }, { returning: true }); + + expect(user.name).to.be.equal('Test default value'); + expect(user.code).to.be.equal(2020); + + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.true; + } }); - - await User.sync({ force: true }); - - const [user, created] = await User.upsert({ name: 'Test default value' }, { returning: true }); - - expect(user.name).to.be.equal('Test default value'); - expect(user.code).to.be.equal(2020); - - if (dialect === 'sqlite' || dialect === 'postgres') { - expect(created).to.be.null; - } else { - expect(created).to.be.true; - } }); } }); diff --git a/test/integration/sequelize/query.test.js b/test/integration/sequelize/query.test.js index 6ed8b346ed6d..ff53f738eddd 100644 --- a/test/integration/sequelize/query.test.js +++ b/test/integration/sequelize/query.test.js @@ -605,23 +605,5 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { .to.eventually.deep.equal([{ 'sum': '5050' }]); }); } - - if (Support.getTestDialect() === 'sqlite') { - it('binds array parameters for upsert are replaced. $$ unescapes only once', async function() { - let logSql; - await this.sequelize.query('select $1 as foo, $2 as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: [1, 2], logging(s) { logSql = s; } }); - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); - - it('binds named parameters for upsert are replaced. $$ unescapes only once', async function() { - let logSql; - await this.sequelize.query('select $one as foo, $two as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); - } }); }); From 97ba2422f2b14f100e202b86e07c6f13cf926e38 Mon Sep 17 00:00:00 2001 From: Emilio Cristalli Date: Sun, 21 Mar 2021 23:43:51 -0300 Subject: [PATCH 280/414] fix(types): allow `(keyof TAttributes)[]` in `UpdateOptions.returning` (#13130) --- types/lib/model.d.ts | 2 +- types/test/update.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 types/test/update.ts diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index f364ab04e40d..72e3a3233b83 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -875,7 +875,7 @@ export interface UpdateOptions extends Logging, Transactionab /** * Return the affected rows (only for postgres) */ - returning?: boolean; + returning?: boolean | (keyof TAttributes)[]; /** * How many rows to update (only for mysql and mariadb) diff --git a/types/test/update.ts b/types/test/update.ts new file mode 100644 index 000000000000..0f944c39ac3c --- /dev/null +++ b/types/test/update.ts @@ -0,0 +1,18 @@ +import { Model } from 'sequelize'; +import { User } from './models/User'; + +class TestModel extends Model { +} + +TestModel.update({}, { where: {} }); +TestModel.update({}, { where: {}, returning: false }); +TestModel.update({}, { where: {}, returning: true }); +TestModel.update({}, { where: {}, returning: ['foo'] }); + + +User.update({}, { where: {} }); +User.update({}, { where: {}, returning: true }); +User.update({}, { where: {}, returning: false }); +User.update({}, { where: {}, returning: ['username'] }); +// @ts-expect-error +User.update({}, { where: {}, returning: ['foo'] }); From a663c54989de6dc873fdc1825d77e3e9731451ad Mon Sep 17 00:00:00 2001 From: markvp <6936351+markvp@users.noreply.github.com> Date: Mon, 22 Mar 2021 11:00:18 +0800 Subject: [PATCH 281/414] fix(query-generator): use `AND` in sql for `not`/`between` (#13043) --- lib/dialects/abstract/query-generator.js | 4 +++- test/unit/sql/where.test.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 4d8d1f434104..c9793a45693e 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -2167,7 +2167,9 @@ class QueryGenerator { model: factory }); } - if (typeof value === 'boolean') { + if ([this.OperatorMap[Op.between], this.OperatorMap[Op.notBetween]].includes(smth.comparator)) { + value = `${this.escape(value[0])} AND ${this.escape(value[1])}`; + } else if (typeof value === 'boolean') { value = this.booleanValue(value); } else { value = this.escape(value); diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js index 779d7828c85b..7739395f2d8e 100644 --- a/test/unit/sql/where.test.js +++ b/test/unit/sql/where.test.js @@ -1263,5 +1263,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { current.where(current.fn('lower', current.col('name')), null)], { default: '(SUM([hours]) > 0 AND lower([name]) IS NULL)' }); + + testsql(current.where(current.col('hours'), Op.between, [0, 5]), { + default: '[hours] BETWEEN 0 AND 5' + }); + + testsql(current.where(current.col('hours'), Op.notBetween, [0, 5]), { + default: '[hours] NOT BETWEEN 0 AND 5' + }); }); }); From a854af44558077a0bffb5912a5e538cbecfde193 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 22 Mar 2021 00:12:31 -0300 Subject: [PATCH 282/414] docs: update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 157bcb743006..42c76483af80 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ You can find the detailed changelog [here](https://github.com/sequelize/sequeliz ## Note: Looking for maintainers! -Due to various personal reasons, a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy recently. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: +Recently, a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: -We are looking for more core maintainers who are interested in finishing the current TypeScript migration, extending the documentation for v6, organizing issues, reviewing PRs, streamlining the overall code base and planning the future roadmap. +We are looking for more core maintainers who are interested in improving/fixing our TypeScript typings, improving the documentation, organizing issues, reviewing PRs, streamlining the overall code base and planning the future roadmap. -If that sounds interesting to you, please reach out to us on [Slack](https://sequelize.slack.com/). We are looking forward to meet you! +If that sounds interesting to you, please reach out to us on [our Slack channel](https://sequelize.slack.com/) by sending a direct message to *Pedro A P B*. If you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/). We are looking forward to meet you! ## Installation From 271c08161a6de031ac980cea3fe236c3a8a3e9c0 Mon Sep 17 00:00:00 2001 From: papb Date: Mon, 22 Mar 2021 00:15:25 -0300 Subject: [PATCH 283/414] docs(upsert): improve return description --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 772230ad6239..926cba4406ab 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2427,7 +2427,7 @@ class Model { * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * - * @returns {Promise} returns record and whether row was created or updated as boolean. For Postgres/SQLite dialects boolean value is always null`. + * @returns {Promise<[Model, boolean | null]>} returns an array with two elements, the first being the new record and the second being `true` if it was just created or `false` if it already existed (except on Postgres and SQLite, which can't detect this and will always return `null` instead of a boolean). */ static async upsert(values, options) { options = { From 5b16b32259f0599a6af2d1eb625622da9054265e Mon Sep 17 00:00:00 2001 From: Daniel Schwartz <505433+danielschwartz@users.noreply.github.com> Date: Mon, 22 Mar 2021 21:42:27 -0400 Subject: [PATCH 284/414] fix(types): fix `Model.prototype.previous()` (#13042) --- types/lib/model.d.ts | 1 + types/test/model.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 72e3a3233b83..da2b3e1bedf6 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2709,6 +2709,7 @@ export abstract class Model; public previous(key: K): TCreationAttributes[K] | undefined; /** diff --git a/types/test/model.ts b/types/test/model.ts index 0bc5486ca352..d7eaf3b0851a 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -184,3 +184,4 @@ expectTypeOf(modelWithAttributes.previous).parameter(0).toEqualTypeOf(); expectTypeOf(modelWithAttributes.previous).returns.toEqualTypeOf(); expectTypeOf(modelWithAttributes.previous('name')).toEqualTypeOf(); +expectTypeOf(modelWithAttributes.previous()).toEqualTypeOf>(); From ac368c966906786dc9eb20e4a006329b2854f9c3 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Mon, 29 Mar 2021 10:25:39 +0200 Subject: [PATCH 285/414] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 3 ++- .github/ISSUE_TEMPLATE/documentational-issue.md | 2 -- .github/ISSUE_TEMPLATE/feature_request.md | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b9f4e14b62c3..40fd6c845b9c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,7 +10,8 @@ assignees: '' ## Issue Description diff --git a/.github/ISSUE_TEMPLATE/documentational-issue.md b/.github/ISSUE_TEMPLATE/documentational-issue.md index 804a80552387..accc38e3a0ba 100644 --- a/.github/ISSUE_TEMPLATE/documentational-issue.md +++ b/.github/ISSUE_TEMPLATE/documentational-issue.md @@ -44,5 +44,3 @@ Add any other context or screenshots about the issue here. - [ ] Yes, I have the time but I don't know how to start, I would need guidance. - [ ] No, I don't have the time, although I believe I could do it if I had the time... - [ ] No, I don't have the time and I wouldn't even know how to start. - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a250df30acee..e451c55d4399 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,6 +4,7 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' + --- -## Issue Description +## Issue Creation Checklist -### What are you doing? +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + +## Bug Description + +### SSCCE ```js // You can delete this code block if you have included a link to your SSCCE above! -// MINIMAL, SELF-CONTAINED code here (SSCCE/MCVE/reprex) +const { createSequelizeInstance } = require('./dev/sscce-helpers'); +const { Model, DataTypes } = require('.'); + +const sequelize = createSequelizeInstance({ benchmark: true }); + +class User extends Model {} +User.init({ + username: DataTypes.STRING, + birthday: DataTypes.DATE +}, { sequelize, modelName: 'user' }); + +(async () => { + await sequelize.sync({ force: true }); + + const jane = await User.create({ + username: 'janedoe', + birthday: new Date(1980, 6, 20) + }); + + console.log('\nJane:', jane.toJSON()); + + await sequelize.close(); +})(); ``` ### What do you expect to happen? @@ -54,16 +79,15 @@ Output here ### Additional context -Add any other context or screenshots about the feature request here. +Add any other context and details here. ### Environment - Sequelize version: XXX - Node.js version: XXX -- Operating System: XXX -- If TypeScript related: TypeScript version: XXX +- If TypeScript related: TypeScript version: XXX -## Issue Template Checklist +## Bug Report Checklist diff --git a/.github/ISSUE_TEMPLATE/docs_issue.md b/.github/ISSUE_TEMPLATE/docs_issue.md new file mode 100644 index 000000000000..c26ce93b8c62 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_issue.md @@ -0,0 +1,32 @@ +--- +name: Docs issue +about: Documentation is unclear, or otherwise insufficient/misleading +title: '' +labels: 'type: docs' +assignees: '' + +--- + + + +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + +## Issue Description + +### What was unclear/insufficient/not covered in the documentation + +Write here. + +### If possible: Provide some suggestion on how we can enhance the docs + +Write here. + +### Additional context + +Add any other context or screenshots about the issue here. diff --git a/.github/ISSUE_TEMPLATE/documentational-issue.md b/.github/ISSUE_TEMPLATE/documentational-issue.md deleted file mode 100644 index accc38e3a0ba..000000000000 --- a/.github/ISSUE_TEMPLATE/documentational-issue.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: Docs issue -about: Documentation is unclear, or otherwise insufficient/misleading -title: '' -labels: 'type: docs' -assignees: '' - ---- - - - -## Issue Description - -### What was unclear/insufficient/not covered in the documentation - -Write here. - -### If possible: Provide some suggestion on how we can enhance the docs - -Write here. - -### Additional context -Add any other context or screenshots about the issue here. - -## Issue Template Checklist - - - -### Is this issue dialect-specific? - -- [ ] No. This issue is relevant to Sequelize as a whole. -- [ ] Yes. This issue only applies to the following dialect(s): XXX, YYY, ZZZ -- [ ] I don't know. - -### Would you be willing to resolve this issue by submitting a Pull Request? - - - -- [ ] Yes, I have the time and I know how to start. -- [ ] Yes, I have the time but I don't know how to start, I would need guidance. -- [ ] No, I don't have the time, although I believe I could do it if I had the time... -- [ ] No, I don't have the time and I wouldn't even know how to start. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index e451c55d4399..9b0b4062f53e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,14 +10,18 @@ assignees: '' +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + ## Feature Description ### Is your feature request related to a problem? Please describe. -A clear and concise description of what the problem is. Example: I'm always frustrated when [...] +A clear and concise description of what the problem is. Example: I'm always frustrated when ... ### Describe the solution you'd like @@ -45,8 +49,8 @@ Add any other context or screenshots about the feature request here. ### Is this feature dialect-specific? -- [ ] No. This issue is relevant to Sequelize as a whole. -- [ ] Yes. This issue only applies to the following dialect(s): XXX, YYY, ZZZ +- [ ] No. This feature is relevant to Sequelize as a whole. +- [ ] Yes. This feature only applies to the following dialect(s): XXX, YYY, ZZZ ### Would you be willing to implement this feature by submitting a Pull Request? diff --git a/.github/ISSUE_TEMPLATE/other_issue.md b/.github/ISSUE_TEMPLATE/other_issue.md index e8a25ba61747..8b29a78cad13 100644 --- a/.github/ISSUE_TEMPLATE/other_issue.md +++ b/.github/ISSUE_TEMPLATE/other_issue.md @@ -10,9 +10,13 @@ assignees: '' +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + ## Issue Description A clear and concise description of what is this issue about. @@ -23,11 +27,8 @@ If applicable, you can add some code. In this case, an SSCCE/MCVE/reprex is much Check http://sscce.org/ or https://stackoverflow.com/help/minimal-reproducible-example to learn more about SSCCE/MCVE/reprex. --> -### StackOverflow / Slack attempts - -If you have tried asking on StackOverflow / Slack about this, add a link to that here. - ### Additional context + Add any other context or screenshots about the issue here. ## Issue Template Checklist diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4aa0fee41c6..5242d218e547 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,153 +1,182 @@ -_Please note!_ The github issue tracker should only be used for feature requests and bugs with a clear description of the issue and the expected behaviour (see below). All questions belong on [Slack](https://sequelize.slack.com) & [StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js). +# Introduction -# Issues +We are happy to see that you might be interested in contributing to Sequelize! There is no need to ask for permission to contribute. For example, anyone can open issues and propose changes to the source code (via Pull Requests). Here are some ways people can contribute: -Issues are always very welcome - after all, they are a big part of making sequelize better. However, there are a couple of things you can do to make the lives of the developers _much, much_ easier: +* Opening well-written bug reports (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +* Opening well-written feature requests (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +* Proposing improvements to the documentation (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +* Opening Pull Requests to fix bugs or make other improvements +* Reviewing (i.e. commenting on) open Pull Requests, to help their creators improve it if needed and allow maintainers to take less time looking into them +* Helping to clarify issues opened by others, commenting and asking for clarification +* Answering [questions tagged with `sequelize.js` on StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) +* Helping people in our [public Slack channel](https://sequelize.slack.com/) (note: if you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/)) -### Tell us: +Sequelize is strongly moved by contributions from people like you. All maintainers also work on their free time here. -- What you are doing? - - Post a _minimal_ code sample that reproduces the issue, including models and associations - - What do you expect to happen? - - What is actually happening? -- Which dialect you are using (postgres, mysql etc)? -- Which sequelize version you are using? +## Opening Issues -When you post code, please use [Github flavored markdown](https://help.github.com/articles/github-flavored-markdown), in order to get proper syntax highlighting! +Issues are always very welcome - after all, they are a big part of making Sequelize better. An issue usually describes a bug, feature request, or documentation improvement request. -If you can even provide a pull request with a failing unit test, we will love you long time! Plus your issue will likely be fixed much faster. +If you open an issue, try to be as clear as possible. Don't assume that the maintainers will immediately understand the problem. Write your issue in a way that new contributors can also help (add links to helpful resources when applicable). -# Pull requests +Make sure you know what is an [SSCCE](http://sscce.org/)/[MCVE](https://stackoverflow.com/help/minimal-reproducible-example). -We're glad to get pull request if any functionality is missing or something is buggy. However, there are a couple of things you can do to make life easier for the maintainers: +Learn to use [GitHub flavored markdown](https://help.github.com/articles/github-flavored-markdown) to write an issue that is nice to read. -- Explain the issue that your PR is solving - or link to an existing issue -- Make sure that all existing tests pass -- Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#coding-guidelines) -- Add some tests for your new functionality or a test exhibiting the bug you are solving. Ideally all new tests should not pass _without_ your changes. - - Use [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) in all new tests. Specifically this means: - - don't use `EventEmitter`, `QueryChainer` or the `success`, `done` and `error` events - - don't use a done callback in your test, just return the promise chain. - - Small bugfixes and direct backports to the 4.x branch are accepted without tests. -- If you are adding to / changing the public API, remember to add API docs, in the form of [JSDoc style](http://usejsdoc.org/about-getting-started.html) comments. See [section 4a](#4a-check-the-documentation) for the specifics. +### Opening an issue to report a bug -Interested? Coolio! Here is how to get started: +It is essential that you provide an [SSCCE](http://sscce.org/)/[MCVE](https://stackoverflow.com/help/minimal-reproducible-example) for your issue. You can use the [papb/sequelize-sscce](https://github.com/papb/sequelize-sscce) repository. Tell us what is the actual (incorrect) behavior and what should have happened (do not expect the maintainers to know what should happen!). Make sure you checked the bug persists in the latest Sequelize version. -### 1. Prepare your environment +If you can even provide a Pull Request with a failing test (unit test or integration test), that is great! The bug will likely be fixed much faster in this case. -Here comes a little surprise: You need [Node.JS](http://nodejs.org). +### Opening an issue to request a new feature -### 2. Install the dependencies +We're more than happy to accept feature requests! Before we get into how you can bring these to our attention, let's talk about our process for evaluating feature requests: -Just "cd" into sequelize directory and run `npm ci`, see an example below: +- A feature request can have three states - *approved*, *pending* and *rejected*. + - *Approved* feature requests are accepted by maintainers as a valuable addition to Sequelize, and are ready to be worked on by anyone. + - *Rejected* feature requests were considered not applicable to be a part of the Sequelize ORM. This can change, so feel free to comment on a rejected feature request providing a good reasoning and clarification on why it should be reconsidered. + - *Pending* feature requests are waiting to be looked at by maintainers. They may or may not need clarification. Contributors can still submit pull requests implementing a pending feature request, if they want, at their own risk of having the feature request rejected (and the pull request closed without being merged). -```sh -$ cd path/to/sequelize -$ npm ci -$ npm run tsc -``` -### 3. Database +Please be sure to communicate the following: -Database instances for testing can be started using Docker or you can use local instances of MySQL and PostgreSQL. + 1. What problem your feature request aims to solve OR what aspect of the Sequelize workflow it aims to improve. -#### 3.a Local instances + 2. Under what conditions are you anticipating this feature to be most beneficial? -For MySQL and PostgreSQL you'll need to create a DB called `sequelize_test`. -For MySQL this would look like this: + 3. Why does it make sense that Sequelize should integrate this feature? -```sh -$ echo "CREATE DATABASE sequelize_test;" | mysql -uroot -``` + 4. See our [Feature Request template](https://github.com/sequelize/sequelize/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) for more details on what to include. Please be sure to follow this template. -**HINT:** by default, your local MySQL install must be with username `root` without password. If you want to customize that, you can set the environment variables `SEQ_DB`, `SEQ_USER`, `SEQ_PW`, `SEQ_HOST` and `SEQ_PORT`. + If we don't approve your feature request, we'll provide you with our reasoning before closing it out. Some common reasons for denial may include (but are not limited to): -For Postgres, creating the database and (optionally) adding the test user this would look like: + - Something too similar to already exists within Sequelize + - This feature seems out of scope of what Sequelize exists to accomplish -```sh -$ psql + We don't want to deny feature requests that could potentially make our users lives easier, so please be sure to clearly communicate your goals within your request! -# create database sequelize_test; -# create user postgres with superuser; -- optional; usually built-in -``` +### Opening an issue to request improvements to the documentation -You may need to specify credentials using the environment variables `SEQ_PG_USER` and `SEQ_PG_PW` when running tests or set a password of 'postgres' for the postgres user on your local database to allow sequelize to connect via TCP to localhost. Refer to `test/config/config.js` for the default credentials and environment variables. +Please state clearly what is missing/unclear/confusing in the documentation. If you have a rough idea of what should be written, please provide a suggestion within the issue. -For Postgres you may also need to install the `postgresql-postgis` package (an optional component of some Postgres distributions, e.g. Ubuntu). The package will be named something like: `postgresql--postgis-`, e.g. `postgresql-9.5-postgis-2.2`. You should be able to find the exact package name on a Debian/Ubuntu system by running the command: `apt-cache search -- -postgis`. +## Opening a Pull Request -Create the following extensions in the test database: +A Pull Request is a request for maintainers to "pull" a specific change in code (or documentation) from your copy ("fork") into the repository. -``` -CREATE EXTENSION postgis; -CREATE EXTENSION hstore; -CREATE EXTENSION btree_gist; -CREATE EXTENSION citext; -``` +Anyone can open a Pull Request, there is no need to ask for permission. Maintainers will look at your pull request and tell you if anything else must be done before it can be merged. -#### 3.b Docker +The target of the Pull Request should be the `main` branch (or in rare cases the `v5` branch, if previously agreed with a maintainer). -Make sure `docker` and `docker-compose` are installed. +Please check the *allow edits from maintainers* box when opening it. Thank you in advance for any pull requests that you open! -If running on macOS, install [Docker for Mac](https://docs.docker.com/docker-for-mac/). +If you started to work on something but didn't finish it yet, you can open a draft pull request if you want (by choosing the "draft" option). Maintainers will know that it's not ready to be reviewed yet. -Now launch the docker mysql and postgres servers with this command (you can add `-d` to run them in daemon mode): +A pull request should mention in its description one or more issues that is addresses. If your pull request does not address any existing issue, explain in its description what it is doing - you are also welcome to write an issue first, and then mention this new issue in the PR description. -```sh -$ docker-compose up postgres-95 mysql-57 mssql -``` +If your pull request implements a new feature, it's better if the feature was already explicitly approved by a maintainer, otherwise you are taking the risk of having the feature request rejected later and your pull request closed without merge. -> **_NOTE:_** If you get the following output: -> -> ``` -> ... -> Creating mysql-57 ... error -> -> ERROR: for mysql-57 Cannot create container for service mysql-57: b'create .: volume name is too short, names should be at least two alphanumeric characters' -> -> ERROR: for mysql-57 Cannot create container for service mysql-57: b'create .: volume name is too short, names should be at least two alphanumeric characters' -> ERROR: Encountered errors while bringing up the project. -> ``` -> -> You need to set the variables `MARIADB_ENTRYPOINT` and `MYSQLDB_ENTRYPOINT` accordingly: -> -> ```sh -> $ export MARIADB_ENTRYPOINT="$PATH_TO_PROJECT/test/config/mariadb" -> $ export MYSQLDB_ENTRYPOINT="$PATH_TO_PROJECT/test/config/mysql" -> ``` - -**MSSQL:** Please run `npm run setup-mssql` to create the test database. - -**POSTGRES:** Sequelize uses [special](https://github.com/sushantdhiman/sequelize-postgres) Docker image for PostgreSQL, which install all the extensions required by tests. +Once you open a pull request, our automated checks will run (they take a few minutes). Make sure they are all passing. If they're not, make new commits to your branch fixing that, and the pull request will pick them up automatically and rerun our automated checks. -### 4. Running tests +Note: if you believe a test failed but is completely unrelated to your changes, it could be a rare situation of a *flaky test* that is not your fault, and if it's indeed the case, and everything else passed, a maintainer will ignore the *flaky test* and merge your pull request, so don't worry. + +A pull request that fixes a bug or implements a new feature must add at least one automated test that: + +- Passes +- Would not pass if executed without your implementation + + +## How to prepare a development environment for Sequelize + +### 0. Requirements + +Most operating systems provide all the needed tools (including Windows, Linux and MacOS): + +* Mandatory: + + * [Node.js](http://nodejs.org) + * [Git](https://git-scm.com/) + +* Optional (recommended): + + * [Docker](https://docs.docker.com/get-docker/) + * It is not mandatory because you can easily locally run tests against SQLite without it. + * It is practically mandatory if you want to locally run tests against any other database engine (MySQL, MariaDB, Postgres and MSSQL), unless you happen to have the engine installed and is willing to make some manual configuration. + * [Visual Studio Code](https://code.visualstudio.com/) + * [EditorConfig extension](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) + * Also run `npm install --global editorconfig` to make sure this extension will work properly + * [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + +### 1. Clone the repository + +Clone the repository (if you haven't already) via `git clone https://github.com/sequelize/sequelize`. If you plan on submitting a pull request, you can create a fork by clicking the *fork* button and clone it instead with `git clone https://github.com/your-github-username/sequelize`, or add your fork as an upstream on the already cloned repo with `git remote add upstream https://github.com/your-github-username/sequelize`. + +### 2. Install the Node.js dependencies + +Run `npm install` (or `yarn install`) within the cloned repository folder. + +### 3. Prepare local databases to run tests + +If you're happy to run tests only against an SQLite database, you can skip this section. -All tests are located in the `test` folder (which contains the -lovely [Mocha](https://mochajs.org/) tests). +#### 3a. With Docker (recommended) -```sh -$ npm run test-all || test-mysql || test-sqlite || test-mssql || test-postgres || test-postgres-native +If you have Docker installed, use any of the following commands to start fresh local databases of the dialect of your choice: -$ # alternatively you can pass database credentials with $variables when testing -$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password npm test +* `npm run setup-mariadb` +* `npm run setup-mysql` +* `npm run setup-postgres` +* `npm run setup-mssql` + +*Note:* if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). + +Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests. + +##### Hint for Postgres + +You can also easily start a local [pgadmin4](https://www.pgadmin.org/docs/pgadmin4/latest/) instance at `localhost:8888` to inspect the contents of the test Postgres database as follows: + +``` +docker run -d --name pgadmin4 -p 8888:80 -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=sequelize_test' dpage/pgadmin4 ``` -For docker users you can use these commands instead +#### 3b. Without Docker + +You will have to manually install and configure each of database engines you want. Check the `dev/dialect-name` folder within this repository and look carefully at how it is defined via Docker and via the auxiliary bash script, and mimic that exactly (except for the database name, username, password, host and port, that you can customize via the `SEQ_DB`, `SEQ_USER`, `SEQ_PW`, `SEQ_HOST` and `SEQ_PORT` environment variables, respectively). + + + + +### 4. Running tests -```sh -$ DIALECT=mysql npm run test-docker # Or DIALECT=postgres for Postgres SQL +Before starting any work, try to run the tests locally in order to be sure your setup is fine. Start by running the SQLite tests: -# Only integration tests -$ DIALECT=mysql npm run test-docker-integration ``` +npm run test-sqlite +``` + +Then, if you want to run tests for another dialect, assuming you've set it up as written on section 3, run the corresponding command: + +* `npm run test-mysql` +* `npm run test-mariadb` +* `npm run test-postgres` +* `npm run test-mssql` + +There are also the `test-unit-*` and `test-integration-*` sets of npm scripts (for example, `test-integration-postgres`). + + -### 5. Commit +### 5. Commit your modifications + +Sequelize follows the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.em2hiij8p46d). The allowed categories are `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test` and `meta`. -Sequelize follows the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.em2hiij8p46d). Example: - feat(pencil): add 'graphiteWidth' option +``` +feat(pencil): add `graphiteWidth` option +``` -Commit messages are used to automatically generate a changelog. They will be validated automatically using [commitlint](https://github.com/marionebl/commitlint) +Commit messages are used to automatically generate a changelog and calculate the next version number according to [semver](https://semver.org/). They will be validated automatically using [commitlint](https://github.com/marionebl/commitlint). Then push and send your pull request. Happy hacking and thank you for contributing. @@ -158,13 +187,3 @@ Have a look at our [.eslintrc.json](https://github.com/sequelize/sequelize/blob/ # Contributing to the documentation For contribution guidelines for the documentation, see [CONTRIBUTING.DOCS.md](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.DOCS.md). - -# Publishing a release (For Maintainers) - -1. Ensure that latest build on the main branch is green -2. Ensure your local code is up to date (`git pull origin main`) -3. `npm version patch|minor|major` (see [Semantic Versioning](http://semver.org)) -4. Update changelog to match version number, commit changelog -5. `git push --tags origin main` -6. `npm publish .` -7. Copy changelog for version to release notes for version on github diff --git a/README.md b/README.md index 42c76483af80..b1ae2d0b4297 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.org/master). You might also be interested in the [API Reference](https://sequelize.org/master/identifiers). +Would you like to contribute? Read [our contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) to know more. There are many ways to help. + ### v6 Release You can find the detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). From aaf32349bacf036af028359a7888cafce916bde1 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 24 Jun 2021 14:06:27 -0300 Subject: [PATCH 290/414] meta: refactor mocha configuration --- .mocharc.jsonc | 6 ++++++ package.json | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .mocharc.jsonc diff --git a/.mocharc.jsonc b/.mocharc.jsonc new file mode 100644 index 000000000000..9d0c45dd152b --- /dev/null +++ b/.mocharc.jsonc @@ -0,0 +1,6 @@ +{ + "exit": true, + "check-leaks": true, + "timeout": 30000, + "reporter": "spec" +} diff --git a/package.json b/package.json index a37ff6034e43..10358373e01c 100644 --- a/package.json +++ b/package.json @@ -180,14 +180,14 @@ "----------------------------------------- documentation -------------------------------------------": "", "docs": "rimraf esdoc && esdoc -c docs/esdoc-config.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER && node docs/run-docs-transforms.js && node docs/redirects/create-redirects.js && rimraf esdoc/file esdoc/source.html", "----------------------------------------- tests ---------------------------------------------------": "", - "test-unit": "mocha --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.test.js\"", - "test-integration": "mocha --exit --check-leaks --colors -t 30000 --reporter spec \"test/integration/**/*.test.js\"", + "test-unit": "mocha \"test/unit/**/*.test.js\"", + "test-integration": "mocha \"test/integration/**/*.test.js\"", "teaser": "node test/teaser.js", "test": "npm run teaser && npm run test-unit && npm run test-integration", "----------------------------------------- coverage ------------------------------------------------": "", "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", - "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha -t 30000 --exit \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", - "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha -t 30000 --exit \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", + "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", + "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", "merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"", "----------------------------------------- local test dbs ------------------------------------------": "", "start-mariadb": "bash dev/mariadb/10.3/start.sh", From 0a9031253e8efb2548987f0d242de1e2af0e228b Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 24 Jun 2021 14:07:07 -0300 Subject: [PATCH 291/414] meta: remove unused Dockerfile --- Dockerfile | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 9cff06d2fa9d..000000000000 --- a/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM node:10 - -RUN apt-get install libpq-dev - -WORKDIR /sequelize -VOLUME /sequelize - -COPY . /sequelize From 97b3767ff2d79362b5d9191c57cdf211646c169c Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 24 Jun 2021 22:58:15 -0300 Subject: [PATCH 292/414] meta: improve `contributing.md` and `sscce.js` --- .mocharc.jsonc | 9 +++++++++ CONTRIBUTING.md | 34 ++++++++++++++++++++++++++++++++-- sscce.js | 6 ++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/.mocharc.jsonc b/.mocharc.jsonc index 9d0c45dd152b..1a10e465d7ff 100644 --- a/.mocharc.jsonc +++ b/.mocharc.jsonc @@ -1,4 +1,13 @@ { + // You can temporarily modify this file during local development to add `spec` (and + // even `grep`) in order to be able to call `DIALECT=some-dialect npx mocha` from a + // terminal and execute only a one (or a few) tests (such as new tests you are + // creating, for example). + // Recall that if you want to `grep` over all tests, you need to specify `spec` as + // `"test/**/*.test.js"`. Not specifying `spec` and calling `npx mocha` will not + // execute any test. + // "spec": ["test/**/bulk-create.test.js", "test/**/upsert.test.js", "test/**/insert.test.js", "test/**/query-generator.test.js"], + // "grep": ["some test title here"], "exit": true, "check-leaks": true, "timeout": 30000, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5242d218e547..a454b3b55ce9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,8 @@ It is essential that you provide an [SSCCE](http://sscce.org/)/[MCVE](https://st If you can even provide a Pull Request with a failing test (unit test or integration test), that is great! The bug will likely be fixed much faster in this case. +You can also create and execute your SSCCE locally: see [Section 5](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#running-an-sscce). + ### Opening an issue to request a new feature We're more than happy to accept feature requests! Before we get into how you can bring these to our attention, let's talk about our process for evaluating feature requests: @@ -130,7 +132,7 @@ If you have Docker installed, use any of the following commands to start fresh l *Note:* if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). -Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests. +Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests (or an SSCCE). ##### Hint for Postgres @@ -164,9 +166,37 @@ Then, if you want to run tests for another dialect, assuming you've set it up as There are also the `test-unit-*` and `test-integration-*` sets of npm scripts (for example, `test-integration-postgres`). +#### 4.1. Running only some tests + +While you're developing, you may want to execute only a single test (or a few), instead of executing everything (which takes some time). You can easily achieve this by modifying the `.mocharc.jsonc` file (but don't commit those changes!) to use `spec` (and maybe `grep`) from Mocha to specify the desired tests. Then, simply call `DIALECT=some-dialect npx mocha` from your terminal (example: `DIALECT=postgres npx mocha`). + +Hint: if you're creating a new test, you can execute only that test locally against all dialects by adapting the `spec` and `grep` options on `.mocharc.jsonc` and running the following from your terminal (assuming you already set up the database instances via the corresponding `npm run setup-*` calls, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended)): + +``` +DIALECT=mariadb npx mocha && DIALECT=mysql npx mocha && DIALECT=postgres npx mocha && DIALECT=sqlite npx mocha && DIALECT=mssql npx mocha +``` + + +### 5. Running an SSCCE + +You can modify the `sscce.js` file (at the root of the repository) to create an [SSCCE](http://www.sscce.org/). + +Run it for the dialect of your choice using one of the following commands: + +* `npm run sscce-mariadb` +* `npm run sscce-mysql` +* `npm run sscce-postgres` +* `npm run sscce-sqlite` +* `npm run sscce-mssql` + +_Note:_ First, you need to set up (once) the database instance for corresponding dialect, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended). + +#### 5.1. Debugging an SSCCE with Visual Studio Code + +If you open the `package.json` file with Visual Studio Code, you will find a small `debug` button rendered right above the `"scripts": {` line. By clicking it, a popup will appear where you can choose which npm script you want to debug. Select one of the `sscce-*` scripts (listed above) and VSCode will immediately launch your SSCCE in debug mode (meaning that it will stop on any breakpoints that you place within `sscce.js` or any other Sequelize source code). -### 5. Commit your modifications +### 6. Commit your modifications Sequelize follows the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.em2hiij8p46d). The allowed categories are `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test` and `meta`. diff --git a/sscce.js b/sscce.js index b1363dfd2a88..0c68f7d88102 100644 --- a/sscce.js +++ b/sscce.js @@ -1,8 +1,12 @@ 'use strict'; +// See https://github.com/papb/sequelize-sscce as another option for running SSCCEs. + const { createSequelizeInstance } = require('./dev/sscce-helpers'); const { Model, DataTypes } = require('.'); +const { expect } = require('chai'); // You can use `expect` on your SSCCE! + const sequelize = createSequelizeInstance({ benchmark: true }); class User extends Model {} @@ -22,4 +26,6 @@ User.init({ console.log('\nJane:', jane.toJSON()); await sequelize.close(); + + expect(jane.username).to.equal('janedoe'); })(); From 6dcb565ea60ce4fbc85c3e1003b0a404797ffa50 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 24 Jun 2021 23:00:28 -0300 Subject: [PATCH 293/414] fix(bulk-create): `ON CONFLICT` with unique index (#13345) Co-authored-by: Brian Schardt <35744893+brianschardt@users.noreply.github.com> Co-authored-by: AllAwesome497 <47748690+AllAwesome497@users.noreply.github.com> --- lib/model.js | 21 ++++++-- test/integration/model/bulk-create.test.js | 61 ++++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/lib/model.js b/lib/model.js index 926cba4406ab..567f37b37bda 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2683,11 +2683,24 @@ class Model { // Map updateOnDuplicate attributes to fields if (options.updateOnDuplicate) { options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr); - // Get primary keys for postgres to enable updateOnDuplicate - options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value(); - if (Object.keys(model.uniqueKeys).length > 0) { - options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length >= 1).map(c => c.fields).reduce(c => c[0]).value(); + + const upsertKeys = []; + + for (const i of model._indexes) { + if (i.unique && !i.where) { // Don't infer partial indexes + upsertKeys.push(...i.fields); + } } + + const firstUniqueKey = Object.values(model.uniqueKeys).find(c => c.fields.length >= 1); + + if (firstUniqueKey && firstUniqueKey.fields) { + upsertKeys.push(...firstUniqueKey.fields); + } + + options.upsertKeys = upsertKeys.length > 0 + ? upsertKeys + : Object.values(model.primaryKeys).map(x => x.field); } // Map returning attributes to fields diff --git a/test/integration/model/bulk-create.test.js b/test/integration/model/bulk-create.test.js index 7c92fe1eef74..4696d29e586a 100644 --- a/test/integration/model/bulk-create.test.js +++ b/test/integration/model/bulk-create.test.js @@ -638,6 +638,67 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(people[1].name).to.equal('Bob'); }); + it('[#12516] when the primary key column names and model field names are different and have composite unique index constraints', async function() { + const Person = this.sequelize.define( + 'Person', + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + autoIncrement: true, + primaryKey: true, + field: 'id' + }, + systemId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'system_id' + }, + system: { + type: DataTypes.STRING, + allowNull: false, + field: 'system' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, + { + indexes: [ + { + unique: true, + fields: ['system_id', 'system'] + } + ] + } + ); + + await Person.sync({ force: true }); + const inserts = [{ systemId: 1, system: 'system1', name: 'Alice' }]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].systemId).to.equal(1); + expect(people0[0].system).to.equal('system1'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { systemId: 1, system: 'system2', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { + updateOnDuplicate: ['systemId', 'system', 'name'] + }); + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); + }); }); From 1c1aa33068c608ad5c66a18d8aae27c697a5d89e Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Thu, 24 Jun 2021 23:03:04 -0300 Subject: [PATCH 294/414] refactor: nonempty array check style --- lib/dialects/abstract/query-interface.js | 4 ++-- lib/model.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index 274453c8d0ff..e1876d5cef64 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -768,8 +768,8 @@ class QueryInterface { const model = options.model; const primaryKeys = Object.values(model.primaryKeys).map(item => item.field); - const uniqueKeys = Object.values(model.uniqueKeys).filter(c => c.fields.length >= 1).map(c => c.fields); - const indexKeys = Object.values(model._indexes).filter(c => c.unique && c.fields.length >= 1).map(c => c.fields); + const uniqueKeys = Object.values(model.uniqueKeys).filter(c => c.fields.length > 0).map(c => c.fields); + const indexKeys = Object.values(model._indexes).filter(c => c.unique && c.fields.length > 0).map(c => c.fields); options.type = QueryTypes.UPSERT; options.updateOnDuplicate = Object.keys(updateValues); diff --git a/lib/model.js b/lib/model.js index 567f37b37bda..595d18147e8d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2692,7 +2692,7 @@ class Model { } } - const firstUniqueKey = Object.values(model.uniqueKeys).find(c => c.fields.length >= 1); + const firstUniqueKey = Object.values(model.uniqueKeys).find(c => c.fields.length > 0); if (firstUniqueKey && firstUniqueKey.fields) { upsertKeys.push(...firstUniqueKey.fields); @@ -3848,10 +3848,10 @@ class Model { const now = Utils.now(this.sequelize.options.dialect); let updatedAtAttr = this.constructor._timestampAttributes.updatedAt; - if (updatedAtAttr && options.fields.length >= 1 && !options.fields.includes(updatedAtAttr)) { + if (updatedAtAttr && options.fields.length > 0 && !options.fields.includes(updatedAtAttr)) { options.fields.push(updatedAtAttr); } - if (versionAttr && options.fields.length >= 1 && !options.fields.includes(versionAttr)) { + if (versionAttr && options.fields.length > 0 && !options.fields.includes(versionAttr)) { options.fields.push(versionAttr); } From 68ef4538d29af727a7989a7367f2616c67915210 Mon Sep 17 00:00:00 2001 From: Mohab Abd El-Dayem Date: Fri, 25 Jun 2021 04:05:15 +0200 Subject: [PATCH 295/414] docs(model-querying-basics.md): fix typo (#13324) --- docs/manual/core-concepts/model-querying-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index 42e89792fbe3..21d5b0608090 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -355,7 +355,7 @@ The above will generate: SELECT * FROM `Projects` WHERE ( - `Projects`.`name` = 'a project' + `Projects`.`name` = 'Some Project' AND NOT ( `Projects`.`id` IN (1,2,3) OR From 421f44d5305701711d84269e5228c089f888b636 Mon Sep 17 00:00:00 2001 From: Daren McCulley Date: Thu, 24 Jun 2021 22:17:57 -0400 Subject: [PATCH 296/414] docs(model-querying-basics.md): fix typo (#13256) --- docs/manual/core-concepts/model-querying-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index 21d5b0608090..cb38b32cd9ae 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -358,7 +358,7 @@ WHERE ( `Projects`.`name` = 'Some Project' AND NOT ( `Projects`.`id` IN (1,2,3) - OR + AND `Projects`.`description` LIKE 'Hello%' ) ) From deeb5c6de0e1f1183396c4f11c3ce43f95cabe3f Mon Sep 17 00:00:00 2001 From: Noah Chase Date: Thu, 24 Jun 2021 22:29:20 -0400 Subject: [PATCH 297/414] fix(plurals): bump inflection dependency (#13260) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 10358373e01c..c21614d24fe5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "dependencies": { "debug": "^4.1.1", "dottie": "^2.0.0", - "inflection": "1.12.0", + "inflection": "1.13.1", "lodash": "^4.17.20", "moment": "^2.26.0", "moment-timezone": "^0.5.31", From 8f2a0d528e34f5fe1c170873c1ab41d81e2d9f4d Mon Sep 17 00:00:00 2001 From: JacobLey <37151850+JacobLey@users.noreply.github.com> Date: Fri, 25 Jun 2021 10:29:14 -0400 Subject: [PATCH 298/414] fix(typings): model init returns model class, not instance (#13214) --- types/lib/model.d.ts | 6 +++--- types/test/model.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index da2b3e1bedf6..b579f2ca4787 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1622,10 +1622,10 @@ export abstract class Model( - this: ModelStatic, + public static init, M extends InstanceType>( + this: MS, attributes: ModelAttributes, options: InitOptions - ): Model; + ): MS; /** * Remove attribute from model definition diff --git a/types/test/model.ts b/types/test/model.ts index d7eaf3b0851a..fea3091a9f04 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -52,7 +52,7 @@ MyModel.update({}, { where: { foo: 'bar' }, paranoid: false}); const sequelize = new Sequelize('mysql://user:user@localhost:3306/mydb'); -MyModel.init({ +const model: typeof MyModel = MyModel.init({ virtual: { type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['num']), get() { From 143cc84c802b688f38b10c13dad67ccf28590fe5 Mon Sep 17 00:00:00 2001 From: JacobLey <37151850+JacobLey@users.noreply.github.com> Date: Fri, 25 Jun 2021 10:30:46 -0400 Subject: [PATCH 299/414] fix(typings): `returning` can specify column names (#13215) --- types/lib/model.d.ts | 2 +- types/test/upsert.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index b579f2ca4787..2159a8adb85c 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -719,7 +719,7 @@ export interface UpsertOptions extends Logging, Transactionab /** * Return the affected rows (only for postgres) */ - returning?: boolean; + returning?: boolean | (keyof TAttributes)[]; /** * Run validations before the row is inserted diff --git a/types/test/upsert.ts b/types/test/upsert.ts index 3f0523d183ca..5879482fbe22 100644 --- a/types/test/upsert.ts +++ b/types/test/upsert.ts @@ -1,15 +1,18 @@ import {Model} from "sequelize" import {sequelize} from './connection'; -class TestModel extends Model { +class TestModel extends Model<{ foo: string; bar: string }, {}> { } -TestModel.init({}, {sequelize}) +TestModel.init({ + foo: '', + bar: '', +}, {sequelize}) sequelize.transaction(async trx => { const res1: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, - fields: ['testField'], + fields: ['foo'], hooks: true, logging: true, returning: true, @@ -20,7 +23,7 @@ sequelize.transaction(async trx => { const res2: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, - fields: ['testField'], + fields: ['foo'], hooks: true, logging: true, returning: false, @@ -31,9 +34,10 @@ sequelize.transaction(async trx => { const res3: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, - fields: ['testField'], + fields: ['foo'], hooks: true, logging: true, + returning: ['foo'], searchPath: 'DEFAULT', transaction: trx, validate: true, From 63ceb7381b3a9a81e3fc27a68e578e37f8c316d3 Mon Sep 17 00:00:00 2001 From: JacobLey <37151850+JacobLey@users.noreply.github.com> Date: Fri, 25 Jun 2021 10:32:28 -0400 Subject: [PATCH 300/414] fix(typings): restrict update typings (#13216) --- types/lib/model.d.ts | 13 ++++++++++--- types/test/update.ts | 23 +++++++++++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 2159a8adb85c..17be752d2a5a 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2087,7 +2087,9 @@ export abstract class Model( this: ModelStatic, - values: Partial, + values: { + [key in keyof M['_attributes']]?: M['_attributes'][key] | Fn | Col | Literal; + }, options: UpdateOptions ): Promise<[number, M[]]>; @@ -2744,8 +2746,13 @@ export abstract class Model(key: K, value: this[K], options?: InstanceUpdateOptions): Promise; - public update(keys: object, options?: InstanceUpdateOptions): Promise; + public update(key: K, value: TModelAttributes[K] | Col | Fn | Literal, options?: InstanceUpdateOptions): Promise; + public update( + keys: { + [key in keyof TModelAttributes]?: TModelAttributes[key] | Fn | Col | Literal; + }, + options?: InstanceUpdateOptions + ): Promise; /** * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will diff --git a/types/test/update.ts b/types/test/update.ts index 0f944c39ac3c..9412eebc89ad 100644 --- a/types/test/update.ts +++ b/types/test/update.ts @@ -1,4 +1,4 @@ -import { Model } from 'sequelize'; +import { Model, fn, col, literal } from 'sequelize'; import { User } from './models/User'; class TestModel extends Model { @@ -11,8 +11,27 @@ TestModel.update({}, { where: {}, returning: ['foo'] }); User.update({}, { where: {} }); +User.update({ + id: 123, + username: fn('FN'), + firstName: col('id'), + lastName: literal('Smith'), +}, { where: {} }); User.update({}, { where: {}, returning: true }); User.update({}, { where: {}, returning: false }); User.update({}, { where: {}, returning: ['username'] }); -// @ts-expect-error +User.build().update({ + id: 123, + username: fn('FN'), + firstName: col('id'), + lastName: literal('Smith'), +}); +// @ts-expect-error invalid `returning` User.update({}, { where: {}, returning: ['foo'] }); +// @ts-expect-error no `where` +User.update({}, {}); +// @ts-expect-error invalid attribute +User.update({ foo: '' }, { where: {} }); +// @ts-expect-error invalid attribute +User.build().update({ foo: '' }); + From 6b0b532ab76d8ab0aa18905c3e688f610e528403 Mon Sep 17 00:00:00 2001 From: JacobLey <37151850+JacobLey@users.noreply.github.com> Date: Fri, 25 Jun 2021 10:40:05 -0400 Subject: [PATCH 301/414] fix(typings): allow `schema` for queryInterface methods (#13223) --- types/lib/model.d.ts | 4 ++-- types/lib/query-interface.d.ts | 32 ++++++++++++++++---------------- types/test/query-interface.ts | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 17be752d2a5a..587b10093ba0 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -4,7 +4,7 @@ import { DataType } from './data-types'; import { Deferrable } from './deferrable'; import { HookReturn, Hooks, ModelHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; -import { QueryOptions, IndexesOptions } from './query-interface'; +import { QueryOptions, IndexesOptions, TableName } from './query-interface'; import { Sequelize, SyncOptions } from './sequelize'; import { Transaction, LOCK } from './transaction'; import { Col, Fn, Literal, Where } from './utils'; @@ -1232,7 +1232,7 @@ export interface ModelAttributeColumnReferencesOptions { /** * If this column references another table, provide it here as a Model, or a string */ - model?: string | ModelType; + model?: TableName | ModelType; /** * The column of the foreign table that this column references diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index 0faa892c8bb1..ec4529af6d42 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -238,7 +238,7 @@ export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { type: 'foreign key'; references?: { - table: string; + table: TableName; field: string; }; onDelete: string; @@ -336,7 +336,7 @@ export class QueryInterface { * @param options Table options. */ public createTable( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, attributes: ModelAttributes, options?: QueryInterfaceCreateTableOptions ): Promise; @@ -347,7 +347,7 @@ export class QueryInterface { * @param tableName Table name. * @param options Query options, particularly "force". */ - public dropTable(tableName: string, options?: QueryInterfaceDropTableOptions): Promise; + public dropTable(tableName: TableName, options?: QueryInterfaceDropTableOptions): Promise; /** * Drops all tables. @@ -366,7 +366,7 @@ export class QueryInterface { /** * Renames a table */ - public renameTable(before: string, after: string, options?: QueryInterfaceOptions): Promise; + public renameTable(before: TableName, after: TableName, options?: QueryInterfaceOptions): Promise; /** * Returns all tables @@ -377,7 +377,7 @@ export class QueryInterface { * Describe a table */ public describeTable( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, options?: string | { schema?: string; schemaDelimiter?: string } & Logging ): Promise; @@ -385,7 +385,7 @@ export class QueryInterface { * Adds a new column to a table */ public addColumn( - table: string | { schema?: string; tableName?: string }, + table: TableName, key: string, attribute: ModelAttributeColumnOptions | DataType, options?: QueryInterfaceOptions @@ -395,7 +395,7 @@ export class QueryInterface { * Removes a column from a table */ public removeColumn( - table: string | { schema?: string; tableName?: string }, + table: TableName, attribute: string, options?: QueryInterfaceOptions ): Promise; @@ -404,7 +404,7 @@ export class QueryInterface { * Changes a column */ public changeColumn( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, attributeName: string, dataTypeOrOptions?: DataType | ModelAttributeColumnOptions, options?: QueryInterfaceOptions @@ -414,7 +414,7 @@ export class QueryInterface { * Renames a column */ public renameColumn( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, attrNameBefore: string, attrNameAfter: string, options?: QueryInterfaceOptions @@ -424,13 +424,13 @@ export class QueryInterface { * Adds a new index to a table */ public addIndex( - tableName: string, + tableName: TableName, attributes: string[], options?: QueryInterfaceIndexOptions, rawTablename?: string ): Promise; public addIndex( - tableName: string, + tableName: TableName, options: SetRequired, rawTablename?: string ): Promise; @@ -438,21 +438,21 @@ export class QueryInterface { /** * Removes an index of a table */ - public removeIndex(tableName: string, indexName: string, options?: QueryInterfaceIndexOptions): Promise; - public removeIndex(tableName: string, attributes: string[], options?: QueryInterfaceIndexOptions): Promise; + public removeIndex(tableName: TableName, indexName: string, options?: QueryInterfaceIndexOptions): Promise; + public removeIndex(tableName: TableName, attributes: string[], options?: QueryInterfaceIndexOptions): Promise; /** * Adds constraints to a table */ public addConstraint( - tableName: string, + tableName: TableName, options?: AddConstraintOptions & QueryInterfaceOptions ): Promise; /** * Removes constraints from a table */ - public removeConstraint(tableName: string, constraintName: string, options?: QueryInterfaceOptions): Promise; + public removeConstraint(tableName: TableName, constraintName: string, options?: QueryInterfaceOptions): Promise; /** * Shows the index of a table @@ -472,7 +472,7 @@ export class QueryInterface { /** * Get foreign key references details for the table */ - public getForeignKeyReferencesForTable(tableName: string, options?: QueryInterfaceOptions): Promise; + public getForeignKeyReferencesForTable(tableName: TableName, options?: QueryInterfaceOptions): Promise; /** * Inserts a new record diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index e24486db7791..09c403b23810 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -25,6 +25,15 @@ async function test() { }, type: DataTypes.INTEGER, }, + attr5: { + onDelete: 'cascade', + onUpdate: 'cascade', + references: { + key: 'id', + model: { schema: '', tableName: 'another_table_name' }, + }, + type: DataTypes.INTEGER, + }, createdAt: { type: DataTypes.DATE, }, @@ -49,8 +58,10 @@ async function test() { } } ); + await queryInterface.createTable({ tableName: '' }, {}); await queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.dropTable({ schema: '', tableName: 'nameOfTheExistingTable' }); await queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); @@ -65,6 +76,10 @@ async function test() { await queryInterface.dropAllTables(); await queryInterface.renameTable('Person', 'User'); + await queryInterface.renameTable( + { schema: '', tableName: 'Person' }, + { schema: '', tableName: 'User' }, + ); const tableNames: string[] = await queryInterface.showAllTables(); @@ -128,9 +143,11 @@ async function test() { ); await queryInterface.renameColumn('Person', 'signature', 'sig'); + await queryInterface.renameColumn({ schema: '', tableName: 'Person' }, 'signature', 'sig'); // This example will create the index person_firstname_lastname await queryInterface.addIndex('Person', ['firstname', 'lastname']); + await queryInterface.addIndex({ schema: '', tableName: 'Person' }, ['firstname', 'lastname']); // This example will create a unique index with the name SuperDuperIndex using the optional 'options' field. // Possible options: @@ -167,6 +184,7 @@ async function test() { }) await queryInterface.removeIndex('Person', 'SuperDuperIndex'); + await queryInterface.removeIndex({ schema: '', tableName: 'Person' }, 'SuperDuperIndex'); // or @@ -180,12 +198,18 @@ async function test() { })) await queryInterface.removeConstraint('Person', 'firstnamexlastname'); + await queryInterface.removeConstraint({ schema: '', tableName: 'Person' }, 'firstnamexlastname'); await queryInterface.select(null, 'Person', { where: { a: 1, }, }); + await queryInterface.select(null, { schema: '', tableName: 'Person' }, { + where: { + a: 1, + }, + }); await queryInterface.delete(null, 'Person', { where: { From b33d78eb81b496d303e9dc4efdd3930b6feea3ce Mon Sep 17 00:00:00 2001 From: JacobLey <37151850+JacobLey@users.noreply.github.com> Date: Fri, 25 Jun 2021 10:49:47 -0400 Subject: [PATCH 302/414] fix(typings): fix `ignoreDuplicates` option (#13220) --- types/lib/model.d.ts | 25 ++++++++------- types/test/create.ts | 74 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 types/test/create.ts diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 587b10093ba0..29a754e09aa4 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -668,9 +668,14 @@ export interface CreateOptions extends BuildOptions, Logging, fields?: (keyof TAttributes)[]; /** - * On Duplicate + * dialect specific ON CONFLICT DO NOTHING / INSERT IGNORE */ - onDuplicate?: string; + ignoreDuplicates?: boolean; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -749,7 +754,7 @@ export interface BulkCreateOptions extends Logging, Transacti individualHooks?: boolean; /** - * Ignore duplicate values for primary keys? (not supported by postgres) + * Ignore duplicate values for primary keys? * * @default false */ @@ -1967,16 +1972,14 @@ export abstract class Model( + public static create< + M extends Model, + O extends CreateOptions = CreateOptions + >( this: ModelStatic, values?: M['_creationAttributes'], - options?: CreateOptions - ): Promise; - public static create( - this: ModelStatic, - values: M['_creationAttributes'], - options: CreateOptions & { returning: false } - ): Promise; + options?: O + ): Promise; /** * Find a row that matches the query, or build (but don't save) the row if none is found. diff --git a/types/test/create.ts b/types/test/create.ts new file mode 100644 index 000000000000..a370249b47f6 --- /dev/null +++ b/types/test/create.ts @@ -0,0 +1,74 @@ +import { expectTypeOf } from 'expect-type' +import { User } from './models/User'; + +async () => { + const user = await User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: false, + returning: true, + }); + expectTypeOf(user).toEqualTypeOf() + + const voidUsers = await Promise.all([ + User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: true, + returning: false, + }), + User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: true, + returning: true, + }), + User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: false, + returning: false, + }), + User.create({ + id: 123, + firstName: '', + }, { returning: false }), + User.create({ + id: 123, + firstName: '', + }, { ignoreDuplicates: true }), + ]); + expectTypeOf(voidUsers).toEqualTypeOf<[void, void, void, void, void]>() + + const emptyUsers = await Promise.all([ + User.create(), + User.create(undefined), + User.create(undefined, undefined), + ]); + expectTypeOf(emptyUsers).toEqualTypeOf<[User, User, User]>() + + const partialUser = await User.create({ + id: 123, + firstName: '', + lastName: '', + }, { + fields: ['firstName'], + returning: ['id'], + }); + expectTypeOf(partialUser).toEqualTypeOf() + + // @ts-expect-error missing attribute + await User.create({ + id: 123, + }); + await User.create({ + id: 123, + firstName: '', + // @ts-expect-error unknown attribute + unknown: '', + }); +}; From 444f06f5df980560c2064c2bb211a3adfda05850 Mon Sep 17 00:00:00 2001 From: Fernando-Rodriguez <62259661+Fernando-Rodriguez@users.noreply.github.com> Date: Sat, 26 Jun 2021 00:03:22 -0400 Subject: [PATCH 303/414] docs(migrations.md): grammar improvements (#13294) --- docs/manual/other-topics/migrations.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/manual/other-topics/migrations.md b/docs/manual/other-topics/migrations.md index 9a36c8c180c0..7b869738146c 100644 --- a/docs/manual/other-topics/migrations.md +++ b/docs/manual/other-topics/migrations.md @@ -93,7 +93,7 @@ This will: ## Running Migrations -Until this step, we haven't inserted anything into the database. We have just created required model and migration files for our first model `User`. Now to actually create that table in database you need to run `db:migrate` command. +Until this step, we haven't inserted anything into the database. We have just created the required model and migration files for our first model, `User`. Now to actually create that table in the database you need to run `db:migrate` command. ```text npx sequelize-cli db:migrate @@ -107,15 +107,15 @@ This command will execute these steps: ## Undoing Migrations -Now our table has been created and saved in database. With migration you can revert to old state by just running a command. +Now our table has been created and saved in the database. With migration you can revert to old state by just running a command. -You can use `db:migrate:undo`, this command will revert most recent migration. +You can use `db:migrate:undo`, this command will revert most the recent migration. ```text npx sequelize-cli db:migrate:undo ``` -You can revert back to initial state by undoing all migrations with `db:migrate:undo:all` command. You can also revert back to a specific migration by passing its name in `--to` option. +You can revert back to the initial state by undoing all migrations with the `db:migrate:undo:all` command. You can also revert back to a specific migration by passing its name with the `--to` option. ```text npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js @@ -123,9 +123,9 @@ npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js ### Creating the first Seed -Suppose we want to insert some data into a few tables by default. If we follow up on previous example we can consider creating a demo user for `User` table. +Suppose we want to insert some data into a few tables by default. If we follow up on the previous example we can consider creating a demo user for the `User` table. -To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database table with sample data or test data. +To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database tables with sample or test data. Let's create a seed file which will add a demo user to our `User` table. @@ -156,13 +156,13 @@ module.exports = { ## Running Seeds -In last step you have create a seed file. It's still not committed to database. To do that we need to run a simple command. +In last step you created a seed file; however, it has not been committed to the database. To do that we run a simple command. ```text npx sequelize-cli db:seed:all ``` -This will execute that seed file and you will have a demo user inserted into `User` table. +This will execute that seed file and a demo user will be inserted into the `User` table. **Note:** _Seeder execution history is not stored anywhere, unlike migrations, which use the `SequelizeMeta` table. If you wish to change this behavior, please read the `Storage` section._ From 1cfbd333df8f2bc45e3eb7addf3aef4493f2be24 Mon Sep 17 00:00:00 2001 From: Nicolas Padula Date: Sat, 26 Jun 2021 05:50:03 +0100 Subject: [PATCH 304/414] fix(data-types): use proper field name for `ARRAY(ENUM)` (#13210) --- lib/dialects/postgres/data-types.js | 2 +- .../integration/dialects/postgres/dao.test.js | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js index 17eaa70a1a65..fc9ab0f420a7 100644 --- a/lib/dialects/postgres/data-types.js +++ b/lib/dialects/postgres/data-types.js @@ -480,7 +480,7 @@ module.exports = BaseTypes => { if (this.type instanceof BaseTypes.ENUM) { castKey = `${Utils.addTicks( - Utils.generateEnumName(options.field.Model.getTableName(), options.field.fieldName), + Utils.generateEnumName(options.field.Model.getTableName(), options.field.field), '"' ) }[]`; } diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index d85d084012c6..39eb80ad50ee 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -437,6 +437,34 @@ if (dialect.match(/^postgres/)) { expect(user.permissions).to.deep.equal(['access', 'write']); }); + it('should be able to insert a new record even with a redefined field name', async function() { + const User = this.sequelize.define('UserEnums', { + name: DataTypes.STRING, + type: DataTypes.ENUM('A', 'B', 'C'), + owners: DataTypes.ARRAY(DataTypes.STRING), + specialPermissions: { + type: DataTypes.ARRAY(DataTypes.ENUM([ + 'access', + 'write', + 'check', + 'delete' + ])), + field: 'special_permissions' + } + }); + + await User.sync({ force: true }); + + const user = await User.bulkCreate([{ + name: 'file.exe', + type: 'C', + owners: ['userA', 'userB'], + specialPermissions: ['access', 'write'] + }]); + + expect(user.length).to.equal(1); + }); + it('should fail when trying to insert foreign element on ARRAY(ENUM)', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, From d0d71887a2baf922d00b493019e630b30900abb5 Mon Sep 17 00:00:00 2001 From: Gabriel Ramos Date: Sat, 26 Jun 2021 01:59:20 -0300 Subject: [PATCH 305/414] docs(eager-loading.md): fix typo (#13161) --- docs/manual/advanced-association-concepts/eager-loading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/advanced-association-concepts/eager-loading.md b/docs/manual/advanced-association-concepts/eager-loading.md index 73fbf24acad5..9dd787c6727e 100644 --- a/docs/manual/advanced-association-concepts/eager-loading.md +++ b/docs/manual/advanced-association-concepts/eager-loading.md @@ -1,6 +1,6 @@ # Eager Loading -As briefly mentioned in [the associations guide](assocs.html), eager Loading is the act of querying data of several models at once (one 'main' model and one or more associated models). At the SQL level, this is a query with one or more [joins](https://en.wikipedia.org/wiki/Join_(SQL)). +As briefly mentioned in [the associations guide](assocs.html), eager Loading is the act of querying data of several models at once (one 'main' model and one or more associated models). At the SQL level, this is a query with one or more [joins](https://en.wikipedia.org/wiki/Join_\(SQL\)). When this is done, the associated models will be added by Sequelize in appropriately named, automatically created field(s) in the returned objects. From 39299a63a513c8539c0d9e0171916eeb22ee11aa Mon Sep 17 00:00:00 2001 From: Selenium39 Date: Sat, 26 Jun 2021 13:03:07 +0800 Subject: [PATCH 306/414] docs(read-replication.md): fix typo (#13179) Co-authored-by: Selenium39 Co-authored-by: wantao --- docs/manual/other-topics/read-replication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/other-topics/read-replication.md b/docs/manual/other-topics/read-replication.md index 95b7a67d9e57..1c16fef1ad74 100644 --- a/docs/manual/other-topics/read-replication.md +++ b/docs/manual/other-topics/read-replication.md @@ -5,7 +5,7 @@ Sequelize supports [read replication](https://en.wikipedia.org/wiki/Replication_ ```js const sequelize = new Sequelize('database', null, null, { dialect: 'mysql', - port: 3306 + port: 3306, replication: { read: [ { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, From 1a16b915ff45ac621a0517aea6c2d86557149500 Mon Sep 17 00:00:00 2001 From: roikoren755 <26850796+roikoren755@users.noreply.github.com> Date: Sat, 26 Jun 2021 08:30:59 +0300 Subject: [PATCH 307/414] fix(utils): clone attributes before mutating them (#13226) --- lib/utils.js | 1 + test/integration/associations/scope.test.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index 0ce6466ea304..65b4c75dcf93 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -186,6 +186,7 @@ exports.mapOptionFieldNames = mapOptionFieldNames; function mapWhereFieldNames(attributes, Model) { if (attributes) { + attributes = cloneDeep(attributes); getComplexKeys(attributes).forEach(attribute => { const rawAttribute = Model.rawAttributes[attribute]; diff --git a/test/integration/associations/scope.test.js b/test/integration/associations/scope.test.js index bd2b0efa1df8..bbf2a178ef00 100644 --- a/test/integration/associations/scope.test.js +++ b/test/integration/associations/scope.test.js @@ -19,6 +19,7 @@ describe(Support.getTestDialectTeaser('associations'), () => { commentable: Sequelize.STRING, commentable_id: Sequelize.INTEGER, isMain: { + field: 'is_main', type: Sequelize.BOOLEAN, defaultValue: false } @@ -298,6 +299,11 @@ describe(Support.getTestDialectTeaser('associations'), () => { expect(comment.type).to.match(/blue|green/); } }); + it('should not mutate scope when running SELECT query (#12868)', async function() { + await this.sequelize.sync({ force: true }); + await this.Post.findOne({ where: {}, include: [{ association: this.Post.associations.mainComment, attributes: ['id'], required: true, where: {} }] }); + expect(this.Post.associations.mainComment.scope.isMain).to.equal(true); + }); }); if (Support.getTestDialect() !== 'sqlite') { From cd2de406a4c87a917d3f2f40547ecb45d11849c5 Mon Sep 17 00:00:00 2001 From: Ariel Barabas <810664+knoid@users.noreply.github.com> Date: Sat, 26 Jun 2021 02:41:14 -0300 Subject: [PATCH 308/414] fix(typings): make `Transactionable` compatible with `TransactionOptions` (#13334) Co-authored-by: Pedro Augusto de Paula Barbosa --- types/lib/transaction.d.ts | 2 +- types/test/models/User.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/types/lib/transaction.d.ts b/types/lib/transaction.d.ts index fabbf117dfea..0ebc2cad0933 100644 --- a/types/lib/transaction.d.ts +++ b/types/lib/transaction.d.ts @@ -149,7 +149,7 @@ export interface TransactionOptions extends Logging { /** * Parent transaction. */ - transaction?: Transaction; + transaction?: Transaction | null; } export default Transaction; diff --git a/types/test/models/User.ts b/types/test/models/User.ts index b29534cb3669..d69639358238 100644 --- a/types/test/models/User.ts +++ b/types/test/models/User.ts @@ -110,6 +110,11 @@ User.addHook('beforeFind', 'test', (options: FindOptions) => { return undefined; }); +User.addHook('afterDestroy', async (instance, options) => { + // `options` from `afterDestroy` should be passable to `sequelize.transaction` + await instance.sequelize.transaction(options, async () => undefined); +}); + // Model#addScope User.addScope('withoutFirstName', { where: { From c7d7ca5ede127160d0acc32fa0476d1aed7ed41d Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 26 Jun 2021 02:52:55 -0300 Subject: [PATCH 309/414] meta: forbid auto major version release --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index c21614d24fe5..49b59d37a8f2 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "pg-hstore": "^2.x", "rimraf": "^3.0.2", "semantic-release": "^17.3.0", + "semantic-release-fail-on-major-bump": "^1.0.0", "sinon": "^9.0.2", "sinon-chai": "^3.3.0", "sqlite3": "^4.2.0", @@ -161,6 +162,9 @@ } }, "release": { + "plugins": [ + "semantic-release-fail-on-major-bump" + ], "branches": [ "v6" ], From dc3ec53f6467f1025c6d525eb237b38619167f54 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 26 Jun 2021 15:12:53 -0300 Subject: [PATCH 310/414] fix(ci): fix semantic-release usage Fixes bug introduced by c7d7ca5 --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 49b59d37a8f2..b0f7186e04cc 100644 --- a/package.json +++ b/package.json @@ -163,14 +163,14 @@ }, "release": { "plugins": [ - "semantic-release-fail-on-major-bump" + "@semantic-release/commit-analyzer", + "semantic-release-fail-on-major-bump", + "@semantic-release/release-notes-generator", + "@semantic-release/npm", + "@semantic-release/github" ], "branches": [ "v6" - ], - "verifyConditions": [ - "@semantic-release/npm", - "@semantic-release/github" ] }, "publishConfig": { From 5fa695fd4f81faeae3528bf4aae519dfd1e5b1ae Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Sat, 26 Jun 2021 15:59:28 -0300 Subject: [PATCH 311/414] meta: empty commit to rerun ci From b67460064f12d1facb51c2e99f2999ecb60b09ac Mon Sep 17 00:00:00 2001 From: Sushant Date: Tue, 6 Jul 2021 14:17:23 +0530 Subject: [PATCH 312/414] chores: keep only @papb email in maintainers field --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index b0f7186e04cc..e968f31156c1 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,6 @@ "description": "Multi dialect ORM for Node.JS", "version": "0.0.0-development", "maintainers": [ - "Sascha Depold ", - "Jan Aagaard Meier ", - "Daniel Durante ", - "Mick Hansen ", - "Sushant Dhiman ", "Pedro Augusto de Paula Barbosa " ], "repository": { From 56bb1d6ef9827f604d7bcef945abb7e213f2322d Mon Sep 17 00:00:00 2001 From: Laure Retru-Chavastel Date: Tue, 6 Jul 2021 15:52:28 +0200 Subject: [PATCH 313/414] fix(dependency): upgrade validator (#13350) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e968f31156c1..a21320925b44 100644 --- a/package.json +++ b/package.json @@ -37,14 +37,14 @@ "sequelize-pool": "^6.0.0", "toposort-class": "^1.0.1", "uuid": "^8.1.0", - "validator": "^10.11.0", + "validator": "^13.6.0", "wkx": "^0.5.0" }, "devDependencies": { "@commitlint/cli": "^11.0.0", "@commitlint/config-angular": "^11.0.0", "@types/node": "^12.12.42", - "@types/validator": "^10.11.0", + "@types/validator": "^13.1.4", "acorn": "^8.0.4", "chai": "^4.x", "chai-as-promised": "^7.x", From 95320e126053d7744ca0cc1e6a73b1deacda86b3 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 2 Oct 2021 13:17:51 +0200 Subject: [PATCH 314/414] Update collaboration related scripts + docs (#13530) * docs(collaboration): fix docker usage * fix(collaboration): verification of mariadb setup --- CONTRIBUTING.md | 8 ++++---- dev/mariadb/10.3/start.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a454b3b55ce9..b04bc882de58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -125,10 +125,10 @@ If you're happy to run tests only against an SQLite database, you can skip this If you have Docker installed, use any of the following commands to start fresh local databases of the dialect of your choice: -* `npm run setup-mariadb` -* `npm run setup-mysql` -* `npm run setup-postgres` -* `npm run setup-mssql` +* `npm run start-mariadb` +* `npm run start-mysql` +* `npm run start-postgres` +* `npm run start-mssql` *Note:* if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). diff --git a/dev/mariadb/10.3/start.sh b/dev/mariadb/10.3/start.sh index 0e80e04f6520..30af181cb667 100755 --- a/dev/mariadb/10.3/start.sh +++ b/dev/mariadb/10.3/start.sh @@ -11,6 +11,6 @@ docker-compose -p sequelize-mariadb-103 up -d docker exec sequelize-mariadb-103 \ mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" -node check.js +DIALECT=mariadb node check.js echo "Local MariaDB-10.3 instance is ready for Sequelize tests." From 26b62c7c3e76fca81c76cabcaf58fff00b7c4da0 Mon Sep 17 00:00:00 2001 From: Abdul Raheem Date: Sat, 2 Oct 2021 16:28:29 +0500 Subject: [PATCH 315/414] feat(test): add test for nested column in where query (#13478) Closes #13288 Co-authored-by: Sascha Depold --- types/test/model.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/test/model.ts b/types/test/model.ts index fea3091a9f04..d01e82c089b2 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -44,6 +44,8 @@ MyModel.findOne({ include: OtherModel }); MyModel.count({ include: OtherModel }); +MyModel.count({ include: [MyModel], where: { '$num$': [10, 120] } }); + MyModel.build({ int: 10 }, { include: OtherModel }); MyModel.bulkCreate([{ int: 10 }], { include: OtherModel }); From fecc67fe321cc749f63d82710b9a4b5b075ca5b3 Mon Sep 17 00:00:00 2001 From: Abdul Raheem Date: Sat, 2 Oct 2021 16:41:56 +0500 Subject: [PATCH 316/414] Issue 13302 (#13477) * fix(types): add overload type for findAndCountAll with group add overload type for findAndCountAll: - add overload type for findAndCountAll with group. - add test for checking return type of findAndCountAll with and without group Closes #13302 * docs: update return type of findAndCountAll in model.js comment Closes #13302 * fix(types): update overload findAndCountAll position Co-authored-by: Sascha Depold --- lib/model.js | 2 +- types/lib/model.d.ts | 4 ++++ types/test/model.ts | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 595d18147e8d..f0b7b2a1444f 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2069,7 +2069,7 @@ class Model { * @see * {@link Model.count} for a specification of count options * - * @returns {Promise<{count: number, rows: Model[]}>} + * @returns {Promise<{count: number | number[], rows: Model[]}>} */ static async findAndCountAll(options) { if (options !== undefined && !_.isPlainObject(options)) { diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 29a754e09aa4..6d27cc2cecc4 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1919,6 +1919,10 @@ export abstract class Model( + this: ModelStatic, + options?: FindAndCountOptions & { group: GroupOption } + ): Promise<{ rows: M[]; count: number[] }>; public static findAndCountAll( this: ModelStatic, options?: FindAndCountOptions diff --git a/types/test/model.ts b/types/test/model.ts index d01e82c089b2..afd9a1f07d51 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -42,6 +42,16 @@ MyModel.findOne({ include: ['OtherModelAlias'] }); MyModel.findOne({ include: OtherModel }); +MyModel.findAndCountAll({ include: OtherModel }).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf(); + expectTypeOf(rows).toEqualTypeOf(); +}); + +MyModel.findAndCountAll({ include: OtherModel, group: ['MyModel.num'] }).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf(); + expectTypeOf(rows).toEqualTypeOf(); +}); + MyModel.count({ include: OtherModel }); MyModel.count({ include: [MyModel], where: { '$num$': [10, 120] } }); From 1b80e0b5a5433eb591b0c45260dcfee852b6f40b Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 2 Oct 2021 20:42:02 +0200 Subject: [PATCH 317/414] Add tests and fix for usage of count with grouping (#13531) * fix(model): convert count to number when grouping * test(model#count): fix ordering issue in count test --- lib/model.js | 13 ++++++++++++- test/integration/associations/has-many.test.js | 1 + test/integration/model.test.js | 4 ++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index f0b7b2a1444f..e206a8c687f8 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2034,7 +2034,18 @@ class Model { options.offset = null; options.order = null; - return await this.aggregate(col, 'count', options); + const result = await this.aggregate(col, 'count', options); + + // When grouping is used, some dialects such as PG are returning the count as string + // --> Manually convert it to number + if (Array.isArray(result)) { + return result.map(item => ({ + ...item, + count: Number(item.count) + })); + } + + return result; } /** diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index c470a1183a9a..1a566847ffaf 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -1352,6 +1352,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); expect(count.length).to.equal(1); + expect(count).to.deep.equal([{ userId: 1, count: 1 }]); expect(rows[0].tasks[0].jobs.length).to.equal(2); }); }); diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 477a3c7dbc66..24b05dea0bc3 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -1720,6 +1720,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { group: ['data'] }); expect(count).to.have.lengthOf(2); + + // The order of count varies across dialects; Hence find element by identified first. + expect(count.find(i => i.data === 'A')).to.deep.equal({ data: 'A', count: 2 }); + expect(count.find(i => i.data === 'B')).to.deep.equal({ data: 'B', count: 1 }); }); if (dialect !== 'mssql') { From 37f676d8a2a1fb3a19cd50a22dca68075d999c5d Mon Sep 17 00:00:00 2001 From: colpachoque Date: Sun, 3 Oct 2021 14:26:22 +0300 Subject: [PATCH 318/414] Update model.d.ts (#13514) Add optional `omitNull` flag to the `SaveOptions` interface. Co-authored-by: Sascha Depold --- types/lib/model.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 6d27cc2cecc4..e037da1c1434 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -983,6 +983,13 @@ export interface SaveOptions extends Logging, Transactionable * @default true */ validate?: boolean; + + /** + * A flag that defines if null values should be passed as values or not. + * + * @default false + */ + omitNull?: boolean; } /** From 15964b13293ced398d78bc6e7203e073b81595b9 Mon Sep 17 00:00:00 2001 From: SunMyeong Lee Date: Sun, 3 Oct 2021 20:26:56 +0900 Subject: [PATCH 319/414] Fix type on getting-started (#13501) Co-authored-by: Sascha Depold --- docs/manual/core-concepts/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/getting-started.md b/docs/manual/core-concepts/getting-started.md index 46a9760255d3..bdc0b2a6e4f8 100644 --- a/docs/manual/core-concepts/getting-started.md +++ b/docs/manual/core-concepts/getting-started.md @@ -38,7 +38,7 @@ const sequelize = new Sequelize({ storage: 'path/to/database.sqlite' }); -// Option 2: Passing parameters separately (other dialects) +// Option 3: Passing parameters separately (other dialects) const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ From 0726ede522966a5c1c7cfb703e2615f373c391ac Mon Sep 17 00:00:00 2001 From: Jairo Mancebo Date: Sun, 3 Oct 2021 07:49:38 -0400 Subject: [PATCH 320/414] Change static this to class reference (#13510) Co-authored-by: Sascha Depold --- lib/sequelize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index b074beef1710..02a0f92f6d66 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -1121,7 +1121,7 @@ class Sequelize { if (!ns || typeof ns !== 'object' || typeof ns.bind !== 'function' || typeof ns.run !== 'function') throw new Error('Must provide CLS namespace'); // save namespace as `Sequelize._cls` - this._cls = ns; + Sequelize._cls = ns; // return Sequelize for chaining return this; From 23aa67e9abe440027aa07091beb35834e584e42e Mon Sep 17 00:00:00 2001 From: Shreyansh shrey Date: Sun, 3 Oct 2021 17:19:59 +0530 Subject: [PATCH 321/414] Typo: Changes name to username (#13504) Co-authored-by: Sascha Depold --- docs/manual/core-concepts/validations-and-constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/validations-and-constraints.md b/docs/manual/core-concepts/validations-and-constraints.md index cdc4139c9640..0ce8668f6a8f 100644 --- a/docs/manual/core-concepts/validations-and-constraints.md +++ b/docs/manual/core-concepts/validations-and-constraints.md @@ -46,7 +46,7 @@ Our code example above defines a unique constraint on the `username` field: } /* ... */ ``` -When this model is synchronized (by calling `sequelize.sync` for example), the `username` field will be created in the table as `` `name` TEXT UNIQUE``, and an attempt to insert an username that already exists there will throw a `SequelizeUniqueConstraintError`. +When this model is synchronized (by calling `sequelize.sync` for example), the `username` field will be created in the table as `` `username` TEXT UNIQUE``, and an attempt to insert an username that already exists there will throw a `SequelizeUniqueConstraintError`. ## Allowing/disallowing null values From 9ea98938a5f0336db33a5730f6773cd35c8a5170 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Fri, 8 Oct 2021 07:57:54 +0200 Subject: [PATCH 322/414] Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000000..1bad4b3d7eda --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: sequelize +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From e439ab42903549a1aecbf59e328c47f988668b01 Mon Sep 17 00:00:00 2001 From: Peter Timoshevsky Date: Fri, 8 Oct 2021 19:49:20 +0200 Subject: [PATCH 323/414] docs(sequelize.js): add mariadb dialect (#13519) --- lib/sequelize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sequelize.js b/lib/sequelize.js index 02a0f92f6d66..b66d8f34b6aa 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -130,7 +130,7 @@ class Sequelize { * @param {string} [options.username=null] The username which is used to authenticate against the database. * @param {string} [options.password=null] The password which is used to authenticate against the database. * @param {string} [options.database=null] The name of the database - * @param {string} [options.dialect] The dialect of the database you are connecting to. One of mysql, postgres, sqlite and mssql. + * @param {string} [options.dialect] The dialect of the database you are connecting to. One of mysql, postgres, sqlite, mariadb and mssql. * @param {string} [options.dialectModule=null] If specified, use this dialect library. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify 'require("pg.js")' here * @param {string} [options.dialectModulePath=null] If specified, load the dialect library from this path. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify '/path/to/pg.js' here * @param {object} [options.dialectOptions] An object of additional options, which are passed directly to the connection library From 83a014eb1183c5848c5655dc9b892da274952313 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Fri, 8 Oct 2021 21:16:12 +0200 Subject: [PATCH 324/414] Fix failing pg native tests (#13547) * test(pg): debugging github actions * test(pg): fix missing credential test * test(pg): clean-up fix --- test/integration/sequelize.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index fbead1f9c6e6..4d9f8b498432 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -438,7 +438,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { 'role "bar" does not exist', 'FATAL: role "bar" does not exist', 'password authentication failed for user "bar"' - ].includes(err.message.trim())); + ].some(fragment => err.message.includes(fragment))); } else if (dialect === 'mssql') { expect(err.message).to.equal('Login failed for user \'bar\'.'); } else { From 0e6955cc5cfe1a84566e8d1b0ad0aa35840d34e4 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Fri, 8 Oct 2021 12:26:40 -0700 Subject: [PATCH 325/414] Document `set()` and `update()` instance methods (#13487) * Document `set()` and `update()` instance methods These are very useful for updating, and were not mentioned elsewhere * Update model-instances.md * Update model-instances.md * Update model-instances.md Co-authored-by: Sascha Depold --- docs/manual/core-concepts/model-instances.md | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/manual/core-concepts/model-instances.md b/docs/manual/core-concepts/model-instances.md index 27699dc2412c..8c47810ddffb 100644 --- a/docs/manual/core-concepts/model-instances.md +++ b/docs/manual/core-concepts/model-instances.md @@ -87,6 +87,31 @@ await jane.save(); // Now the name was updated to "Ada" in the database! ``` +You can update several fields at once with the [`set`](../class/lib/model.js~Model.html#instance-method-set) method: + +```js +const jane = await User.create({ name: "Jane" }); + +jane.set({ + name: "Ada", + favoriteColor: "blue" +}); +// As above, the database still has "Jane" and "green" +await jane.save(); +// The database now has "Ada" and "blue" for name and favorite color +``` + +Note that the `save()` here will also persist any other changes that have been made on this instance, not just those in the previous `set` call. If you want to update a specific set of fields, you can use [`update`](../class/lib/model.js~Model.html#instance-method-update): + +```js +const jane = await User.create({ name: "Jane" }); +jane.favoriteColor = "blue" +await jane.update({ name: "Ada" }) +// The database now has "Ada" for name, but still has the default "green" for favorite color +await jane.save() +// Now the database has "Ada" for name and "blue" for favorite color +``` + ## Deleting an instance You can delete an instance by calling [`destroy`](../class/lib/model.js~Model.html#instance-method-destroy): From bbf3d76474f8520aa9d16d51bd35730774866e13 Mon Sep 17 00:00:00 2001 From: Anuj Joshi Date: Sat, 9 Oct 2021 09:31:30 +0530 Subject: [PATCH 326/414] fix(docs): fix typo in documentation for polymorphic associations (#13405) change juncion -> junction --- .../advanced-association-concepts/polymorphic-associations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/advanced-association-concepts/polymorphic-associations.md b/docs/manual/advanced-association-concepts/polymorphic-associations.md index 39ee46372626..2a1ade8f216b 100644 --- a/docs/manual/advanced-association-concepts/polymorphic-associations.md +++ b/docs/manual/advanced-association-concepts/polymorphic-associations.md @@ -223,7 +223,7 @@ Now, to consider a Many-to-Many polymorphic association, instead of considering The setup for this goes as follows: -* Define the juncion model explicitly, specifying the two foreign keys as `tagId` and `taggableId` (this way it is a junction model for a Many-to-Many relationship between `Tag` and the abstract concept of *taggable*); +* Define the junction model explicitly, specifying the two foreign keys as `tagId` and `taggableId` (this way it is a junction model for a Many-to-Many relationship between `Tag` and the abstract concept of *taggable*); * Define a string field called `taggableType` in the junction model; * Define the `belongsToMany` associations between the two models and `Tag`: * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; @@ -424,4 +424,4 @@ WHERE ( We can see that both scopes were applied automatically: * `` `tag_taggable`.`taggableType` = 'image'`` was added automatically to the `INNER JOIN`; -* `` `tag`.`status` = 'pending'`` was added automatically to an outer where clause. \ No newline at end of file +* `` `tag`.`status` = 'pending'`` was added automatically to an outer where clause. From 6a737fe73163112e8b274f6ec3e5b2844d33841d Mon Sep 17 00:00:00 2001 From: Zach Azar Date: Fri, 8 Oct 2021 22:18:25 -0600 Subject: [PATCH 327/414] docs(eager-loading): add await to code example (#13414) This line is missing an await, so the console.log does not actually log the expected Foo object as it hasn't been retrieved yet. --- docs/manual/advanced-association-concepts/eager-loading.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/advanced-association-concepts/eager-loading.md b/docs/manual/advanced-association-concepts/eager-loading.md index 9dd787c6727e..0d4ad8220fbd 100644 --- a/docs/manual/advanced-association-concepts/eager-loading.md +++ b/docs/manual/advanced-association-concepts/eager-loading.md @@ -378,7 +378,7 @@ await sequelize.sync(); const foo = await Foo.create({ name: 'foo' }); const bar = await Bar.create({ name: 'bar' }); await foo.addBar(bar); -const fetchedFoo = Foo.findOne({ include: Bar }); +const fetchedFoo = await Foo.findOne({ include: Bar }); console.log(JSON.stringify(fetchedFoo, null, 2)); ``` @@ -661,4 +661,4 @@ User.findAndCountAll({ }); ``` -The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. \ No newline at end of file +The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. From 46d6c35c5bb3865b363d3f36ca65849ed4ae2865 Mon Sep 17 00:00:00 2001 From: Abdul Raheem Date: Sat, 9 Oct 2021 16:22:18 +0500 Subject: [PATCH 328/414] docs: fix incorrect model validation example (#13470) Closes #13438 Co-authored-by: AllAwesome497 <47748690+AllAwesome497@users.noreply.github.com> --- docs/manual/core-concepts/validations-and-constraints.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/validations-and-constraints.md b/docs/manual/core-concepts/validations-and-constraints.md index 0ce8668f6a8f..fed749072ed7 100644 --- a/docs/manual/core-concepts/validations-and-constraints.md +++ b/docs/manual/core-concepts/validations-and-constraints.md @@ -16,7 +16,9 @@ const User = sequelize.define("user", { }, hashedPassword: { type: DataTypes.STRING(64), - is: /^[0-9a-f]{64}$/i + validate: { + is: /^[0-9a-f]{64}$/i + } } }); From a450186670285a2e19432fa720d4f3244c612759 Mon Sep 17 00:00:00 2001 From: Felipe Silva Date: Sat, 9 Oct 2021 08:26:48 -0300 Subject: [PATCH 329/414] Fix missing comma in example options object (#13433) --- docs/manual/core-concepts/model-querying-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index cb38b32cd9ae..3f5c1a0ff664 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -161,7 +161,7 @@ Multiple checks can be passed: ```js Post.findAll({ where: { - authorId: 12 + authorId: 12, status: 'active' } }); From ca2a11aed603572f3277c2262d445ec9f464b326 Mon Sep 17 00:00:00 2001 From: Bene-Graham <35272170+Bene-Graham@users.noreply.github.com> Date: Sat, 9 Oct 2021 09:02:39 -0400 Subject: [PATCH 330/414] fix(types): allow rangable to take a string tuple (#13486) Co-authored-by: Sascha Depold --- types/lib/model.d.ts | 2 +- types/test/where.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index e037da1c1434..8e4b4f55b6c4 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -131,7 +131,7 @@ export interface AllOperator { [Op.all]: readonly (string | number | Date | Literal)[]; } -export type Rangable = readonly [number, number] | readonly [Date, Date] | Literal; +export type Rangable = readonly [number, number] | readonly [Date, Date] | readonly [string, string] | Literal; /** * Operators that can be used in WhereOptions diff --git a/types/test/where.ts b/types/test/where.ts index 5b4565248e90..58b30117ff74 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -227,7 +227,7 @@ MyModel.findAll({ [Op.lt]: 10, // id < 10 [Op.lte]: 10, // id <= 10 [Op.ne]: 20, // id != 20 - [Op.between]: [6, 10] || [new Date(), new Date()], // BETWEEN 6 AND 10 + [Op.between]: [6, 10] || [new Date(), new Date()] || ["2020-01-01", "2020-12-31"], // BETWEEN 6 AND 10 [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 [Op.in]: [1, 2], // IN [1, 2] [Op.notIn]: [1, 2], // NOT IN [1, 2] From f078f772d447e9148442ca4e9feae887e65adea0 Mon Sep 17 00:00:00 2001 From: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Date: Sat, 9 Oct 2021 15:03:35 +0200 Subject: [PATCH 331/414] feat(types): make config type deeply writeable for before connect hook (#13424) * feat(types): add deep writeable config type for before connect hook * feat(tests): added typing tests for beforeConnect callback arg types Co-authored-by: Sascha Depold --- types/lib/hooks.d.ts | 3 ++- types/lib/sequelize.d.ts | 6 +++--- types/lib/utils.d.ts | 2 ++ types/test/hooks.ts | 8 ++++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index d19f93065f55..fd0ef7aef816 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -15,6 +15,7 @@ import Model, { UpdateOptions, } from './model'; import { Config, Options, Sequelize, SyncOptions } from './sequelize'; +import { DeepWriteable } from './utils'; export type HookReturn = Promise | void; @@ -69,7 +70,7 @@ export interface SequelizeHooks< afterDefine(model: ModelType): void; beforeInit(config: Config, options: Options): void; afterInit(sequelize: Sequelize): void; - beforeConnect(config: Config): HookReturn; + beforeConnect(config: DeepWriteable): HookReturn; afterConnect(connection: unknown, config: Config): HookReturn; beforeDisconnect(connection: unknown): HookReturn; afterDisconnect(connection: unknown): HookReturn; diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index b88d5ec4e5d7..fd2ecbcd2e70 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -26,7 +26,7 @@ import { ModelManager } from './model-manager'; import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface'; import QueryTypes = require('./query-types'); import { Transaction, TransactionOptions } from './transaction'; -import { Cast, Col, Fn, Json, Literal, Where } from './utils'; +import { Cast, Col, DeepWriteable, Fn, Json, Literal, Where } from './utils'; import { ConnectionManager } from './connection-manager'; /** @@ -666,8 +666,8 @@ export class Sequelize extends Hooks { * @param name * @param fn A callback function that is called with options */ - public static beforeConnect(name: string, fn: (options: Config) => void): void; - public static beforeConnect(fn: (options: Config) => void): void; + public static beforeConnect(name: string, fn: (options: DeepWriteable) => void): void; + public static beforeConnect(fn: (options: DeepWriteable) => void): void; /** * A hook that is run after a connection is established diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts index b06f12cb16dd..02552a46c1f8 100644 --- a/types/lib/utils.d.ts +++ b/types/lib/utils.d.ts @@ -3,6 +3,8 @@ import { Model, ModelCtor, ModelType, WhereOptions } from './model'; export type Primitive = 'string' | 'number' | 'boolean'; +export type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + export interface Inflector { singularize(str: string): string; pluralize(str: string): string; diff --git a/types/test/hooks.ts b/types/test/hooks.ts index 53b2e73c383f..cec4d0cdfa4b 100644 --- a/types/test/hooks.ts +++ b/types/test/hooks.ts @@ -2,6 +2,8 @@ import { expectTypeOf } from "expect-type"; import { SemiDeepWritable } from "./type-helpers/deep-writable"; import { Model, SaveOptions, Sequelize, FindOptions, ModelCtor, ModelType, ModelDefined, ModelStatic } from "sequelize"; import { ModelHooks } from "../lib/hooks"; +import { DeepWriteable } from '../lib/utils'; +import { Config } from '../lib/sequelize'; { class TestModel extends Model {} @@ -59,3 +61,9 @@ import { ModelHooks } from "../lib/hooks"; hooks.beforeSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; hooks.beforeBulkSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; } + +{ + Sequelize.beforeConnect('name', config => expectTypeOf(config).toEqualTypeOf>()); + Sequelize.beforeConnect(config => expectTypeOf(config).toEqualTypeOf>()); + Sequelize.addHook('beforeConnect', (...args) => { expectTypeOf(args).toEqualTypeOf<[DeepWriteable]>(); }) +} From 6f758af103a2bceba32f124ec5a3394b422a8d5f Mon Sep 17 00:00:00 2001 From: Arneesh Aima <44923038+arneesh@users.noreply.github.com> Date: Sat, 9 Oct 2021 18:33:57 +0530 Subject: [PATCH 332/414] docs: removed unnecessary brackets from belongs-to-many docs (#13373) Co-authored-by: Sascha Depold --- lib/associations/belongs-to-many.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index 27f58701b052..6de259eb3f4b 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -48,7 +48,6 @@ const Op = require('../operators'); * const projects = await user.getProjects(); * const p1 = projects[0]; * p1.UserProjects.started // Is this project started yet? - * }) * ``` * * In the API reference below, add the name of the association to the method, e.g. for `User.belongsToMany(Project)` the getter will be `user.getProjects()`. From cf537342b55bd4cf432f16b4b524737a6cb68ab4 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 9 Oct 2021 19:54:35 +0200 Subject: [PATCH 333/414] fix(deps): upgrade to secure versions of dev deps (#13549) --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index a21320925b44..b6eaa7136613 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@types/node": "^12.12.42", "@types/validator": "^13.1.4", "acorn": "^8.0.4", + "axios": ">=0.21.2", "chai": "^4.x", "chai-as-promised": "^7.x", "chai-datetime": "^1.6.0", @@ -71,19 +72,23 @@ "marked": "^1.1.0", "mocha": "^7.1.2", "mysql2": "^2.1.0", + "nth-check": ">=2.0.1", "nyc": "^15.0.0", "p-map": "^4.0.0", "p-props": "^4.0.0", "p-settle": "^4.1.1", "p-timeout": "^4.0.0", + "path-parse": ">=1.0.7", "pg": "^8.2.1", "pg-hstore": "^2.x", "rimraf": "^3.0.2", "semantic-release": "^17.3.0", "semantic-release-fail-on-major-bump": "^1.0.0", + "semver-regex": ">=3.1.3", "sinon": "^9.0.2", "sinon-chai": "^3.3.0", "sqlite3": "^4.2.0", + "tar": ">=4.4.18", "tedious": "8.3.0", "typescript": "^4.1.3" }, From 199b632b021830f9d09210fd7430045710638631 Mon Sep 17 00:00:00 2001 From: caolvchong Date: Sun, 10 Oct 2021 19:10:33 +0800 Subject: [PATCH 334/414] fix(model): Convert number values only if they aren't null to avoid NaN Co-authored-by: Sascha Depold --- lib/dialects/abstract/query-interface.js | 4 +- test/integration/model/notExist.test.js | 58 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/integration/model/notExist.test.js diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js index e1876d5cef64..f77bd92f93f6 100644 --- a/lib/dialects/abstract/query-interface.js +++ b/lib/dialects/abstract/query-interface.js @@ -1011,7 +1011,9 @@ class QueryInterface { } } if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) { - return parseInt(result, 10); + if (result !== null) { + return parseInt(result, 10); + } } if (dataType instanceof DataTypes.DATE) { if (result !== null && !(result instanceof Date)) { diff --git a/test/integration/model/notExist.test.js b/test/integration/model/notExist.test.js new file mode 100644 index 000000000000..b8b0dd017f01 --- /dev/null +++ b/test/integration/model/notExist.test.js @@ -0,0 +1,58 @@ +'use strict'; + +const chai = require('chai'), + expect = chai.expect, + Support = require('../support'), + DataTypes = require('../../../lib/data-types'); + +describe(Support.getTestDialectTeaser('Model'), () => { + beforeEach(async function() { + this.Order = this.sequelize.define('Order', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + sequence: DataTypes.INTEGER, + amount: DataTypes.DECIMAL, + type: DataTypes.STRING + }); + + await this.sequelize.sync({ force: true }); + + await this.Order.bulkCreate([ + { sequence: 1, amount: 3, type: 'A' }, + { sequence: 2, amount: 4, type: 'A' }, + { sequence: 3, amount: 5, type: 'A' }, + { sequence: 4, amount: 1, type: 'A' }, + { sequence: 1, amount: 2, type: 'B' }, + { sequence: 2, amount: 6, type: 'B' } + ]); + }); + + describe('max', () => { + it('should type exist', async function() { + await expect(this.Order.sum('sequence', { where: { type: 'A' } })).to.eventually.be.equal(10); + await expect(this.Order.max('sequence', { where: { type: 'A' } })).to.eventually.be.equal(4); + await expect(this.Order.min('sequence', { where: { type: 'A' } })).to.eventually.be.equal(1); + await expect(this.Order.sum('amount', { where: { type: 'A' } })).to.eventually.be.equal(13); + await expect(this.Order.max('amount', { where: { type: 'A' } })).to.eventually.be.equal(5); + await expect(this.Order.min('amount', { where: { type: 'A' } })).to.eventually.be.equal(1); + + await expect(this.Order.sum('sequence', { where: { type: 'B' } })).to.eventually.be.equal(3); + await expect(this.Order.max('sequence', { where: { type: 'B' } })).to.eventually.be.equal(2); + await expect(this.Order.min('sequence', { where: { type: 'B' } })).to.eventually.be.equal(1); + await expect(this.Order.sum('amount', { where: { type: 'B' } })).to.eventually.be.equal(8); + await expect(this.Order.max('amount', { where: { type: 'B' } })).to.eventually.be.equal(6); + await expect(this.Order.min('amount', { where: { type: 'B' } })).to.eventually.be.equal(2); + }); + + it('should type not exist', async function() { + // DataTypes.INTEGER or DataTypes.BIGINT: previous version should use `.to.eventually.be.NaN` + await expect(this.Order.sum('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.max('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.min('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); + + // DataTypes.DECIMAL or DataTypes.FLOAT: previous and PR#13422 both use `to.eventually.be.equal(0)` + await expect(this.Order.sum('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.max('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.min('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); + }); + }); +}); From 69d899e27b733adb24e4300b48c9bae91455932f Mon Sep 17 00:00:00 2001 From: atrick-speedline <51718553+atrick-speedline@users.noreply.github.com> Date: Sun, 10 Oct 2021 04:16:21 -0700 Subject: [PATCH 335/414] feat(typings): add UnknownConstraintError (#13461) Co-authored-by: Sascha Depold --- types/lib/errors.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/types/lib/errors.d.ts b/types/lib/errors.d.ts index a575486bec62..b4b84a781aee 100644 --- a/types/lib/errors.d.ts +++ b/types/lib/errors.d.ts @@ -156,6 +156,16 @@ export class ExclusionConstraintError extends DatabaseError { constructor(options: { parent?: Error; message?: string; constraint?: string; fields?: string[]; table?: string }); } +/** + * Thrown when constraint name is not found in the database + */ +export class UnknownConstraintError extends DatabaseError { + public constraint: string; + public fields: { [field: string]: string }; + public table: string; + constructor(options: { parent?: Error; message?: string; constraint?: string; fields?: string[]; table?: string }); +} + /** * Thrown when attempting to update a stale model instance */ From d685a9a76ad353aef6df61c19e4385aa9ba79368 Mon Sep 17 00:00:00 2001 From: George Zhao Date: Mon, 11 Oct 2021 03:17:51 +1100 Subject: [PATCH 336/414] fix(model.d): accept [Op.is] in where (broken in TypeScript 4.4) (#13499) * fix(model.d): accept `[Op.is]` in where * fix(model.d): test `[Op.is]` in where Co-authored-by: Sascha Depold Co-authored-by: Constantin Metz <58604248+Keimeno@users.noreply.github.com> --- types/lib/model.d.ts | 3 +++ types/test/where.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 8e4b4f55b6c4..cabfd549d53f 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -161,6 +161,9 @@ export interface WhereOperators { /** Example: `[Op.not]: true,` becomes `IS NOT TRUE` */ [Op.not]?: null | boolean | string | number | Literal | WhereOperators; + /** Example: `[Op.is]: null,` becomes `IS NULL` */ + [Op.is]?: null; + /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ [Op.between]?: Rangable; diff --git a/types/test/where.ts b/types/test/where.ts index 58b30117ff74..6748f30c88ce 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -50,6 +50,7 @@ expectTypeOf({ [Op.lte]: 10, // <= 10 [Op.ne]: 20, // != 20 [Op.not]: true, // IS NOT TRUE + [Op.is]: null, // IS NULL [Op.between]: [6, 10], // BETWEEN 6 AND 10 [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 [Op.in]: [1, 2], // IN [1, 2] From 5e9c209cc8eaa1d38f33bb3ac2de8b8ab33929f2 Mon Sep 17 00:00:00 2001 From: Wesley Reed Date: Sun, 10 Oct 2021 12:53:56 -0400 Subject: [PATCH 337/414] fix(types): add missing upsert hooks (#13394) * Missing upsert hooks Upsert hooks are missing in types * fix(types): missing upsert hooks Fixed upsert options type * fix(types): missing upsert hooks Just having a bad day * fix(types): added tests for upsert hooks * fix(types): incorrect attributes types for afterUpsert Co-authored-by: Sergio Bernal Co-authored-by: Constantin Metz <58604248+Keimeno@users.noreply.github.com> --- types/lib/hooks.d.ts | 3 +++ types/test/hooks.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index fd0ef7aef816..480495bfa461 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -6,6 +6,7 @@ import Model, { CreateOptions, DestroyOptions, RestoreOptions, + UpsertOptions, FindOptions, InstanceDestroyOptions, InstanceRestoreOptions, @@ -34,6 +35,8 @@ export interface ModelHooks { afterRestore(instance: M, options: InstanceRestoreOptions): HookReturn; beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; + beforeUpsert(attributes: M, options: UpsertOptions): HookReturn; + afterUpsert(attributes: [ M, boolean | null ], options: UpsertOptions): HookReturn; beforeSave( instance: M, options: InstanceUpdateOptions | CreateOptions diff --git a/types/test/hooks.ts b/types/test/hooks.ts index cec4d0cdfa4b..340038759c41 100644 --- a/types/test/hooks.ts +++ b/types/test/hooks.ts @@ -1,6 +1,6 @@ import { expectTypeOf } from "expect-type"; import { SemiDeepWritable } from "./type-helpers/deep-writable"; -import { Model, SaveOptions, Sequelize, FindOptions, ModelCtor, ModelType, ModelDefined, ModelStatic } from "sequelize"; +import { Model, SaveOptions, Sequelize, FindOptions, ModelCtor, ModelType, ModelDefined, ModelStatic, UpsertOptions } from "sequelize"; import { ModelHooks } from "../lib/hooks"; import { DeepWriteable } from '../lib/utils'; import { Config } from '../lib/sequelize'; @@ -20,7 +20,15 @@ import { Config } from '../lib/sequelize'; afterFind(m, options) { expectTypeOf(m).toEqualTypeOf(); expectTypeOf(options).toEqualTypeOf(); - } + }, + beforeUpsert(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + }, + afterUpsert(m, options) { + expectTypeOf(m).toEqualTypeOf<[ TestModel, boolean | null ]>(); + expectTypeOf(options).toEqualTypeOf(); + }, }; const sequelize = new Sequelize('uri', { hooks }); @@ -29,6 +37,8 @@ import { Config } from '../lib/sequelize'; TestModel.addHook('beforeSave', hooks.beforeSave!); TestModel.addHook('afterSave', hooks.afterSave!); TestModel.addHook('afterFind', hooks.afterFind!); + TestModel.addHook('beforeUpsert', hooks.beforeUpsert!); + TestModel.addHook('afterUpsert', hooks.afterUpsert!); TestModel.beforeSave(hooks.beforeSave!); TestModel.afterSave(hooks.afterSave!); @@ -60,6 +70,7 @@ import { Config } from '../lib/sequelize'; hooks.beforeFindAfterOptions = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; hooks.beforeSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; hooks.beforeBulkSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeUpsert = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; } { From 47c2d057f857e1eb197ac317f295798313dcedc0 Mon Sep 17 00:00:00 2001 From: Abdul Raheem Date: Sat, 16 Oct 2021 20:53:24 +0500 Subject: [PATCH 338/414] fix(types): extend BulkCreateOptions by SearchPathable (#13469) add missing searchPath in BulkCreate.options: - extend BulkCreateOptions interface by SearchPathable to add missing searchPath option. - include searchPath in bulkCreate type test. Closes #13454 Co-authored-by: Alexey Lyakhov Co-authored-by: Sascha Depold --- types/lib/model.d.ts | 2 +- types/test/model.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index cabfd549d53f..32c9cb4a6e33 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -738,7 +738,7 @@ export interface UpsertOptions extends Logging, Transactionab /** * Options for Model.bulkCreate method */ -export interface BulkCreateOptions extends Logging, Transactionable, Hookable { +export interface BulkCreateOptions extends Logging, Transactionable, Hookable, SearchPathable { /** * Fields to insert (defaults to all fields) */ diff --git a/types/test/model.ts b/types/test/model.ts index afd9a1f07d51..af7cbf89d8e8 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -58,7 +58,7 @@ MyModel.count({ include: [MyModel], where: { '$num$': [10, 120] } }); MyModel.build({ int: 10 }, { include: OtherModel }); -MyModel.bulkCreate([{ int: 10 }], { include: OtherModel }); +MyModel.bulkCreate([{ int: 10 }], { include: OtherModel, searchPath: 'public' }); MyModel.update({}, { where: { foo: 'bar' }, paranoid: false}); From 415989f59e41691426e0edcc54302d6f65f977c3 Mon Sep 17 00:00:00 2001 From: salehdeh76 Date: Sat, 16 Oct 2021 08:55:42 -0700 Subject: [PATCH 339/414] Update documentation - hooks.md - fixed snippet (#13441) fixed code snippet which mixed `transaction` option with model fields. Co-authored-by: Sascha Depold --- docs/manual/other-topics/hooks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/hooks.md b/docs/manual/other-topics/hooks.md index 71791719bb4f..8f60a206d5a3 100644 --- a/docs/manual/other-topics/hooks.md +++ b/docs/manual/other-topics/hooks.md @@ -367,7 +367,8 @@ User.addHook('afterCreate', async (user, options) => { await sequelize.transaction(async t => { await User.create({ username: 'someguy', - mood: 'happy', + mood: 'happy' + }, { transaction: t }); }); From 8e98f475430389fb36d5349f307fdc5d675c8251 Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Sat, 16 Oct 2021 18:56:10 +0300 Subject: [PATCH 340/414] refactor(*): add .gitattributes to force LF line ends (#13377) Co-authored-by: Sascha Depold --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..ce2684934d6f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -lf \ No newline at end of file From dc67dc96e41bda3d173e641100f524fedbf39423 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 16 Oct 2021 18:01:25 +0200 Subject: [PATCH 341/414] Add sponsors badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b1ae2d0b4297..957d12f1bc1d 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) [![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) - [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) +[![sponsor](https://img.shields.io/opencollective/all/sequelize?label=sponsors)](https://opencollective.com/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) + Sequelize is a promise-based [Node.js](https://nodejs.org/en/about/) [ORM tool](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. From c3e608b95a130b661ca01f9af42beaac5995d986 Mon Sep 17 00:00:00 2001 From: Ivan Rubinson Date: Sun, 17 Oct 2021 14:28:31 +0300 Subject: [PATCH 342/414] fix(sqlite): fix wrongly overwriting storage if empty string (#13376) * fix(sqlite): fix wrongly overwriting storage if empty string Empty string in SQLite3 means anonymous disk-based db, but sequelize overwrited it with :memory: Closes #13375 * fix(sqlite): fix node<10 chokes on nullish coalescing operator * refactor(sqlite): remove unnecessary parentheses around expression Co-authored-by: Sascha Depold --- lib/dialects/sqlite/connection-manager.js | 2 +- test/support.js | 2 +- .../sqlite/connection-manager.test.js | 29 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 test/unit/dialects/sqlite/connection-manager.test.js diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index ecb4371c034a..512a8b96d4f1 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -45,7 +45,7 @@ class ConnectionManager extends AbstractConnectionManager { async getConnection(options) { options = options || {}; options.uuid = options.uuid || 'default'; - options.storage = this.sequelize.options.storage || this.sequelize.options.host || ':memory:'; + options.storage = this.sequelize.options.storage !== null && this.sequelize.options.storage !== undefined ? this.sequelize.options.storage : this.sequelize.options.host || ':memory:'; options.inMemory = options.storage === ':memory:' ? 1 : 0; const dialectOptions = this.sequelize.options.dialectOptions; diff --git a/test/support.js b/test/support.js index e62f0861362a..57b351a8d984 100644 --- a/test/support.js +++ b/test/support.js @@ -113,7 +113,7 @@ const Support = { sequelizeOptions.native = true; } - if (config.storage) { + if (config.storage || config.storage === '') { sequelizeOptions.storage = config.storage; } diff --git a/test/unit/dialects/sqlite/connection-manager.test.js b/test/unit/dialects/sqlite/connection-manager.test.js new file mode 100644 index 000000000000..fb23ce985258 --- /dev/null +++ b/test/unit/dialects/sqlite/connection-manager.test.js @@ -0,0 +1,29 @@ +'use strict'; + +const chai = require('chai'), + expect = chai.expect, + Support = require('../../support'), + Sequelize = Support.Sequelize, + dialect = Support.getTestDialect(), + sinon = require('sinon'); + +if (dialect === 'sqlite') { + describe('connectionManager', () => { + describe('getConnection', () => { + it('Should respect storage=\'\'', () => { + // storage='' means anonymous disk-based database + const sequelize = new Sequelize('', '', '', { dialect: 'sqlite', storage: '' }); + sinon.stub(sequelize.connectionManager, 'lib').value({ + Database: function FakeDatabase(_s, _m, cb) { + cb(); + return {}; + } + }); + sinon.stub(sequelize.connectionManager, 'connections').value({ default: { run: () => {} } }); + const options = {}; + sequelize.dialect.connectionManager.getConnection(options); + expect(options.storage).to.be.equal(''); + }); + }); + }); +} From e86c884cffbbe27cf9971ec66a036107dc33f645 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sun, 17 Oct 2021 13:57:14 +0200 Subject: [PATCH 343/414] refactor(connection-manager): change nullish coalescence implementation (#13568) --- lib/dialects/sqlite/connection-manager.js | 10 +++++++++- test/unit/dialects/sqlite/connection-manager.test.js | 6 ++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 512a8b96d4f1..eb6017bbd197 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -45,7 +45,15 @@ class ConnectionManager extends AbstractConnectionManager { async getConnection(options) { options = options || {}; options.uuid = options.uuid || 'default'; - options.storage = this.sequelize.options.storage !== null && this.sequelize.options.storage !== undefined ? this.sequelize.options.storage : this.sequelize.options.host || ':memory:'; + + if (!!this.sequelize.options.storage !== null && this.sequelize.options.storage !== undefined) { + // Check explicitely for the storage option to not be set since an empty string signals + // SQLite will create a temporary disk-based database in that case. + options.storage = this.sequelize.options.storage; + } else { + options.storage = this.sequelize.options.host || ':memory:'; + } + options.inMemory = options.storage === ':memory:' ? 1 : 0; const dialectOptions = this.sequelize.options.dialectOptions; diff --git a/test/unit/dialects/sqlite/connection-manager.test.js b/test/unit/dialects/sqlite/connection-manager.test.js index fb23ce985258..793b20bdd17c 100644 --- a/test/unit/dialects/sqlite/connection-manager.test.js +++ b/test/unit/dialects/sqlite/connection-manager.test.js @@ -8,11 +8,12 @@ const chai = require('chai'), sinon = require('sinon'); if (dialect === 'sqlite') { - describe('connectionManager', () => { + describe('[SQLITE Specific] ConnectionManager', () => { describe('getConnection', () => { - it('Should respect storage=\'\'', () => { + it('should forward empty string storage to SQLite connector to create temporary disk-based database', () => { // storage='' means anonymous disk-based database const sequelize = new Sequelize('', '', '', { dialect: 'sqlite', storage: '' }); + sinon.stub(sequelize.connectionManager, 'lib').value({ Database: function FakeDatabase(_s, _m, cb) { cb(); @@ -20,6 +21,7 @@ if (dialect === 'sqlite') { } }); sinon.stub(sequelize.connectionManager, 'connections').value({ default: { run: () => {} } }); + const options = {}; sequelize.dialect.connectionManager.getConnection(options); expect(options.storage).to.be.equal(''); From 3ca085db318201fa59422a2ce191bcf76e5f37dc Mon Sep 17 00:00:00 2001 From: Kamalakannan Jayaraman Date: Sun, 17 Oct 2021 18:41:10 +0530 Subject: [PATCH 344/414] feat(postgres): support `query_timeout` dialect option (#13258) * Add query_timeout option to postgres dialectOptions * fix: lint issues in the test file Co-authored-by: Kamalakannan Jayaraman Co-authored-by: Sascha Depold --- lib/dialects/postgres/connection-manager.js | 4 +++- .../dialects/postgres/connection-manager.test.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 0cf2f793743d..a07669efcdcc 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -113,8 +113,10 @@ class ConnectionManager extends AbstractConnectionManager { // This should help with backends incorrectly considering idle clients to be dead and prematurely disconnecting them. // this feature has been added in pg module v6.0.0, check pg/CHANGELOG.md 'keepAlive', - // Times out queries after a set time in milliseconds. Added in pg v7.3 + // Times out queries after a set time in milliseconds in the database end. Added in pg v7.3 'statement_timeout', + // Times out queries after a set time in milliseconds in client end, query would be still running in database end. + 'query_timeout', // Terminate any session with an open transaction that has been idle for longer than the specified duration in milliseconds. Added in pg v7.17.0 only supported in postgres >= 10 'idle_in_transaction_session_timeout' ])); diff --git a/test/integration/dialects/postgres/connection-manager.test.js b/test/integration/dialects/postgres/connection-manager.test.js index 0dbd9641980a..b6924416abb2 100644 --- a/test/integration/dialects/postgres/connection-manager.test.js +++ b/test/integration/dialects/postgres/connection-manager.test.js @@ -39,6 +39,14 @@ if (dialect.match(/^postgres/)) { // `notice` is Postgres's default expect(result[0].client_min_messages).to.equal('notice'); }); + + it('should time out the query request when the query runs beyond the configured query_timeout', async () => { + const sequelize = Support.createSequelizeInstance({ + dialectOptions: { query_timeout: 100 } + }); + const error = await sequelize.query('select pg_sleep(2)').catch(e => e); + expect(error.message).to.equal('Query read timeout'); + }); }); describe('Dynamic OIDs', () => { From 1340ea1e13fc13a211770daa8e9a159f13e1a2d7 Mon Sep 17 00:00:00 2001 From: Jan Kleinert Date: Sun, 17 Oct 2021 13:55:42 -0400 Subject: [PATCH 345/414] docs: add sqlcommenter-sequelize to the list of miscellaneous resources (#13449) Co-authored-by: Sascha Depold --- docs/manual/other-topics/resources.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual/other-topics/resources.md b/docs/manual/other-topics/resources.md index 291f25712526..a90b3d40773c 100644 --- a/docs/manual/other-topics/resources.md +++ b/docs/manual/other-topics/resources.md @@ -60,3 +60,4 @@ * [sequelize-deep-update](https://www.npmjs.com/package/sequelize-deep-update) - Update a sequelize instance and its included associated instances with new properties. * [sequelize-noupdate-attributes](https://www.npmjs.com/package/sequelize-noupdate-attributes) - Adds no update/readonly attributes support to models. * [sequelize-joi](https://www.npmjs.com/package/sequelize-joi) - Allows specifying [Joi](https://github.com/hapijs/joi) validation schema for JSONB model attributes in Sequelize. +* [sqlcommenter-sequelize](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Sequelize](https://google.github.io/sqlcommenter/node/sequelize/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements. \ No newline at end of file From 176f4ff48bbb213bab34178889dca2d367e52057 Mon Sep 17 00:00:00 2001 From: Mykyta Lypniahov <80581736+aboutml@users.noreply.github.com> Date: Mon, 18 Oct 2021 16:28:22 +0300 Subject: [PATCH 346/414] refactor(mssql test): Fix 'should not contain views' failing (#13400) * fix(mssql): Fix 'should not contain views' failing * Let's see if we can remove the try/catch statement Co-authored-by: Sascha Depold --- test/integration/query-interface.test.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index 16ab9f55b5de..efc7f4056ad4 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -35,22 +35,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { describe('showAllTables', () => { it('should not contain views', async function() { - async function cleanup() { - // NOTE: The syntax "DROP VIEW [IF EXISTS]"" is not part of the standard - // and might not be available on all RDBMSs. Therefore "DROP VIEW" is - // the compatible option, which can throw an error in case the VIEW does - // not exist. In case of error, it is ignored. - try { - await this.sequelize.query('DROP VIEW V_Fail'); - } catch (error) { - // Ignore error. - } + async function cleanup(sequelize) { + await sequelize.query('DROP VIEW IF EXISTS V_Fail'); } await this.queryInterface.createTable('my_test_table', { name: DataTypes.STRING }); - await cleanup(); + await cleanup(this.sequelize); await this.sequelize.query('CREATE VIEW V_Fail AS SELECT 1 Id'); let tableNames = await this.queryInterface.showAllTables(); - await cleanup(); + await cleanup(this.sequelize); if (tableNames[0] && tableNames[0].tableName) { tableNames = tableNames.map(v => v.tableName); } From 31d0fbce032073917b4b41f1bdde4fb1fe562f97 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Sat, 23 Oct 2021 20:22:03 +0900 Subject: [PATCH 347/414] fix(types): typo in model.d.ts (#13574) successfull -> successful --- types/lib/model.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 32c9cb4a6e33..95c9752838f8 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1997,7 +1997,7 @@ export abstract class Model( this: ModelStatic, From 094333910e105bbc363321eb7557a582363a8f6d Mon Sep 17 00:00:00 2001 From: Victor Korzunin Date: Sat, 23 Oct 2021 13:58:25 +0200 Subject: [PATCH 348/414] fix(select): do not force set `subQuery` to `false` (#13490) * fix(select): do not force set subQuery to false this occurred on validating included elements in spite of user set it to true in include options * test: make include (subQuery alias) tests dry Co-authored-by: Victor Korzunin Co-authored-by: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Co-authored-by: Sascha Depold --- lib/model.js | 3 +- test/unit/sql/select.test.js | 112 +++++++++++++++++++++++++++++------ 2 files changed, 96 insertions(+), 19 deletions(-) diff --git a/lib/model.js b/lib/model.js index e206a8c687f8..d30d2b9c3b38 100644 --- a/lib/model.js +++ b/lib/model.js @@ -528,7 +528,7 @@ class Model { if (include.subQuery !== false && options.hasDuplicating && options.topLimit) { if (include.duplicating) { - include.subQuery = false; + include.subQuery = include.subQuery || false; include.subQueryFilter = include.hasRequired; } else { include.subQuery = include.hasRequired; @@ -538,7 +538,6 @@ class Model { include.subQuery = include.subQuery || false; if (include.duplicating) { include.subQueryFilter = include.subQuery; - include.subQuery = false; } else { include.subQueryFilter = false; include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired && !include.separate; diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js index a511bbb66ef3..727ae7790c75 100644 --- a/test/unit/sql/select.test.js +++ b/test/unit/sql/select.test.js @@ -449,7 +449,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); - it('include (subQuery alias)', () => { + describe('include (subQuery alias)', () => { const User = Support.sequelize.define('User', { name: DataTypes.STRING, age: DataTypes.INTEGER @@ -466,29 +466,107 @@ describe(Support.getTestDialectTeaser('SQL'), () => { User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'postaliasname' }); - expectsql(sql.selectQuery('User', { - table: User.getTableName(), - model: User, - attributes: ['name', 'age'], + it('w/o filters', () => { + expectsql(sql.selectQuery('User', { + table: User.getTableName(), + model: User, + attributes: ['name', 'age'], + include: Model._validateIncludedElements({ + include: [{ + attributes: ['title'], + association: User.Posts, + subQuery: true, + required: true + }], + as: 'User' + }).include, + subQuery: true + }, User), { + default: 'SELECT [User].* FROM ' + + '(SELECT [User].[name], [User].[age], [User].[id] AS [id], [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM [User] AS [User] ' + + 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id] ' + + `WHERE ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id])${sql.addLimitAndOffset({ limit: 1, tableAs: 'postaliasname' }, User)} ) IS NOT NULL) AS [User];` + }); + }); + + it('w/ nested column filter', () => { + expectsql(sql.selectQuery('User', { + table: User.getTableName(), + model: User, + attributes: ['name', 'age'], + where: { '$postaliasname.title$': 'test' }, + include: Model._validateIncludedElements({ + include: [{ + attributes: ['title'], + association: User.Posts, + subQuery: true, + required: true + }], + as: 'User' + }).include, + subQuery: true + }, User), { + default: 'SELECT [User].* FROM ' + + '(SELECT [User].[name], [User].[age], [User].[id] AS [id], [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM [User] AS [User] ' + + 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id] ' + + `WHERE [postaliasname].[title] = ${sql.escape('test')} AND ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id])${sql.addLimitAndOffset({ limit: 1, tableAs: 'postaliasname' }, User)} ) IS NOT NULL) AS [User];` + }); + }); + }); + + it('include w/ subQuery + nested filter + paging', () => { + const User = Support.sequelize.define('User', { + scopeId: DataTypes.INTEGER + }); + + const Company = Support.sequelize.define('Company', { + name: DataTypes.STRING, + public: DataTypes.BOOLEAN, + scopeId: DataTypes.INTEGER + }); + + const Profession = Support.sequelize.define('Profession', { + name: DataTypes.STRING, + scopeId: DataTypes.INTEGER + }); + + User.Company = User.belongsTo(Company, { foreignKey: 'companyId' }); + User.Profession = User.belongsTo(Profession, { foreignKey: 'professionId' }); + Company.Users = Company.hasMany(User, { as: 'Users', foreignKey: 'companyId' }); + Profession.Users = Profession.hasMany(User, { as: 'Users', foreignKey: 'professionId' }); + + expectsql(sql.selectQuery('Company', { + table: Company.getTableName(), + model: Company, + attributes: ['name', 'public'], + where: { '$Users.Profession.name$': 'test', [Op.and]: { scopeId: [42] } }, include: Model._validateIncludedElements({ include: [{ - attributes: ['title'], - association: User.Posts, + association: Company.Users, + attributes: [], + include: [{ + association: User.Profession, + attributes: [], + required: true + }], subQuery: true, required: true }], - as: 'User' + model: Company }).include, + limit: 5, + offset: 0, subQuery: true - }, User), { - default: 'SELECT [User].*, [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM ' + - '(SELECT [User].[name], [User].[age], [User].[id] AS [id] FROM [User] AS [User] ' + - 'WHERE ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id]) LIMIT 1 ) IS NOT NULL) AS [User] ' + - 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id];', - mssql: 'SELECT [User].*, [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM ' + - '(SELECT [User].[name], [User].[age], [User].[id] AS [id] FROM [User] AS [User] ' + - 'WHERE ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id]) ORDER BY [postaliasname].[id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ) IS NOT NULL) AS [User] ' + - 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id];' + }, Company), { + default: 'SELECT [Company].* FROM (' + + 'SELECT [Company].[name], [Company].[public], [Company].[id] AS [id] FROM [Company] AS [Company] ' + + 'INNER JOIN [Users] AS [Users] ON [Company].[id] = [Users].[companyId] ' + + 'INNER JOIN [Professions] AS [Users->Profession] ON [Users].[professionId] = [Users->Profession].[id] ' + + `WHERE ([Company].[scopeId] IN (42)) AND [Users->Profession].[name] = ${sql.escape('test')} AND ( ` + + 'SELECT [Users].[companyId] FROM [Users] AS [Users] ' + + 'INNER JOIN [Professions] AS [Profession] ON [Users].[professionId] = [Profession].[id] ' + + `WHERE ([Users].[companyId] = [Company].[id])${sql.addLimitAndOffset({ limit: 1, tableAs: 'Users' }, User)} ` + + `) IS NOT NULL${sql.addLimitAndOffset({ limit: 5, offset: 0, tableAs: 'Company' }, Company)}) AS [Company];` }); }); From 84421d7d738176ee6d0de705c493b145b9488532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Proen=C3=A7a?= Date: Sat, 23 Oct 2021 18:49:04 +0100 Subject: [PATCH 349/414] fix(postgres): fix `findCreateFind` to work with postgres transactions (#13482) * fix(postgres): fix `findCreateFind` to work with postgres transactions * refactor(test): reduce complexity of test * Update create.test.js * Update create.test.js Co-authored-by: Sascha Depold --- lib/dialects/postgres/query.js | 6 +++ lib/model.js | 16 +++++-- test/integration/model/create.test.js | 61 ++++++++++++++++-------- test/unit/model/find-create-find.test.js | 28 ++++++----- 4 files changed, 74 insertions(+), 37 deletions(-) diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index e4e4135fe6d1..535cab312c5c 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -264,6 +264,12 @@ class Query extends AbstractQuery { } if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) { if (this.instance && this.instance.dataValues) { + // If we are creating an instance, and we get no rows, the create failed but did not throw. + // This probably means a conflict happened and was ignored, to avoid breaking a transaction. + if (this.isInsertQuery() && rowCount === 0) { + throw new sequelizeErrors.EmptyResultError(); + } + for (const key in rows[0]) { if (Object.prototype.hasOwnProperty.call(rows[0], key)) { const record = rows[0][key]; diff --git a/lib/model.js b/lib/model.js index d30d2b9c3b38..effca70af190 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2376,7 +2376,7 @@ class Model { } /** - * A more performant findOrCreate that will not work under a transaction (at least not in postgres) + * A more performant findOrCreate that may not work under a transaction (working in postgres) * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again * * @see @@ -2405,10 +2405,20 @@ class Model { if (found) return [found, false]; try { - const created = await this.create(values, options); + const createOptions = { ...options }; + + // To avoid breaking a postgres transaction, run the create with `ignoreDuplicates`. + if (this.sequelize.options.dialect === 'postgres' && options.transaction) { + createOptions.ignoreDuplicates = true; + } + + const created = await this.create(values, createOptions); return [created, true]; } catch (err) { - if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; + if (!(err instanceof sequelizeErrors.UniqueConstraintError || err instanceof sequelizeErrors.EmptyResultError)) { + throw err; + } + const foundAgain = await this.findOne(options); return [foundAgain, false]; } diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index d495ceb7a933..a5a417f69e35 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -568,31 +568,50 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('findCreateFind', () => { - (dialect !== 'sqlite' ? it : it.skip)('should work with multiple concurrent calls', async function() { - const [first, second, third] = await Promise.all([ - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }) - ]); + if (dialect !== 'sqlite') { + it('should work with multiple concurrent calls', async function() { + const [ + [instance1, created1], + [instance2, created2], + [instance3, created3] + ] = await Promise.all([ + this.User.findCreateFind({ where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ where: { uniqueName: 'winner' } }) + ]); - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1], - thirdInstance = third[0], - thirdCreated = third[1]; + // All instances are the same + expect(instance1.id).to.equal(1); + expect(instance2.id).to.equal(1); + expect(instance3.id).to.equal(1); + // Only one of the createdN values is true + expect(!!(created1 ^ created2 ^ created3)).to.be.true; + }); - expect([firstCreated, secondCreated, thirdCreated].filter(value => { - return value; - }).length).to.equal(1); + if (current.dialect.supports.transactions) { + it('should work with multiple concurrent calls within a transaction', async function() { + const t = await this.sequelize.transaction(); + const [ + [instance1, created1], + [instance2, created2], + [instance3, created3] + ] = await Promise.all([ + this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }) + ]); - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - expect(thirdInstance).to.be.ok; + await t.commit(); - expect(firstInstance.id).to.equal(secondInstance.id); - expect(secondInstance.id).to.equal(thirdInstance.id); - }); + // All instances are the same + expect(instance1.id).to.equal(1); + expect(instance2.id).to.equal(1); + expect(instance3.id).to.equal(1); + // Only one of the createdN values is true + expect(!!(created1 ^ created2 ^ created3)).to.be.true; + }); + } + } }); describe('create', () => { diff --git a/test/unit/model/find-create-find.test.js b/test/unit/model/find-create-find.test.js index bc1df8cda81f..521dfa2116be 100644 --- a/test/unit/model/find-create-find.test.js +++ b/test/unit/model/find-create-find.test.js @@ -2,8 +2,8 @@ const chai = require('chai'), expect = chai.expect, + { EmptyResultError, UniqueConstraintError } = require('../../../lib/errors'), Support = require('../support'), - UniqueConstraintError = require('../../../lib/errors').UniqueConstraintError, current = Support.sequelize, sinon = require('sinon'); @@ -46,22 +46,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(createSpy).to.have.been.calledWith(where); }); - it('should do a second find if create failed do to unique constraint', async function() { - const result = {}, - where = { prop: Math.random().toString() }, - findSpy = this.sinon.stub(Model, 'findOne'); + [EmptyResultError, UniqueConstraintError].forEach(Error => { + it(`should do a second find if create failed due to an error of type ${Error.name}`, async function() { + const result = {}, + where = { prop: Math.random().toString() }, + findSpy = this.sinon.stub(Model, 'findOne'); - this.sinon.stub(Model, 'create').rejects(new UniqueConstraintError()); + this.sinon.stub(Model, 'create').rejects(new Error()); - findSpy.onFirstCall().resolves(null); - findSpy.onSecondCall().resolves(result); + findSpy.onFirstCall().resolves(null); + findSpy.onSecondCall().resolves(result); - await expect(Model.findCreateFind({ - where - })).to.eventually.eql([result, false]); + await expect(Model.findCreateFind({ + where + })).to.eventually.eql([result, false]); - expect(findSpy).to.have.been.calledTwice; - expect(findSpy.getCall(1).args[0].where).to.equal(where); + expect(findSpy).to.have.been.calledTwice; + expect(findSpy.getCall(1).args[0].where).to.equal(where); + }); }); }); }); From 66e6d7665bcd8e4d3a58e7a13b24605084542b9f Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Sun, 24 Oct 2021 17:52:14 +0700 Subject: [PATCH 350/414] chore(probot-stale): reenable auto-close issues bot --- .github/stale.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index dca733fa73dd..8442c6125e98 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,13 +1,14 @@ - # Configuration for probot-stale - https://github.com/probot/stale -# Number of days of inactivity before an issue becomes stale -# daysUntilStale: 90 -daysUntilStale: 900000 # Temporarily disable +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 60 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 -# Number of days of inactivity before a stale issue is closed -# daysUntilClose: 7 -daysUntilClose: 70000 # Temporarily disable +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] # Issues with these labels will never be considered stale exemptLabels: @@ -21,10 +22,16 @@ exemptLabels: - good first issue - suggestion +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: true + # Set to true to ignore issues in a milestone (defaults to false) exemptMilestones: true -# Label to use when marking an issue as stale +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: true + +# Label to use when marking as stale staleLabel: stale # Limit to only `issues` or `pulls` @@ -36,5 +43,5 @@ markComment: > recent activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment 🙂 -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 From 4098eb04cd6531f1568a0bdbce40f297f9af0ed7 Mon Sep 17 00:00:00 2001 From: dror-heller Date: Sun, 24 Oct 2021 13:59:33 +0300 Subject: [PATCH 351/414] chore(docs): Add documentation for increment method (#13254) * Add documentation for increment utility method * Edit docs Co-authored-by: drorh Co-authored-by: Sascha Depold --- docs/manual/core-concepts/model-querying-basics.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index 3f5c1a0ff664..325130befab5 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -699,3 +699,14 @@ await User.min('age', { where: { age: { [Op.gt]: 5 } } }); // 10 await User.sum('age'); // 55 await User.sum('age', { where: { age: { [Op.gt]: 5 } } }); // 50 ``` + +### `increment`, `decrement` + +Sequelize also provides the `increment` convenience method. + +Let's assume we have a user, whose age is 10. + +```js +await User.increment({age: 5}, { where: { id: 1 } }) // Will increase age to 15 +await User.increment({age: -5}, { where: { id: 1 } }) // Will decrease age to 5 +``` From e4aff2f6270bc52fbdc90bed6269537e2f9714e0 Mon Sep 17 00:00:00 2001 From: Colin Cheng Date: Sun, 24 Oct 2021 19:23:59 +0800 Subject: [PATCH 352/414] fix: allows insert primary key with zero (#13458) * fix: allows insert primary key with zero * test: allows insert primary key with zero Co-authored-by: Sascha Depold --- lib/dialects/abstract/query-generator.js | 5 +-- test/unit/sql/insert.test.js | 39 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index c9793a45693e..e6e9e02df1a4 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -156,7 +156,7 @@ class QueryGenerator { fields.push(this.quoteIdentifier(key)); // SERIALS' can't be NULL in postgresql, use DEFAULT where supported - if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && !value) { + if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && value == null) { if (!this._dialect.supports.autoIncrement.defaultValue) { fields.splice(-1, 1); } else if (this._dialect.supports.DEFAULT) { @@ -276,7 +276,8 @@ class QueryGenerator { this._dialect.supports.bulkDefault && serials[key] === true ) { - return fieldValueHash[key] || 'DEFAULT'; + // fieldValueHashes[key] ?? 'DEFAULT' + return fieldValueHash[key] != null ? fieldValueHash[key] : 'DEFAULT'; } return this.escape(fieldValueHash[key], fieldMappedAttributes[key], { context: 'INSERT' }); diff --git a/test/unit/sql/insert.test.js b/test/unit/sql/insert.test.js index 34b09acdaf27..c9ba66b98e9e 100644 --- a/test/unit/sql/insert.test.js +++ b/test/unit/sql/insert.test.js @@ -36,6 +36,26 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); + + it('allow insert primary key with 0', () => { + const M = Support.sequelize.define('m', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + } + }); + + expectsql(sql.insertQuery(M.tableName, { id: 0 }, M.rawAttributes), + { + query: { + mssql: 'SET IDENTITY_INSERT [ms] ON; INSERT INTO [ms] ([id]) VALUES ($1); SET IDENTITY_INSERT [ms] OFF;', + postgres: 'INSERT INTO "ms" ("id") VALUES ($1);', + default: 'INSERT INTO `ms` (`id`) VALUES ($1);' + }, + bind: [0] + }); + }); }); describe('dates', () => { @@ -161,5 +181,24 @@ describe(Support.getTestDialectTeaser('SQL'), () => { sqlite: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\') ON CONFLICT (`user_name`) DO UPDATE SET `user_name`=EXCLUDED.`user_name`,`pass_word`=EXCLUDED.`pass_word`,`updated_at`=EXCLUDED.`updated_at`;' }); }); + + it('allow bulk insert primary key with 0', () => { + const M = Support.sequelize.define('m', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + } + }); + + expectsql(sql.bulkInsertQuery(M.tableName, [{ id: 0 }, { id: null }], {}, M.fieldRawAttributesMap), + { + query: { + mssql: 'SET IDENTITY_INSERT [ms] ON; INSERT INTO [ms] DEFAULT VALUES;INSERT INTO [ms] ([id]) VALUES (0),(NULL);; SET IDENTITY_INSERT [ms] OFF;', + postgres: 'INSERT INTO "ms" ("id") VALUES (0),(DEFAULT);', + default: 'INSERT INTO `ms` (`id`) VALUES (0),(NULL);' + } + }); + }); }); }); From d511d9164e0f469ccba40d94b4865b73466f64f5 Mon Sep 17 00:00:00 2001 From: Lukas Hroch <223967+lukashroch@users.noreply.github.com> Date: Sun, 24 Oct 2021 12:39:35 +0100 Subject: [PATCH 353/414] fix(types): allow any values in `isIn` validator (#12962) * fix(types): allow any values in `isIn` validator * test(types): isIn/notIn validation type tests Co-authored-by: Sascha Depold --- types/lib/model.d.ts | 6 +++--- types/test/validators.ts | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 types/test/validators.ts diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 95c9752838f8..46da75e54c29 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1109,13 +1109,13 @@ export interface ModelValidateOptions { /** * check the value is not one of these */ - notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; /** * check the value is one of these */ - isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; - + isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + /** * don't allow specific substrings */ diff --git a/types/test/validators.ts b/types/test/validators.ts new file mode 100644 index 000000000000..56254de307db --- /dev/null +++ b/types/test/validators.ts @@ -0,0 +1,22 @@ +import { DataTypes, Model, Sequelize } from 'sequelize'; + +const sequelize = new Sequelize('mysql://user:user@localhost:3306/mydb'); + +/** + * Test for isIn/notIn validation - should accept any[] + */ +class ValidatedUser extends Model {} +ValidatedUser.init({ + name: { + type: DataTypes.STRING, + validate: { + isIn: [['first', 1, null]] + } + }, + email: { + type: DataTypes.STRING, + validate: { + notIn: [['second', 2, null]] + } + }, +}, { sequelize }); \ No newline at end of file From a65345f5e7988eba69bb911945564ea5bada333e Mon Sep 17 00:00:00 2001 From: pahadimunu <72349611+pahadimunu@users.noreply.github.com> Date: Sun, 24 Oct 2021 19:19:01 +0530 Subject: [PATCH 354/414] docs(through-attribute): fixed association example to exclude junction table (#13295) * feat(docs): fixed association example to exclude junction table * Update eager-loading.md Co-authored-by: f[nZk] --- docs/manual/advanced-association-concepts/eager-loading.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/manual/advanced-association-concepts/eager-loading.md b/docs/manual/advanced-association-concepts/eager-loading.md index 0d4ad8220fbd..70370ce1536d 100644 --- a/docs/manual/advanced-association-concepts/eager-loading.md +++ b/docs/manual/advanced-association-concepts/eager-loading.md @@ -416,13 +416,15 @@ Foo.findAll({ }); ``` -If you don't want anything from the junction table, you can explicitly provide an empty array to the `attributes` option, and in this case nothing will be fetched and the extra property will not even be created: +If you don't want anything from the junction table, you can explicitly provide an empty array to the `attributes` option inside the `through` option of the `include` option, and in this case nothing will be fetched and the extra property will not even be created: ```js Foo.findOne({ include: { model: Bar, - attributes: [] + through: { + attributes: [] + } } }); ``` From 7e4bb2cc2cf59407c73fb29e1987b96f9d97265d Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Mon, 25 Oct 2021 01:08:10 +0700 Subject: [PATCH 355/414] docs(model-basics): fix UUIDV4 as a method of DataTypes (#13585) --- docs/manual/core-concepts/model-basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md index db4075280dec..166ac6174dbb 100644 --- a/docs/manual/core-concepts/model-basics.md +++ b/docs/manual/core-concepts/model-basics.md @@ -324,12 +324,12 @@ DataTypes.DATEONLY // DATE without time ### UUIDs -For UUIDs, use `DataTypes.UUID`. It becomes the `UUID` data type for PostgreSQL and SQLite, and `CHAR(36)` for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use `Sequelize.UUIDV1` or `Sequelize.UUIDV4` as the default value: +For UUIDs, use `DataTypes.UUID`. It becomes the `UUID` data type for PostgreSQL and SQLite, and `CHAR(36)` for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use `DataTypes.UUIDV1` or `DataTypes.UUIDV4` as the default value: ```js { type: DataTypes.UUID, - defaultValue: Sequelize.UUIDV4 // Or Sequelize.UUIDV1 + defaultValue: DataTypes.UUIDV4 // Or DataTypes.UUIDV1 } ``` From 56079c543ab0178e546071a3575986aa3506f81b Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Mon, 25 Oct 2021 01:12:04 +0700 Subject: [PATCH 356/414] docs: added missing comma (#12918) (#13583) fix wrong branch in PR --- docs/manual/other-topics/scopes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/other-topics/scopes.md b/docs/manual/other-topics/scopes.md index 73eb380364af..96a531115122 100644 --- a/docs/manual/other-topics/scopes.md +++ b/docs/manual/other-topics/scopes.md @@ -44,7 +44,7 @@ Project.init({ } } } - } + }, sequelize, modelName: 'project' } @@ -281,4 +281,4 @@ Observe how the four scopes were merged into one. The includes of scopes are mer The merge illustrated above works in the exact same way regardless of the order applied to the scopes. The order would only make a difference if a certain option was set by two different scopes - which is not the case of the above example, since each scope does a different thing. -This merge strategy also works in the exact same way with options passed to `.findAll`, `.findOne` and the like. \ No newline at end of file +This merge strategy also works in the exact same way with options passed to `.findAll`, `.findOne` and the like. From 82d1072fa262d9fca09068aea6632f06da2ed284 Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Mon, 25 Oct 2021 01:12:33 +0700 Subject: [PATCH 357/414] docs(database): update the explanation to be less confusing #12541 (#13581) fix wrong branch merge that cannot be reopened again. --- docs/manual/core-concepts/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/getting-started.md b/docs/manual/core-concepts/getting-started.md index bdc0b2a6e4f8..94bd14933049 100644 --- a/docs/manual/core-concepts/getting-started.md +++ b/docs/manual/core-concepts/getting-started.md @@ -83,7 +83,7 @@ To experiment with the other dialects, which are harder to setup locally, you ca ## New databases versus existing databases -If you are starting a project from scratch, and your database does not exist yet, Sequelize can be used since the beginning in order to automate the creation of every table in your database. +If you are starting a project from scratch, and your database is still empty, Sequelize can be used since the beginning in order to automate the creation of every table in your database. Also, if you want to use Sequelize to connect to a database that is already filled with tables and data, that works as well! Sequelize has got you covered in both cases. From eeb6a8fbeb6549be038f2dbb0eefb414c7450653 Mon Sep 17 00:00:00 2001 From: Steve Schmitt Date: Sun, 24 Oct 2021 19:31:12 -0700 Subject: [PATCH 358/414] fix(sqlite): quote table names in sqlite getForeignKeysQuery (#13587) --- lib/dialects/sqlite/query-generator.js | 2 +- test/unit/dialects/sqlite/query-generator.test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 8997bb6a0e9a..82d65c44c828 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -462,7 +462,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { * @private */ getForeignKeysQuery(tableName) { - return `PRAGMA foreign_key_list(${tableName})`; + return `PRAGMA foreign_key_list(${this.quoteTable(this.addSchema(tableName))})`; } } diff --git a/test/unit/dialects/sqlite/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js index a3dd0dd51440..3e5b66797063 100644 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ b/test/unit/dialects/sqlite/query-generator.test.js @@ -627,6 +627,13 @@ if (dialect === 'sqlite') { 'INSERT INTO `myTable` SELECT `commit`, `bar` FROM `myTable_backup`;' + 'DROP TABLE `myTable_backup`;' } + ], + getForeignKeysQuery: [ + { + title: 'Property quotes table names', + arguments: ['myTable'], + expectation: 'PRAGMA foreign_key_list(`myTable`)' + } ] }; From 565a3cff2e9f0f2b8005308780afd72ab83af415 Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Wed, 27 Oct 2021 13:52:35 +0700 Subject: [PATCH 359/414] build: remove probot-stale (#13598) --- .github/stale.yml | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 8442c6125e98..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - type: feature - - type: docs - - type: bug - - discussion - - type: performance - - breaking change - - good first issue - - suggestion - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: true - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: true - -# Label to use when marking as stale -staleLabel: stale - -# Limit to only `issues` or `pulls` -only: issues - -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. - If this is still an issue, just leave a comment 🙂 - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 From e21073ac785703453ca4f05bb5f14b20d6e60116 Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Wed, 27 Oct 2021 14:12:06 +0700 Subject: [PATCH 360/414] build(actions/stale): change probot-stale to actions/stale (#13595) * build(actions/stale): change probot-stale to actions/stale * meta: resolve issue message * build: remove probot-stale (#13595) Co-authored-by: Sascha Depold --- .github/workflows/stale.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..1d934f1a560c --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,19 @@ +name: "Stale issue handler" +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@main + id: stale + with: + stale-issue-label: "stale" + stale-issue-message: 'This issue has been automatically marked as stale because it has been open for 7 days without activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment or remove the "stale" label. 🙂' + days-before-stale: 7 + days-before-close: 5 + - name: Print outputs + run: echo ${{ join(steps.stale.outputs.*, ',') }} From ef5b7c3d05ff2d9e651b9971ae6854ab1af9323e Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Wed, 27 Oct 2021 13:28:23 +0200 Subject: [PATCH 361/414] Run stale workflow every 5 minutes --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1d934f1a560c..61904f8ad1d4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: "Stale issue handler" on: workflow_dispatch: schedule: - - cron: "0 0 * * *" + - cron: "*/5 * * * *" jobs: stale: From 6c2d74a0feb4b0db3f5f46afc5486bb79042e99d Mon Sep 17 00:00:00 2001 From: Abhishek Shah Date: Wed, 27 Oct 2021 22:57:47 +0530 Subject: [PATCH 362/414] actions: skip issues/PRs already marked stale (#13604) --- .github/workflows/stale.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 61904f8ad1d4..6f7867481890 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,5 +15,7 @@ jobs: stale-issue-message: 'This issue has been automatically marked as stale because it has been open for 7 days without activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment or remove the "stale" label. 🙂' days-before-stale: 7 days-before-close: 5 + exempt-issue-labels: 'stale' + exempt-pr-labels: 'stale' - name: Print outputs run: echo ${{ join(steps.stale.outputs.*, ',') }} From cb86472473857f17499e30a0a5c1eefc02e7185b Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Wed, 27 Oct 2021 20:55:26 +0200 Subject: [PATCH 363/414] Try to work with more issues per run --- .github/workflows/stale.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6f7867481890..26bbf4b48b88 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,6 @@ jobs: stale-issue-message: 'This issue has been automatically marked as stale because it has been open for 7 days without activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment or remove the "stale" label. 🙂' days-before-stale: 7 days-before-close: 5 - exempt-issue-labels: 'stale' - exempt-pr-labels: 'stale' + operations-per-run: 1000 - name: Print outputs run: echo ${{ join(steps.stale.outputs.*, ',') }} From d1a2572f2b5811cdb8ffbd20e285db44dfc539ca Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Wed, 27 Oct 2021 21:04:15 +0200 Subject: [PATCH 364/414] Run stale workflow once per day --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 26bbf4b48b88..91a8170d5313 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: "Stale issue handler" on: workflow_dispatch: schedule: - - cron: "*/5 * * * *" + - cron: "0 0 * * *" jobs: stale: From 0748fc648a89fc7aabd2897e75e9788a98b10a73 Mon Sep 17 00:00:00 2001 From: Cryptos <74561974+cryptosbyte@users.noreply.github.com> Date: Fri, 29 Oct 2021 18:45:06 +0100 Subject: [PATCH 365/414] Missing : in "sqlite::memory:" (docs) (#13599) * Missing : in "sqlite::memory:" * Update model-basics.md Co-authored-by: Sascha Depold --- docs/manual/core-concepts/model-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md index 166ac6174dbb..f1662ab2d52a 100644 --- a/docs/manual/core-concepts/model-basics.md +++ b/docs/manual/core-concepts/model-basics.md @@ -51,7 +51,7 @@ console.log(User === sequelize.models.User); // true ```js const { Sequelize, DataTypes, Model } = require('sequelize'); -const sequelize = new Sequelize('sqlite::memory'); +const sequelize = new Sequelize('sqlite::memory:'); class User extends Model {} From 7a66841d8a77d1a7a8e6e4216026c7d501764ff6 Mon Sep 17 00:00:00 2001 From: Bishwodahal <61019968+Bishwodahal@users.noreply.github.com> Date: Sat, 30 Oct 2021 13:49:46 +0545 Subject: [PATCH 366/414] docs(model-querying-basics): added semicolons on docs (#13611) --- docs/manual/core-concepts/model-querying-basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md index 325130befab5..91a890208982 100644 --- a/docs/manual/core-concepts/model-querying-basics.md +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -139,7 +139,7 @@ Post.findAll({ authorId: 2 } }); -// SELECT * FROM post WHERE authorId = 2 +// SELECT * FROM post WHERE authorId = 2; ``` Observe that no operator (from `Op`) was explicitly passed, so Sequelize assumed an equality comparison by default. The above code is equivalent to: @@ -153,7 +153,7 @@ Post.findAll({ } } }); -// SELECT * FROM post WHERE authorId = 2 +// SELECT * FROM post WHERE authorId = 2; ``` Multiple checks can be passed: From c3c690b90688941eab5c9efa6918314d52a9b8ef Mon Sep 17 00:00:00 2001 From: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Date: Sun, 31 Oct 2021 14:11:34 +0100 Subject: [PATCH 367/414] fix(docs): using incorrect esdocs syntax (#13615) --- lib/model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index effca70af190..8e1b98e6a765 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2447,7 +2447,7 @@ class Model { * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * - * @returns {Promise<[Model, boolean | null]>} returns an array with two elements, the first being the new record and the second being `true` if it was just created or `false` if it already existed (except on Postgres and SQLite, which can't detect this and will always return `null` instead of a boolean). + * @returns {Promise>} returns an array with two elements, the first being the new record and the second being `true` if it was just created or `false` if it already existed (except on Postgres and SQLite, which can't detect this and will always return `null` instead of a boolean). */ static async upsert(values, options) { options = { From 7e43212f6f12db8afa3e5ceb9ef5502919effbcc Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sun, 31 Oct 2021 20:44:58 +0100 Subject: [PATCH 368/414] test(mysql): add test-support for MySQL 8 (#13610) --- .github/workflows/ci.yml | 8 +++ dev/mysql/8.0/check.js | 8 +++ dev/mysql/8.0/docker-compose.yml | 21 ++++++ dev/mysql/8.0/start.sh | 16 +++++ dev/mysql/8.0/stop.sh | 8 +++ lib/data-types.js | 10 +-- lib/dialects/mysql/query.js | 2 +- package.json | 1 + test/integration/model.test.js | 86 ++++++++++++++++++++++-- test/integration/query-interface.test.js | 4 +- 10 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 dev/mysql/8.0/check.js create mode 100644 dev/mysql/8.0/docker-compose.yml create mode 100755 dev/mysql/8.0/start.sh create mode 100755 dev/mysql/8.0/stop.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 941c0ed80a0e..6ddd0b5508fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,14 @@ jobs: image: mysql:5.7 dialect: mysql node-version: 12 + - name: MySQL 8.0 + image: mysql:8.0 + dialect: mysql + node-version: 10 + - name: MySQL 8.0 + image: mysql:8.0 + dialect: mysql + node-version: 12 - name: MariaDB 10.3 image: mariadb:10.3 dialect: mariadb diff --git a/dev/mysql/8.0/check.js b/dev/mysql/8.0/check.js new file mode 100644 index 000000000000..c364203fd9eb --- /dev/null +++ b/dev/mysql/8.0/check.js @@ -0,0 +1,8 @@ +'use strict'; + +const sequelize = require('../../../test/support').createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); +})(); diff --git a/dev/mysql/8.0/docker-compose.yml b/dev/mysql/8.0/docker-compose.yml new file mode 100644 index 000000000000..fce29b8c9886 --- /dev/null +++ b/dev/mysql/8.0/docker-compose.yml @@ -0,0 +1,21 @@ +services: + mysql-80: + container_name: sequelize-mysql-80 + image: mysql:8.0 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 20057:3306 + # tmpfs: /var/lib/mysql:rw + healthcheck: + test: ["CMD", "mysqladmin", "-usequelize_test", "-psequelize_test", "status"] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mysql-80-network diff --git a/dev/mysql/8.0/start.sh b/dev/mysql/8.0/start.sh new file mode 100755 index 000000000000..d3b0aaab912c --- /dev/null +++ b/dev/mysql/8.0/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mysql-80 down --remove-orphans +docker-compose -p sequelize-mysql-80 up -d + +./../../wait-until-healthy.sh sequelize-mysql-80 + +docker exec sequelize-mysql-80 \ + mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +node check.js + +echo "Local MySQL-8.0 instance is ready for Sequelize tests." diff --git a/dev/mysql/8.0/stop.sh b/dev/mysql/8.0/stop.sh new file mode 100755 index 000000000000..a5a6492ca0f7 --- /dev/null +++ b/dev/mysql/8.0/stop.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mysql-80 down --remove-orphans + +echo "Local MySQL-8.0 instance stopped (if it was running)." diff --git a/lib/data-types.js b/lib/data-types.js index e96e334180c7..90640c9608d7 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -792,7 +792,7 @@ class ARRAY extends ABSTRACT { * GeoJSON is accepted as input and returned as output. * * In PostGIS, the GeoJSON is parsed using the PostGIS function `ST_GeomFromGeoJSON`. - * In MySQL it is parsed using the function `GeomFromText`. + * In MySQL it is parsed using the function `ST_GeomFromText`. * * Therefore, one can just follow the [GeoJSON spec](https://tools.ietf.org/html/rfc7946) for handling geometry objects. See the following examples: * @@ -844,10 +844,10 @@ class GEOMETRY extends ABSTRACT { this.srid = options.srid; } _stringify(value, options) { - return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } _bindParam(value, options) { - return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } } @@ -887,10 +887,10 @@ class GEOGRAPHY extends ABSTRACT { this.srid = options.srid; } _stringify(value, options) { - return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } _bindParam(value, options) { - return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } } diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index a0745a799bcc..f607abc8c58f 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -216,7 +216,7 @@ class Query extends AbstractQuery { let fields = {}; let message = 'Validation error'; const values = match ? match[1].split('-') : undefined; - const fieldKey = match ? match[2] : undefined; + const fieldKey = match ? match[2].split('.').pop() : undefined; const fieldVal = match ? match[1] : undefined; const uniqueKey = this.model && this.model.uniqueKeys[fieldKey]; diff --git a/package.json b/package.json index b6eaa7136613..f5427dc330cc 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,7 @@ "----------------------------------------- local test dbs ------------------------------------------": "", "start-mariadb": "bash dev/mariadb/10.3/start.sh", "start-mysql": "bash dev/mysql/5.7/start.sh", + "start-mysql-8": "bash dev/mysql/8.0/start.sh", "start-postgres": "bash dev/postgres/10/start.sh", "start-mssql": "bash dev/mssql/2019/start.sh", "stop-mariadb": "bash dev/mariadb/10.3/stop.sh", diff --git a/test/integration/model.test.js b/test/integration/model.test.js index 24b05dea0bc3..d6020747b468 100644 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -14,8 +14,10 @@ const chai = require('chai'), Op = Sequelize.Op, semver = require('semver'), pMap = require('p-map'); - + describe(Support.getTestDialectTeaser('Model'), () => { + let isMySQL8; + before(function() { this.clock = sinon.useFakeTimers(); }); @@ -25,6 +27,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); beforeEach(async function() { + isMySQL8 = dialect === 'mysql' && semver.satisfies(current.options.databaseVersion, '>=8.0.0'); + this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -373,6 +377,79 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); + describe('descending indices (MySQL 8 specific)', ()=>{ + it('complains about missing support for descending indexes', async function() { + if (!isMySQL8) { + return; + } + + const indices = [{ + name: 'a_b_uniq', + unique: true, + method: 'BTREE', + fields: [ + 'fieldB', + { + attribute: 'fieldA', + collate: 'en_US', + order: 'DESC', + length: 5 + } + ] + }]; + + this.sequelize.define('model', { + fieldA: Sequelize.STRING, + fieldB: Sequelize.INTEGER, + fieldC: Sequelize.STRING, + fieldD: Sequelize.STRING + }, { + indexes: indices, + engine: 'MyISAM' + }); + + try { + await this.sequelize.sync(); + expect.fail(); + } catch (e) { + expect(e.message).to.equal('The storage engine for the table doesn\'t support descending indexes'); + } + }); + + it('works fine with InnoDB', async function() { + if (!isMySQL8) { + return; + } + + const indices = [{ + name: 'a_b_uniq', + unique: true, + method: 'BTREE', + fields: [ + 'fieldB', + { + attribute: 'fieldA', + collate: 'en_US', + order: 'DESC', + length: 5 + } + ] + }]; + + this.sequelize.define('model', { + fieldA: Sequelize.STRING, + fieldB: Sequelize.INTEGER, + fieldC: Sequelize.STRING, + fieldD: Sequelize.STRING + }, { + indexes: indices, + engine: 'InnoDB' + }); + + await this.sequelize.sync(); + }); + }); + it('should allow the user to specify indexes in options', async function() { const indices = [{ name: 'a_b_uniq', @@ -383,7 +460,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { { attribute: 'fieldA', collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', - order: 'DESC', + order: isMySQL8 ? 'ASC' : 'DESC', length: 5 } ] @@ -2277,8 +2354,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } catch (err) { if (dialect === 'mysql') { - // MySQL 5.7 or above doesn't support POINT EMPTY - if (semver.gte(current.options.databaseVersion, '5.6.0')) { + if (isMySQL8) { + expect(err.message).to.match(/Failed to open the referenced table '4uth0r5'/); + } else if (semver.gte(current.options.databaseVersion, '5.6.0')) { expect(err.message).to.match(/Cannot add foreign key constraint/); } else { expect(err.message).to.match(/Can't create table/); diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index efc7f4056ad4..d37481c147fb 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -82,7 +82,9 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { tableNames = tableNames.map(v => v.tableName); } tableNames.sort(); - expect(tableNames).to.deep.equal(['my_test_table1', 'my_test_table2']); + + expect(tableNames).to.include('my_test_table1'); + expect(tableNames).to.include('my_test_table2'); }); } }); From a58781c17a607a5f0ac28a069051f3fd30b42c06 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Mon, 1 Nov 2021 18:50:46 +0100 Subject: [PATCH 369/414] Add note about sponsoring options. (#13616) * Add note about sponsoring options. * Fix linting --- docs/index.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 79bf6dc78615..c49d1a0fc882 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) [![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) - +[![sponsor](https://img.shields.io/opencollective/all/sequelize?label=sponsors)](https://opencollective.com/sequelize) [![Last commit](https://badgen.net/github/last-commit/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![GitHub stars](https://badgen.net/github/stars/sequelize/sequelize)](https://github.com/sequelize/sequelize) @@ -46,3 +46,9 @@ User.init({ ``` To learn more about how to use Sequelize, read the tutorials available in the left menu. Begin with [Getting Started](manual/getting-started.html). + +## Supporting the project + +Do you like Sequelize and would like to give back to the engineering team behind it? + +We have recently created an [OpenCollective based money pool](https://opencollective.com/sequelize) which is shared amongst all core maintainers based on their contributions. Every support is wholeheartedly welcome. ❤️ From 719bb595275bf8058635407ff5fbf8e1602d82f8 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Mon, 1 Nov 2021 18:51:46 +0100 Subject: [PATCH 370/414] Add note about sponsoring options. (#13617) * Add note about sponsoring options. #13616 * Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 957d12f1bc1d..3878c9b9e336 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![sponsor](https://img.shields.io/opencollective/all/sequelize?label=sponsors)](https://opencollective.com/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) - Sequelize is a promise-based [Node.js](https://nodejs.org/en/about/) [ORM tool](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. @@ -20,13 +19,11 @@ Would you like to contribute? Read [our contribution guidelines](https://github. You can find the detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). -## Note: Looking for maintainers! +## Supporting the project -Recently, a bigger part of the former core maintainers (thanks to all your hard work!) have been rather busy. Hence, the available time to look after our beloved ORM has been shrinking and shrinking drastically, generating a great chance for you: +Do you like Sequelize and would like to give back to the engineering team behind it? -We are looking for more core maintainers who are interested in improving/fixing our TypeScript typings, improving the documentation, organizing issues, reviewing PRs, streamlining the overall code base and planning the future roadmap. - -If that sounds interesting to you, please reach out to us on [our Slack channel](https://sequelize.slack.com/) by sending a direct message to *Pedro A P B*. If you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/). We are looking forward to meet you! +We have recently created an [OpenCollective based money pool](https://opencollective.com/sequelize) which is shared amongst all core maintainers based on their contributions. Every support is wholeheartedly welcome. ❤️ ## Installation From 594cee88a54ef82709b04c5ffd9a1f03d76b2d18 Mon Sep 17 00:00:00 2001 From: Matthew Blasius Date: Mon, 1 Nov 2021 12:57:09 -0500 Subject: [PATCH 371/414] fix(upsert): do not overwrite an explcit created_at during upsert (#13593) We found that when doing an upsert with model data that already included a `createdAt` timestampe, our explcit `createdAt` was being overwritten with the current time any time we also utilized the `fields` option. e.g. ``` const instance = await MyModel.upsert({ id: 1, myfield: 'blah', createdAt: new Date('2010-01-01T12:00:00.000Z') }, { fields: [ 'myfield' ], returning: true }); console.log(instance.createdAt); // expected 2010-01-01T12:00:00.000Z, but got a now()-ish timestamp. ``` Issue appears to be that the check for a provided `createdAt` was being checked against the `updateValues` instead of the `insertValues`. Most of the time, this is inconsequential because the `insertValues` and `updateValues` both contain the same fields. But, when using the `fields` feature, the `createdAt` field is stripped away from the `updateValues`, so sequelize happily overwrites the `insertValues.createAt` value. Co-authored-by: Sascha Depold --- lib/model.js | 2 +- test/integration/model/upsert.test.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/model.js b/lib/model.js index 8e1b98e6a765..22bb01ead46b 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2480,7 +2480,7 @@ class Model { const now = Utils.now(this.sequelize.options.dialect); // Attach createdAt - if (createdAtAttr && !updateValues[createdAtAttr]) { + if (createdAtAttr && !insertValues[createdAtAttr]) { const field = this.rawAttributes[createdAtAttr].field || createdAtAttr; insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now; } diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index 2a67cb18e6c7..26e51c7bbc26 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -302,6 +302,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { clock.restore(); }); + it('does not overwrite createdAt when supplied as an explicit insert value when using fields', async function() { + const clock = sinon.useFakeTimers(); + const originalCreatedAt = new Date('2010-01-01T12:00:00.000Z'); + await this.User.upsert({ id: 42, username: 'john', createdAt: originalCreatedAt }, { fields: ['id', 'username'] }); + const user = await this.User.findByPk(42); + expect(user.createdAt).to.deep.equal(originalCreatedAt); + clock.restore(); + }); + it('does not update using default values', async function() { await this.User.create({ id: 42, username: 'john', baz: 'new baz value' }); const user0 = await this.User.findByPk(42); From 35978f0633efbefc3749363717378996b806cc95 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Mon, 1 Nov 2021 19:26:41 +0100 Subject: [PATCH 372/414] feat(mysql): add support for MySQL v8 (#13618) This is a dummy commit so that the message appears in the changelog --- ENGINE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ENGINE.md b/ENGINE.md index 57f2d36e1066..46b52458ce44 100644 --- a/ENGINE.md +++ b/ENGINE.md @@ -8,3 +8,4 @@ | MariaDB | [10.3](https://mariadb.com/kb/en/changes-improvements-in-mariadb-103/) | | Microsoft SQL Server | `12.0.2000` | | SQLite | [3.0](https://www.sqlite.org/version3.html) | + From 948d95610e707c353db1af3c5f0ab2355b63db60 Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Wed, 3 Nov 2021 02:29:31 +0700 Subject: [PATCH 373/414] docs: minor change to repository words (#13608) * build: remove probot-stale (#13595) * docs: fix markdowns words, and reformat * docs(readme): delete words * docs(readme): change pedro to sdepold * Update PULL_REQUEST_TEMPLATE.md Co-authored-by: Sascha Depold --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++--- .github/ISSUE_TEMPLATE/docs_issue.md | 7 +- .github/ISSUE_TEMPLATE/feature_request.md | 7 +- .github/ISSUE_TEMPLATE/other_issue.md | 7 +- .github/PULL_REQUEST_TEMPLATE.md | 14 ++- .github/workflows/ci.yml | 12 ++- CONTRIBUTING.DOCS.md | 10 +- CONTRIBUTING.md | 111 +++++++++++----------- ENGINE.md | 14 +-- SECURITY.md | 4 +- 10 files changed, 107 insertions(+), 107 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9be60467005d..3f5e52402f12 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a bug report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- -### Pull Request check-list +### Pull Request Checklist _Please make sure to review and check all of these items:_ -- [ ] Does `npm run test` or `npm run test-DIALECT` pass with this change (including linting)? -- [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? - [ ] Have you added new tests to prevent regressions? +- [ ] Does `npm run test` or `npm run test-DIALECT` pass with this change (including linting)? - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? - [ ] Did you update the typescript typings accordingly (if applicable)? +- [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? - [ ] Did you follow the commit message conventions explained in [CONTRIBUTING.md](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md)? -### Description of change +### Description Of Change + +### Todos + +- [ ] +- [ ] +- [ ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ddd0b5508fa..e64716b3a780 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - ts-version: ['3.9', '4.0', '4.1'] + ts-version: ["3.9", "4.0", "4.1"] name: TS Typings (${{ matrix.ts-version }}) runs-on: ubuntu-latest steps: @@ -187,7 +187,15 @@ jobs: release: name: Release runs-on: ubuntu-latest - needs: [lint, test-typings, test-sqlite, test-postgres, test-mysql-mariadb, test-mssql] + needs: + [ + lint, + test-typings, + test-sqlite, + test-postgres, + test-mysql-mariadb, + test-mssql, + ] if: github.event_name == 'push' && github.ref == 'refs/heads/v6' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.DOCS.md b/CONTRIBUTING.DOCS.md index 844993d92094..579b0b8e6d55 100644 --- a/CONTRIBUTING.DOCS.md +++ b/CONTRIBUTING.DOCS.md @@ -9,12 +9,4 @@ The whole documentation is rendered using ESDoc and continuously deployed to Git The tutorials, written in markdown, are located in the `docs` folder. ESDoc is configured to find them in the `"manual"` field of `.esdoc.json`. -To generate the docs locally, run `npm run docs` and open the generated `esdoc/index.html` in your favorite browser. - -## Articles and example based docs - -Write markdown, and have fun :) - -## API docs - -Change the source code documentation comments, using JSDoc syntax, and rerun `npm run docs` to see your changes. +To generate the documentations locally, run `npm run docs` and open the generated `esdoc/index.html` in your favorite browser. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b04bc882de58..4be8da2fb55c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,14 +2,14 @@ We are happy to see that you might be interested in contributing to Sequelize! There is no need to ask for permission to contribute. For example, anyone can open issues and propose changes to the source code (via Pull Requests). Here are some ways people can contribute: -* Opening well-written bug reports (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) -* Opening well-written feature requests (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) -* Proposing improvements to the documentation (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) -* Opening Pull Requests to fix bugs or make other improvements -* Reviewing (i.e. commenting on) open Pull Requests, to help their creators improve it if needed and allow maintainers to take less time looking into them -* Helping to clarify issues opened by others, commenting and asking for clarification -* Answering [questions tagged with `sequelize.js` on StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) -* Helping people in our [public Slack channel](https://sequelize.slack.com/) (note: if you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/)) +- Opening well-written bug reports (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Opening well-written feature requests (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Proposing improvements to the documentation (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Opening Pull Requests to fix bugs or make other improvements +- Reviewing (i.e. commenting on) open Pull Requests, to help their creators improve it if needed and allow maintainers to take less time looking into them +- Helping to clarify issues opened by others, commenting and asking for clarification +- Answering [questions tagged with `sequelize.js` on StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) +- Helping people in our [public Slack channel](https://sequelize.slack.com/) (note: if you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/)) Sequelize is strongly moved by contributions from people like you. All maintainers also work on their free time here. @@ -35,28 +35,27 @@ You can also create and execute your SSCCE locally: see [Section 5](https://gith We're more than happy to accept feature requests! Before we get into how you can bring these to our attention, let's talk about our process for evaluating feature requests: -- A feature request can have three states - *approved*, *pending* and *rejected*. - - *Approved* feature requests are accepted by maintainers as a valuable addition to Sequelize, and are ready to be worked on by anyone. - - *Rejected* feature requests were considered not applicable to be a part of the Sequelize ORM. This can change, so feel free to comment on a rejected feature request providing a good reasoning and clarification on why it should be reconsidered. - - *Pending* feature requests are waiting to be looked at by maintainers. They may or may not need clarification. Contributors can still submit pull requests implementing a pending feature request, if they want, at their own risk of having the feature request rejected (and the pull request closed without being merged). - +- A feature request can have three states - _approved_, _pending_ and _rejected_. + - _Approved_ feature requests are accepted by maintainers as a valuable addition to Sequelize, and are ready to be worked on by anyone. + - _Rejected_ feature requests were considered not applicable to be a part of the Sequelize ORM. This can change, so feel free to comment on a rejected feature request providing a good reasoning and clarification on why it should be reconsidered. + - _Pending_ feature requests are waiting to be looked at by maintainers. They may or may not need clarification. Contributors can still submit pull requests implementing a pending feature request, if they want, at their own risk of having the feature request rejected (and the pull request closed without being merged). Please be sure to communicate the following: - 1. What problem your feature request aims to solve OR what aspect of the Sequelize workflow it aims to improve. +1. What problem your feature request aims to solve OR what aspect of the Sequelize workflow it aims to improve. - 2. Under what conditions are you anticipating this feature to be most beneficial? +2. Under what conditions are you anticipating this feature to be most beneficial? - 3. Why does it make sense that Sequelize should integrate this feature? +3. Why does it make sense that Sequelize should integrate this feature? - 4. See our [Feature Request template](https://github.com/sequelize/sequelize/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) for more details on what to include. Please be sure to follow this template. +4. See our [Feature Request template](https://github.com/sequelize/sequelize/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) for more details on what to include. Please be sure to follow this template. - If we don't approve your feature request, we'll provide you with our reasoning before closing it out. Some common reasons for denial may include (but are not limited to): +If we don't approve your feature request, we'll provide you with our reasoning before closing it out. Some common reasons for denial may include (but are not limited to): - - Something too similar to already exists within Sequelize - - This feature seems out of scope of what Sequelize exists to accomplish +- Something too similar to already exists within Sequelize +- This feature seems out of scope of what Sequelize exists to accomplish - We don't want to deny feature requests that could potentially make our users lives easier, so please be sure to clearly communicate your goals within your request! +We don't want to deny feature requests that could potentially make our users lives easier, so please be sure to clearly communicate your goals within your request! ### Opening an issue to request improvements to the documentation @@ -70,7 +69,7 @@ Anyone can open a Pull Request, there is no need to ask for permission. Maintain The target of the Pull Request should be the `main` branch (or in rare cases the `v5` branch, if previously agreed with a maintainer). -Please check the *allow edits from maintainers* box when opening it. Thank you in advance for any pull requests that you open! +Please check the _allow edits from maintainers_ box when opening it. Thank you in advance for any pull requests that you open! If you started to work on something but didn't finish it yet, you can open a draft pull request if you want (by choosing the "draft" option). Maintainers will know that it's not ready to be reviewed yet. @@ -80,38 +79,37 @@ If your pull request implements a new feature, it's better if the feature was al Once you open a pull request, our automated checks will run (they take a few minutes). Make sure they are all passing. If they're not, make new commits to your branch fixing that, and the pull request will pick them up automatically and rerun our automated checks. -Note: if you believe a test failed but is completely unrelated to your changes, it could be a rare situation of a *flaky test* that is not your fault, and if it's indeed the case, and everything else passed, a maintainer will ignore the *flaky test* and merge your pull request, so don't worry. +Note: if you believe a test failed but is completely unrelated to your changes, it could be a rare situation of a _flaky test_ that is not your fault, and if it's indeed the case, and everything else passed, a maintainer will ignore the _flaky test_ and merge your pull request, so don't worry. A pull request that fixes a bug or implements a new feature must add at least one automated test that: - Passes - Would not pass if executed without your implementation - ## How to prepare a development environment for Sequelize ### 0. Requirements Most operating systems provide all the needed tools (including Windows, Linux and MacOS): -* Mandatory: +- Mandatory: - * [Node.js](http://nodejs.org) - * [Git](https://git-scm.com/) + - [Node.js](http://nodejs.org) + - [Git](https://git-scm.com/) -* Optional (recommended): +- Optional (recommended): - * [Docker](https://docs.docker.com/get-docker/) - * It is not mandatory because you can easily locally run tests against SQLite without it. - * It is practically mandatory if you want to locally run tests against any other database engine (MySQL, MariaDB, Postgres and MSSQL), unless you happen to have the engine installed and is willing to make some manual configuration. - * [Visual Studio Code](https://code.visualstudio.com/) - * [EditorConfig extension](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) - * Also run `npm install --global editorconfig` to make sure this extension will work properly - * [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + - [Docker](https://docs.docker.com/get-docker/) + - It is not mandatory because you can easily locally run tests against SQLite without it. + - It is practically mandatory if you want to locally run tests against any other database engine (MySQL, MariaDB, Postgres and MSSQL), unless you happen to have the engine installed and is willing to make some manual configuration. + - [Visual Studio Code](https://code.visualstudio.com/) + - [EditorConfig extension](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) + - Also run `npm install --global editorconfig` to make sure this extension will work properly + - [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) ### 1. Clone the repository -Clone the repository (if you haven't already) via `git clone https://github.com/sequelize/sequelize`. If you plan on submitting a pull request, you can create a fork by clicking the *fork* button and clone it instead with `git clone https://github.com/your-github-username/sequelize`, or add your fork as an upstream on the already cloned repo with `git remote add upstream https://github.com/your-github-username/sequelize`. +Clone the repository (if you haven't already) via `git clone https://github.com/sequelize/sequelize`. If you plan on submitting a pull request, you can create a fork by clicking the _fork_ button and clone it instead with `git clone https://github.com/your-github-username/sequelize`, or add your fork as an upstream on the already cloned repo with `git remote add upstream https://github.com/your-github-username/sequelize`. ### 2. Install the Node.js dependencies @@ -121,16 +119,16 @@ Run `npm install` (or `yarn install`) within the cloned repository folder. If you're happy to run tests only against an SQLite database, you can skip this section. -#### 3a. With Docker (recommended) +#### 3.1. With Docker (recommended) If you have Docker installed, use any of the following commands to start fresh local databases of the dialect of your choice: -* `npm run start-mariadb` -* `npm run start-mysql` -* `npm run start-postgres` -* `npm run start-mssql` +- `npm run start-mariadb` +- `npm run start-mysql` +- `npm run start-postgres` +- `npm run start-mssql` -*Note:* if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). +_Note:_ if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests (or an SSCCE). @@ -142,13 +140,10 @@ You can also easily start a local [pgadmin4](https://www.pgadmin.org/docs/pgadmi docker run -d --name pgadmin4 -p 8888:80 -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=sequelize_test' dpage/pgadmin4 ``` -#### 3b. Without Docker +#### 3.2. Without Docker You will have to manually install and configure each of database engines you want. Check the `dev/dialect-name` folder within this repository and look carefully at how it is defined via Docker and via the auxiliary bash script, and mimic that exactly (except for the database name, username, password, host and port, that you can customize via the `SEQ_DB`, `SEQ_USER`, `SEQ_PW`, `SEQ_HOST` and `SEQ_PORT` environment variables, respectively). - - - ### 4. Running tests Before starting any work, try to run the tests locally in order to be sure your setup is fine. Start by running the SQLite tests: @@ -159,10 +154,10 @@ npm run test-sqlite Then, if you want to run tests for another dialect, assuming you've set it up as written on section 3, run the corresponding command: -* `npm run test-mysql` -* `npm run test-mariadb` -* `npm run test-postgres` -* `npm run test-mssql` +- `npm run test-mysql` +- `npm run test-mariadb` +- `npm run test-postgres` +- `npm run test-mssql` There are also the `test-unit-*` and `test-integration-*` sets of npm scripts (for example, `test-integration-postgres`). @@ -176,18 +171,19 @@ Hint: if you're creating a new test, you can execute only that test locally agai DIALECT=mariadb npx mocha && DIALECT=mysql npx mocha && DIALECT=postgres npx mocha && DIALECT=sqlite npx mocha && DIALECT=mssql npx mocha ``` - ### 5. Running an SSCCE -You can modify the `sscce.js` file (at the root of the repository) to create an [SSCCE](http://www.sscce.org/). +What is SSCCE? [find out here](http://www.sscce.org/). + +You can modify the `sscce.js` file (at the root of the repository) to create an SSCCE. Run it for the dialect of your choice using one of the following commands: -* `npm run sscce-mariadb` -* `npm run sscce-mysql` -* `npm run sscce-postgres` -* `npm run sscce-sqlite` -* `npm run sscce-mssql` +- `npm run sscce-mariadb` +- `npm run sscce-mysql` +- `npm run sscce-postgres` +- `npm run sscce-sqlite` +- `npm run sscce-mssql` _Note:_ First, you need to set up (once) the database instance for corresponding dialect, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended). @@ -195,7 +191,6 @@ _Note:_ First, you need to set up (once) the database instance for corresponding If you open the `package.json` file with Visual Studio Code, you will find a small `debug` button rendered right above the `"scripts": {` line. By clicking it, a popup will appear where you can choose which npm script you want to debug. Select one of the `sscce-*` scripts (listed above) and VSCode will immediately launch your SSCCE in debug mode (meaning that it will stop on any breakpoints that you place within `sscce.js` or any other Sequelize source code). - ### 6. Commit your modifications Sequelize follows the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.em2hiij8p46d). The allowed categories are `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test` and `meta`. diff --git a/ENGINE.md b/ENGINE.md index 46b52458ce44..fe901fe7fd03 100644 --- a/ENGINE.md +++ b/ENGINE.md @@ -1,11 +1,11 @@ # Database Engine Support ## v6 -| Engine | Minimum supported version | -| :------------: | :------------: | -| PostgreSQL | [9.5](https://www.postgresql.org/docs/9.5/ ) | -| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | -| MariaDB | [10.3](https://mariadb.com/kb/en/changes-improvements-in-mariadb-103/) | -| Microsoft SQL Server | `12.0.2000` | -| SQLite | [3.0](https://www.sqlite.org/version3.html) | +| Engine | Minimum supported version | +| :------------------: | :--------------------------------------------------------------------: | +| PostgreSQL | [9.5](https://www.postgresql.org/docs/9.5/) | +| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | +| MariaDB | [10.3](https://mariadb.com/kb/en/changes-improvements-in-mariadb-103/) | +| Microsoft SQL Server | `12.0.2000` | +| SQLite | [3.0](https://www.sqlite.org/version3.html) | diff --git a/SECURITY.md b/SECURITY.md index f204f8e8e29e..5260ac0b1702 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,8 @@ The following table describes the versions of this project that are currently su | Version | Supported | | ------- | ------------------ | -| 6.x | :heavy_check_mark: | -| 5.x | :heavy_check_mark: | +| 6.x | :heavy_check_mark: | +| 5.x | :heavy_check_mark: | ## Responsible disclosure policy From 3be43deeb9a4e03cffb1d72ebc67a534a3c5dc19 Mon Sep 17 00:00:00 2001 From: Aman Jain Date: Wed, 3 Nov 2021 01:13:58 +0530 Subject: [PATCH 374/414] fix(model): clone options object instead of modifying (#13589) * fix(model): clone options object instead of modifying * fix(model): clone options object instead of modifying Co-authored-by: f[nZk] Co-authored-by: Sascha Depold --- lib/model.js | 1 + test/integration/model/bulk-create.test.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/model.js b/lib/model.js index 22bb01ead46b..706c058dec17 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2543,6 +2543,7 @@ class Model { const dialect = this.sequelize.options.dialect; const now = Utils.now(this.sequelize.options.dialect); + options = Utils.cloneDeep(options); options.model = this; diff --git a/test/integration/model/bulk-create.test.js b/test/integration/model/bulk-create.test.js index 4696d29e586a..e0af4fea371b 100644 --- a/test/integration/model/bulk-create.test.js +++ b/test/integration/model/bulk-create.test.js @@ -64,6 +64,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { await transaction.rollback(); }); } + + it('should not alter options', async function() { + const User = this.sequelize.define('User'); + await User.sync({ force: true }); + const options = { anOption: 1 }; + await User.bulkCreate([{ }], options); + expect(options).to.eql({ anOption: 1 }); + }); it('should be able to set createdAt and updatedAt if using silent: true', async function() { const User = this.sequelize.define('user', { From 73ecf6cf33628eca38973c0eeb5c798dbba177e9 Mon Sep 17 00:00:00 2001 From: l2ysho Date: Tue, 2 Nov 2021 20:51:07 +0100 Subject: [PATCH 375/414] fix(types): Add missing type definitions in models (#13553) * Add missing type definitions in models This commit fixes problem with type definitions for typescript > 4.4 Type definitions for [Op.eq] and [Op.is] added to model.d.ts * fix(types): Add missing type definitions in models This commit fixes problem with type definitions for typescript > 4.4 Type definitions for [Op.eq] and [Op.is] added to model.d.ts * fix(types): changes for PR Implement changes according to PR comments - remove duplicit Op.is definition - add test suite Co-authored-by: Richard Solar Co-authored-by: f[nZk] Co-authored-by: Constantin Metz <58604248+Keimeno@users.noreply.github.com> --- types/lib/model.d.ts | 6 +++++- types/test/where.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 46da75e54c29..a04f47b9b919 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -144,6 +144,10 @@ export interface WhereOperators { * * _PG only_ */ + + /** Example: `[Op.eq]: 6,` becomes `= 6` */ + [Op.eq]?: null | boolean | string | number | Literal | WhereOperators; + [Op.any]?: readonly (string | number | Literal)[] | Literal; /** Example: `[Op.gte]: 6,` becomes `>= 6` */ @@ -986,7 +990,7 @@ export interface SaveOptions extends Logging, Transactionable * @default true */ validate?: boolean; - + /** * A flag that defines if null values should be passed as values or not. * diff --git a/types/test/where.ts b/types/test/where.ts index 6748f30c88ce..037acbbe0b0b 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -44,6 +44,7 @@ expectTypeOf({ }).toMatchTypeOf>(); expectTypeOf({ + [Op.eq]: 6, // = 6 [Op.gt]: 6, // > 6 [Op.gte]: 6, // >= 6 [Op.lt]: 10, // < 10 From c2a793e60cd4f2d524d11697271e1cc7c64788c3 Mon Sep 17 00:00:00 2001 From: Deniz Kanmaz Date: Tue, 2 Nov 2021 21:55:17 +0200 Subject: [PATCH 376/414] fix(query-interface) does not provide existing fkeys (#13600) Co-authored-by: f[nZk] Co-authored-by: Sascha Depold --- lib/dialects/postgres/query-interface.js | 4 +- .../getForeignKeyReferencesForTable.test.js | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 test/integration/query-interface/getForeignKeyReferencesForTable.test.js diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 5aa0c0fde84d..19a07e467564 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -148,7 +148,7 @@ class PostgresQueryInterface extends QueryInterface { /** * @override */ - async getForeignKeyReferencesForTable(tableName, options) { + async getForeignKeyReferencesForTable(table, options) { const queryOptions = { ...options, type: QueryTypes.FOREIGNKEYS @@ -156,7 +156,7 @@ class PostgresQueryInterface extends QueryInterface { // postgres needs some special treatment as those field names returned are all lowercase // in order to keep same result with other dialects. - const query = this.queryGenerator.getForeignKeyReferencesQuery(tableName, this.sequelize.config.database); + const query = this.queryGenerator.getForeignKeyReferencesQuery(table.tableName || table, this.sequelize.config.database); const result = await this.sequelize.query(query, queryOptions); return result.map(Utils.camelizeObjectKeys); } diff --git a/test/integration/query-interface/getForeignKeyReferencesForTable.test.js b/test/integration/query-interface/getForeignKeyReferencesForTable.test.js new file mode 100644 index 000000000000..feacd9e8d457 --- /dev/null +++ b/test/integration/query-interface/getForeignKeyReferencesForTable.test.js @@ -0,0 +1,44 @@ +'use strict'; + +const chai = require('chai'); +const expect = chai.expect; +const Support = require('../support'); +const DataTypes = require('../../../lib/data-types'); + +describe(Support.getTestDialectTeaser('QueryInterface'), () => { + beforeEach(function() { + this.sequelize.options.quoteIdenifiers = true; + this.queryInterface = this.sequelize.getQueryInterface(); + }); + + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); + }); + + describe('getForeignKeyReferencesForTable', () => { + it('should be able to provide existing foreign keys', async function() { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), + User = this.sequelize.define('User', { username: DataTypes.STRING }); + + User.hasOne(Task); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + const expectedObject = { + columnName: 'UserId', + referencedColumnName: 'id', + referencedTableName: 'Users' + }; + + let refs = await this.queryInterface.getForeignKeyReferencesForTable({ tableName: 'Tasks' }); + expect(refs.length).to.equal(1); + expect(refs[0]).deep.include.all(expectedObject); + + refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1); + expect(refs[0]).deep.include.all(expectedObject); + + }); + }); +}); From a9c84d005dbe46529ced3cda47c79619951eb398 Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Wed, 3 Nov 2021 13:52:29 +0700 Subject: [PATCH 377/414] docs(engine): update MSSQL and PostgreSQL (#13586) * docs(engine): update MSSQL and PostgreSQL * build: remove probot-stale (#13595) * fix: dialect supported versions * docs(engine): revert postgresql to 9.5 * docs(engine): revert postgres dialect to 9.5.0 * revert Co-authored-by: Sascha Depold --- ENGINE.md | 14 +++--- lib/dialects/mariadb/index.js | 13 +++-- lib/dialects/mssql/index.js | 74 +++++++++++++++------------- lib/dialects/mysql/index.js | 66 +++++++++++++------------ lib/dialects/postgres/index.js | 90 ++++++++++++++++++---------------- lib/dialects/sqlite/index.js | 60 +++++++++++++---------- package.json | 2 +- 7 files changed, 174 insertions(+), 145 deletions(-) diff --git a/ENGINE.md b/ENGINE.md index fe901fe7fd03..456f69ef7cc1 100644 --- a/ENGINE.md +++ b/ENGINE.md @@ -2,10 +2,10 @@ ## v6 -| Engine | Minimum supported version | -| :------------------: | :--------------------------------------------------------------------: | -| PostgreSQL | [9.5](https://www.postgresql.org/docs/9.5/) | -| MySQL | [5.7](https://dev.mysql.com/doc/refman/5.7/en/) | -| MariaDB | [10.3](https://mariadb.com/kb/en/changes-improvements-in-mariadb-103/) | -| Microsoft SQL Server | `12.0.2000` | -| SQLite | [3.0](https://www.sqlite.org/version3.html) | +| Engine | Minimum supported version | +| :------------------: | :---------------------------------------------------------------------------------------: | +| PostgreSQL | [9.5.0](https://www.postgresql.org/docs/9.5/index.html) | +| MySQL | [5.7.0](https://dev.mysql.com/doc/refman/5.7/en/) | +| MariaDB | [10.1.44](https://mariadb.com/kb/en/changes-improvements-in-mariadb-101/) | +| Microsoft SQL Server | [SQL Server 2014 Express](https://www.microsoft.com/en-US/download/details.aspx?id=42299) | +| SQLite | [3.8.0](https://www.sqlite.org/version3.html) | diff --git a/lib/dialects/mariadb/index.js b/lib/dialects/mariadb/index.js index 6b6090f9cbe8..e1ff8dfb3224 100644 --- a/lib/dialects/mariadb/index.js +++ b/lib/dialects/mariadb/index.js @@ -17,12 +17,16 @@ class MariadbDialect extends AbstractDialect { _dialect: this, sequelize }); - this.queryInterface = new MySQLQueryInterface(sequelize, this.queryGenerator); + this.queryInterface = new MySQLQueryInterface( + sequelize, + this.queryGenerator + ); } } MariadbDialect.prototype.supports = _.merge( - _.cloneDeep(AbstractDialect.prototype.supports), { + _.cloneDeep(AbstractDialect.prototype.supports), + { 'VALUES ()': true, 'LIMIT ON UPDATE': true, lock: true, @@ -50,9 +54,10 @@ MariadbDialect.prototype.supports = _.merge( GEOMETRY: true, JSON: true, REGEXP: true - }); + } +); -MariadbDialect.prototype.defaultVersion = '10.1.44'; +MariadbDialect.prototype.defaultVersion = '10.1.44'; // minimum supported version MariadbDialect.prototype.Query = Query; MariadbDialect.prototype.QueryGenerator = QueryGenerator; MariadbDialect.prototype.DataTypes = DataTypes; diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 5653a2dda7f5..b8801e3abd47 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -17,44 +17,50 @@ class MssqlDialect extends AbstractDialect { _dialect: this, sequelize }); - this.queryInterface = new MSSqlQueryInterface(sequelize, this.queryGenerator); + this.queryInterface = new MSSqlQueryInterface( + sequelize, + this.queryGenerator + ); } } -MssqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'DEFAULT': true, - 'DEFAULT VALUES': true, - 'LIMIT ON UPDATE': true, - 'ORDER NULLS': false, - lock: false, - transactions: true, - migrations: false, - returnValues: { - output: true - }, - schemas: true, - autoIncrement: { - identityInsert: true, - defaultValue: false, - update: false - }, - constraints: { - restrict: false, - default: true - }, - index: { - collate: false, - length: false, - parser: false, - type: true, - using: false, - where: true - }, - NUMERIC: true, - tmpTableTrigger: true -}); +MssqlDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + DEFAULT: true, + 'DEFAULT VALUES': true, + 'LIMIT ON UPDATE': true, + 'ORDER NULLS': false, + lock: false, + transactions: true, + migrations: false, + returnValues: { + output: true + }, + schemas: true, + autoIncrement: { + identityInsert: true, + defaultValue: false, + update: false + }, + constraints: { + restrict: false, + default: true + }, + index: { + collate: false, + length: false, + parser: false, + type: true, + using: false, + where: true + }, + NUMERIC: true, + tmpTableTrigger: true + } +); -MssqlDialect.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express +MssqlDialect.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express, minimum supported version MssqlDialect.prototype.Query = Query; MssqlDialect.prototype.name = 'mssql'; MssqlDialect.prototype.TICK_CHAR = '"'; diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js index 6b3f9cb313a7..a9850b11e83b 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -17,40 +17,46 @@ class MysqlDialect extends AbstractDialect { _dialect: this, sequelize }); - this.queryInterface = new MySQLQueryInterface(sequelize, this.queryGenerator); + this.queryInterface = new MySQLQueryInterface( + sequelize, + this.queryGenerator + ); } } -MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'VALUES ()': true, - 'LIMIT ON UPDATE': true, - lock: true, - forShare: 'LOCK IN SHARE MODE', - settingIsolationLevelDuringTransaction: false, - inserts: { - ignoreDuplicates: ' IGNORE', - updateOnDuplicate: ' ON DUPLICATE KEY UPDATE' - }, - index: { - collate: false, - length: true, - parser: true, - type: true, - using: 1 - }, - constraints: { - dropConstraint: false, - check: false - }, - indexViaAlter: true, - indexHints: true, - NUMERIC: true, - GEOMETRY: true, - JSON: true, - REGEXP: true -}); +MysqlDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + 'VALUES ()': true, + 'LIMIT ON UPDATE': true, + lock: true, + forShare: 'LOCK IN SHARE MODE', + settingIsolationLevelDuringTransaction: false, + inserts: { + ignoreDuplicates: ' IGNORE', + updateOnDuplicate: ' ON DUPLICATE KEY UPDATE' + }, + index: { + collate: false, + length: true, + parser: true, + type: true, + using: 1 + }, + constraints: { + dropConstraint: false, + check: false + }, + indexViaAlter: true, + indexHints: true, + NUMERIC: true, + GEOMETRY: true, + JSON: true, + REGEXP: true + } +); -MysqlDialect.prototype.defaultVersion = '5.7.0'; +MysqlDialect.prototype.defaultVersion = '5.7.0'; // minimum supported version MysqlDialect.prototype.Query = Query; MysqlDialect.prototype.QueryGenerator = QueryGenerator; MysqlDialect.prototype.DataTypes = DataTypes; diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index 3d23f6ed6c6d..562939ad7414 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -17,52 +17,58 @@ class PostgresDialect extends AbstractDialect { _dialect: this, sequelize }); - this.queryInterface = new PostgresQueryInterface(sequelize, this.queryGenerator); + this.queryInterface = new PostgresQueryInterface( + sequelize, + this.queryGenerator + ); } } -PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'DEFAULT VALUES': true, - 'EXCEPTION': true, - 'ON DUPLICATE KEY': false, - 'ORDER NULLS': true, - returnValues: { - returning: true - }, - bulkDefault: true, - schemas: true, - lock: true, - lockOf: true, - lockKey: true, - lockOuterJoinFailure: true, - skipLocked: true, - forShare: 'FOR SHARE', - index: { - concurrently: true, - using: 2, - where: true, - functionBased: true, - operator: true - }, - inserts: { - onConflictDoNothing: ' ON CONFLICT DO NOTHING', - updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' - }, - NUMERIC: true, - ARRAY: true, - RANGE: true, - GEOMETRY: true, - REGEXP: true, - GEOGRAPHY: true, - JSON: true, - JSONB: true, - HSTORE: true, - TSVECTOR: true, - deferrableConstraints: true, - searchPath: true -}); +PostgresDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + 'DEFAULT VALUES': true, + EXCEPTION: true, + 'ON DUPLICATE KEY': false, + 'ORDER NULLS': true, + returnValues: { + returning: true + }, + bulkDefault: true, + schemas: true, + lock: true, + lockOf: true, + lockKey: true, + lockOuterJoinFailure: true, + skipLocked: true, + forShare: 'FOR SHARE', + index: { + concurrently: true, + using: 2, + where: true, + functionBased: true, + operator: true + }, + inserts: { + onConflictDoNothing: ' ON CONFLICT DO NOTHING', + updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' + }, + NUMERIC: true, + ARRAY: true, + RANGE: true, + GEOMETRY: true, + REGEXP: true, + GEOGRAPHY: true, + JSON: true, + JSONB: true, + HSTORE: true, + TSVECTOR: true, + deferrableConstraints: true, + searchPath: true + } +); -PostgresDialect.prototype.defaultVersion = '9.5.0'; +PostgresDialect.prototype.defaultVersion = '9.5.0'; // minimum supported version PostgresDialect.prototype.Query = Query; PostgresDialect.prototype.DataTypes = DataTypes; PostgresDialect.prototype.name = 'postgres'; diff --git a/lib/dialects/sqlite/index.js b/lib/dialects/sqlite/index.js index 1ed11dd20ae5..fe8cb352603c 100644 --- a/lib/dialects/sqlite/index.js +++ b/lib/dialects/sqlite/index.js @@ -18,37 +18,43 @@ class SqliteDialect extends AbstractDialect { sequelize }); - this.queryInterface = new SQLiteQueryInterface(sequelize, this.queryGenerator); + this.queryInterface = new SQLiteQueryInterface( + sequelize, + this.queryGenerator + ); } } -SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'DEFAULT': false, - 'DEFAULT VALUES': true, - 'UNION ALL': false, - 'RIGHT JOIN': false, - inserts: { - ignoreDuplicates: ' OR IGNORE', - updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' - }, - index: { - using: false, - where: true, - functionBased: true - }, - transactionOptions: { - type: true - }, - constraints: { - addConstraint: false, - dropConstraint: false - }, - joinTableDependent: false, - groupedLimit: false, - JSON: true -}); +SqliteDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + DEFAULT: false, + 'DEFAULT VALUES': true, + 'UNION ALL': false, + 'RIGHT JOIN': false, + inserts: { + ignoreDuplicates: ' OR IGNORE', + updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' + }, + index: { + using: false, + where: true, + functionBased: true + }, + transactionOptions: { + type: true + }, + constraints: { + addConstraint: false, + dropConstraint: false + }, + joinTableDependent: false, + groupedLimit: false, + JSON: true + } +); -SqliteDialect.prototype.defaultVersion = '3.8.0'; +SqliteDialect.prototype.defaultVersion = '3.8.0'; // minimum supported version SqliteDialect.prototype.Query = Query; SqliteDialect.prototype.DataTypes = DataTypes; SqliteDialect.prototype.name = 'sqlite'; diff --git a/package.json b/package.json index f5427dc330cc..25d26387e8f6 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,7 @@ }, "scripts": { "----------------------------------------- static analysis -----------------------------------------": "", - "lint": "eslint lib test --quiet", + "lint": "eslint lib test --quiet --fix", "lint-docs": "markdownlint docs", "test-typings": "tsc -b types/tsconfig.json && tsc -b types/test/tsconfig.json", "----------------------------------------- documentation -------------------------------------------": "", From abaf3cd2557025d6c7b6b07424d7cd3e73989bb5 Mon Sep 17 00:00:00 2001 From: "f[nZk]" Date: Wed, 3 Nov 2021 14:49:46 +0700 Subject: [PATCH 378/414] docs(contact): update contributors emails (#13622) --- CONTACT.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTACT.md b/CONTACT.md index 0f7c3b636c8d..6c2f782067f1 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -5,6 +5,5 @@ You can use the information below to contact maintainers directly. We will try t ### Via Email -- **Pedro Augusto de Paula Barbosa** papb1996@gmail.com -- **Jan Aagaard Meier** janzeh@gmail.com - **Sascha Depold** sascha@depold.com +- **Fauzan** fncolon@pm.me From b446ada0f28d4ed2244b7f360441dbc8e8eacb63 Mon Sep 17 00:00:00 2001 From: Mattia Malonni Date: Thu, 4 Nov 2021 07:22:18 +0100 Subject: [PATCH 379/414] Add sequelize-bcrypt plugin (#13623) --- docs/manual/other-topics/resources.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/resources.md b/docs/manual/other-topics/resources.md index a90b3d40773c..eec043cae6d6 100644 --- a/docs/manual/other-topics/resources.md +++ b/docs/manual/other-topics/resources.md @@ -21,6 +21,10 @@ * [sequelize-autoload](https://github.com/boxsnake-nodejs/sequelize-autoload) - An autoloader for Sequelize, inspired by [PSR-0](https://www.php-fig.org/psr/psr-0/) and [PSR-4](https://www.php-fig.org/psr/psr-4/). +### Bcrypt + +* [sequelize-bcrypt](https://github.com/mattiamalonni/sequelize-bcrypt) - Utility to integrate bcrypt into sequelize models + ### Caching * [sequelize-transparent-cache](https://github.com/DanielHreben/sequelize-transparent-cache) @@ -60,4 +64,4 @@ * [sequelize-deep-update](https://www.npmjs.com/package/sequelize-deep-update) - Update a sequelize instance and its included associated instances with new properties. * [sequelize-noupdate-attributes](https://www.npmjs.com/package/sequelize-noupdate-attributes) - Adds no update/readonly attributes support to models. * [sequelize-joi](https://www.npmjs.com/package/sequelize-joi) - Allows specifying [Joi](https://github.com/hapijs/joi) validation schema for JSONB model attributes in Sequelize. -* [sqlcommenter-sequelize](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Sequelize](https://google.github.io/sqlcommenter/node/sequelize/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements. \ No newline at end of file +* [sqlcommenter-sequelize](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Sequelize](https://google.github.io/sqlcommenter/node/sequelize/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements. From 02a58657cecad8e32286831b38a413aaa80b8702 Mon Sep 17 00:00:00 2001 From: Mattia Malonni Date: Thu, 4 Nov 2021 10:25:59 +0100 Subject: [PATCH 380/414] docs: sequelize-joi ownership change (#13624) --- docs/manual/other-topics/resources.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/manual/other-topics/resources.md b/docs/manual/other-topics/resources.md index eec043cae6d6..c5ae37d5c28d 100644 --- a/docs/manual/other-topics/resources.md +++ b/docs/manual/other-topics/resources.md @@ -47,6 +47,10 @@ * [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) - Temporal tables (aka historical records) +### Joi + +* [sequelize-joi](https://github.com/mattiamalonni/sequelize-joi) - Allows specifying [Joi](https://github.com/sideway/joi) validation schema for model attributes in Sequelize. + ### Migrations * [umzug](https://github.com/sequelize/umzug) @@ -63,5 +67,4 @@ * [sequelize-deep-update](https://www.npmjs.com/package/sequelize-deep-update) - Update a sequelize instance and its included associated instances with new properties. * [sequelize-noupdate-attributes](https://www.npmjs.com/package/sequelize-noupdate-attributes) - Adds no update/readonly attributes support to models. -* [sequelize-joi](https://www.npmjs.com/package/sequelize-joi) - Allows specifying [Joi](https://github.com/hapijs/joi) validation schema for JSONB model attributes in Sequelize. * [sqlcommenter-sequelize](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Sequelize](https://google.github.io/sqlcommenter/node/sequelize/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements. From b1fb1f32f7d66c013bbf015345a1076893ffd806 Mon Sep 17 00:00:00 2001 From: lzc <92916529+liangzhicheng0423@users.noreply.github.com> Date: Fri, 5 Nov 2021 16:01:32 +0800 Subject: [PATCH 381/414] fix(types): include 'paranoid' in IncludeThroughOptions definition (#13625) Co-authored-by: zhicheng.liang --- lib/model.js | 1 + types/lib/model.d.ts | 7 +++++++ types/test/model.ts | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/lib/model.js b/lib/model.js index 706c058dec17..3ac585242bf5 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1655,6 +1655,7 @@ class Model { * @param {boolean} [options.include[].separate] If true, runs a separate query to fetch the associated instances, only supported for hasMany associations * @param {number} [options.include[].limit] Limit the joined rows, only supported with include.separate=true * @param {string} [options.include[].through.as] The alias for the join model, in case you want to give it a different name than the default one. + * @param {boolean} [options.include[].through.paranoid] If true, only non-deleted records will be returned from the join table. If false, both deleted and non-deleted records will be returned. Only applies if through model is paranoid. * @param {object} [options.include[].through.where] Filter on the join model for belongsToMany relations * @param {Array} [options.include[].through.attributes] A list of attributes to select from the join model for belongsToMany relations * @param {Array} [options.include[].include] Load further nested related models diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index a04f47b9b919..968cf3bc4cb0 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -381,6 +381,13 @@ export interface IncludeThroughOptions extends Filterable, Projectable { * `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural */ as?: string; + + /** + * If true, only non-deleted records will be returned from the join table. + * If false, both deleted and non-deleted records will be returned. + * Only applies if through model is paranoid. + */ + paranoid?: boolean; } /** diff --git a/types/test/model.ts b/types/test/model.ts index af7cbf89d8e8..d63b56eff1f5 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -30,6 +30,10 @@ MyModel.findOne({ ] }); +MyModel.findOne({ + include: [ { through: { paranoid: true } } ] +}); + MyModel.findOne({ include: [ { model: OtherModel, paranoid: true } From 73d99ab45c069119478d8ef39ff9391181d5578f Mon Sep 17 00:00:00 2001 From: Marces Engel Date: Fri, 5 Nov 2021 09:18:24 +0100 Subject: [PATCH 382/414] fix(mssql): fix sub query issue occurring with renamed primary key fields (#12801) * fix(mssql): add test for hasOne with a primary key with specified field * fix(mssql): use aliased column name for attributes Use proper aliased column names for sub query attributes when parent is top Co-authored-by: Marces Engel --- lib/dialects/abstract/query-generator.js | 3 ++- test/integration/associations/has-one.test.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index e6e9e02df1a4..aa28a6b8d921 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1698,7 +1698,8 @@ class QueryGenerator { joinOn = this._getAliasForField(tableName, attrLeft, topLevelInfo.options) || `${tableName}.${this.quoteIdentifier(attrLeft)}`; if (topLevelInfo.subQuery) { - subqueryAttributes.push(`${tableName}.${this.quoteIdentifier(fieldLeft)}`); + const dbIdentifier = `${tableName}.${this.quoteIdentifier(fieldLeft)}`; + subqueryAttributes.push(dbIdentifier !== joinOn ? `${dbIdentifier} AS ${this.quoteIdentifier(attrLeft)}` : dbIdentifier); } } else { const joinSource = `${asLeft.replace(/->/g, '.')}.${attrLeft}`; diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index 81f7b97f5665..99697f7a86af 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -354,6 +354,24 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { expect(task.UserXYZ).to.exist; }); + + it('should support custom primary key field name in sub queries', async function() { + const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }), + Task = this.sequelize.define('TaskXYZ', { id: { + field: 'Id', + type: Sequelize.INTEGER, + autoIncrement: true, + primaryKey: true + }, title: Sequelize.STRING, status: Sequelize.STRING }); + + Task.hasOne(User); + + await Task.sync({ force: true }); + await User.sync({ force: true }); + + const task0 = await Task.create({ title: 'task', status: 'inactive', User: { username: 'foo', gender: 'male' } }, { include: User }); + await expect(task0.reload({ subQuery: true })).to.not.eventually.be.rejected; + }); }); describe('foreign key constraints', () => { From 45d30d8a27592cc8d457f7bcb67079b93fc242e2 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 6 Nov 2021 13:11:05 +0100 Subject: [PATCH 383/414] docs(logo): add svg logo versions * Add vectorized version of logo Fixes #12844 * docs(logo): add simple svg logo Co-authored-by: Sascha Depold --- docs/images/logo-simple.svg | 1 + docs/images/logo.svg | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 docs/images/logo-simple.svg create mode 100644 docs/images/logo.svg diff --git a/docs/images/logo-simple.svg b/docs/images/logo-simple.svg new file mode 100644 index 000000000000..f8599b6c50f0 --- /dev/null +++ b/docs/images/logo-simple.svg @@ -0,0 +1 @@ +Sequelize diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100644 index 000000000000..0ee676a33cc8 --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1,41 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d4f7558e6f9e04db52b440399d1d67a8cd46e46c Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 6 Nov 2021 17:55:56 +0100 Subject: [PATCH 384/414] meta(dependencies): upgrade validator dependency (#13629) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 25d26387e8f6..87a6a7f4b1a2 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "sequelize-pool": "^6.0.0", "toposort-class": "^1.0.1", "uuid": "^8.1.0", - "validator": "^13.6.0", + "validator": "^13.7.0", "wkx": "^0.5.0" }, "devDependencies": { From 1e17382d892ab75d92e53045bbb771653169ae42 Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Sat, 6 Nov 2021 17:56:39 +0100 Subject: [PATCH 385/414] docs(data-types): fix reference to DataTypes.NOW * docs(data-types): fix reference to DataTypes.NOW --- docs/manual/core-concepts/model-basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md index f1662ab2d52a..ca2733f6d7d9 100644 --- a/docs/manual/core-concepts/model-basics.md +++ b/docs/manual/core-concepts/model-basics.md @@ -241,13 +241,13 @@ sequelize.define('User', { }); ``` -Some special values, such as `Sequelize.NOW`, are also accepted: +Some special values, such as `DataTypes.NOW`, are also accepted: ```js sequelize.define('Foo', { bar: { type: DataTypes.DATETIME, - defaultValue: Sequelize.NOW + defaultValue: DataTypes.NOW // This way, the current date/time will be used to populate this column (at the moment of insertion) } }); From 3cca8a278d6fdf59fa41f9e2e9bc78a00d88f2b8 Mon Sep 17 00:00:00 2001 From: Constantin Metz Date: Sun, 7 Nov 2021 07:43:37 +0100 Subject: [PATCH 386/414] meta: persist lockfile (#13632) --- .github/workflows/ci.yml | 40 +- .gitignore | 1 - yarn.lock | 7913 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 7933 insertions(+), 21 deletions(-) create mode 100644 yarn.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e64716b3a780..ed20f83a1558 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,9 @@ jobs: - uses: actions/setup-node@v1 with: node-version: 12.x - - run: npm install - - run: npm run lint - - run: npm run lint-docs + - run: yarn install --frozen-lockfile --ignore-engines + - run: yarn lint + - run: yarn lint-docs test-typings: strategy: fail-fast: false @@ -30,9 +30,9 @@ jobs: - uses: actions/setup-node@v1 with: node-version: 12.x - - run: npm install - - run: npm install --save-dev typescript@~${{ matrix.ts-version }} - - run: npm run test-typings + - run: yarn install --frozen-lockfile --ignore-engines + - run: yarn add --dev typescript@~${{ matrix.ts-version }} --ignore-engines + - run: yarn test-typings test-sqlite: strategy: fail-fast: false @@ -47,11 +47,11 @@ jobs: - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - run: npm install + - run: yarn install --frozen-lockfile --ignore-engines - name: Unit Tests - run: npm run test-unit + run: yarn test-unit - name: Integration Tests - run: npm run test-integration + run: yarn test-integration test-postgres: strategy: fail-fast: false @@ -82,14 +82,14 @@ jobs: - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm install pg-native + - run: yarn install --frozen-lockfile --ignore-engines + - run: yarn add pg-native --ignore-engines if: matrix.native - name: Unit Tests - run: npm run test-unit + run: yarn test-unit if: ${{ !matrix.minify-aliases }} - name: Integration Tests - run: npm run test-integration + run: yarn test-integration test-mysql-mariadb: strategy: fail-fast: false @@ -141,11 +141,11 @@ jobs: - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - run: npm install + - run: yarn install --frozen-lockfile --ignore-engines - name: Unit Tests - run: npm run test-unit + run: yarn test-unit - name: Integration Tests - run: npm run test-integration + run: yarn test-integration test-mssql: strategy: fail-fast: false @@ -179,11 +179,11 @@ jobs: - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - run: npm install + - run: yarn install --frozen-lockfile --ignore-engines - name: Unit Tests - run: npm run test-unit + run: yarn test-unit - name: Integration Tests - run: npm run test-integration + run: yarn test-integration release: name: Release runs-on: ubuntu-latest @@ -205,5 +205,5 @@ jobs: - uses: actions/setup-node@v1 with: node-version: 12.x - - run: npm install + - run: yarn install --frozen-lockfile --ignore-engines - run: npx semantic-release diff --git a/.gitignore b/.gitignore index b47ee9c01b3e..3c8f2947202c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ npm-debug.log* *~ test.sqlite *.sublime* -yarn.lock .nyc_output coverage-* diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000000..9ae66cb7e924 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,7913 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@azure/abort-controller@^1.0.0": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd" + integrity sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw== + dependencies: + tslib "^2.0.0" + +"@azure/core-auth@^1.1.4": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" + integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/ms-rest-azure-env@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz#8505873afd4a1227ec040894a64fdd736b4a101f" + integrity sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA== + +"@azure/ms-rest-js@^1.8.7": + version "1.11.2" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.11.2.tgz#e83d512b102c302425da5ff03a6d76adf2aa4ae6" + integrity sha512-2AyQ1IKmLGKW7DU3/x3TsTBzZLcbC9YRI+yuDPuXAQrv3zar340K9wsxU413kHFIDjkWNCo9T0w5VtwcyWxhbQ== + dependencies: + "@azure/core-auth" "^1.1.4" + axios "^0.21.1" + form-data "^2.3.2" + tough-cookie "^2.4.3" + tslib "^1.9.2" + tunnel "0.0.6" + uuid "^3.2.1" + xml2js "^0.4.19" + +"@azure/ms-rest-nodeauth@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-2.0.2.tgz#037e29540c5625eaec718b8fcc178dd7ad5dfb96" + integrity sha512-KmNNICOxt3EwViAJI3iu2VH8t8BQg5J2rSAyO4IUYLF9ZwlyYsP419pdvl4NBUhluAP2cgN7dfD2V6E6NOMZlQ== + dependencies: + "@azure/ms-rest-azure-env" "^1.1.2" + "@azure/ms-rest-js" "^1.8.7" + adal-node "^0.1.28" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== + dependencies: + "@babel/highlight" "^7.16.0" + +"@babel/compat-data@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.0.tgz#ea269d7f78deb3a7826c39a4048eecda541ebdaa" + integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew== + +"@babel/core@^7.7.5": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4" + integrity sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-compilation-targets" "^7.16.0" + "@babel/helper-module-transforms" "^7.16.0" + "@babel/helpers" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2" + integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew== + dependencies: + "@babel/types" "^7.16.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-compilation-targets@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.0.tgz#01d615762e796c17952c29e3ede9d6de07d235a8" + integrity sha512-S7iaOT1SYlqK0sQaCi21RX4+13hmdmnxIEAnQUB/eh7GeAnRjOUgTYpLkUOiRXzD+yog1JxP0qyAQZ7ZxVxLVg== + dependencies: + "@babel/compat-data" "^7.16.0" + "@babel/helper-validator-option" "^7.14.5" + browserslist "^4.16.6" + semver "^6.3.0" + +"@babel/helper-function-name@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== + dependencies: + "@babel/helper-get-function-arity" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-get-function-arity@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-hoist-variables@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-member-expression-to-functions@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4" + integrity sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-imports@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-transforms@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5" + integrity sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA== + dependencies: + "@babel/helper-module-imports" "^7.16.0" + "@babel/helper-replace-supers" "^7.16.0" + "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-validator-identifier" "^7.15.7" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-optimise-call-expression@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" + integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-replace-supers@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz#73055e8d3cf9bcba8ddb55cad93fedc860f68f17" + integrity sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.16.0" + "@babel/helper-optimise-call-expression" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-simple-access@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" + integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-split-export-declaration@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-validator-identifier@^7.15.7": + version "7.15.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== + +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helpers@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.0.tgz#875519c979c232f41adfbd43a3b0398c2e388183" + integrity sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ== + dependencies: + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/highlight@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.0": + version "7.16.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.2.tgz#3723cd5c8d8773eef96ce57ea1d9b7faaccd12ac" + integrity sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw== + +"@babel/runtime@^7.11.2": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.0.tgz#e27b977f2e2088ba24748bf99b5e1dece64e4f0b" + integrity sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/traverse@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.0.tgz#965df6c6bfc0a958c1e739284d3c9fa4a6e3c45b" + integrity sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + to-fast-properties "^2.0.0" + +"@commitlint/cli@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-11.0.0.tgz#698199bc52afed50aa28169237758fa14a67b5d3" + integrity sha512-YWZWg1DuqqO5Zjh7vUOeSX76vm0FFyz4y0cpGMFhrhvUi5unc4IVfCXZ6337R9zxuBtmveiRuuhQqnRRer+13g== + dependencies: + "@babel/runtime" "^7.11.2" + "@commitlint/format" "^11.0.0" + "@commitlint/lint" "^11.0.0" + "@commitlint/load" "^11.0.0" + "@commitlint/read" "^11.0.0" + chalk "4.1.0" + core-js "^3.6.1" + get-stdin "8.0.0" + lodash "^4.17.19" + resolve-from "5.0.0" + resolve-global "1.0.0" + yargs "^15.1.0" + +"@commitlint/config-angular-type-enum@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-angular-type-enum/-/config-angular-type-enum-11.0.0.tgz#7a7f6982e45d3696d72eb343a5d1dc23b2f003e0" + integrity sha512-dSyxdkU36aEgDUWBSiM5lsZ/h2K7uCyKf+A5Sf3+Z5JhcLD9GzTo5W+c8KgwTBdL39dkL7sN+EVgsXNjW99pJg== + +"@commitlint/config-angular@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-angular/-/config-angular-11.0.0.tgz#c1cc1dd902a4b9d2a5c072ff38e3ace9be7138e0" + integrity sha512-H8QSEOmfRsPW0Iehid5fY7NZ2HXmyKC6Q83MLFf9KRnmCcbgJtH+faECtqlvPntayO3CYbA4UenIerOaQ0vOAg== + dependencies: + "@commitlint/config-angular-type-enum" "^11.0.0" + +"@commitlint/ensure@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-11.0.0.tgz#3e796b968ab5b72bc6f8a6040076406306c987fb" + integrity sha512-/T4tjseSwlirKZdnx4AuICMNNlFvRyPQimbZIOYujp9DSO6XRtOy9NrmvWujwHsq9F5Wb80QWi4WMW6HMaENug== + dependencies: + "@commitlint/types" "^11.0.0" + lodash "^4.17.19" + +"@commitlint/execute-rule@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-11.0.0.tgz#3ed60ab7a33019e58d90e2d891b75d7df77b4b4d" + integrity sha512-g01p1g4BmYlZ2+tdotCavrMunnPFPhTzG1ZiLKTCYrooHRbmvqo42ZZn4QMStUEIcn+jfLb6BRZX3JzIwA1ezQ== + +"@commitlint/format@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-11.0.0.tgz#ac47b0b9ca46540c0082c721b290794e67bdc51b" + integrity sha512-bpBLWmG0wfZH/svzqD1hsGTpm79TKJWcf6EXZllh2J/LSSYKxGlv967lpw0hNojme0sZd4a/97R3qA2QHWWSLg== + dependencies: + "@commitlint/types" "^11.0.0" + chalk "^4.0.0" + +"@commitlint/is-ignored@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-11.0.0.tgz#7b803eda56276dbe7fec51eb1510676198468f39" + integrity sha512-VLHOUBN+sOlkYC4tGuzE41yNPO2w09sQnOpfS+pSPnBFkNUUHawEuA44PLHtDvQgVuYrMAmSWFQpWabMoP5/Xg== + dependencies: + "@commitlint/types" "^11.0.0" + semver "7.3.2" + +"@commitlint/lint@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-11.0.0.tgz#01e062cd1b0e7c3d756aa2c246462e0b6a3348a4" + integrity sha512-Q8IIqGIHfwKr8ecVZyYh6NtXFmKw4YSEWEr2GJTB/fTZXgaOGtGFZDWOesCZllQ63f1s/oWJYtVv5RAEuwN8BQ== + dependencies: + "@commitlint/is-ignored" "^11.0.0" + "@commitlint/parse" "^11.0.0" + "@commitlint/rules" "^11.0.0" + "@commitlint/types" "^11.0.0" + +"@commitlint/load@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-11.0.0.tgz#f736562f0ffa7e773f8808fea93319042ee18211" + integrity sha512-t5ZBrtgvgCwPfxmG811FCp39/o3SJ7L+SNsxFL92OR4WQxPcu6c8taD0CG2lzOHGuRyuMxZ7ps3EbngT2WpiCg== + dependencies: + "@commitlint/execute-rule" "^11.0.0" + "@commitlint/resolve-extends" "^11.0.0" + "@commitlint/types" "^11.0.0" + chalk "4.1.0" + cosmiconfig "^7.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + +"@commitlint/message@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-11.0.0.tgz#83554c3cbbc884fd07b473593bc3e94bcaa3ee05" + integrity sha512-01ObK/18JL7PEIE3dBRtoMmU6S3ecPYDTQWWhcO+ErA3Ai0KDYqV5VWWEijdcVafNpdeUNrEMigRkxXHQLbyJA== + +"@commitlint/parse@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-11.0.0.tgz#d18b08cf67c35d02115207d7009306a2e8e7c901" + integrity sha512-DekKQAIYWAXIcyAZ6/PDBJylWJ1BROTfDIzr9PMVxZRxBPc1gW2TG8fLgjZfBP5mc0cuthPkVi91KQQKGri/7A== + dependencies: + conventional-changelog-angular "^5.0.0" + conventional-commits-parser "^3.0.0" + +"@commitlint/read@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-11.0.0.tgz#f24240548c63587bba139fa5a364cab926077016" + integrity sha512-37V0V91GSv0aDzMzJioKpCoZw6l0shk7+tRG8RkW1GfZzUIytdg3XqJmM+IaIYpaop0m6BbZtfq+idzUwJnw7g== + dependencies: + "@commitlint/top-level" "^11.0.0" + fs-extra "^9.0.0" + git-raw-commits "^2.0.0" + +"@commitlint/resolve-extends@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-11.0.0.tgz#158ecbe27d4a2a51d426111a01478e216fbb1036" + integrity sha512-WinU6Uv6L7HDGLqn/To13KM1CWvZ09VHZqryqxXa1OY+EvJkfU734CwnOEeNlSCK7FVLrB4kmodLJtL1dkEpXw== + dependencies: + import-fresh "^3.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + +"@commitlint/rules@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-11.0.0.tgz#bdb310cc6fc55c9f8d7d917a22b69055c535c375" + integrity sha512-2hD9y9Ep5ZfoNxDDPkQadd2jJeocrwC4vJ98I0g8pNYn/W8hS9+/FuNpolREHN8PhmexXbkjrwyQrWbuC0DVaA== + dependencies: + "@commitlint/ensure" "^11.0.0" + "@commitlint/message" "^11.0.0" + "@commitlint/to-lines" "^11.0.0" + "@commitlint/types" "^11.0.0" + +"@commitlint/to-lines@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/to-lines/-/to-lines-11.0.0.tgz#86dea151c10eea41e39ea96fa4de07839258a7fe" + integrity sha512-TIDTB0Y23jlCNubDROUVokbJk6860idYB5cZkLWcRS9tlb6YSoeLn1NLafPlrhhkkkZzTYnlKYzCVrBNVes1iw== + +"@commitlint/top-level@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/top-level/-/top-level-11.0.0.tgz#bb2d1b6e5ed3be56874633b59e1f7de118c32783" + integrity sha512-O0nFU8o+Ws+py5pfMQIuyxOtfR/kwtr5ybqTvR+C2lUPer2x6lnQU+OnfD7hPM+A+COIUZWx10mYQvkR3MmtAA== + dependencies: + find-up "^5.0.0" + +"@commitlint/types@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-11.0.0.tgz#719cf05fcc1abb6533610a2e0f5dd1e61eac14fe" + integrity sha512-VoNqai1vR5anRF5Tuh/+SWDFk7xi7oMwHrHrbm1BprYXjB2RJsWLhUrStMssDxEl5lW/z3EUdg8RvH/IUBccSQ== + +"@gar/promisify@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== + +"@isaacs/string-locale-compare@*", "@isaacs/string-locale-compare@^1.0.1": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@js-joda/core@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-2.0.0.tgz#e9a351ee6feb91262770e2a3d085aa0219ad6afb" + integrity sha512-OWm/xa9O9e4ugzNHoRT3IsXZZYfaV6Ia1aRwctOmCQ2GYWMnhKBzMC1WomqCh/oGxEZKNtPy5xv5//VIAOgMqw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/arborist@*", "@npmcli/arborist@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-4.0.4.tgz#a532a7cc430ccbd87c0595a8828f9614f29d2dac" + integrity sha512-5hRkiHF9zu62z6a7CJqhVG5CFUVnbYqvrrcxxEmhxFgyH2ovICyULOrj7nF4VBlfzp7OPu/rveV2ts9iYrn74g== + dependencies: + "@isaacs/string-locale-compare" "^1.0.1" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^2.0.0" + "@npmcli/metavuln-calculator" "^2.0.0" + "@npmcli/move-file" "^1.1.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^1.0.1" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^2.0.0" + bin-links "^2.3.0" + cacache "^15.0.3" + common-ancestor-path "^1.0.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.0" + npm-registry-fetch "^11.0.0" + pacote "^12.0.0" + parse-conflict-json "^1.1.1" + proc-log "^1.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + treeverse "^1.0.4" + walk-up-path "^1.0.0" + +"@npmcli/ci-detect@*", "@npmcli/ci-detect@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz#18478bbaa900c37bfbd8a2006a6262c62e8b0fe1" + integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== + +"@npmcli/config@*": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-2.3.1.tgz#41d80ce272831461b5cb158afa110525d4be0fed" + integrity sha512-F/8R/Zqun8682TgaCILUNoaVfd1LVaYZ/jcVt9KWzfKpzcPus1zEApAl54PqVqVJbNq6f01QTDQHD6L/n56BXw== + dependencies: + ini "^2.0.0" + mkdirp-infer-owner "^2.0.0" + nopt "^5.0.0" + semver "^7.3.4" + walk-up-path "^1.0.0" + +"@npmcli/disparity-colors@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/disparity-colors/-/disparity-colors-1.0.1.tgz#b23c864c9658f9f0318d5aa6d17986619989535c" + integrity sha512-kQ1aCTTU45mPXN+pdAaRxlxr3OunkyztjbbxDY/aIcPS5CnCUrx+1+NvA6pTcYR7wmLZe37+Mi5v3nfbwPxq3A== + dependencies: + ansi-styles "^4.3.0" + +"@npmcli/fs@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" + integrity sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/git@^2.0.7", "@npmcli/git@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" + integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== + dependencies: + "@npmcli/promise-spawn" "^1.3.2" + lru-cache "^6.0.0" + mkdirp "^1.0.4" + npm-pick-manifest "^6.1.1" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^2.0.2" + +"@npmcli/installed-package-contents@^1.0.6", "@npmcli/installed-package-contents@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== + dependencies: + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +"@npmcli/map-workspaces@*", "@npmcli/map-workspaces@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz#e342efbbdd0dad1bba5d7723b674ca668bf8ac5a" + integrity sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^7.1.6" + minimatch "^3.0.4" + read-package-json-fast "^2.0.1" + +"@npmcli/metavuln-calculator@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-2.0.0.tgz#70937b8b5a5cad5c588c8a7b38c4a8bd6f62c84c" + integrity sha512-VVW+JhWCKRwCTE+0xvD6p3uV4WpqocNYYtzyvenqL/u1Q3Xx6fGTJ+6UoIoii07fbuEO9U3IIyuGY0CYHDv1sg== + dependencies: + cacache "^15.0.5" + json-parse-even-better-errors "^2.3.1" + pacote "^12.0.0" + semver "^7.3.2" + +"@npmcli/move-file@^1.0.1", "@npmcli/move-file@^1.1.0": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== + +"@npmcli/node-gyp@^1.0.1", "@npmcli/node-gyp@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" + integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== + +"@npmcli/package-json@*", "@npmcli/package-json@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-1.0.1.tgz#1ed42f00febe5293c3502fd0ef785647355f6e89" + integrity sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg== + dependencies: + json-parse-even-better-errors "^2.3.1" + +"@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" + integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== + dependencies: + infer-owner "^1.0.4" + +"@npmcli/run-script@*", "@npmcli/run-script@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-2.0.0.tgz#9949c0cab415b17aaac279646db4f027d6f1e743" + integrity sha512-fSan/Pu11xS/TdaTpTB0MRn9guwGU8dye+x56mEVgBEd/QsybBbYcAL0phPXi8SGWFEChkQd6M9qL4y6VOpFig== + dependencies: + "@npmcli/node-gyp" "^1.0.2" + "@npmcli/promise-spawn" "^1.3.2" + node-gyp "^8.2.0" + read-package-json-fast "^2.0.1" + +"@npmcli/run-script@^1.8.2": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.6.tgz#18314802a6660b0d4baa4c3afe7f1ad39d8c28b7" + integrity sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g== + dependencies: + "@npmcli/node-gyp" "^1.0.2" + "@npmcli/promise-spawn" "^1.3.2" + node-gyp "^7.1.0" + read-package-json-fast "^2.0.1" + +"@octokit/auth-token@^2.4.4": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" + integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6" + integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA== + +"@octokit/plugin-paginate-rest@^2.16.8": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7" + integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw== + dependencies: + "@octokit/types" "^6.34.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^5.12.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba" + integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA== + dependencies: + "@octokit/types" "^6.34.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.2.tgz#1aa74d5da7b9e04ac60ef232edd9a7438dcf32d8" + integrity sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + universal-user-agent "^6.0.0" + +"@octokit/rest@^18.0.0": + version "18.12.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" + integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== + dependencies: + "@octokit/core" "^3.5.1" + "@octokit/plugin-paginate-rest" "^2.16.8" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^5.12.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0": + version "6.34.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" + integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== + dependencies: + "@octokit/openapi-types" "^11.2.0" + +"@semantic-release/commit-analyzer@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz#5d2a37cd5a3312da0e3ac05b1ca348bf60b90bca" + integrity sha512-5bJma/oB7B4MtwUkZC2Bf7O1MHfi4gWe4mA+MIQ3lsEV0b422Bvl1z5HRpplDnMLHH3EXMoRdEng6Ds5wUqA3A== + dependencies: + conventional-changelog-angular "^5.0.0" + conventional-commits-filter "^2.0.0" + conventional-commits-parser "^3.0.7" + debug "^4.0.0" + import-from "^3.0.0" + lodash "^4.17.4" + micromatch "^4.0.2" + +"@semantic-release/error@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@semantic-release/error/-/error-2.2.0.tgz#ee9d5a09c9969eade1ec864776aeda5c5cddbbf0" + integrity sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg== + +"@semantic-release/github@^7.0.0": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-7.2.3.tgz#20a83abd42dca43d97f03553de970eac72856c85" + integrity sha512-lWjIVDLal+EQBzy697ayUNN8MoBpp+jYIyW2luOdqn5XBH4d9bQGfTnjuLyzARZBHejqh932HVjiH/j4+R7VHw== + dependencies: + "@octokit/rest" "^18.0.0" + "@semantic-release/error" "^2.2.0" + aggregate-error "^3.0.0" + bottleneck "^2.18.1" + debug "^4.0.0" + dir-glob "^3.0.0" + fs-extra "^10.0.0" + globby "^11.0.0" + http-proxy-agent "^4.0.0" + https-proxy-agent "^5.0.0" + issue-parser "^6.0.0" + lodash "^4.17.4" + mime "^2.4.3" + p-filter "^2.0.0" + p-retry "^4.0.0" + url-join "^4.0.0" + +"@semantic-release/npm@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@semantic-release/npm/-/npm-7.1.3.tgz#1d64c41ff31b100299029c766ecc4d1f03aa5f5b" + integrity sha512-x52kQ/jR09WjuWdaTEHgQCvZYMOTx68WnS+TZ4fya5ZAJw4oRtJETtrvUw10FdfM28d/keInQdc66R1Gw5+OEQ== + dependencies: + "@semantic-release/error" "^2.2.0" + aggregate-error "^3.0.0" + execa "^5.0.0" + fs-extra "^10.0.0" + lodash "^4.17.15" + nerf-dart "^1.0.0" + normalize-url "^6.0.0" + npm "^7.0.0" + rc "^1.2.8" + read-pkg "^5.0.0" + registry-auth-token "^4.0.0" + semver "^7.1.2" + tempy "^1.0.0" + +"@semantic-release/release-notes-generator@^9.0.0": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.3.tgz#d541221c6512e9619f25ba8079527e34288e6904" + integrity sha512-hMZyddr0u99OvM2SxVOIelHzly+PP3sYtJ8XOLHdMp8mrluN5/lpeTnIO27oeCYdupY/ndoGfvrqDjHqkSyhVg== + dependencies: + conventional-changelog-angular "^5.0.0" + conventional-changelog-writer "^4.0.0" + conventional-commits-filter "^2.0.0" + conventional-commits-parser "^3.0.0" + debug "^4.0.0" + get-stream "^6.0.0" + import-from "^3.0.0" + into-stream "^6.0.0" + lodash "^4.17.4" + read-pkg-up "^7.0.0" + +"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@sinonjs/samsam@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" + integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@types/geojson@^7946.0.7": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + +"@types/minimist@^1.2.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + +"@types/node@*": + version "16.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" + integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + +"@types/node@^12.12.42": + version "12.20.36" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.36.tgz#5bd54d2383e714fc4d2c258107ee70c5bad86d0c" + integrity sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA== + +"@types/node@^14.14.28": + version "14.17.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.32.tgz#2ca61c9ef8c77f6fa1733be9e623ceb0d372ad96" + integrity sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ== + +"@types/node@^8.0.47": + version "8.10.66" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/retry@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + +"@types/validator@^13.1.4": + version "13.6.6" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.6.tgz#6e6e2d086148db5ae14851614971b715670cbd52" + integrity sha512-+qogUELb4gMhrMjSh/seKmGVvN+uQLfyqJAqYRWqVHsvBsUO2xDBCL8CJ/ZSukbd8vXaoYbpIssAmfLEzzBHEw== + +JSONStream@^1.0.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +abab@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= + +abbrev@*, abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-globals@^1.0.4: + version "1.0.9" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" + integrity sha1-VbtemGkVB7dFedBRNBMhfDgMVM8= + dependencies: + acorn "^2.1.0" + +acorn-jsx@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^2.1.0, acorn@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" + integrity sha1-q259nYhqrKiwhbwzEreaGYQz8Oc= + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.0.4: + version "8.5.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + +adal-node@^0.1.28: + version "0.1.28" + resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485" + integrity sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU= + dependencies: + "@types/node" "^8.0.47" + async ">=0.6.0" + date-utils "*" + jws "3.x.x" + request ">= 2.52.0" + underscore ">= 1.3.1" + uuid "^3.1.0" + xmldom ">= 0.1.x" + xpath.js "~1.1.0" + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" + integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansicolors@*, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= + +ansistyles@*: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" + integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk= + +any-promise@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + +anymatch@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + dependencies: + buffer-equal "^1.0.0" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archy@*, archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@~1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" + integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +argv-formatter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/argv-formatter/-/argv-formatter-1.0.0.tgz#a0ca0cbc29a5b73e836eebe1cbf6c5e0e4eb82f9" + integrity sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk= + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async@>=0.6.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.2.tgz#2eb7671034bb2194d45d30e31e24ec7e7f9670cd" + integrity sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +axios@>=0.21.2: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + +axios@^0.21.1: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-generator@6.11.4: + version "6.11.4" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.11.4.tgz#14f6933abb20c62666d27e3b7b9f5b9dc0712a9a" + integrity sha1-FPaTOrsgxiZm0n47e59bncBxKpo= + dependencies: + babel-messages "^6.8.0" + babel-runtime "^6.9.0" + babel-types "^6.10.2" + detect-indent "^3.0.1" + lodash "^4.2.0" + source-map "^0.5.0" + +babel-generator@6.26.1: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-messages@^6.23.0, babel-messages@^6.8.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= + dependencies: + babel-runtime "^6.22.0" + +babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-traverse@6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.10.2, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@6.18.0, babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + +bin-links@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-2.3.0.tgz#1ff241c86d2c29b24ae52f49544db5d78a4eb967" + integrity sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA== + dependencies: + cmd-shim "^4.0.1" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^1.0.0" + read-cmd-shim "^2.0.0" + rimraf "^3.0.0" + write-file-atomic "^3.0.3" + +binary-extensions@^2.0.0, binary-extensions@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" + integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== + dependencies: + readable-stream "^3.0.1" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +bottleneck@^2.18.1: + version "2.19.5" + resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" + integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.16.6: + version "4.17.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.6.tgz#c76be33e7786b497f66cad25a73756c8b938985d" + integrity sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw== + dependencies: + caniuse-lite "^1.0.30001274" + electron-to-chromium "^1.3.886" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= + +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= + +cacache@*, cacache@^15.0.3, cacache@^15.0.5, cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001274: + version "1.0.30001278" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001278.tgz#51cafc858df77d966b17f59b5839250b24417fff" + integrity sha512-mpF9KeH8u5cMoEmIic/cr7PNS+F5LWBk0t2ekGT60lFf0Wq+n9LspAj0g3P+o7DQhD3sUdlMln4YFAWhFYn9jg== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chai-as-promised@^7.x: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + +chai-datetime@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/chai-datetime/-/chai-datetime-1.8.0.tgz#95a1ff58130f60f16f6d882ec5c014e63aa6d75f" + integrity sha512-qBG84K8oQNz8LWacuzmCBfdoeG2UBFfbGKTSQj6lS+sjuzGUdBvjJxfZfGA4zDAMiCSqApKcuqSLO0lQQ25cHw== + dependencies: + chai ">1.9.0" + +chai@>1.9.0, chai@^4.x: + version "4.3.4" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" + integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.1" + type-detect "^4.0.5" + +chalk@*, chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +cheerio-select@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" + integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== + dependencies: + css-select "^4.1.3" + css-what "^5.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.7.0" + +cheerio@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.20.0.tgz#5c710f2bab95653272842ba01c6ea61b3545ec35" + integrity sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "~3.8.1" + lodash "^4.1.0" + optionalDependencies: + jsdom "^7.0.2" + +cheerio@0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" + integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash.assignin "^4.0.9" + lodash.bind "^4.1.4" + lodash.defaults "^4.0.1" + lodash.filter "^4.4.0" + lodash.flatten "^4.2.0" + lodash.foreach "^4.3.0" + lodash.map "^4.4.0" + lodash.merge "^4.4.0" + lodash.pick "^4.2.1" + lodash.reduce "^4.4.0" + lodash.reject "^4.4.0" + lodash.some "^4.4.0" + +cheerio@1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +cheerio@^1.0.0-rc.3: + version "1.0.0-rc.10" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + +chokidar@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + +chownr@*, chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chownr@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cidr-regex@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-3.1.1.tgz#ba1972c57c66f61875f18fd7dd487469770b571d" + integrity sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw== + dependencies: + ip-regex "^4.1.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-columns@*: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646" + integrity sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ== + dependencies: + string-width "^4.2.3" + strip-ansi "^6.0.1" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@*, cli-table3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== + dependencies: + object-assign "^4.1.0" + string-width "^4.2.0" + optionalDependencies: + colors "^1.1.2" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1, clone@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +cmd-shim@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" + integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== + dependencies: + mkdirp-infer-owner "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-logger@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/color-logger/-/color-logger-0.0.3.tgz#d9b22dd1d973e166b18bf313f9f481bba4df2018" + integrity sha1-2bIt0dlz4Waxi/MT+fSBu6TfIBg= + +color-logger@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/color-logger/-/color-logger-0.0.6.tgz#e56245ef29822657110c7cb75a9cd786cb69ed1b" + integrity sha1-5WJF7ymCJlcRDHy3WpzXhstp7Rs= + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +columnify@*: + version "1.5.4" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= + dependencies: + strip-ansi "^3.0.0" + wcwidth "^1.0.0" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^6.2.0, commander@~6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +comment-parser@^0.7.2: + version "0.7.6" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.7.6.tgz#0e743a53c8e646c899a1323db31f6cd337b10f12" + integrity sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg== + +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +conventional-changelog-angular@^5.0.0: + version "5.0.13" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" + integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-writer@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" + integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== + dependencies: + compare-func "^2.0.0" + conventional-commits-filter "^2.0.7" + dateformat "^3.0.0" + handlebars "^4.7.6" + json-stringify-safe "^5.0.1" + lodash "^4.17.15" + meow "^8.0.0" + semver "^6.0.0" + split "^1.0.0" + through2 "^4.0.0" + +conventional-commits-filter@^2.0.0, conventional-commits-filter@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== + dependencies: + lodash.ismatch "^4.4.0" + modify-values "^1.0.0" + +conventional-commits-parser@^3.0.0, conventional-commits-parser@^3.0.7: + version "3.2.3" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.3.tgz#fc43704698239451e3ef35fd1d8ed644f46bd86e" + integrity sha512-YyRDR7On9H07ICFpRm/igcdjIqebXbvf4Cff+Pf0BrBys1i1EOzx9iFXNlAbdrLAR8jf7bkUYkDAr8pEy0q4Pw== + dependencies: + JSONStream "^1.0.4" + is-text-path "^1.0.1" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +convert-source-map@^1.5.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +core-js@^3.6.1: + version "3.19.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.1.tgz#f6f173cae23e73a7d88fa23b6e9da329276c6641" + integrity sha512-Tnc7E9iKd/b/ff7GFbhwPVzJzPztGrChB8X8GLqoYGdEOG8IpLnK1xPyo3ZoO3HsK6TodJS58VGPOxA+hLHQMg== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-env@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-select@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" + integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== + dependencies: + boolbase "^1.0.0" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" + nth-check "^2.0.0" + +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + +css-what@^5.0.0, css-what@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +"cssstyle@>= 0.2.29 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= + dependencies: + cssom "0.3.x" + +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +date-utils@*: + version "1.2.21" + resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64" + integrity sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q= + +dateformat@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== + +debug@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debuglog@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + +decamelize-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +deep-extend@^0.6.0, deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +default-require-extensions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" + integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== + dependencies: + strip-bom "^4.0.0" + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +del@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" + integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +delay@^4.3.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/delay/-/delay-4.4.1.tgz#6e02d02946a1b6ab98b39262ced965acba2ac4d1" + integrity sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +denque@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" + integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== + +denque@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a" + integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== + +depd@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +detect-indent@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-3.0.1.tgz#9dc5e5ddbceef8325764b9451b02bc6d54084f75" + integrity sha1-ncXl3bzu+DJXZLlFGwK8bVQIT3U= + dependencies: + get-stdin "^4.0.1" + minimist "^1.1.0" + repeating "^1.1.0" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= + dependencies: + repeating "^2.0.0" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +dezalgo@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= + dependencies: + asap "^2.0.0" + wrappy "1" + +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +diff@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +diff@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dir-glob@^3.0.0, dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@^1.0.1, dom-serializer@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domhandler@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + integrity sha1-LeWaCCLVAn+r/28DLCsloqir5zg= + dependencies: + domelementtype "1" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" + integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== + dependencies: + domelementtype "^2.2.0" + +domutils@1.5, domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-prop@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dottie@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.2.tgz#cc91c0726ce3a054ebf11c55fbc92a7f266dd154" + integrity sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg== + +duplexer2@~0.1.0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + +duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +electron-to-chromium@^1.3.886: + version "1.3.890" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.890.tgz#e7143b659f73dc4d0512d1ae4baeb0fb9e7bc835" + integrity sha512-VWlVXSkv0cA/OOehrEyqjUTHwV8YXCPTfPvbtoeU2aHR21vI4Ejh5aC4AxUwOmbLbBgb6Gd3URZahoCxtBqCYQ== + +emitter-listener@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" + integrity sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY= + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + +env-ci@^5.0.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-5.4.1.tgz#814387ddd6857b37472ef612361f34d720c29a18" + integrity sha512-xyuCtyFZLpnW5aH0JstETKTSMwHHQX4m42juzEZzvbUCJX7RiPVlhASKM0f/cJ4vvI/+txMkZ7F5To6dCdPYhg== + dependencies: + execa "^5.0.0" + fromentries "^1.3.2" + java-properties "^1.0.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.6.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esdoc-accessor-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-accessor-plugin/-/esdoc-accessor-plugin-1.0.0.tgz#791ba4872e6c403515ce749b1348d6f0293ad9eb" + integrity sha1-eRukhy5sQDUVznSbE0jW8Ck62es= + +esdoc-brand-plugin@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esdoc-brand-plugin/-/esdoc-brand-plugin-1.0.1.tgz#7c0e1ae90e84c30b2d3369d3a6449f9dc9f8d511" + integrity sha512-Yv9j3M7qk5PSLmSeD6MbPsfIsEf8K43EdH8qZpE/GZwnJCRVmDPrZJ1cLDj/fPu6P35YqgcEaJK4E2NL/CKA7g== + dependencies: + cheerio "0.22.0" + +esdoc-coverage-plugin@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/esdoc-coverage-plugin/-/esdoc-coverage-plugin-1.1.0.tgz#3869869cd7f87891f972625787695a299aece45c" + integrity sha1-OGmGnNf4eJH5cmJXh2laKZrs5Fw= + +esdoc-ecmascript-proposal-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-ecmascript-proposal-plugin/-/esdoc-ecmascript-proposal-plugin-1.0.0.tgz#390dc5656ba8a2830e39dba3570d79138df2ffd9" + integrity sha1-OQ3FZWuoooMOOdujVw15E43y/9k= + +esdoc-external-ecmascript-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-external-ecmascript-plugin/-/esdoc-external-ecmascript-plugin-1.0.0.tgz#78f565d4a0c5185ac63152614dce1fe1a86688db" + integrity sha1-ePVl1KDFGFrGMVJhTc4f4ahmiNs= + dependencies: + fs-extra "1.0.0" + +esdoc-inject-style-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-inject-style-plugin/-/esdoc-inject-style-plugin-1.0.0.tgz#a13597368bb9fb89c365e066495caf97a4decbb1" + integrity sha1-oTWXNou5+4nDZeBmSVyvl6Tey7E= + dependencies: + cheerio "0.22.0" + fs-extra "1.0.0" + +esdoc-integrate-manual-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-integrate-manual-plugin/-/esdoc-integrate-manual-plugin-1.0.0.tgz#1854a6aa1c081035d7c8c51e3bdd4fb65aa4711c" + integrity sha1-GFSmqhwIEDXXyMUeO91PtlqkcRw= + +esdoc-integrate-test-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-integrate-test-plugin/-/esdoc-integrate-test-plugin-1.0.0.tgz#e2d0d00090f7f0c35e5d2f2c033327a79e53e409" + integrity sha1-4tDQAJD38MNeXS8sAzMnp55T5Ak= + +esdoc-lint-plugin@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/esdoc-lint-plugin/-/esdoc-lint-plugin-1.0.2.tgz#4962930c6dc5b25d80cf6eff1b0f3c24609077f7" + integrity sha512-24AYqD2WbZI9We02I7/6dzAa7yUliRTFUaJCZAcYJMQicJT5gUrNFVaI8XmWEN/mhF3szIn1uZBNWeLul4CmNw== + +esdoc-publish-html-plugin@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/esdoc-publish-html-plugin/-/esdoc-publish-html-plugin-1.1.2.tgz#bdece7bc7a0a3e419933503252db7a6772879dab" + integrity sha512-hG1fZmTcEp3P/Hv/qKiMdG1qSp8MjnVZMMkxL5P5ry7I2sX0HQ4P9lt2lms+90Lt0r340HHhSuVx107UL7dphg== + dependencies: + babel-generator "6.11.4" + cheerio "0.22.0" + escape-html "1.0.3" + fs-extra "1.0.0" + ice-cap "0.0.4" + marked "0.3.19" + taffydb "2.7.2" + +esdoc-standard-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-standard-plugin/-/esdoc-standard-plugin-1.0.0.tgz#661201cac7ef868924902446fdac1527253c5d4d" + integrity sha1-ZhIBysfvhokkkCRG/awVJyU8XU0= + dependencies: + esdoc-accessor-plugin "^1.0.0" + esdoc-brand-plugin "^1.0.0" + esdoc-coverage-plugin "^1.0.0" + esdoc-external-ecmascript-plugin "^1.0.0" + esdoc-integrate-manual-plugin "^1.0.0" + esdoc-integrate-test-plugin "^1.0.0" + esdoc-lint-plugin "^1.0.0" + esdoc-publish-html-plugin "^1.0.0" + esdoc-type-inference-plugin "^1.0.0" + esdoc-undocumented-identifier-plugin "^1.0.0" + esdoc-unexported-identifier-plugin "^1.0.0" + +esdoc-type-inference-plugin@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/esdoc-type-inference-plugin/-/esdoc-type-inference-plugin-1.0.2.tgz#916e3f756de1d81d9c0dbe1c008e8dafd322cfaf" + integrity sha512-tMIcEHNe1uhUGA7lT1UTWc9hs2dzthnTgmqXpmeUhurk7fL2tinvoH+IVvG/sLROzwOGZQS9zW/F9KWnpMzLIQ== + +esdoc-undocumented-identifier-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-undocumented-identifier-plugin/-/esdoc-undocumented-identifier-plugin-1.0.0.tgz#82e05d371c32d12871140f1d5c81ec99fd9cc2c8" + integrity sha1-guBdNxwy0ShxFA8dXIHsmf2cwsg= + +esdoc-unexported-identifier-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-unexported-identifier-plugin/-/esdoc-unexported-identifier-plugin-1.0.0.tgz#1f9874c6a7c2bebf9ad397c3ceb75c9c69dabab1" + integrity sha1-H5h0xqfCvr+a05fDzrdcnGnaurE= + +esdoc@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/esdoc/-/esdoc-1.1.0.tgz#07d40ebf791764cd537929c29111e20a857624f3" + integrity sha512-vsUcp52XJkOWg9m1vDYplGZN2iDzvmjDL5M/Mp8qkoDG3p2s0yIQCIjKR5wfPBaM3eV14a6zhQNYiNTCVzPnxA== + dependencies: + babel-generator "6.26.1" + babel-traverse "6.26.0" + babylon "6.18.0" + cheerio "1.0.0-rc.2" + color-logger "0.0.6" + escape-html "1.0.3" + fs-extra "5.0.0" + ice-cap "0.0.4" + marked "0.3.19" + minimist "1.2.0" + taffydb "2.7.3" + +eslint-plugin-jsdoc@^20.4.0: + version "20.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-20.4.0.tgz#ea6725c3d1e68cd1ac0e633d935aa7e932b624c2" + integrity sha512-c/fnEpwWLFeQn+A7pb1qLOdyhovpqGCWCeUv1wtzFNL5G+xedl9wHUnXtp3b1sGHolVimi9DxKVTuhK/snXoOw== + dependencies: + comment-parser "^0.7.2" + debug "^4.1.1" + jsdoctypeparser "^6.1.0" + lodash "^4.17.15" + object.entries-ponyfill "^1.0.1" + regextras "^0.7.0" + semver "^6.3.0" + spdx-expression-parse "^3.0.0" + +eslint-plugin-mocha@^6.2.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz#72bfd06a5c4323e17e30ef41cd726030e8cdb8fd" + integrity sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg== + dependencies: + eslint-utils "^2.0.0" + ramda "^0.27.0" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +expect-type@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.11.0.tgz#bce1a3e283f0334eedb39699b57dd27be7009cc1" + integrity sha512-hkObxepDKhTYloH/UZoxYTT2uUzdhvDEwAi0oqdk29XEkHF8p+5ZRpX/BZES2PtGN9YgyEqutIjXfnL9iMflMw== + +extend@^3.0.0, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastest-levenshtein@*: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-versions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" + integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== + dependencies: + semver-regex "^3.1.2" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flat@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" + integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== + dependencies: + is-buffer "~2.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flush-write-stream@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +follow-redirects@^1.14.0, follow-redirects@^1.14.4: + version "1.14.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" + integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +from2@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fromentries@^1.2.0, fromentries@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs-extra@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" + integrity sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA= + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + +fs-extra@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-jetpack@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fs-jetpack/-/fs-jetpack-4.2.0.tgz#a3efc00abdb36f0f43ebd44405a4826098399d97" + integrity sha512-b7kFUlXAA4Q12qENTdU5DCQkf8ojEk4fpaXXu/bqayobwm0EfjjlwBCFqRBM2t8I75s0ifk0ajRqddXy2bAHJg== + dependencies: + minimatch "^3.0.2" + rimraf "^2.6.3" + +fs-minipass@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + +fs-minipass@^2.0.0, fs-minipass@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gauge@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.1.tgz#4bea07bcde3782f06dced8950e51307aa0f4a346" + integrity sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^1.0.1 || ^2.0.0" + strip-ansi "^3.0.1 || ^4.0.0" + wide-align "^1.1.2" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stdin@8.0.0, get-stdin@~8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +git-log-parser@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/git-log-parser/-/git-log-parser-1.2.0.tgz#2e6a4c1b13fc00028207ba795a7ac31667b9fd4a" + integrity sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo= + dependencies: + argv-formatter "~1.0.0" + spawn-error-forwarder "~1.0.0" + split2 "~1.0.0" + stream-combiner2 "~1.1.1" + through2 "~2.0.0" + traverse "~0.6.6" + +git-raw-commits@^2.0.0: + version "2.0.10" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" + integrity sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ== + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob@*, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + 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" + +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + 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" + +glob@~7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + 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-dirs@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== + +globby@^11.0.0, globby@^11.0.1: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +graceful-fs@*, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +handlebars@^4.7.6: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.0, has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hook-std@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hook-std/-/hook-std-2.0.0.tgz#ff9aafdebb6a989a354f729bb6445cf4a3a7077c" + integrity sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g== + +hosted-git-info@*, hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== + dependencies: + lru-cache "^6.0.0" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@~3.8.1: + version "3.8.3" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" + integrity sha1-mWwosZFRaovoZQGn15dX5ccMEGg= + dependencies: + domelementtype "1" + domhandler "2.3" + domutils "1.5" + entities "1.0" + readable-stream "1.1" + +http-cache-semantics@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= + dependencies: + ms "^2.0.0" + +husky@^4.2.5: + version "4.3.8" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== + dependencies: + chalk "^4.0.0" + ci-info "^2.0.0" + compare-versions "^3.6.0" + cosmiconfig "^7.0.0" + find-versions "^4.0.0" + opencollective-postinstall "^2.0.2" + pkg-dir "^5.0.0" + please-upgrade-node "^3.2.0" + slash "^3.0.0" + which-pm-runs "^1.0.0" + +ice-cap@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/ice-cap/-/ice-cap-0.0.4.tgz#8a6d31ab4cac8d4b56de4fa946df3352561b6e18" + integrity sha1-im0xq0ysjUtW3k+pRt8zUlYbbhg= + dependencies: + cheerio "0.20.0" + color-logger "0.0.3" + +iconv-lite@^0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" + integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-walk@^3.0.1, ignore-walk@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== + dependencies: + minimatch "^3.0.4" + +ignore-walk@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-4.0.1.tgz#fc840e8346cf88a3a9380c5b17933cd8f4d39fa3" + integrity sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw== + dependencies: + minimatch "^3.0.4" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.4, ignore@~5.1.8: + version "5.1.9" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" + integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== + dependencies: + resolve-from "^5.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflection@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.13.1.tgz#c5cadd80888a90cf84c2e96e340d7edc85d5f0cb" + integrity sha512-dldYtl2WlN0QDkIDtg8+xFwOS2Tbmp12t1cHa5/YClU6ZQjTFm7B66UcVbh9NQB+HvT5BAd2t5+yKsBkw5pcqA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@*, ini@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +init-package-json@*: + version "2.0.5" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" + integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== + dependencies: + npm-package-arg "^8.1.5" + promzard "^0.3.0" + read "~1.0.1" + read-package-json "^4.1.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^3.0.0" + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +into-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702" + integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA== + dependencies: + from2 "^2.3.0" + p-is-promise "^3.0.0" + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ip-regex@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + +ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-buffer@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-cidr@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" + integrity sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA== + dependencies: + cidr-regex "^3.1.1" + +is-core-module@^2.2.0, is-core-module@^2.5.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-text-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= + dependencies: + text-extensions "^1.0.0" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + +is-weakref@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2" + integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ== + dependencies: + call-bind "^1.0.0" + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +issue-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-6.0.0.tgz#b1edd06315d4f2044a9755daf85fdafde9b4014a" + integrity sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA== + dependencies: + lodash.capitalize "^4.2.1" + lodash.escaperegexp "^4.1.2" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.uniqby "^4.7.0" + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" + integrity sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.0" + istanbul-lib-coverage "^3.0.0-alpha.1" + make-dir "^3.0.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^3.3.3" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" + integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +java-properties@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211" + integrity sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ== + +js-combinatorics@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/js-combinatorics/-/js-combinatorics-0.5.5.tgz#78d68a6db24bbd58173ded714deee75bc4335e75" + integrity sha512-WglFY9EQvwndNhuJLxxyjnC16649lfZly/G3M3zgQMwcWlJDJ0Jn9niPWeYjnLXwWOEycYVxR2Tk98WLeFkrcw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +js-yaml@3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^3.13.1, js-yaml@~3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbi@^3.1.1: + version "3.2.5" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6" + integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdoctypeparser@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-6.1.0.tgz#acfb936c26300d98f1405cb03e20b06748e512a8" + integrity sha512-UCQBZ3xCUBv/PLfwKAJhp6jmGOSLFNKzrotXGNgbKhWvz27wPsCsVeP7gIcHPElQw2agBmynAitXqhxR58XAmA== + +jsdom@^7.0.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-7.2.2.tgz#40b402770c2bda23469096bee91ab675e3b1fc6e" + integrity sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4= + dependencies: + abab "^1.0.0" + acorn "^2.4.0" + acorn-globals "^1.0.4" + cssom ">= 0.3.0 < 0.4.0" + cssstyle ">= 0.2.29 < 0.3.0" + escodegen "^1.6.1" + nwmatcher ">= 1.3.7 < 2.0.0" + parse5 "^1.5.1" + request "^2.55.0" + sax "^1.1.4" + symbol-tree ">= 3.1.0 < 4.0.0" + tough-cookie "^2.2.0" + webidl-conversions "^2.0.0" + whatwg-url-compat "~0.6.5" + xml-name-validator ">= 2.0.1 < 3.0.0" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@*, json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +jsonc-parser@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0, jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +just-diff-apply@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-3.1.2.tgz#710d8cda00c65dc4e692df50dbe9bac5581c2193" + integrity sha512-TCa7ZdxCeq6q3Rgms2JCRHTCfWAETPZ8SzYUbkYF6KR3I03sN29DaOIC+xyWboIcMvjAsD5iG2u/RWzHD8XpgQ== + +just-diff@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-3.1.1.tgz#d50c597c6fd4776495308c63bdee1b6839082647" + integrity sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ== + +just-extend@^4.0.2: + version "4.2.1" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@3.x.x: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= + optionalDependencies: + graceful-fs "^4.1.9" + +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + +lcov-result-merger@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lcov-result-merger/-/lcov-result-merger-3.1.0.tgz#ae6d1be663dbf7d586d8004642359d39de72039e" + integrity sha512-vGXaMNGZRr4cYvW+xMVg+rg7qd5DX9SbGXl+0S3k85+gRZVK4K7UvxPWzKb/qiMwe+4bx3EOrW2o4mbdb1WnsA== + dependencies: + through2 "^2.0.3" + vinyl "^2.1.0" + vinyl-fs "^3.0.2" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + dependencies: + flush-write-stream "^1.0.2" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +libnpmaccess@*: + version "4.0.3" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" + integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== + dependencies: + aproba "^2.0.0" + minipass "^3.1.1" + npm-package-arg "^8.1.2" + npm-registry-fetch "^11.0.0" + +libnpmdiff@*: + version "2.0.4" + resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-2.0.4.tgz#bb1687992b1a97a8ea4a32f58ad7c7f92de53b74" + integrity sha512-q3zWePOJLHwsLEUjZw3Kyu/MJMYfl4tWCg78Vl6QGSfm4aXBUSVzMzjJ6jGiyarsT4d+1NH4B1gxfs62/+y9iQ== + dependencies: + "@npmcli/disparity-colors" "^1.0.1" + "@npmcli/installed-package-contents" "^1.0.7" + binary-extensions "^2.2.0" + diff "^5.0.0" + minimatch "^3.0.4" + npm-package-arg "^8.1.1" + pacote "^11.3.0" + tar "^6.1.0" + +libnpmexec@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-3.0.1.tgz#bc2fddf1b7bd2c1b2c43b4b726ec4cf11920ad0a" + integrity sha512-VUZTpkKBRPv3Z9DIjbsiHhEQXmQ+OwSQ/yLCY9i6CFE8UIczWyE6wVxP5sJ5NSGtSTUs6I98WewQOL45OKMyxA== + dependencies: + "@npmcli/arborist" "^4.0.0" + "@npmcli/ci-detect" "^1.3.0" + "@npmcli/run-script" "^2.0.0" + chalk "^4.1.0" + mkdirp-infer-owner "^2.0.0" + npm-package-arg "^8.1.2" + pacote "^12.0.0" + proc-log "^1.0.0" + read "^1.0.7" + read-package-json-fast "^2.0.2" + walk-up-path "^1.0.0" + +libnpmfund@*: + version "2.0.1" + resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-2.0.1.tgz#3c7e2be61e8c79e22c4918dde91ef57f64faf064" + integrity sha512-OhDbjB3gqdRyuQ56AhUtO49HZ7cZHSM7yCnhQa1lsNpmAmGPnjCImfx8SoWaAkUM7Ov8jngMR5JHKAr1ddjHTQ== + dependencies: + "@npmcli/arborist" "^4.0.0" + +libnpmhook@*: + version "6.0.3" + resolved "https://registry.yarnpkg.com/libnpmhook/-/libnpmhook-6.0.3.tgz#1d7f0d7e6a7932fbf7ce0881fdb0ed8bf8748a30" + integrity sha512-3fmkZJibIybzmAvxJ65PeV3NzRc0m4xmYt6scui5msocThbEp4sKFT80FhgrCERYDjlUuFahU6zFNbJDHbQ++g== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmorg@*: + version "2.0.3" + resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-2.0.3.tgz#4e605d4113dfa16792d75343824a0625c76703bc" + integrity sha512-JSGl3HFeiRFUZOUlGdiNcUZOsUqkSYrg6KMzvPZ1WVZ478i47OnKSS0vkPmX45Pai5mTKuwIqBMcGWG7O8HfdA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmpack@*: + version "3.0.0" + resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-3.0.0.tgz#b1cdf182106bc0d25910e79bb5c9b6c23cd71670" + integrity sha512-W6lt4blkR9YXu/qOrFknfnKBajz/1GvAc5q1XcWTGuBJn2DYKDWHtA7x1fuMQdn7hKDBOPlZ/Aqll+ZvAnrM6g== + dependencies: + "@npmcli/run-script" "^2.0.0" + npm-package-arg "^8.1.0" + pacote "^12.0.0" + +libnpmpublish@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" + integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== + dependencies: + normalize-package-data "^3.0.2" + npm-package-arg "^8.1.2" + npm-registry-fetch "^11.0.0" + semver "^7.1.3" + ssri "^8.0.1" + +libnpmsearch@*: + version "3.1.2" + resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-3.1.2.tgz#aee81b9e4768750d842b627a3051abc89fdc15f3" + integrity sha512-BaQHBjMNnsPYk3Bl6AiOeVuFgp72jviShNBw5aHaHNKWqZxNi38iVNoXbo6bG/Ccc/m1To8s0GtMdtn6xZ1HAw== + dependencies: + npm-registry-fetch "^11.0.0" + +libnpmteam@*: + version "2.0.4" + resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-2.0.4.tgz#9dbe2e18ae3cb97551ec07d2a2daf9944f3edc4c" + integrity sha512-FPrVJWv820FZFXaflAEVTLRWZrerCvfe7ZHSMzJ/62EBlho2KFlYKjyNEsPW3JiV7TLSXi3vo8u0gMwIkXSMTw== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmversion@*: + version "2.0.1" + resolved "https://registry.yarnpkg.com/libnpmversion/-/libnpmversion-2.0.1.tgz#20b1425d88cd99c66806a54b458d2d654066b550" + integrity sha512-uFGtNTe/m0GOIBQCE4ryIsgGNJdeShW+qvYtKNLCCuiG7JY3YEslL/maFFZbaO4wlQa/oj1t0Bm9TyjahvtgQQ== + dependencies: + "@npmcli/git" "^2.0.7" + "@npmcli/run-script" "^2.0.0" + json-parse-even-better-errors "^2.3.1" + semver "^7.3.5" + stringify-package "^1.0.1" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +linkify-it@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== + dependencies: + uc.micro "^1.0.1" + +lint-staged@^10.2.6: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== + dependencies: + chalk "^4.1.0" + cli-truncate "^2.1.0" + commander "^6.2.0" + cosmiconfig "^7.0.0" + debug "^4.2.0" + dedent "^0.7.0" + enquirer "^2.3.6" + execa "^4.1.0" + listr2 "^3.2.2" + log-symbols "^4.0.0" + micromatch "^4.0.2" + normalize-path "^3.0.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" + stringify-object "^3.3.0" + +listr2@^3.2.2: + version "3.13.3" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.3.tgz#d8f6095c9371b382c9b1c2bc33c5941d8e177f11" + integrity sha512-VqAgN+XVfyaEjSaFewGPcDs5/3hBbWVaX1VgWv2f52MF7US45JuARlArULctiB44IIcEk3JF7GtoFCLqEdeuPA== + dependencies: + cli-truncate "^2.1.0" + clone "^2.1.2" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^7.4.0" + through "^2.3.8" + wrap-ansi "^7.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.assignin@^4.0.9: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" + integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= + +lodash.bind@^4.1.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= + +lodash.capitalize@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" + integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= + +lodash.defaults@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.differencewith@~4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7" + integrity sha1-uvr7yRi1UVTheRdqALsK76rIVLc= + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + +lodash.filter@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= + +lodash.flatten@^4.2.0, lodash.flatten@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.foreach@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.ismatch@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" + integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.map@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= + +lodash.merge@^4.4.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.pick@^4.2.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + +lodash.reduce@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= + +lodash.reject@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= + +lodash.some@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= + +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= + +lodash@^4.1.0, lodash@^4.15.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^4.1.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-fetch-happen@*, make-fetch-happen@^9.0.1, make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + +mariadb@^2.3.1: + version "2.5.5" + resolved "https://registry.yarnpkg.com/mariadb/-/mariadb-2.5.5.tgz#a9aff9f1e57231a415a21254489439beb501c803" + integrity sha512-6dklvcKWuuaV1JjAwnE2ezR+jTt7JrZHftgeHHBmjB0wgfaUpdxol1DPWclwMcCrsO9yoM0FuCOiCcCgXc//9Q== + dependencies: + "@types/geojson" "^7946.0.7" + "@types/node" "^14.14.28" + denque "^1.5.0" + iconv-lite "^0.6.3" + long "^4.0.0" + moment-timezone "^0.5.33" + please-upgrade-node "^3.2.0" + +markdown-it@12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.2.tgz#4401beae8df8aa2221fc6565a7188e60a06ef0ed" + integrity sha512-4Lkvjbv2kK+moL9TbeV+6/NHx+1Q+R/NIdUlFlkqkkzUcTod4uiyTJRiBidKR9qXSdkNFkgv+AELY8KN9vSgVA== + dependencies: + argparse "^2.0.1" + entities "~2.0.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +markdownlint-cli@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.26.0.tgz#cd89e3e39a049303ec125c8aa291da4f3325df29" + integrity sha512-biLfeGNZG9nw0yJbtFBzRlew2/P5w7JSseKwolSox3zejs7dLpGvPgqbC+iqJnqqGWcWLtXaXh8bBEKWmfl10A== + dependencies: + commander "~6.2.1" + deep-extend "~0.6.0" + get-stdin "~8.0.0" + glob "~7.1.6" + ignore "~5.1.8" + js-yaml "~3.14.1" + jsonc-parser "~3.0.0" + lodash.differencewith "~4.5.0" + lodash.flatten "~4.4.0" + markdownlint "~0.22.0" + markdownlint-rule-helpers "~0.13.0" + minimatch "~3.0.4" + minimist "~1.2.5" + rc "~1.2.8" + +markdownlint-rule-helpers@~0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.13.0.tgz#7cc6553bc7f8c4c8a43cf66fb2a3a652124f46f9" + integrity sha512-rRY0itbcHG4e+ntz0bbY3AIceSJMKS0TafEMgEtKVHRZ54/JUSy6/4ypCL618RlJvYRej+xMLxX5nkJqIeTZaQ== + +markdownlint@~0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.22.0.tgz#4ed95b61c17ae9f4dfca6a01f038c744846c0a72" + integrity sha512-J4B+iMc12pOdp/wfYi03W2qfAfEyiZzq3qvQh/8vOMNU8vXYY6Jg440EY7dWTBCqROhb1i4nAn3BTByJ5kdx1w== + dependencies: + markdown-it "12.0.2" + +marked-terminal@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-4.2.0.tgz#593734a53cf9a4bb01ea961aa579bd21889ce502" + integrity sha512-DQfNRV9svZf0Dm9Cf5x5xaVJ1+XjxQW6XjFJ5HFkVyK52SDpj5PCBzS5X5r2w9nHr3mlB0T5201UMLue9fmhUw== + dependencies: + ansi-escapes "^4.3.1" + cardinal "^2.1.1" + chalk "^4.1.0" + cli-table3 "^0.6.0" + node-emoji "^1.10.0" + supports-hyperlinks "^2.1.0" + +marked@0.3.19: + version "0.3.19" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" + integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg== + +marked@^1.1.0: + version "1.2.9" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.9.tgz#53786f8b05d4c01a2a5a76b7d1ec9943d29d72dc" + integrity sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw== + +marked@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" + integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mime-db@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" + integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.33" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" + integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + dependencies: + mime-db "1.50.0" + +mime@^2.4.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@*, minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@*, minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" + integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== + dependencies: + yallist "^4.0.0" + +minipass@^2.6.0, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-infer-owner@*, mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + +mkdirp@*, mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@0.5.5, mkdirp@^0.5.1, mkdirp@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mocha@^7.1.2: + version "7.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" + integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + chokidar "3.3.0" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" + minimatch "3.0.4" + mkdirp "0.5.5" + ms "2.1.1" + node-environment-flags "1.0.6" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" + +modify-values@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" + integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + +moment-timezone@^0.5.31, moment-timezone@^0.5.33: + version "0.5.33" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" + integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@^2.26.0: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + +ms@*, ms@^2.0.0, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8, mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +mysql2@^2.1.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.2.tgz#3efe9814dbf1c2a3d7c2a1fc4666235939943ff9" + integrity sha512-JUSA50rt/nSew8aq8xe3pRk5Q4y/M5QdSJn7Ey3ndOlPp2KXuialQ0sS35DNhPT5Z5PnOiIwSSQvKkl1WorqRA== + dependencies: + denque "^2.0.1" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^4.0.0" + lru-cache "^6.0.0" + named-placeholders "^1.1.2" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + +named-placeholders@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8" + integrity sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA== + dependencies: + lru-cache "^4.1.3" + +nan@^2.12.1: + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + +native-duplexpair@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/native-duplexpair/-/native-duplexpair-1.0.0.tgz#7899078e64bf3c8a3d732601b3d40ff05db58fa0" + integrity sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +needle@^2.2.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +negotiator@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nerf-dart@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nerf-dart/-/nerf-dart-1.0.0.tgz#e6dab7febf5ad816ea81cf5c629c5a0ebde72c1a" + integrity sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nise@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" + integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^6.0.0" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + +node-emoji@^1.10.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-environment-flags@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + +node-fetch@^2.6.1: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + +node-gyp@*, node-gyp@^8.2.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.0.tgz#6e1112b10617f0f8559c64b3f737e8109e5a8338" + integrity sha512-Bi/oCm5bH6F+FmzfUxJpPaxMEyIhszULGR3TprmTeku8/dMFcdTcypk120NeZqEt54r1BrgEKtm2jJiuIKE28Q== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^4.1.2" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +node-gyp@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" + integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.3" + nopt "^5.0.0" + npmlog "^4.1.2" + request "^2.88.2" + rimraf "^3.0.2" + semver "^7.3.2" + tar "^6.0.2" + which "^2.0.2" + +node-pre-gyp@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + +nopt@*, nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +now-and-later@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" + integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + dependencies: + once "^1.3.2" + +npm-audit-report@*: + version "2.1.5" + resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-2.1.5.tgz#a5b8850abe2e8452fce976c8960dd432981737b5" + integrity sha512-YB8qOoEmBhUH1UJgh1xFAv7Jg1d+xoNhsDYiFQlEFThEBui0W1vIz2ZK6FVg4WZjwEdl7uBQlm1jy3MUfyHeEw== + dependencies: + chalk "^4.0.0" + +npm-bundled@^1.0.1, npm-bundled@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-install-checks@*, npm-install-checks@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" + integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-package-arg@*, npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.1, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: + version "8.1.5" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" + integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== + dependencies: + hosted-git-info "^4.0.1" + semver "^7.3.4" + validate-npm-package-name "^3.0.0" + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + +npm-packlist@^2.1.4: + version "2.2.2" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" + integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== + dependencies: + glob "^7.1.6" + ignore-walk "^3.0.3" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +npm-packlist@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-3.0.0.tgz#0370df5cfc2fcc8f79b8f42b37798dd9ee32c2a9" + integrity sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ== + dependencies: + glob "^7.1.6" + ignore-walk "^4.0.1" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +npm-pick-manifest@*, npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.0, npm-pick-manifest@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" + integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== + dependencies: + npm-install-checks "^4.0.0" + npm-normalize-package-bin "^1.0.1" + npm-package-arg "^8.1.2" + semver "^7.3.4" + +npm-profile@*: + version "5.0.4" + resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-5.0.4.tgz#73e5bd1d808edc2c382d7139049cc367ac43161b" + integrity sha512-OKtU7yoAEBOnc8zJ+/uo5E4ugPp09sopo+6y1njPp+W99P8DvQon3BJYmpvyK2Bf1+3YV5LN1bvgXRoZ1LUJBA== + dependencies: + npm-registry-fetch "^11.0.0" + +npm-registry-fetch@*, npm-registry-fetch@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" + integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== + dependencies: + make-fetch-happen "^9.0.1" + minipass "^3.1.3" + minipass-fetch "^1.3.0" + minipass-json-stream "^1.0.1" + minizlib "^2.0.0" + npm-package-arg "^8.0.0" + +npm-run-path@^4.0.0, npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npm-user-validate@*: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561" + integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw== + +npm@^7.0.0: + version "7.24.2" + resolved "https://registry.yarnpkg.com/npm/-/npm-7.24.2.tgz#861117af8241bea592289f22407230e5300e59ca" + integrity sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^2.9.0" + "@npmcli/ci-detect" "^1.2.0" + "@npmcli/config" "^2.3.0" + "@npmcli/map-workspaces" "^1.0.4" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^1.8.6" + abbrev "~1.1.1" + ansicolors "~0.3.2" + ansistyles "~0.1.3" + archy "~1.0.0" + cacache "^15.3.0" + chalk "^4.1.2" + chownr "^2.0.0" + cli-columns "^3.1.2" + cli-table3 "^0.6.0" + columnify "~1.5.4" + fastest-levenshtein "^1.0.12" + glob "^7.2.0" + graceful-fs "^4.2.8" + hosted-git-info "^4.0.2" + ini "^2.0.0" + init-package-json "^2.0.5" + is-cidr "^4.0.2" + json-parse-even-better-errors "^2.3.1" + libnpmaccess "^4.0.2" + libnpmdiff "^2.0.4" + libnpmexec "^2.0.1" + libnpmfund "^1.1.0" + libnpmhook "^6.0.2" + libnpmorg "^2.0.2" + libnpmpack "^2.0.1" + libnpmpublish "^4.0.1" + libnpmsearch "^3.1.1" + libnpmteam "^2.0.3" + libnpmversion "^1.2.1" + make-fetch-happen "^9.1.0" + minipass "^3.1.3" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + ms "^2.1.2" + node-gyp "^7.1.2" + nopt "^5.0.0" + npm-audit-report "^2.1.5" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.1" + npm-profile "^5.0.3" + npm-registry-fetch "^11.0.0" + npm-user-validate "^1.0.1" + npmlog "^5.0.1" + opener "^1.5.2" + pacote "^11.3.5" + parse-conflict-json "^1.1.1" + qrcode-terminal "^0.12.0" + read "~1.0.7" + read-package-json "^4.1.1" + read-package-json-fast "^2.0.3" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + tar "^6.1.11" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^1.0.4" + validate-npm-package-name "~3.0.0" + which "^2.0.2" + write-file-atomic "^3.0.3" + +npmlog@*: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +npmlog@^4.0.2, npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@>=2.0.1, nth-check@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +"nwmatcher@>= 1.3.7 < 2.0.0": + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== + +nyc@^15.0.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.assign@^4.0.4, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries-ponyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.entries-ponyfill/-/object.entries-ponyfill-1.0.1.tgz#29abdf77cbfbd26566dd1aa24e9d88f65433d256" + integrity sha1-Kavfd8v70mVm3RqiTp2I9lQz0lY= + +object.getownpropertydescriptors@^2.0.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" + integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opencollective-postinstall@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + +opener@*: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +optionator@^0.8.1, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + dependencies: + readable-stream "^2.0.1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + +p-filter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" + integrity sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw== + dependencies: + p-map "^2.0.0" + +p-is-promise@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971" + integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-props@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-props/-/p-props-4.0.0.tgz#f37c877a9a722057833e1dc38d43edf3906b3437" + integrity sha512-3iKFbPdoPG7Ne3cMA53JnjPsTMaIzE9gxKZnvKJJivTAeqLEZPBu6zfi6DYq9AsH1nYycWmo3sWCNI8Kz6T2Zg== + dependencies: + p-map "^4.0.0" + +p-reduce@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" + integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== + +p-reflect@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reflect/-/p-reflect-2.1.0.tgz#5d67c7b3c577c4e780b9451fc9129675bd99fe67" + integrity sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg== + +p-retry@^4.0.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.13.1" + +p-settle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/p-settle/-/p-settle-4.1.1.tgz#37fbceb2b02c9efc28658fc8d36949922266035f" + integrity sha512-6THGh13mt3gypcNMm0ADqVNCcYa3BK6DWsuJWFCuEKP1rpY+OKGp7gaZwVmLspmic01+fsg/fN57MfvDzZ/PuQ== + dependencies: + p-limit "^2.2.2" + p-reflect "^2.1.0" + +p-timeout@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-4.1.0.tgz#788253c0452ab0ffecf18a62dff94ff1bd09ca0a" + integrity sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw== + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + +pacote@*, pacote@^12.0.0: + version "12.0.2" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-12.0.2.tgz#14ae30a81fe62ec4fc18c071150e6763e932527c" + integrity sha512-Ar3mhjcxhMzk+OVZ8pbnXdb0l8+pimvlsqBGRNkble2NVgyqOGE3yrCGi/lAYq7E7NRDMz89R1Wx5HIMCGgeYg== + dependencies: + "@npmcli/git" "^2.1.0" + "@npmcli/installed-package-contents" "^1.0.6" + "@npmcli/promise-spawn" "^1.2.0" + "@npmcli/run-script" "^2.0.0" + cacache "^15.0.5" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.3" + mkdirp "^1.0.3" + npm-package-arg "^8.0.1" + npm-packlist "^3.0.0" + npm-pick-manifest "^6.0.0" + npm-registry-fetch "^11.0.0" + promise-retry "^2.0.1" + read-package-json-fast "^2.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.1.0" + +pacote@^11.3.0: + version "11.3.5" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" + integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== + dependencies: + "@npmcli/git" "^2.1.0" + "@npmcli/installed-package-contents" "^1.0.6" + "@npmcli/promise-spawn" "^1.2.0" + "@npmcli/run-script" "^1.8.2" + cacache "^15.0.5" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.3" + mkdirp "^1.0.3" + npm-package-arg "^8.0.1" + npm-packlist "^2.1.4" + npm-pick-manifest "^6.0.0" + npm-registry-fetch "^11.0.0" + promise-retry "^2.0.1" + read-package-json-fast "^2.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.1.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-conflict-json@*, parse-conflict-json@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-1.1.1.tgz#54ec175bde0f2d70abf6be79e0e042290b86701b" + integrity sha512-4gySviBiW5TRl7XHvp1agcS7SOe0KZOjC//71dzZVWJrY9hCrgtvl5v3SyIxCZ4fZF47TxD9nfzmxcx76xmbUw== + dependencies: + json-parse-even-better-errors "^2.3.0" + just-diff "^3.0.1" + just-diff-apply "^3.0.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@>=1.0.7, path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pg-connection-string@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" + integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== + +pg-hstore@^2.x: + version "2.3.4" + resolved "https://registry.yarnpkg.com/pg-hstore/-/pg-hstore-2.3.4.tgz#4425e3e2a3e15d2a334c35581186c27cf2e9b8dd" + integrity sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA== + dependencies: + underscore "^1.13.1" + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" + integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== + +pg-protocol@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" + integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.2.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471" + integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.5.0" + pg-pool "^3.4.1" + pg-protocol "^1.5.0" + pg-types "^2.1.0" + pgpass "1.x" + +pgpass@1.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.4.tgz#85eb93a83800b20f8057a2b029bf05abaf94ea9c" + integrity sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w== + dependencies: + split2 "^3.1.1" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pkg-conf@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +proc-log@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-1.0.0.tgz#0d927307401f69ed79341e83a0b2c9a13395eb77" + integrity sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg== + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process-on-spawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" + integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== + dependencies: + fromentries "^1.2.0" + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +promzard@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4= + dependencies: + read "1" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qrcode-terminal@*: + version "0.12.0" + resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +ramda@^0.27.0: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + +rc@^1.2.7, rc@^1.2.8, rc@~1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-cmd-shim@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" + integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== + +read-package-json-fast@*, read-package-json-fast@^2.0.1, read-package-json-fast@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== + dependencies: + json-parse-even-better-errors "^2.3.0" + npm-normalize-package-bin "^1.0.1" + +read-package-json@*, read-package-json@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-4.1.1.tgz#153be72fce801578c1c86b8ef2b21188df1b9eea" + integrity sha512-P82sbZJ3ldDrWCOSKxJT0r/CXMWR0OR3KRh55SgKo3p91GSIEEC32v3lSHAvO/UcH3/IoL7uqhOFBduAnwdldw== + dependencies: + glob "^7.1.1" + json-parse-even-better-errors "^2.3.0" + normalize-package-data "^3.0.0" + npm-normalize-package-bin "^1.0.0" + +read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.0.0, read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +read@*, read@1, read@^1.0.7, read@~1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= + dependencies: + mute-stream "~0.0.4" + +readable-stream@1.1: + version "1.1.13" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" + integrity sha1-9u73ZPUUyJ4rniMUanW6EGdW0j4= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + 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" + +readdir-scoped-modules@*, readdir-scoped-modules@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== + dependencies: + debuglog "^1.0.1" + dezalgo "^1.0.0" + graceful-fs "^4.1.2" + once "^1.3.0" + +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= + dependencies: + esprima "~4.0.0" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regextras@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.7.1.tgz#be95719d5f43f9ef0b9fa07ad89b7c606995a3b2" + integrity sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w== + +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= + dependencies: + es6-error "^4.0.1" + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeating@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac" + integrity sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw= + dependencies: + is-finite "^1.0.0" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + +"request@>= 2.52.0", request@^2.55.0, request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-global@1.0.0, resolve-global@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" + integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== + dependencies: + global-dirs "^0.1.1" + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + dependencies: + value-or-function "^3.0.0" + +resolve@^1.10.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry-as-promised@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-3.2.0.tgz#769f63d536bec4783549db0777cb56dadd9d8543" + integrity sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg== + dependencies: + any-promise "^1.3.0" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@*, rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.6.1, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +rxjs@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" + integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== + dependencies: + tslib "~2.1.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@>=0.6.0, sax@^1.1.4, sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semantic-release-fail-on-major-bump@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semantic-release-fail-on-major-bump/-/semantic-release-fail-on-major-bump-1.0.0.tgz#a4fe055258415040f6170c175596cedb4d4ffb82" + integrity sha512-vFbUVEQC60p3n+0NJc4D+Z6TS+5Q4AdG72pe5hsmcVEcaK+w+nPxjefLl3bJjphxc6AVH9cAZM0ZTnmiTG6eLA== + +semantic-release@^17.3.0: + version "17.4.7" + resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-17.4.7.tgz#88e1dce7294cc43acc54c4e0a83f582264567206" + integrity sha512-3Ghu8mKCJgCG3QzE5xphkYWM19lGE3XjFdOXQIKBM2PBpBvgFQ/lXv31oX0+fuN/UjNFO/dqhNs8ATLBhg6zBg== + dependencies: + "@semantic-release/commit-analyzer" "^8.0.0" + "@semantic-release/error" "^2.2.0" + "@semantic-release/github" "^7.0.0" + "@semantic-release/npm" "^7.0.0" + "@semantic-release/release-notes-generator" "^9.0.0" + aggregate-error "^3.0.0" + cosmiconfig "^7.0.0" + debug "^4.0.0" + env-ci "^5.0.0" + execa "^5.0.0" + figures "^3.0.0" + find-versions "^4.0.0" + get-stream "^6.0.0" + git-log-parser "^1.2.0" + hook-std "^2.0.0" + hosted-git-info "^4.0.0" + lodash "^4.17.21" + marked "^2.0.0" + marked-terminal "^4.1.1" + micromatch "^4.0.2" + p-each-series "^2.1.0" + p-reduce "^2.0.0" + read-pkg-up "^7.0.0" + resolve-from "^5.0.0" + semver "^7.3.2" + semver-diff "^3.1.1" + signale "^1.2.1" + yargs "^16.2.0" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver-regex@>=3.1.3: + version "4.0.2" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-4.0.2.tgz#fd3124efe81647b33eb90a9de07cb72992424a02" + integrity sha512-xyuBZk1XYqQkB687hMQqrCP+J9bdJSjPpZwdmmNjyxKW1K3LDXxqxw91Egaqkh/yheBIVtKPt4/1eybKVdCx3g== + +semver-regex@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" + integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== + +semver@*, semver@^7.1.1, semver@^7.1.2, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= + +sequelize-pool@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-6.1.0.tgz#caaa0c1e324d3c2c3a399fed2c7998970925d668" + integrity sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg== + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.5" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" + integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== + +signale@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" + integrity sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w== + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + pkg-conf "^2.1.0" + +sinon-chai@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783" + integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g== + +sinon@^9.0.2: + version "9.2.4" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" + integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== + dependencies: + "@sinonjs/commons" "^1.8.1" + "@sinonjs/fake-timers" "^6.0.1" + "@sinonjs/samsam" "^5.3.1" + diff "^4.0.2" + nise "^4.0.4" + supports-color "^7.1.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +smart-buffer@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3" + integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg== + dependencies: + agent-base "^6.0.2" + debug "^4.3.1" + socks "^2.6.1" + +socks@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.1.0" + +source-map@^0.5.0, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-error-forwarder@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz#1afd94738e999b0346d7b9fc373be55e07577029" + integrity sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk= + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== + +split2@^3.0.0, split2@^3.1.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +split2@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-1.0.0.tgz#52e2e221d88c75f9a73f90556e263ff96772b314" + integrity sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ= + dependencies: + through2 "~2.0.0" + +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sqlite3@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901" + integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg== + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.11.0" + +sqlstring@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514" + integrity sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +ssri@*, ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + +stream-combiner2@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +string-argv@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.1 || ^2.0.0", "string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +stringify-package@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" + integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +"strip-ansi@^3.0.1 || ^4.0.0", strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@2.0.1, strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + dependencies: + has-flag "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +"symbol-tree@>= 3.1.0 < 4.0.0": + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +taffydb@2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.7.2.tgz#7bf8106a5c1a48251b3e3bc0a0e1732489fd0dc8" + integrity sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg= + +taffydb@2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.7.3.tgz#2ad37169629498fca5bc84243096d3cde0ec3a34" + integrity sha1-KtNxaWKUmPylvIQkMJbTzeDsOjQ= + +tar@*, tar@>=4.4.18, tar@^6.0.2, tar@^6.1.0, tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tar@^4: + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + +tedious@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/tedious/-/tedious-8.3.0.tgz#74d3d434638b0bdd02b6266f041c003ceca93f67" + integrity sha512-v46Q9SRVgz6IolyPdlsxQtfm9q/sqDs+y4aRFK0ET1iKitbpzCCQRHb6rnVcR1FLnLR0Y7AgcqnWUoMPUXz9HA== + dependencies: + "@azure/ms-rest-nodeauth" "2.0.2" + "@js-joda/core" "^2.0.0" + bl "^3.0.0" + depd "^2.0.0" + iconv-lite "^0.5.0" + jsbi "^3.1.1" + native-duplexpair "^1.0.0" + punycode "^2.1.0" + readable-stream "^3.6.0" + sprintf-js "^1.1.2" + +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +tempy@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-1.0.1.tgz#30fe901fd869cfb36ee2bd999805aa72fbb035de" + integrity sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w== + dependencies: + del "^6.0.0" + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" + integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== + +text-table@*, text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tiny-relative-date@*: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" + integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + dependencies: + through2 "^2.0.3" + +toposort-class@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" + integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg= + +tough-cookie@^2.2.0, tough-cookie@^2.4.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@~0.0.1, tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +traverse@~0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" + integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= + +treeverse@*, treeverse@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-1.0.4.tgz#a6b0ebf98a1bca6846ddc7ecbc900df08cb9cd5f" + integrity sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g== + +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +tslib@^1.9.0, tslib@^1.9.2: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tunnel@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.0, type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.1.3: + version "4.4.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" + integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +uglify-js@^3.1.4: + version "3.14.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.3.tgz#c0f25dfea1e8e5323eccf59610be08b6043c15cf" + integrity sha512-mic3aOdiq01DuSVx0TseaEzMIVqebMZ0Z3vaeDhFEh9bsc24hV1TFvN74reA2vs08D0ZWfNjAcJ3UbVLaBss+g== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + +"underscore@>= 1.3.1", underscore@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" + integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unique-stream@^2.0.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-join@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.1.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@*, validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= + dependencies: + builtins "^1.0.3" + +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vinyl-fs@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^2.0.0, vinyl@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" + integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + +wcwidth@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +webidl-conversions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506" + integrity sha1-O/glj30xjHRDw28uFpQCoaZwNQY= + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url-compat@~0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz#00898111af689bb097541cd5a45ca6c8798445bf" + integrity sha1-AImBEa9om7CXVBzVpFymyHmERb8= + dependencies: + tr46 "~0.0.1" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +which@*, which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +which@1.3.1, which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wide-align@^1.1.0, wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wkx@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.5.0.tgz#c6c37019acf40e517cc6b94657a25a3d4aa33e8c" + integrity sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg== + dependencies: + "@types/node" "*" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@*, write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +"xml-name-validator@>= 2.0.1 < 3.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= + +xml2js@^0.4.19: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +"xmldom@>= 0.1.x": + version "0.6.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== + +xpath.js@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1" + integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== + +xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + +yargs@13.3.2, yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + 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.1.2" + +yargs@^15.0.2, yargs@^15.1.0: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 8db830af9feb08e762de56cb972844eb50dbff1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Boull=C3=A9?= Date: Sun, 7 Nov 2021 07:47:53 +0100 Subject: [PATCH 387/414] refactor: remove joinTableDependent exception for sqlite (#12643) --- lib/dialects/abstract/index.js | 1 - lib/dialects/abstract/query-generator.js | 21 +++------ lib/dialects/sqlite/index.js | 1 - test/integration/include/findAll.test.js | 54 ++++++++++++++++++++++-- test/unit/sql/select.test.js | 3 +- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/lib/dialects/abstract/index.js b/lib/dialects/abstract/index.js index 57d681fac84b..c9e3c91c9bde 100644 --- a/lib/dialects/abstract/index.js +++ b/lib/dialects/abstract/index.js @@ -61,7 +61,6 @@ AbstractDialect.prototype.supports = { functionBased: false, operator: false }, - joinTableDependent: true, groupedLimit: true, indexViaAlter: false, JSON: false, diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index aa28a6b8d921..664487d95f87 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1876,22 +1876,13 @@ class QueryGenerator { throughWhere = this.getWhereConditions(through.where, this.sequelize.literal(this.quoteIdentifier(throughAs)), through.model); } - if (this._dialect.supports.joinTableDependent) { - // Generate a wrapped join so that the through table join can be dependent on the target join - joinBody = `( ${this.quoteTable(throughTable, throughAs)} INNER JOIN ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)} ON ${targetJoinOn}`; - if (throughWhere) { - joinBody += ` AND ${throughWhere}`; - } - joinBody += ')'; - joinCondition = sourceJoinOn; - } else { - // Generate join SQL for left side of through - joinBody = `${this.quoteTable(throughTable, throughAs)} ON ${sourceJoinOn} ${joinType} ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)}`; - joinCondition = targetJoinOn; - if (throughWhere) { - joinCondition += ` AND ${throughWhere}`; - } + // Generate a wrapped join so that the through table join can be dependent on the target join + joinBody = `( ${this.quoteTable(throughTable, throughAs)} INNER JOIN ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)} ON ${targetJoinOn}`; + if (throughWhere) { + joinBody += ` AND ${throughWhere}`; } + joinBody += ')'; + joinCondition = sourceJoinOn; if (include.where || include.through.where) { if (include.where) { diff --git a/lib/dialects/sqlite/index.js b/lib/dialects/sqlite/index.js index fe8cb352603c..35ecbe9b5e06 100644 --- a/lib/dialects/sqlite/index.js +++ b/lib/dialects/sqlite/index.js @@ -48,7 +48,6 @@ SqliteDialect.prototype.supports = _.merge( addConstraint: false, dropConstraint: false }, - joinTableDependent: false, groupedLimit: false, JSON: true } diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index bdbc58178f21..0ce86e1eb222 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -90,7 +90,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { { name: 'Designers' }, { name: 'Managers' } ]); - const groups = await Group.findAll(); + const groups = await Group.findAll(); await Company.bulkCreate([ { name: 'Sequelize' }, { name: 'Coca Cola' }, @@ -851,8 +851,8 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); it('should be possible to define a belongsTo include as required with child hasMany not required', async function() { - const Address = this.sequelize.define('Address', { 'active': DataTypes.BOOLEAN }), - Street = this.sequelize.define('Street', { 'active': DataTypes.BOOLEAN }), + const Address = this.sequelize.define('Address', { 'active': DataTypes.BOOLEAN }), + Street = this.sequelize.define('Street', { 'active': DataTypes.BOOLEAN }), User = this.sequelize.define('User', { 'username': DataTypes.STRING }); // Associate @@ -1920,7 +1920,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { expect(parseInt(post['comments.commentCount'], 10)).to.equal(3); }); - it('Should return posts with nested include with inner join with a m:n association', async function() { + it('should return posts with nested include with inner join with a m:n association', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -2083,5 +2083,51 @@ describe(Support.getTestDialectTeaser('Include'), () => { expect(product.Prices).to.be.an('array'); } }); + + it('should allow through model to be paranoid', async function() { + const User = this.sequelize.define('user', { name: DataTypes.STRING }, { timestamps: false }); + const Customer = this.sequelize.define('customer', { name: DataTypes.STRING }, { timestamps: false }); + const UserCustomer = this.sequelize.define( + 'user_customer', + {}, + { paranoid: true, createdAt: false, updatedAt: false } + ); + User.belongsToMany(Customer, { through: UserCustomer }); + + await this.sequelize.sync({ force: true }); + + const [user, customer1, customer2] = await Promise.all([ + User.create({ name: 'User 1' }), + Customer.create({ name: 'Customer 1' }), + Customer.create({ name: 'Customer 2' }) + ]); + await user.setCustomers([customer1]); + await user.setCustomers([customer2]); + + const users = await User.findAll({ include: Customer }); + + expect(users).to.be.an('array'); + expect(users).to.be.lengthOf(1); + const customers = users[0].customers; + + expect(customers).to.be.an('array'); + expect(customers).to.be.lengthOf(1); + + const user_customer = customers[0].user_customer; + + expect(user_customer.deletedAt).not.to.exist; + + const userCustomers = await UserCustomer.findAll({ + paranoid: false + }); + + expect(userCustomers).to.be.an('array'); + expect(userCustomers).to.be.lengthOf(2); + + const [nonDeletedUserCustomers, deletedUserCustomers] = _.partition(userCustomers, userCustomer => !userCustomer.deletedAt); + + expect(nonDeletedUserCustomers).to.be.lengthOf(1); + expect(deletedUserCustomers).to.be.lengthOf(1); + }); }); }); diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js index 727ae7790c75..6f44cd46bace 100644 --- a/test/unit/sql/select.test.js +++ b/test/unit/sql/select.test.js @@ -444,8 +444,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }).include, model: User }, User), { - default: `SELECT [user].[id_user], [user].[id], [projects].[id] AS [projects.id], [projects].[title] AS [projects.title], [projects].[createdAt] AS [projects.createdAt], [projects].[updatedAt] AS [projects.updatedAt], [projects->project_user].[user_id] AS [projects.project_user.userId], [projects->project_user].[project_id] AS [projects.project_user.projectId] FROM [User] AS [user] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN ( [project_users] AS [projects->project_user] INNER JOIN [projects] AS [projects] ON [projects].[id] = [projects->project_user].[project_id]) ON [user].[id_user] = [projects->project_user].[user_id];`, - sqlite: `SELECT \`user\`.\`id_user\`, \`user\`.\`id\`, \`projects\`.\`id\` AS \`projects.id\`, \`projects\`.\`title\` AS \`projects.title\`, \`projects\`.\`createdAt\` AS \`projects.createdAt\`, \`projects\`.\`updatedAt\` AS \`projects.updatedAt\`, \`projects->project_user\`.\`user_id\` AS \`projects.project_user.userId\`, \`projects->project_user\`.\`project_id\` AS \`projects.project_user.projectId\` FROM \`User\` AS \`user\` ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN \`project_users\` AS \`projects->project_user\` ON \`user\`.\`id_user\` = \`projects->project_user\`.\`user_id\` LEFT OUTER JOIN \`projects\` AS \`projects\` ON \`projects\`.\`id\` = \`projects->project_user\`.\`project_id\`;` + default: `SELECT [user].[id_user], [user].[id], [projects].[id] AS [projects.id], [projects].[title] AS [projects.title], [projects].[createdAt] AS [projects.createdAt], [projects].[updatedAt] AS [projects.updatedAt], [projects->project_user].[user_id] AS [projects.project_user.userId], [projects->project_user].[project_id] AS [projects.project_user.projectId] FROM [User] AS [user] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN ( [project_users] AS [projects->project_user] INNER JOIN [projects] AS [projects] ON [projects].[id] = [projects->project_user].[project_id]) ON [user].[id_user] = [projects->project_user].[user_id];` }); }); From cdd61ddbe83cbfe77dc04a32196dcc66e0052f51 Mon Sep 17 00:00:00 2001 From: Rick Bergfalk Date: Sun, 7 Nov 2021 04:04:51 -0600 Subject: [PATCH 388/414] fix(mariadb): fix MariaDB 10.5 JSON (#13633) --- .github/workflows/ci.yml | 8 ++++++++ lib/dialects/mariadb/query.js | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed20f83a1558..97b6573d77e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,14 @@ jobs: image: mariadb:10.3 dialect: mariadb node-version: 12 + - name: MariaDB 10.5 + image: mariadb:10.5 + dialect: mariadb + node-version: 10 + - name: MariaDB 10.5 + image: mariadb:10.5 + dialect: mariadb + node-version: 12 name: ${{ matrix.name }} (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest services: diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index 2f78761d91bc..b5c346478542 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -183,8 +183,9 @@ class Query extends AbstractQuery { if (modelField.type instanceof DataTypes.JSON) { // Value is returned as String, not JSON rows = rows.map(row => { - row[modelField.fieldName] = row[modelField.fieldName] ? JSON.parse( - row[modelField.fieldName]) : null; + if (row[modelField.fieldName] && typeof row[modelField.fieldName] === 'string') { + row[modelField.fieldName] = JSON.parse(row[modelField.fieldName]); + } if (DataTypes.JSON.parse) { return DataTypes.JSON.parse(modelField, this.sequelize.options, row[modelField.fieldName]); From 37a5858b1e635a28dee1da494f278753d489bbe8 Mon Sep 17 00:00:00 2001 From: Daniel Durante Date: Sun, 7 Nov 2021 12:31:35 -0500 Subject: [PATCH 389/414] feat(definitions): Adds AbstractQuery and before/afterQuery hook definitions (#13635) --- types/lib/hooks.d.ts | 13 +- types/lib/query.d.ts | 328 +++++++++++++++++++++++++++++++++++++++++++ types/test/hooks.ts | 16 ++- 3 files changed, 348 insertions(+), 9 deletions(-) create mode 100644 types/lib/query.d.ts diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index 480495bfa461..f62f7f61573e 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -4,17 +4,15 @@ import Model, { BulkCreateOptions, CountOptions, CreateOptions, - DestroyOptions, - RestoreOptions, - UpsertOptions, - FindOptions, + DestroyOptions, FindOptions, InstanceDestroyOptions, InstanceRestoreOptions, InstanceUpdateOptions, ModelAttributes, - ModelOptions, - UpdateOptions, + ModelOptions, RestoreOptions, UpdateOptions, UpsertOptions } from './model'; +import { AbstractQuery } from './query'; +import { QueryOptions } from './query-interface'; import { Config, Options, Sequelize, SyncOptions } from './sequelize'; import { DeepWriteable } from './utils'; @@ -62,8 +60,11 @@ export interface ModelHooks { afterSync(options: SyncOptions): HookReturn; beforeBulkSync(options: SyncOptions): HookReturn; afterBulkSync(options: SyncOptions): HookReturn; + beforeQuery(options: QueryOptions, query: AbstractQuery): HookReturn; + afterQuery(options: QueryOptions, query: AbstractQuery): HookReturn; } + export interface SequelizeHooks< M extends Model = Model, TAttributes = any, diff --git a/types/lib/query.d.ts b/types/lib/query.d.ts new file mode 100644 index 000000000000..b051d71bd54f --- /dev/null +++ b/types/lib/query.d.ts @@ -0,0 +1,328 @@ +import { IncludeOptions } from '..'; +import { Connection } from './connection-manager'; +import { + Model, ModelType +} from './model'; +import { Sequelize } from './sequelize'; +import QueryTypes = require('./query-types'); + +type BindOrReplacements = { [key: string]: unknown } | unknown[]; +type FieldMap = { [key: string]: string }; + + +export interface AbstractQueryGroupJoinDataOptions { + checkExisting: boolean; +} + +export interface AbstractQueryOptions { + instance?: Model; + model?: ModelType; + type?: QueryTypes; + + fieldMap?: boolean; + plain: boolean; + raw: boolean; + nest: boolean; + hasJoin: boolean; + + /** + * A function that gets executed while running the query to log the sql. + */ + logging?: boolean | ((sql: string, timing?: number) => void); + + include: boolean; + includeNames: unknown[]; + includeMap: any; + + originalAttributes: unknown[]; + attributes: unknown[]; +} + +export interface AbstractQueryFormatBindOptions { + /** + * skip unescaping $$ + */ + skipUnescape: boolean; + + /** + * do not replace (but do unescape $$) + */ + skipValueReplace: boolean; +} + +type replacementFuncType = ((match: string, key: string, values: unknown[], timeZone?: string, dialect?: string, options?: AbstractQueryFormatBindOptions) => undefined | string); + +/** +* An abstract class that Sequelize uses to add query support for a dialect. +* +* This interface is only exposed when running before/afterQuery lifecycle events. +*/ +export class AbstractQuery { + /** + * Returns a unique identifier assigned to a query internally by Sequelize. + */ + public uuid: unknown; + + /** + * A Sequelize connection instance. + * + * @type {Connection} + * @memberof AbstractQuery + */ + public connection: Connection; + + /** + * If provided, returns the model instance. + * + * @type {Model} + * @memberof AbstractQuery + */ + public instance: Model; + + /** + * Model type definition. + * + * @type {ModelType} + * @memberof AbstractQuery + */ + public model: ModelType; + + /** + * Returns the current sequelize instance. + */ + public sequelize: Sequelize; + + /** + * + * @type {AbstractQueryOptions} + * @memberof AbstractQuery + */ + public options: AbstractQueryOptions; + + constructor(connection: Connection, sequelize: Sequelize, options?: AbstractQueryOptions); + + /** + * rewrite query with parameters + * + * Examples: + * + * query.formatBindParameters('select $1 as foo', ['fooval']); + * + * query.formatBindParameters('select $foo as foo', { foo: 'fooval' }); + * + * Options + * skipUnescape: bool, skip unescaping $$ + * skipValueReplace: bool, do not replace (but do unescape $$). Check correct syntax and if all values are available + * + * @param {string} sql + * @param {object|Array} values + * @param {string} dialect + * @param {Function} [replacementFunc] + * @param {object} [options] + * @private + */ + static formatBindParameters(sql: string, values: object | Array, dialect: string, replacementFunc: replacementFuncType, options: AbstractQueryFormatBindOptions): undefined | [string, unknown[]]; + + /** + * Execute the passed sql query. + * + * Examples: + * + * query.run('SELECT 1') + * + * @private + */ + private run(): Error + + /** + * Check the logging option of the instance and print deprecation warnings. + * + * @private + */ + private checkLoggingOption(): void; + + /** + * Get the attributes of an insert query, which contains the just inserted id. + * + * @returns {string} The field name. + * @private + */ + private getInsertIdField(): string; + + /** + * Returns the unique constraint error message for the associated field. + * + * @param field {string} the field name associated with the unique constraint. + * + * @returns {string} The unique constraint error message. + * @private + */ + private getUniqueConstraintErrorMessage(field: string): string; + + /** + * Checks if the query type is RAW + * @returns {boolean} + */ + public isRawQuery(): boolean; + + /** + * Checks if the query type is VERSION + * @returns {boolean} + */ + public isVersionQuery(): boolean; + + /** + * Checks if the query type is UPSERT + * @returns {boolean} + */ + public isUpsertQuery(): boolean; + + /** + * Checks if the query type is INSERT + * @returns {boolean} + */ + public isInsertQuery(results?: unknown[], metaData?: unknown): boolean; + + /** + * Sets auto increment field values (if applicable). + * + * @param results {Array} + * @param metaData {object} + * @returns {boolean} + */ + public handleInsertQuery(results?: unknown[], metaData?: unknown): void; + + /** + * Checks if the query type is SHOWTABLES + * @returns {boolean} + */ + public isShowTablesQuery(): boolean; + + /** + * Flattens and plucks values from results. + * + * @params {Array} + * @returns {Array} + */ + public handleShowTablesQuery(results: unknown[]): unknown[]; + + /** + * Checks if the query type is SHOWINDEXES + * @returns {boolean} + */ + public isShowIndexesQuery(): boolean; + + /** + * Checks if the query type is SHOWCONSTRAINTS + * @returns {boolean} + */ + public isShowConstraintsQuery(): boolean; + + /** + * Checks if the query type is DESCRIBE + * @returns {boolean} + */ + public isDescribeQuery(): boolean; + + /** + * Checks if the query type is SELECT + * @returns {boolean} + */ + public isSelectQuery(): boolean; + + /** + * Checks if the query type is BULKUPDATE + * @returns {boolean} + */ + public isBulkUpdateQuery(): boolean; + + /** + * Checks if the query type is BULKDELETE + * @returns {boolean} + */ + public isBulkDeleteQuery(): boolean; + + /** + * Checks if the query type is FOREIGNKEYS + * @returns {boolean} + */ + public isForeignKeysQuery(): boolean; + + /** + * Checks if the query type is UPDATE + * @returns {boolean} + */ + public isUpdateQuery(): boolean; + + /** + * Maps raw fields to attribute names (if applicable). + * + * @params {Model[]} results from a select query. + * @returns {Model} the first model instance within the select. + */ + public handleSelectQuery(results: Model[]): Model; + + /** + * Checks if the query starts with 'show' or 'describe' + * @returns {boolean} + */ + public isShowOrDescribeQuery(): boolean; + + /** + * Checks if the query starts with 'call' + * @returns {boolean} + */ + public isCallQuery(): boolean; + + /** + * @param {string} sql + * @param {Function} debugContext + * @param {Array|object} parameters + * @protected + * @returns {Function} A function to call after the query was completed. + */ + protected _logQuery(sql: string, debugContext: ((msg: string) => any), parameters: unknown[]): () => void; + + /** + * The function takes the result of the query execution and groups + * the associated data by the callee. + * + * Example: + * groupJoinData([ + * { + * some: 'data', + * id: 1, + * association: { foo: 'bar', id: 1 } + * }, { + * some: 'data', + * id: 1, + * association: { foo: 'bar', id: 2 } + * }, { + * some: 'data', + * id: 1, + * association: { foo: 'bar', id: 3 } + * } + * ]) + * + * Result: + * Something like this: + * + * [ + * { + * some: 'data', + * id: 1, + * association: [ + * { foo: 'bar', id: 1 }, + * { foo: 'bar', id: 2 }, + * { foo: 'bar', id: 3 } + * ] + * } + * ] + * + * @param {Array} rows + * @param {object} includeOptions + * @param {object} options + * @private + */ + static _groupJoinData(rows: unknown[], includeOptions: IncludeOptions, options: AbstractQueryGroupJoinDataOptions): unknown[]; +} diff --git a/types/test/hooks.ts b/types/test/hooks.ts index 340038759c41..8bc11bde189a 100644 --- a/types/test/hooks.ts +++ b/types/test/hooks.ts @@ -1,9 +1,10 @@ import { expectTypeOf } from "expect-type"; -import { SemiDeepWritable } from "./type-helpers/deep-writable"; -import { Model, SaveOptions, Sequelize, FindOptions, ModelCtor, ModelType, ModelDefined, ModelStatic, UpsertOptions } from "sequelize"; +import { FindOptions, Model, QueryOptions, SaveOptions, Sequelize, UpsertOptions } from "sequelize"; import { ModelHooks } from "../lib/hooks"; -import { DeepWriteable } from '../lib/utils'; +import { AbstractQuery } from "../lib/query"; import { Config } from '../lib/sequelize'; +import { DeepWriteable } from '../lib/utils'; +import { SemiDeepWritable } from "./type-helpers/deep-writable"; { class TestModel extends Model {} @@ -29,6 +30,14 @@ import { Config } from '../lib/sequelize'; expectTypeOf(m).toEqualTypeOf<[ TestModel, boolean | null ]>(); expectTypeOf(options).toEqualTypeOf(); }, + beforeQuery(options, query) { + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(query).toEqualTypeOf(); + }, + afterQuery(options, query) { + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(query).toEqualTypeOf(); + }, }; const sequelize = new Sequelize('uri', { hooks }); @@ -70,6 +79,7 @@ import { Config } from '../lib/sequelize'; hooks.beforeFindAfterOptions = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; hooks.beforeSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; hooks.beforeBulkSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeQuery = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; hooks.beforeUpsert = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; } From 4ff26c1ae3e2eff380f0d1eb65b844250ae2356b Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Mon, 8 Nov 2021 10:16:20 +0100 Subject: [PATCH 390/414] ci(stale): update stale timing to 14 days each (#13636) --- .github/workflows/stale.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 91a8170d5313..7b83ca472f5f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,9 +12,9 @@ jobs: id: stale with: stale-issue-label: "stale" - stale-issue-message: 'This issue has been automatically marked as stale because it has been open for 7 days without activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment or remove the "stale" label. 🙂' - days-before-stale: 7 - days-before-close: 5 + stale-issue-message: 'This issue has been automatically marked as stale because it has been open for 14 days without activity. It will be closed if no further activity occurs within the next 14 days. If this is still an issue, just leave a comment or remove the "stale" label. 🙂' + days-before-stale: 14 + days-before-close: 14 operations-per-run: 1000 - name: Print outputs run: echo ${{ join(steps.stale.outputs.*, ',') }} From ddddc244c2019a765ad889226584b8fb07ff50da Mon Sep 17 00:00:00 2001 From: Danny Sullivan Date: Wed, 10 Nov 2021 01:37:11 -0500 Subject: [PATCH 391/414] fix(logger): change logging depth from 3 to 1 (#12879) --- lib/utils/logger.js | 2 +- .../dialects/abstract/query-generator.test.js | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/utils/logger.js b/lib/utils/logger.js index 0b2ca26a53dd..35944c806fec 100644 --- a/lib/utils/logger.js +++ b/lib/utils/logger.js @@ -27,7 +27,7 @@ class Logger { } inspect(value) { - return util.inspect(value, false, 3); + return util.inspect(value, false, 1); } debugContext(name) { diff --git a/test/unit/dialects/abstract/query-generator.test.js b/test/unit/dialects/abstract/query-generator.test.js index 25cd79391316..6a216cec5af5 100644 --- a/test/unit/dialects/abstract/query-generator.test.js +++ b/test/unit/dialects/abstract/query-generator.test.js @@ -41,6 +41,25 @@ describe('QueryGenerator', () => { expect(() => QG.whereItemQuery('test', { $in: [4] })) .to.throw('Invalid value { \'$in\': [ 4 ] }'); + + // simulate transaction passed into where query argument + class Sequelize { + constructor() { + this.config = { + password: 'password' + }; + } + } + + class Transaction { + constructor() { + this.sequelize = new Sequelize(); + } + } + + expect(() => QG.whereItemQuery('test', new Transaction())).to.throw( + 'Invalid value Transaction { sequelize: Sequelize { config: [Object] } }' + ); }); it('should parse set aliases strings as operators', function() { @@ -114,4 +133,3 @@ describe('QueryGenerator', () => { }); }); }); - From f58154334d98038deafbecd017cf5719d1b13b7f Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Tue, 9 Nov 2021 23:41:31 -0800 Subject: [PATCH 392/414] fix(query): make stacktraces include original calling code (#13347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ᛜ ᛝᛉᚲ --- lib/dialects/mariadb/query.js | 12 +-- lib/dialects/mssql/query.js | 27 +++--- lib/dialects/mysql/query.js | 12 +-- lib/dialects/postgres/query.js | 27 ++++-- lib/dialects/sqlite/query.js | 18 ++-- lib/errors/database-error.js | 10 ++- .../database/exclusion-constraint-error.js | 2 +- .../database/foreign-key-constraint-error.js | 2 +- lib/errors/database/timeout-error.js | 4 +- .../database/unknown-constraint-error.js | 2 +- lib/errors/validation-error.js | 7 +- .../validation/unique-constraint-error.js | 2 +- test/integration/sequelize/query.test.js | 90 ++++++++++++++++++- 13 files changed, 168 insertions(+), 47 deletions(-) diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index b5c346478542..12d8f9c9d5b3 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -44,6 +44,7 @@ class Query extends AbstractQuery { } let results; + const errForStack = new Error(); try { results = await connection.query(this.sql, parameters); @@ -63,7 +64,7 @@ class Query extends AbstractQuery { error.sql = sql; error.parameters = parameters; - throw this.formatError(error); + throw this.formatError(error, errForStack.stack); } finally { complete(); } @@ -220,7 +221,7 @@ class Query extends AbstractQuery { return results; } - formatError(err) { + formatError(err, errStack) { switch (err.errno) { case ER_DUP_ENTRY: { const match = err.message.match( @@ -252,7 +253,7 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } case ER_ROW_IS_REFERENCED: @@ -270,12 +271,13 @@ class Query extends AbstractQuery { fields, value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined, index: match ? match[2] : undefined, - parent: err + parent: err, + stack: errStack }); } default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index e95aac6f6f66..bbead007705a 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -44,7 +44,7 @@ class Query extends AbstractQuery { return paramType; } - async _run(connection, sql, parameters) { + async _run(connection, sql, parameters, errStack) { this.sql = sql; const { options } = this; @@ -90,7 +90,7 @@ class Query extends AbstractQuery { err.sql = sql; err.parameters = parameters; - throw this.formatError(err); + throw this.formatError(err, errStack); } complete(); @@ -116,7 +116,10 @@ class Query extends AbstractQuery { } run(sql, parameters) { - return this.connection.queue.enqueue(() => this._run(this.connection, sql, parameters)); + const errForStack = new Error(); + return this.connection.queue.enqueue(() => + this._run(this.connection, sql, parameters, errForStack.stack) + ); } static formatBindParameters(sql, values, dialect) { @@ -248,7 +251,7 @@ class Query extends AbstractQuery { }); } - formatError(err) { + formatError(err, errStack) { let match; match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/); @@ -282,7 +285,7 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./) || @@ -292,7 +295,8 @@ class Query extends AbstractQuery { return new sequelizeErrors.ForeignKeyConstraintError({ fields: null, index: match[1], - parent: err + parent: err, + stack: errStack }); } @@ -307,11 +311,12 @@ class Query extends AbstractQuery { message: match[1], constraint, table, - parent: err + parent: err, + stack: errStack }); } - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } isShowOrDescribeQuery() { @@ -385,14 +390,14 @@ class Query extends AbstractQuery { for (const key in results[0]) { if (Object.prototype.hasOwnProperty.call(results[0], key)) { const record = results[0][key]; - + const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); - + this.instance.dataValues[attr && attr.fieldName || key] = record; } } } - + } } } diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index f607abc8c58f..10219783398e 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -43,6 +43,7 @@ class Query extends AbstractQuery { } let results; + const errForStack = new Error(); try { if (parameters && parameters.length) { @@ -74,7 +75,7 @@ class Query extends AbstractQuery { error.sql = sql; error.parameters = parameters; - throw this.formatError(error); + throw this.formatError(error, errForStack.stack); } finally { complete(); } @@ -207,7 +208,7 @@ class Query extends AbstractQuery { return results; } - formatError(err) { + formatError(err, errStack) { const errCode = err.errno || err.code; switch (errCode) { @@ -239,7 +240,7 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } case ER_ROW_IS_REFERENCED: @@ -257,12 +258,13 @@ class Query extends AbstractQuery { fields, value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined, index: match ? match[2] : undefined, - parent: err + parent: err, + stack: errStack }); } default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 535cab312c5c..3640bda38648 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -73,6 +73,7 @@ class Query extends AbstractQuery { const complete = this._logQuery(sql, debug, parameters); let queryResult; + const errForStack = new Error(); try { queryResult = await query; @@ -84,7 +85,7 @@ class Query extends AbstractQuery { err.sql = sql; err.parameters = parameters; - throw this.formatError(err); + throw this.formatError(err, errForStack.stack); } complete(); @@ -299,7 +300,7 @@ class Query extends AbstractQuery { return rows; } - formatError(err) { + formatError(err, errStack) { let match; let table; let index; @@ -318,7 +319,14 @@ class Query extends AbstractQuery { table = errMessage.match(/on table "(.+?)"/); table = table ? table[1] : undefined; - return new sequelizeErrors.ForeignKeyConstraintError({ message: errMessage, fields: null, index, table, parent: err }); + return new sequelizeErrors.ForeignKeyConstraintError({ + message: errMessage, + fields: null, + index, + table, + parent: err, + stack: errStack + }); case '23505': // there are multiple different formats of error messages for this error code // this regex should check at least two @@ -347,12 +355,13 @@ class Query extends AbstractQuery { }); } - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } return new sequelizeErrors.UniqueConstraintError({ message: errMessage, - parent: err + parent: err, + stack: errStack }); case '23P01': @@ -368,7 +377,8 @@ class Query extends AbstractQuery { constraint: err.constraint, fields, table: err.table, - parent: err + parent: err, + stack: errStack }); case '42704': @@ -384,12 +394,13 @@ class Query extends AbstractQuery { constraint: index, fields, table, - parent: err + parent: err, + stack: errStack }); } // falls through default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 4047ef4792f1..499cf21694f3 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -66,10 +66,10 @@ class Query extends AbstractQuery { return ret; } - _handleQueryResponse(metaData, columnTypes, err, results) { + _handleQueryResponse(metaData, columnTypes, err, results, errStack) { if (err) { err.sql = this.sql; - throw this.formatError(err); + throw this.formatError(err, errStack); } let result = this.instance; @@ -224,6 +224,7 @@ class Query extends AbstractQuery { return new Promise((resolve, reject) => conn.serialize(async () => { const columnTypes = {}; + const errForStack = new Error(); const executeSql = () => { if (sql.startsWith('-- ')) { return resolve(); @@ -235,7 +236,7 @@ class Query extends AbstractQuery { complete(); // `this` is passed from sqlite, we have no control over this. // eslint-disable-next-line no-invalid-this - resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); + resolve(query._handleQueryResponse(this, columnTypes, executionError, results, errForStack.stack)); return; } catch (error) { reject(error); @@ -346,13 +347,14 @@ class Query extends AbstractQuery { return value; } - formatError(err) { + formatError(err, errStack) { switch (err.code) { case 'SQLITE_CONSTRAINT': { if (err.message.includes('FOREIGN KEY constraint failed')) { return new sequelizeErrors.ForeignKeyConstraintError({ - parent: err + parent: err, + stack: errStack }); } @@ -394,13 +396,13 @@ class Query extends AbstractQuery { }); } - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } case 'SQLITE_BUSY': - return new sequelizeErrors.TimeoutError(err); + return new sequelizeErrors.TimeoutError(err, { stack: errStack }); default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/errors/database-error.js b/lib/errors/database-error.js index c7059d3ba9e0..ec09cad182b4 100644 --- a/lib/errors/database-error.js +++ b/lib/errors/database-error.js @@ -6,7 +6,7 @@ const BaseError = require('./base-error'); * A base class for all database related errors. */ class DatabaseError extends BaseError { - constructor(parent) { + constructor(parent, options) { super(parent.message); this.name = 'SequelizeDatabaseError'; /** @@ -29,6 +29,14 @@ class DatabaseError extends BaseError { * @type {Array} */ this.parameters = parent.parameters; + /** + * The stacktrace can be overridden if the original stacktrace isn't very good + * + * @type {string} + */ + if (options && options.stack) { + this.stack = options.stack; + } } } diff --git a/lib/errors/database/exclusion-constraint-error.js b/lib/errors/database/exclusion-constraint-error.js index f77e52aab3ea..66ced0e5b9b5 100644 --- a/lib/errors/database/exclusion-constraint-error.js +++ b/lib/errors/database/exclusion-constraint-error.js @@ -10,7 +10,7 @@ class ExclusionConstraintError extends DatabaseError { options = options || {}; options.parent = options.parent || { sql: '' }; - super(options.parent); + super(options.parent, { stack: options.stack }); this.name = 'SequelizeExclusionConstraintError'; this.message = options.message || options.parent.message || ''; diff --git a/lib/errors/database/foreign-key-constraint-error.js b/lib/errors/database/foreign-key-constraint-error.js index 9bdf02ad0c14..a3acba352234 100644 --- a/lib/errors/database/foreign-key-constraint-error.js +++ b/lib/errors/database/foreign-key-constraint-error.js @@ -10,7 +10,7 @@ class ForeignKeyConstraintError extends DatabaseError { options = options || {}; options.parent = options.parent || { sql: '' }; - super(options.parent); + super(options.parent, { stack: options.stack }); this.name = 'SequelizeForeignKeyConstraintError'; this.message = options.message || options.parent.message || 'Database Error'; diff --git a/lib/errors/database/timeout-error.js b/lib/errors/database/timeout-error.js index b67933b50f77..2342c1ab3d14 100644 --- a/lib/errors/database/timeout-error.js +++ b/lib/errors/database/timeout-error.js @@ -6,8 +6,8 @@ const DatabaseError = require('./../database-error'); * Thrown when a database query times out because of a deadlock */ class TimeoutError extends DatabaseError { - constructor(parent) { - super(parent); + constructor(parent, options) { + super(parent, options); this.name = 'SequelizeTimeoutError'; } } diff --git a/lib/errors/database/unknown-constraint-error.js b/lib/errors/database/unknown-constraint-error.js index e11fa666c7ef..0c1be5d47a15 100644 --- a/lib/errors/database/unknown-constraint-error.js +++ b/lib/errors/database/unknown-constraint-error.js @@ -10,7 +10,7 @@ class UnknownConstraintError extends DatabaseError { options = options || {}; options.parent = options.parent || { sql: '' }; - super(options.parent); + super(options.parent, { stack: options.stack }); this.name = 'SequelizeUnknownConstraintError'; this.message = options.message || 'The specified constraint does not exist'; diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 4bb4c9817bc6..3e0d1095a8b2 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -12,7 +12,7 @@ const BaseError = require('./base-error'); * @property errors {ValidationErrorItems[]} */ class ValidationError extends BaseError { - constructor(message, errors) { + constructor(message, errors, options) { super(message); this.name = 'SequelizeValidationError'; this.message = 'Validation Error'; @@ -30,6 +30,11 @@ class ValidationError extends BaseError { } else if (this.errors.length > 0 && this.errors[0].message) { this.message = this.errors.map(err => `${err.type || err.origin}: ${err.message}`).join(',\n'); } + + // Allow overriding the stack if the original stacktrace is uninformative + if (options && options.stack) { + this.stack = options.stack; + } } /** diff --git a/lib/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js index a509e88ff4fb..e0978c09be36 100644 --- a/lib/errors/validation/unique-constraint-error.js +++ b/lib/errors/validation/unique-constraint-error.js @@ -11,7 +11,7 @@ class UniqueConstraintError extends ValidationError { options.parent = options.parent || { sql: '' }; options.message = options.message || options.parent.message || 'Validation Error'; options.errors = options.errors || {}; - super(options.message, options.errors); + super(options.message, options.errors, { stack: options.stack }); this.name = 'SequelizeUniqueConstraintError'; this.errors = options.errors; diff --git a/test/integration/sequelize/query.test.js b/test/integration/sequelize/query.test.js index ff53f738eddd..37dd1f6e4ce1 100644 --- a/test/integration/sequelize/query.test.js +++ b/test/integration/sequelize/query.test.js @@ -7,6 +7,7 @@ const DataTypes = Support.Sequelize.DataTypes; const dialect = Support.getTestDialect(); const sinon = require('sinon'); const moment = require('moment'); +const { DatabaseError, UniqueConstraintError, ForeignKeyConstraintError } = Support.Sequelize; const qq = str => { if (dialect === 'postgres' || dialect === 'mssql') { @@ -28,11 +29,11 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { beforeEach(async function() { this.User = this.sequelize.define('User', { username: { - type: Sequelize.STRING, + type: DataTypes.STRING, unique: true }, emailAddress: { - type: Sequelize.STRING, + type: DataTypes.STRING, field: 'email_address' } }); @@ -289,6 +290,91 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(users[0].email).to.be.equal('john@gmail.com'); }); + // Only run stacktrace tests on Node 12+, since only Node 12+ supports + // async stacktraces + const nodeVersionMatch = process.version.match(/^v([0-9]+)/); + let nodeMajorVersion = 0; + if (nodeVersionMatch && nodeVersionMatch[1]) { + nodeMajorVersion = parseInt(nodeVersionMatch[1], 10); + } + + if (nodeMajorVersion >= 12) { + describe('stacktraces', () => { + beforeEach(async function() { + this.UserVisit = this.sequelize.define('UserVisit', { + userId: { + type: DataTypes.STRING, + field: 'user_id' + }, + visitedAt: { + type: DataTypes.DATE, + field: 'visited_at' + } + }, { + indexes: [ + { name: 'user_id', fields: ['user_id'] } + ] + }); + + this.User.hasMany(this.UserVisit, { foreignKey: 'user_id' }); + this.UserVisit.belongsTo(this.User, { foreignKey: 'user_id', targetKey: 'id' }); + + await this.UserVisit.sync({ force: true }); + }); + + it('emits full stacktraces for generic database error', async function() { + let error = null; + try { + await this.sequelize.query(`select * from ${qq(this.User.tableName)} where ${qq('unknown_column')} = 1`); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(DatabaseError); + expect(error.stack).to.contain('query.test'); + }); + + it('emits full stacktraces for unique constraint error', async function() { + const query = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ + qq('createdAt') }, ${qq('updatedAt') + }) VALUES ('duplicate', 'duplicate@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; + + // Insert 1 row + await this.sequelize.query(query); + + let error = null; + try { + // Try inserting a duplicate row + await this.sequelize.query(query); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(UniqueConstraintError); + expect(error.stack).to.contain('query.test'); + }); + + it('emits full stacktraces for constraint validation error', async function() { + let error = null; + try { + // Try inserting a row that has a really high userId to any existing username + await this.sequelize.query( + `INSERT INTO ${qq(this.UserVisit.tableName)} (user_id, visited_at, ${qq( + 'createdAt' + )}, ${qq( + 'updatedAt' + )}) VALUES (123456789, '2012-01-01 10:10:10', '2012-01-01 10:10:10', '2012-01-01 10:10:10')` + ); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(ForeignKeyConstraintError); + expect(error.stack).to.contain('query.test'); + }); + }); + } + describe('rejections', () => { it('reject if `values` and `options.replacements` are both passed', async function() { await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) From da3ac091032856f8a74297eff9a9d89e7fc997e5 Mon Sep 17 00:00:00 2001 From: "r253.hmdryou" Date: Wed, 10 Nov 2021 22:15:43 +0900 Subject: [PATCH 393/414] fix: expect result is null but got zero (#13637) * fix: expect result is null but got zero * revert: query-interface.js * fix: model.js to return null * fix: test title * fix: if sum without rows, expect null Co-authored-by: Sascha Depold --- lib/model.js | 3 --- test/integration/model/notExist.test.js | 19 ++++++++++++------- test/integration/model/sum.test.js | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/model.js b/lib/model.js index 3ac585242bf5..9a7dfcd386e0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1984,9 +1984,6 @@ class Model { options = this._paranoidClause(this, options); const value = await this.queryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this); - if (value === null) { - return 0; - } return value; } diff --git a/test/integration/model/notExist.test.js b/test/integration/model/notExist.test.js index b8b0dd017f01..e85c736e4ad2 100644 --- a/test/integration/model/notExist.test.js +++ b/test/integration/model/notExist.test.js @@ -22,12 +22,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { { sequence: 3, amount: 5, type: 'A' }, { sequence: 4, amount: 1, type: 'A' }, { sequence: 1, amount: 2, type: 'B' }, - { sequence: 2, amount: 6, type: 'B' } + { sequence: 2, amount: 6, type: 'B' }, + { sequence: 0, amount: 0, type: 'C' } ]); }); describe('max', () => { - it('should type exist', async function() { + it('type A to C should exist', async function() { await expect(this.Order.sum('sequence', { where: { type: 'A' } })).to.eventually.be.equal(10); await expect(this.Order.max('sequence', { where: { type: 'A' } })).to.eventually.be.equal(4); await expect(this.Order.min('sequence', { where: { type: 'A' } })).to.eventually.be.equal(1); @@ -41,18 +42,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { await expect(this.Order.sum('amount', { where: { type: 'B' } })).to.eventually.be.equal(8); await expect(this.Order.max('amount', { where: { type: 'B' } })).to.eventually.be.equal(6); await expect(this.Order.min('amount', { where: { type: 'B' } })).to.eventually.be.equal(2); - }); - it('should type not exist', async function() { - // DataTypes.INTEGER or DataTypes.BIGINT: previous version should use `.to.eventually.be.NaN` await expect(this.Order.sum('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); await expect(this.Order.max('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); await expect(this.Order.min('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); - - // DataTypes.DECIMAL or DataTypes.FLOAT: previous and PR#13422 both use `to.eventually.be.equal(0)` await expect(this.Order.sum('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); await expect(this.Order.max('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); await expect(this.Order.min('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); }); + + it('type D should not exist', async function() { + await expect(this.Order.sum('sequence', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.max('sequence', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.min('sequence', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.sum('amount', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.max('amount', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.min('amount', { where: { type: 'D' } })).to.eventually.be.null; + }); }); }); diff --git a/test/integration/model/sum.test.js b/test/integration/model/sum.test.js index 4f0aad682f24..081592e3a94f 100644 --- a/test/integration/model/sum.test.js +++ b/test/integration/model/sum.test.js @@ -28,7 +28,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('sum', () => { it('should sum without rows', async function() { - await expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).to.eventually.be.equal(0); + await expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).to.eventually.be.null; }); it('should sum when is 0', async function() { From 1690801cda2ca15f32aaaf5e9ebd96e800808e36 Mon Sep 17 00:00:00 2001 From: WeRDyin Date: Fri, 12 Nov 2021 15:02:07 +0800 Subject: [PATCH 394/414] fix(types): DataType.TEXT overloading definition (#13654) --- types/lib/data-types.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/lib/data-types.d.ts b/types/lib/data-types.d.ts index 617b01e84ec0..d95f78d83dfd 100644 --- a/types/lib/data-types.d.ts +++ b/types/lib/data-types.d.ts @@ -112,6 +112,8 @@ export const TEXT: TextDataTypeConstructor; interface TextDataTypeConstructor extends AbstractDataTypeConstructor { new (length?: TextLength): TextDataType; + new (options?: TextDataTypeOptions): TextDataType; + (length?: TextLength): TextDataType; (options?: TextDataTypeOptions): TextDataType; } From 1f2392423212ca9a4604772c1d0a2f008606695e Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Thu, 11 Nov 2021 23:51:21 -0800 Subject: [PATCH 395/414] fix(types): rename types and update CONTRIBUTING docs (#13348) Co-authored-by: f[nZk] --- CONTRIBUTING.md | 2 ++ types/lib/model.d.ts | 6 +++--- types/lib/sequelize.d.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4be8da2fb55c..b7b25a556cf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -132,6 +132,8 @@ _Note:_ if you're using Windows, make sure you run these from Git Bash (or anoth Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests (or an SSCCE). +You can run `npm run stop-X` to stop the servers once you're done. + ##### Hint for Postgres You can also easily start a local [pgadmin4](https://www.pgadmin.org/docs/pgadmin4/latest/) instance at `localhost:8888` to inspect the contents of the test Postgres database as follows: diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 968cf3bc4cb0..f356cba015db 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1367,13 +1367,13 @@ export interface ModelAttributeColumnOptions extends Co } /** - * Interface for Attributes provided for a column + * Interface for Attributes provided for all columns in a model */ -export type ModelAttributes = { +export type ModelAttributes = { /** * The description of a database column */ - [name in keyof TCreationAttributes]: DataType | ModelAttributeColumnOptions; + [name in keyof TAttributes]: DataType | ModelAttributeColumnOptions; } /** diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index fd2ecbcd2e70..a17d0fdb9021 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1165,9 +1165,9 @@ export class Sequelize extends Hooks { * @param options These options are merged with the default define options provided to the Sequelize * constructor */ - public define( + public define( modelName: string, - attributes: ModelAttributes, + attributes: ModelAttributes, options?: ModelOptions ): ModelCtor; From e6a1c645c072749e48e990a280e9a4818d7ce78d Mon Sep 17 00:00:00 2001 From: Jerry Zhou <33133448+Jerry-zhk@users.noreply.github.com> Date: Sat, 13 Nov 2021 11:48:52 +0800 Subject: [PATCH 396/414] Patch with static decrement method types (#12600) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch with missing type definitions for static decrement method Co-authored-by: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Co-authored-by: ᛜ ᛝᛉᚲ --- types/lib/model.d.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index f356cba015db..ec31540ef004 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -2148,6 +2148,33 @@ export abstract class Model ): Promise; + /** + * Decrements a single field. + */ + public static decrement( + this: ModelStatic, + field: keyof M['_attributes'], + options: IncrementDecrementOptionsWithBy + ): Promise; + + /** + * Decrements multiple fields by the same value. + */ + public static decrement( + this: ModelStatic, + fields: (keyof M['_attributes'])[], + options: IncrementDecrementOptionsWithBy + ): Promise; + + /** + * Decrements multiple fields by different values. + */ + public static decrement( + this: ModelStatic, + fields: { [key in keyof M['_attributes']]?: number }, + options: IncrementDecrementOptions + ): Promise; + /** * Run a describe query on the table. The result will be return to the listener as a hash of attributes and * their types. From 5924be52152232fbd7a925d599c31cac9f90dc6d Mon Sep 17 00:00:00 2001 From: Sander Mol Date: Sat, 13 Nov 2021 13:27:31 +0100 Subject: [PATCH 397/414] fix(types): add specifc tojson type in model.d.ts (#13661) Co-authored-by: sander-mol --- types/lib/model.d.ts | 6 +++--- types/test/model.ts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index ec31540ef004..30d17996baa2 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -4,9 +4,9 @@ import { DataType } from './data-types'; import { Deferrable } from './deferrable'; import { HookReturn, Hooks, ModelHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; -import { QueryOptions, IndexesOptions, TableName } from './query-interface'; +import { IndexesOptions, QueryOptions, TableName } from './query-interface'; import { Sequelize, SyncOptions } from './sequelize'; -import { Transaction, LOCK } from './transaction'; +import { LOCK, Transaction } from './transaction'; import { Col, Fn, Literal, Where } from './utils'; import Op = require('./operators'); @@ -2884,7 +2884,7 @@ export abstract class Model(): T; /** * Helper method to determine if a instance is "soft deleted". This is diff --git a/types/test/model.ts b/types/test/model.ts index d63b56eff1f5..0eb206404244 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -201,3 +201,23 @@ expectTypeOf(modelWithAttributes.previous).parameter(0).not.toEqualTypeOf<'unref expectTypeOf(modelWithAttributes.previous).returns.toEqualTypeOf(); expectTypeOf(modelWithAttributes.previous('name')).toEqualTypeOf(); expectTypeOf(modelWithAttributes.previous()).toEqualTypeOf>(); + +/** + * Tests for toJson() type + */ +interface FilmToJson { + id: number; + name?: string; +} +class FilmModelToJson extends Model implements FilmToJson { + id!: number; + name?: string; +} +const film = FilmModelToJson.build(); + +const result = film.toJSON(); +expectTypeOf(result).toEqualTypeOf() + +type FilmNoNameToJson = Omit +const resultDerived = film.toJSON(); +expectTypeOf(resultDerived).toEqualTypeOf() \ No newline at end of file From 13e8b8378eb93f8afca72533433a170a4b7a8fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=9B=9C=20=E1=9B=9D=E1=9B=89=E1=9A=B2?= Date: Tue, 16 Nov 2021 01:26:36 +0700 Subject: [PATCH 398/414] chore(build): never close PRs (#13648) Co-authored-by: Sascha Depold --- .github/workflows/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7b83ca472f5f..cd091bc3d3a9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,5 +16,6 @@ jobs: days-before-stale: 14 days-before-close: 14 operations-per-run: 1000 + days-before-pr-close: -1 - name: Print outputs run: echo ${{ join(steps.stale.outputs.*, ',') }} From 0ecb0e12cc58c4edb9b7eceaf65adc5d10e6ba98 Mon Sep 17 00:00:00 2001 From: vikas gupta Date: Tue, 16 Nov 2021 00:08:01 +0530 Subject: [PATCH 399/414] Resolved the typescript issue with the Op.match (https://github.com/sequelize/sequelize/pull/12955) (#13481) * Update operators.d.ts resolved the typescript issue for Op.match (https://github.com/sequelize/sequelize/pull/12955) * Update operators.d.ts Co-authored-by: Constantin Metz <58604248+Keimeno@users.noreply.github.com> Co-authored-by: Sascha Depold --- types/lib/operators.d.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/types/lib/operators.d.ts b/types/lib/operators.d.ts index 85bc0ddb6678..352192ee8fc8 100644 --- a/types/lib/operators.d.ts +++ b/types/lib/operators.d.ts @@ -244,16 +244,26 @@ declare const Op: { */ readonly lte: unique symbol; /** - * Operator != + * Operator @@ * * ```js - * [Op.ne]: 20 + * [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` * ``` * In SQL * ```sql - * != 20 + * @@ to_tsquery('fat & rat') * ``` */ + readonly match: unique symbol; + /** + * Operator @@ + * + * ```js + * [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` + * ``` + * In SQL + * `@@ to_tsquery('fat & rat')` + */ readonly ne: unique symbol; /** * Operator &> (PG range does not extend to the left of operator) From 0e5e7f95b8c2cb78deb4620dd8d8f43d9b7ccb2f Mon Sep 17 00:00:00 2001 From: Sascha Depold Date: Mon, 15 Nov 2021 19:54:40 +0100 Subject: [PATCH 400/414] test(type): cover Op.match with test (#13664) --- types/lib/model.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index 30d17996baa2..4f760f7e8655 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -159,6 +159,9 @@ export interface WhereOperators { /** Example: `[Op.lte]: 10,` becomes `<= 10` */ [Op.lte]?: number | string | Date | Literal; + /** Example: `[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` becomes `@@ to_tsquery('fat & rat')` */ + [Op.match]?: Fn; + /** Example: `[Op.ne]: 20,` becomes `!= 20` */ [Op.ne]?: null | string | number | Literal | WhereOperators; From 7ad6d53483c67ffd9008683aac1670e3760a1a57 Mon Sep 17 00:00:00 2001 From: Rik Smale <13023439+WikiRik@users.noreply.github.com> Date: Mon, 15 Nov 2021 19:56:41 +0100 Subject: [PATCH 401/414] chore(stale): exempt issues with type label (#13665) This does require maintainers/issue reviewers to add type labels to all still occurring issues and regularly update them --- .github/workflows/stale.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index cd091bc3d3a9..99e744ce18ed 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,5 +17,6 @@ jobs: days-before-close: 14 operations-per-run: 1000 days-before-pr-close: -1 + exempt-issue-labels: 'type: bug, type: docs, type: feature, type: other, type: performance, type: refactor, type: typescript' # All 'type: ' labels - name: Print outputs run: echo ${{ join(steps.stale.outputs.*, ',') }} From 98485dfcff501c565dbf453a54868a4dfe60a225 Mon Sep 17 00:00:00 2001 From: Constantin Metz Date: Mon, 15 Nov 2021 20:30:11 +0100 Subject: [PATCH 402/414] fix(types): ne op documentation (#13666) --- types/lib/operators.d.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/types/lib/operators.d.ts b/types/lib/operators.d.ts index 352192ee8fc8..18d66589b9ef 100644 --- a/types/lib/operators.d.ts +++ b/types/lib/operators.d.ts @@ -256,13 +256,15 @@ declare const Op: { */ readonly match: unique symbol; /** - * Operator @@ + * Operator != * * ```js - * [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` + * [Op.ne]: 20 * ``` * In SQL - * `@@ to_tsquery('fat & rat')` + * ```sql + * != 20 + * ``` */ readonly ne: unique symbol; /** @@ -398,7 +400,7 @@ declare const Op: { */ readonly overlap: unique symbol; /** - * Internal placeholder + * Internal placeholder * * ```js * [Op.placeholder]: true @@ -467,7 +469,7 @@ declare const Op: { readonly substring: unique symbol; /** * Operator VALUES - * + * * ```js * [Op.values]: [4, 5, 6] * ``` From 47c4494968422585bf265063925d1662ffcd4173 Mon Sep 17 00:00:00 2001 From: sschwenker <30476508+sschwenker@users.noreply.github.com> Date: Mon, 15 Nov 2021 14:35:47 -0500 Subject: [PATCH 403/414] fix(mssql): sqlserver 2008 fix for using offsets and include criteria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(mssql): sqlserver 2008 fix for using offsets and include criteria * fix(mssql): sqlserver 2008 fix for using offsets and include criteria * fix(mssql): sqlserver 2008 fix for using offsets and include criteria * fix(mssql): sqlserver 2008 fix for using offsets and include criteria Co-authored-by: sschwenker Co-authored-by: Sascha Depold Co-authored-by: ᛜ ᛝᛉᚲ --- lib/dialects/mssql/query-generator.js | 51 +++++++++++++++++++ .../dialects/mssql/query-generator.test.js | 36 +++++++++++++ 2 files changed, 87 insertions(+) diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 1c0f71367d53..94e643ee0b23 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -861,6 +861,57 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { const tmpTable = mainTableAs || 'OffsetTable'; + if (options.include) { + const subQuery = options.subQuery === undefined ? options.limit && options.hasMultiAssociation : options.subQuery; + const mainTable = { + name: mainTableAs, + quotedName: null, + as: null, + model + }; + const topLevelInfo = { + names: mainTable, + options, + subQuery + }; + + let mainJoinQueries = []; + for (const include of options.include) { + if (include.separate) { + continue; + } + const joinQueries = this.generateInclude(include, { externalAs: mainTableAs, internalAs: mainTableAs }, topLevelInfo); + mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); + } + + return Utils.joinSQLFragments([ + 'SELECT TOP 100 PERCENT', + attributes.join(', '), + 'FROM (', + [ + 'SELECT', + options.limit && `TOP ${options.limit}`, + '* FROM (', + [ + 'SELECT ROW_NUMBER() OVER (', + [ + 'ORDER BY', + orders.mainQueryOrder.join(', ') + ], + `) as row_num, ${tmpTable}.* FROM (`, + [ + 'SELECT DISTINCT', + `${tmpTable}.* FROM ${tables} AS ${tmpTable}`, + mainJoinQueries, + where && `WHERE ${where}` + ], + `) AS ${tmpTable}` + ], + `) AS ${tmpTable} WHERE row_num > ${offset}` + ], + `) AS ${tmpTable}` + ]); + } return Utils.joinSQLFragments([ 'SELECT TOP 100 PERCENT', attributes.join(', '), diff --git a/test/unit/dialects/mssql/query-generator.test.js b/test/unit/dialects/mssql/query-generator.test.js index 525b7ff99da4..71fb0b11ddf4 100644 --- a/test/unit/dialects/mssql/query-generator.test.js +++ b/test/unit/dialects/mssql/query-generator.test.js @@ -195,6 +195,42 @@ if (current.dialect.name === 'mssql') { expectsql(modifiedGen.selectFromTableFragment({ limit: 10, offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' }); + + // With limit, offset, include, and where + const foo = this.sequelize.define('Foo', { + id: { + type: DataTypes.INTEGER, + field: 'id', + primaryKey: true + } + }, { + tableName: 'Foos' + }); + const bar = this.sequelize.define('Bar', { + id: { + type: DataTypes.INTEGER, + field: 'id', + primaryKey: true + } + }, { + tableName: 'Bars' + }); + foo.Bar = foo.belongsTo(bar, { foreignKey: 'barId' }); + let options = { limit: 10, offset: 10, + include: [ + { + model: bar, + association: foo.Bar, + as: 'Bars', + required: true + } + ] + }; + foo._conformIncludes(options); + options = foo._validateIncludedElements(options); + expectsql(modifiedGen.selectFromTableFragment(options, foo, ['[Foo].[id]', '[Foo].[barId]'], foo.tableName, 'Foo', '[Bar].[id] = 12'), { + mssql: 'SELECT TOP 100 PERCENT [Foo].[id], [Foo].[barId] FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, Foo.* FROM (SELECT DISTINCT Foo.* FROM Foos AS Foo INNER JOIN [Bars] AS [Bar] ON [Foo].[barId] = [Bar].[id] WHERE [Bar].[id] = 12) AS Foo) AS Foo WHERE row_num > 10) AS Foo' + }); }); it('getPrimaryKeyConstraintQuery', function() { From 0312f8eac982b646842f89f56dc90f6c8f935c84 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy Date: Thu, 18 Nov 2021 10:06:51 -0800 Subject: [PATCH 404/414] fix: typing on creation within an association (#13678) * fix: typing on creation within an association * fix: tests Signed-off-by: Mohamed El Mahallawy --- types/lib/associations/belongs-to-many.d.ts | 4 ++-- types/lib/associations/belongs-to.d.ts | 4 ++-- types/lib/associations/has-many.d.ts | 4 ++-- types/lib/associations/has-one.d.ts | 4 ++-- types/test/models/UserGroup.ts | 10 +++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index b04a728af6ee..c82e9294d971 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -303,8 +303,8 @@ export interface BelongsToManyCreateAssociationMixinOptions extends CreateOption * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ -export type BelongsToManyCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type BelongsToManyCreateAssociationMixin = ( + values?: Model['_creationAttributes'], options?: BelongsToManyCreateAssociationMixinOptions ) => Promise; diff --git a/types/lib/associations/belongs-to.d.ts b/types/lib/associations/belongs-to.d.ts index 17754ac93775..fd2a5e356b2a 100644 --- a/types/lib/associations/belongs-to.d.ts +++ b/types/lib/associations/belongs-to.d.ts @@ -116,8 +116,8 @@ export interface BelongsToCreateAssociationMixinOptions * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html * @see Instance */ -export type BelongsToCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type BelongsToCreateAssociationMixin = ( + values?: TModel['_creationAttributes'], options?: BelongsToCreateAssociationMixinOptions ) => Promise; diff --git a/types/lib/associations/has-many.d.ts b/types/lib/associations/has-many.d.ts index 802ad45a7181..d98a96485af3 100644 --- a/types/lib/associations/has-many.d.ts +++ b/types/lib/associations/has-many.d.ts @@ -209,8 +209,8 @@ export interface HasManyCreateAssociationMixinOptions extends CreateOptions * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ -export type HasManyCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type HasManyCreateAssociationMixin = ( + values?: Model['_creationAttributes'], options?: HasManyCreateAssociationMixinOptions ) => Promise; diff --git a/types/lib/associations/has-one.d.ts b/types/lib/associations/has-one.d.ts index 7b41fc05196e..e81784b3d88a 100644 --- a/types/lib/associations/has-one.d.ts +++ b/types/lib/associations/has-one.d.ts @@ -113,7 +113,7 @@ export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociatio * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html * @see Instance */ -export type HasOneCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type HasOneCreateAssociationMixin = ( + values?: TModel['_creationAttributes'], options?: HasOneCreateAssociationMixinOptions ) => Promise; diff --git a/types/test/models/UserGroup.ts b/types/test/models/UserGroup.ts index 66cc046fbc3a..1c170f2302e7 100644 --- a/types/test/models/UserGroup.ts +++ b/types/test/models/UserGroup.ts @@ -10,9 +10,12 @@ import { HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, HasManySetAssociationsMixin, - Model, + Model } from 'sequelize'; import { sequelize } from '../connection'; +// associate +// it is important to import _after_ the model above is already exported so the circular reference works. +import { User } from './User'; // This class doesn't extend the generic Model, but should still // function just fine, with a bit less safe type-checking @@ -30,7 +33,7 @@ export class UserGroup extends Model { public setUsers!: HasManySetAssociationsMixin; public addUser!: HasManyAddAssociationMixin; public addUsers!: HasManyAddAssociationsMixin; - public createUser!: HasManyCreateAssociationMixin; + public createUser!: HasManyCreateAssociationMixin; public countUsers!: HasManyCountAssociationsMixin; public hasUser!: HasManyHasAssociationMixin; public removeUser!: HasManyRemoveAssociationMixin; @@ -41,7 +44,4 @@ export class UserGroup extends Model { // instead of this, you could also use decorators UserGroup.init({ name: DataTypes.STRING }, { sequelize }); -// associate -// it is important to import _after_ the model above is already exported so the circular reference works. -import { User } from './User'; export const Users = UserGroup.hasMany(User, { as: 'users', foreignKey: 'groupId' }); From 95915739443f96996841dacfd6861e9d5ba35c1b Mon Sep 17 00:00:00 2001 From: Rafi Shamim Date: Thu, 18 Nov 2021 13:24:17 -0500 Subject: [PATCH 405/414] feat(postgresql): easier SSL config and options param support (#13673) This commit uses the pg_connection_string package to parse the connection string if the dialect is postgresql. This is helpful because it automatically handles reading SSL certs that are specified in the connection string. As part of this, support was added for the `options` URL parameter, which allows arbitrary session variables to be configured in the connection string. Co-authored-by: Sascha Depold --- lib/dialects/postgres/connection-manager.js | 5 ++++- lib/sequelize.js | 7 +++++++ package.json | 1 + .../dialects/postgres/connection-manager.test.js | 7 +++++++ test/integration/sequelize.test.js | 9 ++++++++- 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index a07669efcdcc..841b9e65f9a3 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -118,7 +118,10 @@ class ConnectionManager extends AbstractConnectionManager { // Times out queries after a set time in milliseconds in client end, query would be still running in database end. 'query_timeout', // Terminate any session with an open transaction that has been idle for longer than the specified duration in milliseconds. Added in pg v7.17.0 only supported in postgres >= 10 - 'idle_in_transaction_session_timeout' + 'idle_in_transaction_session_timeout', + // Postgres allows additional session variables to be configured in the connection string in the `options` param. + // see [https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNECT-OPTIONS] + 'options' ])); } diff --git a/lib/sequelize.js b/lib/sequelize.js index b66d8f34b6aa..3aa6cb9cd54e 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -2,6 +2,7 @@ const url = require('url'); const path = require('path'); +const pgConnectionString = require('pg-connection-string'); const retry = require('retry-as-promised'); const _ = require('lodash'); @@ -232,6 +233,12 @@ class Sequelize { } } } + + // For postgres, we can use this helper to load certs directly from the + // connection string. + if (options.dialect === 'postgres' || options.dialect === 'postgresql') { + Object.assign(options.dialectOptions, pgConnectionString.parse(arguments[0])); + } } else { // new Sequelize(database, username, password, { ... options }) options = options || {}; diff --git a/package.json b/package.json index 87a6a7f4b1a2..18cd5fce87a6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "lodash": "^4.17.20", "moment": "^2.26.0", "moment-timezone": "^0.5.31", + "pg-connection-string": "^2.5.0", "retry-as-promised": "^3.2.0", "semver": "^7.3.2", "sequelize-pool": "^6.0.0", diff --git a/test/integration/dialects/postgres/connection-manager.test.js b/test/integration/dialects/postgres/connection-manager.test.js index b6924416abb2..67778d8e8a04 100644 --- a/test/integration/dialects/postgres/connection-manager.test.js +++ b/test/integration/dialects/postgres/connection-manager.test.js @@ -47,6 +47,13 @@ if (dialect.match(/^postgres/)) { const error = await sequelize.query('select pg_sleep(2)').catch(e => e); expect(error.message).to.equal('Query read timeout'); }); + + it('should allow overriding session variables through the `options` param', async () => { + const sequelize = Support.createSequelizeInstance({ dialectOptions: { options: '-csearch_path=abc' } }); + const result = await sequelize.query('SHOW search_path'); + expect(result[0].search_path).to.equal('abc'); + }); + }); describe('Dynamic OIDs', () => { diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js index 4d9f8b498432..78a404bb9e0e 100644 --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { } if (dialect === 'postgres') { - const getConnectionUri = o => `${o.protocol}://${o.username}:${o.password}@${o.host}${o.port ? `:${o.port}` : ''}/${o.database}`; + const getConnectionUri = o => `${o.protocol}://${o.username}:${o.password}@${o.host}${o.port ? `:${o.port}` : ''}/${o.database}${o.options ? `?options=${o.options}` : ''}`; it('should work with connection strings (postgres protocol)', () => { const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgres' }); // postgres://... @@ -70,6 +70,13 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { // postgresql://... new Sequelize(connectionUri); }); + it('should work with options in the connection string (postgresql protocol)', async () => { + const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgresql', options: '-c%20search_path%3dtest_schema' }); + const sequelize = new Sequelize(connectionUri); + const result = await sequelize.query('SHOW search_path'); + expect(result[0].search_path).to.equal('test_schema'); + }); + } }); From 41876f11a7ef2dec4f7788d8e39cf9864a9e83cd Mon Sep 17 00:00:00 2001 From: Mukesh Suthar Date: Fri, 19 Nov 2021 00:25:46 +0530 Subject: [PATCH 406/414] feat: option for attributes having dotNotation (#13670) * feat: option for attributes having dotnotation - `options.dotnotation`, can be used when the column name has dot in it. * test: add test case for attributes with dot notation * docs: add function doc for `option.dotnotation` * fix: expected query for dot notation test case * refactor: camelcase dotnotation keyword Co-authored-by: Mukesh Suthar --- lib/dialects/abstract/query-generator.js | 2 +- lib/model.js | 1 + test/unit/sql/select.test.js | 36 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js index 664487d95f87..5a438cd3268b 100644 --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -1452,7 +1452,7 @@ class QueryGenerator { ? this.quoteAttribute(attr, options.model) : this.escape(attr); } - if (!_.isEmpty(options.include) && !attr.includes('.') && addTable) { + if (!_.isEmpty(options.include) && (!attr.includes('.') || options.dotNotation) && addTable) { attr = `${mainTableAs}.${attr}`; } diff --git a/lib/model.js b/lib/model.js index 9a7dfcd386e0..9960422d2383 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1672,6 +1672,7 @@ class Model { * @param {object} [options.having] Having options * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean|Error} [options.rejectOnEmpty=false] Throws an error when no records found + * @param {boolean} [options.dotNotation] Allows including tables having the same attribute/column names - which have a dot in them. * * @see * {@link Sequelize#query} diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js index 6f44cd46bace..6aa33a5d8efe 100644 --- a/test/unit/sql/select.test.js +++ b/test/unit/sql/select.test.js @@ -826,6 +826,42 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); + it('attributes with dot notation', () => { + const User = Support.sequelize.define('User', { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + 'status.label': DataTypes.STRING + }, + { + freezeTableName: true + }); + const Post = Support.sequelize.define('Post', { + title: DataTypes.STRING, + 'status.label': DataTypes.STRING + }, + { + freezeTableName: true + }); + + User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); + + expectsql(sql.selectQuery('User', { + attributes: ['name', 'age', 'status.label'], + include: Model._validateIncludedElements({ + include: [{ + attributes: ['title', 'status.label'], + association: User.Posts + }], + model: User + }).include, + model: User, + dotNotation: true + }, User), { + default: 'SELECT [User].[name], [User].[age], [User].[status.label], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title], [Posts].[status.label] AS [Posts.status.label] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];', + postgres: 'SELECT "User".name, "User".age, "User"."status.label", Posts.id AS "Posts.id", Posts.title AS "Posts.title", Posts."status.label" AS "Posts.status.label" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id;' + }); + }); + }); describe('raw query', () => { From 8d59804f75f32b5e86112799be9c2d5f50a83083 Mon Sep 17 00:00:00 2001 From: Vadim Kononov Date: Mon, 22 Nov 2021 17:14:51 +0200 Subject: [PATCH 407/414] merge from seq:v6 --- .editorconfig | 9 - .eslintrc.json | 22 +- .gitattributes | 1 + .github/FUNDING.yml | 12 + .github/ISSUE_TEMPLATE/bug_report.md | 55 +- .github/ISSUE_TEMPLATE/docs_issue.md | 31 + .../ISSUE_TEMPLATE/documentational-issue.md | 48 - .github/ISSUE_TEMPLATE/feature_request.md | 32 +- .github/ISSUE_TEMPLATE/other_issue.md | 18 +- .github/PULL_REQUEST_TEMPLATE.md | 18 +- .github/stale.yml | 40 - .../auto-remove-awaiting-response-label.yml | 19 + .github/workflows/ci.yml | 217 + .github/workflows/stale.yml | 22 + .gitignore | 10 +- .mocharc.jsonc | 15 + .travis.yml | 96 - CONTACT.md | 11 +- CONTRIBUTING.DOCS.md | 16 +- CONTRIBUTING.md | 252 +- Dockerfile | 8 - ENGINE.md | 11 + README.md | 81 +- SECURITY.md | 5 +- appveyor.yml | 44 - dev/mariadb/10.3/check.js | 8 + dev/mariadb/10.3/docker-compose.yml | 20 + dev/mariadb/10.3/start.sh | 16 + dev/mariadb/10.3/stop.sh | 8 + dev/mssql/2019/check.js | 8 + dev/mssql/2019/docker-compose.yml | 18 + dev/mssql/2019/start.sh | 16 + dev/mssql/2019/stop.sh | 8 + dev/mysql/5.7/check.js | 8 + dev/mysql/5.7/docker-compose.yml | 21 + dev/mysql/5.7/start.sh | 16 + dev/mysql/5.7/stop.sh | 8 + dev/mysql/8.0/check.js | 8 + dev/mysql/8.0/docker-compose.yml | 21 + dev/mysql/8.0/start.sh | 16 + dev/mysql/8.0/stop.sh | 8 + dev/postgres/10/check.js | 8 + dev/postgres/10/docker-compose.yml | 19 + dev/postgres/10/start.sh | 14 + dev/postgres/10/stop.sh | 8 + dev/sscce-helpers.d.ts | 3 + dev/sscce-helpers.js | 13 + dev/wait-until-healthy.sh | 20 + docker-compose.yml | 73 - docs/css/style.css | 127 +- docs/esdoc-config.js | 24 +- docs/images/logo-simple.svg | 1 + docs/images/logo.svg | 41 + docs/images/slack.svg | 54 +- docs/index.md | 33 +- docs/manual-groups.json | 67 +- docs/manual-utils.js | 37 + .../advanced-many-to-many.md | 667 ++ .../association-scopes.md | 64 + .../creating-with-associations.md | 130 + .../eager-loading.md | 666 ++ .../polymorphic-associations.md | 427 + docs/manual/associations.md | 1175 --- docs/manual/core-concepts/assocs.md | 784 ++ .../core-concepts/getters-setters-virtuals.md | 201 + docs/manual/core-concepts/getting-started.md | 111 + docs/manual/core-concepts/model-basics.md | 437 + docs/manual/core-concepts/model-instances.md | 197 + .../core-concepts/model-querying-basics.md | 712 ++ .../core-concepts/model-querying-finders.md | 83 + docs/manual/core-concepts/paranoid.md | 105 + docs/manual/core-concepts/raw-queries.md | 186 + .../validations-and-constraints.md | 272 + docs/manual/data-types.md | 330 - docs/manual/dialects.md | 96 - docs/manual/getting-started.md | 226 - docs/manual/hooks.md | 393 - docs/manual/instances.md | 407 - docs/manual/migrations.md | 651 -- docs/manual/models-definition.md | 727 -- docs/manual/models-usage.md | 761 -- docs/manual/moved/associations.md | 16 + docs/manual/moved/data-types.md | 12 + docs/manual/moved/models-definition.md | 55 + docs/manual/moved/models-usage.md | 12 + docs/manual/moved/querying.md | 13 + docs/manual/other-topics/connection-pool.md | 17 + .../constraints-and-circularities.md | 113 + .../other-topics/dialect-specific-things.md | 218 + .../other-topics/extending-data-types.md | 113 + docs/manual/other-topics/hooks.md | 386 + docs/manual/other-topics/indexes.md | 47 + docs/manual/{ => other-topics}/legacy.md | 8 +- docs/manual/{ => other-topics}/legal.md | 2 +- docs/manual/other-topics/migrations.md | 565 ++ docs/manual/other-topics/naming-strategies.md | 157 + .../manual/other-topics/optimistic-locking.md | 7 + docs/manual/other-topics/other-data-types.md | 192 + docs/manual/other-topics/query-interface.md | 152 + .../{ => other-topics}/read-replication.md | 58 +- docs/manual/{ => other-topics}/resources.md | 11 +- docs/manual/{ => other-topics}/scopes.md | 243 +- docs/manual/other-topics/sub-queries.md | 164 + docs/manual/other-topics/transactions.md | 311 + docs/manual/other-topics/typescript.md | 365 + docs/manual/other-topics/upgrade-to-v6.md | 236 + docs/manual/{ => other-topics}/whos-using.md | 0 docs/manual/querying.md | 452 - docs/manual/raw-queries.md | 153 - docs/manual/transactions.md | 204 - docs/manual/typescript.md | 185 - docs/manual/upgrade-to-v5.md | 449 - docs/redirects.json | 4 + docs/redirects/create-redirects.js | 18 + docs/scripts/.eslintrc | 5 + docs/scripts/menu-groups.js | 26 + docs/transforms/fix-ids.js | 46 + docs/transforms/header-customization.js | 4 +- docs/transforms/menu-groups.js | 54 +- docs/transforms/meta-tags.js | 5 + docs/upgrade-to-v6.md | 104 - lib/associations/base.js | 7 +- lib/associations/belongs-to-many.js | 210 +- lib/associations/belongs-to.js | 51 +- lib/associations/has-many.js | 187 +- lib/associations/has-one.js | 108 +- lib/associations/helpers.js | 20 +- lib/associations/mixin.js | 16 +- lib/data-types.js | 43 +- lib/deferrable.js | 14 +- lib/dialects/abstract/connection-manager.js | 186 +- lib/dialects/abstract/index.js | 7 +- lib/dialects/abstract/parser-store.js | 16 - lib/dialects/abstract/query-generator.js | 504 +- .../abstract/query-generator/helpers/quote.js | 4 +- .../abstract/query-generator/operators.js | 40 +- .../abstract/query-generator/transaction.js | 4 +- lib/dialects/abstract/query-interface.js | 1260 +++ lib/dialects/abstract/query.js | 15 +- lib/dialects/mariadb/connection-manager.js | 108 +- lib/dialects/mariadb/data-types.js | 15 +- lib/dialects/mariadb/index.js | 17 +- lib/dialects/mariadb/query-generator.js | 30 +- lib/dialects/mariadb/query.js | 163 +- lib/dialects/mssql/async-queue.js | 46 + lib/dialects/mssql/connection-manager.js | 143 +- lib/dialects/mssql/data-types.js | 3 +- lib/dialects/mssql/index.js | 76 +- lib/dialects/mssql/query-generator.js | 359 +- lib/dialects/mssql/query-interface.js | 131 +- lib/dialects/mssql/query.js | 159 +- lib/dialects/mssql/resource-lock.js | 25 - lib/dialects/mysql/connection-manager.js | 166 +- lib/dialects/mysql/data-types.js | 5 +- lib/dialects/mysql/index.js | 67 +- lib/dialects/mysql/query-generator.js | 237 +- lib/dialects/mysql/query-interface.js | 150 +- lib/dialects/mysql/query.js | 153 +- lib/dialects/parserStore.js | 23 + lib/dialects/postgres/connection-manager.js | 169 +- lib/dialects/postgres/data-types.js | 8 +- lib/dialects/postgres/index.js | 90 +- lib/dialects/postgres/query-generator.js | 96 +- lib/dialects/postgres/query-interface.js | 192 +- lib/dialects/postgres/query.js | 424 +- lib/dialects/sqlite/connection-manager.js | 94 +- lib/dialects/sqlite/index.js | 61 +- lib/dialects/sqlite/query-generator.js | 46 +- lib/dialects/sqlite/query-interface.js | 371 +- lib/dialects/sqlite/query.js | 180 +- lib/errors/aggregate-error.js | 34 + lib/errors/bulk-record-error.js | 2 +- lib/errors/connection-error.js | 1 + lib/errors/database-error.js | 12 +- .../database/exclusion-constraint-error.js | 5 +- .../database/foreign-key-constraint-error.js | 5 +- lib/errors/database/timeout-error.js | 4 +- .../database/unknown-constraint-error.js | 5 +- lib/errors/index.js | 2 + lib/errors/optimistic-lock-error.js | 5 +- lib/errors/validation-error.js | 41 +- .../validation/unique-constraint-error.js | 5 +- lib/hooks.js | 505 +- lib/instance-validator.js | 172 +- lib/model-manager.js | 15 +- lib/model.js | 2469 ++--- lib/operators.js | 79 +- lib/promise.js | 7 - lib/query-interface.js | 1441 --- lib/sequelize.js | 499 +- lib/transaction.js | 187 +- lib/utils.js | 43 +- ...ssToInvokable.js => class-to-invokable.js} | 0 lib/utils/deprecations.js | 4 +- lib/utils/join-sql-fragments.js | 83 + lib/utils/logger.js | 9 +- package.json | 236 +- scripts/appveyor-setup.ps1 | 50 - scripts/mocha-bootload | 1 - scripts/setup-mssql | 1 - sscce.js | 31 + sscce_template.js | 10 - test/config/config.js | 54 +- test/integration/associations/alias.test.js | 110 +- .../associations/belongs-to-many.test.js | 3420 +++---- .../associations/belongs-to.test.js | 1030 +-- .../integration/associations/has-many.test.js | 1950 ++-- test/integration/associations/has-one.test.js | 902 +- .../multiple-level-filters.test.js | 338 +- test/integration/associations/scope.test.js | 743 +- test/integration/associations/self.test.js | 154 +- test/integration/cls.test.js | 181 + test/integration/configuration.test.js | 157 +- test/integration/data-types.test.js | 651 +- .../abstract/connection-manager.test.js | 111 +- .../dialects/mariadb/associations.test.js | 28 +- .../mariadb/connector-manager.test.js | 42 +- .../dialects/mariadb/dao-factory.test.js | 43 +- test/integration/dialects/mariadb/dao.test.js | 117 +- .../dialects/mariadb/errors.test.js | 93 +- .../dialects/mariadb/query-interface.test.js | 20 +- .../dialects/mssql/connection-manager.test.js | 16 +- .../dialects/mssql/query-queue.test.js | 93 +- .../dialects/mssql/regressions.test.js | 266 +- .../dialects/mysql/associations.test.js | 91 +- .../dialects/mysql/connector-manager.test.js | 37 +- .../dialects/mysql/dao-factory.test.js | 43 +- .../integration/dialects/mysql/errors.test.js | 81 +- .../dialects/mysql/warning.test.js | 25 +- .../dialects/postgres/associations.test.js | 95 +- .../postgres/connection-manager.test.js | 146 +- .../integration/dialects/postgres/dao.test.js | 1321 ++- .../dialects/postgres/data-types.test.js | 225 +- .../dialects/postgres/error.test.js | 35 +- .../dialects/postgres/query-interface.test.js | 302 +- .../dialects/postgres/query.test.js | 97 +- .../dialects/postgres/range.test.js | 16 +- .../dialects/postgres/regressions.test.js | 32 +- .../sqlite/connection-manager.test.js | 71 +- .../dialects/sqlite/dao-factory.test.js | 125 +- test/integration/dialects/sqlite/dao.test.js | 100 +- .../dialects/sqlite/sqlite-master.test.js | 64 +- test/integration/error.test.js | 142 +- test/integration/hooks/associations.test.js | 733 +- test/integration/hooks/bulkOperation.test.js | 396 +- test/integration/hooks/count.test.js | 31 +- test/integration/hooks/create.test.js | 165 +- test/integration/hooks/destroy.test.js | 82 +- test/integration/hooks/find.test.js | 115 +- test/integration/hooks/hooks.test.js | 287 +- test/integration/hooks/restore.test.js | 61 +- .../hooks/updateAttributes.test.js | 148 +- test/integration/hooks/upsert.test.js | 54 +- test/integration/hooks/validate.test.js | 143 +- test/integration/include.test.js | 1270 +-- test/integration/include/findAll.test.js | 2720 +++--- .../include/findAndCountAll.test.js | 503 +- test/integration/include/findOne.test.js | 371 +- test/integration/include/limit.test.js | 1091 +-- test/integration/include/paranoid.test.js | 70 +- test/integration/include/schema.test.js | 1849 ++-- test/integration/include/separate.test.js | 752 +- test/integration/instance.test.js | 799 +- test/integration/instance.validations.test.js | 495 +- test/integration/instance/decrement.test.js | 226 +- test/integration/instance/destroy.test.js | 511 +- test/integration/instance/increment.test.js | 214 +- test/integration/instance/reload.test.js | 415 +- test/integration/instance/save.test.js | 750 +- test/integration/instance/to-json.test.js | 212 +- test/integration/instance/update.test.js | 511 +- test/integration/instance/values.test.js | 172 +- test/integration/json.test.js | 350 +- test/integration/model.test.js | 3166 +++---- test/integration/model/attributes.test.js | 133 +- .../model/attributes/field.test.js | 560 +- .../model/attributes/types.test.js | 154 +- test/integration/model/bulk-create.test.js | 1150 ++- .../model/bulk-create/include.test.js | 775 +- test/integration/model/count.test.js | 220 +- test/integration/model/create.test.js | 1514 ++-- test/integration/model/create/include.test.js | 462 +- test/integration/model/findAll.test.js | 1954 ++-- test/integration/model/findAll/group.test.js | 119 +- .../model/findAll/groupedLimit.test.js | 251 +- test/integration/model/findAll/order.test.js | 68 +- .../model/findAll/separate.test.js | 93 +- test/integration/model/findOne.test.js | 1086 ++- test/integration/model/findOrBuild.test.js | 60 +- test/integration/model/geography.test.js | 346 +- test/integration/model/geometry.test.js | 199 +- test/integration/model/increment.test.js | 306 +- test/integration/model/json.test.js | 1036 ++- test/integration/model/notExist.test.js | 63 + .../model/optimistic_locking.test.js | 87 +- test/integration/model/paranoid.test.js | 91 +- test/integration/model/schema.test.js | 685 +- test/integration/model/scope.test.js | 94 +- .../integration/model/scope/aggregate.test.js | 97 +- .../model/scope/associations.test.js | 385 +- test/integration/model/scope/count.test.js | 81 +- test/integration/model/scope/destroy.test.js | 95 +- test/integration/model/scope/find.test.js | 126 +- .../model/scope/findAndCountAll.test.js | 75 +- test/integration/model/scope/merge.test.js | 79 +- test/integration/model/scope/update.test.js | 99 +- test/integration/model/searchPath.test.js | 639 +- test/integration/model/sum.test.js | 30 +- test/integration/model/sync.test.js | 425 +- test/integration/model/update.test.js | 219 +- test/integration/model/upsert.test.js | 724 +- test/integration/operators.test.js | 181 +- test/integration/pool.test.js | 236 +- test/integration/query-interface.test.js | 972 +- .../query-interface/changeColumn.test.js | 426 +- .../query-interface/createTable.test.js | 200 +- .../query-interface/describeTable.test.js | 219 +- .../query-interface/dropEnum.test.js | 46 +- .../getForeignKeyReferencesForTable.test.js | 44 + .../query-interface/removeColumn.test.js | 184 +- test/integration/replication.test.js | 36 +- test/integration/schema.test.js | 42 +- test/integration/sequelize.test.js | 1426 +-- .../integration/sequelize.transaction.test.js | 214 +- test/integration/sequelize/deferrable.test.js | 189 +- test/integration/sequelize/query.test.js | 695 ++ test/integration/support.js | 63 +- test/integration/timezone.test.js | 61 +- test/integration/transaction.test.js | 1442 +-- test/integration/trigger.test.js | 70 +- test/integration/utils.test.js | 60 +- test/integration/vectors.test.js | 19 +- test/support.js | 148 +- scripts/teaser => test/teaser.js | 3 +- test/tmp/.gitkeep | 1 + .../unit/associations/belongs-to-many.test.js | 78 +- test/unit/associations/belongs-to.test.js | 10 +- test/unit/associations/has-many.test.js | 80 +- test/unit/associations/has-one.test.js | 10 +- test/unit/configuration.test.js | 13 + test/unit/connection-manager.test.js | 55 +- .../dialects/abstract/query-generator.test.js | 70 +- .../dialects/mariadb/query-generator.test.js | 64 +- .../dialects/mssql/connection-manager.test.js | 64 +- .../dialects/mssql/query-generator.test.js | 106 +- test/unit/dialects/mssql/query.test.js | 60 +- .../unit/dialects/mssql/resource-lock.test.js | 68 - .../dialects/mysql/query-generator.test.js | 12 +- test/unit/dialects/mysql/query.test.js | 9 +- .../dialects/postgres/query-generator.test.js | 65 +- .../sqlite/connection-manager.test.js | 31 + .../dialects/sqlite/query-generator.test.js | 25 +- test/unit/errors.test.js | 27 + test/unit/hooks.test.js | 299 +- test/unit/increment.test.js | 12 +- test/unit/instance-validator.test.js | 91 +- test/unit/instance/build.test.js | 14 +- test/unit/instance/changed.test.js | 47 +- test/unit/instance/decrement.test.js | 2 +- test/unit/instance/destroy.test.js | 2 +- test/unit/instance/get.test.js | 4 +- test/unit/instance/increment.test.js | 2 +- test/unit/instance/is-soft-deleted.test.js | 4 +- test/unit/instance/previous.test.js | 2 +- test/unit/instance/reload.test.js | 2 +- test/unit/instance/restore.test.js | 2 +- test/unit/instance/save.test.js | 10 +- test/unit/instance/set.test.js | 72 +- test/unit/instance/to-json.test.js | 4 +- test/unit/model/bulkcreate.test.js | 14 +- test/unit/model/count.test.js | 32 +- test/unit/model/destroy.test.js | 18 +- test/unit/model/find-and-count-all.test.js | 22 +- test/unit/model/find-create-find.test.js | 50 +- test/unit/model/find-or-create.test.js | 70 + test/unit/model/findall.test.js | 61 +- test/unit/model/findone.test.js | 49 +- test/unit/model/include.test.js | 20 + test/unit/model/overwriting-builtins.test.js | 2 +- test/unit/model/removeAttribute.test.js | 2 +- test/unit/model/scope.test.js | 23 +- test/unit/model/update.test.js | 31 +- test/unit/model/upsert.test.js | 45 +- test/unit/model/validation.test.js | 236 +- test/unit/promise.test.js | 16 - test/unit/sql/add-column.test.js | 2 +- test/unit/sql/add-constraint.test.js | 21 +- test/unit/sql/create-schema.test.js | 2 +- test/unit/sql/create-table.test.js | 25 +- test/unit/sql/delete.test.js | 2 +- test/unit/sql/enum.test.js | 2 +- test/unit/sql/generateJoin.test.js | 18 +- test/unit/sql/get-constraint-snippet.test.js | 2 +- test/unit/sql/group.test.js | 54 + test/unit/sql/index.test.js | 63 +- test/unit/sql/insert.test.js | 49 +- test/unit/sql/json.test.js | 2 +- test/unit/sql/offset-limit.test.js | 11 +- test/unit/sql/order.test.js | 2 +- test/unit/sql/remove-column.test.js | 2 +- test/unit/sql/remove-constraint.test.js | 2 +- test/unit/sql/select.test.js | 220 +- test/unit/sql/show-constraints.test.js | 2 +- test/unit/sql/update.test.js | 35 +- test/unit/sql/where.test.js | 51 +- test/unit/transaction.test.js | 30 +- test/unit/utils.test.js | 26 +- types/index.d.ts | 7 +- types/lib/associations/base.d.ts | 14 +- types/lib/associations/belongs-to-many.d.ts | 47 +- types/lib/associations/belongs-to.d.ts | 12 +- types/lib/associations/has-many.d.ts | 21 +- types/lib/associations/has-one.d.ts | 11 +- types/lib/connection-manager.d.ts | 2 - types/lib/data-types.d.ts | 2 + types/lib/errors.d.ts | 71 +- types/lib/hooks.d.ts | 200 +- types/lib/instance-validator.d.ts | 9 +- types/lib/model-manager.d.ts | 6 +- types/lib/model.d.ts | 1137 ++- types/lib/operators.d.ts | 16 +- types/lib/promise.d.ts | 6 - types/lib/query-interface.d.ts | 193 +- types/lib/query.d.ts | 328 + types/lib/sequelize.d.ts | 735 +- types/lib/transaction.d.ts | 138 +- types/lib/utils.d.ts | 23 +- types/test/attributes.ts | 44 + types/test/connection.ts | 38 +- types/test/count.ts | 19 +- types/test/create.ts | 74 + types/test/data-types.ts | 56 +- types/test/define.ts | 75 +- types/test/e2e/docs-example.ts | 12 +- types/test/errors.ts | 63 +- types/test/{findById.ts => findByPk.ts} | 0 types/test/findOne.ts | 6 + types/test/hooks.ts | 110 +- types/test/include.ts | 2 + types/test/model.ts | 161 +- types/test/models/User.ts | 54 +- types/test/models/UserGroup.ts | 12 +- types/test/promise.ts | 6 - types/test/query-interface.ts | 291 +- types/test/sequelize.ts | 45 +- types/test/transaction.ts | 52 +- types/test/tsconfig.json | 1 - types/test/type-helpers/deep-writable.ts | 48 + types/test/typescriptDocs/Define.ts | 38 + .../test/typescriptDocs/DefineNoAttributes.ts | 30 + types/test/typescriptDocs/ModelInit.ts | 217 + .../typescriptDocs/ModelInitNoAttributes.ts | 47 + types/test/update.ts | 37 + types/test/upsert.ts | 28 +- types/test/validators.ts | 22 + types/test/where.ts | 506 +- types/type-helpers/set-required.d.ts | 16 + yarn.lock | 7913 +++++++++++++++++ 458 files changed, 56690 insertions(+), 42122 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/docs_issue.md delete mode 100644 .github/ISSUE_TEMPLATE/documentational-issue.md delete mode 100644 .github/stale.yml create mode 100644 .github/workflows/auto-remove-awaiting-response-label.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .mocharc.jsonc delete mode 100644 .travis.yml delete mode 100644 Dockerfile create mode 100644 ENGINE.md delete mode 100644 appveyor.yml create mode 100644 dev/mariadb/10.3/check.js create mode 100644 dev/mariadb/10.3/docker-compose.yml create mode 100755 dev/mariadb/10.3/start.sh create mode 100755 dev/mariadb/10.3/stop.sh create mode 100644 dev/mssql/2019/check.js create mode 100644 dev/mssql/2019/docker-compose.yml create mode 100755 dev/mssql/2019/start.sh create mode 100755 dev/mssql/2019/stop.sh create mode 100644 dev/mysql/5.7/check.js create mode 100644 dev/mysql/5.7/docker-compose.yml create mode 100755 dev/mysql/5.7/start.sh create mode 100755 dev/mysql/5.7/stop.sh create mode 100644 dev/mysql/8.0/check.js create mode 100644 dev/mysql/8.0/docker-compose.yml create mode 100755 dev/mysql/8.0/start.sh create mode 100755 dev/mysql/8.0/stop.sh create mode 100644 dev/postgres/10/check.js create mode 100644 dev/postgres/10/docker-compose.yml create mode 100755 dev/postgres/10/start.sh create mode 100755 dev/postgres/10/stop.sh create mode 100644 dev/sscce-helpers.d.ts create mode 100644 dev/sscce-helpers.js create mode 100755 dev/wait-until-healthy.sh delete mode 100644 docker-compose.yml create mode 100644 docs/images/logo-simple.svg create mode 100644 docs/images/logo.svg create mode 100644 docs/manual-utils.js create mode 100644 docs/manual/advanced-association-concepts/advanced-many-to-many.md create mode 100644 docs/manual/advanced-association-concepts/association-scopes.md create mode 100644 docs/manual/advanced-association-concepts/creating-with-associations.md create mode 100644 docs/manual/advanced-association-concepts/eager-loading.md create mode 100644 docs/manual/advanced-association-concepts/polymorphic-associations.md delete mode 100644 docs/manual/associations.md create mode 100644 docs/manual/core-concepts/assocs.md create mode 100644 docs/manual/core-concepts/getters-setters-virtuals.md create mode 100644 docs/manual/core-concepts/getting-started.md create mode 100644 docs/manual/core-concepts/model-basics.md create mode 100644 docs/manual/core-concepts/model-instances.md create mode 100644 docs/manual/core-concepts/model-querying-basics.md create mode 100644 docs/manual/core-concepts/model-querying-finders.md create mode 100644 docs/manual/core-concepts/paranoid.md create mode 100644 docs/manual/core-concepts/raw-queries.md create mode 100644 docs/manual/core-concepts/validations-and-constraints.md delete mode 100644 docs/manual/data-types.md delete mode 100644 docs/manual/dialects.md delete mode 100644 docs/manual/getting-started.md delete mode 100644 docs/manual/hooks.md delete mode 100644 docs/manual/instances.md delete mode 100644 docs/manual/migrations.md delete mode 100644 docs/manual/models-definition.md delete mode 100644 docs/manual/models-usage.md create mode 100644 docs/manual/moved/associations.md create mode 100644 docs/manual/moved/data-types.md create mode 100644 docs/manual/moved/models-definition.md create mode 100644 docs/manual/moved/models-usage.md create mode 100644 docs/manual/moved/querying.md create mode 100644 docs/manual/other-topics/connection-pool.md create mode 100644 docs/manual/other-topics/constraints-and-circularities.md create mode 100644 docs/manual/other-topics/dialect-specific-things.md create mode 100644 docs/manual/other-topics/extending-data-types.md create mode 100644 docs/manual/other-topics/hooks.md create mode 100644 docs/manual/other-topics/indexes.md rename docs/manual/{ => other-topics}/legacy.md (92%) rename docs/manual/{ => other-topics}/legal.md (98%) create mode 100644 docs/manual/other-topics/migrations.md create mode 100644 docs/manual/other-topics/naming-strategies.md create mode 100644 docs/manual/other-topics/optimistic-locking.md create mode 100644 docs/manual/other-topics/other-data-types.md create mode 100644 docs/manual/other-topics/query-interface.md rename docs/manual/{ => other-topics}/read-replication.md (57%) rename docs/manual/{ => other-topics}/resources.md (75%) rename docs/manual/{ => other-topics}/scopes.md (52%) create mode 100644 docs/manual/other-topics/sub-queries.md create mode 100644 docs/manual/other-topics/transactions.md create mode 100644 docs/manual/other-topics/typescript.md create mode 100644 docs/manual/other-topics/upgrade-to-v6.md rename docs/manual/{ => other-topics}/whos-using.md (100%) delete mode 100644 docs/manual/querying.md delete mode 100644 docs/manual/raw-queries.md delete mode 100644 docs/manual/transactions.md delete mode 100644 docs/manual/typescript.md delete mode 100644 docs/manual/upgrade-to-v5.md create mode 100644 docs/redirects.json create mode 100644 docs/redirects/create-redirects.js create mode 100644 docs/scripts/.eslintrc create mode 100644 docs/scripts/menu-groups.js create mode 100644 docs/transforms/fix-ids.js create mode 100644 docs/transforms/meta-tags.js delete mode 100644 docs/upgrade-to-v6.md delete mode 100644 lib/dialects/abstract/parser-store.js mode change 100755 => 100644 lib/dialects/abstract/query-generator.js create mode 100644 lib/dialects/abstract/query-interface.js mode change 100755 => 100644 lib/dialects/abstract/query.js create mode 100644 lib/dialects/mssql/async-queue.js delete mode 100644 lib/dialects/mssql/resource-lock.js create mode 100644 lib/dialects/parserStore.js mode change 100755 => 100644 lib/dialects/postgres/query-generator.js create mode 100644 lib/errors/aggregate-error.js delete mode 100644 lib/promise.js delete mode 100644 lib/query-interface.js mode change 100755 => 100644 lib/sequelize.js rename lib/utils/{classToInvokable.js => class-to-invokable.js} (100%) create mode 100644 lib/utils/join-sql-fragments.js delete mode 100644 scripts/appveyor-setup.ps1 delete mode 100644 scripts/mocha-bootload delete mode 100755 scripts/setup-mssql create mode 100644 sscce.js delete mode 100644 sscce_template.js create mode 100644 test/integration/cls.test.js mode change 100755 => 100644 test/integration/include.test.js mode change 100755 => 100644 test/integration/include/separate.test.js mode change 100755 => 100644 test/integration/model.test.js create mode 100644 test/integration/model/notExist.test.js create mode 100644 test/integration/query-interface/getForeignKeyReferencesForTable.test.js mode change 100755 => 100644 test/integration/sequelize.test.js create mode 100644 test/integration/sequelize/query.test.js rename scripts/teaser => test/teaser.js (88%) delete mode 100644 test/unit/dialects/mssql/resource-lock.test.js create mode 100644 test/unit/dialects/sqlite/connection-manager.test.js create mode 100644 test/unit/model/find-or-create.test.js delete mode 100644 test/unit/promise.test.js mode change 100755 => 100644 test/unit/sql/generateJoin.test.js create mode 100644 test/unit/sql/group.test.js mode change 100755 => 100644 test/unit/sql/order.test.js mode change 100755 => 100644 test/unit/sql/select.test.js mode change 100755 => 100644 test/unit/sql/where.test.js delete mode 100644 types/lib/promise.d.ts create mode 100644 types/lib/query.d.ts create mode 100644 types/test/attributes.ts create mode 100644 types/test/create.ts rename types/test/{findById.ts => findByPk.ts} (100%) create mode 100644 types/test/findOne.ts delete mode 100644 types/test/promise.ts create mode 100644 types/test/type-helpers/deep-writable.ts create mode 100644 types/test/typescriptDocs/Define.ts create mode 100644 types/test/typescriptDocs/DefineNoAttributes.ts create mode 100644 types/test/typescriptDocs/ModelInit.ts create mode 100644 types/test/typescriptDocs/ModelInitNoAttributes.ts create mode 100644 types/test/update.ts create mode 100644 types/test/validators.ts create mode 100644 types/type-helpers/set-required.d.ts create mode 100644 yarn.lock diff --git a/.editorconfig b/.editorconfig index bc048d8c822c..85e074827ad8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,18 +6,9 @@ root = true [*] -# Change these settings to your own preference indent_style = space indent_size = 2 - -# We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false - -[Makefile] -indent_style = tabs diff --git a/.eslintrc.json b/.eslintrc.json index 2c5351e7c6d0..38d78cf4ea61 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -30,12 +30,15 @@ "new-cap": [ "error", { - "capIsNewExceptionPattern": "^BigInt", "properties": false } ], "semi": ["error", "always"], - "space-before-function-paren": ["error", "never"], + "space-before-function-paren": ["error", { + "named": "never", + "anonymous": "never", + "asyncArrow": "always" + }], "space-before-blocks": "error", "space-infix-ops": "error", "no-multi-spaces": "error", @@ -91,16 +94,25 @@ "no-unused-expressions": "error", "no-sequences": "error", "no-self-compare": "error", - "no-case-declarations": "off" + "no-case-declarations": "off", + "prefer-object-spread": "error" + }, + "settings": { + "jsdoc": { + "tagNamePreference": { + "augments": "extends" + } + } }, "parserOptions": { - "ecmaVersion": 9, + "ecmaVersion": 2020, "sourceType": "script" }, "plugins": ["mocha", "jsdoc"], "env": { "node": true, "mocha": true, - "es6": true + "es6": true, + "es2020": true } } diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..ce2684934d6f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -lf \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000000..1bad4b3d7eda --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: sequelize +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b9f4e14b62c3..3f5e52402f12 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,21 +1,25 @@ --- name: Bug report about: Create a bug report to help us improve -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- -## Issue Description +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + +## Bug Description -### What are you doing? +### SSCCE ```js // You can delete this code block if you have included a link to your SSCCE above! -// MINIMAL, SELF-CONTAINED code here (SSCCE/MCVE/reprex) +const { createSequelizeInstance } = require("./dev/sscce-helpers"); +const { Model, DataTypes } = require("."); + +const sequelize = createSequelizeInstance({ benchmark: true }); + +class User extends Model {} +User.init( + { + username: DataTypes.STRING, + birthday: DataTypes.DATE, + }, + { sequelize, modelName: "user" } +); + +(async () => { + await sequelize.sync({ force: true }); + + const jane = await User.create({ + username: "janedoe", + birthday: new Date(1980, 6, 20), + }); + + console.log("\nJane:", jane.toJSON()); + + await sequelize.close(); +})(); ``` ### What do you expect to happen? @@ -53,16 +81,15 @@ Output here ### Additional context -Add any other context or screenshots about the feature request here. +Add any other context and details here. ### Environment - Sequelize version: XXX - Node.js version: XXX -- Operating System: XXX -- If TypeScript related: TypeScript version: XXX +- If TypeScript related: TypeScript version: XXX -## Issue Template Checklist +## Bug Report Checklist diff --git a/.github/ISSUE_TEMPLATE/docs_issue.md b/.github/ISSUE_TEMPLATE/docs_issue.md new file mode 100644 index 000000000000..b744494940dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/docs_issue.md @@ -0,0 +1,31 @@ +--- +name: Docs issue +about: Documentation is unclear, or otherwise insufficient/misleading +title: "" +labels: "type: docs" +assignees: "" +--- + + + +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + +## Issue Description + +### What was unclear/insufficient/not covered in the documentation + +Write here. + +### If possible: Provide some suggestion on how we can enhance the docs + +Write here. + +### Additional context + +Add any other context or screenshots about the issue here. diff --git a/.github/ISSUE_TEMPLATE/documentational-issue.md b/.github/ISSUE_TEMPLATE/documentational-issue.md deleted file mode 100644 index 804a80552387..000000000000 --- a/.github/ISSUE_TEMPLATE/documentational-issue.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Docs issue -about: Documentation is unclear, or otherwise insufficient/misleading -title: '' -labels: 'type: docs' -assignees: '' - ---- - - - -## Issue Description - -### What was unclear/insufficient/not covered in the documentation - -Write here. - -### If possible: Provide some suggestion on how we can enhance the docs - -Write here. - -### Additional context -Add any other context or screenshots about the issue here. - -## Issue Template Checklist - - - -### Is this issue dialect-specific? - -- [ ] No. This issue is relevant to Sequelize as a whole. -- [ ] Yes. This issue only applies to the following dialect(s): XXX, YYY, ZZZ -- [ ] I don't know. - -### Would you be willing to resolve this issue by submitting a Pull Request? - - - -- [ ] Yes, I have the time and I know how to start. -- [ ] Yes, I have the time but I don't know how to start, I would need guidance. -- [ ] No, I don't have the time, although I believe I could do it if I had the time... -- [ ] No, I don't have the time and I wouldn't even know how to start. - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c1960c62f846..f47e9fbd1223 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,24 +1,29 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- -## Issue Description +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + +## Feature Description ### Is your feature request related to a problem? Please describe. -A clear and concise description of what the problem is. Example: I'm always frustrated when [...] + +A clear and concise description of what the problem is. Example: I'm always frustrated when ... ### Describe the solution you'd like + A clear and concise description of what you want to happen. How can the requested feature be used to approach the problem it's supposed to solve? ```js @@ -26,24 +31,27 @@ A clear and concise description of what you want to happen. How can the requeste ``` ### Why should this be in Sequelize + Short explanation why this should be part of Sequelize rather than a separate package. ### Describe alternatives/workarounds you've considered + A clear and concise description of any alternative solutions or features you've considered. If any workaround exists to the best of your knowledge, include it here. ### Additional context + Add any other context or screenshots about the feature request here. -## Issue Template Checklist +## Feature Request Checklist -### Is this issue dialect-specific? +### Is this feature dialect-specific? -- [ ] No. This issue is relevant to Sequelize as a whole. -- [ ] Yes. This issue only applies to the following dialect(s): XXX, YYY, ZZZ +- [ ] No. This feature is relevant to Sequelize as a whole. +- [ ] Yes. This feature only applies to the following dialect(s): XXX, YYY, ZZZ -### Would you be willing to resolve this issue by submitting a Pull Request? +### Would you be willing to implement this feature by submitting a Pull Request? diff --git a/.github/ISSUE_TEMPLATE/other_issue.md b/.github/ISSUE_TEMPLATE/other_issue.md index e8a25ba61747..4f5ee3cf41a4 100644 --- a/.github/ISSUE_TEMPLATE/other_issue.md +++ b/.github/ISSUE_TEMPLATE/other_issue.md @@ -1,18 +1,21 @@ --- name: Other about: Open an issue that does not fall directly into the other categories -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- +## Issue Creation Checklist + +[ ] I have read the [contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) + ## Issue Description A clear and concise description of what is this issue about. @@ -23,11 +26,8 @@ If applicable, you can add some code. In this case, an SSCCE/MCVE/reprex is much Check http://sscce.org/ or https://stackoverflow.com/help/minimal-reproducible-example to learn more about SSCCE/MCVE/reprex. --> -### StackOverflow / Slack attempts - -If you have tried asking on StackOverflow / Slack about this, add a link to that here. - ### Additional context + Add any other context or screenshots about the issue here. ## Issue Template Checklist diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c1659ed024fa..913e5c042197 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ - -### Pull Request check-list +### Pull Request Checklist _Please make sure to review and check all of these items:_ -- [ ] Does `npm run test` or `npm run test-DIALECT` pass with this change (including linting)? -- [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? - [ ] Have you added new tests to prevent regressions? +- [ ] Does `npm run test` or `npm run test-DIALECT` pass with this change (including linting)? - [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)? - [ ] Did you update the typescript typings accordingly (if applicable)? -- [ ] Did you follow the commit message conventions explained in [CONTRIBUTING.md](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md)? +- [ ] Does the description below contain a link to an existing issue (Closes #[issue]) or a description of the issue you are solving? +- [ ] Did you follow the commit message conventions explained in [CONTRIBUTING.md](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md)? -### Description of change +### Description Of Change + +### Todos + +- [ ] +- [ ] +- [ ] diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index dca733fa73dd..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,40 +0,0 @@ - -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an issue becomes stale -# daysUntilStale: 90 -daysUntilStale: 900000 # Temporarily disable - -# Number of days of inactivity before a stale issue is closed -# daysUntilClose: 7 -daysUntilClose: 70000 # Temporarily disable - -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - type: feature - - type: docs - - type: bug - - discussion - - type: performance - - breaking change - - good first issue - - suggestion - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Label to use when marking an issue as stale -staleLabel: stale - -# Limit to only `issues` or `pulls` -only: issues - -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. - If this is still an issue, just leave a comment 🙂 - -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/auto-remove-awaiting-response-label.yml b/.github/workflows/auto-remove-awaiting-response-label.yml new file mode 100644 index 000000000000..1af396f8c67b --- /dev/null +++ b/.github/workflows/auto-remove-awaiting-response-label.yml @@ -0,0 +1,19 @@ +name: Auto-remove "awaiting response" label +on: + issue_comment: + types: [created] + +jobs: + auto-remove-awaiting-response-label: + name: Run + runs-on: ubuntu-latest + env: + # Case insensitive. Replace spaces with `%20`. + LABEL_TO_REMOVE: "status:%20awaiting%20response" + steps: + - name: Run + run: |- + curl -X DELETE \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "${{ github.event.comment.issue_url }}/labels/$LABEL_TO_REMOVE" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..97b6573d77e4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,217 @@ +name: CI +on: [push, pull_request] + +env: + SEQ_DB: sequelize_test + SEQ_USER: sequelize_test + SEQ_PW: sequelize_test + +jobs: + lint: + name: Lint code and docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: yarn install --frozen-lockfile --ignore-engines + - run: yarn lint + - run: yarn lint-docs + test-typings: + strategy: + fail-fast: false + matrix: + ts-version: ["3.9", "4.0", "4.1"] + name: TS Typings (${{ matrix.ts-version }}) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: yarn install --frozen-lockfile --ignore-engines + - run: yarn add --dev typescript@~${{ matrix.ts-version }} --ignore-engines + - run: yarn test-typings + test-sqlite: + strategy: + fail-fast: false + matrix: + node-version: [10, 12] + name: SQLite (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + env: + DIALECT: sqlite + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install --frozen-lockfile --ignore-engines + - name: Unit Tests + run: yarn test-unit + - name: Integration Tests + run: yarn test-integration + test-postgres: + strategy: + fail-fast: false + matrix: + node-version: [10, 12] + postgres-version: [9.5, 10] # Does not work with 12 + minify-aliases: [true, false] + native: [true, false] + name: Postgres ${{ matrix.postgres-version }}${{ matrix.native && ' (native)' || '' }} (Node ${{ matrix.node-version }})${{ matrix.minify-aliases && ' (minified aliases)' || '' }} + runs-on: ubuntu-latest + services: + postgres: + image: sushantdhiman/postgres:${{ matrix.postgres-version }} + env: + POSTGRES_USER: sequelize_test + POSTGRES_DB: sequelize_test + POSTGRES_PASSWORD: sequelize_test + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + env: + SEQ_PORT: 5432 + DIALECT: ${{ matrix.native && 'postgres-native' || 'postgres' }} + SEQ_PG_MINIFY_ALIASES: ${{ matrix.minify-aliases && '1' || '' }} + steps: + - run: PGPASSWORD=sequelize_test psql -h localhost -p 5432 -U sequelize_test sequelize_test -c '\l' + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install --frozen-lockfile --ignore-engines + - run: yarn add pg-native --ignore-engines + if: matrix.native + - name: Unit Tests + run: yarn test-unit + if: ${{ !matrix.minify-aliases }} + - name: Integration Tests + run: yarn test-integration + test-mysql-mariadb: + strategy: + fail-fast: false + matrix: + include: + - name: MySQL 5.7 + image: mysql:5.7 + dialect: mysql + node-version: 10 + - name: MySQL 5.7 + image: mysql:5.7 + dialect: mysql + node-version: 12 + - name: MySQL 8.0 + image: mysql:8.0 + dialect: mysql + node-version: 10 + - name: MySQL 8.0 + image: mysql:8.0 + dialect: mysql + node-version: 12 + - name: MariaDB 10.3 + image: mariadb:10.3 + dialect: mariadb + node-version: 10 + - name: MariaDB 10.3 + image: mariadb:10.3 + dialect: mariadb + node-version: 12 + - name: MariaDB 10.5 + image: mariadb:10.5 + dialect: mariadb + node-version: 10 + - name: MariaDB 10.5 + image: mariadb:10.5 + dialect: mariadb + node-version: 12 + name: ${{ matrix.name }} (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + services: + mysql: + image: ${{ matrix.image }} + env: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin -usequelize_test -psequelize_test status" --health-interval 10s --health-timeout 5s --health-retries 5 --tmpfs /var/lib/mysql:rw + env: + SEQ_PORT: 3306 + DIALECT: ${{ matrix.dialect }} + steps: + - run: mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install --frozen-lockfile --ignore-engines + - name: Unit Tests + run: yarn test-unit + - name: Integration Tests + run: yarn test-integration + test-mssql: + strategy: + fail-fast: false + matrix: + node-version: [10, 12] + mssql-version: [2017, 2019] + name: MSSQL ${{ matrix.mssql-version }} (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + services: + mssql: + image: mcr.microsoft.com/mssql/server:${{ matrix.mssql-version }}-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: Password12! + ports: + - 1433:1433 + options: >- + --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P \"Password12!\" -l 30 -Q \"SELECT 1\"" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 10 + env: + DIALECT: mssql + SEQ_USER: SA + SEQ_PW: Password12! + SEQ_PORT: 1433 + steps: + - run: /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Password12!" -Q "CREATE DATABASE sequelize_test; ALTER DATABASE sequelize_test SET READ_COMMITTED_SNAPSHOT ON;" + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn install --frozen-lockfile --ignore-engines + - name: Unit Tests + run: yarn test-unit + - name: Integration Tests + run: yarn test-integration + release: + name: Release + runs-on: ubuntu-latest + needs: + [ + lint, + test-typings, + test-sqlite, + test-postgres, + test-mysql-mariadb, + test-mssql, + ] + if: github.event_name == 'push' && github.ref == 'refs/heads/v6' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: yarn install --frozen-lockfile --ignore-engines + - run: npx semantic-release diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..99e744ce18ed --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,22 @@ +name: "Stale issue handler" +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@main + id: stale + with: + stale-issue-label: "stale" + stale-issue-message: 'This issue has been automatically marked as stale because it has been open for 14 days without activity. It will be closed if no further activity occurs within the next 14 days. If this is still an issue, just leave a comment or remove the "stale" label. 🙂' + days-before-stale: 14 + days-before-close: 14 + operations-per-run: 1000 + days-before-pr-close: -1 + exempt-issue-labels: 'type: bug, type: docs, type: feature, type: other, type: performance, type: refactor, type: typescript' # All 'type: ' labels + - name: Print outputs + run: echo ${{ join(steps.stale.outputs.*, ',') }} diff --git a/.gitignore b/.gitignore index 916e35473f73..3c8f2947202c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,16 @@ *.swp .idea .DS_STORE -npm-debug.log +npm-debug.log* *~ -test/dialects/sqlite/test.sqlite -test/sqlite/test.sqlite test.sqlite -docs/api/tmp.md -ssce.js -sscce.js *.sublime* -package-lock.json -yarn.lock .nyc_output coverage-* coverage test/tmp/* test/binary/tmp/* -site .vscode/ esdoc node_modules diff --git a/.mocharc.jsonc b/.mocharc.jsonc new file mode 100644 index 000000000000..1a10e465d7ff --- /dev/null +++ b/.mocharc.jsonc @@ -0,0 +1,15 @@ +{ + // You can temporarily modify this file during local development to add `spec` (and + // even `grep`) in order to be able to call `DIALECT=some-dialect npx mocha` from a + // terminal and execute only a one (or a few) tests (such as new tests you are + // creating, for example). + // Recall that if you want to `grep` over all tests, you need to specify `spec` as + // `"test/**/*.test.js"`. Not specifying `spec` and calling `npx mocha` will not + // execute any test. + // "spec": ["test/**/bulk-create.test.js", "test/**/upsert.test.js", "test/**/insert.test.js", "test/**/query-generator.test.js"], + // "grep": ["some test title here"], + "exit": true, + "check-leaks": true, + "timeout": 30000, + "reporter": "spec" +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e08087eced3a..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,96 +0,0 @@ -sudo: true -dist: trusty - -language: node_js - -branches: - only: - - master - - v6 - - /^greenkeeper/.*$/ - except: - - /^v\d+\.\d+\.\d+$/ - -cache: npm - -install: - - npm install -g npm@latest - - npm install - - |- - if [ "$DIALECT" = "postgres-native" ]; then npm install pg-native; fi - -env: - global: - - SEQ_DB=sequelize_test - - SEQ_USER=sequelize_test - - SEQ_PW=sequelize_test - - SEQ_HOST=127.0.0.1 - - COVERAGE=true - -before_script: - # setup docker - - "if [ $MARIADB_VER ]; then export MARIADB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mariadb; fi" - - "if [ $MYSQL_VER ]; then export MYSQLDB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mysql; fi" - - "if [ $POSTGRES_VER ] || [ $MARIADB_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MARIADB_VER} ${MYSQL_VER}; fi" - - "if [ $MARIADB_VER ]; then docker run --link ${MARIADB_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" - - "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" - - "if [ $POSTGRES_VER ]; then docker run --link ${POSTGRES_VER}:db -e CHECK_PORT=5432 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi" - -script: - - |- - if [ "$COVERAGE" = true ]; then npm run cover && bash <(curl -s https://codecov.io/bash) -f coverage/lcov.info; else npm run test; fi - -jobs: - include: - - stage: lint - node_js: '10' - script: - - npm run lint - - npm run lint-docs - - npm run test-typings - - stage: test - node_js: '8' - env: DIALECT=sqlite TSC=true - - stage: test - node_js: '8' - sudo: required - env: MARIADB_VER=mariadb-103 SEQ_MARIADB_PORT=8960 DIALECT=mariadb - - stage: test - node_js: '8' - sudo: required - env: MYSQL_VER=mysql-57 SEQ_MYSQL_PORT=8980 DIALECT=mysql - - stage: test - node_js: '8' - sudo: required - env: POSTGRES_VER=postgres-10 SEQ_PG_PORT=8991 DIALECT=postgres - - stage: test - node_js: '8' - sudo: required - env: POSTGRES_VER=postgres-10 SEQ_PG_PORT=8991 SEQ_PG_MINIFY_ALIASES=1 DIALECT=postgres - script: - - npm run test-integration - - stage: test - node_js: '8' - sudo: required - env: POSTGRES_VER=postgres-95 SEQ_PG_PORT=8990 DIALECT=postgres-native - - stage: release - node_js: '10' - script: - - npm run semantic-release - - stage: docs - node_js: '10' - before_deploy: - - npm run docs - deploy: - provider: surge - project: ./esdoc/ - domain: docs.sequelizejs.com - skip_cleanup: true - -stages: - - lint - - test - - name: release - if: (branch = master OR branch = beta) AND type = push AND fork = false - - name: docs - if: branch = master AND type = push AND fork = false diff --git a/CONTACT.md b/CONTACT.md index 8aaf39521ef4..6c2f782067f1 100644 --- a/CONTACT.md +++ b/CONTACT.md @@ -5,12 +5,5 @@ You can use the information below to contact maintainers directly. We will try t ### Via Email -- **Jan Aagaard Meier** janzeh@gmail.com -- **Sushant Dhiman** sushantdhiman@outlook.com - -### Via Slack - -Maintainer's usernames for [Sequelize Slack Channel](https://sequelize.slack.com) - -- **Jan Aagaard Meier** @janmeier -- **Sushant Dhiman** @sushantdhiman +- **Sascha Depold** sascha@depold.com +- **Fauzan** fncolon@pm.me diff --git a/CONTRIBUTING.DOCS.md b/CONTRIBUTING.DOCS.md index 586d036cc5c5..579b0b8e6d55 100644 --- a/CONTRIBUTING.DOCS.md +++ b/CONTRIBUTING.DOCS.md @@ -2,19 +2,11 @@ The sequelize documentation is divided in two parts: -* Tutorials, guides, and example based documentation are written in Markdown -* The API reference is generated automatically from source code comments with [ESDoc](http://esdoc.org) (which uses [JSDoc](http://usejsdoc.org) syntax). +- Tutorials, guides, and example based documentation are written in Markdown +- The API reference is generated automatically from source code comments with [ESDoc](http://esdoc.org) (which uses [JSDoc](http://usejsdoc.org) syntax). -The whole documentation is rendered using ESDoc and continuously deployed to [Surge](http://surge.sh). The output is produced in the `esdoc` folder. +The whole documentation is rendered using ESDoc and continuously deployed to Github Pages at https://sequelize.org. The output is produced in the `esdoc` folder. The tutorials, written in markdown, are located in the `docs` folder. ESDoc is configured to find them in the `"manual"` field of `.esdoc.json`. -To generate the docs locally, run `npm run docs` and open the generated `esdoc/index.html` in your favorite browser. - -## Articles and example based docs - -Write markdown, and have fun :) - -## API docs - -Change the source code documentation comments, using JSDoc syntax, and rerun `npm run docs` to see your changes. +To generate the documentations locally, run `npm run docs` and open the generated `esdoc/index.html` in your favorite browser. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f0dab5cb10d..b7b25a556cf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,146 +1,216 @@ -_Please note!_ The github issue tracker should only be used for feature requests and bugs with a clear description of the issue and the expected behaviour (see below). All questions belong on [Slack](https://sequelize.slack.com), [StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) or [Google groups](https://groups.google.com/forum/#!forum/sequelize). +# Introduction -# Issues -Issues are always very welcome - after all, they are a big part of making sequelize better. However, there are a couple of things you can do to make the lives of the developers _much, much_ easier: +We are happy to see that you might be interested in contributing to Sequelize! There is no need to ask for permission to contribute. For example, anyone can open issues and propose changes to the source code (via Pull Requests). Here are some ways people can contribute: -### Tell us: +- Opening well-written bug reports (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Opening well-written feature requests (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Proposing improvements to the documentation (via [New Issue](https://github.com/sequelize/sequelize/issues/new/choose)) +- Opening Pull Requests to fix bugs or make other improvements +- Reviewing (i.e. commenting on) open Pull Requests, to help their creators improve it if needed and allow maintainers to take less time looking into them +- Helping to clarify issues opened by others, commenting and asking for clarification +- Answering [questions tagged with `sequelize.js` on StackOverflow](https://stackoverflow.com/questions/tagged/sequelize.js) +- Helping people in our [public Slack channel](https://sequelize.slack.com/) (note: if you don't have access, get yourself an invite automatically via [this link](http://sequelize-slack.herokuapp.com/)) -* What you are doing? - * Post a _minimal_ code sample that reproduces the issue, including models and associations - * What do you expect to happen? - * What is actually happening? -* Which dialect you are using (postgres, mysql etc)? -* Which sequelize version you are using? +Sequelize is strongly moved by contributions from people like you. All maintainers also work on their free time here. -When you post code, please use [Github flavored markdown](https://help.github.com/articles/github-flavored-markdown), in order to get proper syntax highlighting! +## Opening Issues -If you can even provide a pull request with a failing unit test, we will love you long time! Plus your issue will likely be fixed much faster. +Issues are always very welcome - after all, they are a big part of making Sequelize better. An issue usually describes a bug, feature request, or documentation improvement request. -# Pull requests -We're glad to get pull request if any functionality is missing or something is buggy. However, there are a couple of things you can do to make life easier for the maintainers: +If you open an issue, try to be as clear as possible. Don't assume that the maintainers will immediately understand the problem. Write your issue in a way that new contributors can also help (add links to helpful resources when applicable). -* Explain the issue that your PR is solving - or link to an existing issue -* Make sure that all existing tests pass -* Make sure you followed [coding guidelines](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md#coding-guidelines) -* Add some tests for your new functionality or a test exhibiting the bug you are solving. Ideally all new tests should not pass _without_ your changes. - - Use [promise style](http://bluebirdjs.com/docs/why-promises.html) in all new tests. Specifically this means: - - don't use `EventEmitter`, `QueryChainer` or the `success`, `done` and `error` events - - don't use a done callback in your test, just return the promise chain. - - Small bugfixes and direct backports to the 4.x branch are accepted without tests. -* If you are adding to / changing the public API, remember to add API docs, in the form of [JSDoc style](http://usejsdoc.org/about-getting-started.html) comments. See [section 4a](#4a-check-the-documentation) for the specifics. +Make sure you know what is an [SSCCE](http://sscce.org/)/[MCVE](https://stackoverflow.com/help/minimal-reproducible-example). -Interested? Coolio! Here is how to get started: +Learn to use [GitHub flavored markdown](https://help.github.com/articles/github-flavored-markdown) to write an issue that is nice to read. -### 1. Prepare your environment -Here comes a little surprise: You need [Node.JS](http://nodejs.org). +### Opening an issue to report a bug -### 2. Install the dependencies +It is essential that you provide an [SSCCE](http://sscce.org/)/[MCVE](https://stackoverflow.com/help/minimal-reproducible-example) for your issue. You can use the [papb/sequelize-sscce](https://github.com/papb/sequelize-sscce) repository. Tell us what is the actual (incorrect) behavior and what should have happened (do not expect the maintainers to know what should happen!). Make sure you checked the bug persists in the latest Sequelize version. -Just "cd" into sequelize directory and run `npm install`, see an example below: +If you can even provide a Pull Request with a failing test (unit test or integration test), that is great! The bug will likely be fixed much faster in this case. -```sh -$ cd path/to/sequelize -$ npm install -``` +You can also create and execute your SSCCE locally: see [Section 5](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#running-an-sscce). -### 3. Database +### Opening an issue to request a new feature -Database instances for testing can be started using Docker or you can use local instances of MySQL and PostgreSQL. +We're more than happy to accept feature requests! Before we get into how you can bring these to our attention, let's talk about our process for evaluating feature requests: -#### 3.a Local instances +- A feature request can have three states - _approved_, _pending_ and _rejected_. + - _Approved_ feature requests are accepted by maintainers as a valuable addition to Sequelize, and are ready to be worked on by anyone. + - _Rejected_ feature requests were considered not applicable to be a part of the Sequelize ORM. This can change, so feel free to comment on a rejected feature request providing a good reasoning and clarification on why it should be reconsidered. + - _Pending_ feature requests are waiting to be looked at by maintainers. They may or may not need clarification. Contributors can still submit pull requests implementing a pending feature request, if they want, at their own risk of having the feature request rejected (and the pull request closed without being merged). -For MySQL and PostgreSQL you'll need to create a DB called `sequelize_test`. -For MySQL this would look like this: +Please be sure to communicate the following: -```sh -$ echo "CREATE DATABASE sequelize_test;" | mysql -uroot -``` +1. What problem your feature request aims to solve OR what aspect of the Sequelize workflow it aims to improve. -**HINT:** by default, your local MySQL install must be with username `root` without password. If you want to customize that, you can set the environment variables `SEQ_DB`, `SEQ_USER`, `SEQ_PW`, `SEQ_HOST` and `SEQ_PORT`. +2. Under what conditions are you anticipating this feature to be most beneficial? -For Postgres, creating the database and (optionally) adding the test user this would look like: +3. Why does it make sense that Sequelize should integrate this feature? -```sh -$ psql +4. See our [Feature Request template](https://github.com/sequelize/sequelize/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) for more details on what to include. Please be sure to follow this template. -# create database sequelize_test; -# create user postgres with superuser; -- optional; usually built-in -``` +If we don't approve your feature request, we'll provide you with our reasoning before closing it out. Some common reasons for denial may include (but are not limited to): + +- Something too similar to already exists within Sequelize +- This feature seems out of scope of what Sequelize exists to accomplish + +We don't want to deny feature requests that could potentially make our users lives easier, so please be sure to clearly communicate your goals within your request! + +### Opening an issue to request improvements to the documentation + +Please state clearly what is missing/unclear/confusing in the documentation. If you have a rough idea of what should be written, please provide a suggestion within the issue. + +## Opening a Pull Request + +A Pull Request is a request for maintainers to "pull" a specific change in code (or documentation) from your copy ("fork") into the repository. + +Anyone can open a Pull Request, there is no need to ask for permission. Maintainers will look at your pull request and tell you if anything else must be done before it can be merged. + +The target of the Pull Request should be the `main` branch (or in rare cases the `v5` branch, if previously agreed with a maintainer). + +Please check the _allow edits from maintainers_ box when opening it. Thank you in advance for any pull requests that you open! + +If you started to work on something but didn't finish it yet, you can open a draft pull request if you want (by choosing the "draft" option). Maintainers will know that it's not ready to be reviewed yet. + +A pull request should mention in its description one or more issues that is addresses. If your pull request does not address any existing issue, explain in its description what it is doing - you are also welcome to write an issue first, and then mention this new issue in the PR description. + +If your pull request implements a new feature, it's better if the feature was already explicitly approved by a maintainer, otherwise you are taking the risk of having the feature request rejected later and your pull request closed without merge. + +Once you open a pull request, our automated checks will run (they take a few minutes). Make sure they are all passing. If they're not, make new commits to your branch fixing that, and the pull request will pick them up automatically and rerun our automated checks. + +Note: if you believe a test failed but is completely unrelated to your changes, it could be a rare situation of a _flaky test_ that is not your fault, and if it's indeed the case, and everything else passed, a maintainer will ignore the _flaky test_ and merge your pull request, so don't worry. + +A pull request that fixes a bug or implements a new feature must add at least one automated test that: + +- Passes +- Would not pass if executed without your implementation + +## How to prepare a development environment for Sequelize + +### 0. Requirements + +Most operating systems provide all the needed tools (including Windows, Linux and MacOS): + +- Mandatory: + + - [Node.js](http://nodejs.org) + - [Git](https://git-scm.com/) + +- Optional (recommended): + + - [Docker](https://docs.docker.com/get-docker/) + - It is not mandatory because you can easily locally run tests against SQLite without it. + - It is practically mandatory if you want to locally run tests against any other database engine (MySQL, MariaDB, Postgres and MSSQL), unless you happen to have the engine installed and is willing to make some manual configuration. + - [Visual Studio Code](https://code.visualstudio.com/) + - [EditorConfig extension](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) + - Also run `npm install --global editorconfig` to make sure this extension will work properly + - [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + +### 1. Clone the repository -You may need to specify credentials using the environment variables `SEQ_PG_USER` and `SEQ_PG_PW` when running tests or set a password of 'postgres' for the postgres user on your local database to allow sequelize to connect via TCP to localhost. Refer to `test/config/config.js` for the default credentials and environment variables. +Clone the repository (if you haven't already) via `git clone https://github.com/sequelize/sequelize`. If you plan on submitting a pull request, you can create a fork by clicking the _fork_ button and clone it instead with `git clone https://github.com/your-github-username/sequelize`, or add your fork as an upstream on the already cloned repo with `git remote add upstream https://github.com/your-github-username/sequelize`. -For Postgres you may also need to install the `postgresql-postgis` package (an optional component of some Postgres distributions, e.g. Ubuntu). The package will be named something like: `postgresql--postgis-`, e.g. `postgresql-9.5-postgis-2.2`. You should be able to find the exact package name on a Debian/Ubuntu system by running the command: `apt-cache search -- -postgis`. +### 2. Install the Node.js dependencies + +Run `npm install` (or `yarn install`) within the cloned repository folder. + +### 3. Prepare local databases to run tests + +If you're happy to run tests only against an SQLite database, you can skip this section. + +#### 3.1. With Docker (recommended) + +If you have Docker installed, use any of the following commands to start fresh local databases of the dialect of your choice: + +- `npm run start-mariadb` +- `npm run start-mysql` +- `npm run start-postgres` +- `npm run start-mssql` + +_Note:_ if you're using Windows, make sure you run these from Git Bash (or another MinGW environment), since these commands will execute bash scripts. Recall that [it's very easy to include Git Bash as your default integrated terminal on Visual Studio Code](https://code.visualstudio.com/docs/editor/integrated-terminal). + +Each of these commands will start a Docker container with the corresponding database, ready to run Sequelize tests (or an SSCCE). + +You can run `npm run stop-X` to stop the servers once you're done. + +##### Hint for Postgres + +You can also easily start a local [pgadmin4](https://www.pgadmin.org/docs/pgadmin4/latest/) instance at `localhost:8888` to inspect the contents of the test Postgres database as follows: -Create the following extensions in the test database: ``` -CREATE EXTENSION postgis; -CREATE EXTENSION hstore; -CREATE EXTENSION btree_gist; -CREATE EXTENSION citext; +docker run -d --name pgadmin4 -p 8888:80 -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=sequelize_test' dpage/pgadmin4 ``` -#### 3.b Docker +#### 3.2. Without Docker -Make sure `docker` and `docker-compose` are installed. +You will have to manually install and configure each of database engines you want. Check the `dev/dialect-name` folder within this repository and look carefully at how it is defined via Docker and via the auxiliary bash script, and mimic that exactly (except for the database name, username, password, host and port, that you can customize via the `SEQ_DB`, `SEQ_USER`, `SEQ_PW`, `SEQ_HOST` and `SEQ_PORT` environment variables, respectively). -If running on macOS, install [Docker for Mac](https://docs.docker.com/docker-for-mac/). +### 4. Running tests -Now launch the docker mysql and postgres servers with this command (you can add `-d` to run them in daemon mode): +Before starting any work, try to run the tests locally in order to be sure your setup is fine. Start by running the SQLite tests: -```sh -$ docker-compose up postgres-95 mysql-57 mssql +``` +npm run test-sqlite ``` -**MSSQL:** Please run `npm run setup-mssql` to create the test database. +Then, if you want to run tests for another dialect, assuming you've set it up as written on section 3, run the corresponding command: -**POSTGRES:** Sequelize uses [special](https://github.com/sushantdhiman/sequelize-postgres) Docker image for PostgreSQL, which install all the extensions required by tests. +- `npm run test-mysql` +- `npm run test-mariadb` +- `npm run test-postgres` +- `npm run test-mssql` -### 4. Running tests +There are also the `test-unit-*` and `test-integration-*` sets of npm scripts (for example, `test-integration-postgres`). -All tests are located in the `test` folder (which contains the -lovely [Mocha](https://mochajs.org/) tests). +#### 4.1. Running only some tests -```sh -$ npm run test-all || test-mysql || test-sqlite || test-mssql || test-postgres || test-postgres-native +While you're developing, you may want to execute only a single test (or a few), instead of executing everything (which takes some time). You can easily achieve this by modifying the `.mocharc.jsonc` file (but don't commit those changes!) to use `spec` (and maybe `grep`) from Mocha to specify the desired tests. Then, simply call `DIALECT=some-dialect npx mocha` from your terminal (example: `DIALECT=postgres npx mocha`). -$ # alternatively you can pass database credentials with $variables when testing -$ DIALECT=dialect SEQ_DB=database SEQ_USER=user SEQ_PW=password npm test +Hint: if you're creating a new test, you can execute only that test locally against all dialects by adapting the `spec` and `grep` options on `.mocharc.jsonc` and running the following from your terminal (assuming you already set up the database instances via the corresponding `npm run setup-*` calls, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended)): + +``` +DIALECT=mariadb npx mocha && DIALECT=mysql npx mocha && DIALECT=postgres npx mocha && DIALECT=sqlite npx mocha && DIALECT=mssql npx mocha ``` -For docker users you can use these commands instead +### 5. Running an SSCCE -```sh -$ DIALECT=mysql npm run test-docker # Or DIALECT=postgres for Postgres SQL +What is SSCCE? [find out here](http://www.sscce.org/). -# Only integration tests -$ DIALECT=mysql npm run test-docker-integration -``` +You can modify the `sscce.js` file (at the root of the repository) to create an SSCCE. + +Run it for the dialect of your choice using one of the following commands: + +- `npm run sscce-mariadb` +- `npm run sscce-mysql` +- `npm run sscce-postgres` +- `npm run sscce-sqlite` +- `npm run sscce-mssql` + +_Note:_ First, you need to set up (once) the database instance for corresponding dialect, as explained on [Section 3a](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md#3a-with-docker-recommended). + +#### 5.1. Debugging an SSCCE with Visual Studio Code -### 5. Commit +If you open the `package.json` file with Visual Studio Code, you will find a small `debug` button rendered right above the `"scripts": {` line. By clicking it, a popup will appear where you can choose which npm script you want to debug. Select one of the `sscce-*` scripts (listed above) and VSCode will immediately launch your SSCCE in debug mode (meaning that it will stop on any breakpoints that you place within `sscce.js` or any other Sequelize source code). + +### 6. Commit your modifications + +Sequelize follows the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.em2hiij8p46d). The allowed categories are `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test` and `meta`. -Sequelize follows the [AngularJS Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.em2hiij8p46d). Example: - feat(pencil): add 'graphiteWidth' option +``` +feat(pencil): add `graphiteWidth` option +``` -Commit messages are used to automatically generate a changelog. They will be validated automatically using [commitlint](https://github.com/marionebl/commitlint) +Commit messages are used to automatically generate a changelog and calculate the next version number according to [semver](https://semver.org/). They will be validated automatically using [commitlint](https://github.com/marionebl/commitlint). Then push and send your pull request. Happy hacking and thank you for contributing. # Coding guidelines -Have a look at our [.eslintrc.json](https://github.com/sequelize/sequelize/blob/master/.eslintrc.json) file for the specifics. As part of the test process, all files will be linted, and your PR will **not** be accepted if it does not pass linting. +Have a look at our [.eslintrc.json](https://github.com/sequelize/sequelize/blob/main/.eslintrc.json) file for the specifics. As part of the test process, all files will be linted, and your PR will **not** be accepted if it does not pass linting. # Contributing to the documentation -For contribution guidelines for the documentation, see [CONTRIBUTING.DOCS.md](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.DOCS.md). - -# Publishing a release (For Maintainers) - -1. Ensure that latest build on master is green -2. Ensure your local code is up to date (`git pull origin master`) -3. `npm version patch|minor|major` (see [Semantic Versioning](http://semver.org)) -4. Update changelog to match version number, commit changelog -5. `git push --tags origin master` -6. `npm publish .` -7. Copy changelog for version to release notes for version on github +For contribution guidelines for the documentation, see [CONTRIBUTING.DOCS.md](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.DOCS.md). diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4543daff8c4b..000000000000 --- a/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM node:6 - -RUN apt-get install libpq-dev - -WORKDIR /sequelize -VOLUME /sequelize - -COPY . /sequelize diff --git a/ENGINE.md b/ENGINE.md new file mode 100644 index 000000000000..456f69ef7cc1 --- /dev/null +++ b/ENGINE.md @@ -0,0 +1,11 @@ +# Database Engine Support + +## v6 + +| Engine | Minimum supported version | +| :------------------: | :---------------------------------------------------------------------------------------: | +| PostgreSQL | [9.5.0](https://www.postgresql.org/docs/9.5/index.html) | +| MySQL | [5.7.0](https://dev.mysql.com/doc/refman/5.7/en/) | +| MariaDB | [10.1.44](https://mariadb.com/kb/en/changes-improvements-in-mariadb-101/) | +| Microsoft SQL Server | [SQL Server 2014 Express](https://www.microsoft.com/en-US/download/details.aspx?id=42299) | +| SQLite | [3.8.0](https://www.sqlite.org/version3.html) | diff --git a/README.md b/README.md index 3f79f023ce6c..3878c9b9e336 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,69 @@ # Sequelize [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) -[![Travis Build Status](https://badgen.net/travis/sequelize/sequelize?icon=travis)](https://travis-ci.org/sequelize/sequelize) -[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/9l1ypgwsp5ij46m3/branch/master?svg=true)](https://ci.appveyor.com/project/sushantdhiman/sequelize/branch/master) +[![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) -[![Last commit](https://badgen.net/github/last-commit/sequelize/sequelize)](https://github.com/sequelize/sequelize) +[![sponsor](https://img.shields.io/opencollective/all/sequelize?label=sponsors)](https://opencollective.com/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) -[![GitHub stars](https://badgen.net/github/stars/sequelize/sequelize)](https://github.com/sequelize/sequelize) -[![Slack Status](https://sequelize-slack.herokuapp.com/badge.svg)](http://sequelize-slack.herokuapp.com/) -[![node](https://badgen.net/npm/node/sequelize)](https://www.npmjs.com/package/sequelize) -[![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/master/LICENSE) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. +Sequelize is a promise-based [Node.js](https://nodejs.org/en/about/) [ORM tool](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. -Sequelize follows [SEMVER](http://semver.org). Supports Node v6 and above to use ES6 features. +Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 and above. -New to Sequelize? Take a look at the [Tutorials and Guides](http://docs.sequelizejs.com/). You might also be interested in the [API Reference](https://docs.sequelizejs.com/identifiers). +New to Sequelize? Take a look at the [Tutorials and Guides](https://sequelize.org/master). You might also be interested in the [API Reference](https://sequelize.org/master/identifiers). -## v6 Release +Would you like to contribute? Read [our contribution guidelines](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) to know more. There are many ways to help. -You can find the upgrade guide and changelog [here](http://docs.sequelizejs.com/manual/upgrade-to-v6.html). +### v6 Release -## Table of Contents -- [Installation](#installation) -- [Documentation](#documentation) -- [Responsible disclosure](#responsible-disclosure) -- [Resources](#resources) +You can find the detailed changelog [here](https://github.com/sequelize/sequelize/blob/main/docs/manual/other-topics/upgrade-to-v6.md). + +## Supporting the project + +Do you like Sequelize and would like to give back to the engineering team behind it? + +We have recently created an [OpenCollective based money pool](https://opencollective.com/sequelize) which is shared amongst all core maintainers based on their contributions. Every support is wholeheartedly welcome. ❤️ ## Installation -```bash -$ npm install --save sequelize # This will install v6 +```sh +$ npm i sequelize # This will install v6 # And one of the following: -$ npm install --save pg pg-hstore # Postgres -$ npm install --save mysql2 -$ npm install --save mariadb -$ npm install --save sqlite3 -$ npm install --save tedious # Microsoft SQL Server +$ npm i pg pg-hstore # Postgres +$ npm i mysql2 +$ npm i mariadb +$ npm i sqlite3 +$ npm i tedious # Microsoft SQL Server ``` ## Documentation -- [v6 Documentation](http://docs.sequelizejs.com) -- [v5 Documentation](https://sequelize.org/master) -- [v4 Documentation](https://sequelize.org/v4) -- [v3 Documentation](https://sequelize.org/v3) -- [Contributing](https://github.com/sequelize/sequelize/blob/master/CONTRIBUTING.md) + +- [v6 Documentation](https://sequelize.org/master) +- [v5/v4/v3 Documentation](https://sequelize.org) +- [Contributing](https://github.com/sequelize/sequelize/blob/main/CONTRIBUTING.md) ## Responsible disclosure -If you have security issues to report please refer to our [Responsible Disclosure Policy](./SECURITY.md) for more details. + +If you have security issues to report, please refer to our [Responsible Disclosure Policy](https://github.com/sequelize/sequelize/blob/main/SECURITY.md) for more details. ## Resources + - [Changelog](https://github.com/sequelize/sequelize/releases) -- [Slack](http://sequelize-slack.herokuapp.com/) +- [Slack Inviter](http://sequelize-slack.herokuapp.com/) - [Stack Overflow](https://stackoverflow.com/questions/tagged/sequelize.js) ### Tools -- [Sequelize CLI](https://github.com/sequelize/cli) -- [Sequelize & TypeScript](https://sequelize.org/master/manual/typescript.html) -- [Enhanced TypeScript with decorators](https://github.com/RobinBuschmann/sequelize-typescript) -- [Sequelize & GraphQL](https://github.com/mickhansen/graphql-sequelize) -- [Add-ons & Plugins](https://sequelize.org/master/manual/resources.html) -- [Sequelize & CockroachDB](https://github.com/cockroachdb/sequelize-cockroachdb) -### Learning -- [Getting Started](https://sequelize.org/master/manual/getting-started) -- [Express Example](https://github.com/sequelize/express-example) +- [CLI](https://github.com/sequelize/cli) +- [With TypeScript](https://sequelize.org/master/manual/typescript.html) +- [Enhanced TypeScript with decorators](https://github.com/RobinBuschmann/sequelize-typescript) +- [For GraphQL](https://github.com/mickhansen/graphql-sequelize) +- [For CockroachDB](https://github.com/cockroachdb/sequelize-cockroachdb) +- [Plugins](https://sequelize.org/master/manual/resources.html) ### Translations -- [English v3/v4/v5](https://sequelize.org) (OFFICIAL) -- [中文文档 v4/v5](https://github.com/demopark/sequelize-docs-Zh-CN) (UNOFFICIAL) + +- [English](https://sequelize.org/master) (OFFICIAL) +- [中文文档](https://github.com/demopark/sequelize-docs-Zh-CN) (UNOFFICIAL) diff --git a/SECURITY.md b/SECURITY.md index 0db78fa2ee8e..5260ac0b1702 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,9 +6,8 @@ The following table describes the versions of this project that are currently su | Version | Supported | | ------- | ------------------ | -| 3.x | :heavy_check_mark: | -| 4.x | :heavy_check_mark: | -| 5.x | :heavy_check_mark: | +| 6.x | :heavy_check_mark: | +| 5.x | :heavy_check_mark: | ## Responsible disclosure policy diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 091be3f53b1f..000000000000 --- a/appveyor.yml +++ /dev/null @@ -1,44 +0,0 @@ -version: '{build}' - -platform: - - x64 - -services: - - mssql2017 - -shallow_clone: true - -environment: - matrix: - - { NODE_VERSION: 8, DIALECT: mssql, COVERAGE: true } - -install: - - ps: Install-Product node $env:NODE_VERSION x64 - - ps: | - $pkg = ConvertFrom-Json (Get-Content -Raw package.json) - $pkg.devDependencies.PSObject.Properties.Remove('sqlite3') - $pkg.devDependencies.PSObject.Properties.Remove('pg-native') - ConvertTo-Json $pkg | Out-File package.json -Encoding UTF8 - - npm install - -build: off - -before_test: - - ps: . .\scripts\appveyor-setup.ps1 - -test_script: - - 'IF "%COVERAGE%" == "true" (npm run cover) ELSE (npm test)' - -after_test: - - ps: | - $env:PATH = 'C:\Program Files\Git\usr\bin;' + $env:PATH - if (Test-Path env:\COVERAGE) { - Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh - bash codecov.sh -f "coverage\lcov.info" - } - -branches: - only: - - master - - v6 - - /^greenkeeper/.*$/ diff --git a/dev/mariadb/10.3/check.js b/dev/mariadb/10.3/check.js new file mode 100644 index 000000000000..c364203fd9eb --- /dev/null +++ b/dev/mariadb/10.3/check.js @@ -0,0 +1,8 @@ +'use strict'; + +const sequelize = require('../../../test/support').createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); +})(); diff --git a/dev/mariadb/10.3/docker-compose.yml b/dev/mariadb/10.3/docker-compose.yml new file mode 100644 index 000000000000..94ee2c7293b8 --- /dev/null +++ b/dev/mariadb/10.3/docker-compose.yml @@ -0,0 +1,20 @@ +services: + mariadb-103: + container_name: sequelize-mariadb-103 + image: mariadb:10.3 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 21103:3306 + healthcheck: + test: ["CMD", "mysqladmin", "-usequelize_test", "-psequelize_test", "status"] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mariadb-103-network diff --git a/dev/mariadb/10.3/start.sh b/dev/mariadb/10.3/start.sh new file mode 100755 index 000000000000..30af181cb667 --- /dev/null +++ b/dev/mariadb/10.3/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mariadb-103 down --remove-orphans +docker-compose -p sequelize-mariadb-103 up -d + +./../../wait-until-healthy.sh sequelize-mariadb-103 + +docker exec sequelize-mariadb-103 \ + mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +DIALECT=mariadb node check.js + +echo "Local MariaDB-10.3 instance is ready for Sequelize tests." diff --git a/dev/mariadb/10.3/stop.sh b/dev/mariadb/10.3/stop.sh new file mode 100755 index 000000000000..e2629c115979 --- /dev/null +++ b/dev/mariadb/10.3/stop.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mariadb-103 down --remove-orphans + +echo "Local MariaDB-10.3 instance stopped (if it was running)." diff --git a/dev/mssql/2019/check.js b/dev/mssql/2019/check.js new file mode 100644 index 000000000000..c364203fd9eb --- /dev/null +++ b/dev/mssql/2019/check.js @@ -0,0 +1,8 @@ +'use strict'; + +const sequelize = require('../../../test/support').createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); +})(); diff --git a/dev/mssql/2019/docker-compose.yml b/dev/mssql/2019/docker-compose.yml new file mode 100644 index 000000000000..888932806e8e --- /dev/null +++ b/dev/mssql/2019/docker-compose.yml @@ -0,0 +1,18 @@ +services: + mssql-2019: + container_name: sequelize-mssql-2019 + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + ACCEPT_EULA: Y + SA_PASSWORD: Password12! + ports: + - 22019:1433 + healthcheck: + test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "SA", "-P", "Password12!", "-l", "30", "-Q", "SELECT 1"] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mssql-2019-network diff --git a/dev/mssql/2019/start.sh b/dev/mssql/2019/start.sh new file mode 100755 index 000000000000..9fe3c2b48997 --- /dev/null +++ b/dev/mssql/2019/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mssql-2019 down --remove-orphans +docker-compose -p sequelize-mssql-2019 up -d + +./../../wait-until-healthy.sh sequelize-mssql-2019 + +docker exec sequelize-mssql-2019 \ + /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Password12!" -Q "CREATE DATABASE sequelize_test; ALTER DATABASE sequelize_test SET READ_COMMITTED_SNAPSHOT ON;" + +node check.js + +echo "Local MSSQL-2019 instance is ready for Sequelize tests." diff --git a/dev/mssql/2019/stop.sh b/dev/mssql/2019/stop.sh new file mode 100755 index 000000000000..0c8d73b3fee1 --- /dev/null +++ b/dev/mssql/2019/stop.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mssql-2019 down --remove-orphans + +echo "Local MSSQL-2019 instance stopped (if it was running)." diff --git a/dev/mysql/5.7/check.js b/dev/mysql/5.7/check.js new file mode 100644 index 000000000000..c364203fd9eb --- /dev/null +++ b/dev/mysql/5.7/check.js @@ -0,0 +1,8 @@ +'use strict'; + +const sequelize = require('../../../test/support').createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); +})(); diff --git a/dev/mysql/5.7/docker-compose.yml b/dev/mysql/5.7/docker-compose.yml new file mode 100644 index 000000000000..9409f78c2fa5 --- /dev/null +++ b/dev/mysql/5.7/docker-compose.yml @@ -0,0 +1,21 @@ +services: + mysql-57: + container_name: sequelize-mysql-57 + image: mysql:5.7 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 20057:3306 + # tmpfs: /var/lib/mysql:rw + healthcheck: + test: ["CMD", "mysqladmin", "-usequelize_test", "-psequelize_test", "status"] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mysql-57-network diff --git a/dev/mysql/5.7/start.sh b/dev/mysql/5.7/start.sh new file mode 100755 index 000000000000..fb8b02a8b43d --- /dev/null +++ b/dev/mysql/5.7/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mysql-57 down --remove-orphans +docker-compose -p sequelize-mysql-57 up -d + +./../../wait-until-healthy.sh sequelize-mysql-57 + +docker exec sequelize-mysql-57 \ + mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +node check.js + +echo "Local MySQL-5.7 instance is ready for Sequelize tests." diff --git a/dev/mysql/5.7/stop.sh b/dev/mysql/5.7/stop.sh new file mode 100755 index 000000000000..36e3e076065e --- /dev/null +++ b/dev/mysql/5.7/stop.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mysql-57 down --remove-orphans + +echo "Local MySQL-5.7 instance stopped (if it was running)." diff --git a/dev/mysql/8.0/check.js b/dev/mysql/8.0/check.js new file mode 100644 index 000000000000..c364203fd9eb --- /dev/null +++ b/dev/mysql/8.0/check.js @@ -0,0 +1,8 @@ +'use strict'; + +const sequelize = require('../../../test/support').createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); +})(); diff --git a/dev/mysql/8.0/docker-compose.yml b/dev/mysql/8.0/docker-compose.yml new file mode 100644 index 000000000000..fce29b8c9886 --- /dev/null +++ b/dev/mysql/8.0/docker-compose.yml @@ -0,0 +1,21 @@ +services: + mysql-80: + container_name: sequelize-mysql-80 + image: mysql:8.0 + environment: + MYSQL_DATABASE: sequelize_test + MYSQL_USER: sequelize_test + MYSQL_PASSWORD: sequelize_test + MYSQL_ROOT_PASSWORD: sequelize_test + ports: + - 20057:3306 + # tmpfs: /var/lib/mysql:rw + healthcheck: + test: ["CMD", "mysqladmin", "-usequelize_test", "-psequelize_test", "status"] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-mysql-80-network diff --git a/dev/mysql/8.0/start.sh b/dev/mysql/8.0/start.sh new file mode 100755 index 000000000000..d3b0aaab912c --- /dev/null +++ b/dev/mysql/8.0/start.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mysql-80 down --remove-orphans +docker-compose -p sequelize-mysql-80 up -d + +./../../wait-until-healthy.sh sequelize-mysql-80 + +docker exec sequelize-mysql-80 \ + mysql --host 127.0.0.1 --port 3306 -uroot -psequelize_test -e "GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option; FLUSH PRIVILEGES;" + +node check.js + +echo "Local MySQL-8.0 instance is ready for Sequelize tests." diff --git a/dev/mysql/8.0/stop.sh b/dev/mysql/8.0/stop.sh new file mode 100755 index 000000000000..a5a6492ca0f7 --- /dev/null +++ b/dev/mysql/8.0/stop.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-mysql-80 down --remove-orphans + +echo "Local MySQL-8.0 instance stopped (if it was running)." diff --git a/dev/postgres/10/check.js b/dev/postgres/10/check.js new file mode 100644 index 000000000000..c364203fd9eb --- /dev/null +++ b/dev/postgres/10/check.js @@ -0,0 +1,8 @@ +'use strict'; + +const sequelize = require('../../../test/support').createSequelizeInstance(); + +(async () => { + await sequelize.authenticate(); + await sequelize.close(); +})(); diff --git a/dev/postgres/10/docker-compose.yml b/dev/postgres/10/docker-compose.yml new file mode 100644 index 000000000000..50dfbc65196f --- /dev/null +++ b/dev/postgres/10/docker-compose.yml @@ -0,0 +1,19 @@ +services: + postgres-10: + container_name: sequelize-postgres-10 + image: sushantdhiman/postgres:10 + environment: + POSTGRES_USER: sequelize_test + POSTGRES_PASSWORD: sequelize_test + POSTGRES_DB: sequelize_test + ports: + - 23010:5432 + healthcheck: + test: ["CMD", "pg_isready", "-U", "sequelize_test"] + interval: 3s + timeout: 1s + retries: 10 + +networks: + default: + name: sequelize-postgres-10-network diff --git a/dev/postgres/10/start.sh b/dev/postgres/10/start.sh new file mode 100755 index 000000000000..6a2ea51738e9 --- /dev/null +++ b/dev/postgres/10/start.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-postgres-10 down --remove-orphans +docker-compose -p sequelize-postgres-10 up -d + +./../../wait-until-healthy.sh sequelize-postgres-10 + +# docker exec sequelize-postgres-10 \ +# bash -c "export PGPASSWORD=sequelize_test && psql -h localhost -p 5432 -U sequelize_test sequelize_test -c '\l'" + +echo "Local Postgres-10 instance is ready for Sequelize tests." diff --git a/dev/postgres/10/stop.sh b/dev/postgres/10/stop.sh new file mode 100755 index 000000000000..907d2074513b --- /dev/null +++ b/dev/postgres/10/stop.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -Eeuxo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ +cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" # https://stackoverflow.com/a/17744637 + + +docker-compose -p sequelize-postgres-10 down --remove-orphans + +echo "Local Postgres-10 instance stopped (if it was running)." diff --git a/dev/sscce-helpers.d.ts b/dev/sscce-helpers.d.ts new file mode 100644 index 000000000000..74df2a20717a --- /dev/null +++ b/dev/sscce-helpers.d.ts @@ -0,0 +1,3 @@ +import { Sequelize, Options } from '..'; + +export declare function createSequelizeInstance(options?: Options): Sequelize; diff --git a/dev/sscce-helpers.js b/dev/sscce-helpers.js new file mode 100644 index 000000000000..c15a099b1936 --- /dev/null +++ b/dev/sscce-helpers.js @@ -0,0 +1,13 @@ +'use strict'; + +const Support = require('../test/support'); + +module.exports = { + createSequelizeInstance(options = {}) { + return Support.createSequelizeInstance({ + logging: console.log, + logQueryParameters: true, + ...options + }); + } +}; diff --git a/dev/wait-until-healthy.sh b/dev/wait-until-healthy.sh new file mode 100755 index 000000000000..80815069bb1a --- /dev/null +++ b/dev/wait-until-healthy.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +if [ "$#" -ne 1 ]; then + >&2 echo "Please provide the container name or hash" + exit 1 +fi + +for _ in {1..50} +do + state=$(docker inspect -f '{{ .State.Health.Status }}' $1 2>&1) + return_code=$? + if [ ${return_code} -eq 0 ] && [ "$state" == "healthy" ]; then + echo "$1 is healthy!" + exit 0 + fi + sleep 0.4 +done + +>&2 echo "Timeout of 20s exceeded when waiting for container to be healthy: $1" +exit 1 diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 8e04c61616f8..000000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,73 +0,0 @@ -version: '2' - -services: - sequelize: - build: . - links: - - mysql-57 - - postgres-95 - volumes: - - .:/sequelize - environment: - SEQ_DB: sequelize_test - SEQ_USER: sequelize_test - SEQ_PW: sequelize_test - - # PostgreSQL - postgres-95: - image: sushantdhiman/postgres:9.5 - environment: - POSTGRES_USER: sequelize_test - POSTGRES_PASSWORD: sequelize_test - POSTGRES_DB: sequelize_test - ports: - - "8990:5432" - container_name: postgres-95 - - postgres-10: - image: sushantdhiman/postgres:10 - environment: - POSTGRES_USER: sequelize_test - POSTGRES_PASSWORD: sequelize_test - POSTGRES_DB: sequelize_test - ports: - - "8991:5432" - container_name: postgres-10 - - # MariaDB - mariadb-103: - image: mariadb:10.3 - environment: - MYSQL_ROOT_PASSWORD: lollerskates - MYSQL_DATABASE: sequelize_test - MYSQL_USER: sequelize_test - MYSQL_PASSWORD: sequelize_test - volumes: - - $MARIADB_ENTRYPOINT:/docker-entrypoint-initdb.d - ports: - - "8960:3306" - container_name: mariadb-103 - - # MySQL - mysql-57: - image: mysql:5.7 - environment: - MYSQL_ROOT_PASSWORD: lollerskates - MYSQL_DATABASE: sequelize_test - MYSQL_USER: sequelize_test - MYSQL_PASSWORD: sequelize_test - volumes: - - $MYSQLDB_ENTRYPOINT:/docker-entrypoint-initdb.d - ports: - - "8980:3306" - container_name: mysql-57 - - # MSSQL - mssql: - image: microsoft/mssql-server-linux:latest - environment: - ACCEPT_EULA: "Y" - SA_PASSWORD: yourStrong(!)Password - ports: - - "8970:1433" - container_name: mssql diff --git a/docs/css/style.css b/docs/css/style.css index 635ef42161b4..c99916d7bb00 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -21,10 +21,126 @@ div.sequelize { max-width: 300px; } -.navigation { - margin-top: 40px !important; +.layout-container { + display: flex; + flex-wrap: wrap; + height: 100vh; + overflow: hidden; +} + +.layout-container .navigation { + position: initial; + margin: 0; + padding: 0 0.25em; + flex-grow: 1; + flex-shrink: 1; + max-width: 18em; + height: calc(100% - 4.6em - 40px); + overflow: auto; +} + +.layout-container header { + position: initial; + flex-basis: 100%; + display: flex; +} + +.layout-container header .search-box { + position: initial; + flex-grow: 1; + flex-shrink: 1; + text-align: right; + order: 1; + margin-top: 0.75em; + padding-bottom: 0; +} + +.search-box>span { + display:block; + width: 100%; +} + +.search-box.active .search-input { + width: calc(100% - 29px); + max-width: 300px; +} + +.search-result { + right: 0; +} + +.content { + position: initial; + margin: 0; + flex-grow: 1; + flex-basis: 50%; + height: calc(100% - 4.6em - 40px); + overflow: auto; padding-top: 0; - height: calc(100% - 40px); +} + +.navigation .hamburger { + display: none; + background-color: #eee; + width: 2.3em; + border: none; + padding: 0.25em; + cursor: pointer; + margin: 0.5em 0.25em; +} + +.navigation .hamburger .line { + display: block; + width: 100%; + height: 0.25em; + background-color: #666; + margin: 0.3em 0; + pointer-events: none; +} + +.footer { + flex-basis: 100%; + margin-top: 1em; + padding: 1em 0; + height: 1.6em; +} + +code { + overflow: auto; +} + +@media only screen and (max-width: 660px) { + .layout-container .navigation { + width: auto; + height: auto; + max-width: 100%; + position: absolute; + background-color: #fff; + top: 40px; + z-index: 1; + box-shadow: 1px 2px 4px #aaa; + } + + .layout-container .navigation.open { + height: calc(100% - 40px); + } + + .layout-container .navigation .hamburger { + display: inline-block; + } + + .layout-container .navigation>div { + display: none; + } + + .layout-container .navigation.open>div { + display: block; + } + + .footer { + margin-left: 0; + margin-right: 0; + } } .manual-toc a:hover { @@ -38,7 +154,12 @@ div.sequelize { font-size: 17px; } +.no-mouse { + pointer-events: none; +} + .api-reference-link { + white-space: nowrap; font-weight: bold; padding: 0 20px; } diff --git a/docs/esdoc-config.js b/docs/esdoc-config.js index 10d40c65aa90..3e2f3436a948 100644 --- a/docs/esdoc-config.js +++ b/docs/esdoc-config.js @@ -1,21 +1,20 @@ 'use strict'; -const _ = require('lodash'); +const { getDeclaredManuals, checkManuals } = require('./manual-utils'); -const manualGroups = require('./manual-groups.json'); - -const manual = { - index: './docs/index.md', - globalIndex: true, - asset: './docs/images', - files: _.flatten(_.values(manualGroups)).map(file => `./docs/manual/${file}`) -}; +checkManuals(); module.exports = { source: './lib', destination: './esdoc', includes: ['\\.js$'], plugins: [ + { + name: 'esdoc-ecmascript-proposal-plugin', + option: { + all: true + } + }, { name: 'esdoc-inject-style-plugin', option: { @@ -45,7 +44,12 @@ module.exports = { repository: 'https://github.com/sequelize/sequelize', site: 'https://sequelize.org/master/' }, - manual + manual: { + index: './docs/index.md', + globalIndex: true, + asset: './docs/images', + files: getDeclaredManuals() + } } } ] diff --git a/docs/images/logo-simple.svg b/docs/images/logo-simple.svg new file mode 100644 index 000000000000..f8599b6c50f0 --- /dev/null +++ b/docs/images/logo-simple.svg @@ -0,0 +1 @@ +Sequelize diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100644 index 000000000000..0ee676a33cc8 --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1,41 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/slack.svg b/docs/images/slack.svg index e2f9f365c27f..c37dc5eb49e3 100644 --- a/docs/images/slack.svg +++ b/docs/images/slack.svg @@ -1,21 +1,33 @@ - - - - Group - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.md b/docs/index.md index 15d093bd625d..c49d1a0fc882 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,25 +6,22 @@ [![npm version](https://badgen.net/npm/v/sequelize)](https://www.npmjs.com/package/sequelize) -[![Travis Build Status](https://badgen.net/travis/sequelize/sequelize?icon=travis)](https://travis-ci.org/sequelize/sequelize) -[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/9l1ypgwsp5ij46m3/branch/master?svg=true)](https://ci.appveyor.com/project/sushantdhiman/sequelize/branch/master) +[![Build Status](https://github.com/sequelize/sequelize/workflows/CI/badge.svg)](https://github.com/sequelize/sequelize/actions?query=workflow%3ACI) [![npm downloads](https://badgen.net/npm/dm/sequelize)](https://www.npmjs.com/package/sequelize) -[![codecov](https://badgen.net/codecov/c/github/sequelize/sequelize?icon=codecov)](https://codecov.io/gh/sequelize/sequelize) +[![sponsor](https://img.shields.io/opencollective/all/sequelize?label=sponsors)](https://opencollective.com/sequelize) [![Last commit](https://badgen.net/github/last-commit/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![Merged PRs](https://badgen.net/github/merged-prs/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![GitHub stars](https://badgen.net/github/stars/sequelize/sequelize)](https://github.com/sequelize/sequelize) [![Slack Status](http://sequelize-slack.herokuapp.com/badge.svg)](http://sequelize-slack.herokuapp.com/) [![node](https://badgen.net/npm/node/sequelize)](https://www.npmjs.com/package/sequelize) -[![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/master/LICENSE) +[![License](https://badgen.net/github/license/sequelize/sequelize)](https://github.com/sequelize/sequelize/blob/main/LICENSE) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) -Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. +Sequelize is a promise-based [Node.js](https://nodejs.org/en/about/) [ORM tool](https://en.wikipedia.org/wiki/Object-relational_mapping) for [Postgres](https://en.wikipedia.org/wiki/PostgreSQL), [MySQL](https://en.wikipedia.org/wiki/MySQL), [MariaDB](https://en.wikipedia.org/wiki/MariaDB), [SQLite](https://en.wikipedia.org/wiki/SQLite) and [Microsoft SQL Server](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). It features solid transaction support, relations, eager and lazy loading, read replication and more. -Sequelize follows [SEMVER](http://semver.org). Supports Node v8 and above to use ES2018 features. +Sequelize follows [Semantic Versioning](http://semver.org) and supports Node v10 and above. -**Sequelize v5** was released on March 13, 2019. [Official TypeScript typings are now included](manual/typescript). - -You are currently looking at the **Tutorials and Guides** for Sequelize. You might also be interested in the [API Reference](identifiers). +You are currently looking at the **Tutorials and Guides** for Sequelize. You might also be interested in the [API Reference](identifiers.html). ## Quick example @@ -38,14 +35,20 @@ User.init({ birthday: DataTypes.DATE }, { sequelize, modelName: 'user' }); -sequelize.sync() - .then(() => User.create({ +(async () => { + await sequelize.sync(); + const jane = await User.create({ username: 'janedoe', birthday: new Date(1980, 6, 20) - })) - .then(jane => { - console.log(jane.toJSON()); }); + console.log(jane.toJSON()); +})(); ``` -To learn more about how to use Sequelize, read the tutorials available in the left menu. Begin with [Getting Started](manual/getting-started). +To learn more about how to use Sequelize, read the tutorials available in the left menu. Begin with [Getting Started](manual/getting-started.html). + +## Supporting the project + +Do you like Sequelize and would like to give back to the engineering team behind it? + +We have recently created an [OpenCollective based money pool](https://opencollective.com/sequelize) which is shared amongst all core maintainers based on their contributions. Every support is wholeheartedly welcome. ❤️ diff --git a/docs/manual-groups.json b/docs/manual-groups.json index d1b024341771..91bf48cc2753 100644 --- a/docs/manual-groups.json +++ b/docs/manual-groups.json @@ -1,26 +1,51 @@ { "Core Concepts": [ - "getting-started.md", - "dialects.md", - "data-types.md", - "models-definition.md", - "models-usage.md", - "hooks.md", - "querying.md", - "instances.md", - "associations.md", - "raw-queries.md" + "core-concepts/getting-started.md", + "core-concepts/model-basics.md", + "core-concepts/model-instances.md", + "core-concepts/model-querying-basics.md", + "core-concepts/model-querying-finders.md", + "core-concepts/getters-setters-virtuals.md", + "core-concepts/validations-and-constraints.md", + "core-concepts/raw-queries.md", + "core-concepts/assocs.md", + "core-concepts/paranoid.md" + ], + "Advanced Association Concepts": [ + "advanced-association-concepts/eager-loading.md", + "advanced-association-concepts/creating-with-associations.md", + "advanced-association-concepts/advanced-many-to-many.md", + "advanced-association-concepts/association-scopes.md", + "advanced-association-concepts/polymorphic-associations.md" ], "Other Topics": [ - "transactions.md", - "scopes.md", - "read-replication.md", - "migrations.md", - "resources.md", - "typescript.md", - "upgrade-to-v5.md", - "legacy.md", - "whos-using.md", - "legal.md" + "other-topics/dialect-specific-things.md", + "other-topics/transactions.md", + "other-topics/hooks.md", + "other-topics/query-interface.md", + "other-topics/naming-strategies.md", + "other-topics/scopes.md", + "other-topics/sub-queries.md", + "other-topics/other-data-types.md", + "other-topics/constraints-and-circularities.md", + "other-topics/extending-data-types.md", + "other-topics/indexes.md", + "other-topics/optimistic-locking.md", + "other-topics/read-replication.md", + "other-topics/connection-pool.md", + "other-topics/legacy.md", + "other-topics/migrations.md", + "other-topics/typescript.md", + "other-topics/resources.md", + "other-topics/upgrade-to-v6.md", + "other-topics/whos-using.md", + "other-topics/legal.md" + ], + "__hidden__": [ + "moved/associations.md", + "moved/data-types.md", + "moved/models-definition.md", + "moved/models-usage.md", + "moved/querying.md" ] -} \ No newline at end of file +} diff --git a/docs/manual-utils.js b/docs/manual-utils.js new file mode 100644 index 000000000000..a09ce52d216a --- /dev/null +++ b/docs/manual-utils.js @@ -0,0 +1,37 @@ +'use strict'; + +const _ = require('lodash'); +const jetpack = require('fs-jetpack'); +const { normalize } = require('path'); +const assert = require('assert'); + +function getDeclaredManuals() { + const declaredManualGroups = require('./manual-groups.json'); + return _.flatten(Object.values(declaredManualGroups)).map(file => { + return normalize(`./docs/manual/${file}`); + }); +} + +function getAllManuals() { + return jetpack.find('./docs/manual/', { matching: '*.md' }).map(m => { + return normalize(`./${m}`); + }); +} + +function checkManuals() { + // First we check that declared manuals and all manuals are the same + const declared = getDeclaredManuals().sort(); + const all = getAllManuals().sort(); + assert.deepStrictEqual(declared, all); + + // Then we check that every manual begins with a single `#`. This is + // important for ESDoc to render the left menu correctly. + for (const manualRelativePath of all) { + assert( + /^#[^#]/.test(jetpack.read(manualRelativePath)), + `Manual '${manualRelativePath}' must begin with a single '#'` + ); + } +} + +module.exports = { getDeclaredManuals, getAllManuals, checkManuals }; diff --git a/docs/manual/advanced-association-concepts/advanced-many-to-many.md b/docs/manual/advanced-association-concepts/advanced-many-to-many.md new file mode 100644 index 000000000000..371f66201f8c --- /dev/null +++ b/docs/manual/advanced-association-concepts/advanced-many-to-many.md @@ -0,0 +1,667 @@ +# Advanced M:N Associations + +Make sure you have read the [associations guide](assocs.html) before reading this guide. + +Let's start with an example of a Many-to-Many relationship between `User` and `Profile`. + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + points: DataTypes.INTEGER +}, { timestamps: false }); +const Profile = sequelize.define('profile', { + name: DataTypes.STRING +}, { timestamps: false }); +``` + +The simplest way to define the Many-to-Many relationship is: + +```js +User.belongsToMany(Profile, { through: 'User_Profiles' }); +Profile.belongsToMany(User, { through: 'User_Profiles' }); +``` + +By passing a string to `through` above, we are asking Sequelize to automatically generate a model named `User_Profiles` as the *through table* (also known as junction table), with only two columns: `userId` and `profileId`. A composite unique key will be established on these two columns. + +We can also define ourselves a model to be used as the through table. + +```js +const User_Profile = sequelize.define('User_Profile', {}, { timestamps: false }); +User.belongsToMany(Profile, { through: User_Profile }); +Profile.belongsToMany(User, { through: User_Profile }); +``` + +The above has the exact same effect. Note that we didn't define any attributes on the `User_Profile` model. The fact that we passed it into a `belongsToMany` call tells sequelize to create the two attributes `userId` and `profileId` automatically, just like other associations also cause Sequelize to automatically add a column to one of the involved models. + +However, defining the model by ourselves has several advantages. We can, for example, define more columns on our through table: + +```js +const User_Profile = sequelize.define('User_Profile', { + selfGranted: DataTypes.BOOLEAN +}, { timestamps: false }); +User.belongsToMany(Profile, { through: User_Profile }); +Profile.belongsToMany(User, { through: User_Profile }); +``` + +With this, we can now track an extra information at the through table, namely the `selfGranted` boolean. For example, when calling the `user.addProfile()` we can pass values for the extra columns using the `through` option. + +Example: + +```js +const amidala = await User.create({ username: 'p4dm3', points: 1000 }); +const queen = await Profile.create({ name: 'Queen' }); +await amidala.addProfile(queen, { through: { selfGranted: false } }); +const result = await User.findOne({ + where: { username: 'p4dm3' }, + include: Profile +}); +console.log(result); +``` + +Output: + +```json +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen", + "User_Profile": { + "userId": 4, + "profileId": 6, + "selfGranted": false + } + } + ] +} +``` + +You can create all relationship in single `create` call too. + +Example: + +```js +const amidala = await User.create({ + username: 'p4dm3', + points: 1000, + profiles: [{ + name: 'Queen', + User_Profile: { + selfGranted: true + } + }] +}, { + include: Profile +}); + +const result = await User.findOne({ + where: { username: 'p4dm3' }, + include: Profile +}); + +console.log(result); +``` + +Output: + +```json +{ + "id": 1, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 1, + "name": "Queen", + "User_Profile": { + "selfGranted": true, + "userId": 1, + "profileId": 1 + } + } + ] +} +``` + +You probably noticed that the `User_Profiles` table does not have an `id` field. As mentioned above, it has a composite unique key instead. The name of this composite unique key is chosen automatically by Sequelize but can be customized with the `uniqueKey` option: + +```js +User.belongsToMany(Profile, { through: User_Profiles, uniqueKey: 'my_custom_unique' }); +``` + +Another possibility, if desired, is to force the through table to have a primary key just like other standard tables. To do this, simply define the primary key in the model: + +```js +const User_Profile = sequelize.define('User_Profile', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + selfGranted: DataTypes.BOOLEAN +}, { timestamps: false }); +User.belongsToMany(Profile, { through: User_Profile }); +Profile.belongsToMany(User, { through: User_Profile }); +``` + +The above will still create two columns `userId` and `profileId`, of course, but instead of setting up a composite unique key on them, the model will use its `id` column as primary key. Everything else will still work just fine. + +## Through tables versus normal tables and the "Super Many-to-Many association" + +Now we will compare the usage of the last Many-to-Many setup shown above with the usual One-to-Many relationships, so that in the end we conclude with the concept of a *"Super Many-to-Many relationship"*. + +### Models recap (with minor rename) + +To make things easier to follow, let's rename our `User_Profile` model to `grant`. Note that everything works in the same way as before. Our models are: + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + points: DataTypes.INTEGER +}, { timestamps: false }); + +const Profile = sequelize.define('profile', { + name: DataTypes.STRING +}, { timestamps: false }); + +const Grant = sequelize.define('grant', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + selfGranted: DataTypes.BOOLEAN +}, { timestamps: false }); +``` + +We established a Many-to-Many relationship between `User` and `Profile` using the `Grant` model as the through table: + +```js +User.belongsToMany(Profile, { through: Grant }); +Profile.belongsToMany(User, { through: Grant }); +``` + +This automatically added the columns `userId` and `profileId` to the `Grant` model. + +**Note:** As shown above, we have chosen to force the `grant` model to have a single primary key (called `id`, as usual). This is necessary for the *Super Many-to-Many relationship* that will be defined soon. + +### Using One-to-Many relationships instead + +Instead of setting up the Many-to-Many relationship defined above, what if we did the following instead? + +```js +// Setup a One-to-Many relationship between User and Grant +User.hasMany(Grant); +Grant.belongsTo(User); + +// Also setup a One-to-Many relationship between Profile and Grant +Profile.hasMany(Grant); +Grant.belongsTo(Profile); +``` + +The result is essentially the same! This is because `User.hasMany(Grant)` and `Profile.hasMany(Grant)` will automatically add the `userId` and `profileId` columns to `Grant`, respectively. + +This shows that one Many-to-Many relationship isn't very different from two One-to-Many relationships. The tables in the database look the same. + +The only difference is when you try to perform an eager load with Sequelize. + +```js +// With the Many-to-Many approach, you can do: +User.findAll({ include: Profile }); +Profile.findAll({ include: User }); +// However, you can't do: +User.findAll({ include: Grant }); +Profile.findAll({ include: Grant }); +Grant.findAll({ include: User }); +Grant.findAll({ include: Profile }); + +// On the other hand, with the double One-to-Many approach, you can do: +User.findAll({ include: Grant }); +Profile.findAll({ include: Grant }); +Grant.findAll({ include: User }); +Grant.findAll({ include: Profile }); +// However, you can't do: +User.findAll({ include: Profile }); +Profile.findAll({ include: User }); +// Although you can emulate those with nested includes, as follows: +User.findAll({ + include: { + model: Grant, + include: Profile + } +}); // This emulates the `User.findAll({ include: Profile })`, however + // the resulting object structure is a bit different. The original + // structure has the form `user.profiles[].grant`, while the emulated + // structure has the form `user.grants[].profiles[]`. +``` + +### The best of both worlds: the Super Many-to-Many relationship + +We can simply combine both approaches shown above! + +```js +// The Super Many-to-Many relationship +User.belongsToMany(Profile, { through: Grant }); +Profile.belongsToMany(User, { through: Grant }); +User.hasMany(Grant); +Grant.belongsTo(User); +Profile.hasMany(Grant); +Grant.belongsTo(Profile); +``` + +This way, we can do all kinds of eager loading: + +```js +// All these work: +User.findAll({ include: Profile }); +Profile.findAll({ include: User }); +User.findAll({ include: Grant }); +Profile.findAll({ include: Grant }); +Grant.findAll({ include: User }); +Grant.findAll({ include: Profile }); +``` + +We can even perform all kinds of deeply nested includes: + +```js +User.findAll({ + include: [ + { + model: Grant, + include: [User, Profile] + }, + { + model: Profile, + include: { + model: User, + include: { + model: Grant, + include: [User, Profile] + } + } + } + ] +}); +``` + +## Aliases and custom key names + +Similarly to the other relationships, aliases can be defined for Many-to-Many relationships. + +Before proceeding, please recall [the aliasing example for `belongsTo`](assocs.html#defining-an-alias) on the [associations guide](assocs.html). Note that, in that case, defining an association impacts both the way includes are done (i.e. passing the association name) and the name Sequelize chooses for the foreign key (in that example, `leaderId` was created on the `Ship` model). + +Defining an alias for a `belongsToMany` association also impacts the way includes are performed: + +```js +Product.belongsToMany(Category, { as: 'groups', through: 'product_categories' }); +Category.belongsToMany(Product, { as: 'items', through: 'product_categories' }); + +// [...] + +await Product.findAll({ include: Category }); // This doesn't work + +await Product.findAll({ // This works, passing the alias + include: { + model: Category, + as: 'groups' + } +}); + +await Product.findAll({ include: 'groups' }); // This also works +``` + +However, defining an alias here has nothing to do with the foreign key names. The names of both foreign keys created in the through table are still constructed by Sequelize based on the name of the models being associated. This can readily be seen by inspecting the generated SQL for the through table in the example above: + +```sql +CREATE TABLE IF NOT EXISTS `product_categories` ( + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL, + `productId` INTEGER NOT NULL REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + `categoryId` INTEGER NOT NULL REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (`productId`, `categoryId`) +); +``` + +We can see that the foreign keys are `productId` and `categoryId`. To change these names, Sequelize accepts the options `foreignKey` and `otherKey` respectively (i.e., the `foreignKey` defines the key for the source model in the through relation, and `otherKey` defines it for the target model): + +```js +Product.belongsToMany(Category, { + through: 'product_categories', + foreignKey: 'objectId', // replaces `productId` + otherKey: 'typeId' // replaces `categoryId` +}); +Category.belongsToMany(Product, { + through: 'product_categories', + foreignKey: 'typeId', // replaces `categoryId` + otherKey: 'objectId' // replaces `productId` +}); +``` + +Generated SQL: + +```sql +CREATE TABLE IF NOT EXISTS `product_categories` ( + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL, + `objectId` INTEGER NOT NULL REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + `typeId` INTEGER NOT NULL REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY (`objectId`, `typeId`) +); +``` + +As shown above, when you define a Many-to-Many relationship with two `belongsToMany` calls (which is the standard way), you should provide the `foreignKey` and `otherKey` options appropriately in both calls. If you pass these options in only one of the calls, the Sequelize behavior will be unreliable. + +## Self-references + +Sequelize supports self-referential Many-to-Many relationships, intuitively: + +```js +Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) +// This will create the table PersonChildren which stores the ids of the objects. +``` + +## Specifying attributes from the through table + +By default, when eager loading a many-to-many relationship, Sequelize will return data in the following structure (based on the first example in this guide): + +```json +// User.findOne({ include: Profile }) +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen", + "grant": { + "userId": 4, + "profileId": 6, + "selfGranted": false + } + } + ] +} +``` + +Notice that the outer object is an `User`, which has a field called `profiles`, which is a `Profile` array, such that each `Profile` comes with an extra field called `grant` which is a `Grant` instance. This is the default structure created by Sequelize when eager loading from a Many-to-Many relationship. + +However, if you want only some of the attributes of the through table, you can provide an array with the attributes you want in the `attributes` option. For example, if you only want the `selfGranted` attribute from the through table: + +```js +User.findOne({ + include: { + model: Profile, + through: { + attributes: ['selfGranted'] + } + } +}); +``` + +Output: + +```json +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen", + "grant": { + "selfGranted": false + } + } + ] +} +``` + +If you don't want the nested `grant` field at all, use `attributes: []`: + +```js +User.findOne({ + include: { + model: Profile, + through: { + attributes: [] + } + } +}); +``` + +Output: + +```json +{ + "id": 4, + "username": "p4dm3", + "points": 1000, + "profiles": [ + { + "id": 6, + "name": "queen" + } + ] +} +``` + +If you are using mixins (such as `user.getProfiles()`) instead of finder methods (such as `User.findAll()`), you have to use the `joinTableAttributes` option instead: + +```js +someUser.getProfiles({ joinTableAttributes: ['selfGranted'] }); +``` + +Output: + +```json +[ + { + "id": 6, + "name": "queen", + "grant": { + "selfGranted": false + } + } +] +``` + +## Many-to-many-to-many relationships and beyond + +Consider you are trying to model a game championship. There are players and teams. Teams play games. However, players can change teams in the middle of the championship (but not in the middle of a game). So, given one specific game, there are certain teams participating in that game, and each of these teams has a set of players (for that game). + +So we start by defining the three relevant models: + +```js +const Player = sequelize.define('Player', { username: DataTypes.STRING }); +const Team = sequelize.define('Team', { name: DataTypes.STRING }); +const Game = sequelize.define('Game', { name: DataTypes.INTEGER }); +``` + +Now, the question is: how to associate them? + +First, we note that: + +* One game has many teams associated to it (the ones that are playing that game); +* One team may have participated in many games. + +The above observations show that we need a Many-to-Many relationship between Game and Team. Let's use the Super Many-to-Many relationship as explained earlier in this guide: + +```js +// Super Many-to-Many relationship between Game and Team +const GameTeam = sequelize.define('GameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Team.belongsToMany(Game, { through: GameTeam }); +Game.belongsToMany(Team, { through: GameTeam }); +GameTeam.belongsTo(Game); +GameTeam.belongsTo(Team); +Game.hasMany(GameTeam); +Team.hasMany(GameTeam); +``` + +The part about players is trickier. We note that the set of players that form a team depends not only on the team (obviously), but also on which game is being considered. Therefore, we don't want a Many-to-Many relationship between Player and Team. We also don't want a Many-to-Many relationship between Player and Game. Instead of associating a Player to any of those models, what we need is an association between a Player and something like a *"team-game pair constraint"*, since it is the pair (team plus game) that defines which players belong there. So what we are looking for turns out to be precisely the junction model, GameTeam, itself! And, we note that, since a given *game-team pair* specifies many players, and on the other hand that the same player can participate of many *game-team pairs*, we need a Many-to-Many relationship between Player and GameTeam! + +To provide the greatest flexibility, let's use the Super Many-to-Many relationship construction here again: + +```js +// Super Many-to-Many relationship between Player and GameTeam +const PlayerGameTeam = sequelize.define('PlayerGameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Player.belongsToMany(GameTeam, { through: PlayerGameTeam }); +GameTeam.belongsToMany(Player, { through: PlayerGameTeam }); +PlayerGameTeam.belongsTo(Player); +PlayerGameTeam.belongsTo(GameTeam); +Player.hasMany(PlayerGameTeam); +GameTeam.hasMany(PlayerGameTeam); +``` + +The above associations achieve precisely what we want. Here is a full runnable example of this: + +```js +const { Sequelize, Op, Model, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:', { + define: { timestamps: false } // Just for less clutter in this example +}); +const Player = sequelize.define('Player', { username: DataTypes.STRING }); +const Team = sequelize.define('Team', { name: DataTypes.STRING }); +const Game = sequelize.define('Game', { name: DataTypes.INTEGER }); + +// We apply a Super Many-to-Many relationship between Game and Team +const GameTeam = sequelize.define('GameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Team.belongsToMany(Game, { through: GameTeam }); +Game.belongsToMany(Team, { through: GameTeam }); +GameTeam.belongsTo(Game); +GameTeam.belongsTo(Team); +Game.hasMany(GameTeam); +Team.hasMany(GameTeam); + +// We apply a Super Many-to-Many relationship between Player and GameTeam +const PlayerGameTeam = sequelize.define('PlayerGameTeam', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + } +}); +Player.belongsToMany(GameTeam, { through: PlayerGameTeam }); +GameTeam.belongsToMany(Player, { through: PlayerGameTeam }); +PlayerGameTeam.belongsTo(Player); +PlayerGameTeam.belongsTo(GameTeam); +Player.hasMany(PlayerGameTeam); +GameTeam.hasMany(PlayerGameTeam); + +(async () => { + + await sequelize.sync(); + await Player.bulkCreate([ + { username: 's0me0ne' }, + { username: 'empty' }, + { username: 'greenhead' }, + { username: 'not_spock' }, + { username: 'bowl_of_petunias' } + ]); + await Game.bulkCreate([ + { name: 'The Big Clash' }, + { name: 'Winter Showdown' }, + { name: 'Summer Beatdown' } + ]); + await Team.bulkCreate([ + { name: 'The Martians' }, + { name: 'The Earthlings' }, + { name: 'The Plutonians' } + ]); + + // Let's start defining which teams were in which games. This can be done + // in several ways, such as calling `.setTeams` on each game. However, for + // brevity, we will use direct `create` calls instead, referring directly + // to the IDs we want. We know that IDs are given in order starting from 1. + await GameTeam.bulkCreate([ + { GameId: 1, TeamId: 1 }, // this GameTeam will get id 1 + { GameId: 1, TeamId: 2 }, // this GameTeam will get id 2 + { GameId: 2, TeamId: 1 }, // this GameTeam will get id 3 + { GameId: 2, TeamId: 3 }, // this GameTeam will get id 4 + { GameId: 3, TeamId: 2 }, // this GameTeam will get id 5 + { GameId: 3, TeamId: 3 } // this GameTeam will get id 6 + ]); + + // Now let's specify players. + // For brevity, let's do it only for the second game (Winter Showdown). + // Let's say that that s0me0ne and greenhead played for The Martians, while + // not_spock and bowl_of_petunias played for The Plutonians: + await PlayerGameTeam.bulkCreate([ + // In 'Winter Showdown' (i.e. GameTeamIds 3 and 4): + { PlayerId: 1, GameTeamId: 3 }, // s0me0ne played for The Martians + { PlayerId: 3, GameTeamId: 3 }, // greenhead played for The Martians + { PlayerId: 4, GameTeamId: 4 }, // not_spock played for The Plutonians + { PlayerId: 5, GameTeamId: 4 } // bowl_of_petunias played for The Plutonians + ]); + + // Now we can make queries! + const game = await Game.findOne({ + where: { + name: "Winter Showdown" + }, + include: { + model: GameTeam, + include: [ + { + model: Player, + through: { attributes: [] } // Hide unwanted `PlayerGameTeam` nested object from results + }, + Team + ] + } + }); + + console.log(`Found game: "${game.name}"`); + for (let i = 0; i < game.GameTeams.length; i++) { + const team = game.GameTeams[i].Team; + const players = game.GameTeams[i].Players; + console.log(`- Team "${team.name}" played game "${game.name}" with the following players:`); + console.log(players.map(p => `--- ${p.username}`).join('\n')); + } + +})(); +``` + +Output: + +```text +Found game: "Winter Showdown" +- Team "The Martians" played game "Winter Showdown" with the following players: +--- s0me0ne +--- greenhead +- Team "The Plutonians" played game "Winter Showdown" with the following players: +--- not_spock +--- bowl_of_petunias +``` + +So this is how we can achieve a *many-to-many-to-many* relationship between three models in Sequelize, by taking advantage of the Super Many-to-Many relationship technique! + +This idea can be applied recursively for even more complex, *many-to-many-to-...-to-many* relationships (although at some point queries might become slow). diff --git a/docs/manual/advanced-association-concepts/association-scopes.md b/docs/manual/advanced-association-concepts/association-scopes.md new file mode 100644 index 000000000000..42224f5e0cf6 --- /dev/null +++ b/docs/manual/advanced-association-concepts/association-scopes.md @@ -0,0 +1,64 @@ +# Association Scopes + +This section concerns association scopes, which are similar but not the same as [model scopes](scopes.html). + +Association scopes can be placed both on the associated model (the target of the association) and on the through table for Many-to-Many relationships. + +## Concept + +Similarly to how a [model scope](scopes.html) is automatically applied on the model static calls, such as `Model.scope('foo').findAll()`, an association scope is a rule (more precisely, a set of default attributes and options) that is automatically applied on instance calls from the model. Here, *instance calls* mean method calls that are called from an instance (rather than from the Model itself). Mixins are the main example of instance methods (`instance.getSomething`, `instance.setSomething`, `instance.addSomething` and `instance.createSomething`). + +Association scopes behave just like model scopes, in the sense that both cause an automatic application of things like `where` clauses to finder calls; the difference being that instead of applying to static finder calls (which is the case for model scopes), the association scopes automatically apply to instance finder calls (such as mixins). + +## Example + +A basic example of an association scope for the One-to-Many association between models `Foo` and `Bar` is shown below. + +* Setup: + + ```js + const Foo = sequelize.define('foo', { name: DataTypes.STRING }); + const Bar = sequelize.define('bar', { status: DataTypes.STRING }); + Foo.hasMany(Bar, { + scope: { + status: 'open' + }, + as: 'openBars' + }); + await sequelize.sync(); + const myFoo = await Foo.create({ name: "My Foo" }); + ``` + +* After this setup, calling `myFoo.getOpenBars()` generates the following SQL: + + ```sql + SELECT + `id`, `status`, `createdAt`, `updatedAt`, `fooId` + FROM `bars` AS `bar` + WHERE `bar`.`status` = 'open' AND `bar`.`fooId` = 1; + ``` + +With this we can see that upon calling the `.getOpenBars()` mixin, the association scope `{ status: 'open' }` was automatically applied into the `WHERE` clause of the generated SQL. + +## Achieving the same behavior with standard scopes + +We could have achieved the same behavior with standard scopes: + +```js +// Foo.hasMany(Bar, { +// scope: { +// status: 'open' +// }, +// as: 'openBars' +// }); + +Bar.addScope('open', { + where: { + status: 'open' + } +}); +Foo.hasMany(Bar); +Foo.hasMany(Bar.scope('open'), { as: 'openBars' }); +``` + +With the above code, `myFoo.getOpenBars()` yields the same SQL shown above. \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/creating-with-associations.md b/docs/manual/advanced-association-concepts/creating-with-associations.md new file mode 100644 index 000000000000..60f5e80ea71d --- /dev/null +++ b/docs/manual/advanced-association-concepts/creating-with-associations.md @@ -0,0 +1,130 @@ +# Creating with Associations + +An instance can be created with nested association in one step, provided all elements are new. + +In contrast, performing updates and deletions involving nested objects is currently not possible. For that, you will have to perform each separate action explicitly. + +## BelongsTo / HasMany / HasOne association + +Consider the following models: + +```js +class Product extends Model {} +Product.init({ + title: Sequelize.STRING +}, { sequelize, modelName: 'product' }); +class User extends Model {} +User.init({ + firstName: Sequelize.STRING, + lastName: Sequelize.STRING +}, { sequelize, modelName: 'user' }); +class Address extends Model {} +Address.init({ + type: DataTypes.STRING, + line1: Sequelize.STRING, + line2: Sequelize.STRING, + city: Sequelize.STRING, + state: Sequelize.STRING, + zip: Sequelize.STRING, +}, { sequelize, modelName: 'address' }); + +// We save the return values of the association setup calls to use them later +Product.User = Product.belongsTo(User); +User.Addresses = User.hasMany(Address); +// Also works for `hasOne` +``` + +A new `Product`, `User`, and one or more `Address` can be created in one step in the following way: + +```js +return Product.create({ + title: 'Chair', + user: { + firstName: 'Mick', + lastName: 'Broadstone', + addresses: [{ + type: 'home', + line1: '100 Main St.', + city: 'Austin', + state: 'TX', + zip: '78704' + }] + } +}, { + include: [{ + association: Product.User, + include: [ User.Addresses ] + }] +}); +``` + +Observe the usage of the `include` option in the `Product.create` call. That is necessary for Sequelize to understand what you are trying to create along with the association. + +Note: here, our user model is called `user`, with a lowercase `u` - This means that the property in the object should also be `user`. If the name given to `sequelize.define` was `User`, the key in the object should also be `User`. Likewise for `addresses`, except it's pluralized being a `hasMany` association. + +## BelongsTo association with an alias + +The previous example can be extended to support an association alias. + +```js +const Creator = Product.belongsTo(User, { as: 'creator' }); + +return Product.create({ + title: 'Chair', + creator: { + firstName: 'Matt', + lastName: 'Hansen' + } +}, { + include: [ Creator ] +}); +``` + +## HasMany / BelongsToMany association + +Let's introduce the ability to associate a product with many tags. Setting up the models could look like: + +```js +class Tag extends Model {} +Tag.init({ + name: Sequelize.STRING +}, { sequelize, modelName: 'tag' }); + +Product.hasMany(Tag); +// Also works for `belongsToMany`. +``` + +Now we can create a product with multiple tags in the following way: + +```js +Product.create({ + id: 1, + title: 'Chair', + tags: [ + { name: 'Alpha'}, + { name: 'Beta'} + ] +}, { + include: [ Tag ] +}) +``` + +And, we can modify this example to support an alias as well: + +```js +const Categories = Product.hasMany(Tag, { as: 'categories' }); + +Product.create({ + id: 1, + title: 'Chair', + categories: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] +}, { + include: [{ + association: Categories, + as: 'categories' + }] +}) +``` \ No newline at end of file diff --git a/docs/manual/advanced-association-concepts/eager-loading.md b/docs/manual/advanced-association-concepts/eager-loading.md new file mode 100644 index 000000000000..70370ce1536d --- /dev/null +++ b/docs/manual/advanced-association-concepts/eager-loading.md @@ -0,0 +1,666 @@ +# Eager Loading + +As briefly mentioned in [the associations guide](assocs.html), eager Loading is the act of querying data of several models at once (one 'main' model and one or more associated models). At the SQL level, this is a query with one or more [joins](https://en.wikipedia.org/wiki/Join_\(SQL\)). + +When this is done, the associated models will be added by Sequelize in appropriately named, automatically created field(s) in the returned objects. + +In Sequelize, eager loading is mainly done by using the `include` option on a model finder query (such as `findOne`, `findAll`, etc). + +## Basic example + +Let's assume the following setup: + +```js +const User = sequelize.define('user', { name: DataTypes.STRING }, { timestamps: false }); +const Task = sequelize.define('task', { name: DataTypes.STRING }, { timestamps: false }); +const Tool = sequelize.define('tool', { + name: DataTypes.STRING, + size: DataTypes.STRING +}, { timestamps: false }); +User.hasMany(Task); +Task.belongsTo(User); +User.hasMany(Tool, { as: 'Instruments' }); +``` + +### Fetching a single associated element + +OK. So, first of all, let's load all tasks with their associated user: + +```js +const tasks = await Task.findAll({ include: User }); +console.log(JSON.stringify(tasks, null, 2)); +``` + +Output: + +```json +[{ + "name": "A Task", + "id": 1, + "userId": 1, + "user": { + "name": "John Doe", + "id": 1 + } +}] +``` + +Here, `tasks[0].user instanceof User` is `true`. This shows that when Sequelize fetches associated models, they are added to the output object as model instances. + +Above, the associated model was added to a new field called `user` in the fetched task. The name of this field was automatically chosen by Sequelize based on the name of the associated model, where its pluralized form is used when applicable (i.e., when the association is `hasMany` or `belongsToMany`). In other words, since `Task.belongsTo(User)`, a task is associated to one user, therefore the logical choice is the singular form (which Sequelize follows automatically). + +### Fetching all associated elements + +Now, instead of loading the user that is associated to a given task, we will do the opposite - we will find all tasks associated to a given user. + +The method call is essentially the same. The only difference is that now the extra field created in the query result uses the pluralized form (`tasks` in this case), and its value is an array of task instances (instead of a single instance, as above). + +```js +const users = await User.findAll({ include: Task }); +console.log(JSON.stringify(users, null, 2)); +``` + +Output: + +```json +[{ + "name": "John Doe", + "id": 1, + "tasks": [{ + "name": "A Task", + "id": 1, + "userId": 1 + }] +}] +``` + +Notice that the accessor (the `tasks` property in the resulting instance) is pluralized since the association is one-to-many. + +### Fetching an Aliased association + +If an association is aliased (using the `as` option), you must specify this alias when including the model. Instead of passing the model directly to the `include` option, you should instead provide an object with two options: `model` and `as`. + +Notice how the user's `Tool`s are aliased as `Instruments` above. In order to get that right you have to specify the model you want to load, as well as the alias: + +```js +const users = await User.findAll({ + include: { model: Tool, as: 'Instruments' } +}); +console.log(JSON.stringify(users, null, 2)); +``` + +Output: + +```json +[{ + "name": "John Doe", + "id": 1, + "Instruments": [{ + "name": "Scissor", + "id": 1, + "userId": 1 + }] +}] +``` + +You can also include by alias name by specifying a string that matches the association alias: + +```js +User.findAll({ include: 'Instruments' }); // Also works +User.findAll({ include: { association: 'Instruments' } }); // Also works +``` + +### Required eager loading + +When eager loading, we can force the query to return only records which have an associated model, effectively converting the query from the default `OUTER JOIN` to an `INNER JOIN`. This is done with the `required: true` option, as follows: + +```js +User.findAll({ + include: { + model: Task, + required: true + } +}); +``` + +This option also works on nested includes. + +### Eager loading filtered at the associated model level + +When eager loading, we can also filter the associated model using the `where` option, as in the following example: + +```js +User.findAll({ + include: { + model: Tool, + as: 'Instruments' + where: { + size: { + [Op.ne]: 'small' + } + } + } +}); +``` + +Generated SQL: + +```sql +SELECT + `user`.`id`, + `user`.`name`, + `Instruments`.`id` AS `Instruments.id`, + `Instruments`.`name` AS `Instruments.name`, + `Instruments`.`size` AS `Instruments.size`, + `Instruments`.`userId` AS `Instruments.userId` +FROM `users` AS `user` +INNER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` AND + `Instruments`.`size` != 'small'; +``` + +Note that the SQL query generated above will only fetch users that have at least one tool that matches the condition (of not being `small`, in this case). This is the case because, when the `where` option is used inside an `include`, Sequelize automatically sets the `required` option to `true`. This means that, instead of an `OUTER JOIN`, an `INNER JOIN` is done, returning only the parent models with at least one matching children. + +Note also that the `where` option used was converted into a condition for the `ON` clause of the `INNER JOIN`. In order to obtain a *top-level* `WHERE` clause, instead of an `ON` clause, something different must be done. This will be shown next. + +#### Referring to other columns + +If you want to apply a `WHERE` clause in an included model referring to a value from an associated model, you can simply use the `Sequelize.col` function, as show in the example below: + +```js +// Find all projects with a least one task where task.state === project.state +Project.findAll({ + include: { + model: Task, + where: { + state: Sequelize.col('project.state') + } + } +}) +``` + +### Complex where clauses at the top-level + +To obtain top-level `WHERE` clauses that involve nested columns, Sequelize provides a way to reference nested columns: the `'$nested.column$'` syntax. + +It can be used, for example, to move the where conditions from an included model from the `ON` condition to a top-level `WHERE` clause. + +```js +User.findAll({ + where: { + '$Instruments.size$': { [Op.ne]: 'small' } + }, + include: [{ + model: Tool, + as: 'Instruments' + }] +}); +``` + +Generated SQL: + +```sql +SELECT + `user`.`id`, + `user`.`name`, + `Instruments`.`id` AS `Instruments.id`, + `Instruments`.`name` AS `Instruments.name`, + `Instruments`.`size` AS `Instruments.size`, + `Instruments`.`userId` AS `Instruments.userId` +FROM `users` AS `user` +LEFT OUTER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` +WHERE `Instruments`.`size` != 'small'; +``` + +The `$nested.column$` syntax also works for columns that are nested several levels deep, such as `$some.super.deeply.nested.column$`. Therefore, you can use this to make complex filters on deeply nested columns. + +For a better understanding of all differences between the inner `where` option (used inside an `include`), with and without the `required` option, and a top-level `where` using the `$nested.column$` syntax, below we have four examples for you: + +```js +// Inner where, with default `required: true` +await User.findAll({ + include: { + model: Tool, + as: 'Instruments', + where: { + size: { [Op.ne]: 'small' } + } + } +}); + +// Inner where, `required: false` +await User.findAll({ + include: { + model: Tool, + as: 'Instruments', + where: { + size: { [Op.ne]: 'small' } + }, + required: false + } +}); + +// Top-level where, with default `required: false` +await User.findAll({ + where: { + '$Instruments.size$': { [Op.ne]: 'small' } + }, + include: { + model: Tool, + as: 'Instruments' + } +}); + +// Top-level where, `required: true` +await User.findAll({ + where: { + '$Instruments.size$': { [Op.ne]: 'small' } + }, + include: { + model: Tool, + as: 'Instruments', + required: true + } +}); +``` + +Generated SQLs, in order: + +```sql +-- Inner where, with default `required: true` +SELECT [...] FROM `users` AS `user` +INNER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` + AND `Instruments`.`size` != 'small'; + +-- Inner where, `required: false` +SELECT [...] FROM `users` AS `user` +LEFT OUTER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` + AND `Instruments`.`size` != 'small'; + +-- Top-level where, with default `required: false` +SELECT [...] FROM `users` AS `user` +LEFT OUTER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` +WHERE `Instruments`.`size` != 'small'; + +-- Top-level where, `required: true` +SELECT [...] FROM `users` AS `user` +INNER JOIN `tools` AS `Instruments` ON + `user`.`id` = `Instruments`.`userId` +WHERE `Instruments`.`size` != 'small'; +``` + +### Fetching with `RIGHT OUTER JOIN` (MySQL, MariaDB, PostgreSQL and MSSQL only) + +By default, associations are loaded using a `LEFT OUTER JOIN` - that is to say it only includes records from the parent table. You can change this behavior to a `RIGHT OUTER JOIN` by passing the `right` option, if the dialect you are using supports it. + +Currenly, SQLite does not support [right joins](https://www.sqlite.org/omitted.html). + +*Note:* `right` is only respected if `required` is false. + +```js +User.findAll({ + include: [{ + model: Task // will create a left join + }] +}); +User.findAll({ + include: [{ + model: Task, + right: true // will create a right join + }] +}); +User.findAll({ + include: [{ + model: Task, + required: true, + right: true // has no effect, will create an inner join + }] +}); +User.findAll({ + include: [{ + model: Task, + where: { name: { [Op.ne]: 'empty trash' } }, + right: true // has no effect, will create an inner join + }] +}); +User.findAll({ + include: [{ + model: Tool, + where: { name: { [Op.ne]: 'empty trash' } }, + required: false // will create a left join + }] +}); +User.findAll({ + include: [{ + model: Tool, + where: { name: { [Op.ne]: 'empty trash' } }, + required: false + right: true // will create a right join + }] +}); +``` + +## Multiple eager loading + +The `include` option can receive an array in order to fetch multiple associated models at once: + +```js +Foo.findAll({ + include: [ + { + model: Bar, + required: true + }, + { + model: Baz, + where: /* ... */ + }, + Qux // Shorthand syntax for { model: Qux } also works here + ] +}) +``` + +## Eager loading with Many-to-Many relationships + +When you perform eager loading on a model with a Belongs-to-Many relationship, Sequelize will fetch the junction table data as well, by default. For example: + +```js +const Foo = sequelize.define('Foo', { name: DataTypes.TEXT }); +const Bar = sequelize.define('Bar', { name: DataTypes.TEXT }); +Foo.belongsToMany(Bar, { through: 'Foo_Bar' }); +Bar.belongsToMany(Foo, { through: 'Foo_Bar' }); + +await sequelize.sync(); +const foo = await Foo.create({ name: 'foo' }); +const bar = await Bar.create({ name: 'bar' }); +await foo.addBar(bar); +const fetchedFoo = await Foo.findOne({ include: Bar }); +console.log(JSON.stringify(fetchedFoo, null, 2)); +``` + +Output: + +```json +{ + "id": 1, + "name": "foo", + "Bars": [ + { + "id": 1, + "name": "bar", + "Foo_Bar": { + "FooId": 1, + "BarId": 1 + } + } + ] +} +``` + +Note that every bar instance eager loaded into the `"Bars"` property has an extra property called `Foo_Bar` which is the relevant Sequelize instance of the junction model. By default, Sequelize fetches all attributes from the junction table in order to build this extra property. + +However, you can specify which attributes you want fetched. This is done with the `attributes` option applied inside the `through` option of the include. For example: + +```js +Foo.findAll({ + include: [{ + model: Bar, + through: { + attributes: [/* list the wanted attributes here */] + } + }] +}); +``` + +If you don't want anything from the junction table, you can explicitly provide an empty array to the `attributes` option inside the `through` option of the `include` option, and in this case nothing will be fetched and the extra property will not even be created: + +```js +Foo.findOne({ + include: { + model: Bar, + through: { + attributes: [] + } + } +}); +``` + +Output: + +```json +{ + "id": 1, + "name": "foo", + "Bars": [ + { + "id": 1, + "name": "bar" + } + ] +} +``` + +Whenever including a model from a Many-to-Many relationship, you can also apply a filter on the junction table. This is done with the `where` option applied inside the `through` option of the include. For example: + +```js +User.findAll({ + include: [{ + model: Project, + through: { + where: { + // Here, `completed` is a column present at the junction table + completed: true + } + } + }] +}); +``` + +Generated SQL (using SQLite): + +```sql +SELECT + `User`.`id`, + `User`.`name`, + `Projects`.`id` AS `Projects.id`, + `Projects`.`name` AS `Projects.name`, + `Projects->User_Project`.`completed` AS `Projects.User_Project.completed`, + `Projects->User_Project`.`UserId` AS `Projects.User_Project.UserId`, + `Projects->User_Project`.`ProjectId` AS `Projects.User_Project.ProjectId` +FROM `Users` AS `User` +LEFT OUTER JOIN `User_Projects` AS `Projects->User_Project` ON + `User`.`id` = `Projects->User_Project`.`UserId` +LEFT OUTER JOIN `Projects` AS `Projects` ON + `Projects`.`id` = `Projects->User_Project`.`ProjectId` AND + `Projects->User_Project`.`completed` = 1; +``` + +## Including everything + +To include all associated models, you can use the `all` and `nested` options: + +```js +// Fetch all models associated with User +User.findAll({ include: { all: true }}); + +// Fetch all models associated with User and their nested associations (recursively) +User.findAll({ include: { all: true, nested: true }}); +``` + +## Including soft deleted records + +In case you want to eager load soft deleted records you can do that by setting `include.paranoid` to `false`: + +```js +User.findAll({ + include: [{ + model: Tool, + as: 'Instruments', + where: { size: { [Op.ne]: 'small' } }, + paranoid: false + }] +}); +``` + +## Ordering eager loaded associations + +When you want to apply `ORDER` clauses to eager loaded models, you must use the top-level `order` option with augmented arrays, starting with the specification of the nested model you want to sort. + +This is better understood with examples. + +```js +Company.findAll({ + include: Division, + order: [ + // We start the order array with the model we want to sort + [Division, 'name', 'ASC'] + ] +}); +Company.findAll({ + include: Division, + order: [ + [Division, 'name', 'DESC'] + ] +}); +Company.findAll({ + // If the include uses an alias... + include: { model: Division, as: 'Div' }, + order: [ + // ...we use the same syntax from the include + // in the beginning of the order array + [{ model: Division, as: 'Div' }, 'name', 'DESC'] + ] +}); + +Company.findAll({ + // If we have includes nested in several levels... + include: { + model: Division, + include: Department + }, + order: [ + // ... we replicate the include chain of interest + // at the beginning of the order array + [Division, Department, 'name', 'DESC'] + ] +}); +``` + +In the case of many-to-many relationships, you are also able to sort by attributes in the through table. For example, assuming we have a Many-to-Many relationship between `Division` and `Department` whose junction model is `DepartmentDivision`, you can do: + +```js +Company.findAll({ + include: { + model: Division, + include: Department + }, + order: [ + [Division, DepartmentDivision, 'name', 'ASC'] + ] +}); +``` + +In all the above examples, you have noticed that the `order` option is used at the top-level. The only situation in which `order` also works inside the include option is when `separate: true` is used. In that case, the usage is as follows: + +```js +// This only works for `separate: true` (which in turn +// only works for has-many relationships). +User.findAll({ + include: { + model: Post, + separate: true, + order: [ + ['createdAt', 'DESC'] + ] + } +}); +``` + +### Complex ordering involving sub-queries + +Take a look at the [guide on sub-queries](sub-queries.html) for an example of how to use a sub-query to assist a more complex ordering. + +## Nested eager loading + +You can use nested eager loading to load all related models of a related model: + +```js +const users = await User.findAll({ + include: { + model: Tool, + as: 'Instruments', + include: { + model: Teacher, + include: [ /* etc */ ] + } + } +}); +console.log(JSON.stringify(users, null, 2)); +``` + +Output: + +```json +[{ + "name": "John Doe", + "id": 1, + "Instruments": [{ // 1:M and N:M association + "name": "Scissor", + "id": 1, + "userId": 1, + "Teacher": { // 1:1 association + "name": "Jimi Hendrix" + } + }] +}] +``` + +This will produce an outer join. However, a `where` clause on a related model will create an inner join and return only the instances that have matching sub-models. To return all parent instances, you should add `required: false`. + +```js +User.findAll({ + include: [{ + model: Tool, + as: 'Instruments', + include: [{ + model: Teacher, + where: { + school: "Woodstock Music School" + }, + required: false + }] + }] +}); +``` + +The query above will return all users, and all their instruments, but only those teachers associated with `Woodstock Music School`. + +## Using `findAndCountAll` with includes + +The `findAndCountAll` utility function supports includes. Only the includes that are marked as `required` will be considered in `count`. For example, if you want to find and count all users who have a profile: + +```js +User.findAndCountAll({ + include: [ + { model: Profile, required: true } + ], + limit: 3 +}); +``` + +Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted. Adding a `where` clause to the include automatically makes it required: + +```js +User.findAndCountAll({ + include: [ + { model: Profile, where: { active: true } } + ], + limit: 3 +}); +``` + +The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. diff --git a/docs/manual/advanced-association-concepts/polymorphic-associations.md b/docs/manual/advanced-association-concepts/polymorphic-associations.md new file mode 100644 index 000000000000..2a1ade8f216b --- /dev/null +++ b/docs/manual/advanced-association-concepts/polymorphic-associations.md @@ -0,0 +1,427 @@ +# Polymorphic Associations + +_**Note:** the usage of polymorphic associations in Sequelize, as outlined in this guide, should be done with caution. Don't just copy-paste code from here, otherwise you might easily make mistakes and introduce bugs in your code. Make sure you understand what is going on._ + +## Concept + +A **polymorphic association** consists on two (or more) associations happening with the same foreign key. + +For example, consider the models `Image`, `Video` and `Comment`. The first two represent something that a user might post. We want to allow comments to be placed in both of them. This way, we immediately think of establishing the following associations: + +* A One-to-Many association between `Image` and `Comment`: + + ```js + Image.hasMany(Comment); + Comment.belongsTo(Image); + ``` + +* A One-to-Many association between `Video` and `Comment`: + + ```js + Video.hasMany(Comment); + Comment.belongsTo(Video); + ``` + +However, the above would cause Sequelize to create two foreign keys on the `Comment` table: `ImageId` and `VideoId`. This is not ideal because this structure makes it look like a comment can be attached at the same time to one image and one video, which isn't true. Instead, what we really want here is precisely a polymorphic association, in which a `Comment` points to a single **Commentable**, an abstract polymorphic entity that represents one of `Image` or `Video`. + +Before proceeding to how to configure such an association, let's see how using it looks like: + +```js +const image = await Image.create({ url: "https://placekitten.com/408/287" }); +const comment = await image.createComment({ content: "Awesome!" }); + +console.log(comment.commentableId === image.id); // true + +// We can also retrieve which type of commentable a comment is associated to. +// The following prints the model name of the associated commentable instance. +console.log(comment.commentableType); // "Image" + +// We can use a polymorphic method to retrieve the associated commentable, without +// having to worry whether it's an Image or a Video. +const associatedCommentable = await comment.getCommentable(); + +// In this example, `associatedCommentable` is the same thing as `image`: +const isDeepEqual = require('deep-equal'); +console.log(isDeepEqual(image, commentable)); // true +``` + +## Configuring a One-to-Many polymorphic association + +To setup the polymorphic association for the example above (which is an example of One-to-Many polymorphic association), we have the following steps: + +* Define a string field called `commentableType` in the `Comment` model; +* Define the `hasMany` and `belongsTo` association between `Image`/`Video` and `Comment`: + * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; + * Specifying the appropriate [association scopes](association-scopes.html); +* To properly support lazy loading, define a new instance method on the `Comment` model called `getCommentable` which calls, under the hood, the correct mixin to fetch the appropriate commentable; +* To properly support eager loading, define an `afterFind` hook on the `Comment` model that automatically populates the `commentable` field in every instance; +* To prevent bugs/mistakes in eager loading, you can also delete the concrete fields `image` and `video` from Comment instances in the same `afterFind` hook, leaving only the abstract `commentable` field available. + +Here is an example: + +```js +// Helper function +const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`; + +class Image extends Model {} +Image.init({ + title: DataTypes.STRING, + url: DataTypes.STRING +}, { sequelize, modelName: 'image' }); + +class Video extends Model {} +Video.init({ + title: DataTypes.STRING, + text: DataTypes.STRING +}, { sequelize, modelName: 'video' }); + +class Comment extends Model { + getCommentable(options) { + if (!this.commentableType) return Promise.resolve(null); + const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`; + return this[mixinMethodName](options); + } +} +Comment.init({ + title: DataTypes.STRING, + commentableId: DataTypes.INTEGER, + commentableType: DataTypes.STRING +}, { sequelize, modelName: 'comment' }); + +Image.hasMany(Comment, { + foreignKey: 'commentableId', + constraints: false, + scope: { + commentableType: 'image' + } +}); +Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false }); + +Video.hasMany(Comment, { + foreignKey: 'commentableId', + constraints: false, + scope: { + commentableType: 'video' + } +}); +Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false }); + +Comment.addHook("afterFind", findResult => { + if (!Array.isArray(findResult)) findResult = [findResult]; + for (const instance of findResult) { + if (instance.commentableType === "image" && instance.image !== undefined) { + instance.commentable = instance.image; + } else if (instance.commentableType === "video" && instance.video !== undefined) { + instance.commentable = instance.video; + } + // To prevent mistakes: + delete instance.image; + delete instance.dataValues.image; + delete instance.video; + delete instance.dataValues.video; + } +}); +``` + +Since the `commentableId` column references several tables (two in this case), we cannot add a `REFERENCES` constraint to it. This is why the `constraints: false` option was used. + +Note that, in the code above: + +* The *Image -> Comment* association defined an association scope: `{ commentableType: 'image' }` +* The *Video -> Comment* association defined an association scope: `{ commentableType: 'video' }` + +These scopes are automatically applied when using the association functions (as explained in the [Association Scopes](association-scopes.html) guide). Some examples are below, with their generated SQL statements: + +* `image.getComments()`: + + ```sql + SELECT "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" + FROM "comments" AS "comment" + WHERE "comment"."commentableType" = 'image' AND "comment"."commentableId" = 1; + ``` + + Here we can see that `` `comment`.`commentableType` = 'image'`` was automatically added to the `WHERE` clause of the generated SQL. This is exactly the behavior we want. + +* `image.createComment({ title: 'Awesome!' })`: + + ```sql + INSERT INTO "comments" ( + "id", "title", "commentableType", "commentableId", "createdAt", "updatedAt" + ) VALUES ( + DEFAULT, 'Awesome!', 'image', 1, + '2018-04-17 05:36:40.454 +00:00', '2018-04-17 05:36:40.454 +00:00' + ) RETURNING *; + ``` + +* `image.addComment(comment)`: + + ```sql + UPDATE "comments" + SET "commentableId"=1, "commentableType"='image', "updatedAt"='2018-04-17 05:38:43.948 +00:00' + WHERE "id" IN (1) + ``` + +### Polymorphic lazy loading + +The `getCommentable` instance method on `Comment` provides an abstraction for lazy loading the associated commentable - working whether the comment belongs to an Image or a Video. + +It works by simply converting the `commentableType` string into a call to the correct mixin (either `getImage` or `getVideo`). + +Note that the `getCommentable` implementation above: + +* Returns `null` when no association is present (which is good); +* Allows you to pass an options object to `getCommentable(options)`, just like any other standard Sequelize method. This is useful to specify where-conditions or includes, for example. + +### Polymorphic eager loading + +Now, we want to perform a polymorphic eager loading of the associated commentables for one (or more) comments. We want to achieve something similar to the following idea: + +```js +const comment = await Comment.findOne({ + include: [ /* What to put here? */ ] +}); +console.log(comment.commentable); // This is our goal +``` + +The solution is to tell Sequelize to include both Images and Videos, so that our `afterFind` hook defined above will do the work, automatically adding the `commentable` field to the instance object, providing the abstraction we want. + +For example: + +```js +const comments = await Comment.findAll({ + include: [Image, Video] +}); +for (const comment of comments) { + const message = `Found comment #${comment.id} with ${comment.commentableType} commentable:`; + console.log(message, comment.commentable.toJSON()); +} +``` + +Output example: + +```text +Found comment #1 with image commentable: { id: 1, + title: 'Meow', + url: 'https://placekitten.com/408/287', + createdAt: 2019-12-26T15:04:53.047Z, + updatedAt: 2019-12-26T15:04:53.047Z } +``` + +### Caution - possibly invalid eager/lazy loading! + +Consider a comment `Foo` whose `commentableId` is 2 and `commentableType` is `image`. Consider also that `Image A` and `Video X` both happen to have an id equal to 2. Conceptually, it is clear that `Video X` is not associated to `Foo`, because even though its id is 2, the `commentableType` of `Foo` is `image`, not `video`. However, this distinction is made by Sequelize only at the level of the abstractions performed by `getCommentable` and the hook we created above. + +This means that if you call `Comment.findAll({ include: Video })` in the situation above, `Video X` will be eager loaded into `Foo`. Thankfully, our `afterFind` hook will delete it automatically, to help prevent bugs, but regardless it is important that you understand what is going on. + +The best way to prevent this kind of mistake is to **avoid using the concrete accessors and mixins directly at all costs** (such as `.image`, `.getVideo()`, `.setImage()`, etc), always preferring the abstractions we created, such as `.getCommentable()` and `.commentable`. If you really need to access eager-loaded `.image` and `.video` for some reason, make sure you wrap that in a type check such as `comment.commentableType === 'image'`. + +## Configuring a Many-to-Many polymorphic association + +In the above example, we had the models `Image` and `Video` being abstractly called *commentables*, with one *commentable* having many comments. However, one given comment would belong to a single *commentable* - this is why the whole situation is a One-to-Many polymorphic association. + +Now, to consider a Many-to-Many polymorphic association, instead of considering comments, we will consider tags. For convenience, instead of calling Image and Video as *commentables*, we will now call them *taggables*. One *taggable* may have several tags, and at the same time one tag can be placed in several *taggables*. + +The setup for this goes as follows: + +* Define the junction model explicitly, specifying the two foreign keys as `tagId` and `taggableId` (this way it is a junction model for a Many-to-Many relationship between `Tag` and the abstract concept of *taggable*); +* Define a string field called `taggableType` in the junction model; +* Define the `belongsToMany` associations between the two models and `Tag`: + * Disabling constraints (i.e. using `{ constraints: false }`), since the same foreign key is referencing multiple tables; + * Specifying the appropriate [association scopes](association-scopes.html); +* Define a new instance method on the `Tag` model called `getTaggables` which calls, under the hood, the correct mixin to fetch the appropriate taggables. + +Implementation: + +```js +class Tag extends Model { + getTaggables(options) { + const images = await this.getImages(options); + const videos = await this.getVideos(options); + // Concat images and videos in a single array of taggables + return images.concat(videos); + } +} +Tag.init({ + name: DataTypes.STRING +}, { sequelize, modelName: 'tag' }); + +// Here we define the junction model explicitly +class Tag_Taggable extends Model {} +Tag_Taggable.init({ + tagId: { + type: DataTypes.INTEGER, + unique: 'tt_unique_constraint' + }, + taggableId: { + type: DataTypes.INTEGER, + unique: 'tt_unique_constraint', + references: null + }, + taggableType: { + type: DataTypes.STRING, + unique: 'tt_unique_constraint' + } +}, { sequelize, modelName: 'tag_taggable' }); + +Image.belongsToMany(Tag, { + through: { + model: Tag_Taggable, + unique: false, + scope: { + taggableType: 'image' + } + }, + foreignKey: 'taggableId', + constraints: false +}); +Tag.belongsToMany(Image, { + through: { + model: Tag_Taggable, + unique: false + }, + foreignKey: 'tagId', + constraints: false +}); + +Video.belongsToMany(Tag, { + through: { + model: Tag_Taggable, + unique: false, + scope: { + taggableType: 'video' + } + }, + foreignKey: 'taggableId', + constraints: false +}); +Tag.belongsToMany(Video, { + through: { + model: Tag_Taggable, + unique: false + }, + foreignKey: 'tagId', + constraints: false +}); +``` + +The `constraints: false` option disables references constraints, as the `taggableId` column references several tables, we cannot add a `REFERENCES` constraint to it. + +Note that: + +* The *Image -> Tag* association defined an association scope: `{ taggableType: 'image' }` +* The *Video -> Tag* association defined an association scope: `{ taggableType: 'video' }` + +These scopes are automatically applied when using the association functions. Some examples are below, with their generated SQL statements: + +* `image.getTags()`: + + ```sql + SELECT + `tag`.`id`, + `tag`.`name`, + `tag`.`createdAt`, + `tag`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` + FROM `tags` AS `tag` + INNER JOIN `tag_taggables` AS `tag_taggable` ON + `tag`.`id` = `tag_taggable`.`tagId` AND + `tag_taggable`.`taggableId` = 1 AND + `tag_taggable`.`taggableType` = 'image'; + ``` + + Here we can see that `` `tag_taggable`.`taggableType` = 'image'`` was automatically added to the `WHERE` clause of the generated SQL. This is exactly the behavior we want. + +* `tag.getTaggables()`: + + ```sql + SELECT + `image`.`id`, + `image`.`url`, + `image`.`createdAt`, + `image`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` + FROM `images` AS `image` + INNER JOIN `tag_taggables` AS `tag_taggable` ON + `image`.`id` = `tag_taggable`.`taggableId` AND + `tag_taggable`.`tagId` = 1; + + SELECT + `video`.`id`, + `video`.`url`, + `video`.`createdAt`, + `video`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` + FROM `videos` AS `video` + INNER JOIN `tag_taggables` AS `tag_taggable` ON + `video`.`id` = `tag_taggable`.`taggableId` AND + `tag_taggable`.`tagId` = 1; + ``` + +Note that the above implementation of `getTaggables()` allows you to pass an options object to `getCommentable(options)`, just like any other standard Sequelize method. This is useful to specify where-conditions or includes, for example. + +### Applying scopes on the target model + +In the example above, the `scope` options (such as `scope: { taggableType: 'image' }`) were applied to the *through* model, not the *target* model, since it was used under the `through` option. + +We can also apply an association scope on the target model. We can even do both at the same time. + +To illustrate this, consider an extension of the above example between tags and taggables, where each tag has a status. This way, to get all pending tags of an image, we could establish another `belognsToMany` relationship between `Image` and `Tag`, this time applying a scope on the through model and another scope on the target model: + +```js +Image.belongsToMany(Tag, { + through: { + model: Tag_Taggable, + unique: false, + scope: { + taggableType: 'image' + } + }, + scope: { + status: 'pending' + }, + as: 'pendingTags', + foreignKey: 'taggableId', + constraints: false +}); +``` + +This way, when calling `image.getPendingTags()`, the following SQL query will be generated: + +```sql +SELECT + `tag`.`id`, + `tag`.`name`, + `tag`.`status`, + `tag`.`createdAt`, + `tag`.`updatedAt`, + `tag_taggable`.`tagId` AS `tag_taggable.tagId`, + `tag_taggable`.`taggableId` AS `tag_taggable.taggableId`, + `tag_taggable`.`taggableType` AS `tag_taggable.taggableType`, + `tag_taggable`.`createdAt` AS `tag_taggable.createdAt`, + `tag_taggable`.`updatedAt` AS `tag_taggable.updatedAt` +FROM `tags` AS `tag` +INNER JOIN `tag_taggables` AS `tag_taggable` ON + `tag`.`id` = `tag_taggable`.`tagId` AND + `tag_taggable`.`taggableId` = 1 AND + `tag_taggable`.`taggableType` = 'image' +WHERE ( + `tag`.`status` = 'pending' +); +``` + +We can see that both scopes were applied automatically: + +* `` `tag_taggable`.`taggableType` = 'image'`` was added automatically to the `INNER JOIN`; +* `` `tag`.`status` = 'pending'`` was added automatically to an outer where clause. diff --git a/docs/manual/associations.md b/docs/manual/associations.md deleted file mode 100644 index f2375c389a0b..000000000000 --- a/docs/manual/associations.md +++ /dev/null @@ -1,1175 +0,0 @@ -# Associations - -This section describes the various association types in sequelize. There are four type of -associations available in Sequelize - -1. BelongsTo -2. HasOne -3. HasMany -4. BelongsToMany - -## Basic Concepts - -### Source & Target - -Let's first begin with a basic concept that you will see used in most associations, **source** and **target** model. Suppose you are trying to add an association between two Models. Here we are adding a `hasOne` association between `User` and `Project`. - -```js -class User extends Model {} -User.init({ - name: Sequelize.STRING, - email: Sequelize.STRING -}, { - sequelize, - modelName: 'user' -}); - -class Project extends Model {} -Project.init({ - name: Sequelize.STRING -}, { - sequelize, - modelName: 'project' -}); - -User.hasOne(Project); -``` - -`User` model (the model that the function is being invoked on) is the __source__. `Project` model (the model being passed as an argument) is the __target__. - -### Foreign Keys - -When you create associations between your models in sequelize, foreign key references with constraints will automatically be created. The setup below: - -```js -class Task extends Model {} -Task.init({ title: Sequelize.STRING }, { sequelize, modelName: 'task' }); -class User extends Model {} -User.init({ username: Sequelize.STRING }, { sequelize, modelName: 'user' }); - -User.hasMany(Task); // Will add userId to Task model -Task.belongsTo(User); // Will also add userId to Task model -``` - -Will generate the following SQL: - -```sql -CREATE TABLE IF NOT EXISTS "users" ( - "id" SERIAL, - "username" VARCHAR(255), - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - PRIMARY KEY ("id") -); - -CREATE TABLE IF NOT EXISTS "tasks" ( - "id" SERIAL, - "title" VARCHAR(255), - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "userId" INTEGER REFERENCES "users" ("id") ON DELETE - SET - NULL ON UPDATE CASCADE, - PRIMARY KEY ("id") -); -``` - -The relation between `tasks` and `users` model injects the `userId` foreign key on `tasks` table, and marks it as a reference to the `users` table. By default `userId` will be set to `NULL` if the referenced user is deleted, and updated if the id of the `userId` updated. These options can be overridden by passing `onUpdate` and `onDelete` options to the association calls. The validation options are `RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL`. - -For 1:1 and 1:m associations the default option is `SET NULL` for deletion, and `CASCADE` for updates. For n:m, the default for both is `CASCADE`. This means, that if you delete or update a row from one side of an n:m association, all the rows in the join table referencing that row will also be deleted or updated. - -#### underscored option - -Sequelize allow setting `underscored` option for Model. When `true` this option will set the -`field` option on all attributes to the underscored version of its name. This also applies to -foreign keys generated by associations. - -Let's modify last example to use `underscored` option. - -```js -class Task extends Model {} -Task.init({ - title: Sequelize.STRING -}, { - underscored: true, - sequelize, - modelName: 'task' -}); - -class User extends Model {} -User.init({ - username: Sequelize.STRING -}, { - underscored: true, - sequelize, - modelName: 'user' -}); - -// Will add userId to Task model, but field will be set to `user_id` -// This means column name will be `user_id` -User.hasMany(Task); - -// Will also add userId to Task model, but field will be set to `user_id` -// This means column name will be `user_id` -Task.belongsTo(User); -``` - -Will generate the following SQL: - -```sql -CREATE TABLE IF NOT EXISTS "users" ( - "id" SERIAL, - "username" VARCHAR(255), - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - PRIMARY KEY ("id") -); - -CREATE TABLE IF NOT EXISTS "tasks" ( - "id" SERIAL, - "title" VARCHAR(255), - "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, - "user_id" INTEGER REFERENCES "users" ("id") ON DELETE - SET - NULL ON UPDATE CASCADE, - PRIMARY KEY ("id") -); -``` - -With the underscored option attributes injected to model are still camel cased but `field` option is set to their underscored version. - -#### Cyclic dependencies & Disabling constraints - -Adding constraints between tables means that tables must be created in the database in a certain order, when using `sequelize.sync`. If `Task` has a reference to `User`, the `users` table must be created before the `tasks` table can be created. This can sometimes lead to circular references, where sequelize cannot find an order in which to sync. Imagine a scenario of documents and versions. A document can have multiple versions, and for convenience, a document has a reference to its current version. - -```js -class Document extends Model {} -Document.init({ - author: Sequelize.STRING -}, { sequelize, modelName: 'document' }); -class Version extends Model {} -Version.init({ - timestamp: Sequelize.DATE -}, { sequelize, modelName: 'version' }); - -Document.hasMany(Version); // This adds documentId attribute to version -Document.belongsTo(Version, { - as: 'Current', - foreignKey: 'currentVersionId' -}); // This adds currentVersionId attribute to document -``` - -However, the code above will result in the following error: `Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents`. - -In order to alleviate that, we can pass `constraints: false` to one of the associations: - -```js -Document.hasMany(Version); -Document.belongsTo(Version, { - as: 'Current', - foreignKey: 'currentVersionId', - constraints: false -}); -``` - -Which will allow us to sync the tables correctly: - -```sql -CREATE TABLE IF NOT EXISTS "documents" ( - "id" SERIAL, - "author" VARCHAR(255), - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "currentVersionId" INTEGER, - PRIMARY KEY ("id") -); - -CREATE TABLE IF NOT EXISTS "versions" ( - "id" SERIAL, - "timestamp" TIMESTAMP WITH TIME ZONE, - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, - "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE - SET - NULL ON UPDATE CASCADE, - PRIMARY KEY ("id") -); -``` - -#### Enforcing a foreign key reference without constraints - -Sometimes you may want to reference another table, without adding any constraints, or associations. In that case you can manually add the reference attributes to your schema definition, and mark the relations between them. - -```js -class Trainer extends Model {} -Trainer.init({ - firstName: Sequelize.STRING, - lastName: Sequelize.STRING -}, { sequelize, modelName: 'trainer' }); - -// Series will have a trainerId = Trainer.id foreign reference key -// after we call Trainer.hasMany(series) -class Series extends Model {} -Series.init({ - title: Sequelize.STRING, - subTitle: Sequelize.STRING, - description: Sequelize.TEXT, - // Set FK relationship (hasMany) with `Trainer` - trainerId: { - type: Sequelize.INTEGER, - references: { - model: Trainer, - key: 'id' - } - } -}, { sequelize, modelName: 'series' }); - -// Video will have seriesId = Series.id foreign reference key -// after we call Series.hasOne(Video) -class Video extends Model {} -Video.init({ - title: Sequelize.STRING, - sequence: Sequelize.INTEGER, - description: Sequelize.TEXT, - // set relationship (hasOne) with `Series` - seriesId: { - type: Sequelize.INTEGER, - references: { - model: Series, // Can be both a string representing the table name or a Sequelize model - key: 'id' - } - } -}, { sequelize, modelName: 'video' }); - -Series.hasOne(Video); -Trainer.hasMany(Series); -``` - -## One-To-One associations - -One-To-One associations are associations between exactly two models connected by a single foreign key. - -### BelongsTo - -BelongsTo associations are associations where the foreign key for the one-to-one relation exists on the **source model**. - -A simple example would be a **Player** being part of a **Team** with the foreign key on the player. - -```js -class Player extends Model {} -Player.init({/* attributes */}, { sequelize, modelName: 'player' }); -class Team extends Model {} -Team.init({/* attributes */}, { sequelize, modelName: 'team' }); - -Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team -``` - -#### Foreign keys - -By default the foreign key for a belongsTo relation will be generated from the target model name and the target primary key name. - -The default casing is `camelCase`. If the source model is configured with `underscored: true` the foreignKey will be created with field `snake_case`. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -// will add companyId to user -User.belongsTo(Company); - -class User extends Model {} -User.init({/* attributes */}, { underscored: true, sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({ - uuid: { - type: Sequelize.UUID, - primaryKey: true - } -}, { sequelize, modelName: 'company' }); - -// will add companyUuid to user with field company_uuid -User.belongsTo(Company); -``` - -In cases where `as` has been defined it will be used in place of the target model name. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class UserRole extends Model {} -UserRole.init({/* attributes */}, { sequelize, modelName: 'userRole' }); - -User.belongsTo(UserRole, {as: 'role'}); // Adds roleId to user rather than userRoleId -``` - -In all cases the default foreign key can be overwritten with the `foreignKey` option. -When the foreign key option is used, Sequelize will use it as-is: - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -User.belongsTo(Company, {foreignKey: 'fk_company'}); // Adds fk_company to User -``` - -#### Target keys - -The target key is the column on the target model that the foreign key column on the source model points to. By default the target key for a belongsTo relation will be the target model's primary key. To define a custom column, use the `targetKey` option. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -User.belongsTo(Company, {foreignKey: 'fk_companyname', targetKey: 'name'}); // Adds fk_companyname to User -``` - -### HasOne - -HasOne associations are associations where the foreign key for the one-to-one relation exists on the **target model**. - -```js -class User extends Model {} -User.init({/* ... */}, { sequelize, modelName: 'user' }) -class Project extends Model {} -Project.init({/* ... */}, { sequelize, modelName: 'project' }) - -// One-way associations -Project.hasOne(User) - -/* - In this example hasOne will add an attribute projectId to the User model! - Furthermore, Project.prototype will gain the methods getUser and setUser according - to the first parameter passed to define. If you have underscore style - enabled, the added attribute will be project_id instead of projectId. - - The foreign key will be placed on the users table. - - You can also define the foreign key, e.g. if you already have an existing - database and want to work on it: -*/ - -Project.hasOne(User, { foreignKey: 'initiator_id' }) - -/* - Because Sequelize will use the model's name (first parameter of define) for - the accessor methods, it is also possible to pass a special option to hasOne: -*/ - -Project.hasOne(User, { as: 'Initiator' }) -// Now you will get Project.getInitiator and Project.setInitiator - -// Or let's define some self references -class Person extends Model {} -Person.init({ /* ... */}, { sequelize, modelName: 'person' }) - -Person.hasOne(Person, {as: 'Father'}) -// this will add the attribute FatherId to Person - -// also possible: -Person.hasOne(Person, {as: 'Father', foreignKey: 'DadId'}) -// this will add the attribute DadId to Person - -// In both cases you will be able to do: -Person.setFather -Person.getFather - -// If you need to join a table twice you can double join the same table -Team.hasOne(Game, {as: 'HomeTeam', foreignKey : 'homeTeamId'}); -Team.hasOne(Game, {as: 'AwayTeam', foreignKey : 'awayTeamId'}); - -Game.belongsTo(Team); -``` - -Even though it is called a HasOne association, for most 1:1 relations you usually want the BelongsTo association since BelongsTo will add the foreignKey on the source where hasOne will add on the target. - -#### Source keys - -The source key is the attribute on the source model that the foreign key attribute on the target model points to. By default the source key for a `hasOne` relation will be the source model's primary attribute. To use a custom attribute, use the `sourceKey` option. - -```js -class User extends Model {} -User.init({/* attributes */}, { sequelize, modelName: 'user' }) -class Company extends Model {} -Company.init({/* attributes */}, { sequelize, modelName: 'company' }); - -// Adds companyName attribute to User -// Use name attribute from Company as source attribute -Company.hasOne(User, {foreignKey: 'companyName', sourceKey: 'name'}); -``` - -### Difference between HasOne and BelongsTo - -In Sequelize 1:1 relationship can be set using HasOne and BelongsTo. They are suitable for different scenarios. Lets study this difference using an example. - -Suppose we have two tables to link **Player** and **Team**. Lets define their models. - -```js -class Player extends Model {} -Player.init({/* attributes */}, { sequelize, modelName: 'player' }) -class Team extends Model {} -Team.init({/* attributes */}, { sequelize, modelName: 'team' }); -``` - -When we link two models in Sequelize we can refer them as pairs of **source** and **target** models. Like this - -Having **Player** as the **source** and **Team** as the **target** - -```js -Player.belongsTo(Team); -//Or -Player.hasOne(Team); -``` - -Having **Team** as the **source** and **Player** as the **target** - -```js -Team.belongsTo(Player); -//Or -Team.hasOne(Player); -``` - -HasOne and BelongsTo insert the association key in different models from each other. HasOne inserts the association key in **target** model whereas BelongsTo inserts the association key in the **source** model. - -Here is an example demonstrating use cases of BelongsTo and HasOne. - -```js -class Player extends Model {} -Player.init({/* attributes */}, { sequelize, modelName: 'player' }) -class Coach extends Model {} -Coach.init({/* attributes */}, { sequelize, modelName: 'coach' }) -class Team extends Model {} -Team.init({/* attributes */}, { sequelize, modelName: 'team' }); -``` - -Suppose our `Player` model has information about its team as `teamId` column. Information about each Team's `Coach` is stored in the `Team` model as `coachId` column. These both scenarios requires different kind of 1:1 relation because foreign key relation is present on different models each time. - -When information about association is present in **source** model we can use `belongsTo`. In this case `Player` is suitable for `belongsTo` because it has `teamId` column. - -```js -Player.belongsTo(Team) // `teamId` will be added on Player / Source model -``` - -When information about association is present in **target** model we can use `hasOne`. In this case `Coach` is suitable for `hasOne` because `Team` model store information about its `Coach` as `coachId` field. - -```js -Coach.hasOne(Team) // `coachId` will be added on Team / Target model -``` - -## One-To-Many associations (hasMany) - -One-To-Many associations are connecting one source with multiple targets. The targets however are again connected to exactly one specific source. - -```js -class User extends Model {} -User.init({/* ... */}, { sequelize, modelName: 'user' }) -class Project extends Model {} -Project.init({/* ... */}, { sequelize, modelName: 'project' }) - -// OK. Now things get more complicated (not really visible to the user :)). -// First let's define a hasMany association -Project.hasMany(User, {as: 'Workers'}) -``` - -This will add the attribute `projectId` to User. Depending on your setting for underscored the column in the table will either be called `projectId` or `project_id`. Instances of Project will get the accessors `getWorkers` and `setWorkers`. - -Sometimes you may need to associate records on different columns, you may use `sourceKey` option: - -```js -class City extends Model {} -City.init({ countryCode: Sequelize.STRING }, { sequelize, modelName: 'city' }); -class Country extends Model {} -Country.init({ isoCode: Sequelize.STRING }, { sequelize, modelName: 'country' }); - -// Here we can connect countries and cities base on country code -Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'}); -City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'}); -``` - -So far we dealt with a one-way association. But we want more! Let's define it the other way around by creating a many to many association in the next section. - -## Belongs-To-Many associations - -Belongs-To-Many associations are used to connect sources with multiple targets. Furthermore the targets can also have connections to multiple sources. - -```js -Project.belongsToMany(User, {through: 'UserProject'}); -User.belongsToMany(Project, {through: 'UserProject'}); -``` - -This will create a new model called UserProject with the equivalent foreign keys `projectId` and `userId`. Whether the attributes are camelcase or not depends on the two models joined by the table (in this case User and Project). - -Defining `through` is **required**. Sequelize would previously attempt to autogenerate names but that would not always lead to the most logical setups. - -This will add methods `getUsers`, `setUsers`, `addUser`,`addUsers` to `Project`, and `getProjects`, `setProjects`, `addProject`, and `addProjects` to `User`. - -Sometimes you may want to rename your models when using them in associations. Let's define users as workers and projects as tasks by using the alias (`as`) option. We will also manually define the foreign keys to use: - -```js -User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' }) -Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' }) -``` - -`foreignKey` will allow you to set **source model** key in the **through** relation. -`otherKey` will allow you to set **target model** key in the **through** relation. - -```js -User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId', otherKey: 'projectId'}) -``` - -Of course you can also define self references with belongsToMany: - -```js -Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' }) -// This will create the table PersonChildren which stores the ids of the objects. - -``` - -#### Source and target keys - -If you want to create a belongs to many relationship that does not use the default primary key some setup work is required. -You must set the `sourceKey` (optionally `targetKey`) appropriately for the two ends of the belongs to many. Further you must also ensure you have appropriate indexes created on your relationships. For example: - -```js -const User = this.sequelize.define('User', { - id: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'user_id' - }, - userSecondId: { - type: DataTypes.UUID, - allowNull: false, - defaultValue: DataTypes.UUIDV4, - field: 'user_second_id' - } -}, { - tableName: 'tbl_user', - indexes: [ - { - unique: true, - fields: ['user_second_id'] - } - ] -}); - -const Group = this.sequelize.define('Group', { - id: { - type: DataTypes.UUID, - allowNull: false, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - field: 'group_id' - }, - groupSecondId: { - type: DataTypes.UUID, - allowNull: false, - defaultValue: DataTypes.UUIDV4, - field: 'group_second_id' - } -}, { - tableName: 'tbl_group', - indexes: [ - { - unique: true, - fields: ['group_second_id'] - } - ] -}); - -User.belongsToMany(Group, { - through: 'usergroups', - sourceKey: 'userSecondId' -}); -Group.belongsToMany(User, { - through: 'usergroups', - sourceKey: 'groupSecondId' -}); -``` - -If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one: - -```js -class User extends Model {} -User.init({}, { sequelize, modelName: 'user' }) -class Project extends Model {} -Project.init({}, { sequelize, modelName: 'project' }) -class UserProjects extends Model {} -UserProjects.init({ - status: DataTypes.STRING -}, { sequelize, modelName: 'userProjects' }) - -User.belongsToMany(Project, { through: UserProjects }) -Project.belongsToMany(User, { through: UserProjects }) -``` - -To add a new project to a user and set its status, you pass extra `options.through` to the setter, which contains the attributes for the join table - -```js -user.addProject(project, { through: { status: 'started' }}) -``` - -By default the code above will add projectId and userId to the UserProjects table, and _remove any previously defined primary key attribute_ - the table will be uniquely identified by the combination of the keys of the two tables, and there is no reason to have other PK columns. To enforce a primary key on the `UserProjects` model you can add it manually. - -```js -class UserProjects extends Model {} -UserProjects.init({ - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true - }, - status: DataTypes.STRING -}, { sequelize, modelName: 'userProjects' }) -``` - -With Belongs-To-Many you can query based on **through** relation and select specific attributes. For example using `findAll` with **through** - -```js -User.findAll({ - include: [{ - model: Project, - through: { - attributes: ['createdAt', 'startedAt', 'finishedAt'], - where: {completed: true} - } - }] -}); -``` - -Belongs-To-Many creates a unique key when primary key is not present on through model. This unique key name can be overridden using **uniqueKey** option. - -```js -Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' }) -``` - -## Naming strategy - -By default sequelize will use the model name (the name passed to `sequelize.define`) to figure out the name of the model when used in associations. For example, a model named `user` will add the functions `get/set/add User` to instances of the associated model, and a property named `.user` in eager loading, while a model named `User` will add the same functions, but a property named `.User` (notice the upper case U) in eager loading. - -As we've already seen, you can alias models in associations using `as`. In single associations (has one and belongs to), the alias should be singular, while for many associations (has many) it should be plural. Sequelize then uses the [inflection][0] library to convert the alias to its singular form. However, this might not always work for irregular or non-english words. In this case, you can provide both the plural and the singular form of the alias: - -```js -User.belongsToMany(Project, { as: { singular: 'task', plural: 'tasks' }}) -// Notice that inflection has no problem singularizing tasks, this is just for illustrative purposes. -``` - -If you know that a model will always use the same alias in associations, you can provide it when creating the model - -```js -class Project extends Model {} -Project.init(attributes, { - name: { - singular: 'task', - plural: 'tasks', - }, - sequelize, - modelName: 'project' -}) - -User.belongsToMany(Project); -``` - -This will add the functions `add/set/get Tasks` to user instances. - -Remember, that using `as` to change the name of the association will also change the name of the foreign key. When using `as`, it is safest to also specify the foreign key. - -```js -Invoice.belongsTo(Subscription) -Subscription.hasMany(Invoice) -``` - -Without `as`, this adds `subscriptionId` as expected. However, if you were to say `Invoice.belongsTo(Subscription, { as: 'TheSubscription' })`, you will have both `subscriptionId` and `theSubscriptionId`, because sequelize is not smart enough to figure that the calls are two sides of the same relation. 'foreignKey' fixes this problem; - -```js -Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' }) -Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' }) -``` - -## Associating objects - -Because Sequelize is doing a lot of magic, you have to call `Sequelize.sync` after setting the associations! Doing so will allow you the following: - -```js -Project.hasMany(Task) -Task.belongsTo(Project) - -Project.create()... -Task.create()... -Task.create()... - -// save them... and then: -project.setTasks([task1, task2]).then(() => { - // saved! -}) - -// ok, now they are saved... how do I get them later on? -project.getTasks().then(associatedTasks => { - // associatedTasks is an array of tasks -}) - -// You can also pass filters to the getter method. -// They are equal to the options you can pass to a usual finder method. -project.getTasks({ where: 'id > 10' }).then(tasks => { - // tasks with an id greater than 10 :) -}) - -// You can also only retrieve certain fields of a associated object. -project.getTasks({attributes: ['title']}).then(tasks => { - // retrieve tasks with the attributes "title" and "id" -}) -``` - -To remove created associations you can just call the set method without a specific id: - -```js -// remove the association with task1 -project.setTasks([task2]).then(associatedTasks => { - // you will get task2 only -}) - -// remove 'em all -project.setTasks([]).then(associatedTasks => { - // you will get an empty array -}) - -// or remove 'em more directly -project.removeTask(task1).then(() => { - // it's gone -}) - -// and add 'em again -project.addTask(task1).then(() => { - // it's back again -}) -``` - -You can of course also do it vice versa: - -```js -// project is associated with task1 and task2 -task2.setProject(null).then(() => { - // and it's gone -}) -``` - -For hasOne/belongsTo it's basically the same: - -```js -Task.hasOne(User, {as: "Author"}) -Task.setAuthor(anAuthor) -``` - -Adding associations to a relation with a custom join table can be done in two ways (continuing with the associations defined in the previous chapter): - -```js -// Either by adding a property with the name of the join table model to the object, before creating the association -project.UserProjects = { - status: 'active' -} -u.addProject(project) - -// Or by providing a second options.through argument when adding the association, containing the data that should go in the join table -u.addProject(project, { through: { status: 'active' }}) - - -// When associating multiple objects, you can combine the two options above. In this case the second argument -// will be treated as a defaults object, that will be used if no data is provided -project1.UserProjects = { - status: 'inactive' -} - -u.setProjects([project1, project2], { through: { status: 'active' }}) -// The code above will record inactive for project one, and active for project two in the join table -``` - -When getting data on an association that has a custom join table, the data from the join table will be returned as a DAO instance: - -```js -u.getProjects().then(projects => { - const project = projects[0] - - if (project.UserProjects.status === 'active') { - // .. do magic - - // since this is a real DAO instance, you can save it directly after you are done doing magic - return project.UserProjects.save() - } -}) -``` - -If you only need some of the attributes from the join table, you can provide an array with the attributes you want: - -```js -// This will select only name from the Projects table, and only status from the UserProjects table -user.getProjects({ attributes: ['name'], joinTableAttributes: ['status']}) -``` - -## Check associations - -You can also check if an object is already associated with another one (N:M only). Here is how you'd do it: - -```js -// check if an object is one of associated ones: -Project.create({ /* */ }).then(project => { - return User.create({ /* */ }).then(user => { - return project.hasUser(user).then(result => { - // result would be false - return project.addUser(user).then(() => { - return project.hasUser(user).then(result => { - // result would be true - }) - }) - }) - }) -}) - -// check if all associated objects are as expected: -// let's assume we have already a project and two users -project.setUsers([user1, user2]).then(() => { - return project.hasUsers([user1]); -}).then(result => { - // result would be true - return project.hasUsers([user1, user2]); -}).then(result => { - // result would be true -}) -``` - -## Advanced Concepts - -### Scopes - -This section concerns association scopes. For a definition of association scopes vs. scopes on associated models, see [Scopes](scopes.html). - -Association scopes allow you to place a scope (a set of default attributes for `get` and `create`) on the association. Scopes can be placed both on the associated model (the target of the association), and on the through table for n:m relations. - -#### 1:n - -Assume we have models Comment, Post, and Image. A comment can be associated to either an image or a post via `commentableId` and `commentable` - we say that Post and Image are `Commentable` - -```js -class Post extends Model {} -Post.init({ - title: Sequelize.STRING, - text: Sequelize.STRING -}, { sequelize, modelName: 'post' }); - -class Image extends Model {} -Image.init({ - title: Sequelize.STRING, - link: Sequelize.STRING -}, { sequelize, modelName: 'image' }); - -class Comment extends Model { - getItem(options) { - return this[ - 'get' + - this.get('commentable') - [0] - .toUpperCase() + - this.get('commentable').substr(1) - ](options); - } -} - -Comment.init({ - title: Sequelize.STRING, - commentable: Sequelize.STRING, - commentableId: Sequelize.INTEGER -}, { sequelize, modelName: 'comment' }); - -Post.hasMany(Comment, { - foreignKey: 'commentableId', - constraints: false, - scope: { - commentable: 'post' - } -}); - -Comment.belongsTo(Post, { - foreignKey: 'commentableId', - constraints: false, - as: 'post' -}); - -Image.hasMany(Comment, { - foreignKey: 'commentableId', - constraints: false, - scope: { - commentable: 'image' - } -}); - -Comment.belongsTo(Image, { - foreignKey: 'commentableId', - constraints: false, - as: 'image' -}); -``` - -`constraints: false` disables references constraints, as `commentableId` column references several tables, we cannot add a `REFERENCES` constraint to it. - -Note that the Image -> Comment and Post -> Comment relations define a scope, `commentable: 'image'` and `commentable: 'post'` respectively. This scope is automatically applied when using the association functions: - -```js -image.getComments() -// SELECT "id", "title", "commentable", "commentableId", "createdAt", "updatedAt" FROM "comments" AS -// "comment" WHERE "comment"."commentable" = 'image' AND "comment"."commentableId" = 1; - -image.createComment({ - title: 'Awesome!' -}) -// INSERT INTO "comments" ("id","title","commentable","commentableId","createdAt","updatedAt") VALUES -// (DEFAULT,'Awesome!','image',1,'2018-04-17 05:36:40.454 +00:00','2018-04-17 05:36:40.454 +00:00') -// RETURNING *; - -image.addComment(comment); -// UPDATE "comments" SET "commentableId"=1,"commentable"='image',"updatedAt"='2018-04-17 05:38:43.948 -// +00:00' WHERE "id" IN (1) -``` - -The `getItem` utility function on `Comment` completes the picture - it simply converts the `commentable` string into a call to either `getImage` or `getPost`, providing an abstraction over whether a comment belongs to a post or an image. You can pass a normal options object as a parameter to `getItem(options)` to specify any where conditions or includes. - -#### n:m - -Continuing with the idea of a polymorphic model, consider a tag table - an item can have multiple tags, and a tag can be related to several items. - -For brevity, the example only shows a Post model, but in reality Tag would be related to several other models. - -```js -class ItemTag extends Model {} -ItemTag.init({ - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true - }, - tagId: { - type: Sequelize.INTEGER, - unique: 'item_tag_taggable' - }, - taggable: { - type: Sequelize.STRING, - unique: 'item_tag_taggable' - }, - taggableId: { - type: Sequelize.INTEGER, - unique: 'item_tag_taggable', - references: null - } -}, { sequelize, modelName: 'item_tag' }); - -class Tag extends Model {} -Tag.init({ - name: Sequelize.STRING, - status: Sequelize.STRING -}, { sequelize, modelName: 'tag' }); - -Post.belongsToMany(Tag, { - through: { - model: ItemTag, - unique: false, - scope: { - taggable: 'post' - } - }, - foreignKey: 'taggableId', - constraints: false -}); - -Tag.belongsToMany(Post, { - through: { - model: ItemTag, - unique: false - }, - foreignKey: 'tagId', - constraints: false -}); -``` - -Notice that the scoped column (`taggable`) is now on the through model (`ItemTag`). - -We could also define a more restrictive association, for example, to get all pending tags for a post by applying a scope of both the through model (`ItemTag`) and the target model (`Tag`): - -```js -Post.belongsToMany(Tag, { - through: { - model: ItemTag, - unique: false, - scope: { - taggable: 'post' - } - }, - scope: { - status: 'pending' - }, - as: 'pendingTags', - foreignKey: 'taggableId', - constraints: false -}); - -post.getPendingTags(); -``` - -```sql -SELECT - "tag"."id", - "tag"."name", - "tag"."status", - "tag"."createdAt", - "tag"."updatedAt", - "item_tag"."id" AS "item_tag.id", - "item_tag"."tagId" AS "item_tag.tagId", - "item_tag"."taggable" AS "item_tag.taggable", - "item_tag"."taggableId" AS "item_tag.taggableId", - "item_tag"."createdAt" AS "item_tag.createdAt", - "item_tag"."updatedAt" AS "item_tag.updatedAt" -FROM - "tags" AS "tag" - INNER JOIN "item_tags" AS "item_tag" ON "tag"."id" = "item_tag"."tagId" - AND "item_tag"."taggableId" = 1 - AND "item_tag"."taggable" = 'post' -WHERE - ("tag"."status" = 'pending'); -``` - -`constraints: false` disables references constraints on the `taggableId` column. Because the column is polymorphic, we cannot say that it `REFERENCES` a specific table. - -### Creating with associations - -An instance can be created with nested association in one step, provided all elements are new. - -#### BelongsTo / HasMany / HasOne association - -Consider the following models: - -```js -class Product extends Model {} -Product.init({ - title: Sequelize.STRING -}, { sequelize, modelName: 'product' }); -class User extends Model {} -User.init({ - firstName: Sequelize.STRING, - lastName: Sequelize.STRING -}, { sequelize, modelName: 'user' }); -class Address extends Model {} -Address.init({ - type: Sequelize.STRING, - line1: Sequelize.STRING, - line2: Sequelize.STRING, - city: Sequelize.STRING, - state: Sequelize.STRING, - zip: Sequelize.STRING, -}, { sequelize, modelName: 'address' }); - -Product.User = Product.belongsTo(User); -User.Addresses = User.hasMany(Address); -// Also works for `hasOne` -``` - -A new `Product`, `User`, and one or more `Address` can be created in one step in the following way: - -```js -return Product.create({ - title: 'Chair', - user: { - firstName: 'Mick', - lastName: 'Broadstone', - addresses: [{ - type: 'home', - line1: '100 Main St.', - city: 'Austin', - state: 'TX', - zip: '78704' - }] - } -}, { - include: [{ - association: Product.User, - include: [ User.Addresses ] - }] -}); -``` - -Here, our user model is called `user`, with a lowercase u - This means that the property in the object should also be `user`. If the name given to `sequelize.define` was `User`, the key in the object should also be `User`. Likewise for `addresses`, except it's pluralized being a `hasMany` association. - -#### BelongsTo association with an alias - -The previous example can be extended to support an association alias. - -```js -const Creator = Product.belongsTo(User, { as: 'creator' }); - -return Product.create({ - title: 'Chair', - creator: { - firstName: 'Matt', - lastName: 'Hansen' - } -}, { - include: [ Creator ] -}); -``` - -#### HasMany / BelongsToMany association - -Let's introduce the ability to associate a product with many tags. Setting up the models could look like: - -```js -class Tag extends Model {} -Tag.init({ - name: Sequelize.STRING -}, { sequelize, modelName: 'tag' }); - -Product.hasMany(Tag); -// Also works for `belongsToMany`. -``` - -Now we can create a product with multiple tags in the following way: - -```js -Product.create({ - id: 1, - title: 'Chair', - tags: [ - { name: 'Alpha'}, - { name: 'Beta'} - ] -}, { - include: [ Tag ] -}) -``` - -And, we can modify this example to support an alias as well: - -```js -const Categories = Product.hasMany(Tag, { as: 'categories' }); - -Product.create({ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] -}, { - include: [{ - association: Categories, - as: 'categories' - }] -}) -``` - -*** - -[0]: https://www.npmjs.org/package/inflection diff --git a/docs/manual/core-concepts/assocs.md b/docs/manual/core-concepts/assocs.md new file mode 100644 index 000000000000..b511d7c62f80 --- /dev/null +++ b/docs/manual/core-concepts/assocs.md @@ -0,0 +1,784 @@ +# Associations + +Sequelize supports the standard associations: [One-To-One](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29), [One-To-Many](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) and [Many-To-Many](https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29). + +To do this, Sequelize provides **four** types of associations that should be combined to create them: + +* The `HasOne` association +* The `BelongsTo` association +* The `HasMany` association +* The `BelongsToMany` association + +The guide will start explaining how to define these four types of associations, and then will follow up to explain how to combine those to define the three standard association types ([One-To-One](https://en.wikipedia.org/wiki/One-to-one_%28data_model%29), [One-To-Many](https://en.wikipedia.org/wiki/One-to-many_%28data_model%29) and [Many-To-Many](https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29)). + +## Defining the Sequelize associations + +The four association types are defined in a very similar way. Let's say we have two models, `A` and `B`. Telling Sequelize that you want an association between the two needs just a function call: + +```js +const A = sequelize.define('A', /* ... */); +const B = sequelize.define('B', /* ... */); + +A.hasOne(B); // A HasOne B +A.belongsTo(B); // A BelongsTo B +A.hasMany(B); // A HasMany B +A.belongsToMany(B, { through: 'C' }); // A BelongsToMany B through the junction table C +``` + +They all accept an options object as a second parameter (optional for the first three, mandatory for `belongsToMany` containing at least the `through` property): + +```js +A.hasOne(B, { /* options */ }); +A.belongsTo(B, { /* options */ }); +A.hasMany(B, { /* options */ }); +A.belongsToMany(B, { through: 'C', /* options */ }); +``` + +The order in which the association is defined is relevant. In other words, the order matters, for the four cases. In all examples above, `A` is called the **source** model and `B` is called the **target** model. This terminology is important. + +The `A.hasOne(B)` association means that a One-To-One relationship exists between `A` and `B`, with the foreign key being defined in the target model (`B`). + +The `A.belongsTo(B)` association means that a One-To-One relationship exists between `A` and `B`, with the foreign key being defined in the source model (`A`). + +The `A.hasMany(B)` association means that a One-To-Many relationship exists between `A` and `B`, with the foreign key being defined in the target model (`B`). + +These three calls will cause Sequelize to automatically add foreign keys to the appropriate models (unless they are already present). + +The `A.belongsToMany(B, { through: 'C' })` association means that a Many-To-Many relationship exists between `A` and `B`, using table `C` as [junction table](https://en.wikipedia.org/wiki/Associative_entity), which will have the foreign keys (`aId` and `bId`, for example). Sequelize will automatically create this model `C` (unless it already exists) and define the appropriate foreign keys on it. + +*Note: In the examples above for `belongsToMany`, a string (`'C'`) was passed to the through option. In this case, Sequelize automatically generates a model with this name. However, you can also pass a model directly, if you have already defined it.* + +These are the main ideas involved in each type of association. However, these relationships are often used in pairs, in order to enable better usage with Sequelize. This will be seen later on. + +## Creating the standard relationships + +As mentioned, usually the Sequelize associations are defined in pairs. In summary: + +* To create a **One-To-One** relationship, the `hasOne` and `belongsTo` associations are used together; +* To create a **One-To-Many** relationship, the `hasMany` and `belongsTo` associations are used together; +* To create a **Many-To-Many** relationship, two `belongsToMany` calls are used together. + * Note: there is also a *Super Many-To-Many* relationship, which uses six associations at once, and will be discussed in the [Advanced Many-to-Many relationships guide](advanced-many-to-many.html). + +This will all be seen in detail next. The advantages of using these pairs instead of one single association will be discussed in the end of this chapter. + +## One-To-One relationships + +### Philosophy + +Before digging into the aspects of using Sequelize, it is useful to take a step back to consider what happens with a One-To-One relationship. + +Let's say we have two models, `Foo` and `Bar`. We want to establish a One-To-One relationship between Foo and Bar. We know that in a relational database, this will be done by establishing a foreign key in one of the tables. So in this case, a very relevant question is: in which table do we want this foreign key to be? In other words, do we want `Foo` to have a `barId` column, or should `Bar` have a `fooId` column instead? + +In principle, both options are a valid way to establish a One-To-One relationship between Foo and Bar. However, when we say something like *"there is a One-To-One relationship between Foo and Bar"*, it is unclear whether or not the relationship is *mandatory* or optional. In other words, can a Foo exist without a Bar? Can a Bar exist without a Foo? The answers to these questions helps figuring out where we want the foreign key column to be. + +### Goal + +For the rest of this example, let's assume that we have two models, `Foo` and `Bar`. We want to setup a One-To-One relationship between them such that `Bar` gets a `fooId` column. + +### Implementation + +The main setup to achieve the goal is as follows: + +```js +Foo.hasOne(Bar); +Bar.belongsTo(Foo); +``` + +Since no option was passed, Sequelize will infer what to do from the names of the models. In this case, Sequelize knows that a `fooId` column must be added to `Bar`. + +This way, calling `Bar.sync()` after the above will yield the following SQL (on PostgreSQL, for example): + +```sql +CREATE TABLE IF NOT EXISTS "foos" ( + /* ... */ +); +CREATE TABLE IF NOT EXISTS "bars" ( + /* ... */ + "fooId" INTEGER REFERENCES "foos" ("id") ON DELETE SET NULL ON UPDATE CASCADE + /* ... */ +); +``` + +### Options + +Various options can be passed as a second parameter of the association call. + +#### `onDelete` and `onUpdate` + +For example, to configure the `ON DELETE` and `ON UPDATE` behaviors, you can do: + +```js +Foo.hasOne(Bar, { + onDelete: 'RESTRICT', + onUpdate: 'RESTRICT' +}); +Bar.belongsTo(Foo); +``` + +The possible choices are `RESTRICT`, `CASCADE`, `NO ACTION`, `SET DEFAULT` and `SET NULL`. + +The defaults for the One-To-One associations is `SET NULL` for `ON DELETE` and `CASCADE` for `ON UPDATE`. + +#### Customizing the foreign key + +Both the `hasOne` and `belongsTo` calls shown above will infer that the foreign key to be created should be called `fooId`. To use a different name, such as `myFooId`: + +```js +// Option 1 +Foo.hasOne(Bar, { + foreignKey: 'myFooId' +}); +Bar.belongsTo(Foo); + +// Option 2 +Foo.hasOne(Bar, { + foreignKey: { + name: 'myFooId' + } +}); +Bar.belongsTo(Foo); + +// Option 3 +Foo.hasOne(Bar); +Bar.belongsTo(Foo, { + foreignKey: 'myFooId' +}); + +// Option 4 +Foo.hasOne(Bar); +Bar.belongsTo(Foo, { + foreignKey: { + name: 'myFooId' + } +}); +``` + +As shown above, the `foreignKey` option accepts a string or an object. When receiving an object, this object will be used as the definition for the column just like it would do in a standard `sequelize.define` call. Therefore, specifying options such as `type`, `allowNull`, `defaultValue`, etc, just work. + +For example, to use `UUID` as the foreign key data type instead of the default (`INTEGER`), you can simply do: + +```js +const { DataTypes } = require("Sequelize"); + +Foo.hasOne(Bar, { + foreignKey: { + // name: 'myFooId' + type: DataTypes.UUID + } +}); +Bar.belongsTo(Foo); +``` + +#### Mandatory versus optional associations + +By default, the association is considered optional. In other words, in our example, the `fooId` is allowed to be null, meaning that one Bar can exist without a Foo. Changing this is just a matter of specifying `allowNull: false` in the foreign key options: + +```js +Foo.hasOne(Bar, { + foreignKey: { + allowNull: false + } +}); +// "fooId" INTEGER NOT NULL REFERENCES "foos" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT +``` + +## One-To-Many relationships + +### Philosophy + +One-To-Many associations are connecting one source with multiple targets, while all these targets are connected only with this single source. + +This means that, unlike the One-To-One association, in which we had to choose where the foreign key would be placed, there is only one option in One-To-Many associations. For example, if one Foo has many Bars (and this way each Bar belongs to one Foo), then the only sensible implementation is to have a `fooId` column in the `Bar` table. The opposite is impossible, since one Foo has many Bars. + +### Goal + +In this example, we have the models `Team` and `Player`. We want to tell Sequelize that there is a One-To-Many relationship between them, meaning that one Team has many Players, while each Player belongs to a single Team. + +### Implementation + +The main way to do this is as follows: + +```js +Team.hasMany(Player); +Player.belongsTo(Team); +``` + +Again, as mentioned, the main way to do it used a pair of Sequelize associations (`hasMany` and `belongsTo`). + +For example, in PostgreSQL, the above setup will yield the following SQL upon `sync()`: + +```sql +CREATE TABLE IF NOT EXISTS "Teams" ( + /* ... */ +); +CREATE TABLE IF NOT EXISTS "Players" ( + /* ... */ + "TeamId" INTEGER REFERENCES "Teams" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + /* ... */ +); +``` + +### Options + +The options to be applied in this case are the same from the One-To-One case. For example, to change the name of the foreign key and make sure that the relationship is mandatory, we can do: + +```js +Team.hasMany(Player, { + foreignKey: 'clubId' +}); +Player.belongsTo(Team); +``` + +Like One-To-One relationships, `ON DELETE` defaults to `SET NULL` and `ON UPDATE` defaults to `CASCADE`. + +## Many-To-Many relationships + +### Philosophy + +Many-To-Many associations connect one source with multiple targets, while all these targets can in turn be connected to other sources beyond the first. + +This cannot be represented by adding one foreign key to one of the tables, like the other relationships did. Instead, the concept of a [Junction Model](https://en.wikipedia.org/wiki/Associative_entity) is used. This will be an extra model (and extra table in the database) which will have two foreign key columns and will keep track of the associations. The junction table is also sometimes called *join table* or *through table*. + +### Goal + +For this example, we will consider the models `Movie` and `Actor`. One actor may have participated in many movies, and one movie had many actors involved with its production. The junction table that will keep track of the associations will be called `ActorMovies`, which will contain the foreign keys `movieId` and `actorId`. + +### Implementation + +The main way to do this in Sequelize is as follows: + +```js +const Movie = sequelize.define('Movie', { name: DataTypes.STRING }); +const Actor = sequelize.define('Actor', { name: DataTypes.STRING }); +Movie.belongsToMany(Actor, { through: 'ActorMovies' }); +Actor.belongsToMany(Movie, { through: 'ActorMovies' }); +``` + +Since a string was given in the `through` option of the `belongsToMany` call, Sequelize will automatically create the `ActorMovies` model which will act as the junction model. For example, in PostgreSQL: + +```sql +CREATE TABLE IF NOT EXISTS "ActorMovies" ( + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "MovieId" INTEGER REFERENCES "Movies" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + "ActorId" INTEGER REFERENCES "Actors" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("MovieId","ActorId") +); +``` + +Instead of a string, passing a model directly is also supported, and in that case the given model will be used as the junction model (and no model will be created automatically). For example: + +```js +const Movie = sequelize.define('Movie', { name: DataTypes.STRING }); +const Actor = sequelize.define('Actor', { name: DataTypes.STRING }); +const ActorMovies = sequelize.define('ActorMovies', { + MovieId: { + type: DataTypes.INTEGER, + references: { + model: Movie, // 'Movies' would also work + key: 'id' + } + }, + ActorId: { + type: DataTypes.INTEGER, + references: { + model: Actor, // 'Actors' would also work + key: 'id' + } + } +}); +Movie.belongsToMany(Actor, { through: ActorMovies }); +Actor.belongsToMany(Movie, { through: ActorMovies }); +``` + +The above yields the following SQL in PostgreSQL, which is equivalent to the one shown above: + +```sql +CREATE TABLE IF NOT EXISTS "ActorMovies" ( + "MovieId" INTEGER NOT NULL REFERENCES "Movies" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + "ActorId" INTEGER NOT NULL REFERENCES "Actors" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + UNIQUE ("MovieId", "ActorId"), -- Note: Sequelize generated this UNIQUE constraint but + PRIMARY KEY ("MovieId","ActorId") -- it is irrelevant since it's also a PRIMARY KEY +); +``` + +### Options + +Unlike One-To-One and One-To-Many relationships, the defaults for both `ON UPDATE` and `ON DELETE` are `CASCADE` for Many-To-Many relationships. + +Belongs-To-Many creates a unique key on through model. This unique key name can be overridden using **uniqueKey** option. To prevent creating this unique key, use the ***unique: false*** option. + +```js +Project.belongsToMany(User, { through: UserProjects, uniqueKey: 'my_custom_unique' }) +``` + +## Basics of queries involving associations + +With the basics of defining associations covered, we can look at queries involving associations. The most common queries on this matter are the *read* queries (i.e. SELECTs). Later on, other types of queries will be shown. + +In order to study this, we will consider an example in which we have Ships and Captains, and a one-to-one relationship between them. We will allow null on foreign keys (the default), meaning that a Ship can exist without a Captain and vice-versa. + +```js +// This is the setup of our models for the examples below +const Ship = sequelize.define('ship', { + name: DataTypes.TEXT, + crewCapacity: DataTypes.INTEGER, + amountOfSails: DataTypes.INTEGER +}, { timestamps: false }); +const Captain = sequelize.define('captain', { + name: DataTypes.TEXT, + skillLevel: { + type: DataTypes.INTEGER, + validate: { min: 1, max: 10 } + } +}, { timestamps: false }); +Captain.hasOne(Ship); +Ship.belongsTo(Captain); +``` + +### Fetching associations - Eager Loading vs Lazy Loading + +The concepts of Eager Loading and Lazy Loading are fundamental to understand how fetching associations work in Sequelize. Lazy Loading refers to the technique of fetching the associated data only when you really want it; Eager Loading, on the other hand, refers to the technique of fetching everything at once, since the beginning, with a larger query. + +#### Lazy Loading example + +```js +const awesomeCaptain = await Captain.findOne({ + where: { + name: "Jack Sparrow" + } +}); +// Do stuff with the fetched captain +console.log('Name:', awesomeCaptain.name); +console.log('Skill Level:', awesomeCaptain.skillLevel); +// Now we want information about his ship! +const hisShip = await awesomeCaptain.getShip(); +// Do stuff with the ship +console.log('Ship Name:', hisShip.name); +console.log('Amount of Sails:', hisShip.amountOfSails); +``` + +Observe that in the example above, we made two queries, only fetching the associated ship when we wanted to use it. This can be especially useful if we may or may not need the ship, perhaps we want to fetch it conditionally, only in a few cases; this way we can save time and memory by only fetching it when necessary. + +Note: the `getShip()` instance method used above is one of the methods Sequelize automatically adds to `Captain` instances. There are others. You will learn more about them later in this guide. + +#### Eager Loading Example + +```js +const awesomeCaptain = await Captain.findOne({ + where: { + name: "Jack Sparrow" + }, + include: Ship +}); +// Now the ship comes with it +console.log('Name:', awesomeCaptain.name); +console.log('Skill Level:', awesomeCaptain.skillLevel); +console.log('Ship Name:', awesomeCaptain.ship.name); +console.log('Amount of Sails:', awesomeCaptain.ship.amountOfSails); +``` + +As shown above, Eager Loading is performed in Sequelize by using the `include` option. Observe that here only one query was performed to the database (which brings the associated data along with the instance). + +This was just a quick introduction to Eager Loading in Sequelize. There is a lot more to it, which you can learn at [the dedicated guide on Eager Loading](eager-loading.html). + +### Creating, updating and deleting + +The above showed the basics on queries for fetching data involving associations. For creating, updating and deleting, you can either: + +* Use the standard model queries directly: + + ```js + // Example: creating an associated model using the standard methods + Bar.create({ + name: 'My Bar', + fooId: 5 + }); + // This creates a Bar belonging to the Foo of ID 5 (since fooId is + // a regular column, after all). Nothing very clever going on here. + ``` + +* Or use the *[special methods/mixins](#special-methods-mixins-added-to-instances)* available for associated models, which are explained later on this page. + +**Note:** The [`save()` instance method](../class/lib/model.js~Model.html#instance-method-save) is not aware of associations. In other words, if you change a value from a *child* object that was eager loaded along a *parent* object, calling `save()` on the parent will completely ignore the change that happened on the child. + +## Association Aliases & Custom Foreign Keys + +In all the above examples, Sequelize automatically defined the foreign key names. For example, in the Ship and Captain example, Sequelize automatically defined a `captainId` field on the Ship model. However, it is easy to specify a custom foreign key. + +Let's consider the models Ship and Captain in a simplified form, just to focus on the current topic, as shown below (less fields): + +```js +const Ship = sequelize.define('ship', { name: DataTypes.TEXT }, { timestamps: false }); +const Captain = sequelize.define('captain', { name: DataTypes.TEXT }, { timestamps: false }); +``` + +There are three ways to specify a different name for the foreign key: + +* By providing the foreign key name directly +* By defining an Alias +* By doing both things + +### Recap: the default setup + +By using simply `Ship.belongsTo(Captain)`, sequelize will generate the foreign key name automatically: + +```js +Ship.belongsTo(Captain); // This creates the `captainId` foreign key in Ship. + +// Eager Loading is done by passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); +// Or by providing the associated model name: +console.log((await Ship.findAll({ include: 'captain' })).toJSON()); + +// Also, instances obtain a `getCaptain()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getCaptain()).toJSON()); +``` + +### Providing the foreign key name directly + +The foreign key name can be provided directly with an option in the association definition, as follows: + +```js +Ship.belongsTo(Captain, { foreignKey: 'bossId' }); // This creates the `bossId` foreign key in Ship. + +// Eager Loading is done by passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); +// Or by providing the associated model name: +console.log((await Ship.findAll({ include: 'Captain' })).toJSON()); + +// Also, instances obtain a `getCaptain()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getCaptain()).toJSON()); +``` + +### Defining an Alias + +Defining an Alias is more powerful than simply specifying a custom name for the foreign key. This is better understood with an example: + + + +```js +Ship.belongsTo(Captain, { as: 'leader' }); // This creates the `leaderId` foreign key in Ship. + +// Eager Loading no longer works by passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); // Throws an error +// Instead, you have to pass the alias: +console.log((await Ship.findAll({ include: 'leader' })).toJSON()); +// Or you can pass an object specifying the model and alias: +console.log((await Ship.findAll({ + include: { + model: Captain, + as: 'leader' + } +})).toJSON()); + +// Also, instances obtain a `getLeader()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getLeader()).toJSON()); +``` + +Aliases are especially useful when you need to define two different associations between the same models. For example, if we have the models `Mail` and `Person`, we may want to associate them twice, to represent the `sender` and `receiver` of the Mail. In this case we must use an alias for each association, since otherwise a call like `mail.getPerson()` would be ambiguous. With the `sender` and `receiver` aliases, we would have the two methods available and working: `mail.getSender()` and `mail.getReceiver()`, both of them returning a `Promise`. + +When defining an alias for a `hasOne` or `belongsTo` association, you should use the singular form of a word (such as `leader`, in the example above). On the other hand, when defining an alias for `hasMany` and `belongsToMany`, you should use the plural form. Defining aliases for Many-to-Many relationships (with `belongsToMany`) is covered in the [Advanced Many-to-Many Associations guide](advanced-many-to-many.html). + +### Doing both things + +We can define and alias and also directly define the foreign key: + +```js +Ship.belongsTo(Captain, { as: 'leader', foreignKey: 'bossId' }); // This creates the `bossId` foreign key in Ship. + +// Since an alias was defined, eager Loading doesn't work by simply passing the model to `include`: +console.log((await Ship.findAll({ include: Captain })).toJSON()); // Throws an error +// Instead, you have to pass the alias: +console.log((await Ship.findAll({ include: 'leader' })).toJSON()); +// Or you can pass an object specifying the model and alias: +console.log((await Ship.findAll({ + include: { + model: Captain, + as: 'leader' + } +})).toJSON()); + +// Also, instances obtain a `getLeader()` method for Lazy Loading: +const ship = Ship.findOne(); +console.log((await ship.getLeader()).toJSON()); +``` + +## Special methods/mixins added to instances + +When an association is defined between two models, the instances of those models gain special methods to interact with their associated counterparts. + +For example, if we have two models, `Foo` and `Bar`, and they are associated, their instances will have the following methods/mixins available, depending on the association type: + +### `Foo.hasOne(Bar)` + +* `fooInstance.getBar()` +* `fooInstance.setBar()` +* `fooInstance.createBar()` + +Example: + +```js +const foo = await Foo.create({ name: 'the-foo' }); +const bar1 = await Bar.create({ name: 'some-bar' }); +const bar2 = await Bar.create({ name: 'another-bar' }); +console.log(await foo.getBar()); // null +await foo.setBar(bar1); +console.log((await foo.getBar()).name); // 'some-bar' +await foo.createBar({ name: 'yet-another-bar' }); +const newlyAssociatedBar = await foo.getBar(); +console.log(newlyAssociatedBar.name); // 'yet-another-bar' +await foo.setBar(null); // Un-associate +console.log(await foo.getBar()); // null +``` + +### `Foo.belongsTo(Bar)` + +The same ones from `Foo.hasOne(Bar)`: + +* `fooInstance.getBar()` +* `fooInstance.setBar()` +* `fooInstance.createBar()` + +### `Foo.hasMany(Bar)` + +* `fooInstance.getBars()` +* `fooInstance.countBars()` +* `fooInstance.hasBar()` +* `fooInstance.hasBars()` +* `fooInstance.setBars()` +* `fooInstance.addBar()` +* `fooInstance.addBars()` +* `fooInstance.removeBar()` +* `fooInstance.removeBars()` +* `fooInstance.createBar()` + +Example: + +```js +const foo = await Foo.create({ name: 'the-foo' }); +const bar1 = await Bar.create({ name: 'some-bar' }); +const bar2 = await Bar.create({ name: 'another-bar' }); +console.log(await foo.getBars()); // [] +console.log(await foo.countBars()); // 0 +console.log(await foo.hasBar(bar1)); // false +await foo.addBars([bar1, bar2]); +console.log(await foo.countBars()); // 2 +await foo.addBar(bar1); +console.log(await foo.countBars()); // 2 +console.log(await foo.hasBar(bar1)); // true +await foo.removeBar(bar2); +console.log(await foo.countBars()); // 1 +await foo.createBar({ name: 'yet-another-bar' }); +console.log(await foo.countBars()); // 2 +await foo.setBars([]); // Un-associate all previously associated bars +console.log(await foo.countBars()); // 0 +``` + +The getter method accepts options just like the usual finder methods (such as `findAll`): + +```js +const easyTasks = await project.getTasks({ + where: { + difficulty: { + [Op.lte]: 5 + } + } +}); +const taskTitles = (await project.getTasks({ + attributes: ['title'], + raw: true +})).map(task => task.title); +``` + +### `Foo.belongsToMany(Bar, { through: Baz })` + +The same ones from `Foo.hasMany(Bar)`: + +* `fooInstance.getBars()` +* `fooInstance.countBars()` +* `fooInstance.hasBar()` +* `fooInstance.hasBars()` +* `fooInstance.setBars()` +* `fooInstance.addBar()` +* `fooInstance.addBars()` +* `fooInstance.removeBar()` +* `fooInstance.removeBars()` +* `fooInstance.createBar()` + +### Note: Method names + +As shown in the examples above, the names Sequelize gives to these special methods are formed by a prefix (e.g. `get`, `add`, `set`) concatenated with the model name (with the first letter in uppercase). When necessary, the plural is used, such as in `fooInstance.setBars()`. Again, irregular plurals are also handled automatically by Sequelize. For example, `Person` becomes `People` and `Hypothesis` becomes `Hypotheses`. + +If an alias was defined, it will be used instead of the model name to form the method names. For example: + +```js +Task.hasOne(User, { as: 'Author' }); +``` + +* `taskInstance.getAuthor()` +* `taskInstance.setAuthor()` +* `taskInstance.createAuthor()` + +## Why associations are defined in pairs? + +As mentioned earlier and shown in most examples above, usually associations in Sequelize are defined in pairs: + +* To create a **One-To-One** relationship, the `hasOne` and `belongsTo` associations are used together; +* To create a **One-To-Many** relationship, the `hasMany` and `belongsTo` associations are used together; +* To create a **Many-To-Many** relationship, two `belongsToMany` calls are used together. + +When a Sequelize association is defined between two models, only the *source* model *knows about it*. So, for example, when using `Foo.hasOne(Bar)` (so `Foo` is the source model and `Bar` is the target model), only `Foo` knows about the existence of this association. This is why in this case, as shown above, `Foo` instances gain the methods `getBar()`, `setBar()` and `createBar()`, while on the other hand `Bar` instances get nothing. + +Similarly, for `Foo.hasOne(Bar)`, since `Foo` knows about the relationship, we can perform eager loading as in `Foo.findOne({ include: Bar })`, but we can't do `Bar.findOne({ include: Foo })`. + +Therefore, to bring full power to Sequelize usage, we usually setup the relationship in pairs, so that both models get to *know about it*. + +Practical demonstration: + +* If we do not define the pair of associations, calling for example just `Foo.hasOne(Bar)`: + + ```js + // This works... + await Foo.findOne({ include: Bar }); + + // But this throws an error: + await Bar.findOne({ include: Foo }); + // SequelizeEagerLoadingError: foo is not associated to bar! + ``` + +* If we define the pair as recommended, i.e., both `Foo.hasOne(Bar)` and `Bar.belongsTo(Foo)`: + + ```js + // This works! + await Foo.findOne({ include: Bar }); + + // This also works! + await Bar.findOne({ include: Foo }); + ``` + +## Multiple associations involving the same models + +In Sequelize, it is possible to define multiple associations between the same models. You just have to define different aliases for them: + +```js +Team.hasOne(Game, { as: 'HomeTeam', foreignKey: 'homeTeamId' }); +Team.hasOne(Game, { as: 'AwayTeam', foreignKey: 'awayTeamId' }); +Game.belongsTo(Team); +``` + +## Creating associations referencing a field which is not the primary key + +In all the examples above, the associations were defined by referencing the primary keys of the involved models (in our case, their IDs). However, Sequelize allows you to define an association that uses another field, instead of the primary key field, to establish the association. + +This other field must have a unique constraint on it (otherwise, it wouldn't make sense). + +### For `belongsTo` relationships + +First, recall that the `A.belongsTo(B)` association places the foreign key in the *source model* (i.e., in `A`). + +Let's again use the example of Ships and Captains. Additionally, we will assume that Captain names are unique: + +```js +const Ship = sequelize.define('ship', { name: DataTypes.TEXT }, { timestamps: false }); +const Captain = sequelize.define('captain', { + name: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +``` + +This way, instead of keeping the `captainId` on our Ships, we could keep a `captainName` instead and use it as our association tracker. In other words, instead of referencing the `id` from the target model (Captain), our relationship will reference another column on the target model: the `name` column. To specify this, we have to define a *target key*. We will also have to specify a name for the foreign key itself: + +```js +Ship.belongsTo(Captain, { targetKey: 'name', foreignKey: 'captainName' }); +// This creates a foreign key called `captainName` in the source model (Ship) +// which references the `name` field from the target model (Captain). +``` + +Now we can do things like: + +```js +await Captain.create({ name: "Jack Sparrow" }); +const ship = await Ship.create({ name: "Black Pearl", captainName: "Jack Sparrow" }); +console.log((await ship.getCaptain()).name); // "Jack Sparrow" +``` + +### For `hasOne` and `hasMany` relationships + +The exact same idea can be applied to the `hasOne` and `hasMany` associations, but instead of providing a `targetKey`, we provide a `sourceKey` when defining the association. This is because unlike `belongsTo`, the `hasOne` and `hasMany` associations keep the foreign key on the target model: + +```js +const Foo = sequelize.define('foo', { + name: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +const Bar = sequelize.define('bar', { + title: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +const Baz = sequelize.define('baz', { summary: DataTypes.TEXT }, { timestamps: false }); +Foo.hasOne(Bar, { sourceKey: 'name', foreignKey: 'fooName' }); +Bar.hasMany(Baz, { sourceKey: 'title', foreignKey: 'barTitle' }); +// [...] +await Bar.setFoo("Foo's Name Here"); +await Baz.addBar("Bar's Title Here"); +``` + +### For `belongsToMany` relationships + +The same idea can also be applied to `belongsToMany` relationships. However, unlike the other situations, in which we have only one foreign key involved, the `belongsToMany` relationship involves two foreign keys which are kept on an extra table (the junction table). + +Consider the following setup: + +```js +const Foo = sequelize.define('foo', { + name: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +const Bar = sequelize.define('bar', { + title: { type: DataTypes.TEXT, unique: true } +}, { timestamps: false }); +``` + +There are four cases to consider: + +* We might want a many-to-many relationship using the default primary keys for both `Foo` and `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar' }); +// This creates a junction table `foo_bar` with fields `fooId` and `barId` +``` + +* We might want a many-to-many relationship using the default primary key for `Foo` but a different field for `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar', targetKey: 'title' }); +// This creates a junction table `foo_bar` with fields `fooId` and `barTitle` +``` + +* We might want a many-to-many relationship using the a different field for `Foo` and the default primary key for `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar', sourceKey: 'name' }); +// This creates a junction table `foo_bar` with fields `fooName` and `barId` +``` + +* We might want a many-to-many relationship using different fields for both `Foo` and `Bar`: + +```js +Foo.belongsToMany(Bar, { through: 'foo_bar', sourceKey: 'name', targetKey: 'title' }); +// This creates a junction table `foo_bar` with fields `fooName` and `barTitle` +``` + +### Notes + +Don't forget that the field referenced in the association must have a unique constraint placed on it. Otherwise, an error will be thrown (and sometimes with a mysterious error message - such as `SequelizeDatabaseError: SQLITE_ERROR: foreign key mismatch - "ships" referencing "captains"` for SQLite). + +The trick to deciding between `sourceKey` and `targetKey` is just to remember where each relationship places its foreign key. As mentioned in the beginning of this guide: + +* `A.belongsTo(B)` keeps the foreign key in the source model (`A`), therefore the referenced key is in the target model, hence the usage of `targetKey`. + +* `A.hasOne(B)` and `A.hasMany(B)` keep the foreign key in the target model (`B`), therefore the referenced key is in the source model, hence the usage of `sourceKey`. + +* `A.belongsToMany(B)` involves an extra table (the junction table), therefore both `sourceKey` and `targetKey` are usable, with `sourceKey` corresponding to some field in `A` (the source) and `targetKey` corresponding to some field in `B` (the target). diff --git a/docs/manual/core-concepts/getters-setters-virtuals.md b/docs/manual/core-concepts/getters-setters-virtuals.md new file mode 100644 index 000000000000..c280dc3ae344 --- /dev/null +++ b/docs/manual/core-concepts/getters-setters-virtuals.md @@ -0,0 +1,201 @@ +# Getters, Setters & Virtuals + +Sequelize allows you to define custom getters and setters for the attributes of your models. + +Sequelize also allows you to specify the so-called *virtual attributes*, which are attributes on the Sequelize Model that doesn't really exist in the underlying SQL table, but instead are populated automatically by Sequelize. They are very useful for simplifying code, for example. + +## Getters + +A getter is a `get()` function defined for one column in the model definition: + +```js +const User = sequelize.define('user', { + // Let's say we wanted to see every username in uppercase, even + // though they are not necessarily uppercase in the database itself + username: { + type: DataTypes.STRING, + get() { + const rawValue = this.getDataValue('username'); + return rawValue ? rawValue.toUpperCase() : null; + } + } +}); +``` + +This getter, just like a standard JavaScript getter, is called automatically when the field value is read: + +```js +const user = User.build({ username: 'SuperUser123' }); +console.log(user.username); // 'SUPERUSER123' +console.log(user.getDataValue('username')); // 'SuperUser123' +``` + +Note that, although `SUPERUSER123` was logged above, the value truly stored in the database is still `SuperUser123`. We used `this.getDataValue('username')` to obtain this value, and converted it to uppercase. + +Had we tried to use `this.username` in the getter instead, we would have gotten an infinite loop! This is why Sequelize provides the `getDataValue` method. + +## Setters + +A setter is a `set()` function defined for one column in the model definition. It receives the value being set: + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + password: { + type: DataTypes.STRING, + set(value) { + // Storing passwords in plaintext in the database is terrible. + // Hashing the value with an appropriate cryptographic hash function is better. + this.setDataValue('password', hash(value)); + } + } +}); +``` + +```js +const user = User.build({ username: 'someone', password: 'NotSo§tr0ngP4$SW0RD!' }); +console.log(user.password); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' +console.log(user.getDataValue('password')); // '7cfc84b8ea898bb72462e78b4643cfccd77e9f05678ec2ce78754147ba947acc' +``` + +Observe that Sequelize called the setter automatically, before even sending data to the database. The only data the database ever saw was the already hashed value. + +If we wanted to involve another field from our model instance in the computation, that is possible and very easy! + +```js +const User = sequelize.define('user', { + username: DataTypes.STRING, + password: { + type: DataTypes.STRING, + set(value) { + // Storing passwords in plaintext in the database is terrible. + // Hashing the value with an appropriate cryptographic hash function is better. + // Using the username as a salt is better. + this.setDataValue('password', hash(this.username + value)); + } + } +}); +``` + +**Note:** The above examples involving password handling, although much better than simply storing the password in plaintext, are far from perfect security. Handling passwords properly is hard, everything here is just for the sake of an example to show Sequelize functionality. We suggest involving a cybersecurity expert and/or reading [OWASP](https://www.owasp.org/) documents and/or visiting the [InfoSec StackExchange](https://security.stackexchange.com/). + +## Combining getters and setters + +Getters and setters can be both defined in the same field. + +For the sake of an example, let's say we are modeling a `Post`, whose `content` is a text of unlimited length. To improve memory usage, let's say we want to store a gzipped version of the content. + +*Note: modern databases should do some compression automatically in these cases. Please note that this is just for the sake of an example.* + +```js +const { gzipSync, gunzipSync } = require('zlib'); + +const Post = sequelize.define('post', { + content: { + type: DataTypes.TEXT, + get() { + const storedValue = this.getDataValue('content'); + const gzippedBuffer = Buffer.from(storedValue, 'base64'); + const unzippedBuffer = gunzipSync(gzippedBuffer); + return unzippedBuffer.toString(); + }, + set(value) { + const gzippedBuffer = gzipSync(value); + this.setDataValue('content', gzippedBuffer.toString('base64')); + } + } +}); +``` + +With the above setup, whenever we try to interact with the `content` field of our `Post` model, Sequelize will automatically handle the custom getter and setter. For example: + +```js +const post = await Post.create({ content: 'Hello everyone!' }); + +console.log(post.content); // 'Hello everyone!' +// Everything is happening under the hood, so we can even forget that the +// content is actually being stored as a gzipped base64 string! + +// However, if we are really curious, we can get the 'raw' data... +console.log(post.getDataValue('content')); +// Output: 'H4sIAAAAAAAACvNIzcnJV0gtSy2qzM9LVQQAUuk9jQ8AAAA=' +``` + +## Virtual fields + +Virtual fields are fields that Sequelize populates under the hood, but in reality they don't even exist in the database. + +For example, let's say we have the `firstName` and `lastName` attributes for a User. + +*Again, this is [only for the sake of an example](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/).* + +It would be nice to have a simple way to obtain the *full name* directly! We can combine the idea of `getters` with the special data type Sequelize provides for this kind of situation: `DataTypes.VIRTUAL`: + +```js +const { DataTypes } = require("sequelize"); + +const User = sequelize.define('user', { + firstName: DataTypes.TEXT, + lastName: DataTypes.TEXT, + fullName: { + type: DataTypes.VIRTUAL, + get() { + return `${this.firstName} ${this.lastName}`; + }, + set(value) { + throw new Error('Do not try to set the `fullName` value!'); + } + } +}); +``` + +The `VIRTUAL` field does not cause a column in the table to exist. In other words, the model above will not have a `fullName` column. However, it will appear to have it! + +```js +const user = await User.create({ firstName: 'John', lastName: 'Doe' }); +console.log(user.fullName); // 'John Doe' +``` + +## `getterMethods` and `setterMethods` + +Sequelize also provides the `getterMethods` and `setterMethods` options in the model definition to specify things that look like, but aren't exactly the same as, virtual attributes. This usage is discouraged and likely to be deprecated in the future (in favor of using virtual attributes directly). + +Example: + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:'); + +const User = sequelize.define('user', { + firstName: DataTypes.STRING, + lastName: DataTypes.STRING +}, { + getterMethods: { + fullName() { + return this.firstName + ' ' + this.lastName; + } + }, + setterMethods: { + fullName(value) { + // Note: this is just for demonstration. + // See: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/ + const names = value.split(' '); + const firstName = names[0]; + const lastName = names.slice(1).join(' '); + this.setDataValue('firstName', firstName); + this.setDataValue('lastName', lastName); + } + } +}); + +(async () => { + await sequelize.sync(); + let user = await User.create({ firstName: 'John', lastName: 'Doe' }); + console.log(user.fullName); // 'John Doe' + user.fullName = 'Someone Else'; + await user.save(); + user = await User.findOne(); + console.log(user.firstName); // 'Someone' + console.log(user.lastName); // 'Else' +})(); +``` \ No newline at end of file diff --git a/docs/manual/core-concepts/getting-started.md b/docs/manual/core-concepts/getting-started.md new file mode 100644 index 000000000000..94bd14933049 --- /dev/null +++ b/docs/manual/core-concepts/getting-started.md @@ -0,0 +1,111 @@ +# Getting Started + +In this tutorial you will learn to make a simple setup of Sequelize. + +## Installing + +Sequelize is available via [npm](https://www.npmjs.com/package/sequelize) (or [yarn](https://yarnpkg.com/package/sequelize)). + +```sh +npm install --save sequelize +``` + +You'll also have to manually install the driver for your database of choice: + +```sh +# One of the following: +$ npm install --save pg pg-hstore # Postgres +$ npm install --save mysql2 +$ npm install --save mariadb +$ npm install --save sqlite3 +$ npm install --save tedious # Microsoft SQL Server +``` + +## Connecting to a database + +To connect to the database, you must create a Sequelize instance. This can be done by either passing the connection parameters separately to the Sequelize constructor or by passing a single connection URI: + +```js +const { Sequelize } = require('sequelize'); + +// Option 1: Passing a connection URI +const sequelize = new Sequelize('sqlite::memory:') // Example for sqlite +const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Example for postgres + +// Option 2: Passing parameters separately (sqlite) +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: 'path/to/database.sqlite' +}); + +// Option 3: Passing parameters separately (other dialects) +const sequelize = new Sequelize('database', 'username', 'password', { + host: 'localhost', + dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ +}); +``` + +The Sequelize constructor accepts a lot of options. They are documented in the [API Reference](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). + +### Testing the connection + +You can use the `.authenticate()` function to test if the connection is OK: + +```js +try { + await sequelize.authenticate(); + console.log('Connection has been established successfully.'); +} catch (error) { + console.error('Unable to connect to the database:', error); +} +``` + +### Closing the connection + +Sequelize will keep the connection open by default, and use the same connection for all queries. If you need to close the connection, call `sequelize.close()` (which is asynchronous and returns a Promise). + +## Terminology convention + +Observe that, in the examples above, `Sequelize` refers to the library itself while `sequelize` refers to an instance of Sequelize, which represents a connection to one database. This is the recommended convention and it will be followed throughout the documentation. + +## Tip for reading the docs + +You are encouraged to run code examples locally while reading the Sequelize docs. This will help you learn faster. The easiest way to do this is using the SQLite dialect: + +```js +const { Sequelize, Op, Model, DataTypes } = require("sequelize"); +const sequelize = new Sequelize("sqlite::memory:"); + +// Code here! It works! +``` + +To experiment with the other dialects, which are harder to setup locally, you can use the [Sequelize SSCCE](https://github.com/papb/sequelize-sscce) GitHub repository, which allows you to run code on all supported dialects directly from GitHub, for free, without any setup! + +## New databases versus existing databases + +If you are starting a project from scratch, and your database is still empty, Sequelize can be used since the beginning in order to automate the creation of every table in your database. + +Also, if you want to use Sequelize to connect to a database that is already filled with tables and data, that works as well! Sequelize has got you covered in both cases. + +## Logging + +By default, Sequelize will log to console every SQL query it performs. The `options.logging` option can be used to customize this behavior, by defining the function that gets executed every time Sequelize would log something. The default value is `console.log` and when using that only the first log parameter of log function call is displayed. For example, for query logging the first parameter is the raw query and the second (hidden by default) is the Sequelize object. + +Common useful values for `options.logging`: + +```js +const sequelize = new Sequelize('sqlite::memory:', { + // Choose one of the logging options + logging: console.log, // Default, displays the first parameter of the log function call + logging: (...msg) => console.log(msg), // Displays all log function call parameters + logging: false, // Disables logging + logging: msg => logger.debug(msg), // Use custom logger (e.g. Winston or Bunyan), displays the first parameter + logging: logger.debug.bind(logger) // Alternative way to use custom logger, displays all messages +}); +``` + +## Promises and async/await + +Most of the methods provided by Sequelize are asynchronous and therefore return Promises. They are all [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) , so you can use the Promise API (for example, using `then`, `catch`, `finally`) out of the box. + +Of course, using `async` and `await` works normally as well. diff --git a/docs/manual/core-concepts/model-basics.md b/docs/manual/core-concepts/model-basics.md new file mode 100644 index 000000000000..ca2733f6d7d9 --- /dev/null +++ b/docs/manual/core-concepts/model-basics.md @@ -0,0 +1,437 @@ +# Model Basics + +In this tutorial you will learn what models are in Sequelize and how to use them. + +## Concept + +Models are the essence of Sequelize. A model is an abstraction that represents a table in your database. In Sequelize, it is a class that extends [Model](../class/lib/model.js~Model.html). + +The model tells Sequelize several things about the entity it represents, such as the name of the table in the database and which columns it has (and their data types). + +A model in Sequelize has a name. This name does not have to be the same name of the table it represents in the database. Usually, models have singular names (such as `User`) while tables have pluralized names (such as `Users`), although this is fully configurable. + +## Model Definition + +Models can be defined in two equivalent ways in Sequelize: + +* Calling [`sequelize.define(modelName, attributes, options)`](../class/lib/sequelize.js~Sequelize.html#instance-method-define) +* Extending [Model](../class/lib/model.js~Model.html) and calling [`init(attributes, options)`](../class/lib/model.js~Model.html#static-method-init) + +After a model is defined, it is available within `sequelize.models` by its model name. + +To learn with an example, we will consider that we want to create a model to represent users, which have a `firstName` and a `lastName`. We want our model to be called `User`, and the table it represents is called `Users` in the database. + +Both ways to define this model are shown below. After being defined, we can access our model with `sequelize.models.User`. + +### Using [`sequelize.define`](../class/lib/sequelize.js~Sequelize.html#instance-method-define): + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:'); + +const User = sequelize.define('User', { + // Model attributes are defined here + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING + // allowNull defaults to true + } +}, { + // Other model options go here +}); + +// `sequelize.define` also returns the model +console.log(User === sequelize.models.User); // true +``` + +### Extending [Model](../class/lib/model.js~Model.html) + +```js +const { Sequelize, DataTypes, Model } = require('sequelize'); +const sequelize = new Sequelize('sqlite::memory:'); + +class User extends Model {} + +User.init({ + // Model attributes are defined here + firstName: { + type: DataTypes.STRING, + allowNull: false + }, + lastName: { + type: DataTypes.STRING + // allowNull defaults to true + } +}, { + // Other model options go here + sequelize, // We need to pass the connection instance + modelName: 'User' // We need to choose the model name +}); + +// the defined model is the class itself +console.log(User === sequelize.models.User); // true +``` + +Internally, `sequelize.define` calls `Model.init`, so both approaches are essentially equivalent. + +## Table name inference + +Observe that, in both methods above, the table name (`Users`) was never explicitly defined. However, the model name was given (`User`). + +By default, when the table name is not given, Sequelize automatically pluralizes the model name and uses that as the table name. This pluralization is done under the hood by a library called [inflection](https://www.npmjs.com/package/inflection), so that irregular plurals (such as `person -> people`) are computed correctly. + +Of course, this behavior is easily configurable. + +### Enforcing the table name to be equal to the model name + +You can stop the auto-pluralization performed by Sequelize using the `freezeTableName: true` option. This way, Sequelize will infer the table name to be equal to the model name, without any modifications: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + freezeTableName: true +}); +``` + +The example above will create a model named `User` pointing to a table also named `User`. + +This behavior can also be defined globally for the sequelize instance, when it is created: + +```js +const sequelize = new Sequelize('sqlite::memory:', { + define: { + freezeTableName: true + } +}); +``` + +This way, all tables will use the same name as the model name. + +### Providing the table name directly + +You can simply tell Sequelize the name of the table directly as well: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + tableName: 'Employees' +}); +``` + +## Model synchronization + +When you define a model, you're telling Sequelize a few things about its table in the database. However, what if the table actually doesn't even exist in the database? What if it exists, but it has different columns, less columns, or any other difference? + +This is where model synchronization comes in. A model can be synchronized with the database by calling [`model.sync(options)`](https://sequelize.org/master/class/lib/model.js~Model.html#static-method-sync), an asynchronous function (that returns a Promise). With this call, Sequelize will automatically perform an SQL query to the database. Note that this changes only the table in the database, not the model in the JavaScript side. + +* `User.sync()` - This creates the table if it doesn't exist (and does nothing if it already exists) +* `User.sync({ force: true })` - This creates the table, dropping it first if it already existed +* `User.sync({ alter: true })` - This checks what is the current state of the table in the database (which columns it has, what are their data types, etc), and then performs the necessary changes in the table to make it match the model. + +Example: + +```js +await User.sync({ force: true }); +console.log("The table for the User model was just (re)created!"); +``` + +### Synchronizing all models at once + +You can use [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync) to automatically synchronize all models. Example: + +```js +await sequelize.sync({ force: true }); +console.log("All models were synchronized successfully."); +``` + +### Dropping tables + +To drop the table related to a model: + +```js +await User.drop(); +console.log("User table dropped!"); +``` + +To drop all tables: + +```js +await sequelize.drop(); +console.log("All tables dropped!"); +``` + +### Database safety check + +As shown above, the `sync` and `drop` operations are destructive. Sequelize accepts a `match` option as an additional safety check, which receives a RegExp: + +```js +// This will run .sync() only if database name ends with '_test' +sequelize.sync({ force: true, match: /_test$/ }); +``` + +### Synchronization in production + +As shown above, `sync({ force: true })` and `sync({ alter: true })` can be destructive operations. Therefore, they are not recommended for production-level software. Instead, synchronization should be done with the advanced concept of [Migrations](migrations.html), with the help of the [Sequelize CLI](https://github.com/sequelize/cli). + +## Timestamps + +By default, Sequelize automatically adds the fields `createdAt` and `updatedAt` to every model, using the data type `DataTypes.DATE`. Those fields are automatically managed as well - whenever you use Sequelize to create or update something, those fields will be set correctly. The `createdAt` field will contain the timestamp representing the moment of creation, and the `updatedAt` will contain the timestamp of the latest update. + +**Note:** This is done in the Sequelize level (i.e. not done with *SQL triggers*). This means that direct SQL queries (for example queries performed without Sequelize by any other means) will not cause these fields to be updated automatically. + +This behavior can be disabled for a model with the `timestamps: false` option: + +```js +sequelize.define('User', { + // ... (attributes) +}, { + timestamps: false +}); +``` + +It is also possible to enable only one of `createdAt`/`updatedAt`, and to provide a custom name for these columns: + +```js +class Foo extends Model {} +Foo.init({ /* attributes */ }, { + sequelize, + + // don't forget to enable timestamps! + timestamps: true, + + // I don't want createdAt + createdAt: false, + + // I want updatedAt to actually be called updateTimestamp + updatedAt: 'updateTimestamp' +}); +``` + +## Column declaration shorthand syntax + +If the only thing being specified about a column is its data type, the syntax can be shortened: + +```js +// This: +sequelize.define('User', { + name: { + type: DataTypes.STRING + } +}); + +// Can be simplified to: +sequelize.define('User', { name: DataTypes.STRING }); +``` + +## Default Values + +By default, Sequelize assumes that the default value of a column is `NULL`. This behavior can be changed by passing a specific `defaultValue` to the column definition: + +```js +sequelize.define('User', { + name: { + type: DataTypes.STRING, + defaultValue: "John Doe" + } +}); +``` + +Some special values, such as `DataTypes.NOW`, are also accepted: + +```js +sequelize.define('Foo', { + bar: { + type: DataTypes.DATETIME, + defaultValue: DataTypes.NOW + // This way, the current date/time will be used to populate this column (at the moment of insertion) + } +}); +``` + +## Data Types + +Every column you define in your model must have a data type. Sequelize provides [a lot of built-in data types](https://github.com/sequelize/sequelize/blob/main/lib/data-types.js). To access a built-in data type, you must import `DataTypes`: + +```js +const { DataTypes } = require("sequelize"); // Import the built-in data types +``` + +### Strings + +```js +DataTypes.STRING // VARCHAR(255) +DataTypes.STRING(1234) // VARCHAR(1234) +DataTypes.STRING.BINARY // VARCHAR BINARY +DataTypes.TEXT // TEXT +DataTypes.TEXT('tiny') // TINYTEXT +DataTypes.CITEXT // CITEXT PostgreSQL and SQLite only. +DataTypes.TSVECTOR // TSVECTOR PostgreSQL only. +``` + +### Boolean + +```js +DataTypes.BOOLEAN // TINYINT(1) +``` + +### Numbers + +```js +DataTypes.INTEGER // INTEGER +DataTypes.BIGINT // BIGINT +DataTypes.BIGINT(11) // BIGINT(11) + +DataTypes.FLOAT // FLOAT +DataTypes.FLOAT(11) // FLOAT(11) +DataTypes.FLOAT(11, 10) // FLOAT(11,10) + +DataTypes.REAL // REAL PostgreSQL only. +DataTypes.REAL(11) // REAL(11) PostgreSQL only. +DataTypes.REAL(11, 12) // REAL(11,12) PostgreSQL only. + +DataTypes.DOUBLE // DOUBLE +DataTypes.DOUBLE(11) // DOUBLE(11) +DataTypes.DOUBLE(11, 10) // DOUBLE(11,10) + +DataTypes.DECIMAL // DECIMAL +DataTypes.DECIMAL(10, 2) // DECIMAL(10,2) +``` + +#### Unsigned & Zerofill integers - MySQL/MariaDB only + +In MySQL and MariaDB, the data types `INTEGER`, `BIGINT`, `FLOAT` and `DOUBLE` can be set as unsigned or zerofill (or both), as follows: + +```js +DataTypes.INTEGER.UNSIGNED +DataTypes.INTEGER.ZEROFILL +DataTypes.INTEGER.UNSIGNED.ZEROFILL +// You can also specify the size i.e. INTEGER(10) instead of simply INTEGER +// Same for BIGINT, FLOAT and DOUBLE +``` + +### Dates + +```js +DataTypes.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres +DataTypes.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision +DataTypes.DATEONLY // DATE without time +``` + +### UUIDs + +For UUIDs, use `DataTypes.UUID`. It becomes the `UUID` data type for PostgreSQL and SQLite, and `CHAR(36)` for MySQL. Sequelize can generate UUIDs automatically for these fields, simply use `DataTypes.UUIDV1` or `DataTypes.UUIDV4` as the default value: + +```js +{ + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4 // Or DataTypes.UUIDV1 +} +``` + +### Others + +There are other data types, covered in a [separate guide](other-data-types.html). + +## Column Options + +When defining a column, apart from specifying the `type` of the column, and the `allowNull` and `defaultValue` options mentioned above, there are a lot more options that can be used. Some examples are below. + +```js +const { Model, DataTypes, Deferrable } = require("sequelize"); + +class Foo extends Model {} +Foo.init({ + // instantiating will automatically set the flag to true if not set + flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, + + // default values for dates => current time + myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }, + + // setting allowNull to false will add NOT NULL to the column, which means an error will be + // thrown from the DB when the query is executed if the column is null. If you want to check that a value + // is not null before querying the DB, look at the validations section below. + title: { type: DataTypes.STRING, allowNull: false }, + + // Creating two objects with the same value will throw an error. The unique property can be either a + // boolean, or a string. If you provide the same string for multiple columns, they will form a + // composite unique key. + uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' }, + uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' }, + + // The unique property is simply a shorthand to create a unique constraint. + someUnique: { type: DataTypes.STRING, unique: true }, + + // Go on reading for further information about primary keys + identifier: { type: DataTypes.STRING, primaryKey: true }, + + // autoIncrement can be used to create auto_incrementing integer columns + incrementMe: { type: DataTypes.INTEGER, autoIncrement: true }, + + // You can specify a custom column name via the 'field' attribute: + fieldWithUnderscores: { type: DataTypes.STRING, field: 'field_with_underscores' }, + + // It is possible to create foreign keys: + bar_id: { + type: DataTypes.INTEGER, + + references: { + // This is a reference to another model + model: Bar, + + // This is the column name of the referenced model + key: 'id', + + // With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type. + deferrable: Deferrable.INITIALLY_IMMEDIATE + // Options: + // - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints + // - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction + // - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction + } + }, + + // Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL + commentMe: { + type: DataTypes.INTEGER, + comment: 'This is a column name that has a comment' + } +}, { + sequelize, + modelName: 'foo', + + // Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options: + indexes: [{ unique: true, fields: ['someUnique'] }] +}); +``` + +## Taking advantage of Models being classes + +The Sequelize models are [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). You can very easily add custom instance or class level methods. + +```js +class User extends Model { + static classLevelMethod() { + return 'foo'; + } + instanceLevelMethod() { + return 'bar'; + } + getFullname() { + return [this.firstname, this.lastname].join(' '); + } +} +User.init({ + firstname: Sequelize.TEXT, + lastname: Sequelize.TEXT +}, { sequelize }); + +console.log(User.classLevelMethod()); // 'foo' +const user = User.build({ firstname: 'Jane', lastname: 'Doe' }); +console.log(user.instanceLevelMethod()); // 'bar' +console.log(user.getFullname()); // 'Jane Doe' +``` diff --git a/docs/manual/core-concepts/model-instances.md b/docs/manual/core-concepts/model-instances.md new file mode 100644 index 000000000000..8c47810ddffb --- /dev/null +++ b/docs/manual/core-concepts/model-instances.md @@ -0,0 +1,197 @@ +# Model Instances + +As you already know, a model is an [ES6 class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). An instance of the class represents one object from that model (which maps to one row of the table in the database). This way, model instances are [DAOs](https://en.wikipedia.org/wiki/Data_access_object). + +For this guide, the following setup will be assumed: + +```js +const { Sequelize, Model, DataTypes } = require("sequelize"); +const sequelize = new Sequelize("sqlite::memory:"); + +const User = sequelize.define("user", { + name: DataTypes.TEXT, + favoriteColor: { + type: DataTypes.TEXT, + defaultValue: 'green' + }, + age: DataTypes.INTEGER, + cash: DataTypes.INTEGER +}); + +(async () => { + await sequelize.sync({ force: true }); + // Code here +})(); +``` + +## Creating an instance + +Although a model is a class, you should not create instances by using the `new` operator directly. Instead, the [`build`](../class/lib/model.js~Model.html#static-method-build) method should be used: + +```js +const jane = User.build({ name: "Jane" }); +console.log(jane instanceof User); // true +console.log(jane.name); // "Jane" +``` + +However, the code above does not communicate with the database at all (note that it is not even asynchronous)! This is because the [`build`](../class/lib/model.js~Model.html#static-method-build) method only creates an object that *represents* data that *can* be mapped to a database. In order to really save (i.e. persist) this instance in the database, the [`save`](../class/lib/model.js~Model.html#instance-method-save) method should be used: + +```js +await jane.save(); +console.log('Jane was saved to the database!'); +``` + +Note, from the usage of `await` in the snippet above, that `save` is an asynchronous method. In fact, almost every Sequelize method is asynchronous; `build` is one of the very few exceptions. + +### A very useful shortcut: the `create` method + +Sequelize provides the [`create`](../class/lib/model.js~Model.html#static-method-create) method, which combines the `build` and `save` methods shown above into a single method: + +```js +const jane = await User.create({ name: "Jane" }); +// Jane exists in the database now! +console.log(jane instanceof User); // true +console.log(jane.name); // "Jane" +``` + +## Note: logging instances + +Trying to log a model instance directly to `console.log` will produce a lot of clutter, since Sequelize instances have a lot of things attached to them. Instead, you can use the `.toJSON()` method (which, by the way, automatically guarantees the instances to be `JSON.stringify`-ed well). + +```js +const jane = await User.create({ name: "Jane" }); +// console.log(jane); // Don't do this +console.log(jane.toJSON()); // This is good! +console.log(JSON.stringify(jane, null, 4)); // This is also good! +``` + +## Default values + +Built instances will automatically get default values: + +```js +const jane = User.build({ name: "Jane" }); +console.log(jane.favoriteColor); // "green" +``` + +## Updating an instance + +If you change the value of some field of an instance, calling `save` again will update it accordingly: + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +jane.name = "Ada"; +// the name is still "Jane" in the database +await jane.save(); +// Now the name was updated to "Ada" in the database! +``` + +You can update several fields at once with the [`set`](../class/lib/model.js~Model.html#instance-method-set) method: + +```js +const jane = await User.create({ name: "Jane" }); + +jane.set({ + name: "Ada", + favoriteColor: "blue" +}); +// As above, the database still has "Jane" and "green" +await jane.save(); +// The database now has "Ada" and "blue" for name and favorite color +``` + +Note that the `save()` here will also persist any other changes that have been made on this instance, not just those in the previous `set` call. If you want to update a specific set of fields, you can use [`update`](../class/lib/model.js~Model.html#instance-method-update): + +```js +const jane = await User.create({ name: "Jane" }); +jane.favoriteColor = "blue" +await jane.update({ name: "Ada" }) +// The database now has "Ada" for name, but still has the default "green" for favorite color +await jane.save() +// Now the database has "Ada" for name and "blue" for favorite color +``` + +## Deleting an instance + +You can delete an instance by calling [`destroy`](../class/lib/model.js~Model.html#instance-method-destroy): + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +await jane.destroy(); +// Now this entry was removed from the database +``` + +## Reloading an instance + +You can reload an instance from the database by calling [`reload`](../class/lib/model.js~Model.html#instance-method-reload): + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +jane.name = "Ada"; +// the name is still "Jane" in the database +await jane.reload(); +console.log(jane.name); // "Jane" +``` + +The reload call generates a `SELECT` query to get the up-to-date data from the database. + +## Saving only some fields + +It is possible to define which attributes should be saved when calling `save`, by passing an array of column names. + +This is useful when you set attributes based on a previously defined object, for example, when you get the values of an object via a form of a web app. Furthermore, this is used internally in the `update` implementation. This is how it looks like: + +```js +const jane = await User.create({ name: "Jane" }); +console.log(jane.name); // "Jane" +console.log(jane.favoriteColor); // "green" +jane.name = "Jane II"; +jane.favoriteColor = "blue"; +await jane.save({ fields: ['name'] }); +console.log(jane.name); // "Jane II" +console.log(jane.favoriteColor); // "blue" +// The above printed blue because the local object has it set to blue, but +// in the database it is still "green": +await jane.reload(); +console.log(jane.name); // "Jane II" +console.log(jane.favoriteColor); // "green" +``` + +## Change-awareness of save + +The `save` method is optimized internally to only update fields that really changed. This means that if you don't change anything and call `save`, Sequelize will know that the save is superfluous and do nothing, i.e., no query will be generated (it will still return a Promise, but it will resolve immediately). + +Also, if only a few attributes have changed when you call `save`, only those fields will be sent in the `UPDATE` query, to improve performance. + +## Incrementing and decrementing integer values + +In order to increment/decrement values of an instance without running into concurrency issues, Sequelize provides the [`increment`](../class/lib/model.js~Model.html#instance-method-increment) and [`decrement`](../class/lib/model.js~Model.html#instance-method-decrement) instance methods. + +```js +const jane = await User.create({ name: "Jane", age: 100 }); +const incrementResult = await jane.increment('age', { by: 2 }); +// Note: to increment by 1 you can omit the `by` option and just do `user.increment('age')` + +// In PostgreSQL, `incrementResult` will be the updated user, unless the option +// `{ returning: false }` was set (and then it will be undefined). + +// In other dialects, `incrementResult` will be undefined. If you need the updated instance, you will have to call `user.reload()`. +``` + +You can also increment multiple fields at once: + +```js +const jane = await User.create({ name: "Jane", age: 100, cash: 5000 }); +await jane.increment({ + 'age': 2, + 'cash': 100 +}); + +// If the values are incremented by the same amount, you can use this other syntax as well: +await jane.increment(['age', 'cash'], { by: 2 }); +``` + +Decrementing works in the exact same way. diff --git a/docs/manual/core-concepts/model-querying-basics.md b/docs/manual/core-concepts/model-querying-basics.md new file mode 100644 index 000000000000..91a890208982 --- /dev/null +++ b/docs/manual/core-concepts/model-querying-basics.md @@ -0,0 +1,712 @@ +# Model Querying - Basics + +Sequelize provides various methods to assist querying your database for data. + +*Important notice: to perform production-ready queries with Sequelize, make sure you have read the [Transactions guide](transactions.html) as well. Transactions are important to ensure data integrity and to provide other benefits.* + +This guide will show how to make the standard [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) queries. + +## Simple INSERT queries + +First, a simple example: + +```js +// Create a new user +const jane = await User.create({ firstName: "Jane", lastName: "Doe" }); +console.log("Jane's auto-generated ID:", jane.id); +``` + +The [`Model.create()`](../class/lib/model.js~Model.html#static-method-create) method is a shorthand for building an unsaved instance with [`Model.build()`](../class/lib/model.js~Model.html#static-method-build) and saving the instance with [`instance.save()`](../class/lib/model.js~Model.html#instance-method-save). + +It is also possible to define which attributes can be set in the `create` method. This can be especially useful if you create database entries based on a form which can be filled by a user. Using that would, for example, allow you to restrict the `User` model to set only an username but not an admin flag (i.e., `isAdmin`): + +```js +const user = await User.create({ + username: 'alice123', + isAdmin: true +}, { fields: ['username'] }); +// let's assume the default of isAdmin is false +console.log(user.username); // 'alice123' +console.log(user.isAdmin); // false +``` + +## Simple SELECT queries + +You can read the whole table from the database with the [`findAll`](../class/lib/model.js~Model.html#static-method-findAll) method: + +```js +// Find all users +const users = await User.findAll(); +console.log(users.every(user => user instanceof User)); // true +console.log("All users:", JSON.stringify(users, null, 2)); +``` + +```sql +SELECT * FROM ... +``` + +## Specifying attributes for SELECT queries + +To select only some attributes, you can use the `attributes` option: + +```js +Model.findAll({ + attributes: ['foo', 'bar'] +}); +``` + +```sql +SELECT foo, bar FROM ... +``` + +Attributes can be renamed using a nested array: + +```js +Model.findAll({ + attributes: ['foo', ['bar', 'baz'], 'qux'] +}); +``` + +```sql +SELECT foo, bar AS baz, qux FROM ... +``` + +You can use [`sequelize.fn`](../class/lib/sequelize.js~Sequelize.html#static-method-fn) to do aggregations: + +```js +Model.findAll({ + attributes: [ + 'foo', + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'], + 'bar' + ] +}); +``` + +```sql +SELECT foo, COUNT(hats) AS n_hats, bar FROM ... +``` + +When using aggregation function, you must give it an alias to be able to access it from the model. In the example above you can get the number of hats with `instance.n_hats`. + +Sometimes it may be tiresome to list all the attributes of the model if you only want to add an aggregation: + +```js +// This is a tiresome way of getting the number of hats (along with every column) +Model.findAll({ + attributes: [ + 'id', 'foo', 'bar', 'baz', 'qux', 'hats', // We had to list all attributes... + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'] // To add the aggregation... + ] +}); + +// This is shorter, and less error prone because it still works if you add / remove attributes from your model later +Model.findAll({ + attributes: { + include: [ + [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'] + ] + } +}); +``` + +```sql +SELECT id, foo, bar, baz, qux, hats, COUNT(hats) AS n_hats FROM ... +``` + +Similarly, it's also possible to remove a selected few attributes: + +```js +Model.findAll({ + attributes: { exclude: ['baz'] } +}); +``` + +```sql +-- Assuming all columns are 'id', 'foo', 'bar', 'baz' and 'qux' +SELECT id, foo, bar, qux FROM ... +``` + +## Applying WHERE clauses + +The `where` option is used to filter the query. There are lots of operators to use for the `where` clause, available as Symbols from [`Op`](../variable/index.html#static-variable-Op). + +### The basics + +```js +Post.findAll({ + where: { + authorId: 2 + } +}); +// SELECT * FROM post WHERE authorId = 2; +``` + +Observe that no operator (from `Op`) was explicitly passed, so Sequelize assumed an equality comparison by default. The above code is equivalent to: + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + authorId: { + [Op.eq]: 2 + } + } +}); +// SELECT * FROM post WHERE authorId = 2; +``` + +Multiple checks can be passed: + +```js +Post.findAll({ + where: { + authorId: 12, + status: 'active' + } +}); +// SELECT * FROM post WHERE authorId = 12 AND status = 'active'; +``` + +Just like Sequelize inferred the `Op.eq` operator in the first example, here Sequelize inferred that the caller wanted an `AND` for the two checks. The code above is equivalent to: + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + [Op.and]: [ + { authorId: 12 }, + { status: 'active' } + ] + } +}); +// SELECT * FROM post WHERE authorId = 12 AND status = 'active'; +``` + +An `OR` can be easily performed in a similar way: + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + [Op.or]: [ + { authorId: 12 }, + { authorId: 13 } + ] + } +}); +// SELECT * FROM post WHERE authorId = 12 OR authorId = 13; +``` + +Since the above was an `OR` involving the same field, Sequelize allows you to use a slightly different structure which is more readable and generates the same behavior: + +```js +const { Op } = require("sequelize"); +Post.destroy({ + where: { + authorId: { + [Op.or]: [12, 13] + } + } +}); +// DELETE FROM post WHERE authorId = 12 OR authorId = 13; +``` + +### Operators + +Sequelize provides several operators. + +```js +const { Op } = require("sequelize"); +Post.findAll({ + where: { + [Op.and]: [{ a: 5 }, { b: 6 }], // (a = 5) AND (b = 6) + [Op.or]: [{ a: 5 }, { b: 6 }], // (a = 5) OR (b = 6) + someAttribute: { + // Basics + [Op.eq]: 3, // = 3 + [Op.ne]: 20, // != 20 + [Op.is]: null, // IS NULL + [Op.not]: true, // IS NOT TRUE + [Op.or]: [5, 6], // (someAttribute = 5) OR (someAttribute = 6) + + // Using dialect specific column identifiers (PG in the following example): + [Op.col]: 'user.organization_id', // = "user"."organization_id" + + // Number comparisons + [Op.gt]: 6, // > 6 + [Op.gte]: 6, // >= 6 + [Op.lt]: 10, // < 10 + [Op.lte]: 10, // <= 10 + [Op.between]: [6, 10], // BETWEEN 6 AND 10 + [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 + + // Other operators + + [Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1) + + [Op.in]: [1, 2], // IN [1, 2] + [Op.notIn]: [1, 2], // NOT IN [1, 2] + + [Op.like]: '%hat', // LIKE '%hat' + [Op.notLike]: '%hat', // NOT LIKE '%hat' + [Op.startsWith]: 'hat', // LIKE 'hat%' + [Op.endsWith]: 'hat', // LIKE '%hat' + [Op.substring]: 'hat', // LIKE '%hat%' + [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) + [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) + [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) + [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) + [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) + [Op.notIRegexp]: '^[h|a|t]', // !~* '^[h|a|t]' (PG only) + + [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // match text search for strings 'fat' and 'rat' (PG only) + + // In Postgres, Op.like/Op.iLike/Op.notLike can be combined to Op.any: + [Op.like]: { [Op.any]: ['cat', 'hat'] } // LIKE ANY ARRAY['cat', 'hat'] + + // There are more postgres-only range operators, see below + } + } +}); +``` + +#### Shorthand syntax for `Op.in` + +Passing an array directly to the `where` option will implicitly use the `IN` operator: + +```js +Post.findAll({ + where: { + id: [1,2,3] // Same as using `id: { [Op.in]: [1,2,3] }` + } +}); +// SELECT ... FROM "posts" AS "post" WHERE "post"."id" IN (1, 2, 3); +``` + +### Logical combinations with operators + +The operators `Op.and`, `Op.or` and `Op.not` can be used to create arbitrarily complex nested logical comparisons. + +#### Examples with `Op.and` and `Op.or` + +```js +const { Op } = require("sequelize"); + +Foo.findAll({ + where: { + rank: { + [Op.or]: { + [Op.lt]: 1000, + [Op.eq]: null + } + }, + // rank < 1000 OR rank IS NULL + + { + createdAt: { + [Op.lt]: new Date(), + [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000) + } + }, + // createdAt < [timestamp] AND createdAt > [timestamp] + + { + [Op.or]: [ + { + title: { + [Op.like]: 'Boat%' + } + }, + { + description: { + [Op.like]: '%boat%' + } + } + ] + } + // title LIKE 'Boat%' OR description LIKE '%boat%' + } +}); +``` + +#### Examples with `Op.not` + +```js +Project.findAll({ + where: { + name: 'Some Project', + [Op.not]: [ + { id: [1,2,3] }, + { + description: { + [Op.like]: 'Hello%' + } + } + ] + } +}); +``` + +The above will generate: + +```sql +SELECT * +FROM `Projects` +WHERE ( + `Projects`.`name` = 'Some Project' + AND NOT ( + `Projects`.`id` IN (1,2,3) + AND + `Projects`.`description` LIKE 'Hello%' + ) +) +``` + +### Advanced queries with functions (not just columns) + +What if you wanted to obtain something like `WHERE char_length("content") = 7`? + +```js +Post.findAll({ + where: sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7) +}); +// SELECT ... FROM "posts" AS "post" WHERE char_length("content") = 7 +``` + +Note the usage of the [`sequelize.fn`](../class/lib/sequelize.js~Sequelize.html#static-method-fn) and [`sequelize.col`](../class/lib/sequelize.js~Sequelize.html#static-method-col) methods, which should be used to specify an SQL function call and a table column, respectively. These methods should be used instead of passing a plain string (such as `char_length(content)`) because Sequelize needs to treat this situation differently (for example, using other symbol escaping approaches). + +What if you need something even more complex? + +```js +Post.findAll({ + where: { + [Op.or]: [ + sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7), + { + content: { + [Op.like]: 'Hello%' + } + }, + { + [Op.and]: [ + { status: 'draft' }, + sequelize.where(sequelize.fn('char_length', sequelize.col('content')), { + [Op.gt]: 10 + }) + ] + } + ] + } +}); +``` + +The above generates the following SQL: + +```sql +SELECT + ... +FROM "posts" AS "post" +WHERE ( + char_length("content") = 7 + OR + "post"."content" LIKE 'Hello%' + OR ( + "post"."status" = 'draft' + AND + char_length("content") > 10 + ) +) +``` + +### Postgres-only Range Operators + +Range types can be queried with all supported operators. + +Keep in mind, the provided range value can [define the bound inclusion/exclusion](data-types.html#range-types) as well. + +```js +[Op.contains]: 2, // @> '2'::integer (PG range contains element operator) +[Op.contains]: [1, 2], // @> [1, 2) (PG range contains range operator) +[Op.contained]: [1, 2], // <@ [1, 2) (PG range is contained by operator) +[Op.overlap]: [1, 2], // && [1, 2) (PG range overlap (have points in common) operator) +[Op.adjacent]: [1, 2], // -|- [1, 2) (PG range is adjacent to operator) +[Op.strictLeft]: [1, 2], // << [1, 2) (PG range strictly left of operator) +[Op.strictRight]: [1, 2], // >> [1, 2) (PG range strictly right of operator) +[Op.noExtendRight]: [1, 2], // &< [1, 2) (PG range does not extend to the right of operator) +[Op.noExtendLeft]: [1, 2], // &> [1, 2) (PG range does not extend to the left of operator) +``` + +### Deprecated: Operator Aliases + +In Sequelize v4, it was possible to specify strings to refer to operators, instead of using Symbols. This is now deprecated and heavily discouraged, and will probably be removed in the next major version. If you really need it, you can pass the `operatorAliases` option in the Sequelize constructor. + +For example: + +```js +const { Sequelize, Op } = require("sequelize"); +const sequelize = new Sequelize('sqlite::memory:', { + operatorsAliases: { + $gt: Op.gt + } +}); + +// Now we can use `$gt` instead of `[Op.gt]` in where clauses: +Foo.findAll({ + where: { + $gt: 6 // Works like using [Op.gt] + } +}); +``` + +## Simple UPDATE queries + +Update queries also accept the `where` option, just like the read queries shown above. + +```js +// Change everyone without a last name to "Doe" +await User.update({ lastName: "Doe" }, { + where: { + lastName: null + } +}); +``` + +## Simple DELETE queries + +Delete queries also accept the `where` option, just like the read queries shown above. + +```js +// Delete everyone named "Jane" +await User.destroy({ + where: { + firstName: "Jane" + } +}); +``` + +To destroy everything the `TRUNCATE` SQL can be used: + +```js +// Truncate the table +await User.destroy({ + truncate: true +}); +``` + +## Creating in bulk + +Sequelize provides the `Model.bulkCreate` method to allow creating multiple records at once, with only one query. + +The usage of `Model.bulkCreate` is very similar to `Model.create`, by receiving an array of objects instead of a single object. + +```js +const captains = await Captain.bulkCreate([ + { name: 'Jack Sparrow' }, + { name: 'Davy Jones' } +]); +console.log(captains.length); // 2 +console.log(captains[0] instanceof Captain); // true +console.log(captains[0].name); // 'Jack Sparrow' +console.log(captains[0].id); // 1 // (or another auto-generated value) +``` + +However, by default, `bulkCreate` does not run validations on each object that is going to be created (which `create` does). To make `bulkCreate` run these validations as well, you must pass the `validate: true` option. This will decrease performance. Usage example: + +```js +const Foo = sequelize.define('foo', { + bar: { + type: DataTypes.TEXT, + validate: { + len: [4, 6] + } + } +}); + +// This will not throw an error, both instances will be created +await Foo.bulkCreate([ + { name: 'abc123' }, + { name: 'name too long' } +]); + +// This will throw an error, nothing will be created +await Foo.bulkCreate([ + { name: 'abc123' }, + { name: 'name too long' } +], { validate: true }); +``` + +If you are accepting values directly from the user, it might be beneficial to limit the columns that you want to actually insert. To support this, `bulkCreate()` accepts a `fields` option, an array defining which fields must be considered (the rest will be ignored). + +```js +await User.bulkCreate([ + { username: 'foo' }, + { username: 'bar', admin: true } +], { fields: ['username'] }); +// Neither foo nor bar are admins. +``` + +## Ordering and Grouping + +Sequelize provides the `order` and `group` options to work with `ORDER BY` and `GROUP BY`. + +### Ordering + +The `order` option takes an array of items to order the query by or a sequelize method. These *items* are themselves arrays in the form `[column, direction]`. The column will be escaped correctly and the direction will be checked in a whitelist of valid directions (such as `ASC`, `DESC`, `NULLS FIRST`, etc). + +```js +Subtask.findAll({ + order: [ + // Will escape title and validate DESC against a list of valid direction parameters + ['title', 'DESC'], + + // Will order by max(age) + sequelize.fn('max', sequelize.col('age')), + + // Will order by max(age) DESC + [sequelize.fn('max', sequelize.col('age')), 'DESC'], + + // Will order by otherfunction(`col1`, 12, 'lalala') DESC + [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], + + // Will order an associated model's createdAt using the model name as the association's name. + [Task, 'createdAt', 'DESC'], + + // Will order through an associated model's createdAt using the model names as the associations' names. + [Task, Project, 'createdAt', 'DESC'], + + // Will order by an associated model's createdAt using the name of the association. + ['Task', 'createdAt', 'DESC'], + + // Will order by a nested associated model's createdAt using the names of the associations. + ['Task', 'Project', 'createdAt', 'DESC'], + + // Will order by an associated model's createdAt using an association object. (preferred method) + [Subtask.associations.Task, 'createdAt', 'DESC'], + + // Will order by a nested associated model's createdAt using association objects. (preferred method) + [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'], + + // Will order by an associated model's createdAt using a simple association object. + [{model: Task, as: 'Task'}, 'createdAt', 'DESC'], + + // Will order by a nested associated model's createdAt simple association objects. + [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC'] + ], + + // Will order by max age descending + order: sequelize.literal('max(age) DESC'), + + // Will order by max age ascending assuming ascending is the default order when direction is omitted + order: sequelize.fn('max', sequelize.col('age')), + + // Will order by age ascending assuming ascending is the default order when direction is omitted + order: sequelize.col('age'), + + // Will order randomly based on the dialect (instead of fn('RAND') or fn('RANDOM')) + order: sequelize.random() +}); + +Foo.findOne({ + order: [ + // will return `name` + ['name'], + // will return `username` DESC + ['username', 'DESC'], + // will return max(`age`) + sequelize.fn('max', sequelize.col('age')), + // will return max(`age`) DESC + [sequelize.fn('max', sequelize.col('age')), 'DESC'], + // will return otherfunction(`col1`, 12, 'lalala') DESC + [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], + // will return otherfunction(awesomefunction(`col`)) DESC, This nesting is potentially infinite! + [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'] + ] +}); +``` + +To recap, the elements of the order array can be the following: + +* A string (which will be automatically quoted) +* An array, whose first element will be quoted, second will be appended verbatim +* An object with a `raw` field: + * The content of `raw` will be added verbatim without quoting + * Everything else is ignored, and if raw is not set, the query will fail +* A call to `Sequelize.fn` (which will generate a function call in SQL) +* A call to `Sequelize.col` (which will quoute the column name) + +### Grouping + +The syntax for grouping and ordering are equal, except that grouping does not accept a direction as last argument of the array (there is no `ASC`, `DESC`, `NULLS FIRST`, etc). + +You can also pass a string directly to `group`, which will be included directly (verbatim) into the generated SQL. Use with caution and don't use with user generated content. + +```js +Project.findAll({ group: 'name' }); +// yields 'GROUP BY name' +``` + +## Limits and Pagination + +The `limit` and `offset` options allow you to work with limiting / pagination: + +```js +// Fetch 10 instances/rows +Project.findAll({ limit: 10 }); + +// Skip 8 instances/rows +Project.findAll({ offset: 8 }); + +// Skip 5 instances and fetch the 5 after that +Project.findAll({ offset: 5, limit: 5 }); +``` + +Usually these are used alongside the `order` option. + +## Utility methods + +Sequelize also provides a few utility methods. + +### `count` + +The `count` method simply counts the occurrences of elements in the database. + +```js +console.log(`There are ${await Project.count()} projects`); + +const amount = await Project.count({ + where: { + id: { + [Op.gt]: 25 + } + } +}); +console.log(`There are ${amount} projects with an id greater than 25`); +``` + +### `max`, `min` and `sum` + +Sequelize also provides the `max`, `min` and `sum` convenience methods. + +Let's assume we have three users, whose ages are 10, 5, and 40. + +```js +await User.max('age'); // 40 +await User.max('age', { where: { age: { [Op.lt]: 20 } } }); // 10 +await User.min('age'); // 5 +await User.min('age', { where: { age: { [Op.gt]: 5 } } }); // 10 +await User.sum('age'); // 55 +await User.sum('age', { where: { age: { [Op.gt]: 5 } } }); // 50 +``` + +### `increment`, `decrement` + +Sequelize also provides the `increment` convenience method. + +Let's assume we have a user, whose age is 10. + +```js +await User.increment({age: 5}, { where: { id: 1 } }) // Will increase age to 15 +await User.increment({age: -5}, { where: { id: 1 } }) // Will decrease age to 5 +``` diff --git a/docs/manual/core-concepts/model-querying-finders.md b/docs/manual/core-concepts/model-querying-finders.md new file mode 100644 index 000000000000..c5644a9dca57 --- /dev/null +++ b/docs/manual/core-concepts/model-querying-finders.md @@ -0,0 +1,83 @@ +# Model Querying - Finders + +Finder methods are the ones that generate `SELECT` queries. + +By default, the results of all finder methods are instances of the model class (as opposed to being just plain JavaScript objects). This means that after the database returns the results, Sequelize automatically wraps everything in proper instance objects. In a few cases, when there are too many results, this wrapping can be inefficient. To disable this wrapping and receive a plain response instead, pass `{ raw: true }` as an option to the finder method. + +## `findAll` + +The `findAll` method is already known from the previous tutorial. It generates a standard `SELECT` query which will retrieve all entries from the table (unless restricted by something like a `where` clause, for example). + +## `findByPk` + +The `findByPk` method obtains only a single entry from the table, using the provided primary key. + +```js +const project = await Project.findByPk(123); +if (project === null) { + console.log('Not found!'); +} else { + console.log(project instanceof Project); // true + // Its primary key is 123 +} +``` + +## `findOne` + +The `findOne` method obtains the first entry it finds (that fulfills the optional query options, if provided). + +```js +const project = await Project.findOne({ where: { title: 'My Title' } }); +if (project === null) { + console.log('Not found!'); +} else { + console.log(project instanceof Project); // true + console.log(project.title); // 'My Title' +} +``` + +## `findOrCreate` + +The method `findOrCreate` will create an entry in the table unless it can find one fulfilling the query options. In both cases, it will return an instance (either the found instance or the created instance) and a boolean indicating whether that instance was created or already existed. + +The `where` option is considered for finding the entry, and the `defaults` option is used to define what must be created in case nothing was found. If the `defaults` do not contain values for every column, Sequelize will take the values given to `where` (if present). + +Let's assume we have an empty database with a `User` model which has a `username` and a `job`. + +```js +const [user, created] = await User.findOrCreate({ + where: { username: 'sdepold' }, + defaults: { + job: 'Technical Lead JavaScript' + } +}); +console.log(user.username); // 'sdepold' +console.log(user.job); // This may or may not be 'Technical Lead JavaScript' +console.log(created); // The boolean indicating whether this instance was just created +if (created) { + console.log(user.job); // This will certainly be 'Technical Lead JavaScript' +} +``` + +## `findAndCountAll` + +The `findAndCountAll` method is a convenience method that combines `findAll` and `count`. This is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query. + +The `findAndCountAll` method returns an object with two properties: + +* `count` - an integer - the total number records matching the query +* `rows` - an array of objects - the obtained records + +```js +const { count, rows } = await Project.findAndCountAll({ + where: { + title: { + [Op.like]: 'foo%' + } + }, + offset: 10, + limit: 2 +}); +console.log(count); +console.log(rows); +``` \ No newline at end of file diff --git a/docs/manual/core-concepts/paranoid.md b/docs/manual/core-concepts/paranoid.md new file mode 100644 index 000000000000..dd580d0578a9 --- /dev/null +++ b/docs/manual/core-concepts/paranoid.md @@ -0,0 +1,105 @@ +# Paranoid + +Sequelize supports the concept of *paranoid* tables. A *paranoid* table is one that, when told to delete a record, it will not truly delete it. Instead, a special column called `deletedAt` will have its value set to the timestamp of that deletion request. + +This means that paranoid tables perform a *soft-deletion* of records, instead of a *hard-deletion*. + +## Defining a model as paranoid + +To make a model paranoid, you must pass the `paranoid: true` option to the model definition. Paranoid requires timestamps to work (i.e. it won't work if you also pass `timestamps: false`). + +You can also change the default column name (which is `deletedAt`) to something else. + +```js +class Post extends Model {} +Post.init({ /* attributes here */ }, { + sequelize, + paranoid: true, + + // If you want to give a custom name to the deletedAt column + deletedAt: 'destroyTime' +}); +``` + +## Deleting + +When you call the `destroy` method, a soft-deletion will happen: + +```js +await Post.destroy({ + where: { + id: 1 + } +}); +// UPDATE "posts" SET "deletedAt"=[timestamp] WHERE "deletedAt" IS NULL AND "id" = 1 +``` + +If you really want a hard-deletion and your model is paranoid, you can force it using the `force: true` option: + +```js +await Post.destroy({ + where: { + id: 1 + }, + force: true +}); +// DELETE FROM "posts" WHERE "id" = 1 +``` + +The above examples used the static `destroy` method as an example (`Post.destroy`), but everything works in the same way with the instance method: + +```js +const post = await Post.create({ title: 'test' }); +console.log(post instanceof Post); // true +await post.destroy(); // Would just set the `deletedAt` flag +await post.destroy({ force: true }); // Would really delete the record +``` + +## Restoring + +To restore soft-deleted records, you can use the `restore` method, which comes both in the static version as well as in the instance version: + +```js +// Example showing the instance `restore` method +// We create a post, soft-delete it and then restore it back +const post = await Post.create({ title: 'test' }); +console.log(post instanceof Post); // true +await post.destroy(); +console.log('soft-deleted!'); +await post.restore(); +console.log('restored!'); + +// Example showing the static `restore` method. +// Restoring every soft-deleted post with more than 100 likes +await Post.restore({ + where: { + likes: { + [Op.gt]: 100 + } + } +}); +``` + +## Behavior with other queries + +Every query performed by Sequelize will automatically ignore soft-deleted records (except raw queries, of course). + +This means that, for example, the `findAll` method will not see the soft-deleted records, fetching only the ones that were not deleted. + +Even if you simply call `findByPk` providing the primary key of a soft-deleted record, the result will be `null` as if that record didn't exist. + +If you really want to let the query see the soft-deleted records, you can pass the `paranoid: false` option to the query method. For example: + +```js +await Post.findByPk(123); // This will return `null` if the record of id 123 is soft-deleted +await Post.findByPk(123, { paranoid: false }); // This will retrieve the record + +await Post.findAll({ + where: { foo: 'bar' } +}); // This will not retrieve soft-deleted records + +await Post.findAll({ + where: { foo: 'bar' }, + paranoid: false +}); // This will also retrieve soft-deleted records +``` \ No newline at end of file diff --git a/docs/manual/core-concepts/raw-queries.md b/docs/manual/core-concepts/raw-queries.md new file mode 100644 index 000000000000..ff18dffcf8d6 --- /dev/null +++ b/docs/manual/core-concepts/raw-queries.md @@ -0,0 +1,186 @@ +# Raw Queries + +As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can use the [`sequelize.query`](../class/lib/sequelize.js~Sequelize.html#instance-method-query) method. + +By default the function will return two arguments - a results array, and an object containing metadata (such as amount of affected rows, etc). Note that since this is a raw query, the metadata are dialect specific. Some dialects return the metadata "within" the results object (as properties on an array). However, two arguments will always be returned, but for MSSQL and MySQL it will be two references to the same object. + +```js +const [results, metadata] = await sequelize.query("UPDATE users SET y = 42 WHERE x = 12"); +// Results will be an empty array and metadata will contain the number of affected rows. +``` + +In cases where you don't need to access the metadata you can pass in a query type to tell sequelize how to format the results. For example, for a simple select query you could do: + +```js +const { QueryTypes } = require('sequelize'); +const users = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes.SELECT }); +// We didn't need to destructure the result here - the results were returned directly +``` + +Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/main/src/query-types.ts). + +A second option is the model. If you pass a model the returned data will be instances of that model. + +```js +// Callee is the model definition. This allows you to easily map a query to a predefined model +const projects = await sequelize.query('SELECT * FROM projects', { + model: Projects, + mapToModel: true // pass true here if you have any mapped fields +}); +// Each element of `projects` is now an instance of Project +``` + +See more options in the [query API reference](../class/lib/sequelize.js~Sequelize.html#instance-method-query). Some examples: + +```js +const { QueryTypes } = require('sequelize'); +await sequelize.query('SELECT 1', { + // A function (or false) for logging your queries + // Will get called for every SQL query that gets sent + // to the server. + logging: console.log, + + // If plain is true, then sequelize will only return the first + // record of the result set. In case of false it will return all records. + plain: false, + + // Set this to true if you don't have a model definition for your query. + raw: false, + + // The type of query you are executing. The query type affects how results are formatted before they are passed back. + type: QueryTypes.SELECT +}); + +// Note the second argument being null! +// Even if we declared a callee here, the raw: true would +// supersede and return a raw object. +console.log(await sequelize.query('SELECT * FROM projects', { raw: true })); +``` + +## "Dotted" attributes and the `nest` option + +If an attribute name of the table contains dots, the resulting objects can become nested objects by setting the `nest: true` option. This is achieved with [dottie.js](https://github.com/mickhansen/dottie.js/) under the hood. See below: + +* Without `nest: true`: + + ```js + const { QueryTypes } = require('sequelize'); + const records = await sequelize.query('select 1 as `foo.bar.baz`', { + type: QueryTypes.SELECT + }); + console.log(JSON.stringify(records[0], null, 2)); + ``` + + ```json + { + "foo.bar.baz": 1 + } + ``` + +* With `nest: true`: + + ```js + const { QueryTypes } = require('sequelize'); + const records = await sequelize.query('select 1 as `foo.bar.baz`', { + nest: true, + type: QueryTypes.SELECT + }); + console.log(JSON.stringify(records[0], null, 2)); + ``` + + ```json + { + "foo": { + "bar": { + "baz": 1 + } + } + } + ``` + +## Replacements + +Replacements in a query can be done in two different ways, either using named parameters (starting with `:`), or unnamed, represented by a `?`. Replacements are passed in the options object. + +* If an array is passed, `?` will be replaced in the order that they appear in the array +* If an object is passed, `:key` will be replaced with the keys from that object. If the object contains keys not found in the query or vice versa, an exception will be thrown. + +```js +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT * FROM projects WHERE status = ?', + { + replacements: ['active'], + type: QueryTypes.SELECT + } +); + +await sequelize.query( + 'SELECT * FROM projects WHERE status = :status', + { + replacements: { status: 'active' }, + type: QueryTypes.SELECT + } +); +``` + +Array replacements will automatically be handled, the following query searches for projects where the status matches an array of values. + +```js +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT * FROM projects WHERE status IN(:status)', + { + replacements: { status: ['active', 'inactive'] }, + type: QueryTypes.SELECT + } +); +``` + +To use the wildcard operator `%`, append it to your replacement. The following query matches users with names that start with 'ben'. + +```js +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT * FROM users WHERE name LIKE :search_name', + { + replacements: { search_name: 'ben%' }, + type: QueryTypes.SELECT + } +); +``` + +## Bind Parameter + +Bind parameters are like replacements. Except replacements are escaped and inserted into the query by sequelize before the query is sent to the database, while bind parameters are sent to the database outside the SQL query text. A query can have either bind parameters or replacements. Bind parameters are referred to by either $1, $2, ... (numeric) or $key (alpha-numeric). This is independent of the dialect. + +* If an array is passed, `$1` is bound to the 1st element in the array (`bind[0]`) +* If an object is passed, `$key` is bound to `object['key']`. Each key must begin with a non-numeric char. `$1` is not a valid key, even if `object['1']` exists. +* In either case `$$` can be used to escape a literal `$` sign. + +The array or object must contain all bound values or Sequelize will throw an exception. This applies even to cases in which the database may ignore the bound parameter. + +The database may add further restrictions to this. Bind parameters cannot be SQL keywords, nor table or column names. They are also ignored in quoted text or data. In PostgreSQL it may also be needed to typecast them, if the type cannot be inferred from the context `$1::varchar`. + +```js +const { QueryTypes } = require('sequelize'); + +await sequelize.query( + 'SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1', + { + bind: ['active'], + type: QueryTypes.SELECT + } +); + +await sequelize.query( + 'SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status', + { + bind: { status: 'active' }, + type: QueryTypes.SELECT + } +); +``` diff --git a/docs/manual/core-concepts/validations-and-constraints.md b/docs/manual/core-concepts/validations-and-constraints.md new file mode 100644 index 000000000000..fed749072ed7 --- /dev/null +++ b/docs/manual/core-concepts/validations-and-constraints.md @@ -0,0 +1,272 @@ +# Validations & Constraints + +In this tutorial you will learn how to setup validations and constraints for your models in Sequelize. + +For this tutorial, the following setup will be assumed: + +```js +const { Sequelize, Op, Model, DataTypes } = require("sequelize"); +const sequelize = new Sequelize("sqlite::memory:"); + +const User = sequelize.define("user", { + username: { + type: DataTypes.TEXT, + allowNull: false, + unique: true + }, + hashedPassword: { + type: DataTypes.STRING(64), + validate: { + is: /^[0-9a-f]{64}$/i + } + } +}); + +(async () => { + await sequelize.sync({ force: true }); + // Code here +})(); +``` + +## Difference between Validations and Constraints + +Validations are checks performed in the Sequelize level, in pure JavaScript. They can be arbitrarily complex if you provide a custom validator function, or can be one of the built-in validators offered by Sequelize. If a validation fails, no SQL query will be sent to the database at all. + +On the other hand, constraints are rules defined at SQL level. The most basic example of constraint is an Unique Constraint. If a constraint check fails, an error will be thrown by the database and Sequelize will forward this error to JavaScript (in this example, throwing a `SequelizeUniqueConstraintError`). Note that in this case, the SQL query was performed, unlike the case for validations. + +## Unique Constraint + +Our code example above defines a unique constraint on the `username` field: + +```js +/* ... */ { + username: { + type: DataTypes.TEXT, + allowNull: false, + unique: true + }, +} /* ... */ +``` + +When this model is synchronized (by calling `sequelize.sync` for example), the `username` field will be created in the table as `` `username` TEXT UNIQUE``, and an attempt to insert an username that already exists there will throw a `SequelizeUniqueConstraintError`. + +## Allowing/disallowing null values + +By default, `null` is an allowed value for every column of a model. This can be disabled setting the `allowNull: false` option for a column, as it was done in the `username` field from our code example: + +```js +/* ... */ { + username: { + type: DataTypes.TEXT, + allowNull: false, + unique: true + }, +} /* ... */ +``` + +Without `allowNull: false`, the call `User.create({})` would work. + +### Note about `allowNull` implementation + +The `allowNull` check is the only check in Sequelize that is a mix of a *validation* and a *constraint* in the senses described at the beginning of this tutorial. This is because: + +* If an attempt is made to set `null` to a field that does not allow null, a `ValidationError` will be thrown *without any SQL query being performed*. +* In addition, after `sequelize.sync`, the column that has `allowNull: false` will be defined with a `NOT NULL` SQL constraint. This way, direct SQL queries that attempt to set the value to `null` will also fail. + +## Validators + +Model validators allow you to specify format/content/inheritance validations for each attribute of the model. Validations are automatically run on `create`, `update` and `save`. You can also call `validate()` to manually validate an instance. + +### Per-attribute validations + +You can define your custom validators or use several built-in validators, implemented by [validator.js (10.11.0)](https://github.com/chriso/validator.js), as shown below. + +```js +sequelize.define('foo', { + bar: { + type: DataTypes.STRING, + validate: { + is: /^[a-z]+$/i, // matches this RegExp + is: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string + not: /^[a-z]+$/i, // does not match this RegExp + not: ["^[a-z]+$",'i'], // same as above, but constructing the RegExp from a string + isEmail: true, // checks for email format (foo@bar.com) + isUrl: true, // checks for url format (http://foo.com) + isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format + isIPv4: true, // checks for IPv4 (129.89.23.1) + isIPv6: true, // checks for IPv6 format + isAlpha: true, // will only allow letters + isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail + isNumeric: true, // will only allow numbers + isInt: true, // checks for valid integers + isFloat: true, // checks for valid floating point numbers + isDecimal: true, // checks for any numbers + isLowercase: true, // checks for lowercase + isUppercase: true, // checks for uppercase + notNull: true, // won't allow null + isNull: true, // only allows null + notEmpty: true, // don't allow empty strings + equals: 'specific value', // only allow a specific value + contains: 'foo', // force specific substrings + notIn: [['foo', 'bar']], // check the value is not one of these + isIn: [['foo', 'bar']], // check the value is one of these + notContains: 'bar', // don't allow specific substrings + len: [2,10], // only allow values with length between 2 and 10 + isUUID: 4, // only allow uuids + isDate: true, // only allow date strings + isAfter: "2011-11-05", // only allow date strings after a specific date + isBefore: "2011-11-05", // only allow date strings before a specific date + max: 23, // only allow values <= 23 + min: 23, // only allow values >= 23 + isCreditCard: true, // check for valid credit card numbers + + // Examples of custom validators: + isEven(value) { + if (parseInt(value) % 2 !== 0) { + throw new Error('Only even values are allowed!'); + } + } + isGreaterThanOtherField(value) { + if (parseInt(value) <= parseInt(this.otherField)) { + throw new Error('Bar must be greater than otherField.'); + } + } + } + } +}); +``` + +Note that where multiple arguments need to be passed to the built-in validation functions, the arguments to be passed must be in an array. But if a single array argument is to be passed, for instance an array of acceptable strings for `isIn`, this will be interpreted as multiple string arguments instead of one array argument. To work around this pass a single-length array of arguments, such as `[['foo', 'bar']]` as shown above. + +To use a custom error message instead of that provided by [validator.js](https://github.com/chriso/validator.js), use an object instead of the plain value or array of arguments, for example a validator which needs no argument can be given a custom message with + +```js +isInt: { + msg: "Must be an integer number of pennies" +} +``` + +or if arguments need to also be passed add an `args` property: + +```js +isIn: { + args: [['en', 'zh']], + msg: "Must be English or Chinese" +} +``` + +When using custom validator functions the error message will be whatever message the thrown `Error` object holds. + +See [the validator.js project](https://github.com/chriso/validator.js) for more details on the built in validation methods. + +**Hint:** You can also define a custom function for the logging part. Just pass a function. The first parameter will be the string that is logged. + +### `allowNull` interaction with other validators + +If a particular field of a model is set to not allow null (with `allowNull: false`) and that value has been set to `null`, all validators will be skipped and a `ValidationError` will be thrown. + +On the other hand, if it is set to allow null (with `allowNull: true`) and that value has been set to `null`, only the built-in validators will be skipped, while the custom validators will still run. + +This means you can, for instance, have a string field which validates its length to be between 5 and 10 characters, but which also allows `null` (since the length validator will be skipped automatically when the value is `null`): + +```js +class User extends Model {} +User.init({ + username: { + type: DataTypes.STRING, + allowNull: true, + validate: { + len: [5, 10] + } + } +}, { sequelize }); +``` + +You also can conditionally allow `null` values, with a custom validator, since it won't be skipped: + +```js +class User extends Model {} +User.init({ + age: Sequelize.INTEGER, + name: { + type: DataTypes.STRING, + allowNull: true, + validate: { + customValidator(value) { + if (value === null && this.age !== 10) { + throw new Error("name can't be null unless age is 10"); + } + }) + } + } +}, { sequelize }); +``` + +You can customize `allowNull` error message by setting the `notNull` validator: + +```js +class User extends Model {} +User.init({ + name: { + type: DataTypes.STRING, + allowNull: false, + validate: { + notNull: { + msg: 'Please enter your name' + } + } + } +}, { sequelize }); +``` + +### Model-wide validations + +Validations can also be defined to check the model after the field-specific validators. Using this you could, for example, ensure either neither of `latitude` and `longitude` are set or both, and fail if one but not the other is set. + +Model validator methods are called with the model object's context and are deemed to fail if they throw an error, otherwise pass. This is just the same as with custom field-specific validators. + +Any error messages collected are put in the validation result object alongside the field validation errors, with keys named after the failed validation method's key in the `validate` option object. Even though there can only be one error message for each model validation method at any one time, it is presented as a single string error in an array, to maximize consistency with the field errors. + +An example: + +```js +class Place extends Model {} +Place.init({ + name: Sequelize.STRING, + address: Sequelize.STRING, + latitude: { + type: DataTypes.INTEGER, + validate: { + min: -90, + max: 90 + } + }, + longitude: { + type: DataTypes.INTEGER, + validate: { + min: -180, + max: 180 + } + }, +}, { + sequelize, + validate: { + bothCoordsOrNone() { + if ((this.latitude === null) !== (this.longitude === null)) { + throw new Error('Either both latitude and longitude, or neither!'); + } + } + } +}) +``` + +In this simple case an object fails validation if either latitude or longitude is given, but not both. If we try to build one with an out-of-range latitude and no longitude, `somePlace.validate()` might return: + +```js +{ + 'latitude': ['Invalid number: latitude'], + 'bothCoordsOrNone': ['Either both latitude and longitude, or neither!'] +} +``` + +Such validation could have also been done with a custom validator defined on a single attribute (such as the `latitude` attribute, by checking `(value === null) !== (this.longitude === null)`), but the model-wide validation approach is cleaner. diff --git a/docs/manual/data-types.md b/docs/manual/data-types.md deleted file mode 100644 index 0ce73b7d14da..000000000000 --- a/docs/manual/data-types.md +++ /dev/null @@ -1,330 +0,0 @@ -# Datatypes - -Below are some of the datatypes supported by sequelize. For a full and updated list, see [DataTypes](/master/variable/index.html#static-variable-DataTypes). - -```js -Sequelize.STRING // VARCHAR(255) -Sequelize.STRING(1234) // VARCHAR(1234) -Sequelize.STRING.BINARY // VARCHAR BINARY -Sequelize.TEXT // TEXT -Sequelize.TEXT('tiny') // TINYTEXT -Sequelize.CITEXT // CITEXT PostgreSQL and SQLite only. - -Sequelize.INTEGER // INTEGER -Sequelize.BIGINT // BIGINT -Sequelize.BIGINT(11) // BIGINT(11) - -Sequelize.FLOAT // FLOAT -Sequelize.FLOAT(11) // FLOAT(11) -Sequelize.FLOAT(11, 10) // FLOAT(11,10) - -Sequelize.REAL // REAL PostgreSQL only. -Sequelize.REAL(11) // REAL(11) PostgreSQL only. -Sequelize.REAL(11, 12) // REAL(11,12) PostgreSQL only. - -Sequelize.DOUBLE // DOUBLE -Sequelize.DOUBLE(11) // DOUBLE(11) -Sequelize.DOUBLE(11, 10) // DOUBLE(11,10) - -Sequelize.DECIMAL // DECIMAL -Sequelize.DECIMAL(10, 2) // DECIMAL(10,2) - -Sequelize.DATE // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres -Sequelize.DATE(6) // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision -Sequelize.DATEONLY // DATE without time. -Sequelize.BOOLEAN // TINYINT(1) - -Sequelize.ENUM('value 1', 'value 2') // An ENUM with allowed values 'value 1' and 'value 2' -Sequelize.ARRAY(Sequelize.TEXT) // Defines an array. PostgreSQL only. -Sequelize.ARRAY(Sequelize.ENUM) // Defines an array of ENUM. PostgreSQL only. - -Sequelize.JSON // JSON column. PostgreSQL, SQLite and MySQL only. -Sequelize.JSONB // JSONB column. PostgreSQL only. - -Sequelize.BLOB // BLOB (bytea for PostgreSQL) -Sequelize.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL. Other options are medium and long) - -Sequelize.UUID // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically) - -Sequelize.CIDR // CIDR datatype for PostgreSQL -Sequelize.INET // INET datatype for PostgreSQL -Sequelize.MACADDR // MACADDR datatype for PostgreSQL - -Sequelize.RANGE(Sequelize.INTEGER) // Defines int4range range. PostgreSQL only. -Sequelize.RANGE(Sequelize.BIGINT) // Defined int8range range. PostgreSQL only. -Sequelize.RANGE(Sequelize.DATE) // Defines tstzrange range. PostgreSQL only. -Sequelize.RANGE(Sequelize.DATEONLY) // Defines daterange range. PostgreSQL only. -Sequelize.RANGE(Sequelize.DECIMAL) // Defines numrange range. PostgreSQL only. - -Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only. - -Sequelize.GEOMETRY // Spatial column. PostgreSQL (with PostGIS) or MySQL only. -Sequelize.GEOMETRY('POINT') // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only. -Sequelize.GEOMETRY('POINT', 4326) // Spatial column with geometry type and SRID. PostgreSQL (with PostGIS) or MySQL only. -``` - -The BLOB datatype allows you to insert data both as strings and as buffers. When you do a find or findAll on a model which has a BLOB column, that data will always be returned as a buffer. - -If you are working with the PostgreSQL TIMESTAMP WITHOUT TIME ZONE and you need to parse it to a different timezone, please use the pg library's own parser: - -```js -require('pg').types.setTypeParser(1114, stringValue => { - return new Date(stringValue + '+0000'); - // e.g., UTC offset. Use any offset that you would like. -}); -``` - -In addition to the type mentioned above, integer, bigint, float and double also support unsigned and zerofill properties, which can be combined in any order: -Be aware that this does not apply for PostgreSQL! - -```js -Sequelize.INTEGER.UNSIGNED // INTEGER UNSIGNED -Sequelize.INTEGER(11).UNSIGNED // INTEGER(11) UNSIGNED -Sequelize.INTEGER(11).ZEROFILL // INTEGER(11) ZEROFILL -Sequelize.INTEGER(11).ZEROFILL.UNSIGNED // INTEGER(11) UNSIGNED ZEROFILL -Sequelize.INTEGER(11).UNSIGNED.ZEROFILL // INTEGER(11) UNSIGNED ZEROFILL -``` - -_The examples above only show integer, but the same can be done with bigint and float_ - -Usage in object notation: - -```js -// for enums: -class MyModel extends Model {} -MyModel.init({ - states: { - type: Sequelize.ENUM, - values: ['active', 'pending', 'deleted'] - } -}, { sequelize }) -``` - -### Array(ENUM) - -Its only supported with PostgreSQL. - -Array(Enum) type require special treatment. Whenever Sequelize will talk to database it has to typecast Array values with ENUM name. - -So this enum name must follow this pattern `enum__`. If you are using `sync` then correct name will automatically be generated. - -### Range types - -Since range types have extra information for their bound inclusion/exclusion it's not -very straightforward to just use a tuple to represent them in javascript. - -When supplying ranges as values you can choose from the following APIs: - -```js -// defaults to '["2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' -// inclusive lower bound, exclusive upper bound -Timeline.create({ range: [new Date(Date.UTC(2016, 0, 1)), new Date(Date.UTC(2016, 1, 1))] }); - -// control inclusion -const range = [ - { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, - { value: new Date(Date.UTC(2016, 1, 1)), inclusive: true }, -]; -// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]' - -// composite form -const range = [ - { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, - new Date(Date.UTC(2016, 1, 1)), -]; -// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' - -Timeline.create({ range }); -``` - -However, please note that whenever you get back a value that is range you will -receive: - -```js -// stored value: ("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"] -range // [{ value: Date, inclusive: false }, { value: Date, inclusive: true }] -``` - -You will need to call reload after updating an instance with a range type or use `returning: true` option. - -#### Special Cases - -```js -// empty range: -Timeline.create({ range: [] }); // range = 'empty' - -// Unbounded range: -Timeline.create({ range: [null, null] }); // range = '[,)' -// range = '[,"2016-01-01 00:00:00+00:00")' -Timeline.create({ range: [null, new Date(Date.UTC(2016, 0, 1))] }); - -// Infinite range: -// range = '[-infinity,"2016-01-01 00:00:00+00:00")' -Timeline.create({ range: [-Infinity, new Date(Date.UTC(2016, 0, 1))] }); -``` - -## Extending datatypes - -Most likely the type you are trying to implement is already included in [DataTypes](data-types.html). If a new datatype is not included, this manual will show how to write it yourself. - -Sequelize doesn't create new datatypes in the database. This tutorial explains how to make Sequelize recognize new datatypes and assumes that those new datatypes are already created in the database. - -To extend Sequelize datatypes, do it before any instance is created. This example creates a dummy `NEWTYPE` that replicates the built-in datatype `Sequelize.INTEGER(11).ZEROFILL.UNSIGNED`. - -```js -// myproject/lib/sequelize.js - -const Sequelize = require('Sequelize'); -const sequelizeConfig = require('../config/sequelize') -const sequelizeAdditions = require('./sequelize-additions') - -// Function that adds new datatypes -sequelizeAdditions(Sequelize) - -// In this exmaple a Sequelize instance is created and exported -const sequelize = new Sequelize(sequelizeConfig) - -module.exports = sequelize -``` - -```js -// myproject/lib/sequelize-additions.js - -module.exports = function sequelizeAdditions(Sequelize) { - - DataTypes = Sequelize.DataTypes - - /* - * Create new types - */ - class NEWTYPE extends DataTypes.ABSTRACT { - // Mandatory, complete definition of the new type in the database - toSql() { - return 'INTEGER(11) UNSIGNED ZEROFILL' - } - - // Optional, validator function - validate(value, options) { - return (typeof value === 'number') && (! Number.isNaN(value)) - } - - // Optional, sanitizer - _sanitize(value) { - // Force all numbers to be positive - if (value < 0) { - value = 0 - } - - return Math.round(value) - } - - // Optional, value stringifier before sending to database - _stringify(value) { - return value.toString() - } - - // Optional, parser for values received from the database - static parse(value) { - return Number.parseInt(value) - } - } - - DataTypes.NEWTYPE = NEWTYPE; - - // Mandatory, set key - DataTypes.NEWTYPE.prototype.key = DataTypes.NEWTYPE.key = 'NEWTYPE' - - // Optional, disable escaping after stringifier. Not recommended. - // Warning: disables Sequelize protection against SQL injections - // DataTypes.NEWTYPE.escape = false - - // For convenience - // `classToInvokable` allows you to use the datatype without `new` - Sequelize.NEWTYPE = Sequelize.Utils.classToInvokable(DataTypes.NEWTYPE) - -} -``` - -After creating this new datatype, you need to map this datatype in each database dialect and make some adjustments. - -## PostgreSQL - -Let's say the name of the new datatype is `pg_new_type` in the postgres database. That name has to be mapped to `DataTypes.NEWTYPE`. Additionally, it is required to create a child postgres-specific datatype. - -```js -// myproject/lib/sequelize-additions.js - -module.exports = function sequelizeAdditions(Sequelize) { - - DataTypes = Sequelize.DataTypes - - /* - * Create new types - */ - - ... - - /* - * Map new types - */ - - // Mandatory, map postgres datatype name - DataTypes.NEWTYPE.types.postgres = ['pg_new_type'] - - // Mandatory, create a postgres-specific child datatype with its own parse - // method. The parser will be dynamically mapped to the OID of pg_new_type. - PgTypes = DataTypes.postgres - - PgTypes.NEWTYPE = function NEWTYPE() { - if (!(this instanceof PgTypes.NEWTYPE)) return new PgTypes.NEWTYPE(); - DataTypes.NEWTYPE.apply(this, arguments); - } - inherits(PgTypes.NEWTYPE, DataTypes.NEWTYPE); - - // Mandatory, create, override or reassign a postgres-specific parser - //PgTypes.NEWTYPE.parse = value => value; - PgTypes.NEWTYPE.parse = DataTypes.NEWTYPE.parse; - - // Optional, add or override methods of the postgres-specific datatype - // like toSql, escape, validate, _stringify, _sanitize... - -} -``` - -### Ranges - -After a new range type has been [defined in postgres](https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-DEFINING), it is trivial to add it to Sequelize. - -In this example the name of the postgres range type is `newtype_range` and the name of the underlying postgres datatype is `pg_new_type`. The key of `subtypes` and `castTypes` is the key of the Sequelize datatype `DataTypes.NEWTYPE.key`, in lower case. - -```js -// myproject/lib/sequelize-additions.js - -module.exports = function sequelizeAdditions(Sequelize) { - - DataTypes = Sequelize.DataTypes - - /* - * Create new types - */ - - ... - - /* - * Map new types - */ - - ... - - /* - * Add suport for ranges - */ - - // Add postgresql range, newtype comes from DataType.NEWTYPE.key in lower case - DataTypes.RANGE.types.postgres.subtypes.newtype = 'newtype_range'; - DataTypes.RANGE.types.postgres.castTypes.newtype = 'pg_new_type'; - -} -``` - -The new range can be used in model definitions as `Sequelize.RANGE(Sequelize.NEWTYPE)` or `DataTypes.RANGE(DataTypes.NEWTYPE)`. diff --git a/docs/manual/dialects.md b/docs/manual/dialects.md deleted file mode 100644 index ed1032d4c909..000000000000 --- a/docs/manual/dialects.md +++ /dev/null @@ -1,96 +0,0 @@ -# Dialects - -Sequelize is independent from specific dialects. This means that you'll have to install the respective connector library to your project yourself. - -## MySQL - -In order to get Sequelize working nicely together with MySQL, you'll need to install`mysql2@^1.5.2`or higher. Once that's done you can use it like this: - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'mysql' -}) -``` - -**Note:** You can pass options directly to dialect library by setting the -`dialectOptions` parameter. - -## MariaDB - -Library for MariaDB is `mariadb`. - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'mariadb', - dialectOptions: {connectTimeout: 1000} // mariadb connector option -}) -``` - -or using connection String: - -```js -const sequelize = new Sequelize('mariadb://user:password@example.com:9821/database') -``` - -## SQLite - -For SQLite compatibility you'll need`sqlite3@^4.0.0`. Configure Sequelize like this: - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - // sqlite! now! - dialect: 'sqlite', - - // the storage engine for sqlite - // - default ':memory:' - storage: 'path/to/database.sqlite' -}) -``` - -Or you can use a connection string as well with a path: - -```js -const sequelize = new Sequelize('sqlite:/home/abs/path/dbname.db') -const sequelize = new Sequelize('sqlite:relativePath/dbname.db') -``` - -## PostgreSQL - -For PostgreSQL, two libraries are needed, `pg@^7.0.0` and `pg-hstore`. You'll just need to define the dialect: - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - // gimme postgres, please! - dialect: 'postgres' -}) -``` - -To connect over a unix domain socket, specify the path to the socket directory -in the `host` option. - -The socket path must start with `/`. - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - // gimme postgres, please! - dialect: 'postgres', - host: '/path/to/socket_directory' -}) -``` - -## MSSQL - -The library for MSSQL is`tedious@^6.0.0` You'll just need to define the dialect. -Please note: `tedious@^6.0.0` requires you to nest MSSQL specific options inside an additional `options`-object inside the `dialectOptions`-object. - -```js -const sequelize = new Sequelize('database', 'username', 'password', { - dialect: 'mssql', - dialectOptions: { - options: { - useUTC: false, - dateFirst: 1, - } - } -}) -``` diff --git a/docs/manual/getting-started.md b/docs/manual/getting-started.md deleted file mode 100644 index e2a85c9c0b86..000000000000 --- a/docs/manual/getting-started.md +++ /dev/null @@ -1,226 +0,0 @@ -# Getting started - -In this tutorial you will learn to make a simple setup of Sequelize to learn the basics. - -## Installing - -Sequelize is available via [npm](https://www.npmjs.com/package/sequelize) (or [yarn](https://yarnpkg.com/package/sequelize)). - -```sh -npm install --save sequelize -``` - -You'll also have to manually install the driver for your database of choice: - -```sh -# One of the following: -$ npm install --save pg pg-hstore # Postgres -$ npm install --save mysql2 -$ npm install --save mariadb -$ npm install --save sqlite3 -$ npm install --save tedious # Microsoft SQL Server -``` - -## Setting up a connection - -To connect to the database, you must create a Sequelize instance. This can be done by either passing the connection parameters separately to the Sequelize constructor or by passing a single connection URI: - -```js -const Sequelize = require('sequelize'); - -// Option 1: Passing parameters separately -const sequelize = new Sequelize('database', 'username', 'password', { - host: 'localhost', - dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */ -}); - -// Option 2: Passing a connection URI -const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname'); -``` - -The Sequelize constructor takes a whole slew of options that are documented in the [API Reference for the Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). - -### Note: setting up SQLite - -If you're using SQLite, you should use the following instead: - -```js -const sequelize = new Sequelize({ - dialect: 'sqlite', - storage: 'path/to/database.sqlite' -}); -``` - -### Note: connection pool (production) - -If you're connecting to the database from a single process, you should create only one Sequelize instance. Sequelize will set up a connection pool on initialization. This connection pool can be configured through the constructor's `options` parameter (using `options.pool`), as is shown in the following example: - -```js -const sequelize = new Sequelize(/* ... */, { - // ... - pool: { - max: 5, - min: 0, - acquire: 30000, - idle: 10000 - } -}); -``` - -Learn more in the [API Reference for the Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). If you're connecting to the database from multiple processes, you'll have to create one instance per process, but each instance should have a maximum connection pool size of such that the total maximum size is respected. For example, if you want a max connection pool size of 90 and you have three processes, the Sequelize instance of each process should have a max connection pool size of 30. - -### Testing the connection - -You can use the `.authenticate()` function to test if the connection is OK: - -```js -sequelize - .authenticate() - .then(() => { - console.log('Connection has been established successfully.'); - }) - .catch(err => { - console.error('Unable to connect to the database:', err); - }); -``` - -### Closing the connection - -Sequelize will keep the connection open by default, and use the same connection for all queries. If you need to close the connection, call `sequelize.close()` (which is asynchronous and returns a Promise). - -## Modeling a table - -A model is a class that extends `Sequelize.Model`. Models can be defined in two equivalent ways. The first, with `Sequelize.Model.init(attributes, options)`: - -```js -const Model = Sequelize.Model; -class User extends Model {} -User.init({ - // attributes - firstName: { - type: Sequelize.STRING, - allowNull: false - }, - lastName: { - type: Sequelize.STRING - // allowNull defaults to true - } -}, { - sequelize, - modelName: 'user' - // options -}); -``` - -Alternatively, using `sequelize.define`: - -```js -const User = sequelize.define('user', { - // attributes - firstName: { - type: Sequelize.STRING, - allowNull: false - }, - lastName: { - type: Sequelize.STRING - // allowNull defaults to true - } -}, { - // options -}); -``` - -Internally, `sequelize.define` calls `Model.init`. - -The above code tells Sequelize to expect a table named `users` in the database with the fields `firstName` and `lastName`. The table name is automatically pluralized by default (a library called [inflection](https://www.npmjs.com/package/inflection) is used under the hood to do this). This behavior can be stopped for a specific model by using the `freezeTableName: true` option, or for all models by using the `define` option from the [Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). - -Sequelize also defines by default the fields `id` (primary key), `createdAt` and `updatedAt` to every model. This behavior can also be changed, of course (check the API Reference to learn more about the available options). - -### Changing the default model options - -The Sequelize constructor takes a `define` option which will change the default options for all defined models. - -```js -const sequelize = new Sequelize(connectionURI, { - define: { - // The `timestamps` field specify whether or not the `createdAt` and `updatedAt` fields will be created. - // This was true by default, but now is false by default - timestamps: false - } -}); - -// Here `timestamps` will be false, so the `createdAt` and `updatedAt` fields will not be created. -class Foo extends Model {} -Foo.init({ /* ... */ }, { sequelize }); - -// Here `timestamps` is directly set to true, so the `createdAt` and `updatedAt` fields will be created. -class Bar extends Model {} -Bar.init({ /* ... */ }, { sequelize, timestamps: true }); -``` - -You can read more about creating models in the [Model.init API Reference](../class/lib/model.js~Model.html#static-method-init), or in the [sequelize.define API reference](../class/lib/sequelize.js~Sequelize.html#instance-method-define). - -## Synchronizing the model with the database - -If you want Sequelize to automatically create the table (or modify it as needed) according to your model definition, you can use the `sync` method, as follows: - -```js -// Note: using `force: true` will drop the table if it already exists -User.sync({ force: true }).then(() => { - // Now the `users` table in the database corresponds to the model definition - return User.create({ - firstName: 'John', - lastName: 'Hancock' - }); -}); -``` - -### Synchronizing all models at once - -Instead of calling `sync()` for every model, you can call `sequelize.sync()` which will automatically sync all models. - -### Note for production - -In production, you might want to consider using Migrations instead of calling `sync()` in your code. Learn more in the [Migrations](migrations.html) guide. - -## Querying - -A few simple queries are shown below: - -```js -// Find all users -User.findAll().then(users => { - console.log("All users:", JSON.stringify(users, null, 4)); -}); - -// Create a new user -User.create({ firstName: "Jane", lastName: "Doe" }).then(jane => { - console.log("Jane's auto-generated ID:", jane.id); -}); - -// Delete everyone named "Jane" -User.destroy({ - where: { - firstName: "Jane" - } -}).then(() => { - console.log("Done"); -}); - -// Change everyone without a last name to "Doe" -User.update({ lastName: "Doe" }, { - where: { - lastName: null - } -}).then(() => { - console.log("Done"); -}); -``` - -Sequelize has a lot of options for querying. You will learn more about those in the next tutorials. It is also possible to make raw SQL queries, if you really need them. - -## Promises and async/await - -As shown above by the extensive usage of `.then` calls, Sequelize uses Promises extensively. This means that, if your Node version supports it, you can use ES2017 `async/await` syntax for all asynchronous calls made with Sequelize. - -Also, all Sequelize promises are in fact [Bluebird](http://bluebirdjs.com) promises, so you have the rich Bluebird API to use as well (for example, using `finally`, `tap`, `tapCatch`, `map`, `mapSeries`, etc). You can access the Bluebird constructor used internally by Sequelize with `Sequelize.Promise`, if you want to set any Bluebird specific options. diff --git a/docs/manual/hooks.md b/docs/manual/hooks.md deleted file mode 100644 index 6b2a30cd3675..000000000000 --- a/docs/manual/hooks.md +++ /dev/null @@ -1,393 +0,0 @@ -# Hooks - -Hooks (also known as lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a `beforeUpdate` hook. - -**Note:** _You can't use hooks with instances. Hooks are used with models._ - -For a full list of hooks, see [Hooks file](https://github.com/sequelize/sequelize/blob/master/lib/hooks.js#L7). - -## Order of Operations - -```text -(1) - beforeBulkCreate(instances, options) - beforeBulkDestroy(options) - beforeBulkUpdate(options) -(2) - beforeValidate(instance, options) -(-) - validate -(3) - afterValidate(instance, options) - - or - - validationFailed(instance, options, error) -(4) - beforeCreate(instance, options) - beforeDestroy(instance, options) - beforeUpdate(instance, options) - beforeSave(instance, options) - beforeUpsert(values, options) -(-) - create - destroy - update -(5) - afterCreate(instance, options) - afterDestroy(instance, options) - afterUpdate(instance, options) - afterSave(instance, options) - afterUpsert(created, options) -(6) - afterBulkCreate(instances, options) - afterBulkDestroy(options) - afterBulkUpdate(options) -``` - -## Declaring Hooks - -Arguments to hooks are passed by reference. This means, that you can change the values, and this will be reflected in the insert / update statement. A hook may contain async actions - in this case the hook function should return a promise. - -There are currently three ways to programmatically add hooks: - -```js -// Method 1 via the .init() method -class User extends Model {} -User.init({ - username: DataTypes.STRING, - mood: { - type: DataTypes.ENUM, - values: ['happy', 'sad', 'neutral'] - } -}, { - hooks: { - beforeValidate: (user, options) => { - user.mood = 'happy'; - }, - afterValidate: (user, options) => { - user.username = 'Toni'; - } - }, - sequelize -}); - -// Method 2 via the .hooks.add() method -User.hooks.add('beforeValidate', (user, options) => { - user.mood = 'happy'; -}); - -User.hooks.add('afterValidate', (user, options) => { - return Promise.reject(new Error("I'm afraid I can't let you do that!")); -}); -``` - -## Removing hooks - -Only a hook with name param can be removed. - -```js -class Book extends Model {} -Book.init({ - title: DataTypes.STRING -}, { sequelize }); - -function notifyUsers(book, options) { - -} - -Book.hooks.add('afterCreate', notifyUsers); - -Book.hooks.remove('afterCreate', notifyUsers); -``` - -You can have many hooks with same name. Calling `.hooks.remove(()` will remove all of them. - -## Global / universal hooks - -Global hooks are hooks which are run for all models. They can define behaviours that you want for all your models, and are especially useful for plugins. They can be defined in two ways, which have slightly different semantics: - -### Default Hooks (Sequelize.options.define) - -```js -const sequelize = new Sequelize(..., { - define: { - hooks: { - beforeCreate: () => { - // Do stuff - } - } - } -}); -``` - -This adds a default hook to all models, which is run if the model does not define its own `beforeCreate` hook: - -```js -class User extends Model {} -User.init({}, { sequelize }); -class Project extends Model {} -Project.init({}, { - hooks: { - beforeCreate: () => { - // Do other stuff - } - }, - sequelize -}); - -User.create() // Runs the global hook -Project.create() // Runs its own hook (because the global hook is overwritten) -``` - -### Permanent Hooks (Sequelize.hooks.add) - -```js -sequelize.hooks.add('beforeCreate', () => { - // Do stuff -}); -``` - -This hook is always run before create, regardless of whether the model specifies its own `beforeCreate` hook. Local hooks are always run before global hooks: - -```js -class User extends Model {} -User.init({}, { sequelize }); -class Project extends Model {} -Project.init({}, { - hooks: { - beforeCreate: () => { - // Do other stuff - } - }, - sequelize -}); - -User.create() // Runs the global hook -Project.create() // Runs its own hook, followed by the global hook -``` - -Permanent hooks may also be defined in `Sequelize.options`: - -```js -new Sequelize(..., { - hooks: { - beforeCreate: () => { - // do stuff - } - } -}); -``` - -### Connection Hooks - -Sequelize provides four hooks that are executed immediately before and after a database connection is obtained or released: - -```text -beforeConnect(config) -afterConnect(connection, config) -beforeDisconnect(connection) -afterDisconnect(connection) -``` - -These hooks can be useful if you need to asynchronously obtain database credentials, or need to directly access the low-level database connection after it has been created. - -For example, we can asynchronously obtain a database password from a rotating token store, and mutate Sequelize's configuration object with the new credentials: - -```js -sequelize.hooks.add('beforeConnect', (config) => { - return getAuthToken() - .then((token) => { - config.password = token; - }); - }); -``` - -These hooks may _only_ be declared as a permanent global hook, as the connection pool is shared by all models. - -## Instance hooks - -The following hooks will emit whenever you're editing a single object - -```text -beforeValidate -afterValidate or validationFailed -beforeCreate / beforeUpdate / beforeSave / beforeDestroy -afterCreate / afterUpdate / afterSave / afterDestroy -``` - -```js -// ...define ... -User.hooks.add('beforeCreate', user => { - if (user.accessLevel > 10 && user.username !== "Boss") { - throw new Error("You can't grant this user an access level above 10!") - } -}) -``` - -This example will return an error: - -```js -User.create({username: 'Not a Boss', accessLevel: 20}).catch(err => { - console.log(err); // You can't grant this user an access level above 10! -}); -``` - -The following example would return successful: - -```js -User.create({username: 'Boss', accessLevel: 20}).then(user => { - console.log(user); // user object with username as Boss and accessLevel of 20 -}); -``` - -### Model hooks - -Sometimes you'll be editing more than one record at a time by utilizing the `bulkCreate, update, destroy` methods on the model. The following will emit whenever you're using one of those methods: - -```text -beforeBulkCreate(instances, options) -beforeBulkUpdate(options) -beforeBulkDestroy(options) -afterBulkCreate(instances, options) -afterBulkUpdate(options) -afterBulkDestroy(options) -``` - -If you want to emit hooks for each individual record, along with the bulk hooks you can pass `individualHooks: true` to the call. - -**WARNING**: if you use individual hooks, *all instances that are updated or destroyed will get loaded into memory* before your hooks are called. The number of instances Sequelize can handle with individual hooks is limited by available memory. - -```js -Model.destroy({ where: {accessLevel: 0}, individualHooks: true}); -// Will select all records that are about to be deleted and emit before- + after- Destroy on each instance - -Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: true}); -// Will select all records that are about to be updated and emit before- + after- Update on each instance -``` - -The `options` argument of hook method would be the second argument provided to the corresponding method or its -cloned and extended version. - -```js -Model.hooks.add('beforeBulkCreate', (records, {fields}) => { - // records = the first argument sent to .bulkCreate - // fields = one of the second argument fields sent to .bulkCreate -}) - -Model.bulkCreate([ - {username: 'Toni'}, // part of records argument - {username: 'Tobi'} // part of records argument - ], {fields: ['username']} // options parameter -) - -Model.hooks.add('beforeBulkUpdate', ({attributes, where}) => { - // where - in one of the fields of the clone of second argument sent to .update - // attributes - is one of the fields that the clone of second argument of .update would be extended with -}) - -Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/) - -Model.hooks.add('beforeBulkDestroy', ({where, individualHooks}) => { - // individualHooks - default of overridden value of extended clone of second argument sent to Model.destroy - // where - in one of the fields of the clone of second argument sent to Model.destroy -}) - -Model.destroy({ where: {username: 'Tom'}} /*where argument*/) -``` - -If you use `Model.bulkCreate(...)` with the `updateOnDuplicate` option, changes made in the hook to fields that aren't given in the `updateOnDuplicate` array will not be persisted to the database. However it is possible to change the updateOnDuplicate option inside the hook if this is what you want. - -```js -// Bulk updating existing users with updateOnDuplicate option -Users.bulkCreate([ - { id: 1, isMember: true }, - { id: 2, isMember: false } -], { - updateOnDuplicate: ['isMember'] -}); - -User.hooks.add('beforeBulkCreate', (users, options) => { - for (const user of users) { - if (user.isMember) { - user.memberSince = new Date(); - } - } - - // Add memberSince to updateOnDuplicate otherwise the memberSince date wont be - // saved to the database - options.updateOnDuplicate.push('memberSince'); -}); -``` - -## Associations - -For the most part hooks will work the same for instances when being associated except a few things - -1. When using add/set functions the beforeUpdate/afterUpdate hooks will run. -2. The only way to call beforeDestroy/afterDestroy hooks are on associations with `onDelete: 'cascade'` and the option `hooks: true`. For instance: - -```js -class Projects extends Model {} -Projects.init({ - title: DataTypes.STRING -}, { sequelize }); - -class Tasks extends Model {} -Tasks.init({ - title: DataTypes.STRING -}, { sequelize }); - -Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true }); -Tasks.belongsTo(Projects); -``` - -This code will run beforeDestroy/afterDestroy on the Tasks table. Sequelize, by default, will try to optimize your queries as much as possible. When calling cascade on delete, Sequelize will simply execute a - -```sql -DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey -``` - -However, adding `hooks: true` explicitly tells Sequelize that optimization is not of your concern and will perform a `SELECT` on the associated objects and destroy each instance one by one in order to be able to call the hooks with the right parameters. - -If your association is of type `n:m`, you may be interested in firing hooks on the through model when using the `remove` call. Internally, sequelize is using `Model.destroy` resulting in calling the `bulkDestroy` instead of the `before/afterDestroy` hooks on each through instance. - -This can be simply solved by passing `{individualHooks: true}` to the `remove` call, resulting on each hook to be called on each removed through instance object. - -## A Note About Transactions - -Note that many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction _is_ specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet: - -```js -// Here we use the promise-style of async hooks rather than -// the callback. -User.hooks.add('afterCreate', (user, options) => { - // 'transaction' will be available in options.transaction - - // This operation will be part of the same transaction as the - // original User.create call. - return User.update({ - mood: 'sad' - }, { - where: { - id: user.id - }, - transaction: options.transaction - }); -}); - - -sequelize.transaction(transaction => { - User.create({ - username: 'someguy', - mood: 'happy', - transaction - }); -}); -``` - -If we had not included the transaction option in our call to `User.update` in the preceding code, no change would have occurred, since our newly created user does not exist in the database until the pending transaction has been committed. - -### Internal Transactions - -It is very important to recognize that sequelize may make use of transactions internally for certain operations such as `Model.findOrCreate`. If your hook functions execute read or write operations that rely on the object's presence in the database, or modify the object's stored values like the example in the preceding section, you should always specify `{ transaction: options.transaction }`. - -If the hook has been called in the process of a transacted operation, this makes sure that your dependent read/write is a part of that same transaction. If the hook is not transacted, you have simply specified `{ transaction: null }` and can expect the default behaviour. diff --git a/docs/manual/instances.md b/docs/manual/instances.md deleted file mode 100644 index b380eba24aa0..000000000000 --- a/docs/manual/instances.md +++ /dev/null @@ -1,407 +0,0 @@ -# Instances - -## Building a non-persistent instance - -In order to create instances of defined classes just do as follows. You might recognize the syntax if you coded Ruby in the past. Using the `build`-method will return an unsaved object, which you explicitly have to save. - -```js -const project = new Project({ - title: 'my awesome project', - description: 'woot woot. this will make me a rich man' -}) - -const task = new Task({ - title: 'specify the project idea', - description: 'bla', - deadline: new Date() -}) -``` - -Built instances will automatically get default values when they were defined: - -```js -// first define the model -class Task extends Model {} -Task.init({ - title: Sequelize.STRING, - rating: { type: Sequelize.TINYINT, defaultValue: 3 } -}, { sequelize, modelName: 'task' }); - -// now instantiate an object -const task = new Task({title: 'very important task'}) - -task.title // ==> 'very important task' -task.rating // ==> 3 -``` - -To get it stored in the database, use the `save`-method and catch the events ... if needed: - -```js -project.save().then(() => { - // my nice callback stuff -}) - -task.save().catch(error => { - // mhhh, wth! -}) - -// you can also build, save and access the object with chaining: -new Task({ title: 'foo', description: 'bar', deadline: new Date() }) - .save() - .then(anotherTask => { - // you can now access the currently saved task with the variable anotherTask... nice! - }) - .catch(error => { - // Ooops, do some error-handling - }) -``` - -## Creating persistent instances - -While an instance created with `new` requires an explicit `.save()` call to be stored in the database, `.create()` omits that requirement altogether and automatically stores your instance's data once called. - -```js -Task.create({ title: 'foo', description: 'bar', deadline: new Date() }).then(task => { - // you can now access the newly created task via the variable task -}) -``` - -It is also possible to define which attributes can be set via the create method. This can be especially very handy if you create database entries based on a form which can be filled by a user. Using that would for example allow you to restrict the `User` model to set only a username and an address but not an admin flag: - -```js -User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] }).then(user => { - // let's assume the default of isAdmin is false: - console.log(user.get({ - plain: true - })) // => { username: 'barfooz', isAdmin: false } -}) -``` - -## Updating / Saving / Persisting an instance - -Now lets change some values and save changes to the database... There are two ways to do that: - -```js -// way 1 -task.title = 'a very different title now' -task.save().then(() => {}) - -// way 2 -task.update({ - title: 'a very different title now' -}).then(() => {}) -``` - -It's also possible to define which attributes should be saved when calling `save`, by passing an array of column names. This is useful when you set attributes based on a previously defined object. E.g. if you get the values of an object via a form of a web app. Furthermore this is used internally for `update`. This is how it looks like: - -```js -task.title = 'foooo' -task.description = 'baaaaaar' -task.save({fields: ['title']}).then(() => { - // title will now be 'foooo' but description is the very same as before -}) - -// The equivalent call using update looks like this: -task.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']}).then(() => { - // title will now be 'foooo' but description is the very same as before -}) -``` - -When you call `save` without changing any attribute, this method will execute nothing; - -## Destroying / Deleting persistent instances - -Once you created an object and got a reference to it, you can delete it from the database. The relevant method is `destroy`: - -```js -Task.create({ title: 'a task' }).then(task => { - // now you see me... - return task.destroy(); -}).then(() => { - // now i'm gone :) -}) -``` - -If the `paranoid` options is true, the object will not be deleted, instead the `deletedAt` column will be set to the current timestamp. To force the deletion, you can pass `force: true` to the destroy call: - -```js -task.destroy({ force: true }) -``` - -After an object is soft deleted in `paranoid` mode, you will not be able to create a new instance with the same primary key -until you have force-deleted the old instance. - -## Restoring soft-deleted instances - -If you have soft-deleted an instance of a model with `paranoid: true`, and would like to undo the deletion, use the `restore` method: - -```js -Task.create({ title: 'a task' }).then(task => { - // now you see me... - return task.destroy(); -}).then((task) => { -  // now i'm gone, but wait... - return task.restore(); -}) -``` - -## Working in bulk (creating, updating and destroying multiple rows at once) - -In addition to updating a single instance, you can also create, update, and delete multiple instances at once. The functions you are looking for are called - -* `Model.bulkCreate` -* `Model.update` -* `Model.destroy` - -Since you are working with multiple models, the callbacks will not return DAO instances. BulkCreate will return an array of model instances/DAOs, they will however, unlike `create`, not have the resulting values of autoIncrement attributes.`update` and `destroy` will return the number of affected rows. - -First lets look at bulkCreate - -```js -User.bulkCreate([ - { username: 'barfooz', isAdmin: true }, - { username: 'foo', isAdmin: true }, - { username: 'bar', isAdmin: false } -]).then(() => { // Notice: There are no arguments here, as of right now you'll have to... - return User.findAll(); -}).then(users => { - console.log(users) // ... in order to get the array of user objects -}) -``` - -Insert several rows and return all columns (Postgres only): - -```js -User.bulkCreate([ - { username: 'barfooz', isAdmin: true }, - { username: 'foo', isAdmin: true }, - { username: 'bar', isAdmin: false } -], { returning: true }) // will return all columns for each row inserted -.then((result) => { - console.log(result); -}); -``` - -Insert several rows and return specific columns (Postgres only): - -```js -User.bulkCreate([ - { username: 'barfooz', isAdmin: true }, - { username: 'foo', isAdmin: true }, - { username: 'bar', isAdmin: false } -], { returning: ['username'] }) // will return only the specified columns for each row inserted -.then((result) => { - console.log(result); -}); -``` - -To update several rows at once: - -```js -Task.bulkCreate([ - {subject: 'programming', status: 'executing'}, - {subject: 'reading', status: 'executing'}, - {subject: 'programming', status: 'finished'} -]).then(() => { - return Task.update( - { status: 'inactive' }, /* set attributes' value */ - { where: { subject: 'programming' }} /* where criteria */ - ); -}).then(([affectedCount, affectedRows]) => { - // Notice that affectedRows will only be defined in dialects which support returning: true - - // affectedCount will be 2 - return Task.findAll(); -}).then(tasks => { - console.log(tasks) // the 'programming' tasks will both have a status of 'inactive' -}) -``` - -And delete them: - -```js -Task.bulkCreate([ - {subject: 'programming', status: 'executing'}, - {subject: 'reading', status: 'executing'}, - {subject: 'programming', status: 'finished'} -]).then(() => { - return Task.destroy({ - where: { - subject: 'programming' - }, - truncate: true /* this will ignore where and truncate the table instead */ - }); -}).then(affectedRows => { - // affectedRows will be 2 - return Task.findAll(); -}).then(tasks => { - console.log(tasks) // no programming, just reading :( -}) -``` - -If you are accepting values directly from the user, it might be beneficial to limit the columns that you want to actually insert.`bulkCreate()`accepts an options object as the second parameter. The object can have a `fields` parameter, (an array) to let it know which fields you want to build explicitly - -```js -User.bulkCreate([ - { username: 'foo' }, - { username: 'bar', admin: true} -], { fields: ['username'] }).then(() => { - // nope bar, you can't be admin! -}) -``` - -`bulkCreate` was originally made to be a mainstream/fast way of inserting records, however, sometimes you want the luxury of being able to insert multiple rows at once without sacrificing model validations even when you explicitly tell Sequelize which columns to sift through. You can do by adding a `validate: true` property to the options object. - -```js -class Tasks extends Model {} -Tasks.init({ - name: { - type: Sequelize.STRING, - validate: { - notNull: { args: true, msg: 'name cannot be null' } - } - }, - code: { - type: Sequelize.STRING, - validate: { - len: [3, 10] - } - } -}, { sequelize, modelName: 'tasks' }) - -Tasks.bulkCreate([ - {name: 'foo', code: '123'}, - {code: '1234'}, - {name: 'bar', code: '1'} -], { validate: true }).catch(errors => { - /* console.log(errors) would look like: - [ - { record: - ... - name: 'SequelizeBulkRecordError', - message: 'Validation error', - errors: - { name: 'SequelizeValidationError', - message: 'Validation error', - errors: [Object] } }, - { record: - ... - name: 'SequelizeBulkRecordError', - message: 'Validation error', - errors: - { name: 'SequelizeValidationError', - message: 'Validation error', - errors: [Object] } } - ] - */ -}) -``` - -## Values of an instance - -If you log an instance you will notice, that there is a lot of additional stuff. In order to hide such stuff and reduce it to the very interesting information, you can use the`get`-attribute. Calling it with the option `plain` = true will only return the values of an instance. - -```js -Person.create({ - name: 'Rambow', - firstname: 'John' -}).then(john => { - console.log(john.get({ - plain: true - })) -}) - -// result: - -// { name: 'Rambow', -// firstname: 'John', -// id: 1, -// createdAt: Tue, 01 May 2012 19:12:16 GMT, -// updatedAt: Tue, 01 May 2012 19:12:16 GMT -// } -``` - -**Hint:**You can also transform an instance into JSON by using `JSON.stringify(instance)`. This will basically return the very same as `values`. - -## Reloading instances - -If you need to get your instance in sync, you can use the method`reload`. It will fetch the current data from the database and overwrite the attributes of the model on which the method has been called on. - -```js -Person.findOne({ where: { name: 'john' } }).then(person => { - person.name = 'jane' - console.log(person.name) // 'jane' - - person.reload().then(() => { - console.log(person.name) // 'john' - }) -}) -``` - -## Incrementing - -In order to increment values of an instance without running into concurrency issues, you may use `increment`. - -First of all you can define a field and the value you want to add to it. - -```js -User.findByPk(1).then(user => { - return user.increment('my-integer-field', {by: 2}) -}).then(user => { - // Postgres will return the updated user by default (unless disabled by setting { returning: false }) - // In other dialects, you'll want to call user.reload() to get the updated instance... -}) -``` - -Second, you can define multiple fields and the value you want to add to them. - -```js -User.findByPk(1).then(user => { - return user.increment([ 'my-integer-field', 'my-very-other-field' ], {by: 2}) -}).then(/* ... */) -``` - -Third, you can define an object containing fields and its increment values. - -```js -User.findByPk(1).then(user => { - return user.increment({ - 'my-integer-field': 2, - 'my-very-other-field': 3 - }) -}).then(/* ... */) -``` - -## Decrementing - -In order to decrement values of an instance without running into concurrency issues, you may use `decrement`. - -First of all you can define a field and the value you want to add to it. - -```js -User.findByPk(1).then(user => { - return user.decrement('my-integer-field', {by: 2}) -}).then(user => { - // Postgres will return the updated user by default (unless disabled by setting { returning: false }) - // In other dialects, you'll want to call user.reload() to get the updated instance... -}) -``` - -Second, you can define multiple fields and the value you want to add to them. - -```js -User.findByPk(1).then(user => { - return user.decrement([ 'my-integer-field', 'my-very-other-field' ], {by: 2}) -}).then(/* ... */) -``` - -Third, you can define an object containing fields and its decrement values. - -```js -User.findByPk(1).then(user => { - return user.decrement({ - 'my-integer-field': 2, - 'my-very-other-field': 3 - }) -}).then(/* ... */) -``` diff --git a/docs/manual/migrations.md b/docs/manual/migrations.md deleted file mode 100644 index 30ab89c175d9..000000000000 --- a/docs/manual/migrations.md +++ /dev/null @@ -1,651 +0,0 @@ -# Migrations - -Just like you use Git / SVN to manage changes in your source code, you can use migrations to keep track of changes to the database. With migrations you can transfer your existing database into another state and vice versa: Those state transitions are saved in migration files, which describe how to get to the new state and how to revert the changes in order to get back to the old state. - -You will need [Sequelize CLI][0]. The CLI ships support for migrations and project bootstrapping. - -## The CLI - -### Installing CLI - -Let's start with installing CLI, you can find instructions [here][0]. Most preferred way is installing locally like this - -```bash -$ npm install --save sequelize-cli -``` - -### Bootstrapping - -To create an empty project you will need to execute `init` command - -```bash -$ npx sequelize-cli init -``` - -This will create following folders - -- `config`, contains config file, which tells CLI how to connect with database -- `models`, contains all models for your project -- `migrations`, contains all migration files -- `seeders`, contains all seed files - -#### Configuration - -Before continuing further we will need to tell CLI how to connect to database. To do that let's open default config file `config/config.json`. It looks something like this - -```json -{ - "development": { - "username": "root", - "password": null, - "database": "database_development", - "host": "127.0.0.1", - "dialect": "mysql" - }, - "test": { - "username": "root", - "password": null, - "database": "database_test", - "host": "127.0.0.1", - "dialect": "mysql" - }, - "production": { - "username": "root", - "password": null, - "database": "database_production", - "host": "127.0.0.1", - "dialect": "mysql" - } -} -``` - -Now edit this file and set correct database credentials and dialect. The keys of the objects(ex. "development") are used on `model/index.js` for matching `process.env.NODE_ENV` (When undefined, "development" is a default value.). - -**Note:** _If your database doesn't exists yet, you can just call `db:create` command. With proper access it will create that database for you._ - -### Creating first Model (and Migration) - -Once you have properly configured CLI config file you are ready to create your first migration. It's as simple as executing a simple command. - -We will use `model:generate` command. This command requires two options - -- `name`, Name of the model -- `attributes`, List of model attributes - -Let's create a model named `User`. - -```bash -$ npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string -``` - -This will do following - -- Create a model file `user` in `models` folder -- Create a migration file with name like `XXXXXXXXXXXXXX-create-user.js` in `migrations` folder - -**Note:** _Sequelize will only use Model files, it's the table representation. On the other hand, the migration file is a change in that model or more specifically that table, used by CLI. Treat migrations like a commit or a log for some change in database._ - -### Running Migrations - -Until this step, we haven't inserted anything into the database. We have just created required model and migration files for our first model `User`. Now to actually create that table in database you need to run `db:migrate` command. - -```bash -$ npx sequelize-cli db:migrate -``` - -This command will execute these steps: - -- Will ensure a table called `SequelizeMeta` in database. This table is used to record which migrations have run on the current database -- Start looking for any migration files which haven't run yet. This is possible by checking `SequelizeMeta` table. In this case it will run `XXXXXXXXXXXXXX-create-user.js` migration, which we created in last step. -- Creates a table called `Users` with all columns as specified in its migration file. - -### Undoing Migrations - -Now our table has been created and saved in database. With migration you can revert to old state by just running a command. - -You can use `db:migrate:undo`, this command will revert most recent migration. - -```bash -$ npx sequelize-cli db:migrate:undo -``` - -You can revert back to initial state by undoing all migrations with `db:migrate:undo:all` command. You can also revert back to a specific migration by passing its name in `--to` option. - -```bash -$ npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js -``` - -### Creating First Seed - -Suppose we want to insert some data into a few tables by default. If we follow up on previous example we can consider creating a demo user for `User` table. - -To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database table with sample data or test data. - -Let's create a seed file which will add a demo user to our `User` table. - -```bash -$ npx sequelize-cli seed:generate --name demo-user -``` - -This command will create a seed file in `seeders` folder. File name will look something like `XXXXXXXXXXXXXX-demo-user.js`. It follows the same `up / down` semantics as the migration files. - -Now we should edit this file to insert demo user to `User` table. - -```js -'use strict'; - -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.bulkInsert('Users', [{ - firstName: 'John', - lastName: 'Doe', - email: 'demo@demo.com', - createdAt: new Date(), - updatedAt: new Date() - }], {}); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.bulkDelete('Users', null, {}); - } -}; - -``` - -### Running Seeds - -In last step you have create a seed file. It's still not committed to database. To do that we need to run a simple command. - -```bash -$ npx sequelize-cli db:seed:all -``` - -This will execute that seed file and you will have a demo user inserted into `User` table. - -**Note:** _Seeders execution is not stored anywhere unlike migrations, which use the `SequelizeMeta` table. If you wish to override this please read `Storage` section_ - -### Undoing Seeds - -Seeders can be undone if they are using any storage. There are two commands available for that: - -If you wish to undo most recent seed - -```bash -$ npx sequelize-cli db:seed:undo -``` - -If you wish to undo a specific seed - -```bash -$ npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data -``` - -If you wish to undo all seeds - -```bash -$ npx sequelize-cli db:seed:undo:all -``` - -## Advance Topics - -### Migration Skeleton - -The following skeleton shows a typical migration file. - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - // logic for transforming into the new state - }, - - down: (queryInterface, Sequelize) => { - // logic for reverting the changes - } -} -``` - -We can generate this file using `migration:generate`. This will create `xxx-migration-skeleton.js` in your migration folder. - -```bash -$ npx sequelize-cli migration:generate --name migration-skeleton -``` - -The passed `queryInterface` object can be used to modify the database. The `Sequelize` object stores the available data types such as `STRING` or `INTEGER`. Function `up` or `down` should return a `Promise`. Let's look at an example: - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('Person', { - name: Sequelize.STRING, - isBetaMember: { - type: Sequelize.BOOLEAN, - defaultValue: false, - allowNull: false - } - }); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Person'); - } -} -``` - -The following is an example of a migration that performs two changes in the database, using a transaction to ensure that all instructions are successfully executed or rolled back in case of failure: - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.addColumn('Person', 'petName', { - type: Sequelize.STRING - }, { transaction: t }), - queryInterface.addColumn('Person', 'favoriteColor', { - type: Sequelize.STRING, - }, { transaction: t }) - ]) - }) - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.removeColumn('Person', 'petName', { transaction: t }), - queryInterface.removeColumn('Person', 'favoriteColor', { transaction: t }) - ]) - }) - } -}; -``` - -The next is an example of a migration that has a foreign key. You can use references to specify a foreign key: - -```js -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('Person', { - name: Sequelize.STRING, - isBetaMember: { - type: Sequelize.BOOLEAN, - defaultValue: false, - allowNull: false - }, - userId: { - type: Sequelize.INTEGER, - references: { - model: { - tableName: 'users', - schema: 'schema' - } - key: 'id' - }, - allowNull: false - }, - }); - }, - - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Person'); - } -} - -``` - -The next is an example of a migration that has uses async/await where you create an unique index on a new column: - -```js -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.addColumn( - 'Person', - 'petName', - { - type: Sequelize.STRING, - }, - { transaction } - ); - await queryInterface.addIndex( - 'Person', - 'petName', - { - fields: 'petName', - unique: true, - }, - { transaction } - ); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.removeColumn('Person', 'petName', { transaction }); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; -``` - -### The `.sequelizerc` File - -This is a special configuration file. It lets you specify various options that you would usually pass as arguments to CLI. Some scenarios where you can use it. - -- You want to override default path to `migrations`, `models`, `seeders` or `config` folder. -- You want to rename `config.json` to something else like `database.json` - -And a whole lot more. Let's see how you can use this file for custom configuration. - -For starters, let's create an empty file in the root directory of your project. - -```bash -$ touch .sequelizerc -``` - -Now let's work with an example config. - -```js -const path = require('path'); - -module.exports = { - 'config': path.resolve('config', 'database.json'), - 'models-path': path.resolve('db', 'models'), - 'seeders-path': path.resolve('db', 'seeders'), - 'migrations-path': path.resolve('db', 'migrations') -} -``` - -With this config you are telling CLI to - -- Use `config/database.json` file for config settings -- Use `db/models` as models folder -- Use `db/seeders` as seeders folder -- Use `db/migrations` as migrations folder - -### Dynamic Configuration - -Configuration file is by default a JSON file called `config.json`. But sometimes you want to execute some code or access environment variables which is not possible in JSON files. - -Sequelize CLI can read from both `JSON` and `JS` files. This can be setup with `.sequelizerc` file. Let see how - -First you need to create a `.sequelizerc` file in the root folder of your project. This file should override config path to a `JS` file. Like this - -```js -const path = require('path'); - -module.exports = { - 'config': path.resolve('config', 'config.js') -} -``` - -Now Sequelize CLI will load `config/config.js` for getting configuration options. Since this is a JS file you can have any code executed and export final dynamic configuration file. - -An example of `config/config.js` file - -```js -const fs = require('fs'); - -module.exports = { - development: { - username: 'database_dev', - password: 'database_dev', - database: 'database_dev', - host: '127.0.0.1', - dialect: 'mysql' - }, - test: { - username: 'database_test', - password: null, - database: 'database_test', - host: '127.0.0.1', - dialect: 'mysql' - }, - production: { - username: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - host: process.env.DB_HOSTNAME, - dialect: 'mysql', - dialectOptions: { - ssl: { - ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') - } - } - } -}; -``` - -### Using Babel - -Now you know how to use `.sequelizerc` file. Now let's see how to use this file to use babel with `sequelize-cli` setup. This will allow you to write migrations and seeders with ES6/ES7 syntax. - -First install `babel-register` - -```bash -$ npm i --save-dev babel-register -``` - -Now let's create `.sequelizerc` file, it can include any configuration you may want to change for `sequelize-cli` but in addition to that we want it to register babel for our codebase. Something like this - -```bash -$ touch .sequelizerc # Create rc file -``` - -Now include `babel-register` setup in this file - -```js -require("babel-register"); - -const path = require('path'); - -module.exports = { - 'config': path.resolve('config', 'config.json'), - 'models-path': path.resolve('models'), - 'seeders-path': path.resolve('seeders'), - 'migrations-path': path.resolve('migrations') -} -``` - -Now CLI will be able to run ES6/ES7 code from migrations/seeders etc. Please keep in mind this depends upon your configuration of `.babelrc`. Please read more about that at [babeljs.io](https://babeljs.io). - -### Using Environment Variables - -With CLI you can directly access the environment variables inside the `config/config.js`. You can use `.sequelizerc` to tell CLI to use `config/config.js` for configuration. This is explained in last section. - -Then you can just expose file with proper environment variables. - -```js -module.exports = { - development: { - username: 'database_dev', - password: 'database_dev', - database: 'database_dev', - host: '127.0.0.1', - dialect: 'mysql' - }, - test: { - username: process.env.CI_DB_USERNAME, - password: process.env.CI_DB_PASSWORD, - database: process.env.CI_DB_NAME, - host: '127.0.0.1', - dialect: 'mysql' - }, - production: { - username: process.env.PROD_DB_USERNAME, - password: process.env.PROD_DB_PASSWORD, - database: process.env.PROD_DB_NAME, - host: process.env.PROD_DB_HOSTNAME, - dialect: 'mysql' - } -}; -``` - -### Specifying Dialect Options - -Sometime you want to specify a dialectOption, if it's a general config you can just add it in `config/config.json`. Sometime you want to execute some code to get dialectOptions, you should use dynamic config file for those cases. - -```json -{ - "production": { - "dialect":"mysql", - "dialectOptions": { - "bigNumberStrings": true - } - } -} -``` - -### Production Usages - -Some tips around using CLI and migration setup in production environment. - -1) Use environment variables for config settings. This is better achieved with dynamic configuration. A sample production safe configuration may look like. - -```js -const fs = require('fs'); - -module.exports = { - development: { - username: 'database_dev', - password: 'database_dev', - database: 'database_dev', - host: '127.0.0.1', - dialect: 'mysql' - }, - test: { - username: 'database_test', - password: null, - database: 'database_test', - host: '127.0.0.1', - dialect: 'mysql' - }, - production: { - username: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - host: process.env.DB_HOSTNAME, - dialect: 'mysql', - dialectOptions: { - ssl: { - ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt') - } - } - } -}; -``` - -Our goal is to use environment variables for various database secrets and not accidentally check them in to source control. - -### Storage - -There are three types of storage that you can use: `sequelize`, `json`, and `none`. - -- `sequelize` : stores migrations and seeds in a table on the sequelize database -- `json` : stores migrations and seeds on a json file -- `none` : does not store any migration/seed - -#### Migration Storage - -By default the CLI will create a table in your database called `SequelizeMeta` containing an entry -for each executed migration. To change this behavior, there are three options you can add to the -configuration file. Using `migrationStorage`, you can choose the type of storage to be used for -migrations. If you choose `json`, you can specify the path of the file using `migrationStoragePath` -or the CLI will write to the file `sequelize-meta.json`. If you want to keep the information in the -database, using `sequelize`, but want to use a different table, you can change the table name using -`migrationStorageTableName`. Also you can define a different schema for the `SequelizeMeta` table by -providing the `migrationStorageTableSchema` property. - -```json -{ - "development": { - "username": "root", - "password": null, - "database": "database_development", - "host": "127.0.0.1", - "dialect": "mysql", - - // Use a different storage type. Default: sequelize - "migrationStorage": "json", - - // Use a different file name. Default: sequelize-meta.json - "migrationStoragePath": "sequelizeMeta.json", - - // Use a different table name. Default: SequelizeMeta - "migrationStorageTableName": "sequelize_meta", - - // Use a different schema for the SequelizeMeta table - "migrationStorageTableSchema": "custom_schema" - } -} -``` - -**Note:** _The `none` storage is not recommended as a migration storage. If you decide to use it, be -aware of the implications of having no record of what migrations did or didn't run._ - -#### Seed Storage - -By default the CLI will not save any seed that is executed. If you choose to change this behavior (!), -you can use `seederStorage` in the configuration file to change the storage type. If you choose `json`, -you can specify the path of the file using `seederStoragePath` or the CLI will write to the file -`sequelize-data.json`. If you want to keep the information in the database, using `sequelize`, you can -specify the table name using `seederStorageTableName`, or it will default to `SequelizeData`. - -```json -{ - "development": { - "username": "root", - "password": null, - "database": "database_development", - "host": "127.0.0.1", - "dialect": "mysql", - // Use a different storage. Default: none - "seederStorage": "json", - // Use a different file name. Default: sequelize-data.json - "seederStoragePath": "sequelizeData.json", - // Use a different table name. Default: SequelizeData - "seederStorageTableName": "sequelize_data" - } -} -``` - -### Configuration Connection String - -As an alternative to the `--config` option with configuration files defining your database, you can -use the `--url` option to pass in a connection string. For example: - -```bash -$ npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name' -``` - -### Passing Dialect Specific Options - -```json -{ - "production": { - "dialect":"postgres", - "dialectOptions": { - // dialect options like SSL etc here - } - } -} -``` - -### Programmatic use - -Sequelize has a [sister library][1] for programmatically handling execution and logging of migration tasks. - -## Query Interface - -Using `queryInterface` object described before you can change database schema. To see full list of public methods it supports check [QueryInterface API][2] - -[0]: https://github.com/sequelize/cli -[1]: https://github.com/sequelize/umzug -[2]: ../class/lib/query-interface.js~QueryInterface.html diff --git a/docs/manual/models-definition.md b/docs/manual/models-definition.md deleted file mode 100644 index 94b367cbe8d0..000000000000 --- a/docs/manual/models-definition.md +++ /dev/null @@ -1,727 +0,0 @@ -# Model definition - -To define mappings between a model and a table, use the `define` method. Each column must have a datatype, see more about [datatypes][1]. - -```js -class Project extends Model {} -Project.init({ - title: Sequelize.STRING, - description: Sequelize.TEXT -}, { sequelize, modelName: 'project' }); - -class Task extends Model {} -Task.init({ - title: Sequelize.STRING, - description: Sequelize.TEXT, - deadline: Sequelize.DATE -}, { sequelize, modelName: 'task' }) -``` - -Apart from [datatypes][1], there are plenty of options that you can set on each column. - -```js -class Foo extends Model {} -Foo.init({ - // instantiating will automatically set the flag to true if not set - flag: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true }, - - // default values for dates => current time - myDate: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }, - - // setting allowNull to false will add NOT NULL to the column, which means an error will be - // thrown from the DB when the query is executed if the column is null. If you want to check that a value - // is not null before querying the DB, look at the validations section below. - title: { type: Sequelize.STRING, allowNull: false }, - - // Creating two objects with the same value will throw an error. The unique property can be either a - // boolean, or a string. If you provide the same string for multiple columns, they will form a - // composite unique key. - uniqueOne: { type: Sequelize.STRING, unique: 'compositeIndex' }, - uniqueTwo: { type: Sequelize.INTEGER, unique: 'compositeIndex' }, - - // The unique property is simply a shorthand to create a unique constraint. - someUnique: { type: Sequelize.STRING, unique: true }, - - // It's exactly the same as creating the index in the model's options. - { someUnique: { type: Sequelize.STRING } }, - { indexes: [ { unique: true, fields: [ 'someUnique' ] } ] }, - - // Go on reading for further information about primary keys - identifier: { type: Sequelize.STRING, primaryKey: true }, - - // autoIncrement can be used to create auto_incrementing integer columns - incrementMe: { type: Sequelize.INTEGER, autoIncrement: true }, - - // You can specify a custom column name via the 'field' attribute: - fieldWithUnderscores: { type: Sequelize.STRING, field: 'field_with_underscores' }, - - // It is possible to create foreign keys: - bar_id: { - type: Sequelize.INTEGER, - - references: { - // This is a reference to another model - model: Bar, - - // This is the column name of the referenced model - key: 'id', - - // This declares when to check the foreign key constraint. PostgreSQL only. - deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE - } - }, - - // It is possible to add comments on columns for MySQL, PostgreSQL and MSSQL only - commentMe: { - type: Sequelize.INTEGER, - - comment: 'This is a column name that has a comment' - } -}, { - sequelize, - modelName: 'foo' -}); -``` - -The comment option can also be used on a table, see [model configuration][0]. - -## Timestamps - -By default, Sequelize will add the attributes `createdAt` and `updatedAt` to your model so you will be able to know when the database entry went into the db and when it was updated last. - -Note that if you are using Sequelize migrations you will need to add the `createdAt` and `updatedAt` fields to your migration definition: - -```js -module.exports = { - up(queryInterface, Sequelize) { - return queryInterface.createTable('my-table', { - id: { - type: Sequelize.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - - // Timestamps - createdAt: Sequelize.DATE, - updatedAt: Sequelize.DATE, - }) - }, - down(queryInterface, Sequelize) { - return queryInterface.dropTable('my-table'); - }, -} - -``` - -If you do not want timestamps on your models, only want some timestamps, or you are working with an existing database where the columns are named something else, jump straight on to [configuration][0] to see how to do that. - -## Deferrable - -When you specify a foreign key column it is optionally possible to declare the deferrable -type in PostgreSQL. The following options are available: - -```js -// Defer all foreign key constraint check to the end of a transaction -Sequelize.Deferrable.INITIALLY_DEFERRED - -// Immediately check the foreign key constraints -Sequelize.Deferrable.INITIALLY_IMMEDIATE - -// Don't defer the checks at all -Sequelize.Deferrable.NOT -``` - -The last option is the default in PostgreSQL and won't allow you to dynamically change -the rule in a transaction. See [the transaction section](transactions.html#options) for further information. - -## Getters & setters - -It is possible to define 'object-property' getters and setter functions on your models, these can be used both for 'protecting' properties that map to database fields and for defining 'pseudo' properties. - -Getters and Setters can be defined in 2 ways (you can mix and match these 2 approaches): - -* as part of a single property definition -* as part of a model options - -**N.B:** If a getter or setter is defined in both places then the function found in the relevant property definition will always take precedence. - -### Defining as part of a property - -```js -class Employee extends Model {} -Employee.init({ - name: { - type: Sequelize.STRING, - allowNull: false, - get() { - const title = this.getDataValue('title'); - // 'this' allows you to access attributes of the instance - return this.getDataValue('name') + ' (' + title + ')'; - }, - }, - title: { - type: Sequelize.STRING, - allowNull: false, - set(val) { - this.setDataValue('title', val.toUpperCase()); - } - } -}, { sequelize, modelName: 'employee' }); - -Employee - .create({ name: 'John Doe', title: 'senior engineer' }) - .then(employee => { - console.log(employee.get('name')); // John Doe (SENIOR ENGINEER) - console.log(employee.get('title')); // SENIOR ENGINEER - }) -``` - -### Defining as part of the model options - -Below is an example of defining the getters and setters in the model options. - -The `fullName` getter, is an example of how you can define pseudo properties on your models - attributes which are not actually part of your database schema. In fact, pseudo properties can be defined in two ways: using model getters, or by using a column with the [`VIRTUAL` datatype](/variable/index.html#static-variable-DataTypes). Virtual datatypes can have validations, while getters for virtual attributes cannot. - -Note that the `this.firstname` and `this.lastname` references in the `fullName` getter function will trigger a call to the respective getter functions. If you do not want that then use the `getDataValue()` method to access the raw value (see below). - -```js -class Foo extends Model { - get fullName() { - return this.firstname + ' ' + this.lastname; - } - - set fullName(value) { - const names = value.split(' '); - this.setDataValue('firstname', names.slice(0, -1).join(' ')); - this.setDataValue('lastname', names.slice(-1).join(' ')); - } -} -Foo.init({ - firstname: Sequelize.STRING, - lastname: Sequelize.STRING -}, { - sequelize, - modelName: 'foo' -}); - -// Or with `sequelize.define` -sequelize.define('Foo', { - firstname: Sequelize.STRING, - lastname: Sequelize.STRING -}, { - getterMethods: { - fullName() { - return this.firstname + ' ' + this.lastname; - } - }, - - setterMethods: { - fullName(value) { - const names = value.split(' '); - - this.setDataValue('firstname', names.slice(0, -1).join(' ')); - this.setDataValue('lastname', names.slice(-1).join(' ')); - } - } -}); -``` - -### Helper functions for use inside getter and setter definitions - -* retrieving an underlying property value - always use `this.getDataValue()` - -```js -/* a getter for 'title' property */ -get() { - return this.getDataValue('title') -} -``` - -* setting an underlying property value - always use `this.setDataValue()` - -```js -/* a setter for 'title' property */ -set(title) { - this.setDataValue('title', title.toString().toLowerCase()); -} -``` - -**N.B:** It is important to stick to using the `setDataValue()` and `getDataValue()` functions (as opposed to accessing the underlying "data values" property directly) - doing so protects your custom getters and setters from changes in the underlying model implementations. - -## Validations - -Model validations allow you to specify format/content/inheritance validations for each attribute of the model. - -Validations are automatically run on `create`, `update` and `save`. You can also call `validate()` to manually validate an instance. - -### Per-attribute validations - -You can define your custom validators or use several built-in validators, implemented by [validator.js][3], as shown below. - -```js -class ValidateMe extends Model {} -ValidateMe.init({ - bar: { - type: Sequelize.STRING, - validate: { - is: ["^[a-z]+$",'i'], // will only allow letters - is: /^[a-z]+$/i, // same as the previous example using real RegExp - not: ["[a-z]",'i'], // will not allow letters - isEmail: true, // checks for email format (foo@bar.com) - isUrl: true, // checks for url format (http://foo.com) - isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format - isIPv4: true, // checks for IPv4 (129.89.23.1) - isIPv6: true, // checks for IPv6 format - isAlpha: true, // will only allow letters - isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail - isNumeric: true, // will only allow numbers - isInt: true, // checks for valid integers - isFloat: true, // checks for valid floating point numbers - isDecimal: true, // checks for any numbers - isLowercase: true, // checks for lowercase - isUppercase: true, // checks for uppercase - notNull: true, // won't allow null - isNull: true, // only allows null - notEmpty: true, // don't allow empty strings - equals: 'specific value', // only allow a specific value - contains: 'foo', // force specific substrings - notIn: [['foo', 'bar']], // check the value is not one of these - isIn: [['foo', 'bar']], // check the value is one of these - notContains: 'bar', // don't allow specific substrings - len: [2,10], // only allow values with length between 2 and 10 - isUUID: 4, // only allow uuids - isDate: true, // only allow date strings - isAfter: "2011-11-05", // only allow date strings after a specific date - isBefore: "2011-11-05", // only allow date strings before a specific date - max: 23, // only allow values <= 23 - min: 23, // only allow values >= 23 - isCreditCard: true, // check for valid credit card numbers - - // Examples of custom validators: - isEven(value) { - if (parseInt(value) % 2 !== 0) { - throw new Error('Only even values are allowed!'); - } - } - isGreaterThanOtherField(value) { - if (parseInt(value) <= parseInt(this.otherField)) { - throw new Error('Bar must be greater than otherField.'); - } - } - } - } -}, { sequelize }); -``` - -Note that where multiple arguments need to be passed to the built-in validation functions, the arguments to be passed must be in an array. But if a single array argument is to be passed, for instance an array of acceptable strings for `isIn`, this will be interpreted as multiple string arguments instead of one array argument. To work around this pass a single-length array of arguments, such as `[['one', 'two']]` as shown above. - -To use a custom error message instead of that provided by [validator.js][3], use an object instead of the plain value or array of arguments, for example a validator which needs no argument can be given a custom message with - -```js -isInt: { - msg: "Must be an integer number of pennies" -} -``` - -or if arguments need to also be passed add an `args` property: - -```js -isIn: { - args: [['en', 'zh']], - msg: "Must be English or Chinese" -} -``` - -When using custom validator functions the error message will be whatever message the thrown `Error` object holds. - -See [the validator.js project][3] for more details on the built in validation methods. - -**Hint:** You can also define a custom function for the logging part. Just pass a function. The first parameter will be the string that is logged. - -### Per-attribute validators and `allowNull` - -If a particular field of a model is set to not allow null (with `allowNull: false`) and that value has been set to `null`, all validators will be skipped and a `ValidationError` will be thrown. - -On the other hand, if it is set to allow null (with `allowNull: true`) and that value has been set to `null`, only the built-in validators will be skipped, while the custom validators will still run. - -This means you can, for instance, have a string field which validates its length to be between 5 and 10 characters, but which also allows `null` (since the length validator will be skipped automatically when the value is `null`): - -```js -class User extends Model {} -User.init({ - username: { - type: Sequelize.STRING, - allowNull: true, - validate: { - len: [5, 10] - } - } -}, { sequelize }); -``` - -You also can conditionally allow `null` values, with a custom validator, since it won't be skipped: - -```js -class User extends Model {} -User.init({ - age: Sequelize.INTEGER, - name: { - type: Sequelize.STRING, - allowNull: true, - validate: { - customValidator(value) { - if (value === null && this.age !== 10) { - throw new Error("name can't be null unless age is 10"); - } - }) - } - } -}, { sequelize }); -``` - -You can customize `allowNull` error message by setting the `notNull` validator: - -```js -class User extends Model {} -User.init({ - name: { - type: Sequelize.STRING, - allowNull: false, - validate: { - notNull: { - msg: 'Please enter your name' - } - } - } -}, { sequelize }); -``` - -### Model-wide validations - -Validations can also be defined to check the model after the field-specific validators. Using this you could, for example, ensure either neither of `latitude` and `longitude` are set or both, and fail if one but not the other is set. - -Model validator methods are called with the model object's context and are deemed to fail if they throw an error, otherwise pass. This is just the same as with custom field-specific validators. - -Any error messages collected are put in the validation result object alongside the field validation errors, with keys named after the failed validation method's key in the `validate` option object. Even though there can only be one error message for each model validation method at any one time, it is presented as a single string error in an array, to maximize consistency with the field errors. - -An example: - -```js -class Pub extends Model {} -Pub.init({ - name: { type: Sequelize.STRING }, - address: { type: Sequelize.STRING }, - latitude: { - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: null, - validate: { min: -90, max: 90 } - }, - longitude: { - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: null, - validate: { min: -180, max: 180 } - }, -}, { - validate: { - bothCoordsOrNone() { - if ((this.latitude === null) !== (this.longitude === null)) { - throw new Error('Require either both latitude and longitude or neither') - } - } - }, - sequelize, -}) -``` - -In this simple case an object fails validation if either latitude or longitude is given, but not both. If we try to build one with an out-of-range latitude and no longitude, `raging_bullock_arms.validate()` might return - -```js -{ - 'latitude': ['Invalid number: latitude'], - 'bothCoordsOrNone': ['Require either both latitude and longitude or neither'] -} -``` - -Such validation could have also been done with a custom validator defined on a single attribute (such as the `latitude` attribute, by checking `(value === null) !== (this.longitude === null)`), but the model-wide validation approach is cleaner. - -## Configuration - -You can also influence the way Sequelize handles your column names: - -```js -class Bar extends Model {} -Bar.init({ /* bla */ }, { - // The name of the model. The model will be stored in `sequelize.models` under this name. - // This defaults to class name i.e. Bar in this case. This will control name of auto-generated - // foreignKey and association naming - modelName: 'bar', - - // don't add the timestamp attributes (updatedAt, createdAt) - timestamps: false, - - // don't delete database entries but set the newly added attribute deletedAt - // to the current date (when deletion was done). paranoid will only work if - // timestamps are enabled - paranoid: true, - - // Will automatically set field option for all attributes to snake cased name. - // Does not override attribute with field option already defined - underscored: true, - - // disable the modification of table names; By default, sequelize will automatically - // transform all passed model names (first parameter of define) into plural. - // if you don't want that, set the following - freezeTableName: true, - - // define the table's name - tableName: 'my_very_custom_table_name', - - // Enable optimistic locking. When enabled, sequelize will add a version count attribute - // to the model and throw an OptimisticLockingError error when stale instances are saved. - // Set to true or a string with the attribute name you want to use to enable. - version: true, - - // Sequelize instance - sequelize, -}) -``` - -If you want sequelize to handle timestamps, but only want some of them, or want your timestamps to be called something else, you can override each column individually: - -```js -class Foo extends Model {} -Foo.init({ /* bla */ }, { - // don't forget to enable timestamps! - timestamps: true, - - // I don't want createdAt - createdAt: false, - - // I want updatedAt to actually be called updateTimestamp - updatedAt: 'updateTimestamp', - - // And deletedAt to be called destroyTime (remember to enable paranoid for this to work) - deletedAt: 'destroyTime', - paranoid: true, - - sequelize, -}) -``` - -You can also change the database engine, e.g. to MyISAM. InnoDB is the default. - -```js -class Person extends Model {} -Person.init({ /* attributes */ }, { - engine: 'MYISAM', - sequelize -}) - -// or globally -const sequelize = new Sequelize(db, user, pw, { - define: { engine: 'MYISAM' } -}) -``` - -Finally you can specify a comment for the table in MySQL and PG - -```js -class Person extends Model {} -Person.init({ /* attributes */ }, { - comment: "I'm a table comment!", - sequelize -}) -``` - -## Import - -You can also store your model definitions in a single file using the `import` method. The returned object is exactly the same as defined in the imported file's function. Since `v1:5.0` of Sequelize the import is cached, so you won't run into troubles when calling the import of a file twice or more often. - -```js -// in your server file - e.g. app.js -const Project = sequelize.import(__dirname + "/path/to/models/project") - -// The model definition is done in /path/to/models/project.js -// As you might notice, the DataTypes are the very same as explained above -module.exports = (sequelize, DataTypes) => { - class Project extends sequelize.Model { } - Project.init({ - name: DataTypes.STRING, - description: DataTypes.TEXT - }, { sequelize }); - return Project; -} -``` - -The `import` method can also accept a callback as an argument. - -```js -sequelize.import('project', (sequelize, DataTypes) => { - class Project extends sequelize.Model {} - Project.init({ - name: DataTypes.STRING, - description: DataTypes.TEXT - }, { sequelize }) - return Project; -}) -``` - -This extra capability is useful when, for example, `Error: Cannot find module` is thrown even though `/path/to/models/project` seems to be correct. Some frameworks, such as Meteor, overload `require`, and spit out "surprise" results like : - -```text -Error: Cannot find module '/home/you/meteorApp/.meteor/local/build/programs/server/app/path/to/models/project.js' -``` - -This is solved by passing in Meteor's version of `require`. So, while this probably fails ... - -```js -const AuthorModel = db.import('./path/to/models/project'); -``` - -... this should succeed ... - -```js -const AuthorModel = db.import('project', require('./path/to/models/project')); -``` - -## Optimistic Locking - -Sequelize has built-in support for optimistic locking through a model instance version count. -Optimistic locking is disabled by default and can be enabled by setting the `version` property to true in a specific model definition or global model configuration. See [model configuration][0] for more details. - -Optimistic locking allows concurrent access to model records for edits and prevents conflicts from overwriting data. It does this by checking whether another process has made changes to a record since it was read and throws an OptimisticLockError when a conflict is detected. - -## Database synchronization - -When starting a new project you won't have a database structure and using Sequelize you won't need to. Just specify your model structures and let the library do the rest. Currently supported is the creation and deletion of tables: - -```js -// Create the tables: -Project.sync() -Task.sync() - -// Force the creation! -Project.sync({force: true}) // this will drop the table first and re-create it afterwards - -// drop the tables: -Project.drop() -Task.drop() - -// event handling: -Project.[sync|drop]().then(() => { - // ok ... everything is nice! -}).catch(error => { - // oooh, did you enter wrong database credentials? -}) -``` - -Because synchronizing and dropping all of your tables might be a lot of lines to write, you can also let Sequelize do the work for you: - -```js -// Sync all models that aren't already in the database -sequelize.sync() - -// Force sync all models -sequelize.sync({force: true}) - -// Drop all tables -sequelize.drop() - -// emit handling: -sequelize.[sync|drop]().then(() => { - // woot woot -}).catch(error => { - // whooops -}) -``` - -Because `.sync({ force: true })` is destructive operation, you can use `match` option as an additional safety check. -`match` option tells sequelize to match a regex against the database name before syncing - a safety check for cases -where `force: true` is used in tests but not live code. - -```js -// This will run .sync() only if database name ends with '_test' -sequelize.sync({ force: true, match: /_test$/ }); -``` - -## Expansion of models - -Sequelize Models are ES6 classes. You can very easily add custom instance or class level methods. - -```js -class User extends Model { - // Adding a class level method - static classLevelMethod() { - return 'foo'; - } - - // Adding an instance level method - instanceLevelMethod() { - return 'bar'; - } -} -User.init({ firstname: Sequelize.STRING }, { sequelize }); -``` - -Of course you can also access the instance's data and generate virtual getters: - -```js -class User extends Model { - getFullname() { - return [this.firstname, this.lastname].join(' '); - } -} -User.init({ firstname: Sequelize.STRING, lastname: Sequelize.STRING }, { sequelize }); - -// Example: -new User({ firstname: 'foo', lastname: 'bar' }).getFullname() // 'foo bar' -``` - -### Indexes - -Sequelize supports adding indexes to the model definition which will be created during `Model.sync()` or `sequelize.sync`. - -```js -class User extends Model {} -User.init({}, { - indexes: [ - // Create a unique index on email - { - unique: true, - fields: ['email'] - }, - - // Creates a gin index on data with the jsonb_path_ops operator - { - fields: ['data'], - using: 'gin', - operator: 'jsonb_path_ops' - }, - - // By default index name will be [table]_[fields] - // Creates a multi column partial index - { - name: 'public_by_author', - fields: ['author', 'status'], - where: { - status: 'public' - } - }, - - // A BTREE index with an ordered field - { - name: 'title_index', - using: 'BTREE', - fields: ['author', {attribute: 'title', collate: 'en_US', order: 'DESC', length: 5}] - } - ], - sequelize -}); -``` - -[0]: models-definition.html#configuration -[1]: data-types.html -[3]: https://github.com/chriso/validator.js -[5]: /docs/final/misc#asynchronicity diff --git a/docs/manual/models-usage.md b/docs/manual/models-usage.md deleted file mode 100644 index dfbdb43db1fb..000000000000 --- a/docs/manual/models-usage.md +++ /dev/null @@ -1,761 +0,0 @@ -# Model usage - -## Data retrieval / Finders - -Finder methods are intended to query data from the database. They do *not* return plain objects but instead return model instances. Because finder methods return model instances you can call any model instance member on the result as described in the documentation for [*instances*](instances.html). - -In this document we'll explore what finder methods can do: - -### `find` - Search for one specific element in the database - -```js -// search for known ids -Project.findByPk(123).then(project => { - // project will be an instance of Project and stores the content of the table entry - // with id 123. if such an entry is not defined you will get null -}) - -// search for attributes -Project.findOne({ where: {title: 'aProject'} }).then(project => { - // project will be the first entry of the Projects table with the title 'aProject' || null -}) - - -Project.findOne({ - where: {title: 'aProject'}, - attributes: ['id', ['name', 'title']] -}).then(project => { - // project will be the first entry of the Projects table with the title 'aProject' || null - // project.get('title') will contain the name of the project -}) -``` - -### `findOrCreate` - Search for a specific element or create it if not available - -The method `findOrCreate` can be used to check if a certain element already exists in the database. If that is the case the method will result in a respective instance. If the element does not yet exist, it will be created. - -Let's assume we have an empty database with a `User` model which has a `username` and a `job`. - -`where` option will be appended to `defaults` for create case. - -```js -User - .findOrCreate({where: {username: 'sdepold'}, defaults: {job: 'Technical Lead JavaScript'}}) - .then(([user, created]) => { - console.log(user.get({ - plain: true - })) - console.log(created) - - /* - findOrCreate returns an array containing the object that was found or created and a boolean that - will be true if a new object was created and false if not, like so: - - [ { - username: 'sdepold', - job: 'Technical Lead JavaScript', - id: 1, - createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), - updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) - }, - true ] - - In the example above, the array spread on line 3 divides the array into its 2 parts and passes them - as arguments to the callback function defined beginning at line 39, which treats them as "user" and - "created" in this case. (So "user" will be the object from index 0 of the returned array and - "created" will equal "true".) - */ - }) -``` - -The code created a new instance. So when we already have an instance ... - -```js -User.create({ username: 'fnord', job: 'omnomnom' }) - .then(() => User.findOrCreate({where: {username: 'fnord'}, defaults: {job: 'something else'}})) - .then(([user, created]) => { - console.log(user.get({ - plain: true - })) - console.log(created) - - /* - In this example, findOrCreate returns an array like this: - [ { - username: 'fnord', - job: 'omnomnom', - id: 2, - createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), - updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) - }, - false - ] - The array returned by findOrCreate gets spread into its 2 parts by the array spread on line 3, and - the parts will be passed as 2 arguments to the callback function beginning on line 69, which will - then treat them as "user" and "created" in this case. (So "user" will be the object from index 0 - of the returned array and "created" will equal "false".) - */ - }) -``` - -... the existing entry will not be changed. See the `job` of the second user, and the fact that created was false. - -### `findAndCountAll` - Search for multiple elements in the database, returns both data and total count - -This is a convenience method that combines`findAll` and `count` (see below) this is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query: - -The success handler will always receive an object with two properties: - -* `count` - an integer, total number records matching the where clause and other filters due to associations -* `rows` - an array of objects, the records matching the where clause and other filters due to associations, within the limit and offset range - -```js -Project - .findAndCountAll({ - where: { - title: { - [Op.like]: 'foo%' - } - }, - offset: 10, - limit: 2 - }) - .then(result => { - console.log(result.count); - console.log(result.rows); - }); -``` - -It support includes. Only the includes that are marked as `required` will be added to the count part: - -Suppose you want to find all users who have a profile attached: - -```js -User.findAndCountAll({ - include: [ - { model: Profile, required: true } - ], - limit: 3 -}); -``` - -Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted. Adding a `where` clause to the include automatically makes it required: - -```js -User.findAndCountAll({ - include: [ - { model: Profile, where: { active: true }} - ], - limit: 3 -}); -``` - -The query above will only count users who have an active profile, because `required` is implicitly set to true when you add a where clause to the include. - -The options object that you pass to `findAndCountAll` is the same as for `findAll` (described below). - -### `findAll` - Search for multiple elements in the database - -```js -// find multiple entries -Project.findAll().then(projects => { - // projects will be an array of all Project instances -}) - -// search for specific attributes - hash usage -Project.findAll({ where: { name: 'A Project' } }).then(projects => { - // projects will be an array of Project instances with the specified name -}) - -// search within a specific range -Project.findAll({ where: { id: [1,2,3] } }).then(projects => { - // projects will be an array of Projects having the id 1, 2 or 3 - // this is actually doing an IN query -}) - -Project.findAll({ - where: { - id: { - [Op.and]: {a: 5}, // AND (a = 5) - [Op.or]: [{a: 5}, {a: 6}], // (a = 5 OR a = 6) - [Op.gt]: 6, // id > 6 - [Op.gte]: 6, // id >= 6 - [Op.lt]: 10, // id < 10 - [Op.lte]: 10, // id <= 10 - [Op.ne]: 20, // id != 20 - [Op.between]: [6, 10], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only) - }, - status: { - [Op.not]: false // status NOT FALSE - } - } -}) -``` - -### Complex filtering / OR / NOT queries - -It's possible to do complex where queries with multiple levels of nested AND, OR and NOT conditions. In order to do that you can use `or`, `and` or `not` `Operators`: - -```js -Project.findOne({ - where: { - name: 'a project', - [Op.or]: [ - { id: [1,2,3] }, - { id: { [Op.gt]: 10 } } - ] - } -}) - -Project.findOne({ - where: { - name: 'a project', - id: { - [Op.or]: [ - [1,2,3], - { [Op.gt]: 10 } - ] - } - } -}) -``` - -Both pieces of code will generate the following: - -```sql -SELECT * -FROM `Projects` -WHERE ( - `Projects`.`name` = 'a project' - AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10) -) -LIMIT 1; -``` - -`not` example: - -```js -Project.findOne({ - where: { - name: 'a project', - [Op.not]: [ - { id: [1,2,3] }, - { array: { [Op.contains]: [3,4,5] } } - ] - } -}); -``` - -Will generate: - -```sql -SELECT * -FROM `Projects` -WHERE ( - `Projects`.`name` = 'a project' - AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[]) -) -LIMIT 1; -``` - -### Manipulating the dataset with limit, offset, order and group - -To get more relevant data, you can use limit, offset, order and grouping: - -```js -// limit the results of the query -Project.findAll({ limit: 10 }) - -// step over the first 10 elements -Project.findAll({ offset: 10 }) - -// step over the first 10 elements, and take 2 -Project.findAll({ offset: 10, limit: 2 }) -``` - -The syntax for grouping and ordering are equal, so below it is only explained with a single example for group, and the rest for order. Everything you see below can also be done for group - -```js -Project.findAll({order: [['title', 'DESC']]}) -// yields ORDER BY title DESC - -Project.findAll({group: 'name'}) -// yields GROUP BY name -``` - -Notice how in the two examples above, the string provided is inserted verbatim into the query, i.e. column names are not escaped. When you provide a string to order/group, this will always be the case. If you want to escape column names, you should provide an array of arguments, even though you only want to order/group by a single column - -```js -something.findOne({ - order: [ - // will return `name` - ['name'], - // will return `username` DESC - ['username', 'DESC'], - // will return max(`age`) - sequelize.fn('max', sequelize.col('age')), - // will return max(`age`) DESC - [sequelize.fn('max', sequelize.col('age')), 'DESC'], - // will return otherfunction(`col1`, 12, 'lalala') DESC - [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], - // will return otherfunction(awesomefunction(`col`)) DESC, This nesting is potentially infinite! - [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'] - ] -}) -``` - -To recap, the elements of the order/group array can be the following: - -* String - will be quoted -* Array - first element will be quoted, second will be appended verbatim -* Object - - * Raw will be added verbatim without quoting - * Everything else is ignored, and if raw is not set, the query will fail -* Sequelize.fn and Sequelize.col returns functions and quoted column names - -### Raw queries - -Sometimes you might be expecting a massive dataset that you just want to display, without manipulation. For each row you select, Sequelize creates an instance with functions for update, delete, get associations etc. If you have thousands of rows, this might take some time. If you only need the raw data and don't want to update anything, you can do like this to get the raw data. - -```js -// Are you expecting a massive dataset from the DB, -// and don't want to spend the time building DAOs for each entry? -// You can pass an extra query option to get the raw data instead: -Project.findAll({ where: { ... }, raw: true }) -``` - -### `count` - Count the occurrences of elements in the database - -There is also a method for counting database objects: - -```js -Project.count().then(c => { - console.log("There are " + c + " projects!") -}) - -Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => { - console.log("There are " + c + " projects with an id greater than 25.") -}) -``` - -### `max` - Get the greatest value of a specific attribute within a specific table - -And here is a method for getting the max value of an attribute - -```js -/* - Let's assume 3 person objects with an attribute age. - The first one is 10 years old, - the second one is 5 years old, - the third one is 40 years old. -*/ -Project.max('age').then(max => { - // this will return 40 -}) - -Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => { - // will be 10 -}) -``` - -### `min` - Get the least value of a specific attribute within a specific table - -And here is a method for getting the min value of an attribute: - -```js -/* - Let's assume 3 person objects with an attribute age. - The first one is 10 years old, - the second one is 5 years old, - the third one is 40 years old. -*/ -Project.min('age').then(min => { - // this will return 5 -}) - -Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => { - // will be 10 -}) -``` - -### `sum` - Sum the value of specific attributes - -In order to calculate the sum over a specific column of a table, you can -use the `sum` method. - -```js -/* - Let's assume 3 person objects with an attribute age. - The first one is 10 years old, - the second one is 5 years old, - the third one is 40 years old. -*/ -Project.sum('age').then(sum => { - // this will return 55 -}) - -Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => { - // will be 50 -}) -``` - -## Eager loading - -When you are retrieving data from the database there is a fair chance that you also want to get associations with the same query - this is called eager loading. The basic idea behind that, is the use of the attribute `include` when you are calling `find` or `findAll`. Lets assume the following setup: - -```js -class User extends Model {} -User.init({ name: Sequelize.STRING }, { sequelize, modelName: 'user' }) -class Task extends Model {} -Task.init({ name: Sequelize.STRING }, { sequelize, modelName: 'task' }) -class Tool extends Model {} -Tool.init({ name: Sequelize.STRING }, { sequelize, modelName: 'tool' }) - -Task.belongsTo(User) -User.hasMany(Task) -User.hasMany(Tool, { as: 'Instruments' }) - -sequelize.sync().then(() => { - // this is where we continue ... -}) -``` - -OK. So, first of all, let's load all tasks with their associated user. - -```js -Task.findAll({ include: [ User ] }).then(tasks => { - console.log(JSON.stringify(tasks)) - - /* - [{ - "name": "A Task", - "id": 1, - "createdAt": "2013-03-20T20:31:40.000Z", - "updatedAt": "2013-03-20T20:31:40.000Z", - "userId": 1, - "user": { - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z" - } - }] - */ -}) -``` - -Notice that the accessor (the `User` property in the resulting instance) is singular because the association is one-to-something. - -Next thing: Loading of data with many-to-something associations! - -```js -User.findAll({ include: [ Task ] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "tasks": [{ - "name": "A Task", - "id": 1, - "createdAt": "2013-03-20T20:31:40.000Z", - "updatedAt": "2013-03-20T20:31:40.000Z", - "userId": 1 - }] - }] - */ -}) -``` - -Notice that the accessor (the `Tasks` property in the resulting instance) is plural because the association is many-to-something. - -If an association is aliased (using the `as` option), you must specify this alias when including the model. Notice how the user's `Tool`s are aliased as `Instruments` above. In order to get that right you have to specify the model you want to load, as well as the alias: - -```js -User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }] - */ -}) -``` - -You can also include by alias name by specifying a string that matches the association alias: - -```js -User.findAll({ include: ['Instruments'] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }] - */ -}) - -User.findAll({ include: [{ association: 'Instruments' }] }).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }] - */ -}) -``` - -When eager loading we can also filter the associated model using `where`. This will return all `User`s in which the `where` clause of `Tool` model matches rows. - -```js -User.findAll({ - include: [{ - model: Tool, - as: 'Instruments', - where: { name: { [Op.like]: '%ooth%' } } - }] -}).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - - [{ - "name": "John Smith", - "id": 2, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - */ - }) -``` - -When an eager loaded model is filtered using `include.where` then `include.required` is implicitly set to -`true`. This means that an inner join is done returning parent models with any matching children. - -### Top level where with eagerly loaded models - -To move the where conditions from an included model from the `ON` condition to the top level `WHERE` you can use the `'$nested.column$'` syntax: - -```js -User.findAll({ - where: { - '$Instruments.name$': { [Op.iLike]: '%ooth%' } - }, - include: [{ - model: Tool, - as: 'Instruments' - }] -}).then(users => { - console.log(JSON.stringify(users)); - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - - [{ - "name": "John Smith", - "id": 2, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1 - }] - }], - */ -``` - -### Including everything - -To include all attributes, you can pass a single object with `all: true`: - -```js -User.findAll({ include: [{ all: true }]}); -``` - -### Including soft deleted records - -In case you want to eager load soft deleted records you can do that by setting `include.paranoid` to `false` - -```js -User.findAll({ - include: [{ - model: Tool, - where: { name: { [Op.like]: '%ooth%' } }, - paranoid: false // query and loads the soft deleted records - }] -}); -``` - -### Ordering Eager Loaded Associations - -In the case of a one-to-many relationship. - -```js -Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] }); -Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] }); -Company.findAll({ - include: [ { model: Division, as: 'Div' } ], - order: [ [ { model: Division, as: 'Div' }, 'name' ] ] -}); -Company.findAll({ - include: [ { model: Division, as: 'Div' } ], - order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ] -}); -Company.findAll({ - include: [ { model: Division, include: [ Department ] } ], - order: [ [ Division, Department, 'name' ] ] -}); -``` - -In the case of many-to-many joins, you are also able to sort by attributes in the through table. - -```js -Company.findAll({ - include: [ { model: Division, include: [ Department ] } ], - order: [ [ Division, DepartmentDivision, 'name' ] ] -}); -``` - -### Nested eager loading - -You can use nested eager loading to load all related models of a related model: - -```js -User.findAll({ - include: [ - {model: Tool, as: 'Instruments', include: [ - {model: Teacher, include: [ /* etc */]} - ]} - ] -}).then(users => { - console.log(JSON.stringify(users)) - - /* - [{ - "name": "John Doe", - "id": 1, - "createdAt": "2013-03-20T20:31:45.000Z", - "updatedAt": "2013-03-20T20:31:45.000Z", - "Instruments": [{ // 1:M and N:M association - "name": "Toothpick", - "id": 1, - "createdAt": null, - "updatedAt": null, - "userId": 1, - "Teacher": { // 1:1 association - "name": "Jimi Hendrix" - } - }] - }] - */ -}) -``` - -This will produce an outer join. However, a `where` clause on a related model will create an inner join and return only the instances that have matching sub-models. To return all parent instances, you should add `required: false`. - -```js -User.findAll({ - include: [{ - model: Tool, - as: 'Instruments', - include: [{ - model: Teacher, - where: { - school: "Woodstock Music School" - }, - required: false - }] - }] -}).then(users => { - /* ... */ -}) -``` - -The query above will return all users, and all their instruments, but only those teachers associated with `Woodstock Music School`. - -Include all also supports nested loading: - -```js -User.findAll({ include: [{ all: true, nested: true }]}); -``` diff --git a/docs/manual/moved/associations.md b/docs/manual/moved/associations.md new file mode 100644 index 000000000000..cf001aac731f --- /dev/null +++ b/docs/manual/moved/associations.md @@ -0,0 +1,16 @@ +# \[MOVED\] Associations + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Associations](assocs.html) +* **Advanced Association Concepts** + * [Eager Loading](eager-loading.html) + * [Creating with Associations](creating-with-associations.html) + * [Advanced M:N Associations](advanced-many-to-many.html) + * [Polymorphism & Scopes](polymorphism-and-scopes.html) +* **Other Topics** + * [Naming Strategies](naming-strategies.html) + * [Constraints & Circularities](constraints-and-circularities.html) \ No newline at end of file diff --git a/docs/manual/moved/data-types.md b/docs/manual/moved/data-types.md new file mode 100644 index 000000000000..4ba48b9798d9 --- /dev/null +++ b/docs/manual/moved/data-types.md @@ -0,0 +1,12 @@ +# \[MOVED\] Data Types + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Model Basics: Data Types](model-basics.html#data-types) +* **Other Topics** + * [Other Data Types](other-data-types.html) + * [Extending Data Types](extending-data-types.html) + * [Dialect-Specific Things](dialect-specific-things.html) \ No newline at end of file diff --git a/docs/manual/moved/models-definition.md b/docs/manual/moved/models-definition.md new file mode 100644 index 000000000000..177e8a28dcc1 --- /dev/null +++ b/docs/manual/moved/models-definition.md @@ -0,0 +1,55 @@ +# \[MOVED\] Models Definition + +The contents of this page were moved to [Model Basics](model-basics.html). + +The only exception is the guide on `sequelize.import`, which is deprecated and was removed from the docs. However, if you really need it, it was kept here. + +---- + +## Deprecated: `sequelize.import` + +> _**Note:** You should not use `sequelize.import`. Please just use `require` instead._ +> +> _This documentation has been kept just in case you really need to maintain old code that uses it._ + +You can store your model definitions in a single file using the `sequelize.import` method. The returned object is exactly the same as defined in the imported file's function. The import is cached, just like `require`, so you won't run into trouble if importing a file more than once. + +```js +// in your server file - e.g. app.js +const Project = sequelize.import(__dirname + "/path/to/models/project"); + +// The model definition is done in /path/to/models/project.js +module.exports = (sequelize, DataTypes) => { + return sequelize.define('project', { + name: DataTypes.STRING, + description: DataTypes.TEXT + }); +}; +``` + +The `import` method can also accept a callback as an argument. + +```js +sequelize.import('project', (sequelize, DataTypes) => { + return sequelize.define('project', { + name: DataTypes.STRING, + description: DataTypes.TEXT + }); +}); +``` + +This extra capability is useful when, for example, `Error: Cannot find module` is thrown even though `/path/to/models/project` seems to be correct. Some frameworks, such as Meteor, overload `require`, and might raise an error such as: + +```text +Error: Cannot find module '/home/you/meteorApp/.meteor/local/build/programs/server/app/path/to/models/project.js' +``` + +This can be worked around by passing in Meteor's version of `require`: + +```js +// If this fails... +const AuthorModel = db.import('./path/to/models/project'); + +// Try this instead! +const AuthorModel = db.import('project', require('./path/to/models/project')); +``` \ No newline at end of file diff --git a/docs/manual/moved/models-usage.md b/docs/manual/moved/models-usage.md new file mode 100644 index 000000000000..020eeacab726 --- /dev/null +++ b/docs/manual/moved/models-usage.md @@ -0,0 +1,12 @@ +# \[MOVED\] Models Usage + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Model Querying - Basics](model-querying-basics.html) + * [Model Querying - Finders](model-querying-finders.html) + * [Raw Queries](raw-queries.html) +* **Advanced Association Concepts** + * [Eager Loading](eager-loading.html) \ No newline at end of file diff --git a/docs/manual/moved/querying.md b/docs/manual/moved/querying.md new file mode 100644 index 000000000000..94b8d8ae9c99 --- /dev/null +++ b/docs/manual/moved/querying.md @@ -0,0 +1,13 @@ +# \[MOVED\] Querying + +The contents of this page were moved to other specialized guides. + +If you're here, you might be looking for these topics: + +* **Core Concepts** + * [Model Querying - Basics](model-querying-basics.html) + * [Model Querying - Finders](model-querying-finders.html) + * [Raw Queries](raw-queries.html) + * [Associations](assocs.html) +* **Other Topics** + * [Dialect-Specific Things](dialect-specific-things.html) \ No newline at end of file diff --git a/docs/manual/other-topics/connection-pool.md b/docs/manual/other-topics/connection-pool.md new file mode 100644 index 000000000000..d8501ef5ef3c --- /dev/null +++ b/docs/manual/other-topics/connection-pool.md @@ -0,0 +1,17 @@ +# Connection Pool + +If you're connecting to the database from a single process, you should create only one Sequelize instance. Sequelize will set up a connection pool on initialization. This connection pool can be configured through the constructor's `options` parameter (using `options.pool`), as is shown in the following example: + +```js +const sequelize = new Sequelize(/* ... */, { + // ... + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } +}); +``` + +Learn more in the [API Reference for the Sequelize constructor](../class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor). If you're connecting to the database from multiple processes, you'll have to create one instance per process, but each instance should have a maximum connection pool size of such that the total maximum size is respected. For example, if you want a max connection pool size of 90 and you have three processes, the Sequelize instance of each process should have a max connection pool size of 30. diff --git a/docs/manual/other-topics/constraints-and-circularities.md b/docs/manual/other-topics/constraints-and-circularities.md new file mode 100644 index 000000000000..c48708a0168f --- /dev/null +++ b/docs/manual/other-topics/constraints-and-circularities.md @@ -0,0 +1,113 @@ +# Constraints & Circularities + +Adding constraints between tables means that tables must be created in the database in a certain order, when using `sequelize.sync`. If `Task` has a reference to `User`, the `User` table must be created before the `Task` table can be created. This can sometimes lead to circular references, where Sequelize cannot find an order in which to sync. Imagine a scenario of documents and versions. A document can have multiple versions, and for convenience, a document has a reference to its current version. + +```js +const { Sequelize, Model, DataTypes } = require("sequelize"); + +class Document extends Model {} +Document.init({ + author: DataTypes.STRING +}, { sequelize, modelName: 'document' }); + +class Version extends Model {} +Version.init({ + timestamp: DataTypes.DATE +}, { sequelize, modelName: 'version' }); + +Document.hasMany(Version); // This adds documentId attribute to version +Document.belongsTo(Version, { + as: 'Current', + foreignKey: 'currentVersionId' +}); // This adds currentVersionId attribute to document +``` + +However, unfortunately the code above will result in the following error: + +```text +Cyclic dependency found. documents is dependent of itself. Dependency chain: documents -> versions => documents +``` + +In order to alleviate that, we can pass `constraints: false` to one of the associations: + +```js +Document.hasMany(Version); +Document.belongsTo(Version, { + as: 'Current', + foreignKey: 'currentVersionId', + constraints: false +}); +``` + +Which will allow us to sync the tables correctly: + +```sql +CREATE TABLE IF NOT EXISTS "documents" ( + "id" SERIAL, + "author" VARCHAR(255), + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "currentVersionId" INTEGER, + PRIMARY KEY ("id") +); + +CREATE TABLE IF NOT EXISTS "versions" ( + "id" SERIAL, + "timestamp" TIMESTAMP WITH TIME ZONE, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "documentId" INTEGER REFERENCES "documents" ("id") ON DELETE + SET + NULL ON UPDATE CASCADE, + PRIMARY KEY ("id") +); +``` + +## Enforcing a foreign key reference without constraints + +Sometimes you may want to reference another table, without adding any constraints, or associations. In that case you can manually add the reference attributes to your schema definition, and mark the relations between them. + +```js +class Trainer extends Model {} +Trainer.init({ + firstName: Sequelize.STRING, + lastName: Sequelize.STRING +}, { sequelize, modelName: 'trainer' }); + +// Series will have a trainerId = Trainer.id foreign reference key +// after we call Trainer.hasMany(series) +class Series extends Model {} +Series.init({ + title: Sequelize.STRING, + subTitle: Sequelize.STRING, + description: Sequelize.TEXT, + // Set FK relationship (hasMany) with `Trainer` + trainerId: { + type: DataTypes.INTEGER, + references: { + model: Trainer, + key: 'id' + } + } +}, { sequelize, modelName: 'series' }); + +// Video will have seriesId = Series.id foreign reference key +// after we call Series.hasOne(Video) +class Video extends Model {} +Video.init({ + title: Sequelize.STRING, + sequence: Sequelize.INTEGER, + description: Sequelize.TEXT, + // set relationship (hasOne) with `Series` + seriesId: { + type: DataTypes.INTEGER, + references: { + model: Series, // Can be both a string representing the table name or a Sequelize model + key: 'id' + } + } +}, { sequelize, modelName: 'video' }); + +Series.hasOne(Video); +Trainer.hasMany(Series); +``` \ No newline at end of file diff --git a/docs/manual/other-topics/dialect-specific-things.md b/docs/manual/other-topics/dialect-specific-things.md new file mode 100644 index 000000000000..d7c5e9427609 --- /dev/null +++ b/docs/manual/other-topics/dialect-specific-things.md @@ -0,0 +1,218 @@ +# Dialect-Specific Things + +## Underlying Connector Libraries + +### MySQL + +The underlying connector library used by Sequelize for MySQL is the [mysql2](https://www.npmjs.com/package/mysql2) npm package (version 1.5.2 or higher). + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'mysql', + dialectOptions: { + // Your mysql2 options here + } +}) +``` + +### MariaDB + +The underlying connector library used by Sequelize for MariaDB is the [mariadb](https://www.npmjs.com/package/mariadb) npm package. + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'mariadb', + dialectOptions: { + // Your mariadb options here + // connectTimeout: 1000 + } +}); +``` + +### SQLite + +The underlying connector library used by Sequelize for SQLite is the [sqlite3](https://www.npmjs.com/package/sqlite3) npm package (version 4.0.0 or above). + +You specify the storage file in the Sequelize constructor with the `storage` option (use `:memory:` for an in-memory SQLite instance). + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'sqlite', + storage: 'path/to/database.sqlite' // or ':memory:' + dialectOptions: { + // Your sqlite3 options here + } +}); +``` + +### PostgreSQL + +The underlying connector library used by Sequelize for PostgreSQL is the [pg](https://www.npmjs.com/package/pg) npm package (version 7.0.0 or above). The module [pg-hstore](https://www.npmjs.com/package/pg-hstore) is also necessary. + +You can provide custom options to it using the `dialectOptions` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'postgres', + dialectOptions: { + // Your pg options here + } +}); +``` + +To connect over a unix domain socket, specify the path to the socket directory in the `host` option. The socket path must start with `/`. + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'postgres', + host: '/path/to/socket_directory' +}); +``` + +### MSSQL + +The underlying connector library used by Sequelize for MSSQL is the [tedious](https://www.npmjs.com/package/tedious) npm package (version 6.0.0 or above). + +You can provide custom options to it using `dialectOptions.options` in the Sequelize constructor: + +```js +const sequelize = new Sequelize('database', 'username', 'password', { + dialect: 'mssql', + dialectOptions: { + // Observe the need for this nested `options` field for MSSQL + options: { + // Your tedious options here + useUTC: false, + dateFirst: 1 + } + } +}); +``` + +#### MSSQL Domain Account + +In order to connect with a domain account, use the following format. + +```js +const sequelize = new Sequelize('database', null, null, { + dialect: 'mssql', + dialectOptions: { + authentication: { + type: 'ntlm', + options: { + domain: 'yourDomain', + userName: 'username', + password: 'password' + } + }, + options: { + instanceName: 'SQLEXPRESS' + } + } +}) +``` + +## Data type: TIMESTAMP WITHOUT TIME ZONE - PostgreSQL only + +If you are working with the PostgreSQL `TIMESTAMP WITHOUT TIME ZONE` and you need to parse it to a different timezone, please use the pg library's own parser: + +```js +require('pg').types.setTypeParser(1114, stringValue => { + return new Date(stringValue + '+0000'); + // e.g., UTC offset. Use any offset that you would like. +}); +``` + +## Data type: ARRAY(ENUM) - PostgreSQL only + +Array(Enum) type requireS special treatment. Whenever Sequelize will talk to the database, it has to typecast array values with ENUM name. + +So this enum name must follow this pattern `enum__`. If you are using `sync` then correct name will automatically be generated. + +## Table Hints - MSSQL only + +The `tableHint` option can be used to define a table hint. The hint must be a value from `TableHints` and should only be used when absolutely necessary. Only a single table hint is currently supported per query. + +Table hints override the default behavior of MSSQL query optimizer by specifing certain options. They only affect the table or view referenced in that clause. + +```js +const { TableHints } = require('sequelize'); +Project.findAll({ + // adding the table hint NOLOCK + tableHint: TableHints.NOLOCK + // this will generate the SQL 'WITH (NOLOCK)' +}) +``` + +## Index Hints - MySQL/MariaDB only + +The `indexHints` option can be used to define index hints. The hint type must be a value from `IndexHints` and the values should reference existing indexes. + +Index hints [override the default behavior of the MySQL query optimizer](https://dev.mysql.com/doc/refman/5.7/en/index-hints.html). + +```js +const { IndexHints } = require("sequelize"); +Project.findAll({ + indexHints: [ + { type: IndexHints.USE, values: ['index_project_on_name'] } + ], + where: { + id: { + [Op.gt]: 623 + }, + name: { + [Op.like]: 'Foo %' + } + } +}); +``` + +The above will generate a MySQL query that looks like this: + +```sql +SELECT * FROM Project USE INDEX (index_project_on_name) WHERE name LIKE 'FOO %' AND id > 623; +``` + +`Sequelize.IndexHints` includes `USE`, `FORCE`, and `IGNORE`. + +See [Issue #9421](https://github.com/sequelize/sequelize/issues/9421) for the original API proposal. + +## Engines - MySQL/MariaDB only + +The default engine for a model is InnoDB. + +You can change the engine for a model with the `engine` option (e.g., to MyISAM): + +```js +const Person = sequelize.define('person', { /* attributes */ }, { + engine: 'MYISAM' +}); +``` + +Like every option for the definition of a model, this setting can also be changed globally with the `define` option of the Sequelize constructor: + +```js +const sequelize = new Sequelize(db, user, pw, { + define: { engine: 'MYISAM' } +}) +``` + +## Table comments - MySQL/MariaDB/PostgreSQL only + +You can specify a comment for a table when defining the model: + +```js +class Person extends Model {} +Person.init({ /* attributes */ }, { + comment: "I'm a table comment!", + sequelize +}) +``` + +The comment will be set when calling `sync()`. diff --git a/docs/manual/other-topics/extending-data-types.md b/docs/manual/other-topics/extending-data-types.md new file mode 100644 index 000000000000..2e0938916faf --- /dev/null +++ b/docs/manual/other-topics/extending-data-types.md @@ -0,0 +1,113 @@ +# Extending Data Types + +Most likely the type you are trying to implement is already included in [DataTypes](data-types.html). If a new datatype is not included, this manual will show how to write it yourself. + +Sequelize doesn't create new datatypes in the database. This tutorial explains how to make Sequelize recognize new datatypes and assumes that those new datatypes are already created in the database. + +To extend Sequelize datatypes, do it before any Sequelize instance is created. + +## Example + +In this example, we will create a type called `SOMETYPE` that replicates the built-in datatype `DataTypes.INTEGER(11).ZEROFILL.UNSIGNED`. + +```js +const { Sequelize, DataTypes, Utils } = require('Sequelize'); +createTheNewDataType(); +const sequelize = new Sequelize('sqlite::memory:'); + +function createTheNewDataType() { + + class SOMETYPE extends DataTypes.ABSTRACT { + // Mandatory: complete definition of the new type in the database + toSql() { + return 'INTEGER(11) UNSIGNED ZEROFILL' + } + + // Optional: validator function + validate(value, options) { + return (typeof value === 'number') && (!Number.isNaN(value)); + } + + // Optional: sanitizer + _sanitize(value) { + // Force all numbers to be positive + return value < 0 ? 0 : Math.round(value); + } + + // Optional: value stringifier before sending to database + _stringify(value) { + return value.toString(); + } + + // Optional: parser for values received from the database + static parse(value) { + return Number.parseInt(value); + } + } + + // Mandatory: set the type key + SOMETYPE.prototype.key = SOMETYPE.key = 'SOMETYPE'; + + // Mandatory: add the new type to DataTypes. Optionally wrap it on `Utils.classToInvokable` to + // be able to use this datatype directly without having to call `new` on it. + DataTypes.SOMETYPE = Utils.classToInvokable(SOMETYPE); + + // Optional: disable escaping after stringifier. Do this at your own risk, since this opens opportunity for SQL injections. + // DataTypes.SOMETYPE.escape = false; + +} +``` + +After creating this new datatype, you need to map this datatype in each database dialect and make some adjustments. + +## PostgreSQL + +Let's say the name of the new datatype is `pg_new_type` in the postgres database. That name has to be mapped to `DataTypes.SOMETYPE`. Additionally, it is required to create a child postgres-specific datatype. + +```js +function createTheNewDataType() { + // [...] + + const PgTypes = DataTypes.postgres; + + // Mandatory: map postgres datatype name + DataTypes.SOMETYPE.types.postgres = ['pg_new_type']; + + // Mandatory: create a postgres-specific child datatype with its own parse + // method. The parser will be dynamically mapped to the OID of pg_new_type. + PgTypes.SOMETYPE = function SOMETYPE() { + if (!(this instanceof PgTypes.SOMETYPE)) { + return new PgTypes.SOMETYPE(); + } + DataTypes.SOMETYPE.apply(this, arguments); + } + const util = require('util'); // Built-in Node package + util.inherits(PgTypes.SOMETYPE, DataTypes.SOMETYPE); + + // Mandatory: create, override or reassign a postgres-specific parser + // PgTypes.SOMETYPE.parse = value => value; + PgTypes.SOMETYPE.parse = DataTypes.SOMETYPE.parse || x => x; + + // Optional: add or override methods of the postgres-specific datatype + // like toSql, escape, validate, _stringify, _sanitize... + +} +``` + +### Ranges + +After a new range type has been [defined in postgres](https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-DEFINING), it is trivial to add it to Sequelize. + +In this example the name of the postgres range type is `SOMETYPE_range` and the name of the underlying postgres datatype is `pg_new_type`. The key of `subtypes` and `castTypes` is the key of the Sequelize datatype `DataTypes.SOMETYPE.key`, in lower case. + +```js +function createTheNewDataType() { + // [...] + + // Add postgresql range, SOMETYPE comes from DataType.SOMETYPE.key in lower case + DataTypes.RANGE.types.postgres.subtypes.SOMETYPE = 'SOMETYPE_range'; + DataTypes.RANGE.types.postgres.castTypes.SOMETYPE = 'pg_new_type'; +} +``` + +The new range can be used in model definitions as `DataTypes.RANGE(DataTypes.SOMETYPE)` or `DataTypes.RANGE(DataTypes.SOMETYPE)`. diff --git a/docs/manual/other-topics/hooks.md b/docs/manual/other-topics/hooks.md new file mode 100644 index 000000000000..8f60a206d5a3 --- /dev/null +++ b/docs/manual/other-topics/hooks.md @@ -0,0 +1,386 @@ +# Hooks + +Hooks (also known as lifecycle events), are functions which are called before and after calls in sequelize are executed. For example, if you want to always set a value on a model before saving it, you can add a `beforeUpdate` hook. + +**Note:** _You can't use hooks with instances. Hooks are used with models._ + +## Available hooks + +Sequelize provides a lot of hooks. The full list can be found in directly in the [source code - lib/hooks.js](https://github.com/sequelize/sequelize/blob/v6/lib/hooks.js#L7). + +## Hooks firing order + +The diagram below shows the firing order for the most common hooks. + +_**Note:** this list is not exhaustive._ + +```text +(1) + beforeBulkCreate(instances, options) + beforeBulkDestroy(options) + beforeBulkUpdate(options) +(2) + beforeValidate(instance, options) + +[... validation happens ...] + +(3) + afterValidate(instance, options) + validationFailed(instance, options, error) +(4) + beforeCreate(instance, options) + beforeDestroy(instance, options) + beforeUpdate(instance, options) + beforeSave(instance, options) + beforeUpsert(values, options) + +[... creation/update/destruction happens ...] + +(5) + afterCreate(instance, options) + afterDestroy(instance, options) + afterUpdate(instance, options) + afterSave(instance, options) + afterUpsert(created, options) +(6) + afterBulkCreate(instances, options) + afterBulkDestroy(options) + afterBulkUpdate(options) +``` + +## Declaring Hooks + +Arguments to hooks are passed by reference. This means, that you can change the values, and this will be reflected in the insert / update statement. A hook may contain async actions - in this case the hook function should return a promise. + +There are currently three ways to programmatically add hooks: + +```js +// Method 1 via the .init() method +class User extends Model {} +User.init({ + username: DataTypes.STRING, + mood: { + type: DataTypes.ENUM, + values: ['happy', 'sad', 'neutral'] + } +}, { + hooks: { + beforeValidate: (user, options) => { + user.mood = 'happy'; + }, + afterValidate: (user, options) => { + user.username = 'Toni'; + } + }, + sequelize +}); + +// Method 2 via the .addHook() method +User.addHook('beforeValidate', (user, options) => { + user.mood = 'happy'; +}); + +User.addHook('afterValidate', 'someCustomName', (user, options) => { + return Promise.reject(new Error("I'm afraid I can't let you do that!")); +}); + +// Method 3 via the direct method +User.beforeCreate(async (user, options) => { + const hashedPassword = await hashPassword(user.password); + user.password = hashedPassword; +}); + +User.afterValidate('myHookAfter', (user, options) => { + user.username = 'Toni'; +}); +``` + +## Removing hooks + +Only a hook with name param can be removed. + +```js +class Book extends Model {} +Book.init({ + title: DataTypes.STRING +}, { sequelize }); + +Book.addHook('afterCreate', 'notifyUsers', (book, options) => { + // ... +}); + +Book.removeHook('afterCreate', 'notifyUsers'); +``` + +You can have many hooks with same name. Calling `.removeHook()` will remove all of them. + +## Global / universal hooks + +Global hooks are hooks which are run for all models. They can define behaviours that you want for all your models, and are especially useful for plugins. They can be defined in two ways, which have slightly different semantics: + +### Default Hooks (on Sequelize constructor options) + +```js +const sequelize = new Sequelize(..., { + define: { + hooks: { + beforeCreate() { + // Do stuff + } + } + } +}); +``` + +This adds a default hook to all models, which is run if the model does not define its own `beforeCreate` hook: + +```js +const User = sequelize.define('User', {}); +const Project = sequelize.define('Project', {}, { + hooks: { + beforeCreate() { + // Do other stuff + } + } +}); + +await User.create({}); // Runs the global hook +await Project.create({}); // Runs its own hook (because the global hook is overwritten) +``` + +### Permanent Hooks (with `sequelize.addHook`) + +```js +sequelize.addHook('beforeCreate', () => { + // Do stuff +}); +``` + +This hook is always run, whether or not the model specifies its own `beforeCreate` hook. Local hooks are always run before global hooks: + +```js +const User = sequelize.define('User', {}); +const Project = sequelize.define('Project', {}, { + hooks: { + beforeCreate() { + // Do other stuff + } + } +}); + +await User.create({}); // Runs the global hook +await Project.create({}); // Runs its own hook, followed by the global hook +``` + +Permanent hooks may also be defined in the options passed to the Sequelize constructor: + +```js +new Sequelize(..., { + hooks: { + beforeCreate() { + // do stuff + } + } +}); +``` + +Note that the above is not the same as the *Default Hooks* mentioned above. That one uses the `define` option of the constructor. This one does not. + +### Connection Hooks + +Sequelize provides four hooks that are executed immediately before and after a database connection is obtained or released: + +* `sequelize.beforeConnect(callback)` + * The callback has the form `async (config) => /* ... */` +* `sequelize.afterConnect(callback)` + * The callback has the form `async (connection, config) => /* ... */` +* `sequelize.beforeDisconnect(callback)` + * The callback has the form `async (connection) => /* ... */` +* `sequelize.afterDisconnect(callback)` + * The callback has the form `async (connection) => /* ... */` + +These hooks can be useful if you need to asynchronously obtain database credentials, or need to directly access the low-level database connection after it has been created. + +For example, we can asynchronously obtain a database password from a rotating token store, and mutate Sequelize's configuration object with the new credentials: + +```js +sequelize.beforeConnect(async (config) => { + config.password = await getAuthToken(); +}); +``` + +These hooks may *only* be declared as a permanent global hook, as the connection pool is shared by all models. + +## Instance hooks + +The following hooks will emit whenever you're editing a single object: + +* `beforeValidate` +* `afterValidate` / `validationFailed` +* `beforeCreate` / `beforeUpdate` / `beforeSave` / `beforeDestroy` +* `afterCreate` / `afterUpdate` / `afterSave` / `afterDestroy` + +```js +User.beforeCreate(user => { + if (user.accessLevel > 10 && user.username !== "Boss") { + throw new Error("You can't grant this user an access level above 10!"); + } +}); +``` + +The following example will throw an error: + +```js +try { + await User.create({ username: 'Not a Boss', accessLevel: 20 }); +} catch (error) { + console.log(error); // You can't grant this user an access level above 10! +}; +``` + +The following example will be successful: + +```js +const user = await User.create({ username: 'Boss', accessLevel: 20 }); +console.log(user); // user object with username 'Boss' and accessLevel of 20 +``` + +### Model hooks + +Sometimes you'll be editing more than one record at a time by using methods like `bulkCreate`, `update` and `destroy`. The following hooks will emit whenever you're using one of those methods: + +* `YourModel.beforeBulkCreate(callback)` + * The callback has the form `(instances, options) => /* ... */` +* `YourModel.beforeBulkUpdate(callback)` + * The callback has the form `(options) => /* ... */` +* `YourModel.beforeBulkDestroy(callback)` + * The callback has the form `(options) => /* ... */` +* `YourModel.afterBulkCreate(callback)` + * The callback has the form `(instances, options) => /* ... */` +* `YourModel.afterBulkUpdate(callback)` + * The callback has the form `(options) => /* ... */` +* `YourModel.afterBulkDestroy(callback)` + * The callback has the form `(options) => /* ... */` + +Note: methods like `bulkCreate` do not emit individual hooks by default - only the bulk hooks. However, if you want individual hooks to be emitted as well, you can pass the `{ individualHooks: true }` option to the query call. However, this can drastically impact performance, depending on the number of records involved (since, among other things, all instances will be loaded into memory). Examples: + +```js +await Model.destroy({ + where: { accessLevel: 0 }, + individualHooks: true +}); +// This will select all records that are about to be deleted and emit `beforeDestroy` and `afterDestroy` on each instance. + +await Model.update({ username: 'Tony' }, { + where: { accessLevel: 0 }, + individualHooks: true +}); +// This will select all records that are about to be updated and emit `beforeUpdate` and `afterUpdate` on each instance. +``` + +If you use `Model.bulkCreate(...)` with the `updateOnDuplicate` option, changes made in the hook to fields that aren't given in the `updateOnDuplicate` array will not be persisted to the database. However it is possible to change the `updateOnDuplicate` option inside the hook if this is what you want. + +```js +User.beforeBulkCreate((users, options) => { + for (const user of users) { + if (user.isMember) { + user.memberSince = new Date(); + } + } + + // Add `memberSince` to updateOnDuplicate otherwise it won't be persisted + if (options.updateOnDuplicate && !options.updateOnDuplicate.includes('memberSince')) { + options.updateOnDuplicate.push('memberSince'); + } +}); + +// Bulk updating existing users with updateOnDuplicate option +await Users.bulkCreate([ + { id: 1, isMember: true }, + { id: 2, isMember: false } +], { + updateOnDuplicate: ['isMember'] +}); +``` + +## Associations + +For the most part hooks will work the same for instances when being associated. + +### One-to-One and One-to-Many associations + +* When using `add`/`set` mixin methods the `beforeUpdate` and `afterUpdate` hooks will run. + +* The `beforeDestroy` and `afterDestroy` hooks will only be called on associations that have `onDelete: 'CASCADE'` and `hooks: true`. For example: + +```js +class Projects extends Model {} +Projects.init({ + title: DataTypes.STRING +}, { sequelize }); + +class Tasks extends Model {} +Tasks.init({ + title: DataTypes.STRING +}, { sequelize }); + +Projects.hasMany(Tasks, { onDelete: 'CASCADE', hooks: true }); +Tasks.belongsTo(Projects); +``` + +This code will run `beforeDestroy` and `afterDestroy` hooks on the Tasks model. + +Sequelize, by default, will try to optimize your queries as much as possible. When calling cascade on delete, Sequelize will simply execute: + +```sql +DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey +``` + +However, adding `hooks: true` explicitly tells Sequelize that optimization is not of your concern. Then, Sequelize will first perform a `SELECT` on the associated objects and destroy each instance, one by one, in order to be able to properly call the hooks (with the right parameters). + +### Many-to-Many associations + +* When using `add` mixin methods for `belongsToMany` relationships (that will add one or more records to the junction table) the `beforeBulkCreate` and `afterBulkCreate` hooks in the junction model will run. + * If `{ individualHooks: true }` was passed to the call, then each individual hook will also run. + +* When using `remove` mixin methods for `belongsToMany` relationships (that will remove one or more records to the junction table) the `beforeBulkDestroy` and `afterBulkDestroy` hooks in the junction model will run. + * If `{ individualHooks: true }` was passed to the call, then each individual hook will also run. + +If your association is Many-to-Many, you may be interested in firing hooks on the through model when using the `remove` call. Internally, sequelize is using `Model.destroy` resulting in calling the `bulkDestroy` instead of the `before/afterDestroy` hooks on each through instance. + +## Hooks and Transactions + +Many model operations in Sequelize allow you to specify a transaction in the options parameter of the method. If a transaction *is* specified in the original call, it will be present in the options parameter passed to the hook function. For example, consider the following snippet: + +```js +User.addHook('afterCreate', async (user, options) => { + // We can use `options.transaction` to perform some other call + // using the same transaction of the call that triggered this hook + await User.update({ mood: 'sad' }, { + where: { + id: user.id + }, + transaction: options.transaction + }); +}); + +await sequelize.transaction(async t => { + await User.create({ + username: 'someguy', + mood: 'happy' + }, { + transaction: t + }); +}); +``` + +If we had not included the transaction option in our call to `User.update` in the preceding code, no change would have occurred, since our newly created user does not exist in the database until the pending transaction has been committed. + +### Internal Transactions + +It is very important to recognize that sequelize may make use of transactions internally for certain operations such as `Model.findOrCreate`. If your hook functions execute read or write operations that rely on the object's presence in the database, or modify the object's stored values like the example in the preceding section, you should always specify `{ transaction: options.transaction }`: + +* If a transaction was used, then `{ transaction: options.transaction }` will ensure it is used again; +* Otherwise, `{ transaction: options.transaction }` will be equivalent to `{ transaction: undefined }`, which won't use a transaction (which is ok). + +This way your hooks will always behave correctly. diff --git a/docs/manual/other-topics/indexes.md b/docs/manual/other-topics/indexes.md new file mode 100644 index 000000000000..123ec878457e --- /dev/null +++ b/docs/manual/other-topics/indexes.md @@ -0,0 +1,47 @@ +# Indexes + +Sequelize supports adding indexes to the model definition which will be created on [`sequelize.sync()`](../class/lib/sequelize.js~Sequelize.html#instance-method-sync). + +```js +const User = sequelize.define('User', { /* attributes */ }, { + indexes: [ + // Create a unique index on email + { + unique: true, + fields: ['email'] + }, + + // Creates a gin index on data with the jsonb_path_ops operator + { + fields: ['data'], + using: 'gin', + operator: 'jsonb_path_ops' + }, + + // By default index name will be [table]_[fields] + // Creates a multi column partial index + { + name: 'public_by_author', + fields: ['author', 'status'], + where: { + status: 'public' + } + }, + + // A BTREE index with an ordered field + { + name: 'title_index', + using: 'BTREE', + fields: [ + 'author', + { + attribute: 'title', + collate: 'en_US', + order: 'DESC', + length: 5 + } + ] + } + ] +}); +``` \ No newline at end of file diff --git a/docs/manual/legacy.md b/docs/manual/other-topics/legacy.md similarity index 92% rename from docs/manual/legacy.md rename to docs/manual/other-topics/legacy.md index 499f15394ff2..249f5a9638de 100644 --- a/docs/manual/legacy.md +++ b/docs/manual/other-topics/legacy.md @@ -1,4 +1,4 @@ -# Working with legacy tables +# Working with Legacy Tables While out of the box Sequelize will seem a bit opinionated it's easy to work legacy tables and forward proof your application by defining (otherwise generated) table and field names. @@ -21,7 +21,7 @@ User.init({ class MyModel extends Model {} MyModel.init({ userId: { - type: Sequelize.INTEGER, + type: DataTypes.INTEGER, field: 'user_id' } }, { sequelize }); @@ -37,7 +37,7 @@ To define your own primary key: class Collection extends Model {} Collection.init({ uid: { - type: Sequelize.INTEGER, + type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true // Automatically gets converted to SERIAL for postgres } @@ -46,7 +46,7 @@ Collection.init({ class Collection extends Model {} Collection.init({ uuid: { - type: Sequelize.UUID, + type: DataTypes.UUID, primaryKey: true } }, { sequelize }); diff --git a/docs/manual/legal.md b/docs/manual/other-topics/legal.md similarity index 98% rename from docs/manual/legal.md rename to docs/manual/other-topics/legal.md index a78f37cab681..0d8d4b428b2a 100644 --- a/docs/manual/legal.md +++ b/docs/manual/other-topics/legal.md @@ -2,7 +2,7 @@ ## License -Sequelize library is distributed with MIT license. You can find original license [here.](https://github.com/sequelize/sequelize/blob/master/LICENSE) +Sequelize library is distributed with MIT license. You can find original license [here.](https://github.com/sequelize/sequelize/blob/main/LICENSE) ```text MIT License diff --git a/docs/manual/other-topics/migrations.md b/docs/manual/other-topics/migrations.md new file mode 100644 index 000000000000..7b869738146c --- /dev/null +++ b/docs/manual/other-topics/migrations.md @@ -0,0 +1,565 @@ +# Migrations + +Just like you use [version control](https://en.wikipedia.org/wiki/Version_control) systems such as [Git](https://en.wikipedia.org/wiki/Git) to manage changes in your source code, you can use **migrations** to keep track of changes to the database. With migrations you can transfer your existing database into another state and vice versa: Those state transitions are saved in migration files, which describe how to get to the new state and how to revert the changes in order to get back to the old state. + +You will need the [Sequelize Command-Line Interface (CLI)](https://github.com/sequelize/cli). The CLI ships support for migrations and project bootstrapping. + +A Migration in Sequelize is javascript file which exports two functions, `up` and `down`, that dictate how to perform the migration and undo it. You define those functions manually, but you don't call them manually; they will be called automatically by the CLI. In these functions, you should simply perform whatever queries you need, with the help of `sequelize.query` and whichever other methods Sequelize provides to you. There is no extra magic beyond that. + +## Installing the CLI + +To install the Sequelize CLI: + +```text +npm install --save-dev sequelize-cli +``` + +For details see the [CLI GitHub repository](https://github.com/sequelize/cli). + +## Project bootstrapping + +To create an empty project you will need to execute `init` command + +```text +npx sequelize-cli init +``` + +This will create following folders + +- `config`, contains config file, which tells CLI how to connect with database +- `models`, contains all models for your project +- `migrations`, contains all migration files +- `seeders`, contains all seed files + +### Configuration + +Before continuing further we will need to tell the CLI how to connect to the database. To do that let's open default config file `config/config.json`. It looks something like this: + +```json +{ + "development": { + "username": "root", + "password": null, + "database": "database_development", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "test": { + "username": "root", + "password": null, + "database": "database_test", + "host": "127.0.0.1", + "dialect": "mysql" + }, + "production": { + "username": "root", + "password": null, + "database": "database_production", + "host": "127.0.0.1", + "dialect": "mysql" + } +} +``` + +Note that the Sequelize CLI assumes mysql by default. If you're using another dialect, you need to change the content of the `"dialect"` option. + +Now edit this file and set correct database credentials and dialect. The keys of the objects (e.g. "development") are used on `model/index.js` for matching `process.env.NODE_ENV` (When undefined, "development" is a default value). + +Sequelize will use the default connection port for each dialect (for example, for postgres, it is port 5432). If you need to specify a different port, use the `"port"` field (it is not present by default in `config/config.js` but you can simply add it). + +**Note:** _If your database doesn't exist yet, you can just call `db:create` command. With proper access it will create that database for you._ + +## Creating the first Model (and Migration) + +Once you have properly configured CLI config file you are ready to create your first migration. It's as simple as executing a simple command. + +We will use `model:generate` command. This command requires two options: + +- `name`: the name of the model; +- `attributes`: the list of model attributes. + +Let's create a model named `User`. + +```text +npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string +``` + +This will: + +- Create a model file `user` in `models` folder; +- Create a migration file with name like `XXXXXXXXXXXXXX-create-user.js` in `migrations` folder. + +**Note:** _Sequelize will only use Model files, it's the table representation. On the other hand, the migration file is a change in that model or more specifically that table, used by CLI. Treat migrations like a commit or a log for some change in database._ + +## Running Migrations + +Until this step, we haven't inserted anything into the database. We have just created the required model and migration files for our first model, `User`. Now to actually create that table in the database you need to run `db:migrate` command. + +```text +npx sequelize-cli db:migrate +``` + +This command will execute these steps: + +- Will ensure a table called `SequelizeMeta` in database. This table is used to record which migrations have run on the current database +- Start looking for any migration files which haven't run yet. This is possible by checking `SequelizeMeta` table. In this case it will run `XXXXXXXXXXXXXX-create-user.js` migration, which we created in last step. +- Creates a table called `Users` with all columns as specified in its migration file. + +## Undoing Migrations + +Now our table has been created and saved in the database. With migration you can revert to old state by just running a command. + +You can use `db:migrate:undo`, this command will revert most the recent migration. + +```text +npx sequelize-cli db:migrate:undo +``` + +You can revert back to the initial state by undoing all migrations with the `db:migrate:undo:all` command. You can also revert back to a specific migration by passing its name with the `--to` option. + +```text +npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js +``` + +### Creating the first Seed + +Suppose we want to insert some data into a few tables by default. If we follow up on the previous example we can consider creating a demo user for the `User` table. + +To manage all data migrations you can use seeders. Seed files are some change in data that can be used to populate database tables with sample or test data. + +Let's create a seed file which will add a demo user to our `User` table. + +```text +npx sequelize-cli seed:generate --name demo-user +``` + +This command will create a seed file in `seeders` folder. File name will look something like `XXXXXXXXXXXXXX-demo-user.js`. It follows the same `up / down` semantics as the migration files. + +Now we should edit this file to insert demo user to `User` table. + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.bulkInsert('Users', [{ + firstName: 'John', + lastName: 'Doe', + email: 'example@example.com', + createdAt: new Date(), + updatedAt: new Date() + }]); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.bulkDelete('Users', null, {}); + } +}; +``` + +## Running Seeds + +In last step you created a seed file; however, it has not been committed to the database. To do that we run a simple command. + +```text +npx sequelize-cli db:seed:all +``` + +This will execute that seed file and a demo user will be inserted into the `User` table. + +**Note:** _Seeder execution history is not stored anywhere, unlike migrations, which use the `SequelizeMeta` table. If you wish to change this behavior, please read the `Storage` section._ + +## Undoing Seeds + +Seeders can be undone if they are using any storage. There are two commands available for that: + +If you wish to undo the most recent seed: + +```text +npx sequelize-cli db:seed:undo +``` + +If you wish to undo a specific seed: + +```text +npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data +``` + +If you wish to undo all seeds: + +```text +npx sequelize-cli db:seed:undo:all +``` + +## Migration Skeleton + +The following skeleton shows a typical migration file. + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + // logic for transforming into the new state + }, + down: (queryInterface, Sequelize) => { + // logic for reverting the changes + } +} +``` + +We can generate this file using `migration:generate`. This will create `xxx-migration-skeleton.js` in your migration folder. + +```text +npx sequelize-cli migration:generate --name migration-skeleton +``` + +The passed `queryInterface` object can be used to modify the database. The `Sequelize` object stores the available data types such as `STRING` or `INTEGER`. Function `up` or `down` should return a `Promise`. Let's look at an example: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Person', { + name: Sequelize.DataTypes.STRING, + isBetaMember: { + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + } + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Person'); + } +}; +``` + +The following is an example of a migration that performs two changes in the database, using an automatically-managed transaction to ensure that all instructions are successfully executed or rolled back in case of failure: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction(t => { + return Promise.all([ + queryInterface.addColumn('Person', 'petName', { + type: Sequelize.DataTypes.STRING + }, { transaction: t }), + queryInterface.addColumn('Person', 'favoriteColor', { + type: Sequelize.DataTypes.STRING, + }, { transaction: t }) + ]); + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction(t => { + return Promise.all([ + queryInterface.removeColumn('Person', 'petName', { transaction: t }), + queryInterface.removeColumn('Person', 'favoriteColor', { transaction: t }) + ]); + }); + } +}; +``` + +The next example is of a migration that has a foreign key. You can use references to specify a foreign key: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Person', { + name: Sequelize.DataTypes.STRING, + isBetaMember: { + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + }, + userId: { + type: Sequelize.DataTypes.INTEGER, + references: { + model: { + tableName: 'users', + schema: 'schema' + }, + key: 'id' + }, + allowNull: false + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Person'); + } +} +``` + +The next example is of a migration that uses async/await where you create an unique index on a new column, with a manually-managed transaction: + +```js +module.exports = { + async up(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'Person', + 'petName', + { + type: Sequelize.DataTypes.STRING, + }, + { transaction } + ); + await queryInterface.addIndex( + 'Person', + 'petName', + { + fields: 'petName', + unique: true, + transaction, + } + ); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + async down(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('Person', 'petName', { transaction }); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + } +}; +``` + +The next example is of a migration that creates an unique index composed of multiple fields with a condition, which allows a relation to exist multiple times but only one can satisfy the condition: + +```js +module.exports = { + up: (queryInterface, Sequelize) => { + queryInterface.createTable('Person', { + name: Sequelize.DataTypes.STRING, + bool: { + type: Sequelize.DataTypes.BOOLEAN, + defaultValue: false + } + }).then((queryInterface, Sequelize) => { + queryInterface.addIndex( + 'Person', + ['name', 'bool'], + { + indicesType: 'UNIQUE', + where: { bool : 'true' }, + } + ); + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Person'); + } +} +``` + +### The `.sequelizerc` file + +This is a special configuration file. It lets you specify the following options that you would usually pass as arguments to CLI: + +- `env`: The environment to run the command in +- `config`: The path to the config file +- `options-path`: The path to a JSON file with additional options +- `migrations-path`: The path to the migrations folder +- `seeders-path`: The path to the seeders folder +- `models-path`: The path to the models folder +- `url`: The database connection string to use. Alternative to using --config files +- `debug`: When available show various debug information + +Some scenarios where you can use it: + +- You want to override default path to `migrations`, `models`, `seeders` or `config` folder. +- You want to rename `config.json` to something else like `database.json` + +And a whole lot more. Let's see how you can use this file for custom configuration. + +To begin, let's create the `.sequelizerc` file in the root directory of your project, with the following content: + +```js +// .sequelizerc + +const path = require('path'); + +module.exports = { + 'config': path.resolve('config', 'database.json'), + 'models-path': path.resolve('db', 'models'), + 'seeders-path': path.resolve('db', 'seeders'), + 'migrations-path': path.resolve('db', 'migrations') +}; +``` + +With this config you are telling the CLI to: + +- Use `config/database.json` file for config settings; +- Use `db/models` as models folder; +- Use `db/seeders` as seeders folder; +- Use `db/migrations` as migrations folder. + +### Dynamic configuration + +The configuration file is by default a JSON file called `config.json`. But sometimes you need a dynamic configuration, for example to access environment variables or execute some other code to determine the configuration. + +Thankfully, the Sequelize CLI can read from both `.json` and `.js` files. This can be setup with `.sequelizerc` file. You just have to provide the path to your `.js` file as the `config` option of your exported object: + +```js +const path = require('path'); + +module.exports = { + 'config': path.resolve('config', 'config.js') +} +``` + +Now the Sequelize CLI will load `config/config.js` for getting configuration options. + +An example of `config/config.js` file: + +```js +const fs = require('fs'); + +module.exports = { + development: { + username: 'database_dev', + password: 'database_dev', + database: 'database_dev', + host: '127.0.0.1', + port: 3306, + dialect: 'mysql', + dialectOptions: { + bigNumberStrings: true + } + }, + test: { + username: process.env.CI_DB_USERNAME, + password: process.env.CI_DB_PASSWORD, + database: process.env.CI_DB_NAME, + host: '127.0.0.1', + port: 3306, + dialect: 'mysql', + dialectOptions: { + bigNumberStrings: true + } + }, + production: { + username: process.env.PROD_DB_USERNAME, + password: process.env.PROD_DB_PASSWORD, + database: process.env.PROD_DB_NAME, + host: process.env.PROD_DB_HOSTNAME, + port: process.env.PROD_DB_PORT, + dialect: 'mysql', + dialectOptions: { + bigNumberStrings: true, + ssl: { + ca: fs.readFileSync(__dirname + '/mysql-ca-main.crt') + } + } + } +}; +``` + +The example above also shows how to add custom dialect options to the configuration. + +### Using Babel + +To enable more modern constructions in your migrations and seeders, you can simply install `babel-register` and require it at the beginning of `.sequelizerc`: + +```text +npm i --save-dev babel-register +``` + +```js +// .sequelizerc + +require("babel-register"); + +const path = require('path'); + +module.exports = { + 'config': path.resolve('config', 'config.json'), + 'models-path': path.resolve('models'), + 'seeders-path': path.resolve('seeders'), + 'migrations-path': path.resolve('migrations') +} +``` + +Of course, the outcome will depend upon your babel configuration (such as in a `.babelrc` file). Learn more at [babeljs.io](https://babeljs.io). + +### Security tip + +Use environment variables for config settings. This is because secrets such as passwords should never be part of the source code (and especially not committed to version control). + +### Storage + +There are three types of storage that you can use: `sequelize`, `json`, and `none`. + +- `sequelize` : stores migrations and seeds in a table on the sequelize database +- `json` : stores migrations and seeds on a json file +- `none` : does not store any migration/seed + +#### Migration Storage + +By default the CLI will create a table in your database called `SequelizeMeta` containing an entry for each executed migration. To change this behavior, there are three options you can add to the configuration file. Using `migrationStorage`, you can choose the type of storage to be used for migrations. If you choose `json`, you can specify the path of the file using `migrationStoragePath` or the CLI will write to the file `sequelize-meta.json`. If you want to keep the information in the database, using `sequelize`, but want to use a different table, you can change the table name using `migrationStorageTableName`. Also you can define a different schema for the `SequelizeMeta` table by providing the `migrationStorageTableSchema` property. + +```json +{ + "development": { + "username": "root", + "password": null, + "database": "database_development", + "host": "127.0.0.1", + "dialect": "mysql", + + // Use a different storage type. Default: sequelize + "migrationStorage": "json", + + // Use a different file name. Default: sequelize-meta.json + "migrationStoragePath": "sequelizeMeta.json", + + // Use a different table name. Default: SequelizeMeta + "migrationStorageTableName": "sequelize_meta", + + // Use a different schema for the SequelizeMeta table + "migrationStorageTableSchema": "custom_schema" + } +} +``` + +**Note:** _The `none` storage is not recommended as a migration storage. If you decide to use it, be aware of the implications of having no record of what migrations did or didn't run._ + +#### Seed Storage + +By default the CLI will not save any seed that is executed. If you choose to change this behavior (!), you can use `seederStorage` in the configuration file to change the storage type. If you choose `json`, you can specify the path of the file using `seederStoragePath` or the CLI will write to the file `sequelize-data.json`. If you want to keep the information in the database, using `sequelize`, you can specify the table name using `seederStorageTableName`, or it will default to `SequelizeData`. + +```json +{ + "development": { + "username": "root", + "password": null, + "database": "database_development", + "host": "127.0.0.1", + "dialect": "mysql", + // Use a different storage. Default: none + "seederStorage": "json", + // Use a different file name. Default: sequelize-data.json + "seederStoragePath": "sequelizeData.json", + // Use a different table name. Default: SequelizeData + "seederStorageTableName": "sequelize_data" + } +} +``` + +### Configuration Connection String + +As an alternative to the `--config` option with configuration files defining your database, you can use the `--url` option to pass in a connection string. For example: + +```text +npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name' +``` + +### Programmatic usage + +Sequelize has a sister library called [umzug](https://github.com/sequelize/umzug) for programmatically handling execution and logging of migration tasks. diff --git a/docs/manual/other-topics/naming-strategies.md b/docs/manual/other-topics/naming-strategies.md new file mode 100644 index 000000000000..9daf8b1a3567 --- /dev/null +++ b/docs/manual/other-topics/naming-strategies.md @@ -0,0 +1,157 @@ +# Naming Strategies + +## The `underscored` option + +Sequelize provides the `underscored` option for a model. When `true`, this option will set the `field` option on all attributes to the [snake_case](https://en.wikipedia.org/wiki/Snake_case) version of its name. This also applies to foreign keys automatically generated by associations and other automatically generated fields. Example: + +```js +const User = sequelize.define('user', { username: Sequelize.STRING }, { + underscored: true +}); +const Task = sequelize.define('task', { title: Sequelize.STRING }, { + underscored: true +}); +User.hasMany(Task); +Task.belongsTo(User); +``` + +Above we have the models User and Task, both using the `underscored` option. We also have a One-to-Many relationship between them. Also, recall that since `timestamps` is true by default, we should expect the `createdAt` and `updatedAt` fields to be automatically created as well. + +Without the `underscored` option, Sequelize would automatically define: + +* A `createdAt` attribute for each model, pointing to a column named `createdAt` in each table +* An `updatedAt` attribute for each model, pointing to a column named `updatedAt` in each table +* A `userId` attribute in the `Task` model, pointing to a column named `userId` in the task table + +With the `underscored` option enabled, Sequelize will instead define: + +* A `createdAt` attribute for each model, pointing to a column named `created_at` in each table +* An `updatedAt` attribute for each model, pointing to a column named `updated_at` in each table +* A `userId` attribute in the `Task` model, pointing to a column named `user_id` in the task table + +Note that in both cases the fields are still [camelCase](https://en.wikipedia.org/wiki/Camel_case) in the JavaScript side; this option only changes how these fields are mapped to the database itself. The `field` option of every attribute is set to their snake_case version, but the attribute itself remains camelCase. + +This way, calling `sync()` on the above code will generate the following: + +```sql +CREATE TABLE IF NOT EXISTS "users" ( + "id" SERIAL, + "username" VARCHAR(255), + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY ("id") +); +CREATE TABLE IF NOT EXISTS "tasks" ( + "id" SERIAL, + "title" VARCHAR(255), + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, + "user_id" INTEGER REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + PRIMARY KEY ("id") +); +``` + +## Singular vs. Plural + +At a first glance, it can be confusing whether the singular form or plural form of a name shall be used around in Sequelize. This section aims at clarifying that a bit. + +Recall that Sequelize uses a library called [inflection](https://www.npmjs.com/package/inflection) under the hood, so that irregular plurals (such as `person -> people`) are computed correctly. However, if you're working in another language, you may want to define the singular and plural forms of names directly; sequelize allows you to do this with some options. + +### When defining models + +Models should be defined with the singular form of a word. Example: + +```js +sequelize.define('foo', { name: DataTypes.STRING }); +``` + +Above, the model name is `foo` (singular), and the respective table name is `foos`, since Sequelize automatically gets the plural for the table name. + +### When defining a reference key in a model + +```js +sequelize.define('foo', { + name: DataTypes.STRING, + barId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: "bars", + key: "id" + }, + onDelete: "CASCADE" + }, +}); +``` + +In the above example we are manually defining a key that references another model. It's not usual to do this, but if you have to, you should use the table name there. This is because the reference is created upon the referencced table name. In the example above, the plural form was used (`bars`), assuming that the `bar` model was created with the default settings (making its underlying table automatically pluralized). + +### When retrieving data from eager loading + +When you perform an `include` in a query, the included data will be added to an extra field in the returned objects, according to the following rules: + +* When including something from a single association (`hasOne` or `belongsTo`) - the field name will be the singular version of the model name; +* When including something from a multiple association (`hasMany` or `belongsToMany`) - the field name will be the plural form of the model. + +In short, the name of the field will take the most logical form in each situation. + +Examples: + +```js +// Assuming Foo.hasMany(Bar) +const foo = Foo.findOne({ include: Bar }); +// foo.bars will be an array +// foo.bar will not exist since it doens't make sense + +// Assuming Foo.hasOne(Bar) +const foo = Foo.findOne({ include: Bar }); +// foo.bar will be an object (possibly null if there is no associated model) +// foo.bars will not exist since it doens't make sense + +// And so on. +``` + +### Overriding singulars and plurals when defining aliases + +When defining an alias for an association, instead of using simply `{ as: 'myAlias' }`, you can pass an object to specify the singular and plural forms: + +```js +Project.belongsToMany(User, { + as: { + singular: 'líder', + plural: 'líderes' + } +}); +``` + +If you know that a model will always use the same alias in associations, you can provide the singular and plural forms directly to the model itself: + +```js +const User = sequelize.define('user', { /* ... */ }, { + name: { + singular: 'líder', + plural: 'líderes', + } +}); +Project.belongsToMany(User); +``` + +The mixins added to the user instances will use the correct forms. For example, instead of `project.addUser()`, Sequelize will provide `project.getLíder()`. Also, instead of `project.setUsers()`, Sequelize will provide `project.setLíderes()`. + +Note: recall that using `as` to change the name of the association will also change the name of the foreign key. Therefore it is recommended to also specify the foreign key(s) involved directly in this case. + +```js +// Example of possible mistake +Invoice.belongsTo(Subscription, { as: 'TheSubscription' }); +Subscription.hasMany(Invoice); +``` + +The first call above will establish a foreign key called `theSubscriptionId` on `Invoice`. However, the second call will also establish a foreign key on `Invoice` (since as we know, `hasMany` calls places foreign keys in the target model) - however, it will be named `subscriptionId`. This way you will have both `subscriptionId` and `theSubscriptionId` columns. + +The best approach is to choose a name for the foreign key and place it explicitly in both calls. For example, if `subscription_id` was chosen: + +```js +// Fixed example +Invoice.belongsTo(Subscription, { as: 'TheSubscription', foreignKey: 'subscription_id' }); +Subscription.hasMany(Invoice, { foreignKey: 'subscription_id' }); +``` \ No newline at end of file diff --git a/docs/manual/other-topics/optimistic-locking.md b/docs/manual/other-topics/optimistic-locking.md new file mode 100644 index 000000000000..7db529e43319 --- /dev/null +++ b/docs/manual/other-topics/optimistic-locking.md @@ -0,0 +1,7 @@ +# Optimistic Locking + +Sequelize has built-in support for optimistic locking through a model instance version count. + +Optimistic locking is disabled by default and can be enabled by setting the `version` property to true in a specific model definition or global model configuration. See [model configuration](models-definition.html#configuration) for more details. + +Optimistic locking allows concurrent access to model records for edits and prevents conflicts from overwriting data. It does this by checking whether another process has made changes to a record since it was read and throws an OptimisticLockError when a conflict is detected. \ No newline at end of file diff --git a/docs/manual/other-topics/other-data-types.md b/docs/manual/other-topics/other-data-types.md new file mode 100644 index 000000000000..fa0561385520 --- /dev/null +++ b/docs/manual/other-topics/other-data-types.md @@ -0,0 +1,192 @@ +# Other Data Types + +Apart from the most common data types mentioned in the Model Basics guide, Sequelize provides several other data types. + +## Ranges (PostgreSQL only) + +```js +DataTypes.RANGE(DataTypes.INTEGER) // int4range +DataTypes.RANGE(DataTypes.BIGINT) // int8range +DataTypes.RANGE(DataTypes.DATE) // tstzrange +DataTypes.RANGE(DataTypes.DATEONLY) // daterange +DataTypes.RANGE(DataTypes.DECIMAL) // numrange +``` + +Since range types have extra information for their bound inclusion/exclusion it's not very straightforward to just use a tuple to represent them in javascript. + +When supplying ranges as values you can choose from the following APIs: + +```js +// defaults to inclusive lower bound, exclusive upper bound +const range = [ + new Date(Date.UTC(2016, 0, 1)), + new Date(Date.UTC(2016, 1, 1)) +]; +// '["2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' + +// control inclusion +const range = [ + { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, + { value: new Date(Date.UTC(2016, 1, 1)), inclusive: true }, +]; +// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]' + +// composite form +const range = [ + { value: new Date(Date.UTC(2016, 0, 1)), inclusive: false }, + new Date(Date.UTC(2016, 1, 1)), +]; +// '("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00")' + +const Timeline = sequelize.define('Timeline', { + range: DataTypes.RANGE(DataTypes.DATE) +}); + +await Timeline.create({ range }); +``` + +However, retrieved range values always come in the form of an array of objects. For example, if the stored value is `("2016-01-01 00:00:00+00:00", "2016-02-01 00:00:00+00:00"]`, after a finder query you will get: + +```js +[ + { value: Date, inclusive: false }, + { value: Date, inclusive: true } +] +``` + +You will need to call `reload()` after updating an instance with a range type or use the `returning: true` option. + +### Special Cases + +```js +// empty range: +Timeline.create({ range: [] }); // range = 'empty' + +// Unbounded range: +Timeline.create({ range: [null, null] }); // range = '[,)' +// range = '[,"2016-01-01 00:00:00+00:00")' +Timeline.create({ range: [null, new Date(Date.UTC(2016, 0, 1))] }); + +// Infinite range: +// range = '[-infinity,"2016-01-01 00:00:00+00:00")' +Timeline.create({ range: [-Infinity, new Date(Date.UTC(2016, 0, 1))] }); +``` + +## BLOBs + +```js +DataTypes.BLOB // BLOB (bytea for PostgreSQL) +DataTypes.BLOB('tiny') // TINYBLOB (bytea for PostgreSQL) +DataTypes.BLOB('medium') // MEDIUMBLOB (bytea for PostgreSQL) +DataTypes.BLOB('long') // LONGBLOB (bytea for PostgreSQL) +``` + +The blob datatype allows you to insert data both as strings and as buffers. However, when a blob is retrieved from database with Sequelize, it will always be retrieved as a buffer. + +## ENUMs + +The ENUM is a data type that accepts only a few values, specified as a list. + +```js +DataTypes.ENUM('foo', 'bar') // An ENUM with allowed values 'foo' and 'bar' +``` + +ENUMs can also be specified with the `values` field of the column definition, as follows: + +```js +sequelize.define('foo', { + states: { + type: DataTypes.ENUM, + values: ['active', 'pending', 'deleted'] + } +}); +``` + +## JSON (SQLite, MySQL, MariaDB and PostgreSQL only) + +The `DataTypes.JSON` data type is only supported for SQLite, MySQL, MariaDB and PostgreSQL. However, there is a minimum support for MSSQL (see below). + +### Note for PostgreSQL + +The JSON data type in PostgreSQL stores the value as plain text, as opposed to binary representation. If you simply want to store and retrieve a JSON representation, using JSON will take less disk space and less time to build from its input representation. However, if you want to do any operations on the JSON value, you should prefer the JSONB data type described below. + +### JSONB (PostgreSQL only) + +PostgreSQL also supports a JSONB data type: `DataTypes.JSONB`. It can be queried in three different ways: + +```js +// Nested object +await Foo.findOne({ + where: { + meta: { + video: { + url: { + [Op.ne]: null + } + } + } + } +}); + +// Nested key +await Foo.findOne({ + where: { + "meta.audio.length": { + [Op.gt]: 20 + } + } +}); + +// Containment +await Foo.findOne({ + where: { + meta: { + [Op.contains]: { + site: { + url: 'http://google.com' + } + } + } + } +}); +``` + +### MSSQL + +MSSQL does not have a JSON data type, however it does provide some support for JSON stored as strings through certain functions since SQL Server 2016. Using these functions, you will be able to query the JSON stored in the string, but any returned values will need to be parsed seperately. + +```js +// ISJSON - to test if a string contains valid JSON +await User.findAll({ + where: sequelize.where(sequelize.fn('ISJSON', sequelize.col('userDetails')), 1) +}) + +// JSON_VALUE - extract a scalar value from a JSON string +await User.findAll({ + attributes: [[ sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), 'address line 1']] +}) + +// JSON_VALUE - query a scalar value from a JSON string +await User.findAll({ + where: sequelize.where(sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), '14, Foo Street') +}) + +// JSON_QUERY - extract an object or array +await User.findAll({ + attributes: [[ sequelize.fn('JSON_QUERY', sequelize.col('userDetails'), '$.address'), 'full address']] +}) +``` + +## Others + +```js +DataTypes.ARRAY(/* DataTypes.SOMETHING */) // Defines an array of DataTypes.SOMETHING. PostgreSQL only. + +DataTypes.CIDR // CIDR PostgreSQL only +DataTypes.INET // INET PostgreSQL only +DataTypes.MACADDR // MACADDR PostgreSQL only + +DataTypes.GEOMETRY // Spatial column. PostgreSQL (with PostGIS) or MySQL only. +DataTypes.GEOMETRY('POINT') // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only. +DataTypes.GEOMETRY('POINT', 4326) // Spatial column with geometry type and SRID. PostgreSQL (with PostGIS) or MySQL only. +``` \ No newline at end of file diff --git a/docs/manual/other-topics/query-interface.md b/docs/manual/other-topics/query-interface.md new file mode 100644 index 000000000000..5225d0adf1d3 --- /dev/null +++ b/docs/manual/other-topics/query-interface.md @@ -0,0 +1,152 @@ +# Query Interface + +An instance of Sequelize uses something called **Query Interface** to communicate to the database in a dialect-agnostic way. Most of the methods you've learned in this manual are implemented with the help of several methods from the query interface. + +The methods from the query interface are therefore lower-level methods; you should use them only if you do not find another way to do it with higher-level APIs from Sequelize. They are, of course, still higher-level than running raw queries directly (i.e., writing SQL by hand). + +This guide shows a few examples, but for the full list of what it can do, and for detailed usage of each method, check the [QueryInterface API](../class/lib/dialects/abstract/query-interface.js~QueryInterface.html). + +## Obtaining the query interface + +From now on, we will call `queryInterface` the singleton instance of the [QueryInterface](../class/lib/dialects/abstract/query-interface.js~QueryInterface.html) class, which is available on your Sequelize instance: + +```js +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize(/* ... */); +const queryInterface = sequelize.getQueryInterface(); +``` + +## Creating a table + +```js +queryInterface.createTable('Person', { + name: DataTypes.STRING, + isBetaMember: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + } +}); +``` + +Generated SQL (using SQLite): + +```SQL +CREATE TABLE IF NOT EXISTS `Person` ( + `name` VARCHAR(255), + `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0 +); +``` + +**Note:** Consider defining a Model instead and calling `YourModel.sync()` instead, which is a higher-level approach. + +## Adding a column to a table + +```js +queryInterface.addColumn('Person', 'petName', { type: DataTypes.STRING }); +``` + +Generated SQL (using SQLite): + +```sql +ALTER TABLE `Person` ADD `petName` VARCHAR(255); +``` + +## Changing the datatype of a column + +```js +queryInterface.changeColumn('Person', 'foo', { + type: DataTypes.FLOAT, + defaultValue: 3.14, + allowNull: false +}); +``` + +Generated SQL (using MySQL): + +```sql +ALTER TABLE `Person` CHANGE `foo` `foo` FLOAT NOT NULL DEFAULT 3.14; +``` + +## Removing a column + +```js +queryInterface.removeColumn('Person', 'petName', { /* query options */ }); +``` + +Generated SQL (using PostgreSQL): + +```SQL +ALTER TABLE "public"."Person" DROP COLUMN "petName"; +``` + +## Changing and removing columns in SQLite + +SQLite does not support directly altering and removing columns. However, Sequelize will try to work around this by recreating the whole table with the help of a backup table, inspired by [these instructions](https://www.sqlite.org/lang_altertable.html#otheralter). + +For example: + +```js +// Assuming we have a table in SQLite created as follows: +queryInterface.createTable('Person', { + name: DataTypes.STRING, + isBetaMember: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false + }, + petName: DataTypes.STRING, + foo: DataTypes.INTEGER +}); + +// And we change a column: +queryInterface.changeColumn('Person', 'foo', { + type: DataTypes.FLOAT, + defaultValue: 3.14, + allowNull: false +}); +``` + +The following SQL calls are generated for SQLite: + +```sql +PRAGMA TABLE_INFO(`Person`); + +CREATE TABLE IF NOT EXISTS `Person_backup` ( + `name` VARCHAR(255), + `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0, + `foo` FLOAT NOT NULL DEFAULT '3.14', + `petName` VARCHAR(255) +); + +INSERT INTO `Person_backup` + SELECT + `name`, + `isBetaMember`, + `foo`, + `petName` + FROM `Person`; + +DROP TABLE `Person`; + +CREATE TABLE IF NOT EXISTS `Person` ( + `name` VARCHAR(255), + `isBetaMember` TINYINT(1) NOT NULL DEFAULT 0, + `foo` FLOAT NOT NULL DEFAULT '3.14', + `petName` VARCHAR(255) +); + +INSERT INTO `Person` + SELECT + `name`, + `isBetaMember`, + `foo`, + `petName` + FROM `Person_backup`; + +DROP TABLE `Person_backup`; +``` + +## Other + +As mentioned in the beginning of this guide, there is a lot more to the Query Interface available in Sequelize! Check the [QueryInterface API](../class/lib/dialects/abstract/query-interface.js~QueryInterface.html) for a full list of what can be done. \ No newline at end of file diff --git a/docs/manual/read-replication.md b/docs/manual/other-topics/read-replication.md similarity index 57% rename from docs/manual/read-replication.md rename to docs/manual/other-topics/read-replication.md index 10c7166e3bc5..1c16fef1ad74 100644 --- a/docs/manual/read-replication.md +++ b/docs/manual/other-topics/read-replication.md @@ -1,29 +1,29 @@ -# Read replication - -Sequelize supports read replication, i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the write master, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). - -```js -const sequelize = new Sequelize('database', null, null, { - dialect: 'mysql', - port: 3306 - replication: { - read: [ - { host: '8.8.8.8', username: 'read-username', password: 'some-password' }, - { host: '9.9.9.9', username: 'another-username', password: null } - ], - write: { host: '1.1.1.1', username: 'write-username', password: 'any-password' } - }, - pool: { // If you want to override the options used for the read/write pool you can do so here - max: 20, - idle: 30000 - }, -}) -``` - -If you have any general settings that apply to all replicas you do not need to provide them for each instance. In the code above, database name and port is propagated to all replicas. The same will happen for user and password, if you leave them out for any of the replicas. Each replica has the following options:`host`,`port`,`username`,`password`,`database`. - -Sequelize uses a pool to manage connections to your replicas. Internally Sequelize will maintain two pools created using `pool` configuration. - -If you want to modify these, you can pass pool as an options when instantiating Sequelize, as shown above. - -Each `write` or `useMaster: true` query will use write pool. For `SELECT` read pool will be used. Read replica are switched using a basic round robin scheduling. \ No newline at end of file +# Read Replication + +Sequelize supports [read replication](https://en.wikipedia.org/wiki/Replication_%28computing%29#Database_replication), i.e. having multiple servers that you can connect to when you want to do a SELECT query. When you do read replication, you specify one or more servers to act as read replicas, and one server to act as the main writer, which handles all writes and updates and propagates them to the replicas (note that the actual replication process is **not** handled by Sequelize, but should be set up by database backend). + +```js +const sequelize = new Sequelize('database', null, null, { + dialect: 'mysql', + port: 3306, + replication: { + read: [ + { host: '8.8.8.8', username: 'read-1-username', password: process.env.READ_DB_1_PW }, + { host: '9.9.9.9', username: 'read-2-username', password: process.env.READ_DB_2_PW } + ], + write: { host: '1.1.1.1', username: 'write-username', password: process.env.WRITE_DB_PW } + }, + pool: { // If you want to override the options used for the read/write pool you can do so here + max: 20, + idle: 30000 + }, +}) +``` + +If you have any general settings that apply to all replicas you do not need to provide them for each instance. In the code above, database name and port is propagated to all replicas. The same will happen for user and password, if you leave them out for any of the replicas. Each replica has the following options:`host`,`port`,`username`,`password`,`database`. + +Sequelize uses a pool to manage connections to your replicas. Internally Sequelize will maintain two pools created using `pool` configuration. + +If you want to modify these, you can pass pool as an options when instantiating Sequelize, as shown above. + +Each `write` or `useMaster: true` query will use write pool. For `SELECT` read pool will be used. Read replica are switched using a basic round robin scheduling. diff --git a/docs/manual/resources.md b/docs/manual/other-topics/resources.md similarity index 75% rename from docs/manual/resources.md rename to docs/manual/other-topics/resources.md index 5c6d8860cb62..c5ae37d5c28d 100644 --- a/docs/manual/resources.md +++ b/docs/manual/other-topics/resources.md @@ -6,6 +6,7 @@ * [ssacl](https://github.com/pumpupapp/ssacl) * [ssacl-attribute-roles](https://github.com/mickhansen/ssacl-attribute-roles) +* [SequelizeGuard](https://github.com/lotivo/sequelize-acl) - Role, Permission based Authorization for Sequelize. ### Auto Code Generation & Scaffolding @@ -20,6 +21,10 @@ * [sequelize-autoload](https://github.com/boxsnake-nodejs/sequelize-autoload) - An autoloader for Sequelize, inspired by [PSR-0](https://www.php-fig.org/psr/psr-0/) and [PSR-4](https://www.php-fig.org/psr/psr-4/). +### Bcrypt + +* [sequelize-bcrypt](https://github.com/mattiamalonni/sequelize-bcrypt) - Utility to integrate bcrypt into sequelize models + ### Caching * [sequelize-transparent-cache](https://github.com/DanielHreben/sequelize-transparent-cache) @@ -42,6 +47,10 @@ * [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) - Temporal tables (aka historical records) +### Joi + +* [sequelize-joi](https://github.com/mattiamalonni/sequelize-joi) - Allows specifying [Joi](https://github.com/sideway/joi) validation schema for model attributes in Sequelize. + ### Migrations * [umzug](https://github.com/sequelize/umzug) @@ -58,4 +67,4 @@ * [sequelize-deep-update](https://www.npmjs.com/package/sequelize-deep-update) - Update a sequelize instance and its included associated instances with new properties. * [sequelize-noupdate-attributes](https://www.npmjs.com/package/sequelize-noupdate-attributes) - Adds no update/readonly attributes support to models. -* [sequelize-joi](https://www.npmjs.com/package/sequelize-joi) - Allows specifying [Joi](https://github.com/hapijs/joi) validation schema for JSONB model attributes in Sequelize. +* [sqlcommenter-sequelize](https://github.com/google/sqlcommenter/tree/master/nodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize) A [sqlcommenter](https://google.github.io/sqlcommenter/) plugin with [support for Sequelize](https://google.github.io/sqlcommenter/node/sequelize/) to augment SQL statements with comments that can be used later to correlate application code with SQL statements. diff --git a/docs/manual/scopes.md b/docs/manual/other-topics/scopes.md similarity index 52% rename from docs/manual/scopes.md rename to docs/manual/other-topics/scopes.md index 3a21d68a9734..96a531115122 100644 --- a/docs/manual/scopes.md +++ b/docs/manual/other-topics/scopes.md @@ -1,6 +1,8 @@ # Scopes -Scoping allows you to define commonly used queries that you can easily use later. Scopes can include all the same attributes as regular finders, `where`, `include`, `limit` etc. +Scopes are used to help you reuse code. You can define commonly used queries, specifying options such as `where`, `include`, `limit`, etc. + +This guide concerns model scopes. You might also be interested in the [guide for association scopes](association-scopes.html), which are similar but not the same thing. ## Definition @@ -24,17 +26,17 @@ Project.init({ }, activeUsers: { include: [ - { model: User, where: { active: true }} + { model: User, where: { active: true } } ] }, - random () { + random() { return { where: { someNumber: Math.random() } } }, - accessLevel (value) { + accessLevel(value) { return { where: { accessLevel: { @@ -42,14 +44,14 @@ Project.init({ } } } - } + }, sequelize, modelName: 'project' } }); ``` -You can also add scopes after a model has been defined by calling `addScope`. This is especially useful for scopes with includes, where the model in the include might not be defined at the time the other model is being defined. +You can also add scopes after a model has been defined by calling [`YourModel.addScope`](../class/lib/model.js~Model.html#static-method-addScope). This is especially useful for scopes with includes, where the model in the include might not be defined at the time the other model is being defined. The default scope is always applied. This means, that with the model definition above, `Project.findAll()` will create the following query: @@ -60,22 +62,22 @@ SELECT * FROM projects WHERE active = true The default scope can be removed by calling `.unscoped()`, `.scope(null)`, or by invoking another scope: ```js -Project.scope('deleted').findAll(); // Removes the default scope +await Project.scope('deleted').findAll(); // Removes the default scope ``` ```sql SELECT * FROM projects WHERE deleted = true ``` -It is also possible to include scoped models in a scope definition. This allows you to avoid duplicating `include`, `attributes` or `where` definitions. -Using the above example, and invoking the `active` scope on the included User model (rather than specifying the condition directly in that include object): +It is also possible to include scoped models in a scope definition. This allows you to avoid duplicating `include`, `attributes` or `where` definitions. Using the above example, and invoking the `active` scope on the included User model (rather than specifying the condition directly in that include object): ```js -activeUsers: { +// The `activeUsers` scope defined in the example above could also have been defined this way: +Project.addScope('activeUsers', { include: [ - { model: User.scope('active')} + { model: User.scope('active') } ] -} +}); ``` ## Usage @@ -84,12 +86,14 @@ Scopes are applied by calling `.scope` on the model definition, passing the name ```js const DeletedProjects = Project.scope('deleted'); +await DeletedProjects.findAll(); -DeletedProjects.findAll(); -// some time passes - -// let's look for deleted projects again! -DeletedProjects.findAll(); +// The above is equivalent to: +await Project.findAll({ + where: { + deleted: true + } +}); ``` Scopes apply to `.find`, `.findAll`, `.count`, `.update`, `.increment` and `.destroy`. @@ -97,9 +101,11 @@ Scopes apply to `.find`, `.findAll`, `.count`, `.update`, `.increment` and `.des Scopes which are functions can be invoked in two ways. If the scope does not take any arguments it can be invoked as normally. If the scope takes arguments, pass an object: ```js -Project.scope('random', { method: ['accessLevel', 19]}).findAll(); +await Project.scope('random', { method: ['accessLevel', 19] }).findAll(); ``` +Generated SQL: + ```sql SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19 ``` @@ -110,10 +116,12 @@ Several scopes can be applied simultaneously by passing an array of scopes to `. ```js // These two are equivalent -Project.scope('deleted', 'activeUsers').findAll(); -Project.scope(['deleted', 'activeUsers']).findAll(); +await Project.scope('deleted', 'activeUsers').findAll(); +await Project.scope(['deleted', 'activeUsers']).findAll(); ``` +Generated SQL: + ```sql SELECT * FROM projects INNER JOIN users ON projects.userId = users.id @@ -124,9 +132,11 @@ AND users.active = true If you want to apply another scope alongside the default scope, pass the key `defaultScope` to `.scope`: ```js -Project.scope('defaultScope', 'deleted').findAll(); +await Project.scope('defaultScope', 'deleted').findAll(); ``` +Generated SQL: + ```sql SELECT * FROM projects WHERE active = true AND deleted = true ``` @@ -134,28 +144,26 @@ SELECT * FROM projects WHERE active = true AND deleted = true When invoking several scopes, keys from subsequent scopes will overwrite previous ones (similarly to [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)), except for `where` and `include`, which will be merged. Consider two scopes: ```js -{ - scope1: { - where: { - firstName: 'bob', - age: { - [Op.gt]: 20 - } - }, - limit: 2 +YourMode.addScope('scope1', { + where: { + firstName: 'bob', + age: { + [Op.gt]: 20 + } }, - scope2: { - where: { - age: { - [Op.gt]: 30 - } - }, - limit: 10 - } -} + limit: 2 +}); +YourMode.addScope('scope2', { + where: { + age: { + [Op.gt]: 30 + } + }, + limit: 10 +}); ``` -Calling `.scope('scope1', 'scope2')` will yield the following query +Using `.scope('scope1', 'scope2')` will yield the following WHERE clause: ```sql WHERE firstName = 'bob' AND age > 30 LIMIT 10 @@ -175,6 +183,8 @@ Project.scope('deleted').findAll({ }) ``` +Generated where clause: + ```sql WHERE deleted = true AND firstName = 'john' ``` @@ -185,17 +195,13 @@ Here the `deleted` scope is merged with the finder. If we were to pass `where: { Includes are merged recursively based on the models being included. This is a very powerful merge, added on v5, and is better understood with an example. -Consider four models: Foo, Bar, Baz and Qux, with has-many associations as follows: +Consider the models `Foo`, `Bar`, `Baz` and `Qux`, with One-to-Many associations as follows: ```js -class Foo extends Model {} -class Bar extends Model {} -class Baz extends Model {} -class Qux extends Model {} -Foo.init({ name: Sequelize.STRING }, { sequelize }); -Bar.init({ name: Sequelize.STRING }, { sequelize }); -Baz.init({ name: Sequelize.STRING }, { sequelize }); -Qux.init({ name: Sequelize.STRING }, { sequelize }); +const Foo = sequelize.define('Foo', { name: Sequelize.STRING }); +const Bar = sequelize.define('Bar', { name: Sequelize.STRING }); +const Baz = sequelize.define('Baz', { name: Sequelize.STRING }); +const Qux = sequelize.define('Qux', { name: Sequelize.STRING }); Foo.hasMany(Bar, { foreignKey: 'fooId' }); Bar.hasMany(Baz, { foreignKey: 'barId' }); Baz.hasMany(Qux, { foreignKey: 'bazId' }); @@ -204,62 +210,71 @@ Baz.hasMany(Qux, { foreignKey: 'bazId' }); Now, consider the following four scopes defined on Foo: ```js -{ - includeEverything: { - include: { - model: this.Bar, - include: [{ - model: this.Baz, - include: this.Qux - }] - } - }, - limitedBars: { +Foo.addScope('includeEverything', { + include: { + model: Bar, include: [{ - model: this.Bar, - limit: 2 + model: Baz, + include: Qux }] - }, - limitedBazs: { + } +}); + +Foo.addScope('limitedBars', { + include: [{ + model: Bar, + limit: 2 + }] +}); + +Foo.addScope('limitedBazs', { + include: [{ + model: Bar, include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - limit: 2 - }] + model: Baz, + limit: 2 }] - }, - excludeBazName: { + }] +}); + +Foo.addScope('excludeBazName', { + include: [{ + model: Bar, include: [{ - model: this.Bar, - include: [{ - model: this.Baz, - attributes: { - exclude: ['name'] - } - }] + model: Baz, + attributes: { + exclude: ['name'] + } }] - } -} + }] +}); ``` These four scopes can be deeply merged easily, for example by calling `Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll()`, which would be entirely equivalent to calling the following: ```js -Foo.findAll({ +await Foo.findAll({ include: { - model: this.Bar, + model: Bar, limit: 2, include: [{ - model: this.Baz, + model: Baz, limit: 2, attributes: { exclude: ['name'] }, - include: this.Qux + include: Qux }] } }); + +// The above is equivalent to: +await Foo.scope([ + 'includeEverything', + 'limitedBars', + 'limitedBazs', + 'excludeBazName' +]).findAll(); ``` Observe how the four scopes were merged into one. The includes of scopes are merged based on the model being included. If one scope includes model A and another includes model B, the merged result will include both models A and B. On the other hand, if both scopes include the same model A, but with different options (such as nested includes or other attributes), those will be merged recursively, as shown above. @@ -267,63 +282,3 @@ Observe how the four scopes were merged into one. The includes of scopes are mer The merge illustrated above works in the exact same way regardless of the order applied to the scopes. The order would only make a difference if a certain option was set by two different scopes - which is not the case of the above example, since each scope does a different thing. This merge strategy also works in the exact same way with options passed to `.findAll`, `.findOne` and the like. - -## Associations - -Sequelize has two different but related scope concepts in relation to associations. The difference is subtle but important: - -* **Association scopes** Allow you to specify default attributes when getting and setting associations - useful when implementing polymorphic associations. This scope is only invoked on the association between the two models, when using the `get`, `set`, `add` and `create` associated model functions -* **Scopes on associated models** Allows you to apply default and other scopes when fetching associations, and allows you to pass a scoped model when creating associations. These scopes both apply to regular finds on the model and to find through the association. - -As an example, consider the models Post and Comment. Comment is associated to several other models (Image, Video etc.) and the association between Comment and other models is polymorphic, which means that Comment stores a `commentable` column, in addition to the foreign key `commentable_id`. - -The polymorphic association can be implemented with an _association scope_ : - -```js -this.Post.hasMany(this.Comment, { - foreignKey: 'commentable_id', - scope: { - commentable: 'post' - } -}); -``` - -When calling `post.getComments()`, this will automatically add `WHERE commentable = 'post'`. Similarly, when adding new comments to a post, `commentable` will automagically be set to `'post'`. The association scope is meant to live in the background without the programmer having to worry about it - it cannot be disabled. For a more complete polymorphic example, see [Association scopes](associations.html#scopes) - -Consider then, that Post has a default scope which only shows active posts: `where: { active: true }`. This scope lives on the associated model (Post), and not on the association like the `commentable` scope did. Just like the default scope is applied when calling `Post.findAll()`, it is also applied when calling `User.getPosts()` - this will only return the active posts for that user. - -To disable the default scope, pass `scope: null` to the getter: `User.getPosts({ scope: null })`. Similarly, if you want to apply other scopes, pass an array like you would to `.scope`: - -```js -User.getPosts({ scope: ['scope1', 'scope2']}); -``` - -If you want to create a shortcut method to a scope on an associated model, you can pass the scoped model to the association. Consider a shortcut to get all deleted posts for a user: - -```js -class Post extends Model {} -Post.init(attributes, { - defaultScope: { - where: { - active: true - } - }, - scopes: { - deleted: { - where: { - deleted: true - } - } - }, - sequelize, -}); - -User.hasMany(Post); // regular getPosts association -User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' }); - -``` - -```js -User.getPosts(); // WHERE active = true -User.getDeletedPosts(); // WHERE deleted = true -``` diff --git a/docs/manual/other-topics/sub-queries.md b/docs/manual/other-topics/sub-queries.md new file mode 100644 index 000000000000..213e4ec3a1ab --- /dev/null +++ b/docs/manual/other-topics/sub-queries.md @@ -0,0 +1,164 @@ +# Sub Queries + +Consider you have two models, `Post` and `Reaction`, with a One-to-Many relationship set up, so that one post has many reactions: + +```js +const Post = sequelize.define('post', { + content: DataTypes.STRING +}, { timestamps: false }); + +const Reaction = sequelize.define('reaction', { + type: DataTypes.STRING +}, { timestamps: false }); + +Post.hasMany(Reaction); +Reaction.belongsTo(Post); +``` + +*Note: we have disabled timestamps just to have shorter queries for the next examples.* + +Let's fill our tables with some data: + +```js +async function makePostWithReactions(content, reactionTypes) { + const post = await Post.create({ content }); + await Reaction.bulkCreate( + reactionTypes.map(type => ({ type, postId: post.id })) + ); + return post; +} + +await makePostWithReactions('Hello World', [ + 'Like', 'Angry', 'Laugh', 'Like', 'Like', 'Angry', 'Sad', 'Like' +]); +await makePostWithReactions('My Second Post', [ + 'Laugh', 'Laugh', 'Like', 'Laugh' +]); +``` + +Now, we are ready for examples of the power of subqueries. + +Let's say we wanted to compute via SQL a `laughReactionsCount` for each post. We can achieve that with a sub-query, such as the following: + +```sql +SELECT + *, + ( + SELECT COUNT(*) + FROM reactions AS reaction + WHERE + reaction.postId = post.id + AND + reaction.type = "Laugh" + ) AS laughReactionsCount +FROM posts AS post +``` + +If we run the above raw SQL query through Sequelize, we get: + +```json +[ + { + "id": 1, + "content": "Hello World", + "laughReactionsCount": 1 + }, + { + "id": 2, + "content": "My Second Post", + "laughReactionsCount": 3 + } +] +``` + +So how can we achieve that with more help from Sequelize, without having to write the whole raw query by hand? + +The answer: by combining the `attributes` option of the finder methods (such as `findAll`) with the `sequelize.literal` utility function, that allows you to directly insert arbitrary content into the query without any automatic escaping. + +This means that Sequelize will help you with the main, larger query, but you will still have to write that sub-query by yourself: + +```js +Post.findAll({ + attributes: { + include: [ + [ + // Note the wrapping parentheses in the call below! + sequelize.literal(`( + SELECT COUNT(*) + FROM reactions AS reaction + WHERE + reaction.postId = post.id + AND + reaction.type = "Laugh" + )`), + 'laughReactionsCount' + ] + ] + } +}); +``` + +*Important Note: Since `sequelize.literal` inserts arbitrary content without escaping to the query, it deserves very special attention since it may be a source of (major) security vulnerabilities. It should not be used on user-generated content.* However, here, we are using `sequelize.literal` with a fixed string, carefully written by us (the coders). This is ok, since we know what we are doing. + +The above gives the following output: + +```json +[ + { + "id": 1, + "content": "Hello World", + "laughReactionsCount": 1 + }, + { + "id": 2, + "content": "My Second Post", + "laughReactionsCount": 3 + } +] +``` + +Success! + +## Using sub-queries for complex ordering + +This idea can be used to enable complex ordering, such as ordering posts by the number of laugh reactions they have: + +```js +Post.findAll({ + attributes: { + include: [ + [ + sequelize.literal(`( + SELECT COUNT(*) + FROM reactions AS reaction + WHERE + reaction.postId = post.id + AND + reaction.type = "Laugh" + )`), + 'laughReactionsCount' + ] + ] + }, + order: [ + [sequelize.literal('laughReactionsCount'), 'DESC'] + ] +}); +``` + +Result: + +```json +[ + { + "id": 2, + "content": "My Second Post", + "laughReactionsCount": 3 + }, + { + "id": 1, + "content": "Hello World", + "laughReactionsCount": 1 + } +] +``` \ No newline at end of file diff --git a/docs/manual/other-topics/transactions.md b/docs/manual/other-topics/transactions.md new file mode 100644 index 000000000000..e24f6d216781 --- /dev/null +++ b/docs/manual/other-topics/transactions.md @@ -0,0 +1,311 @@ +# Transactions + +Sequelize does not use [transactions](https://en.wikipedia.org/wiki/Database_transaction) by default. However, for production-ready usage of Sequelize, you should definitely configure Sequelize to use transactions. + +Sequelize supports two ways of using transactions: + +1. **Unmanaged transactions:** Committing and rolling back the transaction should be done manually by the user (by calling the appropriate Sequelize methods). + +2. **Managed transactions**: Sequelize will automatically rollback the transaction if any error is thrown, or commit the transaction otherwise. Also, if CLS (Continuation Local Storage) is enabled, all queries within the transaction callback will automatically receive the transaction object. + +## Unmanaged transactions + +Let's start with an example: + +```js +// First, we start a transaction and save it into a variable +const t = await sequelize.transaction(); + +try { + + // Then, we do some calls passing this transaction as an option: + + const user = await User.create({ + firstName: 'Bart', + lastName: 'Simpson' + }, { transaction: t }); + + await user.addSibling({ + firstName: 'Lisa', + lastName: 'Simpson' + }, { transaction: t }); + + // If the execution reaches this line, no errors were thrown. + // We commit the transaction. + await t.commit(); + +} catch (error) { + + // If the execution reaches this line, an error was thrown. + // We rollback the transaction. + await t.rollback(); + +} +``` + +As shown above, the *unmanaged transaction* approach requires that you commit and rollback the transaction manually, when necessary. + +## Managed transactions + +Managed transactions handle committing or rolling back the transaction automatically. You start a managed transaction by passing a callback to `sequelize.transaction`. This callback can be `async` (and usually is). + +The following will happen in this case: + +* Sequelize will automatically start a transaction and obtain a transaction object `t` +* Then, Sequelize will execute the callback you provided, passing `t` into it +* If your callback throws, Sequelize will automatically rollback the transaction +* If your callback succeeds, Sequelize will automatically commit the transaction +* Only then the `sequelize.transaction` call will settle: + * Either resolving with the resolution of your callback + * Or, if your callback throws, rejecting with the thrown error + +Example code: + +```js +try { + + const result = await sequelize.transaction(async (t) => { + + const user = await User.create({ + firstName: 'Abraham', + lastName: 'Lincoln' + }, { transaction: t }); + + await user.setShooter({ + firstName: 'John', + lastName: 'Boothe' + }, { transaction: t }); + + return user; + + }); + + // If the execution reaches this line, the transaction has been committed successfully + // `result` is whatever was returned from the transaction callback (the `user`, in this case) + +} catch (error) { + + // If the execution reaches this line, an error occurred. + // The transaction has already been rolled back automatically by Sequelize! + +} +``` + +Note that `t.commit()` and `t.rollback()` were not called directly (which is correct). + +### Throw errors to rollback + +When using the managed transaction you should *never* commit or rollback the transaction manually. If all queries are successful (in the sense of not throwing any error), but you still want to rollback the transaction, you should throw an error yourself: + +```js +await sequelize.transaction(async t => { + const user = await User.create({ + firstName: 'Abraham', + lastName: 'Lincoln' + }, { transaction: t }); + + // Woops, the query was successful but we still want to roll back! + // We throw an error manually, so that Sequelize handles everything automatically. + throw new Error(); +}); +``` + +### Automatically pass transactions to all queries + +In the examples above, the transaction is still manually passed, by passing `{ transaction: t }` as the second argument. To automatically pass the transaction to all queries you must install the [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) (CLS) module and instantiate a namespace in your own code: + +```js +const cls = require('cls-hooked'); +const namespace = cls.createNamespace('my-very-own-namespace'); +``` + +To enable CLS you must tell sequelize which namespace to use by using a static method of the sequelize constructor: + +```js +const Sequelize = require('sequelize'); +Sequelize.useCLS(namespace); + +new Sequelize(....); +``` + +Notice, that the `useCLS()` method is on the *constructor*, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances. + +CLS works like a thread-local storage for callbacks. What this means in practice is that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the `transaction` property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time: + +```js +sequelize.transaction((t1) => { + namespace.get('transaction') === t1; // true +}); + +sequelize.transaction((t2) => { + namespace.get('transaction') === t2; // true +}); +``` + +In most case you won't need to access `namespace.get('transaction')` directly, since all queries will automatically look for a transaction on the namespace: + +```js +sequelize.transaction((t1) => { + // With CLS enabled, the user will be created inside the transaction + return User.create({ name: 'Alice' }); +}); +``` + +## Concurrent/Partial transactions + +You can have concurrent transactions within a sequence of queries or have some of them excluded from any transactions. Use the `transaction` option to control which transaction a query belongs to: + +**Note:** *SQLite does not support more than one transaction at the same time.* + +### With CLS enabled + +```js +sequelize.transaction((t1) => { + return sequelize.transaction((t2) => { + // With CLS enabled, queries here will by default use t2. + // Pass in the `transaction` option to define/alter the transaction they belong to. + return Promise.all([ + User.create({ name: 'Bob' }, { transaction: null }), + User.create({ name: 'Mallory' }, { transaction: t1 }), + User.create({ name: 'John' }) // this would default to t2 + ]); + }); +}); +``` + +## Passing options + +The `sequelize.transaction` method accepts options. + +For unmanaged transactions, just use `sequelize.transaction(options)`. + +For managed transactions, use `sequelize.transaction(options, callback)`. + +## Isolation levels + +The possible isolations levels to use when starting a transaction: + +```js +const { Transaction } = require('sequelize'); + +// The following are valid isolation levels: +Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" +Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" +Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" +Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE" +``` + +By default, sequelize uses the isolation level of the database. If you want to use a different isolation level, pass in the desired level as the first argument: + +```js +const { Transaction } = require('sequelize'); + +await sequelize.transaction({ + isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE +}, async (t) => { + // Your code +}); +``` + +You can also overwrite the `isolationLevel` setting globally with an option in the Sequelize constructor: + +```js +const { Sequelize, Transaction } = require('sequelize'); + +const sequelize = new Sequelize('sqlite::memory:', { + isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE +}); +``` + +**Note for MSSQL:** _The `SET ISOLATION LEVEL` queries are not logged since the specified `isolationLevel` is passed directly to `tedious`._ + +## Usage with other sequelize methods + +The `transaction` option goes with most other options, which are usually the first argument of a method. + +For methods that take values, like `.create`, `.update()`, etc. `transaction` should be passed to the option in the second argument. + +If unsure, refer to the API documentation for the method you are using to be sure of the signature. + +Examples: + +```js +await User.create({ name: 'Foo Bar' }, { transaction: t }); + +await User.findAll({ + where: { + name: 'Foo Bar' + }, + transaction: t +}); +``` + +## The `afterCommit` hook + +A `transaction` object allows tracking if and when it is committed. + +An `afterCommit` hook can be added to both managed and unmanaged transaction objects: + +```js +// Managed transaction: +await sequelize.transaction(async (t) => { + t.afterCommit(() => { + // Your logic + }); +}); + +// Unmanaged transaction: +const t = await sequelize.transaction(); +t.afterCommit(() => { + // Your logic +}); +await t.commit(); +``` + +The callback passed to `afterCommit` can be `async`. In this case: + +* For a managed transaction: the `sequelize.transaction` call will wait for it before settling; +* For an unmanaged transaction: the `t.commit` call will wait for it before settling. + +Notes: + +* The `afterCommit` hook is not raised if the transaction is rolled back; +* The `afterCommit` hook does not modify the return value of the transaction (unlike most hooks) + +You can use the `afterCommit` hook in conjunction with model hooks to know when a instance is saved and available outside of a transaction + +```js +User.afterSave((instance, options) => { + if (options.transaction) { + // Save done within a transaction, wait until transaction is committed to + // notify listeners the instance has been saved + options.transaction.afterCommit(() => /* Notify */) + return; + } + // Save done outside a transaction, safe for callers to fetch the updated model + // Notify +}); +``` + +## Locks + +Queries within a `transaction` can be performed with locks: + +```js +return User.findAll({ + limit: 1, + lock: true, + transaction: t1 +}); +``` + +Queries within a transaction can skip locked rows: + +```js +return User.findAll({ + limit: 1, + lock: true, + skipLocked: true, + transaction: t2 +}); +``` diff --git a/docs/manual/other-topics/typescript.md b/docs/manual/other-topics/typescript.md new file mode 100644 index 000000000000..836963e20de6 --- /dev/null +++ b/docs/manual/other-topics/typescript.md @@ -0,0 +1,365 @@ +# TypeScript + +Since v5, Sequelize provides its own TypeScript definitions. Please note that only TS >= 3.1 is supported. + +As Sequelize heavily relies on runtime property assignments, TypeScript won't be very useful out of the box. A decent amount of manual type declarations are needed to make models workable. + +## Installation + +In order to avoid installation bloat for non TS users, you must install the following typing packages manually: + +- `@types/node` (this is universally required in node projects) +- `@types/validator` + +## Usage + +Example of a minimal TypeScript project with strict type-checking for attributes. + +**NOTE:** Keep the following code in sync with `/types/test/typescriptDocs/ModelInit.ts` to ensure it typechecks correctly. + +```ts +import { + Sequelize, + Model, + ModelDefined, + DataTypes, + HasManyGetAssociationsMixin, + HasManyAddAssociationMixin, + HasManyHasAssociationMixin, + Association, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + Optional, +} from "sequelize"; + +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); + +// These are all the attributes in the User model +interface UserAttributes { + id: number; + name: string; + preferredName: string | null; +} + +// Some attributes are optional in `User.build` and `User.create` calls +interface UserCreationAttributes extends Optional {} + +class User extends Model + implements UserAttributes { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields + + // timestamps! + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + // Since TS cannot determine model association at compile time + // we have to declare them here purely virtually + // these will not exist until `Model.init` was called. + public getProjects!: HasManyGetAssociationsMixin; // Note the null assertions! + public addProject!: HasManyAddAssociationMixin; + public hasProject!: HasManyHasAssociationMixin; + public countProjects!: HasManyCountAssociationsMixin; + public createProject!: HasManyCreateAssociationMixin; + + // You can also pre-declare possible inclusions, these will only be populated if you + // actively include a relation. + public readonly projects?: Project[]; // Note this is optional since it's only populated when explicitly requested in code + + public static associations: { + projects: Association; + }; +} + +interface ProjectAttributes { + id: number; + ownerId: number; + name: string; +} + +interface ProjectCreationAttributes extends Optional {} + +class Project extends Model + implements ProjectAttributes { + public id!: number; + public ownerId!: number; + public name!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +interface AddressAttributes { + userId: number; + address: string; +} + +// You can write `extends Model` instead, +// but that will do the exact same thing as below +class Address extends Model implements AddressAttributes { + public userId!: number; + public address!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +// You can also define modules in a functional way +interface NoteAttributes { + id: number; + title: string; + content: string; +} + +// You can also set multiple attributes optional at once +interface NoteCreationAttributes extends Optional {}; + +Project.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + ownerId: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + }, + { + sequelize, + tableName: "projects", + } +); + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: "users", + sequelize, // passing the `sequelize` instance is required + } +); + +Address.init( + { + userId: { + type: DataTypes.INTEGER.UNSIGNED, + }, + address: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + }, + { + tableName: "address", + sequelize, // passing the `sequelize` instance is required + } +); + +// And with a functional approach defining a module looks like this +const Note: ModelDefined< + NoteAttributes, + NoteCreationAttributes +> = sequelize.define( + 'Note', + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + title: { + type: new DataTypes.STRING(64), + defaultValue: 'Unnamed Note', + }, + content: { + type: new DataTypes.STRING(4096), + allowNull: false, + }, + }, + { + tableName: 'notes', + } +); + +// Here we associate which actually populates out pre-declared `association` static and other methods. +User.hasMany(Project, { + sourceKey: "id", + foreignKey: "ownerId", + as: "projects", // this determines the name in `associations`! +}); + +Address.belongsTo(User, { targetKey: "id" }); +User.hasOne(Address, { sourceKey: "id" }); + +async function doStuffWithUser() { + const newUser = await User.create({ + name: "Johnny", + preferredName: "John", + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const project = await newUser.createProject({ + name: "first!", + }); + + const ourUser = await User.findByPk(1, { + include: [User.associations.projects], + rejectOnEmpty: true, // Specifying true here removes `null` from the return type! + }); + + // Note the `!` null assertion since TS can't know if we included + // the model or not + console.log(ourUser.projects![0].name); +} +``` + +### Usage without strict types for attributes + +The typings for Sequelize v5 allowed you to define models without specifying types for the attributes. This is still possible for backwards compatibility and for cases where you feel strict typing for attributes isn't worth it. + +**NOTE:** Keep the following code in sync with `typescriptDocs/ModelInitNoAttributes.ts` to ensure +it typechecks correctly. + +```ts +import { Sequelize, Model, DataTypes } from "sequelize"; + +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); + +class User extends Model { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields +} + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: "users", + sequelize, // passing the `sequelize` instance is required + } +); + +async function doStuffWithUserModel() { + const newUser = await User.create({ + name: "Johnny", + preferredName: "John", + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const foundUser = await User.findOne({ where: { name: "Johnny" } }); + if (foundUser === null) return; + console.log(foundUser.name); +} +``` + +## Usage of `sequelize.define` + +In Sequelize versions before v5, the default way of defining a model involved using `sequelize.define`. It's still possible to define models with that, and you can also add typings to these models using interfaces. + +**NOTE:** Keep the following code in sync with `typescriptDocs/Define.ts` to ensure +it typechecks correctly. + +```ts +import { Sequelize, Model, DataTypes, Optional } from "sequelize"; + +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); + +// We recommend you declare an interface for the attributes, for stricter typechecking +interface UserAttributes { + id: number; + name: string; +} + +// Some fields are optional when calling UserModel.create() or UserModel.build() +interface UserCreationAttributes extends Optional {} + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance + extends Model, + UserAttributes {} + +const UserModel = sequelize.define("User", { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + }, +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} +``` + +If you're comfortable with somewhat less strict typing for the attributes on a model, you can save some code by defining the Instance to just extend `Model` without any attributes in the generic types. + +**NOTE:** Keep the following code in sync with `typescriptDocs/DefineNoAttributes.ts` to ensure +it typechecks correctly. + +```ts +import { Sequelize, Model, DataTypes } from "sequelize"; + +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance extends Model { + id: number; + name: string; +} + +const UserModel = sequelize.define("User", { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + }, +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} +``` diff --git a/docs/manual/other-topics/upgrade-to-v6.md b/docs/manual/other-topics/upgrade-to-v6.md new file mode 100644 index 000000000000..fd047d5cd694 --- /dev/null +++ b/docs/manual/other-topics/upgrade-to-v6.md @@ -0,0 +1,236 @@ +# Upgrade to v6 + +Sequelize v6 is the next major release after v5. Below is a list of breaking changes to help you upgrade. + +## Breaking Changes + +### Support for Node 10 and up + +Sequelize v6 will only support Node 10 and up [#10821](https://github.com/sequelize/sequelize/issues/10821). + +### CLS + +You should now use [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked) package for CLS support. + +```js +const cls = require("cls-hooked"); +const namespace = cls.createNamespace("...."); +const Sequelize = require("sequelize"); + +Sequelize.useCLS(namespace); +``` + +### Database Engine Support + +We have updated our minimum supported database engine versions. Using older database engine will show `SEQUELIZE0006` deprecation warning. Please check [ENGINE.md](https://github.com/sequelize/sequelize/blob/main/ENGINE.md) for version table. + +### Sequelize + +- Bluebird has been removed. Internally all methods are now using async/await. Public API now returns native promises. Thanks to [Andy Edwards](https://github.com/jedwards1211) for this refactor work. +- `Sequelize.Promise` is no longer available. +- `sequelize.import` method has been removed. CLI users should update to `sequelize-cli@6`. +- All instances of QueryInterface and QueryGenerator have been renamed to their lowerCamelCase variants eg. queryInterface and queryGenerator when used as property names on Model and Dialect, the class names remain the same. + +### Model + +#### `options.returning` + +Option `returning: true` will no longer return attributes that are not defined in the model. Old behavior can be achieved by using `returning: ['*']` instead. + +#### `Model.changed()` + +This method now tests for equality with [`_.isEqual`](https://lodash.com/docs/4.17.15#isEqual) and is now deep aware for JSON objects. Modifying a nested value for a JSON object won't mark it as changed (since it is still the same object). + +```js +const instance = await MyModel.findOne(); + +instance.myJsonField.someProperty = 12345; // Changed from something else to 12345 +console.log(instance.changed()); // false + +await instance.save(); // this will not save anything + +instance.changed("myJsonField", true); +console.log(instance.changed()); // ['myJsonField'] + +await instance.save(); // will save +``` + +#### `Model.bulkCreate()` + +This method now throws `Sequelize.AggregateError` instead of `Bluebird.AggregateError`. All errors are now exposed as `errors` key. + +#### `Model.upsert()` + +Native upsert is now supported for all dialects. + +```js +const [instance, created] = await MyModel.upsert({}); +``` + +Signature for this method has been changed to `Promise`. First index contains upserted `instance`, second index contains a boolean (or `null`) indicating if record was created or updated. For SQLite/Postgres, `created` value will always be `null`. + +- MySQL - Implemented with ON DUPLICATE KEY UPDATE +- PostgreSQL - Implemented with ON CONFLICT DO UPDATE +- SQLite - Implemented with ON CONFLICT DO UPDATE +- MSSQL - Implemented with MERGE statement + +_Note for Postgres users:_ If upsert payload contains PK field, then PK will be used as the conflict target. Otherwise first unique constraint will be selected as the conflict key. + +### QueryInterface + +#### `addConstraint` + +This method now only takes 2 parameters, `tableName` and `options`. Previously the second parameter could be a list of column names to apply the constraint to, this list must now be passed as `options.fields` property. + +## Changelog + +### 6.0.0-beta.7 + +- docs(associations): belongs to many create with through table +- docs(query-interface): fix broken links [#12272](https://github.com/sequelize/sequelize/pull/12272) +- docs(sequelize): omitNull only works for CREATE/UPDATE queries +- docs: asyncify [#12297](https://github.com/sequelize/sequelize/pull/12297) +- docs: responsive [#12308](https://github.com/sequelize/sequelize/pull/12308) +- docs: update feature request template +- feat(postgres): native upsert [#12301](https://github.com/sequelize/sequelize/pull/12301) +- feat(sequelize): allow passing dialectOptions.options from url [#12404](https://github.com/sequelize/sequelize/pull/12404) +- fix(include): check if attributes specified for included through model [#12316](https://github.com/sequelize/sequelize/pull/12316) +- fix(model.destroy): return 0 with truncate [#12281](https://github.com/sequelize/sequelize/pull/12281) +- fix(mssql): empty order array generates invalid FETCH statement [#12261](https://github.com/sequelize/sequelize/pull/12261) +- fix(postgres): parse enums correctly when describing a table [#12409](https://github.com/sequelize/sequelize/pull/12409) +- fix(query): ensure correct return signature for QueryTypes.RAW [#12305](https://github.com/sequelize/sequelize/pull/12305) +- fix(query): preserve cls context for logger [#12328](https://github.com/sequelize/sequelize/pull/12328) +- fix(query-generator): do not generate GROUP BY clause if options.group is empty [#12343](https://github.com/sequelize/sequelize/pull/12343) +- fix(reload): include default scope [#12399](https://github.com/sequelize/sequelize/pull/12399) +- fix(types): add Association into OrderItem type [#12332](https://github.com/sequelize/sequelize/pull/12332) +- fix(types): add clientMinMessages to Options interface [#12375](https://github.com/sequelize/sequelize/pull/12375) +- fix(types): transactionType in Options [#12377](https://github.com/sequelize/sequelize/pull/12377) +- fix(types): add support for optional values in "where" clauses [#12337](https://github.com/sequelize/sequelize/pull/12337) +- fix(types): add missing fields to 'FindOrCreateType' [#12338](https://github.com/sequelize/sequelize/pull/12338) +- fix: add missing sql and parameters properties to some query errors [#12299](https://github.com/sequelize/sequelize/pull/12299) +- fix: remove custom inspect [#12262](https://github.com/sequelize/sequelize/pull/12262) +- refactor: cleanup query generators [#12304](https://github.com/sequelize/sequelize/pull/12304) + +### 6.0.0-beta.6 + +- docs(add-constraint): options.fields support +- docs(association): document uniqueKey for belongs to many [#12166](https://github.com/sequelize/sequelize/pull/12166) +- docs(association): options.through.where support +- docs(association): use and instead of 'a nd' [#12191](https://github.com/sequelize/sequelize/pull/12191) +- docs(association): use correct scope name [#12204](https://github.com/sequelize/sequelize/pull/12204) +- docs(manuals): avoid duplicate header ids [#12201](https://github.com/sequelize/sequelize/pull/12201) +- docs(model): correct syntax error in example code [#12137](https://github.com/sequelize/sequelize/pull/12137) +- docs(query-interface): removeIndex indexNameOrAttributes [#11947](https://github.com/sequelize/sequelize/pull/11947) +- docs(resources): add sequelize-guard library [#12235](https://github.com/sequelize/sequelize/pull/12235) +- docs(typescript): fix confusing comments [#12226](https://github.com/sequelize/sequelize/pull/12226) +- docs(v6-guide): bluebird removal API changes +- docs: database version support info [#12168](https://github.com/sequelize/sequelize/pull/12168) +- docs: remove remaining bluebird references [#12167](https://github.com/sequelize/sequelize/pull/12167) +- feat(belongs-to-many): allow creation of paranoid join tables [#12088](https://github.com/sequelize/sequelize/pull/12088) +- feat(belongs-to-many): get/has/count for paranoid join table [#12256](https://github.com/sequelize/sequelize/pull/12256) +- feat(pool): expose maxUses pool config option [#12101](https://github.com/sequelize/sequelize/pull/12101) +- feat(postgres): minify include aliases over limit [#11940](https://github.com/sequelize/sequelize/pull/11940) +- feat(sequelize): handle query string host value [#12041](https://github.com/sequelize/sequelize/pull/12041) +- fix(associations): ensure correct schema on all generated attributes [#12258](https://github.com/sequelize/sequelize/pull/12258) +- fix(docs/instances): use correct variable for increment [#12087](https://github.com/sequelize/sequelize/pull/12087) +- fix(include): separate queries are not sub-queries [#12144](https://github.com/sequelize/sequelize/pull/12144) +- fix(model): fix unchained promise in association logic in bulkCreate [#12163](https://github.com/sequelize/sequelize/pull/12163) +- fix(model): updateOnDuplicate handles composite keys [#11984](https://github.com/sequelize/sequelize/pull/11984) +- fix(model.count): distinct without any column generates invalid SQL [#11946](https://github.com/sequelize/sequelize/pull/11946) +- fix(model.reload): ignore options.where and always use this.where() [#12211](https://github.com/sequelize/sequelize/pull/12211) +- fix(mssql) insert record failure because of BOOLEAN column type [#12090](https://github.com/sequelize/sequelize/pull/12090) +- fix(mssql): cast sql_variant in query generator [#11994](https://github.com/sequelize/sequelize/pull/11994) +- fix(mssql): dont use OUTPUT INSERTED for update without returning [#12260](https://github.com/sequelize/sequelize/pull/12260) +- fix(mssql): duplicate order in FETCH/NEXT queries [#12257](https://github.com/sequelize/sequelize/pull/12257) +- fix(mssql): set correct scale for float [#11962](https://github.com/sequelize/sequelize/pull/11962) +- fix(mssql): tedious v9 requires connect call [#12182](https://github.com/sequelize/sequelize/pull/12182) +- fix(mssql): use uppercase for engine table and columns [#12212](https://github.com/sequelize/sequelize/pull/12212) +- fix(pool): show deprecation when engine is not supported [#12218](https://github.com/sequelize/sequelize/pull/12218) +- fix(postgres): addColumn support ARRAY(ENUM) [#12259](https://github.com/sequelize/sequelize/pull/12259) +- fix(query): do not bind \$ used within a whole-word [#12250](https://github.com/sequelize/sequelize/pull/12250) +- fix(query-generator): handle literal for substring based operators [#12210](https://github.com/sequelize/sequelize/pull/12210) +- fix(query-interface): allow passing null for query interface insert [#11931](https://github.com/sequelize/sequelize/pull/11931) +- fix(query-interface): allow sequelize.fn and sequelize.literal in fields of IndexesOptions [#12224](https://github.com/sequelize/sequelize/pull/12224) +- fix(scope): don't modify original scope definition [#12207](https://github.com/sequelize/sequelize/pull/12207) +- fix(sqlite): multiple primary keys results in syntax error [#12237](https://github.com/sequelize/sequelize/pull/12237) +- fix(sync): pass options to all query methods [#12208](https://github.com/sequelize/sequelize/pull/12208) +- fix(typings): add type_helpers to file list [#12000](https://github.com/sequelize/sequelize/pull/12000) +- fix(typings): correct Model.init return type [#12148](https://github.com/sequelize/sequelize/pull/12148) +- fix(typings): fn is assignable to where [#12040](https://github.com/sequelize/sequelize/pull/12040) +- fix(typings): getForeignKeysForTables argument definition [#12084](https://github.com/sequelize/sequelize/pull/12084) +- fix(typings): make between operator accept date ranges [#12162](https://github.com/sequelize/sequelize/pull/12162) +- refactor(ci): improve database wait script [#12132](https://github.com/sequelize/sequelize/pull/12132) +- refactor(tsd-test-setup): add & setup dtslint [#11879](https://github.com/sequelize/sequelize/pull/11879) +- refactor: move all dialect conditional logic into subclass [#12217](https://github.com/sequelize/sequelize/pull/12217) +- refactor: remove sequelize.import helper [#12175](https://github.com/sequelize/sequelize/pull/12175) +- refactor: use native versions [#12159](https://github.com/sequelize/sequelize/pull/12159) +- refactor: use object spread instead of Object.assign [#12213](https://github.com/sequelize/sequelize/pull/12213) + +### 6.0.0-beta.5 + +- fix(find-all): throw on empty attributes [#11867](https://github.com/sequelize/sequelize/pull/11867) +- fix(types): `queryInterface.addIndex` [#11844](https://github.com/sequelize/sequelize/pull/11844) +- fix(types): `plain` option in `sequelize.query` [#11596](https://github.com/sequelize/sequelize/pull/11596) +- fix(types): correct overloaded method order [#11727](https://github.com/sequelize/sequelize/pull/11727) +- fix(types): `comparator` arg of `Sequelize.where` [#11843](https://github.com/sequelize/sequelize/pull/11843) +- fix(types): fix BelongsToManyGetAssociationsMixinOptions [#11818](https://github.com/sequelize/sequelize/pull/11818) +- fix(types): adds `hooks` to `CreateOptions` [#11736](https://github.com/sequelize/sequelize/pull/11736) +- fix(increment): broken queries [#11852](https://github.com/sequelize/sequelize/pull/11852) +- fix(associations): gets on many-to-many with non-primary target key [#11778](https://github.com/sequelize/sequelize11778/pull/) +- fix: properly select SRID if present [#11763](https://github.com/sequelize/sequelize/pull/11763) +- feat(sqlite): automatic path provision for `options.storage` [#11853](https://github.com/sequelize/sequelize/pull/11853) +- feat(postgres): `idle_in_transaction_session_timeout` connection option [#11775](https://github.com/sequelize/sequelize11775/pull/) +- feat(index): improve to support multiple fields with operator [#11934](https://github.com/sequelize/sequelize/pull/11934) +- docs(transactions): fix addIndex example and grammar [#11759](https://github.com/sequelize/sequelize/pull/11759) +- docs(raw-queries): remove outdated info [#11833](https://github.com/sequelize/sequelize/pull/11833) +- docs(optimistic-locking): fix missing manual [#11850](https://github.com/sequelize/sequelize/pull/11850) +- docs(model): findOne return value for empty result [#11762](https://github.com/sequelize/sequelize/pull/11762) +- docs(model-querying-basics.md): add some commas [#11891](https://github.com/sequelize/sequelize/pull/11891) +- docs(manuals): fix missing models-definition page [#11838](https://github.com/sequelize/sequelize/pull/11838) +- docs(manuals): extensive rewrite [#11825](https://github.com/sequelize/sequelize/pull/11825) +- docs(dialect-specific): add MSSQL domain auth example [#11799](https://github.com/sequelize/sequelize/pull/11799) +- docs(associations): fix typos in assocs manual [#11888](https://github.com/sequelize/sequelize/pull/11888) +- docs(associations): fix typo [#11869](https://github.com/sequelize/sequelize/pull/11869) + +### 6.0.0-beta.4 + +- feat(sync): allow to bypass drop statements when sync with alter enabled [#11708](https://github.com/sequelize/sequelize/pull/11708) +- fix(model): injectDependentVirtualAttrs on included models [#11713](https://github.com/sequelize/sequelize/pull/11713) +- fix(model): generate ON CONFLICT ... DO UPDATE correctly [#11666](https://github.com/sequelize/sequelize/pull/11666) +- fix(mssql): optimize formatError RegEx [#11725](https://github.com/sequelize/sequelize/pull/11725) +- fix(types): add getForeignKeyReferencesForTable type [#11738](https://github.com/sequelize/sequelize/pull/11738) +- fix(types): add 'restore' hooks to types [#11730](https://github.com/sequelize/sequelize/pull/11730) +- fix(types): added 'fieldMaps' to QueryOptions typings [#11702](https://github.com/sequelize/sequelize/pull/11702) +- fix(types): add isSoftDeleted to Model [#11628](https://github.com/sequelize/sequelize/pull/11628) +- fix(types): fix upsert typing [#11674](https://github.com/sequelize/sequelize/pull/11674) +- fix(types): specified 'this' for getters and setters in fields [#11648](https://github.com/sequelize/sequelize/pull/11648) +- fix(types): add paranoid to UpdateOptions interface [#11647](https://github.com/sequelize/sequelize/pull/11647) +- fix(types): include 'as' in IncludeThroughOptions definition [#11624](https://github.com/sequelize/sequelize/pull/11624) +- fix(types): add Includeable to IncludeOptions.include type [#11622](https://github.com/sequelize/sequelize/pull/11622) +- fix(types): transaction lock [#11620](https://github.com/sequelize/sequelize/pull/11620) +- fix(sequelize.fn): escape dollarsign (#11533) [#11606](https://github.com/sequelize/sequelize/pull/11606) +- fix(types): add nested to Includeable [#11354](https://github.com/sequelize/sequelize/pull/11354) +- fix(types): add date to where [#11612](https://github.com/sequelize/sequelize/pull/11612) +- fix(types): add getDatabaseName (#11431) [#11614](https://github.com/sequelize/sequelize/pull/11614) +- fix(types): beforeDestroy [#11618](https://github.com/sequelize/sequelize/pull/11618) +- fix(types): query-interface table schema [#11582](https://github.com/sequelize/sequelize/pull/11582) +- docs: README.md [#11698](https://github.com/sequelize/sequelize/pull/11698) +- docs(sequelize): detail options.retry usage [#11643](https://github.com/sequelize/sequelize/pull/11643) +- docs: clarify logging option in Sequelize constructor [#11653](https://github.com/sequelize/sequelize/pull/11653) +- docs(migrations): fix syntax error in example [#11626](https://github.com/sequelize/sequelize/pull/11626) +- docs: describe logging option [#11654](https://github.com/sequelize/sequelize/pull/11654) +- docs(transaction): fix typo [#11659](https://github.com/sequelize/sequelize/pull/11659) +- docs(hooks): add info about belongs-to-many [#11601](https://github.com/sequelize/sequelize/pull/11601) +- docs(associations): fix typo [#11592](https://github.com/sequelize/sequelize/pull/11592) + +### 6.0.0-beta.3 + +- feat: support cls-hooked / tests [#11584](https://github.com/sequelize/sequelize/pull/11584) + +### 6.0.0-beta.2 + +- feat(postgres): change returning option to only return model attributes [#11526](https://github.com/sequelize/sequelize/pull/11526) +- fix(associations): allow binary key for belongs-to-many [#11578](https://github.com/sequelize/sequelize/pull/11578) +- fix(postgres): always replace returning statement for upsertQuery +- fix(model): make .changed() deep aware [#10851](https://github.com/sequelize/sequelize/pull/10851) +- change: use node 10 [#11580](https://github.com/sequelize/sequelize/pull/11580) diff --git a/docs/manual/whos-using.md b/docs/manual/other-topics/whos-using.md similarity index 100% rename from docs/manual/whos-using.md rename to docs/manual/other-topics/whos-using.md diff --git a/docs/manual/querying.md b/docs/manual/querying.md deleted file mode 100644 index 7b3b50bd5aed..000000000000 --- a/docs/manual/querying.md +++ /dev/null @@ -1,452 +0,0 @@ -# Querying - -## Attributes - -To select only some attributes, you can use the `attributes` option. Most often, you pass an array: - -```js -Model.findAll({ - attributes: ['foo', 'bar'] -}); -``` - -```sql -SELECT foo, bar ... -``` - -Attributes can be renamed using a nested array: - -```js -Model.findAll({ - attributes: ['foo', ['bar', 'baz']] -}); -``` - -```sql -SELECT foo, bar AS baz ... -``` - -You can use `sequelize.fn` to do aggregations: - -```js -Model.findAll({ - attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] -}); -``` - -```sql -SELECT COUNT(hats) AS no_hats ... -``` - -When using aggregation function, you must give it an alias to be able to access it from the model. In the example above you can get the number of hats with `instance.get('no_hats')`. - -Sometimes it may be tiresome to list all the attributes of the model if you only want to add an aggregation: - -```js -// This is a tiresome way of getting the number of hats... -Model.findAll({ - attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] -}); - -// This is shorter, and less error prone because it still works if you add / remove attributes -Model.findAll({ - attributes: { include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] } -}); -``` - -```sql -SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ... -``` - -Similarly, it's also possible to remove a selected few attributes: - -```js -Model.findAll({ - attributes: { exclude: ['baz'] } -}); -``` - -```sql -SELECT id, foo, bar, quz ... -``` - -## Where - -Whether you are querying with findAll/find or doing bulk updates/destroys you can pass a `where` object to filter the query. - -`where` generally takes an object from attribute:value pairs, where value can be primitives for equality matches or keyed objects for other operators. - -It's also possible to generate complex AND/OR conditions by nesting sets of `or` and `and` `Operators`. - -### Basics - -```js -const Op = Sequelize.Op; - -Post.findAll({ - where: { - authorId: 2 - } -}); -// SELECT * FROM post WHERE authorId = 2 - -Post.findAll({ - where: { - authorId: 12, - status: 'active' - } -}); -// SELECT * FROM post WHERE authorId = 12 AND status = 'active'; - -Post.findAll({ - where: { - [Op.or]: [{authorId: 12}, {authorId: 13}] - } -}); -// SELECT * FROM post WHERE authorId = 12 OR authorId = 13; - -Post.findAll({ - where: { - authorId: { - [Op.or]: [12, 13] - } - } -}); -// SELECT * FROM post WHERE authorId = 12 OR authorId = 13; - -Post.destroy({ - where: { - status: 'inactive' - } -}); -// DELETE FROM post WHERE status = 'inactive'; - -Post.update({ - updatedAt: null, -}, { - where: { - deletedAt: { - [Op.ne]: null - } - } -}); -// UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL; - -Post.findAll({ - where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6) -}); -// SELECT * FROM post WHERE char_length(status) = 6; -``` - -### Operators - -Sequelize exposes symbol operators that can be used for to create more complex comparisons - - -```js -const Op = Sequelize.Op - -[Op.and]: [{a: 5}, {b: 6}] // (a = 5) AND (b = 6) -[Op.or]: [{a: 5}, {a: 6}] // (a = 5 OR a = 6) -[Op.gt]: 6, // > 6 -[Op.gte]: 6, // >= 6 -[Op.lt]: 10, // < 10 -[Op.lte]: 10, // <= 10 -[Op.ne]: 20, // != 20 -[Op.eq]: 3, // = 3 -[Op.is]: null // IS NULL -[Op.not]: true, // IS NOT TRUE -[Op.between]: [6, 10], // BETWEEN 6 AND 10 -[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 -[Op.in]: [1, 2], // IN [1, 2] -[Op.notIn]: [1, 2], // NOT IN [1, 2] -[Op.like]: '%hat', // LIKE '%hat' -[Op.notLike]: '%hat' // NOT LIKE '%hat' -[Op.iLike]: '%hat' // ILIKE '%hat' (case insensitive) (PG only) -[Op.notILike]: '%hat' // NOT ILIKE '%hat' (PG only) -[Op.startsWith]: 'hat' // LIKE 'hat%' -[Op.endsWith]: 'hat' // LIKE '%hat' -[Op.substring]: 'hat' // LIKE '%hat%' -[Op.regexp]: '^[h|a|t]' // REGEXP/~ '^[h|a|t]' (MySQL/PG only) -[Op.notRegexp]: '^[h|a|t]' // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) -[Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (PG only) -[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) -[Op.like]: { [Op.any]: ['cat', 'hat']} - // LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike -[Op.overlap]: [1, 2] // && [1, 2] (PG array overlap operator) -[Op.contains]: [1, 2] // @> [1, 2] (PG array contains operator) -[Op.contained]: [1, 2] // <@ [1, 2] (PG array contained by operator) -[Op.any]: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only) - -[Op.col]: 'user.organization_id' // = "user"."organization_id", with dialect specific column identifiers, PG in this example -[Op.gt]: { [Op.all]: literal('SELECT 1') } - // > ALL (SELECT 1) -``` - -#### Range Operators - -Range types can be queried with all supported operators. - -Keep in mind, the provided range value can -[define the bound inclusion/exclusion](data-types.html#range-types) -as well. - -```js -// All the above equality and inequality operators plus the following: - -[Op.contains]: 2 // @> '2'::integer (PG range contains element operator) -[Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator) -[Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator) -[Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator) -[Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator) -[Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator) -[Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator) -[Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator) -[Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator) -``` - -#### Combinations - -```js -const Op = Sequelize.Op; - -{ - rank: { - [Op.or]: { - [Op.lt]: 1000, - [Op.eq]: null - } - } -} -// rank < 1000 OR rank IS NULL - -{ - createdAt: { - [Op.lt]: new Date(), - [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000) - } -} -// createdAt < [timestamp] AND createdAt > [timestamp] - -{ - [Op.or]: [ - { - title: { - [Op.like]: 'Boat%' - } - }, - { - description: { - [Op.like]: '%boat%' - } - } - ] -} -// title LIKE 'Boat%' OR description LIKE '%boat%' -``` - -### JSON - -The JSON data type is supported by the PostgreSQL, SQLite, MySQL and MariaDB dialects only. - -#### PostgreSQL - -The JSON data type in PostgreSQL stores the value as plain text, as opposed to binary representation. If you simply want to store and retrieve a JSON representation, using JSON will take less disk space and less time to build from its input representation. However, if you want to do any operations on the JSON value, you should prefer the JSONB data type described below. - -#### MSSQL - -MSSQL does not have a JSON data type, however it does provide support for JSON stored as strings through certain functions since SQL Server 2016. Using these functions, you will be able to query the JSON stored in the string, but any returned values will need to be parsed seperately. - -```js -// ISJSON - to test if a string contains valid JSON -User.findAll({ - where: sequelize.where(sequelize.fn('ISJSON', sequelize.col('userDetails')), 1) -}) - -// JSON_VALUE - extract a scalar value from a JSON string -User.findAll({ - attributes: [[ sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), 'address line 1']] -}) - -// JSON_VALUE - query a scalar value from a JSON string -User.findAll({ - where: sequelize.where(sequelize.fn('JSON_VALUE', sequelize.col('userDetails'), '$.address.Line1'), '14, Foo Street') -}) - -// JSON_QUERY - extract an object or array -User.findAll({ - attributes: [[ sequelize.fn('JSON_QUERY', sequelize.col('userDetails'), '$.address'), 'full address']] -}) -``` - -### JSONB - -JSONB can be queried in three different ways. - -#### Nested object - -```js -{ - meta: { - video: { - url: { - [Op.ne]: null - } - } - } -} -``` - -#### Nested key - -```js -{ - "meta.audio.length": { - [Op.gt]: 20 - } -} -``` - -#### Containment - -```js -{ - "meta": { - [Op.contains]: { - site: { - url: 'http://google.com' - } - } - } -} -``` - -### Relations / Associations - -```js -// Find all projects with a least one task where task.state === project.state -Project.findAll({ - include: [{ - model: Task, - where: { state: Sequelize.col('project.state') } - }] -}) -``` - -## Pagination / Limiting - -```js -// Fetch 10 instances/rows -Project.findAll({ limit: 10 }) - -// Skip 8 instances/rows -Project.findAll({ offset: 8 }) - -// Skip 5 instances and fetch the 5 after that -Project.findAll({ offset: 5, limit: 5 }) -``` - -## Ordering - -`order` takes an array of items to order the query by or a sequelize method. Generally you will want to use a tuple/array of either attribute, direction or just direction to ensure proper escaping. - -```js -Subtask.findAll({ - order: [ - // Will escape title and validate DESC against a list of valid direction parameters - ['title', 'DESC'], - - // Will order by max(age) - sequelize.fn('max', sequelize.col('age')), - - // Will order by max(age) DESC - [sequelize.fn('max', sequelize.col('age')), 'DESC'], - - // Will order by otherfunction(`col1`, 12, 'lalala') DESC - [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], - - // Will order an associated model's created_at using the model name as the association's name. - [Task, 'createdAt', 'DESC'], - - // Will order through an associated model's created_at using the model names as the associations' names. - [Task, Project, 'createdAt', 'DESC'], - - // Will order by an associated model's created_at using the name of the association. - ['Task', 'createdAt', 'DESC'], - - // Will order by a nested associated model's created_at using the names of the associations. - ['Task', 'Project', 'createdAt', 'DESC'], - - // Will order by an associated model's created_at using an association object. (preferred method) - [Subtask.associations.Task, 'createdAt', 'DESC'], - - // Will order by a nested associated model's created_at using association objects. (preferred method) - [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'], - - // Will order by an associated model's created_at using a simple association object. - [{model: Task, as: 'Task'}, 'createdAt', 'DESC'], - - // Will order by a nested associated model's created_at simple association objects. - [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC'] - ] - - // Will order by max age descending - order: sequelize.literal('max(age) DESC') - - // Will order by max age ascending assuming ascending is the default order when direction is omitted - order: sequelize.fn('max', sequelize.col('age')) - - // Will order by age ascending assuming ascending is the default order when direction is omitted - order: sequelize.col('age') - - // Will order randomly based on the dialect (instead of fn('RAND') or fn('RANDOM')) - order: sequelize.random() -}) -``` - -## Table Hint - -`tableHint` can be used to optionally pass a table hint when using mssql. The hint must be a value from `Sequelize.TableHints` and should only be used when absolutely necessary. Only a single table hint is currently supported per query. - -Table hints override the default behavior of mssql query optimizer by specifing certain options. They only affect the table or view referenced in that clause. - -```js -const TableHints = Sequelize.TableHints; - -Project.findAll({ - // adding the table hint NOLOCK - tableHint: TableHints.NOLOCK - // this will generate the SQL 'WITH (NOLOCK)' -}) -``` - -## Index Hints - -`indexHints` can be used to optionally pass index hints when using mysql. The hint type must be a value from `Sequelize.IndexHints` and the values should reference existing indexes. - -Index hints [override the default behavior of the mysql query optimizer](https://dev.mysql.com/doc/refman/5.7/en/index-hints.html). - -```js -Project.findAll({ - indexHints: [ - { type: IndexHints.USE, values: ['index_project_on_name'] } - ], - where: { - id: { - [Op.gt]: 623 - }, - name: { - [Op.like]: 'Foo %' - } - } -}) -``` - -Will generate a mysql query that looks like this: - -```sql -SELECT * FROM Project USE INDEX (index_project_on_name) WHERE name LIKE 'FOO %' AND id > 623; -``` - -`Sequelize.IndexHints` includes `USE`, `FORCE`, and `IGNORE`. - -See [Issue #9421](https://github.com/sequelize/sequelize/issues/9421) for the original API proposal. diff --git a/docs/manual/raw-queries.md b/docs/manual/raw-queries.md deleted file mode 100644 index 3bf8e450c307..000000000000 --- a/docs/manual/raw-queries.md +++ /dev/null @@ -1,153 +0,0 @@ -# Raw queries - -As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can use the function `sequelize.query`. - -By default the function will return two arguments - a results array, and an object containing metadata (affected rows etc.). Note that since this is a raw query, the metadata (property names etc.) is dialect specific. Some dialects return the metadata "within" the results object (as properties on an array). However, two arguments will always be returned, but for MSSQL and MySQL it will be two references to the same object. - -```js -sequelize.query("UPDATE users SET y = 42 WHERE x = 12").then(([results, metadata]) => { - // Results will be an empty array and metadata will contain the number of affected rows. -}) -``` - -In cases where you don't need to access the metadata you can pass in a query type to tell sequelize how to format the results. For example, for a simple select query you could do: - -```js -sequelize.query("SELECT * FROM `users`", { type: sequelize.QueryTypes.SELECT}) - .then(users => { - // We don't need spread here, since only the results will be returned for select queries - }) -``` - -Several other query types are available. [Peek into the source for details](https://github.com/sequelize/sequelize/blob/master/lib/query-types.js) - -A second option is the model. If you pass a model the returned data will be instances of that model. - -```js -// Callee is the model definition. This allows you to easily map a query to a predefined model -sequelize - .query('SELECT * FROM projects', { - model: Projects, - mapToModel: true // pass true here if you have any mapped fields - }) - .then(projects => { - // Each record will now be an instance of Project - }) -``` - -See more options in the [query API reference](../class/lib/sequelize.js~Sequelize.html#instance-method-query). Some examples below: - -```js -sequelize.query('SELECT 1', { - // A function (or false) for logging your queries - // Will get called for every SQL query that gets sent - // to the server. - logging: console.log, - - // If plain is true, then sequelize will only return the first - // record of the result set. In case of false it will return all records. - plain: false, - - // Set this to true if you don't have a model definition for your query. - raw: false, - - // The type of query you are executing. The query type affects how results are formatted before they are passed back. - type: Sequelize.QueryTypes.SELECT -}) - -// Note the second argument being null! -// Even if we declared a callee here, the raw: true would -// supersede and return a raw object. -sequelize - .query('SELECT * FROM projects', { raw: true }) - .then(projects => { - console.log(projects) - }) -``` - -## "Dotted" attributes - -If an attribute name of the table contains dots, the resulting objects will be nested. This is due to the usage of [dottie.js](https://github.com/mickhansen/dottie.js/) under the hood. See below: - -```js -sequelize.query('select 1 as `foo.bar.baz`').then(rows => { - console.log(JSON.stringify(rows)) -}) -``` - -```json -[{ - "foo": { - "bar": { - "baz": 1 - } - } -}] -``` - -## Replacements - -Replacements in a query can be done in two different ways, either using named parameters (starting with `:`), or unnamed, represented by a `?`. Replacements are passed in the options object. - -* If an array is passed, `?` will be replaced in the order that they appear in the array -* If an object is passed, `:key` will be replaced with the keys from that object. If the object contains keys not found in the query or vice versa, an exception will be thrown. - -```js -sequelize.query('SELECT * FROM projects WHERE status = ?', - { replacements: ['active'], type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) - -sequelize.query('SELECT * FROM projects WHERE status = :status ', - { replacements: { status: 'active' }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) -``` - -Array replacements will automatically be handled, the following query searches for projects where the status matches an array of values. - -```js -sequelize.query('SELECT * FROM projects WHERE status IN(:status) ', - { replacements: { status: ['active', 'inactive'] }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) -``` - -To use the wildcard operator %, append it to your replacement. The following query matches users with names that start with 'ben'. - -```js -sequelize.query('SELECT * FROM users WHERE name LIKE :search_name ', - { replacements: { search_name: 'ben%' }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) -``` - -## Bind Parameter - -Bind parameters are like replacements. Except replacements are escaped and inserted into the query by sequelize before the query is sent to the database, while bind parameters are sent to the database outside the SQL query text. A query can have either bind parameters or replacements. Bind parameters are referred to by either $1, $2, ... (numeric) or $key (alpha-numeric). This is independent of the dialect. - -* If an array is passed, `$1` is bound to the 1st element in the array (`bind[0]`) -* If an object is passed, `$key` is bound to `object['key']`. Each key must begin with a non-numeric char. `$1` is not a valid key, even if `object['1']` exists. -* In either case `$$` can be used to escape a literal `$` sign. - -The array or object must contain all bound values or Sequelize will throw an exception. This applies even to cases in which the database may ignore the bound parameter. - -The database may add further restrictions to this. Bind parameters cannot be SQL keywords, nor table or column names. They are also ignored in quoted text or data. In PostgreSQL it may also be needed to typecast them, if the type cannot be inferred from the context `$1::varchar`. - -```js -sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $1', - { bind: ['active'], type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) - -sequelize.query('SELECT *, "text with literal $$1 and literal $$status" as t FROM projects WHERE status = $status', - { bind: { status: 'active' }, type: sequelize.QueryTypes.SELECT } -).then(projects => { - console.log(projects) -}) -``` diff --git a/docs/manual/transactions.md b/docs/manual/transactions.md deleted file mode 100644 index fab97d2709d5..000000000000 --- a/docs/manual/transactions.md +++ /dev/null @@ -1,204 +0,0 @@ -# Transactions - -Sequelize supports two ways of using transactions: - -1. **Managed**, One which will automatically commit or rollback the transaction based on the result of a promise chain and, (if CLS enabled) pass the transaction to all calls within the callback -2. **Unmanaged**, One which leaves committing, rolling back and passing the transaction to the user - -The key difference is that the managed transaction uses a callback that expects a promise to be returned to it while the unmanaged transaction returns a promise. - -## Managed transaction (auto-callback) - -Managed transactions handle committing or rolling back the transaction automatically. You start a managed transaction by passing a callback to `sequelize.transaction`. - -Notice how the callback passed to `transaction` returns a promise chain, and does not explicitly call `t.commit()` nor `t.rollback()`. If all promises in the returned chain are resolved successfully the transaction is committed. If one or several of the promises are rejected, the transaction is rolled back. - -```js -return sequelize.transaction(t => { - - // chain all your queries here. make sure you return them. - return User.create({ - firstName: 'Abraham', - lastName: 'Lincoln' - }, {transaction: t}).then(user => { - return user.setShooter({ - firstName: 'John', - lastName: 'Boothe' - }, {transaction: t}); - }); - -}).then(result => { - // Transaction has been committed - // result is whatever the result of the promise chain returned to the transaction callback -}).catch(err => { - // Transaction has been rolled back - // err is whatever rejected the promise chain returned to the transaction callback -}); -``` - -### Throw errors to rollback - -When using the managed transaction you should _never_ commit or rollback the transaction manually. If all queries are successful, but you still want to rollback the transaction (for example because of a validation failure) you should throw an error to break and reject the chain: - -```js -return sequelize.transaction(t => { - return User.create({ - firstName: 'Abraham', - lastName: 'Lincoln' - }, {transaction: t}).then(user => { - // Woops, the query was successful but we still want to roll back! - throw new Error(); - }); -}); -``` - -### Example - -```js -sequelize.transaction((t1) => { - return sequelize.transaction((t2) => { - // Pass in the `transaction` option to define/alter the transaction they belong to. - return Promise.all([ - User.create({ name: 'Bob' }, { transaction: null }), - User.create({ name: 'Mallory' }, { transaction: t1 }), - User.create({ name: 'John' }) // No transaction - ]); - }); -}); -``` - -## Isolation levels - -The possible isolations levels to use when starting a transaction: - -```js -Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" -Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" -Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" -Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE" -``` - -By default, sequelize uses the isolation level of the database. If you want to use a different isolation level, pass in the desired level as the first argument: - -```js -return sequelize.transaction({ - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE - }, (t) => { - - // your transactions - - }); -``` - -The `isolationLevel` can either be set globally when initializing the Sequelize instance or -locally for every transaction: - -```js -// globally -new Sequelize('db', 'user', 'pw', { - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE -}); - -// locally -sequelize.transaction({ - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE -}); -``` - -**Note:** _The SET ISOLATION LEVEL queries are not logged in case of MSSQL as the specified isolationLevel is passed directly to tedious_ - -## Unmanaged transaction (then-callback) - -Unmanaged transactions force you to manually rollback or commit the transaction. If you don't do that, the transaction will hang until it times out. To start an unmanaged transaction, call `sequelize.transaction()` without a callback (you can still pass an options object) and call `then` on the returned promise. Notice that `commit()` and `rollback()` returns a promise. - -```js -return sequelize.transaction().then(t => { - return User.create({ - firstName: 'Bart', - lastName: 'Simpson' - }, {transaction: t}).then(user => { - return user.addSibling({ - firstName: 'Lisa', - lastName: 'Simpson' - }, {transaction: t}); - }).then(() => { - return t.commit(); - }).catch((err) => { - return t.rollback(); - }); -}); -``` - -## Usage with other sequelize methods - -The `transaction` option goes with most other options, which are usually the first argument of a method. -For methods that take values, like `.create`, `.update()`, etc. `transaction` should be passed to the option in the second argument. -If unsure, refer to the API documentation for the method you are using to be sure of the signature. - -## After commit hook - -A `transaction` object allows tracking if and when it is committed. - -An `afterCommit` hook can be added to both managed and unmanaged transaction objects: - -```js -sequelize.transaction(t => { - t.hooks.add('afterCommit', (transaction) => { - // Your logic - }); -}); - -sequelize.transaction().then(t => { - t.hooks.add('afterCommit', (transaction) => { - // Your logic - }); - - return t.commit(); -}) -``` - -The function passed to `afterCommit` can optionally return a promise that will resolve before the promise chain -that created the transaction resolves - -`afterCommit` hooks are _not_ raised if a transaction is rolled back - -`afterCommit` hooks do _not_ modify the return value of the transaction, unlike standard hooks - -You can use the `afterCommit` hook in conjunction with model hooks to know when a instance is saved and available outside -of a transaction - -```js -model.hooks.add('afterSave', (instance, options) => { - if (options.transaction) { - // Save done within a transaction, wait until transaction is committed to - // notify listeners the instance has been saved - options.transaction.hooks.add('afterCommit', () => /* Notify */) - return; - } - // Save done outside a transaction, safe for callers to fetch the updated model - // Notify -}) -``` - -## Locks - -Queries within a `transaction` can be performed with locks - -```js -return User.findAll({ - limit: 1, - lock: true, - transaction: t1 -}) -``` - -Queries within a transaction can skip locked rows - -```js -return User.findAll({ - limit: 1, - lock: true, - skipLocked: true, - transaction: t2 -}) -``` diff --git a/docs/manual/typescript.md b/docs/manual/typescript.md deleted file mode 100644 index 11daa0ba157b..000000000000 --- a/docs/manual/typescript.md +++ /dev/null @@ -1,185 +0,0 @@ -# TypeScript - -Since v5, Sequelize provides its own TypeScript definitions. Please note that only TS >= 3.1 is supported. - -As Sequelize heavily relies on runtime property assignments, TypeScript won't be very useful out of the box. A decent amount of manual type declarations are needed to make models workable. - -## Installation - -In order to avoid installation bloat for non TS users, you must install the following typing packages manually: - -- `@types/node` (this is universally required) -- `@types/validator` -- `@types/bluebird` - -## Usage - -Example of a minimal TypeScript project: - -```ts -import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize'; -import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize'; - -class User extends Model { - public id!: number; // Note that the `null assertion` `!` is required in strict mode. - public name!: string; - public preferredName!: string | null; // for nullable fields - - // timestamps! - public readonly createdAt!: Date; - public readonly updatedAt!: Date; - - // Since TS cannot determine model association at compile time - // we have to declare them here purely virtually - // these will not exist until `Model.init` was called. - - public getProjects!: HasManyGetAssociationsMixin; // Note the null assertions! - public addProject!: HasManyAddAssociationMixin; - public hasProject!: HasManyHasAssociationMixin; - public countProjects!: HasManyCountAssociationsMixin; - public createProject!: HasManyCreateAssociationMixin; - - // You can also pre-declare possible inclusions, these will only be populated if you - // actively include a relation. - public readonly projects?: Project[]; // Note this is optional since it's only populated when explicitly requested in code - - public static associations: { - projects: Association; - }; -} - -const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); - -class Project extends Model { - public id!: number; - public ownerId!: number; - public name!: string; - - public readonly createdAt!: Date; - public readonly updatedAt!: Date; -} - -class Address extends Model { - public userId!: number; - public address!: string; - - public readonly createdAt!: Date; - public readonly updatedAt!: Date; -} - -Project.init({ - id: { - type: DataTypes.INTEGER.UNSIGNED, // you can omit the `new` but this is discouraged - autoIncrement: true, - primaryKey: true, - }, - ownerId: { - type: DataTypes.INTEGER.UNSIGNED, - allowNull: false, - }, - name: { - type: new DataTypes.STRING(128), - allowNull: false, - } -}, { - sequelize, - tableName: 'projects', -}); - -User.init({ - id: { - type: DataTypes.INTEGER.UNSIGNED, - autoIncrement: true, - primaryKey: true, - }, - name: { - type: new DataTypes.STRING(128), - allowNull: false, - }, - preferredName: { - type: new DataTypes.STRING(128), - allowNull: true - } -}, { - tableName: 'users', - sequelize: sequelize, // this bit is important -}); - -Address.init({ - userId: { - type: DataTypes.INTEGER.UNSIGNED, - }, - address: { - type: new DataTypes.STRING(128), - allowNull: false, - } -}, { - tableName: 'address', - sequelize: sequelize, // this bit is important -}); - -// Here we associate which actually populates out pre-declared `association` static and other methods. -User.hasMany(Project, { - sourceKey: 'id', - foreignKey: 'ownerId', - as: 'projects' // this determines the name in `associations`! -}); - -Address.belongsTo(User, {targetKey: 'id'}); -User.hasOne(Address,{sourceKey: 'id'}); - -async function stuff() { - // Please note that when using async/await you lose the `bluebird` promise context - // and you fall back to native - const newUser = await User.create({ - name: 'Johnny', - preferredName: 'John', - }); - console.log(newUser.id, newUser.name, newUser.preferredName); - - const project = await newUser.createProject({ - name: 'first!', - }); - - const ourUser = await User.findByPk(1, { - include: [User.associations.projects], - rejectOnEmpty: true, // Specifying true here removes `null` from the return type! - }); - console.log(ourUser.projects![0].name); // Note the `!` null assertion since TS can't know if we included - // the model or not -} -``` - -## Usage of `sequelize.define` - -TypeScript doesn't know how to generate a `class` definition when we use the `sequelize.define` method to define a Model. Therefore, we need to do some manual work and declare an interface and a type, and eventually cast the result of `.define` to the _static_ type. - -```ts -// We need to declare an interface for our model that is basically what our class would be -interface MyModel extends Model { - readonly id: number; -} - -// Need to declare the static model so `findOne` etc. use correct types. -type MyModelStatic = typeof Model & { - new (values?: object, options?: BuildOptions): MyModel; -} - -// TS can't derive a proper class definition from a `.define` call, therefor we need to cast here. -const MyDefineModel = sequelize.define('MyDefineModel', { - id: { - primaryKey: true, - type: DataTypes.INTEGER.UNSIGNED, - } -}); - -function stuffTwo() { - MyDefineModel.findByPk(1, { - rejectOnEmpty: true, - }) - .then(myModel => { - console.log(myModel.id); - }); -} - -``` diff --git a/docs/manual/upgrade-to-v5.md b/docs/manual/upgrade-to-v5.md deleted file mode 100644 index bf74af62d962..000000000000 --- a/docs/manual/upgrade-to-v5.md +++ /dev/null @@ -1,449 +0,0 @@ -# Upgrade to v5 - -Sequelize v5 is the next major release after v4 - -## Breaking Changes - -### Support for Node 6 and up - -Sequelize v5 will only support Node 6 and up [#9015](https://github.com/sequelize/sequelize/issues/9015) - -### Secure Operators - -With v4 you started to get a deprecation warning `String based operators are now deprecated`. Also concept of operators was introduced. These operators are Symbols which prevent hash injection attacks. - -[operators-security](querying.html#operators-security) - -**With v5** - -- Operators are now enabled by default. -- You can still use string operators by passing an operators map in `operatorsAliases`, but that will give you deprecation warning. -- Op.$raw is removed - -### Typescript Support - -Sequelize now ship official typings [#10287](https://github.com/sequelize/sequelize/pull/10287). You can consider migrating away from external typings which may get out of sync. - -### Pooling - -With v5 Sequelize now use `sequelize-pool` which is a modernized fork of generic-pool@2.5. You no longer need to call `sequelize.close` to shutdown pool, this helps with lambda executions. [#8468](https://github.com/sequelize/sequelize/issues/8468) - -### Model - -**Validators** - -Custom validators defined per attribute (as opposed to the custom validators defined in the model's options) now run when the attribute's value is `null` and `allowNull` is `true` (while previously they didn't run and the validation succeeded immediately). To avoid problems when upgrading, please check all your custom validators defined per attribute, where `allowNull` is `true`, and make sure all these validators behave correctly when the value is `null`. See [#9143](https://github.com/sequelize/sequelize/issues/9143). - -**Attributes** - -`Model.attributes` now removed, use `Model.rawAttributes`. [#5320](https://github.com/sequelize/sequelize/issues/5320) - -__Note__: _Please don't confuse this with `options.attributes`, they are still valid_ - -**Paranoid Mode** - -With v5 if `deletedAt` is set, record will be considered as deleted. `paranoid` option will only use `deletedAt` as flag. [#8496](https://github.com/sequelize/sequelize/issues/8496) - -**Model.bulkCreate** - -`updateOnDuplicate` option which used to accept boolean and array, now only accepts non-empty array of attributes. [#9288](https://github.com/sequelize/sequelize/issues/9288) - -**Underscored Mode** - -Implementation of `Model.options.underscored` is changed. You can find full specifications [here](https://github.com/sequelize/sequelize/issues/6423#issuecomment-379472035). - -Main outline - -1. Both `underscoredAll` and `underscored` options are merged into single `underscored` option -2. All attributes are now generated with camelcase naming by default. With the `underscored` option set to `true`, the `field` option for attributes will be set as underscored version of attribute name. -3. `underscored` will control all attributes including timestamps, version and foreign keys. It will not affect any attribute which already specifies the `field` option. - -[#9304](https://github.com/sequelize/sequelize/pull/9304) - -**Removed aliases** - -Many model based aliases has been removed [#9372](https://github.com/sequelize/sequelize/issues/9372) - -| Removed in v5 | Official Alternative | -| :------ | :------ | -| insertOrUpdate | upsert | -| find | findOne | -| findAndCount | findAndCountAll | -| findOrInitialize | findOrBuild | -| updateAttributes | update | -| findById, findByPrimary | findByPk | -| all | findAll | -| hook | addHook | - -### Datatypes - -**Range** - -Now supports only one standard format `[{ value: 1, inclusive: true }, { value: 20, inclusive: false }]` [#9364](https://github.com/sequelize/sequelize/pull/9364) - -**Case insensitive text** - -Added support for `CITEXT` for Postgres and SQLite - -**Removed** - -`NONE` type has been removed, use `VIRTUAL` instead - -### Hooks - -**Removed aliases** - -Hooks aliases has been removed [#9372](https://github.com/sequelize/sequelize/issues/9372) - -| Removed in v5 | Official Alternative | -| :------ | :------ | -| [after,before]BulkDelete | [after,before]BulkDestroy | -| [after,before]Delete | [after,before]Destroy | -| beforeConnection | beforeConnect | - -### Sequelize - -**Removed aliases** - -Prototype references for many constants, objects and classes has been removed [#9372](https://github.com/sequelize/sequelize/issues/9372) - -| Removed in v5 | Official Alternative | -| :------ | :------ | -| Sequelize.prototype.Utils | Sequelize.Utils | -| Sequelize.prototype.Promise | Sequelize.Promise | -| Sequelize.prototype.TableHints | Sequelize.TableHints | -| Sequelize.prototype.Op | Sequelize.Op | -| Sequelize.prototype.Transaction | Sequelize.Transaction | -| Sequelize.prototype.Model | Sequelize.Model | -| Sequelize.prototype.Deferrable | Sequelize.Deferrable | -| Sequelize.prototype.Error | Sequelize.Error | -| Sequelize.prototype[error] | Sequelize[error] | - -```js -import Sequelize from 'sequelize'; -const sequelize = new Sequelize('postgres://user:password@127.0.0.1:mydb'); - -/** - * In v4 you can do this - */ -console.log(sequelize.Op === Sequelize.Op) // logs `true` -console.log(sequelize.UniqueConstraintError === Sequelize.UniqueConstraintError) // logs `true` - -Model.findAll({ - where: { - [sequelize.Op.and]: [ // Using sequelize.Op or Sequelize.Op interchangeably - { - name: "Abc" - }, - { - age: { - [Sequelize.Op.gte]: 18 - } - } - ] - } -}).catch(sequelize.ConnectionError, () => { - console.error('Something wrong with connection?'); -}); - -/** - * In v5 aliases has been removed from Sequelize prototype - * You should use Sequelize directly to access Op, Errors etc - */ - -Model.findAll({ - where: { - [Sequelize.Op.and]: [ // Don't use sequelize.Op, use Sequelize.Op instead - { - name: "Abc" - }, - { - age: { - [Sequelize.Op.gte]: 18 - } - } - ] - } -}).catch(Sequelize.ConnectionError, () => { - console.error('Something wrong with connection?'); -}); -``` - -### Query Interface - -- `changeColumn` no longer generates constraint with `_idx` suffix. Now Sequelize does not specify any name for constraints thus defaulting to database engine naming. This aligns behavior of `sync`, `createTable` and `changeColumn`. -- `addIndex` aliases options aliases have been removed, use the following instead. - - `indexName` => `name` - - `indicesType` => `type` - - `indexType`/`method` => `using` - -### Others - -- Sequelize now use parameterized queries for all INSERT / UPDATE operations (except UPSERT). They provide better protection against SQL Injection attack. -- `ValidationErrorItem` now holds reference to original error in the `original` property, rather than the `__raw` property. -- [retry-as-promised](https://github.com/mickhansen/retry-as-promised) has been updated to `3.1.0`, which use [any-promise](https://github.com/kevinbeaty/any-promise). This module repeat all `sequelize.query` operations. You can configure `any-promise` to use `bluebird` for better performance on Node 4 or 6 -- Sequelize will throw for all `undefined` keys in `where` options, In past versions `undefined` was converted to `null`. - -### Dialect Specific - -#### MSSQL - -- Sequelize now works with `tedious >= 6.0.0`. Old `dialectOptions` has to be updated to match their new format. Please refer to tedious [documentation](http://tediousjs.github.io/tedious/api-connection.html#function_newConnection). An example of new `dialectOptions` is given below - -```javascript -dialectOptions: { - authentication: { - domain: 'my-domain' - }, - options: { - requestTimeout: 60000, - cryptoCredentialsDetails: { - ciphers: "RC4-MD5" - } - } -} -``` - -#### MySQL - -- Requires `mysql2 >= 1.5.2` for prepared statements - -#### MariaDB - -- `dialect: 'mariadb'` is now [supported](https://github.com/sequelize/sequelize/pull/10192) with `mariadb` package - -### Packages - -- removed: terraformer-wkt-parser [#9545](https://github.com/sequelize/sequelize/pull/9545) -- removed: `generic-pool` -- added: `sequelize-pool` - -## Changelog - -### 5.0.0-beta.17 - -- fix(build): default null for multiple primary keys -- fix(util): improve performance of classToInvokable [#10534](https://github.com/sequelize/sequelize/pull/10534) -- fix(model/update): propagate paranoid to individualHooks query [#10369](https://github.com/sequelize/sequelize/pull/10369) -- fix(association): use minimal select for hasAssociation [#10529](https://github.com/sequelize/sequelize/pull/10529) -- fix(query-interface): reject with error for describeTable [#10528](https://github.com/sequelize/sequelize/pull/10528) -- fix(model): throw for invalid include type [#10527](https://github.com/sequelize/sequelize/pull/10527) -- fix(types): additional options for db.query and add missing retry [#10512](https://github.com/sequelize/sequelize/pull/10512) -- fix(query): don't prepare options & sql for every retry [#10498](https://github.com/sequelize/sequelize/pull/10498) -- feat: expose Sequelize.BaseError -- feat: upgrade to tedious@6.0.0 [#10494](https://github.com/sequelize/sequelize/pull/10494) -- feat(sqlite/query-generator): support restart identity for truncate-table [#10522](https://github.com/sequelize/sequelize/pull/10522) -- feat(data-types): handle numbers passed as objects [#10492](https://github.com/sequelize/sequelize/pull/10492) -- feat(types): enabled string association [#10481](https://github.com/sequelize/sequelize/pull/10481) -- feat(postgres): allow customizing client_min_messages [#10448](https://github.com/sequelize/sequelize/pull/10448) -- refactor(data-types): move to classes [#10495](https://github.com/sequelize/sequelize/pull/10495) -- docs(legacy): fix N:M example [#10509](https://github.com/sequelize/sequelize/pull/10509) -- docs(migrations): use migrationStorageTableSchema [#10417](https://github.com/sequelize/sequelize/pull/10417) -- docs(hooks): add documentation for connection hooks [#10410](https://github.com/sequelize/sequelize/pull/10410) -- docs(addIndex): concurrently option [#10409](https://github.com/sequelize/sequelize/pull/10409) -- docs(model): fix typo [#10405](https://github.com/sequelize/sequelize/pull/10405) -- docs(usage): fix broken link on Basic Usage [#10381](https://github.com/sequelize/sequelize/pull/10381) -- docs(package.json): add homepage [#10372](https://github.com/sequelize/sequelize/pull/10372) - -### 5.0.0-beta.16 - -- feat: add typescript typings [#10287](https://github.com/sequelize/sequelize/pull/10117) -- fix(mysql): match with newlines in error message [#10320](https://github.com/sequelize/sequelize/pull/10320) -- fix(update): skips update when nothing to update [#10248](https://github.com/sequelize/sequelize/pull/10248) -- fix(utils): flattenObject for null values [#10293](https://github.com/sequelize/sequelize/pull/10293) -- fix(instance-validator): don't skip custom validators on null [#9143](https://github.com/sequelize/sequelize/pull/9143) -- docs(transaction): after save example [#10280](https://github.com/sequelize/sequelize/pull/10280) -- docs(query-generator): typo [#10277](https://github.com/sequelize/sequelize/pull/10277) -- refactor(errors): restructure [#10355](https://github.com/sequelize/sequelize/pull/10355) -- refactor(scope): documentation #9087 [#10312](https://github.com/sequelize/sequelize/pull/10312) -- refactor: cleanup association and spread use [#10276](https://github.com/sequelize/sequelize/pull/10276) - -### 5.0.0-beta.15 - -- fix(query-generator): fix addColumn create comment [#10117](https://github.com/sequelize/sequelize/pull/10117) -- fix(sync): throw when no models defined [#10175](https://github.com/sequelize/sequelize/pull/10175) -- fix(association): enable eager load with include all(#9928) [#10173](https://github.com/sequelize/sequelize/pull/10173) -- fix(sqlite): simplify connection error handling -- fix(model): prevent version number from being incremented as string [#10217](https://github.com/sequelize/sequelize/pull/10217) -- feat(dialect): mariadb [#10192](https://github.com/sequelize/sequelize/pull/10192) -- docs(migrations): improve dialect options docs -- docs: fix favicon [#10242](https://github.com/sequelize/sequelize/pull/10242) -- docs(model.init): `attribute.column.validate` option [#10237](https://github.com/sequelize/sequelize/pull/10237) -- docs(bulk-create): update support information about ignoreDuplicates -- docs: explain custom/new data types [#10170](https://github.com/sequelize/sequelize/pull/10170) -- docs(migrations): Simplify CLI Call [#10201](https://github.com/sequelize/sequelize/pull/10201) -- docs(migrations): added advanced skeleton example [#10190](https://github.com/sequelize/sequelize/pull/10190) -- docs(transaction): default isolation level [#10111](https://github.com/sequelize/sequelize/pull/10111) -- docs: typo in associations.md [#10157](https://github.com/sequelize/sequelize/pull/10157) -- refactor: reduce code complexity [#10120](https://github.com/sequelize/sequelize/pull/10120) -- refactor: optimize memoize use, misc cases [#10122](https://github.com/sequelize/sequelize/pull/10122) -- chore(lint): enforce consistent spacing [#10193](https://github.com/sequelize/sequelize/pull/10193) - -### 5.0.0-beta.14 - -- fix(query): correctly quote identifier for attributes (#9964) [#10118](https://github.com/sequelize/sequelize/pull/10118) -- feat(postgres): dyanmic oids [#10077](https://github.com/sequelize/sequelize/pull/10077) -- fix(error): optimistic lock message [#10068](https://github.com/sequelize/sequelize/pull/10068) -- fix(package): update depd to version 2.0.0 [#10081](https://github.com/sequelize/sequelize/pull/10081) -- fix(model): validate virtual attribute (#9947) [#10085](https://github.com/sequelize/sequelize/pull/10085) -- fix(test): actually test get method with raw option [#10059](https://github.com/sequelize/sequelize/pull/10059) -- fix(model): return deep cloned value for toJSON [#10058](https://github.com/sequelize/sequelize/pull/10058) -- fix(model): create instance with many-to-many association with extra column (#10034) [#10050](https://github.com/sequelize/sequelize/pull/10050) -- fix(query-generator): fix bad property access [#10056](https://github.com/sequelize/sequelize/pull/10056) -- docs(upgrade-to-v4): typo [#10060](https://github.com/sequelize/sequelize/pull/10060) -- docs(model-usage): order expression format [#10061](https://github.com/sequelize/sequelize/pull/10061) -- chore(package): update retry-as-promised to version 3.1.0 [#10065](https://github.com/sequelize/sequelize/pull/10065) -- refactor(scopes): just in time options conforming [#9735](https://github.com/sequelize/sequelize/pull/9735) -- refactor: use sequelize-pool for pooling [#10051](https://github.com/sequelize/sequelize/pull/10051) -- refactor(*): cleanup code [#10091](https://github.com/sequelize/sequelize/pull/10091) -- refactor: use template strings [#10055](https://github.com/sequelize/sequelize/pull/10055) -- refactor(query-generation): cleanup template usage [#10047](https://github.com/sequelize/sequelize/pull/10047) - -### 5.0.0-beta.13 - -- fix: throw on undefined where parameters [#10048](https://github.com/sequelize/sequelize/pull/10048) -- fix(model): improve wrong alias error message [#10041](https://github.com/sequelize/sequelize/pull/10041) -- feat(sqlite): CITEXT datatype [#10036](https://github.com/sequelize/sequelize/pull/10036) -- fix(postgres): remove if not exists and cascade from create/drop database queries [#10033](https://github.com/sequelize/sequelize/pull/10033) -- fix(syntax): correct parentheses around union [#10003](https://github.com/sequelize/sequelize/pull/10003) -- feat(query-interface): createDatabase / dropDatabase support [#10027](https://github.com/sequelize/sequelize/pull/10027) -- feat(postgres): CITEXT datatype [#10024](https://github.com/sequelize/sequelize/pull/10024) -- feat: pass uri query parameters to dialectOptions [#10025](https://github.com/sequelize/sequelize/pull/10025) -- docs(query-generator): remove doc about where raw query [#10017](https://github.com/sequelize/sequelize/pull/10017) -- fix(query): handle undefined field on unique constraint error [#10018](https://github.com/sequelize/sequelize/pull/10018) -- fix(model): sum returns zero when empty matching [#9984](https://github.com/sequelize/sequelize/pull/9984) -- feat(query-generator): add startsWith, endsWith and substring operators [#9999](https://github.com/sequelize/sequelize/pull/9999) -- docs(sequelize): correct jsdoc annotations for authenticate [#10002](https://github.com/sequelize/sequelize/pull/10002) -- docs(query-interface): add bulkUpdate docs [#10005](https://github.com/sequelize/sequelize/pull/10005) -- fix(tinyint): ignore params for TINYINT on postgres [#9992](https://github.com/sequelize/sequelize/pull/9992) -- fix(belongs-to): create now returns target model [#9980](https://github.com/sequelize/sequelize/pull/9980) -- refactor(model): remove .all alias [#9975](https://github.com/sequelize/sequelize/pull/9975) -- perf: fix memory leak due to instance reference by isImmutable [#9973](https://github.com/sequelize/sequelize/pull/9973) -- feat(sequelize): dialectModule option [#9972](https://github.com/sequelize/sequelize/pull/9972) -- fix(query): check valid warn message [#9948](https://github.com/sequelize/sequelize/pull/9948) -- fix(model): check for own property when overriding association mixins [#9953](https://github.com/sequelize/sequelize/pull/9953) -- fix(create-table): support for uniqueKeys [#9946](https://github.com/sequelize/sequelize/pull/9946) -- refactor(transaction): remove autocommit mode [#9921](https://github.com/sequelize/sequelize/pull/9921) -- feat(sequelize): getDatabaseName [#9937](https://github.com/sequelize/sequelize/pull/9937) -- refactor: remove aliases [#9933](https://github.com/sequelize/sequelize/pull/9933) -- feat(belongsToMany): override unique constraint name with uniqueKey [#9914](https://github.com/sequelize/sequelize/pull/9914) -- fix(postgres): properly disconnect connections [#9911](https://github.com/sequelize/sequelize/pull/9911) -- docs(instances.md): add section for restore() [#9917](https://github.com/sequelize/sequelize/pull/9917) -- docs(hooks.md): add warning about memory limits of individual hooks [#9881](https://github.com/sequelize/sequelize/pull/9881) -- fix(package): update debug to version 4.0.0 [#9908](https://github.com/sequelize/sequelize/pull/9908) -- feat(postgres): support ignoreDuplicates with ON CONFLICT DO NOTHING [#9883](https://github.com/sequelize/sequelize/pull/9883) - -### 5.0.0-beta.12 - -- fix(changeColumn): normalize attribute [#9897](https://github.com/sequelize/sequelize/pull/9897) -- feat(describeTable): support string length for mssql [#9896](https://github.com/sequelize/sequelize/pull/9896) -- feat(describeTable): support autoIncrement for mysql [#9894](https://github.com/sequelize/sequelize/pull/9894) -- fix(sqlite): unable to reference foreignKey on primaryKey [#9893](https://github.com/sequelize/sequelize/pull/9893) -- fix(postgres): enum with string COMMENT breaks query [#9891](https://github.com/sequelize/sequelize/pull/9891) -- fix(changeColumn): use engine defaults for foreign/unique key naming [#9890](https://github.com/sequelize/sequelize/pull/9890) -- fix(transaction): fixed unhandled rejection when connection acquire timeout [#9879](https://github.com/sequelize/sequelize/pull/9879) -- fix(sqlite): close connection properly and cleanup files [#9851](https://github.com/sequelize/sequelize/pull/9851) -- fix(model): incorrect error message for findCreateFind [#9849](https://github.com/sequelize/sequelize/pull/9849) - -### 5.0.0-beta.11 - -- fix(count): duplicate mapping of fields break scopes [#9788](https://github.com/sequelize/sequelize/pull/9788) -- fix(model): bulkCreate should populate dataValues directly [#9797](https://github.com/sequelize/sequelize/pull/9797) -- fix(mysql): improve unique key violation handling [#9724](https://github.com/sequelize/sequelize/pull/9724) -- fix(separate): don't propagate group to separated queries [#9754](https://github.com/sequelize/sequelize/pull/9754) -- fix(scope): incorrect query generated when sequelize.fn used with scopes [#9730](https://github.com/sequelize/sequelize/pull/9730) -- fix(json): access included data with attributes [#9662](https://github.com/sequelize/sequelize/pull/9662) -- (fix): pass offset in UNION'ed queries [#9577](https://github.com/sequelize/sequelize/pull/9577) -- fix(destroy): attributes updated in a beforeDestroy hook are now persisted on soft delete [#9319](https://github.com/sequelize/sequelize/pull/9319) -- fix(addScope): only throw when defaultScope is defined [#9703](https://github.com/sequelize/sequelize/pull/9703) - -### 5.0.0-beta.10 - -- fix(belongsToMany): association.add returns array of array of through records [#9700](https://github.com/sequelize/sequelize/pull/9700) -- feat: association hooks [#9590](https://github.com/sequelize/sequelize/pull/9590) -- fix(bulkCreate): dont map dataValue to fields for individualHooks:true[#9672](https://github.com/sequelize/sequelize/pull/9672) -- feat(postgres): drop enum support [#9641](https://github.com/sequelize/sequelize/pull/9641) -- feat(validation): improve validation for type[#9660](https://github.com/sequelize/sequelize/pull/9660) -- feat: allow querying sqlite_master table [#9645](https://github.com/sequelize/sequelize/pull/9645) -- fix(hasOne.sourceKey): setup sourceKeyAttribute for joins [#9658](https://github.com/sequelize/sequelize/pull/9658) -- fix: throw when type of array values is not defined [#9649](https://github.com/sequelize/sequelize/pull/9649) -- fix(query-generator): ignore undefined keys in query [#9548](https://github.com/sequelize/sequelize/pull/9548) -- fix(model): unable to override rejectOnEmpty [#9632](https://github.com/sequelize/sequelize/pull/9632) -- fix(reload): instance.changed() remains unaffected [#9615](https://github.com/sequelize/sequelize/pull/9615) -- feat(model): column level comments [#9573](https://github.com/sequelize/sequelize/pull/9573) -- docs: cleanup / correct jsdoc references [#9702](https://github.com/sequelize/sequelize/pull/9702) - -### 5.0.0-beta.9 - -- fix(model): ignore undefined values in update payload [#9587](https://github.com/sequelize/sequelize/pull/9587) -- fix(mssql): set encrypt as default false for dialect options [#9588](https://github.com/sequelize/sequelize/pull/9588) -- fix(model): ignore VIRTUAL/getters with attributes.exclude [#9568](https://github.com/sequelize/sequelize/pull/9568) -- feat(data-types): CIDR, INET, MACADDR support for Postgres [#9567](https://github.com/sequelize/sequelize/pull/9567) -- fix: customize allowNull message with notNull validator [#9549](https://github.com/sequelize/sequelize/pull/9549) - -### 5.0.0-beta.8 - -- feat(query-generator): Generate INSERT / UPDATE using bind parameters [#9431](https://github.com/sequelize/sequelize/pull/9431) [#9492](https://github.com/sequelize/sequelize/pull/9492) -- performance: remove terraformer-wkt-parser dependency [#9545](https://github.com/sequelize/sequelize/pull/9545) -- fix(constructor): set username, password, database via options in addition to connection string[#9517](https://github.com/sequelize/sequelize/pull/9517) -- fix(associations/belongs-to-many): catch EmptyResultError in set/add helpers [#9535](https://github.com/sequelize/sequelize/pull/9535) -- fix: sync with alter:true doesn't use field name [#9529](https://github.com/sequelize/sequelize/pull/9529) -- fix(UnknownConstraintError): improper handling of error options [#9547](https://github.com/sequelize/sequelize/pull/9547) - -### 5.0.0-beta.7 - -- fix(data-types/blob): only return null for mysql binary null [#9441](https://github.com/sequelize/sequelize/pull/9441) -- fix(errors): use standard .original rather than .__raw for actual error -- fix(connection-manager): mssql datatype parsing [#9470](https://github.com/sequelize/sequelize/pull/9470) -- fix(query/removeConstraint): support schemas -- fix: use Buffer.from -- fix(transactions): return patched promise from sequelize.query [#9473](https://github.com/sequelize/sequelize/pull/9473) - -### 5.0.0-beta.6 - -- fix(postgres/query-generator): syntax error with auto-increment SMALLINT [#9406](https://github.com/sequelize/sequelize/pull/9406) -- fix(postgres/range): inclusive property lost in JSON format [#8471](https://github.com/sequelize/sequelize/issues/8471) -- fix(postgres/range): range bound not applied [#8176](https://github.com/sequelize/sequelize/issues/8176) -- fix(mssql): no unique constraint error thrown for PRIMARY case [#9415](https://github.com/sequelize/sequelize/pull/9415) -- fix(query-generator): regexp operator escaping -- docs: various improvements and hinting update - -### 5.0.0-beta.5 - -- fix: inject foreignKey when using separate:true [#9396](https://github.com/sequelize/sequelize/pull/9396) -- fix(isSoftDeleted): just use deletedAt as flag -- feat(hasOne): sourceKey support with key validation [#9382](https://github.com/sequelize/sequelize/pull/9382) -- fix(query-generator/deleteQuery): remove auto limit [#9377](https://github.com/sequelize/sequelize/pull/9377) -- feat(postgres): skip locked support [#9197](https://github.com/sequelize/sequelize/pull/9197) -- fix(mssql): case sensitive operation fails because of uppercased system table references [#9337](https://github.com/sequelize/sequelize/pull/9337) - -### 5.0.0-beta.4 - -- change(model): setDataValue should not mark null to null as changed [#9347](https://github.com/sequelize/sequelize/pull/9347) -- change(mysql/connection-manager): do not execute SET time_zone query if keepDefaultTimezone config is true [#9358](https://github.com/sequelize/sequelize/pull/9358) -- feat(transactions): Add afterCommit hooks for transactions [#9287](https://github.com/sequelize/sequelize/pull/9287) - -### 5.0.0-beta.3 - -- change(model): new options.underscored implementation [#9304](https://github.com/sequelize/sequelize/pull/9304) -- fix(mssql): duplicate order generated with limit offset [#9307](https://github.com/sequelize/sequelize/pull/9307) -- fix(scope): do not assign scope on eagerly loaded associations [#9292](https://github.com/sequelize/sequelize/pull/9292) -- change(bulkCreate): only support non-empty array as updateOnDuplicate - -### 5.0.0-beta.2 - -- change(operators): Symbol operators now enabled by default, removed deprecation warning -- fix(model): don't add LIMIT in findOne() queries on unique key [#9248](https://github.com/sequelize/sequelize/pull/9248) -- fix(model): use schema when generating foreign keys [#9029](https://github.com/sequelize/sequelize/issues/9029) - -### 5.0.0-beta.1 - -- fix(postgres): reserved words support [#9236](https://github.com/sequelize/sequelize/pull/9236) -- fix(findOrCreate): warn and handle unknown attributes in defaults -- fix(query-generator): 1-to-many join in subQuery filter missing where clause [#9228](https://github.com/sequelize/sequelize/issues/9228) - -### 5.0.0-beta - -- `Model.attributes` now removed, use `Model.rawAttributes` [#5320](https://github.com/sequelize/sequelize/issues/5320) -- `paranoid` mode will now treat any record with `deletedAt` as deleted [#8496](https://github.com/sequelize/sequelize/issues/8496) -- Node 6 and up [#9015](https://github.com/sequelize/sequelize/issues/9015) diff --git a/docs/redirects.json b/docs/redirects.json new file mode 100644 index 000000000000..6701da309294 --- /dev/null +++ b/docs/redirects.json @@ -0,0 +1,4 @@ +{ + "manual/dialects.html": "dialect-specific-things.html", + "manual/instances.html": "model-instances.html" +} \ No newline at end of file diff --git a/docs/redirects/create-redirects.js b/docs/redirects/create-redirects.js new file mode 100644 index 000000000000..5e0e0c68c791 --- /dev/null +++ b/docs/redirects/create-redirects.js @@ -0,0 +1,18 @@ +'use strict'; + +const jetpack = require('fs-jetpack'); +const redirectMap = require('./../redirects.json'); + +function makeBoilerplate(url) { + return ` + + + Redirecting... + + + `; +} + +for (const source of Object.keys(redirectMap)) { + jetpack.write(`esdoc/${source}`, makeBoilerplate(redirectMap[source])); +} \ No newline at end of file diff --git a/docs/scripts/.eslintrc b/docs/scripts/.eslintrc new file mode 100644 index 000000000000..b99c1118dd5e --- /dev/null +++ b/docs/scripts/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "browser": true + } +} diff --git a/docs/scripts/menu-groups.js b/docs/scripts/menu-groups.js new file mode 100644 index 000000000000..bec7591e5589 --- /dev/null +++ b/docs/scripts/menu-groups.js @@ -0,0 +1,26 @@ +'use strict'; + +(() => { + function toggleNavigationBar() { + const navigationElements = document.getElementsByClassName('navigation'); + for (let i = 0; i < navigationElements.length; ++i) { + const navigationElement = navigationElements[i]; + navigationElement.classList.toggle('open'); + } + } + + // Hamburger button - toggles the navigation bar + const hamburger = document.getElementById('navigationHamburger'); + hamburger.addEventListener('click', () => { + toggleNavigationBar(); + }); + + // Each link in the navigation bar - closes the navigation bar + const navigationLinks = document.querySelectorAll('.navigation a'); + for (let i = 0; i < navigationLinks.length; ++i) { + const linkElement = navigationLinks[i]; + linkElement.addEventListener('click', () => { + toggleNavigationBar(); + }); + } +})(); diff --git a/docs/transforms/fix-ids.js b/docs/transforms/fix-ids.js new file mode 100644 index 000000000000..127b4cc71ee9 --- /dev/null +++ b/docs/transforms/fix-ids.js @@ -0,0 +1,46 @@ +'use strict'; + +const _ = require('lodash'); +const assert = require('assert'); + +module.exports = function transform($, filePath) { + // The rest of this script assumes forward slashes, so let's ensure this works on windows + filePath = filePath.replace(/\\/g, '/'); + + // Detect every heading with an ID + const headingsWithId = $('h1,h2,h3,h4,h5').filter('[id]'); + + // Find duplicate IDs among them + const headingsWithDuplicateId = _.chain(headingsWithId) + .groupBy(h => $(h).attr('id')) + .filter(g => g.length > 1) + .value(); + + // Replace their IDs according to the following rule + // #original-header --> #original-header + // #original-header --> #original-header-2 + // #original-header --> #original-header-3 + for (const headingGroup of headingsWithDuplicateId) { + const id = $(headingGroup[0]).attr('id'); + + // Find the corresponding nav links + const urlPath = filePath.replace('esdoc/', ''); + const navLinks = $(`li[data-ice="manualNav"] > a[href="${urlPath}#${id}"]`); + + // make sure there are same number of headings and links + assert(headingGroup.length === navLinks.length, + `not every heading is linked to in nav: + ${headingGroup.length} headings but ${navLinks.length} links + heading id is ${id} in file ${filePath}. NavLinks is ${require('util').inspect(navLinks, { compact: false, depth: 5 })}`); + + // Fix the headings and nav links beyond the first + for (let i = 1; i < headingGroup.length; i++) { + const heading = headingGroup[i]; + const navLink = navLinks[i]; + const newId = `${id}-${i + 1}`; + $(heading).attr('id', newId); + $(navLink).attr('href', `${urlPath}#${newId}`); + } + } + +}; diff --git a/docs/transforms/header-customization.js b/docs/transforms/header-customization.js index 25825bfc10a7..4f9dfcf1477b 100644 --- a/docs/transforms/header-customization.js +++ b/docs/transforms/header-customization.js @@ -17,7 +17,7 @@ module.exports = function transform($) { .css('top', '') .after(` - + `); -}; \ No newline at end of file +}; diff --git a/docs/transforms/menu-groups.js b/docs/transforms/menu-groups.js index d28627528c8c..d8a40b5d5b3c 100644 --- a/docs/transforms/menu-groups.js +++ b/docs/transforms/menu-groups.js @@ -1,12 +1,33 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const _ = require('lodash'); const manualGroups = require('./../manual-groups.json'); -module.exports = function transform($) { - const listItems = $('nav div.manual-toc-root div[data-ice=manual]'); +function extractFileNameFromPath(path) { + if (/\.\w+$/.test(path)) { + return /([^/]*)\.\w+$/.exec(path)[1]; + } + return /[^/]*$/.exec(path)[0]; +} - $(listItems.get(0)).before(` +const hiddenManualNames = manualGroups.__hidden__.map(extractFileNameFromPath); + +function isLinkToHiddenManual(link) { + const linkTargetName = extractFileNameFromPath(link); + return hiddenManualNames.includes(linkTargetName); +} + +module.exports = function transform($, filePath) { + // The three s are used to draw the menu button icon + $('nav.navigation').prepend($('')); + const menuGroupsScripts = fs.readFileSync(path.join(__dirname, '..', 'scripts', 'menu-groups.js'), 'utf8'); + $('body').append($(``)); + + const sidebarManualDivs = $('nav div.manual-toc-root div[data-ice=manual]'); + + $(sidebarManualDivs.get(0)).before(` @@ -14,11 +35,28 @@ module.exports = function transform($) { let count = 0; _.each(manualGroups, (manuals, groupName) => { - $(listItems.get(count)).before(` -
- ${groupName} -
- `); + if (groupName !== '__hidden__') { + const groupTitleElement = $(`
${groupName}
`); + $(sidebarManualDivs.get(count)).before(groupTitleElement); + } count += manuals.length; }); + + // Remove links to hidden manuals + sidebarManualDivs.each(/* @this */ function() { + const link = $(this).find('li.indent-h1').data('link'); + if (isLinkToHiddenManual(link)) { + $(this).remove(); + } + }); + + // Remove previews for hidden manuals in index.html + if (filePath.endsWith('index.html') && $('div.manual-cards').length > 0) { + $('div.manual-card-wrap').each(/* @this */ function() { + const link = $(this).find('a').attr('href'); + if (isLinkToHiddenManual(link)) { + $(this).remove(); + } + }); + } }; \ No newline at end of file diff --git a/docs/transforms/meta-tags.js b/docs/transforms/meta-tags.js new file mode 100644 index 000000000000..62bb58e10404 --- /dev/null +++ b/docs/transforms/meta-tags.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function transform($) { + $('head').append(''); +}; diff --git a/docs/upgrade-to-v6.md b/docs/upgrade-to-v6.md deleted file mode 100644 index df85a81ce0bc..000000000000 --- a/docs/upgrade-to-v6.md +++ /dev/null @@ -1,104 +0,0 @@ -# Upgrade to v6 - -Sequelize v6 is the next major release after v4 - -## Breaking Changes - -### Support for Node 8 and up - -Sequelize v6 will only support Node 8 and up. - -### Removed support for `operatorAliases` - -Operator aliases were soft deprecated via the `opt-in` option `operatorAlises` in v5 they have been entirely removed. - -Please refer to previous changelogs for the migration guide. - -### Renamed operator symbols - -If you have relied on accessing sequelize operators via `Symbol.for('gt')` etc. you must now prefix them with `sequelize.operator` eg. -`Symbol.for('sequelize.operator.gt')` - -### Removed `Model.build` - -`Model.build` has been acting as proxy for `bulkBuild` and `new Model` for a while. - -Use `Model.bulkBuild` or `new Model` instead. - -### Removal of CLS - -CLS allowed implicit passing of the `transaction` property to query options inside of a transaction. -This feature was removed for 3 reasons. - -- It required hooking the promise implementation which is not sustainable for the future of sequelize. -- It's generally unsafe due to it's implicit nature. -- It wasn't always reliable when mixed promise implementations were used. - -Check all your usage of the `.transaction` method and make sure to explicitly pass the `transaction` object for each subsequent query. - -#### Example - -```js -db.transaction(async transaction => { - const mdl = await myModel.findByPk(1); - await mdl.update({ - a: 1; - }); -}); -``` - -should now be: - -```js -db.transaction(async transaction => { - const mdl = await myModel.findByPk(1, { transaction }); - await mdl.update({ - a: 1; - }, { transaction }); -}); -``` - -### Refactored hooks - -In order to streamline API: - -- All method style add hook functions have been removed in favor of a composition based approach. -- Hook names have been removed, you can add and remove them by function reference instead which was supported before. -- Another notable change that `this` inside of hooks no longer refers to the the the hook subject, it should not be used. - -This affects `Model`, `Sequelize` and `Transaction`. - -#### Composition - -Before: `MyModel.beforeCreate(...)` -After: `MyModel.hooks.add('beforeCreate', ...)` - -Before: `MyModel.addHook('beforeCreate', ...)` -After: `MyModel.hooks.add('beforeCreate', ...)` - -Before: `MyModel.removeHook('beforeCreate', ...)` -After: `MyModel.hooks.remove('beforeCreate', ...)` - -Before: `transaction.afterCommit(...)` -After: `transaction.hooks.add('afterCommit', ...)` - -#### Names - -Before: - -```js -MyModel.addHook('beforeCreate', 'named', fn); -MyModel.removeHook('beforeCreate', 'named'); -``` - -After: - -```js -MyModel.hooks.add('beforeCreate', fn); -MyModel.hooks.remove('beforeCreate', fn); -``` - -#### Scope - -Before: `MyModel.addHook('beforeCreate', function() { this.someMethod(); });` -After: `MyModel.hooks.add('beforeCreate', () => { MyModel.someMethod(); });` diff --git a/lib/associations/base.js b/lib/associations/base.js index 106f62129e71..40ce304445c4 100644 --- a/lib/associations/base.js +++ b/lib/associations/base.js @@ -98,6 +98,7 @@ class Association { /** * The type of the association. One of `HasMany`, `BelongsTo`, `HasOne`, `BelongsToMany` + * * @type {string} */ this.associationType = ''; @@ -128,17 +129,13 @@ class Association { const tmpInstance = {}; tmpInstance[this.target.primaryKeyAttribute] = element; - return new this.target(tmpInstance, { isNewRecord: false }); + return this.target.build(tmpInstance, { isNewRecord: false }); }); } [Symbol.for('nodejs.util.inspect.custom')]() { return this.as; } - - inspect() { - return this.as; - } } module.exports = Association; diff --git a/lib/associations/belongs-to-many.js b/lib/associations/belongs-to-many.js index a4ebffe19b5f..6de259eb3f4b 100644 --- a/lib/associations/belongs-to-many.js +++ b/lib/associations/belongs-to-many.js @@ -30,9 +30,8 @@ const Op = require('../operators'); * All methods allow you to pass either a persisted instance, its primary key, or a mixture: * * ```js - * Project.create({ id: 11 }).then(project => { - * user.addProjects([project, 12]); - * }); + * const project = await Project.create({ id: 11 }); + * await user.addProjects([project, 12]); * ``` * * If you want to set several target instances, but with different attributes you have to set the attributes on the instance, using a property with the name of the through model: @@ -46,10 +45,9 @@ const Op = require('../operators'); * * Similarly, when fetching through a join table with custom attributes, these attributes will be available as an object with the name of the through model. * ```js - * user.getProjects().then(projects => { - * let p1 = projects[0] - * p1.UserProjects.started // Is this project started yet? - * }) + * const projects = await user.getProjects(); + * const p1 = projects[0]; + * p1.UserProjects.started // Is this project started yet? * ``` * * In the API reference below, add the name of the association to the method, e.g. for `User.belongsToMany(Project)` the getter will be `user.getProjects()`. @@ -73,7 +71,7 @@ class BelongsToMany extends Association { this.associationType = 'BelongsToMany'; this.targetAssociation = null; this.sequelize = source.sequelize; - this.through = Object.assign({}, this.options.through); + this.through = { ...this.options.through }; this.isMultiAssociation = true; this.doubleLinked = false; @@ -145,7 +143,7 @@ class BelongsToMany extends Association { this.through.model = this.sequelize.define(this.through.model, {}, Object.assign(this.options, { tableName: this.through.model, indexes: [], //we don't want indexes here (as referenced in #2416) - paranoid: false, // A paranoid join table does not make sense + paranoid: this.through.paranoid ? this.through.paranoid : false, // Default to non-paranoid join (referenced in #11991) validate: {} // Don't propagate model-level validations })); } else { @@ -153,7 +151,7 @@ class BelongsToMany extends Association { } } - this.options = Object.assign(this.options, _.pick(this.through.model.options, [ + Object.assign(this.options, _.pick(this.through.model.options, [ 'timestamps', 'createdAt', 'updatedAt', 'deletedAt', 'paranoid' ])); @@ -284,10 +282,7 @@ class BelongsToMany extends Association { const targetKey = this.target.rawAttributes[this.targetKey]; const targetKeyType = targetKey.type; const targetKeyField = this.targetKeyField; - const sourceAttribute = { - type: sourceKeyType, - ...this.foreignKeyAttribute - }; + const sourceAttribute = { type: sourceKeyType, ...this.foreignKeyAttribute }; const targetAttribute = { type: targetKeyType, ...this.otherKeyAttribute }; if (this.primaryKeyDeleted === true) { @@ -338,8 +333,8 @@ class BelongsToMany extends Association { if (!targetAttribute.onUpdate) targetAttribute.onUpdate = 'CASCADE'; } - this.through.model.rawAttributes[this.foreignKey] = Object.assign(this.through.model.rawAttributes[this.foreignKey], sourceAttribute); - this.through.model.rawAttributes[this.otherKey] = Object.assign(this.through.model.rawAttributes[this.otherKey], targetAttribute); + Object.assign(this.through.model.rawAttributes[this.foreignKey], sourceAttribute); + Object.assign(this.through.model.rawAttributes[this.otherKey], targetAttribute); this.through.model.refreshAttributes(); @@ -358,6 +353,7 @@ class BelongsToMany extends Association { }); this.oneFromSource = new HasOne(this.source, this.through.model, { foreignKey: this.foreignKey, + sourceKey: this.sourceKey, as: this.through.model.name }); @@ -369,6 +365,7 @@ class BelongsToMany extends Association { }); this.oneFromTarget = new HasOne(this.target, this.through.model, { foreignKey: this.otherKey, + sourceKey: this.targetKey, as: this.through.model.name }); @@ -379,6 +376,7 @@ class BelongsToMany extends Association { this.paired.oneFromTarget = new HasOne(this.paired.target, this.paired.through.model, { foreignKey: this.paired.otherKey, + sourceKey: this.paired.targetKey, as: this.paired.through.model.name }); } @@ -411,11 +409,13 @@ class BelongsToMany extends Association { * @param {object} [options.where] An optional where clause to limit the associated models * @param {string|boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false * @param {string} [options.schema] Apply a schema on the related model + * @param {object} [options.through.where] An optional where clause applied to through model (join table) + * @param {boolean} [options.through.paranoid=true] If true, only non-deleted records will be returned from the join table. If false, both deleted and non-deleted records will be returned. Only applies if through model is paranoid * * @returns {Promise>} */ - get(instance, options = {}) { - options = Utils.cloneDeep(options); + async get(instance, options) { + options = Utils.cloneDeep(options) || {}; const through = this.through; let scopeWhere; @@ -452,6 +452,7 @@ class BelongsToMany extends Association { association: this.oneFromTarget, attributes: options.joinTableAttributes, required: true, + paranoid: _.get(options.through, 'paranoid', true), where: throughWhere }); } @@ -482,7 +483,7 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - count(instance, options) { + async count(instance, options) { const sequelize = this.target.sequelize; options = Utils.cloneDeep(options); @@ -493,7 +494,9 @@ class BelongsToMany extends Association { options.raw = true; options.plain = true; - return this.get(instance, options).then(result => parseInt(result.count, 10)); + const result = await this.get(instance, options); + + return parseInt(result.count, 10); } /** @@ -505,18 +508,18 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - has(sourceInstance, instances, options) { + async has(sourceInstance, instances, options) { if (!Array.isArray(instances)) { instances = [instances]; } - options = Object.assign({ - raw: true - }, options, { + options = { + raw: true, + ...options, scope: false, attributes: [this.targetKey], joinTableAttributes: [] - }); + }; const instancePrimaryKeys = instances.map(instance => { if (instance instanceof this.target) { @@ -534,9 +537,10 @@ class BelongsToMany extends Association { ] }; - return this.get(sourceInstance, options).then(associatedObjects => - _.differenceBy(instancePrimaryKeys, associatedObjects, this.targetKey).length === 0 - ); + const associatedObjects = await this.get(sourceInstance, options); + + return _.differenceWith(instancePrimaryKeys, associatedObjects, + (a, b) => _.isEqual(a[this.targetKey], b[this.targetKey])).length === 0; } /** @@ -551,23 +555,23 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - set(sourceInstance, newAssociatedObjects, options = {}) { + async set(sourceInstance, newAssociatedObjects, options) { options = options || {}; const sourceKey = this.sourceKey; const targetKey = this.targetKey; const identifier = this.identifier; const foreignIdentifier = this.foreignIdentifier; - let where = {}; if (newAssociatedObjects === null) { newAssociatedObjects = []; } else { newAssociatedObjects = this.toInstanceArray(newAssociatedObjects); } - - where[identifier] = sourceInstance.get(sourceKey); - where = Object.assign(where, this.through.scope); + const where = { + [identifier]: sourceInstance.get(sourceKey), + ...this.through.scope + }; const updateAssociations = currentRows => { const obsoleteAssociations = []; @@ -607,14 +611,14 @@ class BelongsToMany extends Association { } if (obsoleteAssociations.length > 0) { - const where = Object.assign({ - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]) - }, this.through.scope); promises.push( this.through.model.destroy({ ...options, - where + where: { + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: obsoleteAssociations.map(obsoleteAssociation => obsoleteAssociation[foreignIdentifier]), + ...this.through.scope + } }) ); } @@ -622,26 +626,27 @@ class BelongsToMany extends Association { if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { return { - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: unassociatedObject.get(targetKey), ...defaultAttributes, ...unassociatedObject[this.through.model.name], + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: unassociatedObject.get(targetKey), ...this.through.scope }; }); - promises.push(this.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); + promises.push(this.through.model.bulkCreate(bulk, { validate: true, ...options })); } - return Utils.Promise.all(promises); + return Promise.all(promises); }; - return this.through.model.findAll({ ...options, where, raw: true }) - .then(currentRows => updateAssociations(currentRows)) - .catch(error => { - if (error instanceof EmptyResultError) return updateAssociations([]); - throw error; - }); + try { + const currentRows = await this.through.model.findAll({ ...options, where, raw: true }); + return await updateAssociations(currentRows); + } catch (error) { + if (error instanceof EmptyResultError) return updateAssociations([]); + throw error; + } } /** @@ -656,27 +661,27 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - add(sourceInstance, newInstances, options) { + async add(sourceInstance, newInstances, options) { // If newInstances is null or undefined, no-op - if (!newInstances) return Utils.Promise.resolve(); + if (!newInstances) return Promise.resolve(); options = { ...options }; - const sourceKey = this.sourceKey; - const targetKey = this.targetKey; - const identifier = this.identifier; - const foreignIdentifier = this.foreignIdentifier; + const association = this; + const sourceKey = association.sourceKey; + const targetKey = association.targetKey; + const identifier = association.identifier; + const foreignIdentifier = association.foreignIdentifier; const defaultAttributes = options.through || {}; - newInstances = this.toInstanceArray(newInstances); + newInstances = association.toInstanceArray(newInstances); const where = { [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: newInstances.map(newInstance => newInstance.get(targetKey)) + [foreignIdentifier]: newInstances.map(newInstance => newInstance.get(targetKey)), + ...association.through.scope }; - Object.assign(where, this.through.scope); - const updateAssociations = currentRows => { const promises = []; const unassociatedObjects = []; @@ -687,7 +692,7 @@ class BelongsToMany extends Association { if (!existingAssociation) { unassociatedObjects.push(obj); } else { - const throughAttributes = obj[this.through.model.name]; + const throughAttributes = obj[association.through.model.name]; const attributes = { ...defaultAttributes, ...throughAttributes }; if (Object.keys(attributes).some(attribute => attributes[attribute] !== existingAssociation[attribute])) { @@ -698,40 +703,45 @@ class BelongsToMany extends Association { if (unassociatedObjects.length > 0) { const bulk = unassociatedObjects.map(unassociatedObject => { - const throughAttributes = unassociatedObject[this.through.model.name]; - return { - ...defaultAttributes, - ...throughAttributes, - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: unassociatedObject.get(targetKey), - ...this.through.scope - }; + const throughAttributes = unassociatedObject[association.through.model.name]; + const attributes = { ...defaultAttributes, ...throughAttributes }; + + attributes[identifier] = sourceInstance.get(sourceKey); + attributes[foreignIdentifier] = unassociatedObject.get(targetKey); + + Object.assign(attributes, association.through.scope); + + return attributes; }); - promises.push(this.through.model.bulkCreate(bulk, Object.assign({ validate: true }, options))); + promises.push(association.through.model.bulkCreate(bulk, { validate: true, ...options })); } for (const assoc of changedAssociations) { - const attributes = { ...defaultAttributes, ...assoc[this.through.model.name] }; + let throughAttributes = assoc[association.through.model.name]; + const attributes = { ...defaultAttributes, ...throughAttributes }; + // Quick-fix for subtle bug when using existing objects that might have the through model attached (not as an attribute object) + if (throughAttributes instanceof association.through.model) { + throughAttributes = {}; + } - promises.push(this.through.model.update(attributes, Object.assign(options, { - where: { - [identifier]: sourceInstance.get(sourceKey), - [foreignIdentifier]: assoc.get(targetKey) - } - }))); + promises.push(association.through.model.update(attributes, Object.assign(options, { where: { + [identifier]: sourceInstance.get(sourceKey), + [foreignIdentifier]: assoc.get(targetKey) + } }))); } - return Utils.Promise.all(promises); + return Promise.all(promises); }; - return this.through.model.findAll({ ...options, where, raw: true }) - .then(currentRows => updateAssociations(currentRows)) - .then(([associations]) => associations) - .catch(error => { - if (error instanceof EmptyResultError) return updateAssociations(); - throw error; - }); + try { + const currentRows = await association.through.model.findAll({ ...options, where, raw: true }); + const [associations] = await updateAssociations(currentRows); + return associations; + } catch (error) { + if (error instanceof EmptyResultError) return updateAssociations(); + throw error; + } } /** @@ -743,15 +753,19 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - remove(sourceInstance, oldAssociatedObjects, options = {}) { - oldAssociatedObjects = this.toInstanceArray(oldAssociatedObjects); + remove(sourceInstance, oldAssociatedObjects, options) { + const association = this; + + options = options || {}; + + oldAssociatedObjects = association.toInstanceArray(oldAssociatedObjects); const where = { - [this.identifier]: sourceInstance.get(this.sourceKey), - [this.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(this.targetKey)) + [association.identifier]: sourceInstance.get(association.sourceKey), + [association.foreignIdentifier]: oldAssociatedObjects.map(newInstance => newInstance.get(association.targetKey)) }; - return this.through.model.destroy({ ...options, where }); + return association.through.model.destroy({ ...options, where }); } /** @@ -764,24 +778,30 @@ class BelongsToMany extends Association { * * @returns {Promise} */ - create(sourceInstance, values = {}, options = {}) { + async create(sourceInstance, values, options) { + const association = this; + + options = options || {}; + values = values || {}; + if (Array.isArray(options)) { options = { fields: options }; } - if (this.scope) { - Object.assign(values, this.scope); + if (association.scope) { + Object.assign(values, association.scope); if (options.fields) { - options.fields = options.fields.concat(Object.keys(this.scope)); + options.fields = options.fields.concat(Object.keys(association.scope)); } } // Create the related model instance - return this.target.create(values, options).then(newAssociatedObject => - sourceInstance[this.accessors.add](newAssociatedObject, _.omit(options, ['fields'])).return(newAssociatedObject) - ); + const newAssociatedObject = await association.target.create(values, options); + + await sourceInstance[association.accessors.add](newAssociatedObject, _.omit(options, ['fields'])); + return newAssociatedObject; } verifyAssociationAlias(alias) { diff --git a/lib/associations/belongs-to.js b/lib/associations/belongs-to.js index a09df933d2a9..cf78bd5b021c 100644 --- a/lib/associations/belongs-to.js +++ b/lib/associations/belongs-to.js @@ -124,7 +124,7 @@ class BelongsTo extends Association { * * @returns {Promise} */ - get(instances, options) { + async get(instances, options) { const where = {}; let Target = this.target; let instance; @@ -150,7 +150,7 @@ class BelongsTo extends Association { if (instances) { where[this.targetKey] = { - [Op.in]: instances.map(instance => instance.get(this.foreignKey)) + [Op.in]: instances.map(_instance => _instance.get(this.foreignKey)) }; } else { if (this.targetKeyIsPrimary && !options.where) { @@ -165,18 +165,17 @@ class BelongsTo extends Association { where; if (instances) { - return Target.findAll(options).then(results => { - const result = {}; - for (const instance of instances) { - result[instance.get(this.foreignKey, { raw: true })] = null; - } - - for (const instance of results) { - result[instance.get(this.targetKey, { raw: true })] = instance; - } - - return result; - }); + const results = await Target.findAll(options); + const result = {}; + for (const _instance of instances) { + result[_instance.get(this.foreignKey, { raw: true })] = null; + } + + for (const _instance of results) { + result[_instance.get(this.targetKey, { raw: true })] = _instance; + } + + return result; } return Target.findOne(options); @@ -192,7 +191,7 @@ class BelongsTo extends Association { * * @returns {Promise} */ - set(sourceInstance, associatedInstance, options = {}) { + async set(sourceInstance, associatedInstance, options = {}) { let value = associatedInstance; if (associatedInstance instanceof this.target) { @@ -203,14 +202,15 @@ class BelongsTo extends Association { if (options.save === false) return; - options = Object.assign({ + options = { fields: [this.foreignKey], allowNull: [this.foreignKey], - association: true - }, options); + association: true, + ...options + }; // passes the changed field to save, so only that field get updated. - return sourceInstance.save(options); + return await sourceInstance.save(options); } /** @@ -225,11 +225,14 @@ class BelongsTo extends Association { * * @returns {Promise} The created target model */ - create(sourceInstance, values = {}, options = {}) { - return this.target.create(values, options) - .then(newAssociatedObject => sourceInstance[this.accessors.set](newAssociatedObject, options) - .then(() => newAssociatedObject) - ); + async create(sourceInstance, values, options) { + values = values || {}; + options = options || {}; + + const newAssociatedObject = await this.target.create(values, options); + await sourceInstance[this.accessors.set](newAssociatedObject, options); + + return newAssociatedObject; } verifyAssociationAlias(alias) { diff --git a/lib/associations/has-many.js b/lib/associations/has-many.js index 27c73033cfd7..e8c184bb630b 100644 --- a/lib/associations/has-many.js +++ b/lib/associations/has-many.js @@ -112,16 +112,17 @@ class HasMany extends Association { // the id is in the target table // or in an extra table which connects two tables _injectAttributes() { - const newAttributes = {}; + const newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, + allowNull: true, + ...this.foreignKeyAttribute + } + }; + // Create a new options object for use with addForeignKeyConstraints, to avoid polluting this.options in case it is later used for a n:m const constraintOptions = { ...this.options }; - newAttributes[this.foreignKey] = { - type: this.options.keyType || this.source.rawAttributes[this.sourceKeyAttribute].type, - allowNull: true, - ...this.foreignKeyAttribute - }; - if (this.options.constraints !== false) { const target = this.target.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey]; constraintOptions.onDelete = constraintOptions.onDelete || (target.allowNull ? 'SET NULL' : 'CASCADE'); @@ -169,7 +170,7 @@ class HasMany extends Association { * * @returns {Promise>} */ - get(instances, options = {}) { + async get(instances, options = {}) { const where = {}; let Model = this.target; @@ -181,14 +182,14 @@ class HasMany extends Association { instances = undefined; } - options = Object.assign({}, options); + options = { ...options }; if (this.scope) { Object.assign(where, this.scope); } if (instances) { - values = instances.map(instance => instance.get(this.sourceKey, { raw: true })); + values = instances.map(_instance => _instance.get(this.sourceKey, { raw: true })); if (options.limit && instances.length > 1) { options.groupedLimit = { @@ -224,20 +225,19 @@ class HasMany extends Association { Model = Model.schema(options.schema, options.schemaDelimiter); } - return Model.findAll(options).then(results => { - if (instance) return results; + const results = await Model.findAll(options); + if (instance) return results; - const result = {}; - for (const instance of instances) { - result[instance.get(this.sourceKey, { raw: true })] = []; - } + const result = {}; + for (const _instance of instances) { + result[_instance.get(this.sourceKey, { raw: true })] = []; + } - for (const instance of results) { - result[instance.get(this.foreignKey, { raw: true })].push(instance); - } + for (const _instance of results) { + result[_instance.get(this.foreignKey, { raw: true })].push(_instance); + } - return result; - }); + return result; } /** @@ -250,7 +250,7 @@ class HasMany extends Association { * * @returns {Promise} */ - count(instance, options) { + async count(instance, options) { options = Utils.cloneDeep(options); options.attributes = [ @@ -265,7 +265,9 @@ class HasMany extends Association { options.raw = true; options.plain = true; - return this.get(instance, options).then(result => parseInt(result.count, 10)); + const result = await this.get(instance, options); + + return parseInt(result.count, 10); } /** @@ -277,18 +279,19 @@ class HasMany extends Association { * * @returns {Promise} */ - has(sourceInstance, targetInstances, options) { + async has(sourceInstance, targetInstances, options) { const where = {}; if (!Array.isArray(targetInstances)) { targetInstances = [targetInstances]; } - options = Object.assign({}, options, { + options = { + ...options, scope: false, attributes: [this.target.primaryKeyAttribute], raw: true - }); + }; where[Op.or] = targetInstances.map(instance => { if (instance instanceof this.target) { @@ -306,7 +309,9 @@ class HasMany extends Association { ] }; - return this.get(sourceInstance, options).then(associatedObjects => associatedObjects.length === targetInstances.length); + const associatedObjects = await this.get(sourceInstance, options); + + return associatedObjects.length === targetInstances.length; } /** @@ -319,74 +324,71 @@ class HasMany extends Association { * * @returns {Promise} */ - set(sourceInstance, targetInstances, options) { + async set(sourceInstance, targetInstances, options) { if (targetInstances === null) { targetInstances = []; } else { targetInstances = this.toInstanceArray(targetInstances); } - return this.get(sourceInstance, { - ...options, - scope: false, - raw: true - }).then(oldAssociations => { - const promises = []; - const obsoleteAssociations = oldAssociations.filter(old => - !targetInstances.find(obj => - obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] - ) - ); - const unassociatedObjects = targetInstances.filter(obj => - !oldAssociations.find(old => - obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] - ) - ); - let updateWhere; - let update; + const oldAssociations = await this.get(sourceInstance, { ...options, scope: false, raw: true }); + const promises = []; + const obsoleteAssociations = oldAssociations.filter(old => + !targetInstances.find(obj => + obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] + ) + ); + const unassociatedObjects = targetInstances.filter(obj => + !oldAssociations.find(old => + obj[this.target.primaryKeyAttribute] === old[this.target.primaryKeyAttribute] + ) + ); + let updateWhere; + let update; - if (obsoleteAssociations.length > 0) { - update = {}; - update[this.foreignKey] = null; + if (obsoleteAssociations.length > 0) { + update = {}; + update[this.foreignKey] = null; - updateWhere = { - [this.target.primaryKeyAttribute]: obsoleteAssociations.map(associatedObject => - associatedObject[this.target.primaryKeyAttribute] - ) - }; + updateWhere = { + [this.target.primaryKeyAttribute]: obsoleteAssociations.map(associatedObject => + associatedObject[this.target.primaryKeyAttribute] + ) + }; - promises.push(this.target.unscoped().update( - update, - { - ...options, - where: updateWhere - } - )); - } + promises.push(this.target.unscoped().update( + update, + { + ...options, + where: updateWhere + } + )); + } + + if (unassociatedObjects.length > 0) { + updateWhere = {}; - if (unassociatedObjects.length > 0) { - updateWhere = {}; + update = {}; + update[this.foreignKey] = sourceInstance.get(this.sourceKey); - update = {}; - update[this.foreignKey] = sourceInstance.get(this.sourceKey); + Object.assign(update, this.scope); + updateWhere[this.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject => + unassociatedObject[this.target.primaryKeyAttribute] + ); - Object.assign(update, this.scope); - updateWhere[this.target.primaryKeyAttribute] = unassociatedObjects.map(unassociatedObject => - unassociatedObject[this.target.primaryKeyAttribute] - ); + promises.push(this.target.unscoped().update( + update, + { + ...options, + where: updateWhere + } + )); + } - promises.push(this.target.unscoped().update( - update, - { - ...options, - where: updateWhere - } - )); - } + await Promise.all(promises); - return Utils.Promise.all(promises).return(sourceInstance); - }); + return sourceInstance; } /** @@ -399,15 +401,16 @@ class HasMany extends Association { * * @returns {Promise} */ - add(sourceInstance, targetInstances, options = {}) { - if (!targetInstances) return Utils.Promise.resolve(); + async add(sourceInstance, targetInstances, options = {}) { + if (!targetInstances) return Promise.resolve(); - const update = {}; targetInstances = this.toInstanceArray(targetInstances); - update[this.foreignKey] = sourceInstance.get(this.sourceKey); - Object.assign(update, this.scope); + const update = { + [this.foreignKey]: sourceInstance.get(this.sourceKey), + ...this.scope + }; const where = { [this.target.primaryKeyAttribute]: targetInstances.map(unassociatedObject => @@ -415,7 +418,9 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, { ...options, where }).return(sourceInstance); + await this.target.unscoped().update(update, { ...options, where }); + + return sourceInstance; } /** @@ -427,7 +432,7 @@ class HasMany extends Association { * * @returns {Promise} */ - remove(sourceInstance, targetInstances, options = {}) { + async remove(sourceInstance, targetInstances, options = {}) { const update = { [this.foreignKey]: null }; @@ -441,7 +446,9 @@ class HasMany extends Association { ) }; - return this.target.unscoped().update(update, { ...options, where }).return(this); + await this.target.unscoped().update(update, { ...options, where }); + + return this; } /** @@ -453,7 +460,7 @@ class HasMany extends Association { * * @returns {Promise} */ - create(sourceInstance, values, options = {}) { + async create(sourceInstance, values, options = {}) { if (Array.isArray(options)) { options = { fields: options @@ -473,7 +480,7 @@ class HasMany extends Association { values[this.foreignKey] = sourceInstance.get(this.sourceKey); if (options.fields) options.fields.push(this.foreignKey); - return this.target.create(values, options); + return await this.target.create(values, options); } verifyAssociationAlias(alias) { diff --git a/lib/associations/has-one.js b/lib/associations/has-one.js index a2195a4155bd..6d19f4263c0a 100644 --- a/lib/associations/has-one.js +++ b/lib/associations/has-one.js @@ -78,12 +78,12 @@ class HasOne extends Association { // the id is in the target table _injectAttributes() { - const newAttributes = {}; - - newAttributes[this.foreignKey] = { - type: this.options.keyType || this.source.rawAttributes[this.sourceKey].type, - allowNull: true, - ...this.foreignKeyAttribute + const newAttributes = { + [this.foreignKey]: { + type: this.options.keyType || this.source.rawAttributes[this.sourceKey].type, + allowNull: true, + ...this.foreignKeyAttribute + } }; if (this.options.constraints !== false) { @@ -123,7 +123,7 @@ class HasOne extends Association { * * @returns {Promise} */ - get(instances, options) { + async get(instances, options) { const where = {}; let Target = this.target; @@ -150,7 +150,7 @@ class HasOne extends Association { if (instances) { where[this.foreignKey] = { - [Op.in]: instances.map(instance => instance.get(this.sourceKey)) + [Op.in]: instances.map(_instance => _instance.get(this.sourceKey)) }; } else { where[this.foreignKey] = instance.get(this.sourceKey); @@ -165,18 +165,17 @@ class HasOne extends Association { where; if (instances) { - return Target.findAll(options).then(results => { - const result = {}; - for (const instance of instances) { - result[instance.get(this.sourceKey, { raw: true })] = null; - } + const results = await Target.findAll(options); + const result = {}; + for (const _instance of instances) { + result[_instance.get(this.sourceKey, { raw: true })] = null; + } - for (const instance of results) { - result[instance.get(this.foreignKey, { raw: true })] = instance; - } + for (const _instance of results) { + result[_instance.get(this.foreignKey, { raw: true })] = _instance; + } - return result; - }); + return result; } return Target.findOne(options); @@ -191,45 +190,41 @@ class HasOne extends Association { * * @returns {Promise} */ - set(sourceInstance, associatedInstance, options) { - let alreadyAssociated; - - options = Object.assign({}, options, { - scope: false - }); - - return sourceInstance[this.accessors.get](options).then(oldInstance => { - // TODO Use equals method once #5605 is resolved - alreadyAssociated = oldInstance && associatedInstance && this.target.primaryKeyAttributes.every(attribute => - oldInstance.get(attribute, { raw: true }) === (associatedInstance.get ? associatedInstance.get(attribute, { raw: true }) : associatedInstance) - ); - - if (oldInstance && !alreadyAssociated) { - oldInstance[this.foreignKey] = null; - return oldInstance.save(Object.assign({}, options, { - fields: [this.foreignKey], - allowNull: [this.foreignKey], - association: true - })); + async set(sourceInstance, associatedInstance, options) { + options = { ...options, scope: false }; + + const oldInstance = await sourceInstance[this.accessors.get](options); + // TODO Use equals method once #5605 is resolved + const alreadyAssociated = oldInstance && associatedInstance && this.target.primaryKeyAttributes.every(attribute => + oldInstance.get(attribute, { raw: true }) === (associatedInstance.get ? associatedInstance.get(attribute, { raw: true }) : associatedInstance) + ); + + if (oldInstance && !alreadyAssociated) { + oldInstance[this.foreignKey] = null; + + await oldInstance.save({ + ...options, + fields: [this.foreignKey], + allowNull: [this.foreignKey], + association: true + }); + } + if (associatedInstance && !alreadyAssociated) { + if (!(associatedInstance instanceof this.target)) { + const tmpInstance = {}; + tmpInstance[this.target.primaryKeyAttribute] = associatedInstance; + associatedInstance = this.target.build(tmpInstance, { + isNewRecord: false + }); } - }).then(() => { - if (associatedInstance && !alreadyAssociated) { - if (!(associatedInstance instanceof this.target)) { - const tmpInstance = {}; - tmpInstance[this.target.primaryKeyAttribute] = associatedInstance; - associatedInstance = new this.target(tmpInstance, { - isNewRecord: false - }); - } - Object.assign(associatedInstance, this.scope); - associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceKeyAttribute)); + Object.assign(associatedInstance, this.scope); + associatedInstance.set(this.foreignKey, sourceInstance.get(this.sourceKeyAttribute)); - return associatedInstance.save(options); - } + return associatedInstance.save(options); + } - return null; - }); + return null; } /** @@ -244,7 +239,10 @@ class HasOne extends Association { * * @returns {Promise} The created target model */ - create(sourceInstance, values = {}, options = {}) { + async create(sourceInstance, values, options) { + values = values || {}; + options = options || {}; + if (this.scope) { for (const attribute of Object.keys(this.scope)) { values[attribute] = this.scope[attribute]; @@ -259,7 +257,7 @@ class HasOne extends Association { options.fields.push(this.foreignKey); } - return this.target.create(values, options); + return await this.target.create(values, options); } verifyAssociationAlias(alias) { diff --git a/lib/associations/helpers.js b/lib/associations/helpers.js index a04c265e29ff..41e2f27a3e08 100644 --- a/lib/associations/helpers.js +++ b/lib/associations/helpers.js @@ -21,19 +21,11 @@ function addForeignKeyConstraints(newAttribute, source, target, options, key) { .map(primaryKeyAttribute => source.rawAttributes[primaryKeyAttribute].field || primaryKeyAttribute); if (primaryKeys.length === 1 || !primaryKeys.includes(key)) { - if (source._schema) { - newAttribute.references = { - model: source.sequelize.getQueryInterface().QueryGenerator.addSchema({ - tableName: source.tableName, - _schema: source._schema, - _schemaDelimiter: source._schemaDelimiter - }) - }; - } else { - newAttribute.references = { model: source.tableName }; - } + newAttribute.references = { + model: source.getTableName(), + key: key || primaryKeys[0] + }; - newAttribute.references.key = key || primaryKeys[0]; newAttribute.onDelete = options.onDelete; newAttribute.onUpdate = options.onUpdate; } @@ -52,7 +44,9 @@ exports.addForeignKeyConstraints = addForeignKeyConstraints; * @param {object} aliases Mapping between model and association method names * */ -function mixinMethods(association, obj, methods, aliases = {}) { +function mixinMethods(association, obj, methods, aliases) { + aliases = aliases || {}; + for (const method of methods) { // don't override custom methods if (!Object.prototype.hasOwnProperty.call(obj, association.accessors[method])) { diff --git a/lib/associations/mixin.js b/lib/associations/mixin.js index 1901c7af9b5a..76646e2d93b4 100644 --- a/lib/associations/mixin.js +++ b/lib/associations/mixin.js @@ -24,10 +24,10 @@ const Mixin = { options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); options.useHooks = options.hooks; - options = Object.assign(options, _.omit(source.options, ['hooks'])); + Object.assign(options, _.omit(source.options, ['hooks'])); if (options.useHooks) { - this.hooks.run('beforeAssociate', { source, target, type: HasMany }, options); + this.runHooks('beforeAssociate', { source, target, type: HasMany }, options); } // the id is in the foreign table or in a connecting table @@ -38,7 +38,7 @@ const Mixin = { association.mixin(source.prototype); if (options.useHooks) { - this.hooks.run('afterAssociate', { source, target, type: HasMany, association }, options); + this.runHooks('afterAssociate', { source, target, type: HasMany, association }, options); } return association; @@ -55,10 +55,10 @@ const Mixin = { options.hooks = options.hooks === undefined ? false : Boolean(options.hooks); options.useHooks = options.hooks; options.timestamps = options.timestamps === undefined ? this.sequelize.options.timestamps : options.timestamps; - options = Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope'])); + Object.assign(options, _.omit(source.options, ['hooks', 'timestamps', 'scopes', 'defaultScope'])); if (options.useHooks) { - this.hooks.run('beforeAssociate', { source, target, type: BelongsToMany }, options); + this.runHooks('beforeAssociate', { source, target, type: BelongsToMany }, options); } // the id is in the foreign table or in a connecting table const association = new BelongsToMany(source, target, options); @@ -68,7 +68,7 @@ const Mixin = { association.mixin(source.prototype); if (options.useHooks) { - this.hooks.run('afterAssociate', { source, target, type: BelongsToMany, association }, options); + this.runHooks('afterAssociate', { source, target, type: BelongsToMany, association }, options); } return association; @@ -99,7 +99,7 @@ function singleLinked(Type) { options.useHooks = options.hooks; if (options.useHooks) { - source.hooks.run('beforeAssociate', { source, target, type: Type }, options); + source.runHooks('beforeAssociate', { source, target, type: Type }, options); } // the id is in the foreign table const association = new Type(source, target, Object.assign(options, source.options)); @@ -109,7 +109,7 @@ function singleLinked(Type) { association.mixin(source.prototype); if (options.useHooks) { - source.hooks.run('afterAssociate', { source, target, type: Type, association }, options); + source.runHooks('afterAssociate', { source, target, type: Type, association }, options); } return association; diff --git a/lib/data-types.js b/lib/data-types.js index 256275e857a2..90640c9608d7 100644 --- a/lib/data-types.js +++ b/lib/data-types.js @@ -9,7 +9,8 @@ const momentTz = require('moment-timezone'); const moment = require('moment'); const { logger } = require('./utils/logger'); const warnings = {}; -const { classToInvokable } = require('./utils/classToInvokable'); +const { classToInvokable } = require('./utils/class-to-invokable'); +const { joinSQLFragments } = require('./utils/join-sql-fragments'); class ABSTRACT { toString(options) { @@ -62,7 +63,10 @@ class STRING extends ABSTRACT { this._length = options.length || 255; } toSql() { - return `VARCHAR(${this._length})${this._binary ? ' BINARY' : ''}`; + return joinSQLFragments([ + `VARCHAR(${this._length})`, + this._binary && 'BINARY' + ]); } validate(value) { if (Object.prototype.toString.call(value) !== '[object String]') { @@ -97,7 +101,10 @@ class CHAR extends STRING { super(typeof length === 'object' && length || { length, binary }); } toSql() { - return `CHAR(${this._length})${this._binary ? ' BINARY' : ''}`; + return joinSQLFragments([ + `CHAR(${this._length})`, + this._binary && 'BINARY' + ]); } } @@ -785,9 +792,9 @@ class ARRAY extends ABSTRACT { * GeoJSON is accepted as input and returned as output. * * In PostGIS, the GeoJSON is parsed using the PostGIS function `ST_GeomFromGeoJSON`. - * In MySQL it is parsed using the function `GeomFromText`. + * In MySQL it is parsed using the function `ST_GeomFromText`. * - * Therefore, one can just follow the [GeoJSON spec](http://geojson.org/geojson-spec.html) for handling geometry objects. See the following examples: + * Therefore, one can just follow the [GeoJSON spec](https://tools.ietf.org/html/rfc7946) for handling geometry objects. See the following examples: * * @example
* DataTypes.GEOMETRY @@ -837,10 +844,10 @@ class GEOMETRY extends ABSTRACT { this.srid = options.srid; } _stringify(value, options) { - return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } _bindParam(value, options) { - return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } } @@ -880,10 +887,10 @@ class GEOGRAPHY extends ABSTRACT { this.srid = options.srid; } _stringify(value, options) { - return `GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.escape(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } _bindParam(value, options) { - return `GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; + return `ST_GeomFromText(${options.bindParam(wkx.Geometry.parseGeoJSON(value).toWkt())})`; } } @@ -933,6 +940,21 @@ class MACADDR extends ABSTRACT { } } +/** + * The TSVECTOR type stores text search vectors. + * + * Only available for Postgres + * + */ +class TSVECTOR extends ABSTRACT { + validate(value) { + if (typeof value !== 'string') { + throw new sequelizeErrors.ValidationError(util.format('%j is not a valid string', value)); + } + return true; + } +} + /** * A convenience class holding commonly used data types. The data types are used when defining a new model using `Sequelize.define`, like this: * ```js @@ -1016,7 +1038,8 @@ const DataTypes = module.exports = { CIDR, INET, MACADDR, - CITEXT + CITEXT, + TSVECTOR }; _.each(DataTypes, (dataType, name) => { diff --git a/lib/deferrable.js b/lib/deferrable.js index 84dce479d4b2..a2379b533da6 100644 --- a/lib/deferrable.js +++ b/lib/deferrable.js @@ -87,17 +87,19 @@ class SET_IMMEDIATE extends ABSTRACT { * }); * ``` * - * @property INITIALLY_DEFERRED Defer constraints checks to the end of transactions. - * @property INITIALLY_IMMEDIATE Trigger the constraint checks immediately - * @property NOT Set the constraints to not deferred. This is the default in PostgreSQL and it make it impossible to dynamically defer the constraints within a transaction. - * @property SET_DEFERRED - * @property SET_IMMEDIATE + * @property INITIALLY_DEFERRED Use when declaring a constraint. Allow and enable by default this constraint's checks to be deferred at the end of transactions. + * @property INITIALLY_IMMEDIATE Use when declaring a constraint. Allow the constraint's checks to be deferred at the end of transactions. + * @property NOT Use when declaring a constraint. Set the constraint to not deferred. This is the default in PostgreSQL and makes it impossible to dynamically defer the constraints within a transaction. + * @property SET_DEFERRED Use when declaring a transaction. Defer the deferrable checks involved in this transaction at commit. + * @property SET_IMMEDIATE Use when declaring a transaction. Execute the deferrable checks involved in this transaction immediately. */ -const Deferrable = module.exports = { // eslint-disable-line +const Deferrable = { INITIALLY_DEFERRED: classToInvokable(INITIALLY_DEFERRED), INITIALLY_IMMEDIATE: classToInvokable(INITIALLY_IMMEDIATE), NOT: classToInvokable(NOT), SET_DEFERRED: classToInvokable(SET_DEFERRED), SET_IMMEDIATE: classToInvokable(SET_IMMEDIATE) }; + +module.exports = Deferrable; diff --git a/lib/dialects/abstract/connection-manager.js b/lib/dialects/abstract/connection-manager.js index 1f27005937ed..8f6ee9f44f99 100644 --- a/lib/dialects/abstract/connection-manager.js +++ b/lib/dialects/abstract/connection-manager.js @@ -3,11 +3,10 @@ const { Pool, TimeoutError } = require('sequelize-pool'); const _ = require('lodash'); const semver = require('semver'); -const Promise = require('../../promise'); const errors = require('../../errors'); const { logger } = require('../../utils/logger'); +const deprecations = require('../../utils/deprecations'); const debug = logger.debugContext('pool'); -const { ParserStore } = require('./parser-store'); /** * Abstract Connection Manager @@ -31,17 +30,14 @@ class ConnectionManager { throw new Error('Support for pool:false was removed in v4.0'); } - config.pool = { + config.pool = _.defaults(config.pool || {}, { max: 5, min: 0, idle: 10000, acquire: 60000, evict: 1000, - validate: this._validate.bind(this), - ...config.pool - }; - - this.parserStore = new ParserStore(this.dialectName); + validate: this._validate.bind(this) + }); this.initPools(); } @@ -95,23 +91,15 @@ class ConnectionManager { * @private * @returns {Promise} */ - _onProcessExit() { + async _onProcessExit() { if (!this.pool) { - return Promise.resolve(); + return; } - return this.pool.drain().then(() => { - debug('connection drain due to process exit'); - return this.pool.destroyAllNow(); - }); - } - - _refreshTypeParser(dataType) { - this.parserStore.refresh(dataType); - } + await this.pool.drain(); + debug('connection drain due to process exit'); - _clearTypeParser() { - this.parserStore.clear(); + return await this.pool.destroyAllNow(); } /** @@ -119,13 +107,13 @@ class ConnectionManager { * * @returns {Promise} */ - close() { + async close() { // Mark close of pool - this.getConnection = function getConnection() { - return Promise.reject(new Error('ConnectionManager.getConnection was called after the connection manager was closed!')); + this.getConnection = async function getConnection() { + throw new Error('ConnectionManager.getConnection was called after the connection manager was closed!'); }; - return this._onProcessExit(); + return await this._onProcessExit(); } /** @@ -139,16 +127,18 @@ class ConnectionManager { this.pool = new Pool({ name: 'sequelize', create: () => this._connect(config), - destroy: connection => { - return this._disconnect(connection) - .tap(() => { debug('connection destroy'); }); + destroy: async connection => { + const result = await this._disconnect(connection); + debug('connection destroy'); + return result; }, validate: config.pool.validate, max: config.pool.max, min: config.pool.min, acquireTimeoutMillis: config.pool.acquire, idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses }); debug(`pool created with max/min: ${config.pool.max}/${config.pool.min}, no replication`); @@ -160,19 +150,12 @@ class ConnectionManager { config.replication.read = [config.replication.read]; } - const configWithoutReplication = _.omit(this.config, 'replication'); - // Map main connection config - config.replication.write = { - ...configWithoutReplication, - ...config.replication.write - }; + config.replication.write = _.defaults(config.replication.write, _.omit(config, 'replication')); // Apply defaults to each read config - config.replication.read = config.replication.read.map(readConfig => ({ - ...configWithoutReplication, - ...readConfig - }) + config.replication.read = config.replication.read.map(readConfig => + _.defaults(readConfig, _.omit(this.config, 'replication')) ); // custom pooling for replication (original author @janmeier) @@ -196,26 +179,26 @@ class ConnectionManager { this.pool[connection.queryType].destroy(connection); debug('connection destroy'); }, - destroyAllNow: () => { - return Promise.join( + destroyAllNow: async () => { + await Promise.all([ this.pool.read.destroyAllNow(), this.pool.write.destroyAllNow() - ).tap(() => { debug('all connections destroyed'); }); - }, - drain: () => { - return Promise.join( - this.pool.write.drain(), - this.pool.read.drain() - ); + ]); + + debug('all connections destroyed'); }, + drain: async () => Promise.all([ + this.pool.write.drain(), + this.pool.read.drain() + ]), read: new Pool({ name: 'sequelize:read', - create: () => { + create: async () => { // round robin config const nextRead = reads++ % config.replication.read.length; - return this._connect(config.replication.read[nextRead]).tap(connection => { - connection.queryType = 'read'; - }); + const connection = await this._connect(config.replication.read[nextRead]); + connection.queryType = 'read'; + return connection; }, destroy: connection => this._disconnect(connection), validate: config.pool.validate, @@ -223,14 +206,15 @@ class ConnectionManager { min: config.pool.min, acquireTimeoutMillis: config.pool.acquire, idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses }), write: new Pool({ name: 'sequelize:write', - create: () => { - return this._connect(config.replication.write).tap(connection => { - connection.queryType = 'write'; - }); + create: async () => { + const connection = await this._connect(config.replication.write); + connection.queryType = 'write'; + return connection; }, destroy: connection => this._disconnect(connection), validate: config.pool.validate, @@ -238,7 +222,8 @@ class ConnectionManager { min: config.pool.min, acquireTimeoutMillis: config.pool.acquire, idleTimeoutMillis: config.pool.idle, - reapIntervalMillis: config.pool.evict + reapIntervalMillis: config.pool.evict, + maxUses: config.pool.maxUses }) }; @@ -255,15 +240,14 @@ class ConnectionManager { * * @returns {Promise} */ - getConnection(options = {}) { + async getConnection(options) { + options = options || {}; - let promise; if (this.sequelize.options.databaseVersion === 0) { - if (this.versionPromise) { - promise = this.versionPromise; - } else { - promise = this.versionPromise = this._connect(this.config.replication.write || this.config) - .then(connection => { + if (!this.versionPromise) { + this.versionPromise = (async () => { + try { + const connection = await this._connect(this.config.replication.write || this.config); const _options = {}; _options.transaction = { connection }; // Cheat .query to use our private connection @@ -273,34 +257,41 @@ class ConnectionManager { //connection might have set databaseVersion value at initialization, //avoiding a useless round trip if (this.sequelize.options.databaseVersion === 0) { - return this.sequelize.databaseVersion(_options).then(version => { - const parsedVersion = _.get(semver.coerce(version), 'version') || version; - this.sequelize.options.databaseVersion = semver.valid(parsedVersion) - ? parsedVersion - : this.defaultVersion; - this.versionPromise = null; - return this._disconnect(connection); - }); + const version = await this.sequelize.databaseVersion(_options); + const parsedVersion = _.get(semver.coerce(version), 'version') || version; + this.sequelize.options.databaseVersion = semver.valid(parsedVersion) + ? parsedVersion + : this.dialect.defaultVersion; + } + + if (semver.lt(this.sequelize.options.databaseVersion, this.dialect.defaultVersion)) { + deprecations.unsupportedEngine(); + debug(`Unsupported database engine version ${this.sequelize.options.databaseVersion}`); } this.versionPromise = null; - return this._disconnect(connection); - }).catch(err => { + return await this._disconnect(connection); + } catch (err) { this.versionPromise = null; throw err; - }); + } + })(); } - } else { - promise = Promise.resolve(); + await this.versionPromise; + } + + let result; + + try { + result = await this.pool.acquire(options.type, options.useMaster); + } catch (error) { + if (error instanceof TimeoutError) throw new errors.ConnectionAcquireTimeoutError(error); + throw error; } - return promise.then(() => { - return this.pool.acquire(options.type, options.useMaster) - .catch(error => { - if (error instanceof TimeoutError) throw new errors.ConnectionAcquireTimeoutError(error); - throw error; - }); - }).tap(() => { debug('connection acquired'); }); + debug('connection acquired'); + + return result; } /** @@ -310,11 +301,9 @@ class ConnectionManager { * * @returns {Promise} */ - releaseConnection(connection) { - return Promise.try(() => { - this.pool.release(connection); - debug('connection released'); - }); + async releaseConnection(connection) { + this.pool.release(connection); + debug('connection released'); } /** @@ -324,10 +313,11 @@ class ConnectionManager { * @private * @returns {Promise} */ - _connect(config) { - return this.sequelize.hooks.run('beforeConnect', config) - .then(() => this.dialect.connectionManager.connect(config)) - .then(connection => this.sequelize.hooks.run('afterConnect', connection, config).return(connection)); + async _connect(config) { + await this.sequelize.runHooks('beforeConnect', config); + const connection = await this.dialect.connectionManager.connect(config); + await this.sequelize.runHooks('afterConnect', connection, config); + return connection; } /** @@ -337,10 +327,10 @@ class ConnectionManager { * @private * @returns {Promise} */ - _disconnect(connection) { - return this.sequelize.hooks.run('beforeDisconnect', connection) - .then(() => this.dialect.connectionManager.disconnect(connection)) - .then(() => this.sequelize.hooks.run('afterDisconnect', connection)); + async _disconnect(connection) { + await this.sequelize.runHooks('beforeDisconnect', connection); + await this.dialect.connectionManager.disconnect(connection); + return this.sequelize.runHooks('afterDisconnect', connection); } /** diff --git a/lib/dialects/abstract/index.js b/lib/dialects/abstract/index.js index c558b60d8587..c9e3c91c9bde 100644 --- a/lib/dialects/abstract/index.js +++ b/lib/dialects/abstract/index.js @@ -7,10 +7,10 @@ AbstractDialect.prototype.supports = { 'DEFAULT VALUES': false, 'VALUES ()': false, 'LIMIT ON UPDATE': false, - 'ON DUPLICATE KEY': true, 'ORDER NULLS': false, 'UNION': true, 'UNION ALL': true, + 'RIGHT JOIN': true, /* does the dialect support returning values for inserted/updated fields */ returnValues: false, @@ -30,6 +30,7 @@ AbstractDialect.prototype.supports = { bulkDefault: false, schemas: false, transactions: true, + settingIsolationLevelDuringTransaction: true, transactionOptions: { type: false }, @@ -57,9 +58,9 @@ AbstractDialect.prototype.supports = { concurrently: false, type: false, using: true, - functionBased: false + functionBased: false, + operator: false }, - joinTableDependent: true, groupedLimit: true, indexViaAlter: false, JSON: false, diff --git a/lib/dialects/abstract/parser-store.js b/lib/dialects/abstract/parser-store.js deleted file mode 100644 index 15350e868a47..000000000000 --- a/lib/dialects/abstract/parser-store.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -class ParserStore extends Map { - constructor(dialectName) { - super(); - this.dialectName = dialectName; - } - - refresh(dataType) { - for (const type of dataType.types[this.dialectName]) { - this.set(type, dataType.parse); - } - } -} - -module.exports.ParserStore = ParserStore; diff --git a/lib/dialects/abstract/query-generator.js b/lib/dialects/abstract/query-generator.js old mode 100755 new mode 100644 index 49630274cc18..5a438cd3268b --- a/lib/dialects/abstract/query-generator.js +++ b/lib/dialects/abstract/query-generator.js @@ -2,8 +2,7 @@ const util = require('util'); const _ = require('lodash'); -const uuidv4 = require('uuid/v4'); -const semver = require('semver'); +const uuidv4 = require('uuid').v4; const Utils = require('../../utils'); const deprecations = require('../../utils/deprecations'); @@ -38,7 +37,9 @@ class QueryGenerator { this._dialect = options._dialect; } - extractTableDetails(tableName = {}, options = {}) { + extractTableDetails(tableName, options) { + options = options || {}; + tableName = tableName || {}; return { schema: tableName.schema || options.schema || 'public', tableName: _.isPlainObject(tableName) ? tableName.tableName : tableName, @@ -96,21 +97,21 @@ class QueryGenerator { * @private */ insertQuery(table, valueHash, modelAttributes, options) { - options = { - ...this.options, - ...options - }; + options = options || {}; + _.defaults(options, this.options); const modelAttributeMap = {}; + const bind = []; const fields = []; + const returningModelAttributes = []; const values = []; - const bind = []; const quotedTable = this.quoteTable(table); const bindParam = options.bindParam === undefined ? this.bindParam(bind) : options.bindParam; let query; let valueQuery = ''; let emptyQuery = ''; let outputFragment = ''; + let returningFragment = ''; let identityWrapperRequired = false; let tmpTable = ''; //tmpTable declaration for trigger @@ -130,39 +131,12 @@ class QueryGenerator { } if (this._dialect.supports.returnValues && options.returning) { - if (this._dialect.supports.returnValues.returning) { - valueQuery += ' RETURNING *'; - emptyQuery += ' RETURNING *'; - } else if (this._dialect.supports.returnValues.output) { - outputFragment = ' OUTPUT INSERTED.*'; - - //To capture output rows when there is a trigger on MSSQL DB - if (modelAttributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) { - - let tmpColumns = ''; - let outputColumns = ''; - - for (const modelKey in modelAttributes) { - const attribute = modelAttributes[modelKey]; - if (!(attribute.type instanceof DataTypes.VIRTUAL)) { - if (tmpColumns.length > 0) { - tmpColumns += ','; - outputColumns += ','; - } + const returnValues = this.generateReturnValues(modelAttributes, options); - tmpColumns += `${this.quoteIdentifier(attribute.field)} ${attribute.type.toSql()}`; - outputColumns += `INSERTED.${this.quoteIdentifier(attribute.field)}`; - } - } - - tmpTable = `declare @tmp table (${tmpColumns});`; - outputFragment = ` OUTPUT ${outputColumns} into @tmp`; - const selectFromTmp = ';select * from @tmp'; - - valueQuery += selectFromTmp; - emptyQuery += selectFromTmp; - } - } + returningModelAttributes.push(...returnValues.returnFields); + returningFragment = returnValues.returningFragment; + tmpTable = returnValues.tmpTable || ''; + outputFragment = returnValues.outputFragment || ''; } if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) { @@ -182,7 +156,7 @@ class QueryGenerator { fields.push(this.quoteIdentifier(key)); // SERIALS' can't be NULL in postgresql, use DEFAULT where supported - if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && !value) { + if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && value == null) { if (!this._dialect.supports.autoIncrement.defaultValue) { fields.splice(-1, 1); } else if (this._dialect.supports.DEFAULT) { @@ -204,6 +178,20 @@ class QueryGenerator { } } + let onDuplicateKeyUpdate = ''; + + if (this._dialect.supports.inserts.updateOnDuplicate && options.updateOnDuplicate) { + if (this._dialect.supports.inserts.updateOnDuplicate == ' ON CONFLICT DO UPDATE SET') { // postgres / sqlite + // If no conflict target columns were specified, use the primary key names from options.upsertKeys + const conflictKeys = options.upsertKeys.map(attr => this.quoteIdentifier(attr)); + const updateKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=EXCLUDED.${this.quoteIdentifier(attr)}`); + onDuplicateKeyUpdate = ` ON CONFLICT (${conflictKeys.join(',')}) DO UPDATE SET ${updateKeys.join(',')}`; + } else { + const valueKeys = options.updateOnDuplicate.map(attr => `${this.quoteIdentifier(attr)}=VALUES(${this.quoteIdentifier(attr)})`); + onDuplicateKeyUpdate += `${this._dialect.supports.inserts.updateOnDuplicate} ${valueKeys.join(',')}`; + } + } + const replacements = { ignoreDuplicates: options.ignoreDuplicates ? this._dialect.supports.inserts.ignoreDuplicates : '', onConflictDoNothing: options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : '', @@ -213,29 +201,26 @@ class QueryGenerator { tmpTable }; - valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${replacements.onConflictDoNothing}${valueQuery}`; - emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${replacements.onConflictDoNothing}${emptyQuery}`; + valueQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable} (${replacements.attributes})${replacements.output} VALUES (${replacements.values})${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${valueQuery}`; + emptyQuery = `${tmpTable}INSERT${replacements.ignoreDuplicates} INTO ${quotedTable}${replacements.output}${onDuplicateKeyUpdate}${replacements.onConflictDoNothing}${emptyQuery}`; + // Mostly for internal use, so we expect the user to know what he's doing! + // pg_temp functions are private per connection, so we never risk this function interfering with another one. if (this._dialect.supports.EXCEPTION && options.exception) { - // Mostly for internal use, so we expect the user to know what he's doing! - // pg_temp functions are private per connection, so we never risk this function interfering with another one. - if (semver.gte(this.sequelize.options.databaseVersion, '9.2.0')) { - // >= 9.2 - Use a UUID but prefix with 'func_' (numbers first not allowed) - const delimiter = `$func_${uuidv4().replace(/-/g, '')}$`; - - options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; - valueQuery = `${`CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter}` + - ' BEGIN '}${valueQuery} INTO response; EXCEPTION ${options.exception} END ${delimiter - } LANGUAGE plpgsql; SELECT (testfunc.response).*, testfunc.sequelize_caught_exception FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc()`; - } else { - options.exception = 'WHEN unique_violation THEN NULL;'; - valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc() RETURNS SETOF ${quotedTable} AS $body$ BEGIN RETURN QUERY ${valueQuery}; EXCEPTION ${options.exception} END; $body$ LANGUAGE plpgsql; SELECT * FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc();`; + const dropFunction = 'DROP FUNCTION IF EXISTS pg_temp.testfunc()'; + + if (returningModelAttributes.length === 0) { + returningModelAttributes.push('*'); } - } - if (this._dialect.supports['ON DUPLICATE KEY'] && options.onDuplicate) { - valueQuery += ` ON DUPLICATE KEY ${options.onDuplicate}`; - emptyQuery += ` ON DUPLICATE KEY ${options.onDuplicate}`; + const delimiter = `$func_${uuidv4().replace(/-/g, '')}$`; + const selectQuery = `SELECT (testfunc.response).${returningModelAttributes.join(', (testfunc.response).')}, testfunc.sequelize_caught_exception FROM pg_temp.testfunc();`; + + options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;'; + valueQuery = `CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response ${quotedTable}, OUT sequelize_caught_exception text) RETURNS RECORD AS ${delimiter} BEGIN ${valueQuery} RETURNING * INTO response; EXCEPTION ${options.exception} END ${delimiter} LANGUAGE plpgsql; ${selectQuery} ${dropFunction}`; + } else { + valueQuery += returningFragment; + emptyQuery += returningFragment; } query = `${replacements.attributes.length ? valueQuery : emptyQuery};`; @@ -248,6 +233,7 @@ class QueryGenerator { if (options.bindParam !== false) { result.bind = bind; } + return result; } @@ -261,7 +247,10 @@ class QueryGenerator { * * @private */ - bulkInsertQuery(tableName, fieldValueHashes, options = {}, fieldMappedAttributes = {}) { + bulkInsertQuery(tableName, fieldValueHashes, options, fieldMappedAttributes) { + options = options || {}; + fieldMappedAttributes = fieldMappedAttributes || {}; + const tuples = []; const serials = {}; const allAttributes = []; @@ -287,7 +276,8 @@ class QueryGenerator { this._dialect.supports.bulkDefault && serials[key] === true ) { - return fieldValueHash[key] || 'DEFAULT'; + // fieldValueHashes[key] ?? 'DEFAULT' + return fieldValueHash[key] != null ? fieldValueHash[key] : 'DEFAULT'; } return this.escape(fieldValueHash[key], fieldMappedAttributes[key], { context: 'INSERT' }); @@ -313,14 +303,25 @@ class QueryGenerator { const onConflictDoNothing = options.ignoreDuplicates ? this._dialect.supports.inserts.onConflictDoNothing : ''; let returning = ''; - if (this._dialect.supports.returnValues && Array.isArray(options.returning)) { - const fields = options.returning.map(field => this.quoteIdentifier(field)).join(','); - returning += ` RETURNING ${fields}`; - } else { - returning += this._dialect.supports.returnValues && options.returning ? ' RETURNING *' : ''; - } - - return `INSERT${ignoreDuplicates} INTO ${this.quoteTable(tableName)} (${attributes}) VALUES ${tuples.join(',')}${onDuplicateKeyUpdate}${onConflictDoNothing}${returning};`; + if (this._dialect.supports.returnValues && options.returning) { + const returnValues = this.generateReturnValues(fieldMappedAttributes, options); + + returning += returnValues.returningFragment; + } + + return Utils.joinSQLFragments([ + 'INSERT', + ignoreDuplicates, + 'INTO', + this.quoteTable(tableName), + `(${attributes})`, + 'VALUES', + tuples.join(','), + onDuplicateKeyUpdate, + onConflictDoNothing, + returning, + ';' + ]); } /** @@ -335,7 +336,6 @@ class QueryGenerator { * @private */ updateQuery(tableName, attrValueHash, where, options, attributes) { - // TODO: Mutates argument, should be fixed! options = options || {}; _.defaults(options, this.options); @@ -346,7 +346,6 @@ class QueryGenerator { const modelAttributeMap = {}; let outputFragment = ''; let tmpTable = ''; // tmpTable declaration for trigger - let selectFromTmp = ''; // Select statement for trigger let suffix = ''; if (_.get(this, ['sequelize', 'options', 'dialectOptions', 'prependSearchPath']) || options.searchPath) { @@ -362,39 +361,16 @@ class QueryGenerator { } } - if (this._dialect.supports.returnValues) { - if (this._dialect.supports.returnValues.output) { - // we always need this for mssql - outputFragment = ' OUTPUT INSERTED.*'; - - //To capture output rows when there is a trigger on MSSQL DB - if (attributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) { - let tmpColumns = ''; - let outputColumns = ''; - - for (const modelKey in attributes) { - const attribute = attributes[modelKey]; - if (!(attribute.type instanceof DataTypes.VIRTUAL)) { - if (tmpColumns.length > 0) { - tmpColumns += ','; - outputColumns += ','; - } - - tmpColumns += `${this.quoteIdentifier(attribute.field)} ${attribute.type.toSql()}`; - outputColumns += `INSERTED.${this.quoteIdentifier(attribute.field)}`; - } - } + if (this._dialect.supports.returnValues && options.returning) { + const returnValues = this.generateReturnValues(attributes, options); - tmpTable = `declare @tmp table (${tmpColumns}); `; - outputFragment = ` OUTPUT ${outputColumns} into @tmp`; - selectFromTmp = ';select * from @tmp'; + suffix += returnValues.returningFragment; + tmpTable = returnValues.tmpTable || ''; + outputFragment = returnValues.outputFragment || ''; - suffix += selectFromTmp; - } - } else if (this._dialect.supports.returnValues && options.returning) { - // ensure that the return output is properly mapped to model fields. + // ensure that the return output is properly mapped to model fields. + if (!this._dialect.supports.returnValues.output && options.returning) { options.mapToModel = true; - suffix += ' RETURNING *'; } } @@ -424,10 +400,7 @@ class QueryGenerator { } } - const whereOptions = { - ...options, - bindParam - }; + const whereOptions = { ...options, bindParam }; if (values.length === 0) { return ''; @@ -445,45 +418,54 @@ class QueryGenerator { /** * Returns an update query using arithmetic operator * - * @param {string} operator String with the arithmetic operator (e.g. '+' or '-') - * @param {string} tableName Name of the table - * @param {object} attrValueHash A hash with attribute-value-pairs - * @param {object} where A hash with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {string} operator String with the arithmetic operator (e.g. '+' or '-') + * @param {string} tableName Name of the table + * @param {object} where A plain-object with conditions (e.g. {name: 'foo'}) OR an ID as integer + * @param {object} incrementAmountsByField A plain-object with attribute-value-pairs + * @param {object} extraAttributesToBeUpdated A plain-object with attribute-value-pairs * @param {object} options - * @param {object} attributes + * + * @private */ - arithmeticQuery(operator, tableName, attrValueHash, where, options, attributes = {}) { - options = { - returning: true, - ...options - }; + arithmeticQuery(operator, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { + options = options || {}; + _.defaults(options, { returning: true }); - attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull); + extraAttributesToBeUpdated = Utils.removeNullValuesFromHash(extraAttributesToBeUpdated, this.options.omitNull); - const values = []; let outputFragment = ''; let returningFragment = ''; if (this._dialect.supports.returnValues && options.returning) { - if (this._dialect.supports.returnValues.returning) { - options.mapToModel = true; - returningFragment = 'RETURNING *'; - } else if (this._dialect.supports.returnValues.output) { - outputFragment = ' OUTPUT INSERTED.*'; - } - } - - for (const key in attrValueHash) { - const value = attrValueHash[key]; - values.push(`${this.quoteIdentifier(key)}=${this.quoteIdentifier(key)}${operator} ${this.escape(value)}`); - } - - for (const key in attributes) { - const value = attributes[key]; - values.push(`${this.quoteIdentifier(key)}=${this.escape(value)}`); - } - - return `UPDATE ${this.quoteTable(tableName)} SET ${values.join(',')}${outputFragment} ${this.whereQuery(where)} ${returningFragment}`.trim(); + const returnValues = this.generateReturnValues(null, options); + + outputFragment = returnValues.outputFragment; + returningFragment = returnValues.returningFragment; + } + + const updateSetSqlFragments = []; + for (const field in incrementAmountsByField) { + const incrementAmount = incrementAmountsByField[field]; + const quotedField = this.quoteIdentifier(field); + const escapedAmount = this.escape(incrementAmount); + updateSetSqlFragments.push(`${quotedField}=${quotedField}${operator} ${escapedAmount}`); + } + for (const field in extraAttributesToBeUpdated) { + const newValue = extraAttributesToBeUpdated[field]; + const quotedField = this.quoteIdentifier(field); + const escapedValue = this.escape(newValue); + updateSetSqlFragments.push(`${quotedField}=${escapedValue}`); + } + + return Utils.joinSQLFragments([ + 'UPDATE', + this.quoteTable(tableName), + 'SET', + updateSetSqlFragments.join(','), + outputFragment, + this.whereQuery(where), + returningFragment + ]); } /* @@ -505,7 +487,9 @@ class QueryGenerator { - rawTablename, the name of the table, without schema. Used to create the name of the index @private */ - addIndexQuery(tableName, attributes, options = {}, rawTablename) { + addIndexQuery(tableName, attributes, options, rawTablename) { + options = options || {}; + if (!Array.isArray(attributes)) { options = attributes; attributes = undefined; @@ -520,12 +504,14 @@ class QueryGenerator { } const fieldsSql = options.fields.map(field => { - if (typeof field === 'string') { - return this.quoteIdentifier(field); - } if (field instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(field); } + if (typeof field === 'string') { + field = { + name: field + }; + } let result = ''; if (field.attribute) { @@ -542,6 +528,13 @@ class QueryGenerator { result += ` COLLATE ${this.quoteIdentifier(field.collate)}`; } + if (this._dialect.supports.index.operator) { + const operator = field.operator || options.operator; + if (operator) { + result += ` ${operator}`; + } + } + if (this._dialect.supports.index.length && field.length) { result += `(${field.length})`; } @@ -596,7 +589,7 @@ class QueryGenerator { this._dialect.supports.index.using === 1 && options.using ? `USING ${options.using}` : '', !this._dialect.supports.indexViaAlter ? `ON ${tableName}` : undefined, this._dialect.supports.index.using === 2 && options.using ? `USING ${options.using}` : '', - `(${fieldsSql.join(', ')}${options.operator ? ` ${options.operator}` : ''})`, + `(${fieldsSql.join(', ')})`, this._dialect.supports.index.parser && options.parser ? `WITH PARSER ${options.parser}` : undefined, this._dialect.supports.index.where && options.where ? options.where : undefined ); @@ -604,16 +597,20 @@ class QueryGenerator { return _.compact(ind).join(' '); } - addConstraintQuery(tableName, options = {}) { - const constraintSnippet = this.getConstraintSnippet(tableName, options); - + addConstraintQuery(tableName, options) { if (typeof tableName === 'string') { tableName = this.quoteIdentifiers(tableName); } else { tableName = this.quoteTable(tableName); } - return `ALTER TABLE ${tableName} ADD ${constraintSnippet};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + tableName, + 'ADD', + this.getConstraintSnippet(tableName, options || {}), + ';' + ]); } getConstraintSnippet(tableName, options) { @@ -668,11 +665,15 @@ class QueryGenerator { break; case 'FOREIGN KEY': const references = options.references; - if (!references || !references.table || !references.field) { + if (!references || !references.table || !(references.field || references.fields)) { throw new Error('references object with table and field must be specified'); } constraintName = this.quoteIdentifier(options.name || `${tableName}_${fieldsSqlString}_${references.table}_fk`); - const referencesSnippet = `${this.quoteTable(references.table)} (${this.quoteIdentifier(references.field)})`; + const quotedReferences = + typeof references.field !== 'undefined' + ? this.quoteIdentifier(references.field) + : references.fields.map(f => this.quoteIdentifier(f)).join(', '); + const referencesSnippet = `${this.quoteTable(references.table)} (${quotedReferences})`; constraintSnippet = `CONSTRAINT ${constraintName} `; constraintSnippet += `FOREIGN KEY (${fieldsSqlQuotedString}) REFERENCES ${referencesSnippet}`; if (options.onUpdate) { @@ -684,6 +685,11 @@ class QueryGenerator { break; default: throw new Error(`${options.type} is invalid.`); } + + if (options.deferrable && ['UNIQUE', 'PRIMARY KEY', 'FOREIGN KEY'].includes(options.type.toUpperCase())) { + constraintSnippet += ` ${this.deferConstraintsQuery(options)}`; + } + return constraintSnippet; } @@ -694,7 +700,12 @@ class QueryGenerator { tableName = this.quoteTable(tableName); } - return `ALTER TABLE ${tableName} DROP CONSTRAINT ${this.quoteIdentifiers(constraintName)}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + tableName, + 'DROP CONSTRAINT', + this.quoteIdentifiers(constraintName) + ]); } /* @@ -958,7 +969,9 @@ class QueryGenerator { Escape a value (e.g. a string, number or date) @private */ - escape(value, field, options = {}) { + escape(value, field, options) { + options = options || {}; + if (value !== null && value !== undefined) { if (value instanceof Utils.SequelizeMethod) { return this.handleSequelizeMethod(value); @@ -979,7 +992,6 @@ class QueryGenerator { } } } - return SqlString.escape(value, this.options.timezone, this.dialect); } @@ -994,7 +1006,9 @@ class QueryGenerator { Returns a bind parameter representation of a value (e.g. a string, number or date) @private */ - format(value, field, options = {}, bindParam) { + format(value, field, options, bindParam) { + options = options || {}; + if (value !== null && value !== undefined) { if (value instanceof Utils.SequelizeMethod) { throw new Error('Cannot pass SequelizeMethod as a bind parameter - use escape instead'); @@ -1109,7 +1123,8 @@ class QueryGenerator { - offset -> An offset value to start from. Only useable with limit! @private */ - selectQuery(tableName, options = {}, model) { + selectQuery(tableName, options, model) { + options = options || {}; const limit = options.limit; const mainQueryItems = []; const subQueryItems = []; @@ -1137,6 +1152,7 @@ class QueryGenerator { if (this.options.minifyAliases && !options.aliasesMapping) { options.aliasesMapping = new Map(); options.aliasesByTable = {}; + options.includeAliases = new Map(); } // resolve table name options @@ -1196,7 +1212,7 @@ class QueryGenerator { if (!mainTable.as) { mainTable.as = mainTable.quotedName; } - const where = Object.assign({}, options.where); + const where = { ...options.where }; let groupedLimitOrder, whereKey, include, @@ -1216,9 +1232,10 @@ class QueryGenerator { association: options.groupedLimit.on.manyFromSource, duplicating: false, // The UNION'ed query may contain duplicates, but each sub-query cannot required: true, - where: Object.assign({ - [Op.placeholder]: true - }, options.groupedLimit.through && options.groupedLimit.through.where) + where: { + [Op.placeholder]: true, + ...options.groupedLimit.through && options.groupedLimit.through.where + } }], model }); @@ -1326,9 +1343,9 @@ class QueryGenerator { if (options.group) { options.group = Array.isArray(options.group) ? options.group.map(t => this.aliasGrouping(t, model, mainTable.as, options)).join(', ') : this.aliasGrouping(options.group, model, mainTable.as, options); - if (subQuery) { + if (subQuery && options.group) { subQueryItems.push(` GROUP BY ${options.group}`); - } else { + } else if (options.group) { mainQueryItems.push(` GROUP BY ${options.group}`); } } @@ -1367,6 +1384,7 @@ class QueryGenerator { } if (subQuery) { + this._throwOnEmptyAttributes(attributes.main, { modelName: model && model.name, as: mainTable.as }); query = `SELECT ${attributes.main.join(', ')} FROM (${subQueryItems.join('')}) AS ${mainTable.as}${mainJoinQueries.join('')}${mainQueryItems.join('')}`; } else { query = mainQueryItems.join(''); @@ -1434,7 +1452,7 @@ class QueryGenerator { ? this.quoteAttribute(attr, options.model) : this.escape(attr); } - if (!_.isEmpty(options.include) && !attr.includes('.') && addTable) { + if (!_.isEmpty(options.include) && (!attr.includes('.') || options.dotNotation) && addTable) { attr = `${mainTableAs}.${attr}`; } @@ -1516,7 +1534,11 @@ class QueryGenerator { alias = this._getMinifiedAlias(alias, includeAs.internalAs, topLevelInfo.options); } - return `${prefix} AS ${this.quoteIdentifier(alias, true)}`; + return Utils.joinSQLFragments([ + prefix, + 'AS', + this.quoteIdentifier(alias, true) + ]); }); if (include.subQuery && topLevelInfo.subQuery) { for (const attr of includeAttributes) { @@ -1676,7 +1698,8 @@ class QueryGenerator { joinOn = this._getAliasForField(tableName, attrLeft, topLevelInfo.options) || `${tableName}.${this.quoteIdentifier(attrLeft)}`; if (topLevelInfo.subQuery) { - subqueryAttributes.push(`${tableName}.${this.quoteIdentifier(fieldLeft)}`); + const dbIdentifier = `${tableName}.${this.quoteIdentifier(fieldLeft)}`; + subqueryAttributes.push(dbIdentifier !== joinOn ? `${dbIdentifier} AS ${this.quoteIdentifier(attrLeft)}` : dbIdentifier); } } else { const joinSource = `${asLeft.replace(/->/g, '.')}.${attrLeft}`; @@ -1709,8 +1732,14 @@ class QueryGenerator { } } + if (this.options.minifyAliases && asRight.length > 63) { + const alias = `%${topLevelInfo.options.includeAliases.size}`; + + topLevelInfo.options.includeAliases.set(alias, asRight); + } + return { - join: include.required ? 'INNER JOIN' : 'LEFT OUTER JOIN', + join: include.required ? 'INNER JOIN' : include.right && this._dialect.supports['RIGHT JOIN'] ? 'RIGHT OUTER JOIN' : 'LEFT OUTER JOIN', body: this.quoteTable(tableRight, asRight), condition: joinOn, attributes: { @@ -1720,6 +1749,54 @@ class QueryGenerator { }; } + /** + * Returns the SQL fragments to handle returning the attributes from an insert/update query. + * + * @param {object} modelAttributes An object with the model attributes. + * @param {object} options An object with options. + * + * @private + */ + generateReturnValues(modelAttributes, options) { + const returnFields = []; + const returnTypes = []; + let outputFragment = ''; + let returningFragment = ''; + let tmpTable = ''; + + if (Array.isArray(options.returning)) { + returnFields.push(...options.returning.map(field => this.quoteIdentifier(field))); + } else if (modelAttributes) { + _.each(modelAttributes, attribute => { + if (!(attribute.type instanceof DataTypes.VIRTUAL)) { + returnFields.push(this.quoteIdentifier(attribute.field)); + returnTypes.push(attribute.type); + } + }); + } + + if (_.isEmpty(returnFields)) { + returnFields.push('*'); + } + + if (this._dialect.supports.returnValues.returning) { + returningFragment = ` RETURNING ${returnFields.join(',')}`; + } else if (this._dialect.supports.returnValues.output) { + outputFragment = ` OUTPUT ${returnFields.map(field => `INSERTED.${field}`).join(',')}`; + + //To capture output rows when there is a trigger on MSSQL DB + if (options.hasTrigger && this._dialect.supports.tmpTableTrigger) { + const tmpColumns = returnFields.map((field, i) => `${field} ${returnTypes[i].toSql()}`); + + tmpTable = `DECLARE @tmp TABLE (${tmpColumns.join(',')}); `; + outputFragment += ' INTO @tmp'; + returningFragment = '; SELECT * FROM @tmp'; + } + } + + return { outputFragment, returnFields, returningFragment, tmpTable }; + } + generateThroughJoin(include, includeAs, parentTableName, topLevelInfo) { const through = include.through; const throughTable = through.model.getTableName(); @@ -1732,9 +1809,11 @@ class QueryGenerator { alias = this._getMinifiedAlias(alias, throughAs, topLevelInfo.options); } - return `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) - } AS ${ - this.quoteIdentifier(alias)}`; + return Utils.joinSQLFragments([ + `${this.quoteIdentifier(throughAs)}.${this.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr)}`, + 'AS', + this.quoteIdentifier(alias) + ]); }); const association = include.association; const parentIsTop = !include.parent.association && include.parent.model.name === topLevelInfo.options.model.name; @@ -1744,7 +1823,7 @@ class QueryGenerator { const identTarget = association.foreignIdentifierField; const attrTarget = association.targetKeyField; - const joinType = include.required ? 'INNER JOIN' : 'LEFT OUTER JOIN'; + const joinType = include.required ? 'INNER JOIN' : include.right && this._dialect.supports['RIGHT JOIN'] ? 'RIGHT OUTER JOIN' : 'LEFT OUTER JOIN'; let joinBody; let joinCondition; const attributes = { @@ -1797,22 +1876,13 @@ class QueryGenerator { throughWhere = this.getWhereConditions(through.where, this.sequelize.literal(this.quoteIdentifier(throughAs)), through.model); } - if (this._dialect.supports.joinTableDependent) { - // Generate a wrapped join so that the through table join can be dependent on the target join - joinBody = `( ${this.quoteTable(throughTable, throughAs)} INNER JOIN ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)} ON ${targetJoinOn}`; - if (throughWhere) { - joinBody += ` AND ${throughWhere}`; - } - joinBody += ')'; - joinCondition = sourceJoinOn; - } else { - // Generate join SQL for left side of through - joinBody = `${this.quoteTable(throughTable, throughAs)} ON ${sourceJoinOn} ${joinType} ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)}`; - joinCondition = targetJoinOn; - if (throughWhere) { - joinCondition += ` AND ${throughWhere}`; - } + // Generate a wrapped join so that the through table join can be dependent on the target join + joinBody = `( ${this.quoteTable(throughTable, throughAs)} INNER JOIN ${this.quoteTable(include.model.getTableName(), includeAs.internalAs)} ON ${targetJoinOn}`; + if (throughWhere) { + joinBody += ` AND ${throughWhere}`; } + joinBody += ')'; + joinCondition = sourceJoinOn; if (include.where || include.through.where) { if (include.where) { @@ -1863,7 +1933,7 @@ class QueryGenerator { return; } - nestedIncludes = [Object.assign({}, child, { include: nestedIncludes, attributes: [] })]; + nestedIncludes = [{ ...child, include: nestedIncludes, attributes: [] }]; child = parent; } @@ -1940,7 +2010,7 @@ class QueryGenerator { * are preserved. */ _getRequiredClosure(include) { - const copy = Object.assign({}, include, { attributes: [], include: [] }); + const copy = { ...include, attributes: [], include: [] }; if (Array.isArray(include.include)) { copy.include = include.include @@ -2001,7 +2071,17 @@ class QueryGenerator { return { mainQueryOrder, subQueryOrder }; } + _throwOnEmptyAttributes(attributes, extraInfo = {}) { + if (attributes.length > 0) return; + const asPart = extraInfo.as && `as ${extraInfo.as}` || ''; + const namePart = extraInfo.modelName && `for model '${extraInfo.modelName}'` || ''; + const message = `Attempted a SELECT query ${namePart} ${asPart} without selecting any columns`; + throw new sequelizeError.QueryError(message.replace(/ +/g, ' ')); + } + selectFromTableFragment(options, model, attributes, tables, mainTableAs) { + this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); + let fragment = `SELECT ${attributes.join(', ')} FROM ${tables}`; if (mainTableAs) { @@ -2080,7 +2160,9 @@ class QueryGenerator { model: factory }); } - if (typeof value === 'boolean') { + if ([this.OperatorMap[Op.between], this.OperatorMap[Op.notBetween]].includes(smth.comparator)) { + value = `${this.escape(value[0])} AND ${this.escape(value[1])}`; + } else if (typeof value === 'boolean') { value = this.booleanValue(value); } else { value = this.escape(value); @@ -2112,15 +2194,17 @@ class QueryGenerator { return `CAST(${result} AS ${smth.type.toUpperCase()})`; } if (smth instanceof Utils.Fn) { - return `${smth.fn}(${smth.args.map(arg => { - if (arg instanceof Utils.SequelizeMethod) { - return this.handleSequelizeMethod(arg, tableName, factory, options, prepend); - } - if (_.isPlainObject(arg)) { - return this.whereItemsQuery(arg); - } - return this.escape(arg); - }).join(', ')})`; + return `${smth.fn}(${ + smth.args.map(arg => { + if (arg instanceof Utils.SequelizeMethod) { + return this.handleSequelizeMethod(arg, tableName, factory, options, prepend); + } + if (_.isPlainObject(arg)) { + return this.whereItemsQuery(arg); + } + return this.escape(typeof arg === 'string' ? arg.replace('$', '$$$') : arg); + }).join(', ') + })`; } if (smth instanceof Utils.Col) { if (Array.isArray(smth.col) && !factory) { @@ -2184,7 +2268,7 @@ class QueryGenerator { const tmp = {}; const field = options.model.rawAttributes[keyParts[0]]; _.set(tmp, keyParts.slice(1), value); - return this.whereItemQuery(field.field || keyParts[0], tmp, Object.assign({ field }, options)); + return this.whereItemQuery(field.field || keyParts[0], tmp, { field, ...options }); } } @@ -2193,6 +2277,10 @@ class QueryGenerator { const isPlainObject = _.isPlainObject(value); const isArray = !isPlainObject && Array.isArray(value); + key = this.OperatorsAliasMap && this.OperatorsAliasMap[key] || key; + if (isPlainObject) { + value = this._replaceAliases(value); + } const valueKeys = isPlainObject && Utils.getComplexKeys(value); if (key === undefined) { @@ -2344,7 +2432,7 @@ class QueryGenerator { const where = { [op]: value[op] }; - items.push(this.whereItemQuery(key, where, Object.assign({}, options, { json: false }))); + items.push(this.whereItemQuery(key, where, { ...options, json: false })); }); _.forOwn(value, (item, prop) => { @@ -2508,14 +2596,20 @@ class QueryGenerator { return this._joinKeyValue(key, value.map(identifier => this.quoteIdentifier(identifier)).join('.'), comparator, options.prefix); case Op.startsWith: - comparator = this.OperatorMap[Op.like]; - return this._joinKeyValue(key, this.escape(`${value}%`), comparator, options.prefix); case Op.endsWith: - comparator = this.OperatorMap[Op.like]; - return this._joinKeyValue(key, this.escape(`%${value}`), comparator, options.prefix); case Op.substring: comparator = this.OperatorMap[Op.like]; - return this._joinKeyValue(key, this.escape(`%${value}%`), comparator, options.prefix); + + if (value instanceof Utils.Literal) { + value = value.val; + } + + let pattern = `${value}%`; + + if (prop === Op.endsWith) pattern = `%${value}`; + if (prop === Op.substring) pattern = `%${value}%`; + + return this._joinKeyValue(key, this.escape(pattern), comparator, options.prefix); } const escapeOptions = { @@ -2550,7 +2644,7 @@ class QueryGenerator { Takes something and transforms it into values of a where condition. @private */ - getWhereConditions(smth, tableName, factory, options = {}, prepend = true) { + getWhereConditions(smth, tableName, factory, options, prepend) { const where = {}; if (Array.isArray(tableName)) { @@ -2560,6 +2654,12 @@ class QueryGenerator { } } + options = options || {}; + + if (prepend === undefined) { + prepend = true; + } + if (smth && smth instanceof Utils.SequelizeMethod) { // Checking a property is cheaper than a lot of instanceof calls return this.handleSequelizeMethod(smth, tableName, factory, options, prepend); } diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js index a1968db20f4d..19a1d983b5e5 100644 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ b/lib/dialects/abstract/query-generator/helpers/quote.js @@ -32,10 +32,10 @@ const postgresReservedWords = 'all,analyse,analyze,and,any,array,as,asc,asymmetr * @returns {string} * @private */ -function quoteIdentifier(dialect, identifier, options = {}) { +function quoteIdentifier(dialect, identifier, options) { if (identifier === '*') return identifier; - options = Utils.defaults(options, { + options = Utils.defaults(options || {}, { force: false, quoteIdentifiers: true }); diff --git a/lib/dialects/abstract/query-generator/operators.js b/lib/dialects/abstract/query-generator/operators.js index 98d51a123bcb..91d3caa2bcc0 100644 --- a/lib/dialects/abstract/query-generator/operators.js +++ b/lib/dialects/abstract/query-generator/operators.js @@ -1,6 +1,8 @@ 'use strict'; +const _ = require('lodash'); const Op = require('../../../operators'); +const Utils = require('../../../utils'); const OperatorHelpers = { OperatorMap: { @@ -40,7 +42,43 @@ const OperatorHelpers = { [Op.and]: ' AND ', [Op.or]: ' OR ', [Op.col]: 'COL', - [Op.placeholder]: '$$PLACEHOLDER$$' + [Op.placeholder]: '$$PLACEHOLDER$$', + [Op.match]: '@@' + }, + + OperatorsAliasMap: {}, + + setOperatorsAliases(aliases) { + if (!aliases || _.isEmpty(aliases)) { + this.OperatorsAliasMap = false; + } else { + this.OperatorsAliasMap = { ...aliases }; + } + }, + + _replaceAliases(orig) { + const obj = {}; + if (!this.OperatorsAliasMap) { + return orig; + } + + Utils.getOperators(orig).forEach(op => { + const item = orig[op]; + if (_.isPlainObject(item)) { + obj[op] = this._replaceAliases(item); + } else { + obj[op] = item; + } + }); + + _.forOwn(orig, (item, prop) => { + prop = this.OperatorsAliasMap[prop] || prop; + if (_.isPlainObject(item)) { + item = this._replaceAliases(item); + } + obj[prop] = item; + }); + return obj; } }; diff --git a/lib/dialects/abstract/query-generator/transaction.js b/lib/dialects/abstract/query-generator/transaction.js index 1cda86a6979a..c047008f9696 100644 --- a/lib/dialects/abstract/query-generator/transaction.js +++ b/lib/dialects/abstract/query-generator/transaction.js @@ -1,6 +1,6 @@ 'use strict'; -const uuidv4 = require('uuid/v4'); +const uuidv4 = require('uuid').v4; const TransactionQueries = { /** @@ -16,7 +16,7 @@ const TransactionQueries = { return; } - return `SET SESSION TRANSACTION ISOLATION LEVEL ${value};`; + return `SET TRANSACTION ISOLATION LEVEL ${value};`; }, generateTransactionId() { diff --git a/lib/dialects/abstract/query-interface.js b/lib/dialects/abstract/query-interface.js new file mode 100644 index 000000000000..f77bd92f93f6 --- /dev/null +++ b/lib/dialects/abstract/query-interface.js @@ -0,0 +1,1260 @@ +'use strict'; + +const _ = require('lodash'); + +const Utils = require('../../utils'); +const DataTypes = require('../../data-types'); +const Transaction = require('../../transaction'); +const QueryTypes = require('../../query-types'); + +/** + * The interface that Sequelize uses to talk to all databases + */ +class QueryInterface { + constructor(sequelize, queryGenerator) { + this.sequelize = sequelize; + this.queryGenerator = queryGenerator; + } + + /** + * Create a database + * + * @param {string} database Database name to create + * @param {object} [options] Query options + * @param {string} [options.charset] Database default character set, MYSQL only + * @param {string} [options.collate] Database default collation + * @param {string} [options.encoding] Database default character set, PostgreSQL only + * @param {string} [options.ctype] Database character classification, PostgreSQL only + * @param {string} [options.template] The name of the template from which to create the new database, PostgreSQL only + * + * @returns {Promise} + */ + async createDatabase(database, options) { + options = options || {}; + const sql = this.queryGenerator.createDatabaseQuery(database, options); + return await this.sequelize.query(sql, options); + } + + /** + * Drop a database + * + * @param {string} database Database name to drop + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async dropDatabase(database, options) { + options = options || {}; + const sql = this.queryGenerator.dropDatabaseQuery(database); + return await this.sequelize.query(sql, options); + } + + /** + * Create a schema + * + * @param {string} schema Schema name to create + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async createSchema(schema, options) { + options = options || {}; + const sql = this.queryGenerator.createSchema(schema); + return await this.sequelize.query(sql, options); + } + + /** + * Drop a schema + * + * @param {string} schema Schema name to drop + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async dropSchema(schema, options) { + options = options || {}; + const sql = this.queryGenerator.dropSchema(schema); + return await this.sequelize.query(sql, options); + } + + /** + * Drop all schemas + * + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async dropAllSchemas(options) { + options = options || {}; + + if (!this.queryGenerator._dialect.supports.schemas) { + return this.sequelize.drop(options); + } + const schemas = await this.showAllSchemas(options); + return Promise.all(schemas.map(schemaName => this.dropSchema(schemaName, options))); + } + + /** + * Show all schemas + * + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async showAllSchemas(options) { + options = { + ...options, + raw: true, + type: this.sequelize.QueryTypes.SELECT + }; + + const showSchemasSql = this.queryGenerator.showSchemasQuery(options); + + const schemaNames = await this.sequelize.query(showSchemasSql, options); + + return _.flatten(schemaNames.map(value => value.schema_name ? value.schema_name : value)); + } + + /** + * Return database version + * + * @param {object} [options] Query options + * @param {QueryType} [options.type] Query type + * + * @returns {Promise} + * @private + */ + async databaseVersion(options) { + return await this.sequelize.query( + this.queryGenerator.versionQuery(), + { ...options, type: QueryTypes.VERSION } + ); + } + + /** + * Create a table with given set of attributes + * + * ```js + * queryInterface.createTable( + * 'nameOfTheNewTable', + * { + * id: { + * type: Sequelize.INTEGER, + * primaryKey: true, + * autoIncrement: true + * }, + * createdAt: { + * type: Sequelize.DATE + * }, + * updatedAt: { + * type: Sequelize.DATE + * }, + * attr1: Sequelize.STRING, + * attr2: Sequelize.INTEGER, + * attr3: { + * type: Sequelize.BOOLEAN, + * defaultValue: false, + * allowNull: false + * }, + * //foreign key usage + * attr4: { + * type: Sequelize.INTEGER, + * references: { + * model: 'another_table_name', + * key: 'id' + * }, + * onUpdate: 'cascade', + * onDelete: 'cascade' + * } + * }, + * { + * engine: 'MYISAM', // default: 'InnoDB' + * charset: 'latin1', // default: null + * schema: 'public', // default: public, PostgreSQL only. + * comment: 'my table', // comment for table + * collate: 'latin1_danish_ci' // collation, MYSQL only + * } + * ) + * ``` + * + * @param {string} tableName Name of table to create + * @param {object} attributes Object representing a list of table attributes to create + * @param {object} [options] create table and query options + * @param {Model} [model] model class + * + * @returns {Promise} + */ + async createTable(tableName, attributes, options, model) { + let sql = ''; + + options = { ...options }; + + if (options && options.uniqueKeys) { + _.forOwn(options.uniqueKeys, uniqueKey => { + if (uniqueKey.customIndex === undefined) { + uniqueKey.customIndex = true; + } + }); + } + + if (model) { + options.uniqueKeys = options.uniqueKeys || model.uniqueKeys; + } + + attributes = _.mapValues( + attributes, + attribute => this.sequelize.normalizeAttribute(attribute) + ); + + // Postgres requires special SQL commands for ENUM/ENUM[] + await this.ensureEnums(tableName, attributes, options, model); + + if ( + !tableName.schema && + (options.schema || !!model && model._schema) + ) { + tableName = this.queryGenerator.addSchema({ + tableName, + _schema: !!model && model._schema || options.schema + }); + } + + attributes = this.queryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' }); + sql = this.queryGenerator.createTableQuery(tableName, attributes, options); + + return await this.sequelize.query(sql, options); + } + + /** + * Drop a table from database + * + * @param {string} tableName Table name to drop + * @param {object} options Query options + * + * @returns {Promise} + */ + async dropTable(tableName, options) { + // if we're forcing we should be cascading unless explicitly stated otherwise + options = { ...options }; + options.cascade = options.cascade || options.force || false; + + const sql = this.queryGenerator.dropTableQuery(tableName, options); + + await this.sequelize.query(sql, options); + } + + async _dropAllTables(tableNames, skip, options) { + for (const tableName of tableNames) { + // if tableName is not in the Array of tables names then don't drop it + if (!skip.includes(tableName.tableName || tableName)) { + await this.dropTable(tableName, { ...options, cascade: true } ); + } + } + } + + /** + * Drop all tables from database + * + * @param {object} [options] query options + * @param {Array} [options.skip] List of table to skip + * + * @returns {Promise} + */ + async dropAllTables(options) { + options = options || {}; + const skip = options.skip || []; + + const tableNames = await this.showAllTables(options); + const foreignKeys = await this.getForeignKeysForTables(tableNames, options); + + for (const tableName of tableNames) { + let normalizedTableName = tableName; + if (_.isObject(tableName)) { + normalizedTableName = `${tableName.schema}.${tableName.tableName}`; + } + + for (const foreignKey of foreignKeys[normalizedTableName]) { + await this.sequelize.query(this.queryGenerator.dropForeignKeyQuery(tableName, foreignKey)); + } + } + await this._dropAllTables(tableNames, skip, options); + } + + /** + * Rename a table + * + * @param {string} before Current name of table + * @param {string} after New name from table + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async renameTable(before, after, options) { + options = options || {}; + const sql = this.queryGenerator.renameTableQuery(before, after); + return await this.sequelize.query(sql, options); + } + + /** + * Get all tables in current database + * + * @param {object} [options] Query options + * @param {boolean} [options.raw=true] Run query in raw mode + * @param {QueryType} [options.type=QueryType.SHOWTABLE] query type + * + * @returns {Promise} + * @private + */ + async showAllTables(options) { + options = { + ...options, + raw: true, + type: QueryTypes.SHOWTABLES + }; + + const showTablesSql = this.queryGenerator.showTablesQuery(this.sequelize.config.database); + const tableNames = await this.sequelize.query(showTablesSql, options); + return _.flatten(tableNames); + } + + /** + * Describe a table structure + * + * This method returns an array of hashes containing information about all attributes in the table. + * + * ```js + * { + * name: { + * type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg! + * allowNull: true, + * defaultValue: null + * }, + * isBetaMember: { + * type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg! + * allowNull: false, + * defaultValue: false + * } + * } + * ``` + * + * @param {string} tableName table name + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async describeTable(tableName, options) { + let schema = null; + let schemaDelimiter = null; + + if (typeof options === 'string') { + schema = options; + } else if (typeof options === 'object' && options !== null) { + schema = options.schema || null; + schemaDelimiter = options.schemaDelimiter || null; + } + + if (typeof tableName === 'object' && tableName !== null) { + schema = tableName.schema; + tableName = tableName.tableName; + } + + const sql = this.queryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); + options = { ...options, type: QueryTypes.DESCRIBE }; + + try { + const data = await this.sequelize.query(sql, options); + /* + * If no data is returned from the query, then the table name may be wrong. + * Query generators that use information_schema for retrieving table info will just return an empty result set, + * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql). + */ + if (_.isEmpty(data)) { + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + } + + return data; + } catch (e) { + if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') { + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + } + + throw e; + } + } + + /** + * Add a new column to a table + * + * ```js + * queryInterface.addColumn('tableA', 'columnC', Sequelize.STRING, { + * after: 'columnB' // after option is only supported by MySQL + * }); + * ``` + * + * @param {string} table Table to add column to + * @param {string} key Column name + * @param {object} attribute Attribute definition + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async addColumn(table, key, attribute, options) { + if (!table || !key || !attribute) { + throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); + } + + options = options || {}; + attribute = this.sequelize.normalizeAttribute(attribute); + return await this.sequelize.query(this.queryGenerator.addColumnQuery(table, key, attribute), options); + } + + /** + * Remove a column from a table + * + * @param {string} tableName Table to remove column from + * @param {string} attributeName Column name to remove + * @param {object} [options] Query options + */ + async removeColumn(tableName, attributeName, options) { + return this.sequelize.query(this.queryGenerator.removeColumnQuery(tableName, attributeName), options); + } + + normalizeAttribute(dataTypeOrOptions) { + let attribute; + if (Object.values(DataTypes).includes(dataTypeOrOptions)) { + attribute = { type: dataTypeOrOptions, allowNull: true }; + } else { + attribute = dataTypeOrOptions; + } + + return this.sequelize.normalizeAttribute(attribute); + } + + /** + * Change a column definition + * + * @param {string} tableName Table name to change from + * @param {string} attributeName Column name + * @param {object} dataTypeOrOptions Attribute definition for new column + * @param {object} [options] Query options + */ + async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { + options = options || {}; + + const query = this.queryGenerator.attributesToSQL({ + [attributeName]: this.normalizeAttribute(dataTypeOrOptions) + }, { + context: 'changeColumn', + table: tableName + }); + const sql = this.queryGenerator.changeColumnQuery(tableName, query); + + return this.sequelize.query(sql, options); + } + + /** + * Rejects if the table doesn't have the specified column, otherwise returns the column description. + * + * @param {string} tableName + * @param {string} columnName + * @param {object} options + * @private + */ + async assertTableHasColumn(tableName, columnName, options) { + const description = await this.describeTable(tableName, options); + if (description[columnName]) { + return description; + } + throw new Error(`Table ${tableName} doesn't have the column ${columnName}`); + } + + /** + * Rename a column + * + * @param {string} tableName Table name whose column to rename + * @param {string} attrNameBefore Current column name + * @param {string} attrNameAfter New column name + * @param {object} [options] Query option + * + * @returns {Promise} + */ + async renameColumn(tableName, attrNameBefore, attrNameAfter, options) { + options = options || {}; + const data = (await this.assertTableHasColumn(tableName, attrNameBefore, options))[attrNameBefore]; + + const _options = {}; + + _options[attrNameAfter] = { + attribute: attrNameAfter, + type: data.type, + allowNull: data.allowNull, + defaultValue: data.defaultValue + }; + + // fix: a not-null column cannot have null as default value + if (data.defaultValue === null && !data.allowNull) { + delete _options[attrNameAfter].defaultValue; + } + + const sql = this.queryGenerator.renameColumnQuery( + tableName, + attrNameBefore, + this.queryGenerator.attributesToSQL(_options) + ); + return await this.sequelize.query(sql, options); + } + + /** + * Add an index to a column + * + * @param {string|object} tableName Table name to add index on, can be a object with schema + * @param {Array} [attributes] Use options.fields instead, List of attributes to add index on + * @param {object} options indexes options + * @param {Array} options.fields List of attributes to add index on + * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created + * @param {boolean} [options.unique] Create a unique index + * @param {string} [options.using] Useful for GIN indexes + * @param {string} [options.operator] Index operator + * @param {string} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL + * @param {string} [options.name] Name of the index. Default is
UNIQUECHECKDefault - MSSQL onlyPrimary KeyForeign KeyA syntax for automatically committing or rolling back based on the promise chain resolution is also supportedTo enable CLS, add it do your project, create a namespace and set it on the sequelize constructor:Composite Foreign KeyDefining a Geometry type attributeDefining a Geometry type attribute
__ + * @param {object} [options.where] Where condition on index, for partial indexes + * @param {string} [rawTablename] table name, this is just for backward compatibiity + * + * @returns {Promise} + */ + async addIndex(tableName, attributes, options, rawTablename) { + // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes) + if (!Array.isArray(attributes)) { + rawTablename = options; + options = attributes; + attributes = options.fields; + } + + if (!rawTablename) { + // Map for backwards compat + rawTablename = tableName; + } + + options = Utils.cloneDeep(options); + options.fields = attributes; + const sql = this.queryGenerator.addIndexQuery(tableName, options, rawTablename); + return await this.sequelize.query(sql, { ...options, supportsSearchPath: false }); + } + + /** + * Show indexes on a table + * + * @param {string} tableName table name + * @param {object} [options] Query options + * + * @returns {Promise} + * @private + */ + async showIndex(tableName, options) { + const sql = this.queryGenerator.showIndexesQuery(tableName, options); + return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWINDEXES }); + } + + + /** + * Returns all foreign key constraints of requested tables + * + * @param {string[]} tableNames table names + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async getForeignKeysForTables(tableNames, options) { + if (tableNames.length === 0) { + return {}; + } + + options = { ...options, type: QueryTypes.FOREIGNKEYS }; + + const results = await Promise.all(tableNames.map(tableName => + this.sequelize.query(this.queryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options))); + + const result = {}; + + tableNames.forEach((tableName, i) => { + if (_.isObject(tableName)) { + tableName = `${tableName.schema}.${tableName.tableName}`; + } + + result[tableName] = Array.isArray(results[i]) + ? results[i].map(r => r.constraint_name) + : [results[i] && results[i].constraint_name]; + + result[tableName] = result[tableName].filter(_.identity); + }); + + return result; + } + + /** + * Get foreign key references details for the table + * + * Those details contains constraintSchema, constraintName, constraintCatalog + * tableCatalog, tableSchema, tableName, columnName, + * referencedTableCatalog, referencedTableCatalog, referencedTableSchema, referencedTableName, referencedColumnName. + * Remind: constraint informations won't return if it's sqlite. + * + * @param {string} tableName table name + * @param {object} [options] Query options + */ + async getForeignKeyReferencesForTable(tableName, options) { + const queryOptions = { + ...options, + type: QueryTypes.FOREIGNKEYS + }; + const query = this.queryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database); + return this.sequelize.query(query, queryOptions); + } + + /** + * Remove an already existing index from a table + * + * @param {string} tableName Table name to drop index from + * @param {string|string[]} indexNameOrAttributes Index name or list of attributes that in the index + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async removeIndex(tableName, indexNameOrAttributes, options) { + options = options || {}; + const sql = this.queryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); + return await this.sequelize.query(sql, options); + } + + /** + * Add a constraint to a table + * + * Available constraints: + * - UNIQUE + * - DEFAULT (MSSQL only) + * - CHECK (MySQL - Ignored by the database engine ) + * - FOREIGN KEY + * - PRIMARY KEY + * + * @example + * queryInterface.addConstraint('Users', { + * fields: ['email'], + * type: 'unique', + * name: 'custom_unique_constraint_name' + * }); + * + * @example + * queryInterface.addConstraint('Users', { + * fields: ['roles'], + * type: 'check', + * where: { + * roles: ['user', 'admin', 'moderator', 'guest'] + * } + * }); + * + * @example + * queryInterface.addConstraint('Users', { + * fields: ['roles'], + * type: 'default', + * defaultValue: 'guest' + * }); + * + * @example + * queryInterface.addConstraint('Users', { + * fields: ['username'], + * type: 'primary key', + * name: 'custom_primary_constraint_name' + * }); + * + * @example + * queryInterface.addConstraint('Posts', { + * fields: ['username'], + * type: 'foreign key', + * name: 'custom_fkey_constraint_name', + * references: { //Required field + * table: 'target_table_name', + * field: 'target_column_name' + * }, + * onDelete: 'cascade', + * onUpdate: 'cascade' + * }); + * + * @example + * queryInterface.addConstraint('TableName', { + * fields: ['source_column_name', 'other_source_column_name'], + * type: 'foreign key', + * name: 'custom_fkey_constraint_name', + * references: { //Required field + * table: 'target_table_name', + * fields: ['target_column_name', 'other_target_column_name'] + * }, + * onDelete: 'cascade', + * onUpdate: 'cascade' + * }); + * + * @param {string} tableName Table name where you want to add a constraint + * @param {object} options An object to define the constraint name, type etc + * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) + * @param {Array} options.fields Array of column names to apply the constraint over + * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names + * @param {string} [options.defaultValue] The value for the default constraint + * @param {object} [options.where] Where clause/expression for the CHECK constraint + * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint + * @param {string} [options.references.table] Target table name + * @param {string} [options.references.field] Target column name + * @param {string} [options.references.fields] Target column names for a composite primary key. Must match the order of fields in options.fields. + * @param {string} [options.deferrable] Sets the constraint to be deferred or immediately checked. See Sequelize.Deferrable. PostgreSQL Only + * + * @returns {Promise} + */ + async addConstraint(tableName, options) { + if (!options.fields) { + throw new Error('Fields must be specified through options.fields'); + } + + if (!options.type) { + throw new Error('Constraint type must be specified through options.type'); + } + + options = Utils.cloneDeep(options); + + const sql = this.queryGenerator.addConstraintQuery(tableName, options); + return await this.sequelize.query(sql, options); + } + + async showConstraint(tableName, constraintName, options) { + const sql = this.queryGenerator.showConstraintsQuery(tableName, constraintName); + return await this.sequelize.query(sql, { ...options, type: QueryTypes.SHOWCONSTRAINTS }); + } + + /** + * Remove a constraint from a table + * + * @param {string} tableName Table name to drop constraint from + * @param {string} constraintName Constraint name + * @param {object} options Query options + */ + async removeConstraint(tableName, constraintName, options) { + return this.sequelize.query(this.queryGenerator.removeConstraintQuery(tableName, constraintName), options); + } + + async insert(instance, tableName, values, options) { + options = Utils.cloneDeep(options); + options.hasTrigger = instance && instance.constructor.options.hasTrigger; + const sql = this.queryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options); + + options.type = QueryTypes.INSERT; + options.instance = instance; + + const results = await this.sequelize.query(sql, options); + if (instance) results[0].isNewRecord = false; + + return results; + } + + /** + * Upsert + * + * @param {string} tableName table to upsert on + * @param {object} insertValues values to be inserted, mapped to field name + * @param {object} updateValues values to be updated, mapped to field name + * @param {object} where where conditions, which can be used for UPDATE part when INSERT fails + * @param {object} options query options + * + * @returns {Promise} Resolves an array with + */ + async upsert(tableName, insertValues, updateValues, where, options) { + options = { ...options }; + + const model = options.model; + const primaryKeys = Object.values(model.primaryKeys).map(item => item.field); + const uniqueKeys = Object.values(model.uniqueKeys).filter(c => c.fields.length > 0).map(c => c.fields); + const indexKeys = Object.values(model._indexes).filter(c => c.unique && c.fields.length > 0).map(c => c.fields); + + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + options.upsertKeys = []; + + // For fields in updateValues, try to find a constraint or unique index + // that includes given field. Only first matching upsert key is used. + for (const field of options.updateOnDuplicate) { + const uniqueKey = uniqueKeys.find(fields => fields.includes(field)); + if (uniqueKey) { + options.upsertKeys = uniqueKey; + break; + } + + const indexKey = indexKeys.find(fields => fields.includes(field)); + if (indexKey) { + options.upsertKeys = indexKey; + break; + } + } + + // Always use PK, if no constraint available OR update data contains PK + if ( + options.upsertKeys.length === 0 + || _.intersection(options.updateOnDuplicate, primaryKeys).length + ) { + options.upsertKeys = primaryKeys; + } + + options.upsertKeys = _.uniq(options.upsertKeys); + + const sql = this.queryGenerator.insertQuery(tableName, insertValues, model.rawAttributes, options); + return await this.sequelize.query(sql, options); + } + + /** + * Insert multiple records into a table + * + * @example + * queryInterface.bulkInsert('roles', [{ + * label: 'user', + * createdAt: new Date(), + * updatedAt: new Date() + * }, { + * label: 'admin', + * createdAt: new Date(), + * updatedAt: new Date() + * }]); + * + * @param {string} tableName Table name to insert record to + * @param {Array} records List of records to insert + * @param {object} options Various options, please see Model.bulkCreate options + * @param {object} attributes Various attributes mapped by field name + * + * @returns {Promise} + */ + async bulkInsert(tableName, records, options, attributes) { + options = { ...options }; + options.type = QueryTypes.INSERT; + + const results = await this.sequelize.query( + this.queryGenerator.bulkInsertQuery(tableName, records, options, attributes), + options + ); + + return results[0]; + } + + async update(instance, tableName, values, identifier, options) { + options = { ...options }; + options.hasTrigger = instance && instance.constructor.options.hasTrigger; + + const sql = this.queryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); + + options.type = QueryTypes.UPDATE; + + options.instance = instance; + return await this.sequelize.query(sql, options); + } + + /** + * Update multiple records of a table + * + * @example + * queryInterface.bulkUpdate('roles', { + * label: 'admin', + * }, { + * userType: 3, + * }, + * ); + * + * @param {string} tableName Table name to update + * @param {object} values Values to be inserted, mapped to field name + * @param {object} identifier A hash with conditions OR an ID as integer OR a string with conditions + * @param {object} [options] Various options, please see Model.bulkCreate options + * @param {object} [attributes] Attributes on return objects if supported by SQL dialect + * + * @returns {Promise} + */ + async bulkUpdate(tableName, values, identifier, options, attributes) { + options = Utils.cloneDeep(options); + if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier); + + const sql = this.queryGenerator.updateQuery(tableName, values, identifier, options, attributes); + const table = _.isObject(tableName) ? tableName : { tableName }; + const model = _.find(this.sequelize.modelManager.models, { tableName: table.tableName }); + + options.type = QueryTypes.BULKUPDATE; + options.model = model; + return await this.sequelize.query(sql, options); + } + + async delete(instance, tableName, identifier, options) { + const cascades = []; + const sql = this.queryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); + + options = { ...options }; + + // Check for a restrict field + if (!!instance.constructor && !!instance.constructor.associations) { + const keys = Object.keys(instance.constructor.associations); + const length = keys.length; + let association; + + for (let i = 0; i < length; i++) { + association = instance.constructor.associations[keys[i]]; + if (association.options && association.options.onDelete && + association.options.onDelete.toLowerCase() === 'cascade' && + association.options.useHooks === true) { + cascades.push(association.accessors.get); + } + } + } + + for (const cascade of cascades) { + let instances = await instance[cascade](options); + // Check for hasOne relationship with non-existing associate ("has zero") + if (!instances) continue; + if (!Array.isArray(instances)) instances = [instances]; + for (const _instance of instances) await _instance.destroy(options); + } + options.instance = instance; + return await this.sequelize.query(sql, options); + } + + /** + * Delete multiple records from a table + * + * @param {string} tableName table name from where to delete records + * @param {object} where where conditions to find records to delete + * @param {object} [options] options + * @param {boolean} [options.truncate] Use truncate table command + * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. + * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table. + * @param {Model} [model] Model + * + * @returns {Promise} + */ + async bulkDelete(tableName, where, options, model) { + options = Utils.cloneDeep(options); + options = _.defaults(options, { limit: null }); + + if (options.truncate === true) { + return this.sequelize.query( + this.queryGenerator.truncateTableQuery(tableName, options), + options + ); + } + + if (typeof identifier === 'object') where = Utils.cloneDeep(where); + + return await this.sequelize.query( + this.queryGenerator.deleteQuery(tableName, where, options, model), + options + ); + } + + async select(model, tableName, optionsArg) { + const options = { ...optionsArg, type: QueryTypes.SELECT, model }; + + return await this.sequelize.query( + this.queryGenerator.selectQuery(tableName, options, model), + options + ); + } + + async increment(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { + options = Utils.cloneDeep(options); + + const sql = this.queryGenerator.arithmeticQuery('+', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); + + options.type = QueryTypes.UPDATE; + options.model = model; + + return await this.sequelize.query(sql, options); + } + + async decrement(model, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options) { + options = Utils.cloneDeep(options); + + const sql = this.queryGenerator.arithmeticQuery('-', tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options); + + options.type = QueryTypes.UPDATE; + options.model = model; + + return await this.sequelize.query(sql, options); + } + + async rawSelect(tableName, options, attributeSelector, Model) { + options = Utils.cloneDeep(options); + options = _.defaults(options, { + raw: true, + plain: true, + type: QueryTypes.SELECT + }); + + const sql = this.queryGenerator.selectQuery(tableName, options, Model); + + if (attributeSelector === undefined) { + throw new Error('Please pass an attribute selector!'); + } + + const data = await this.sequelize.query(sql, options); + if (!options.plain) { + return data; + } + + const result = data ? data[attributeSelector] : null; + + if (!options || !options.dataType) { + return result; + } + + const dataType = options.dataType; + + if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) { + if (result !== null) { + return parseFloat(result); + } + } + if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) { + if (result !== null) { + return parseInt(result, 10); + } + } + if (dataType instanceof DataTypes.DATE) { + if (result !== null && !(result instanceof Date)) { + return new Date(result); + } + } + return result; + } + + async createTrigger( + tableName, + triggerName, + timingType, + fireOnArray, + functionName, + functionParams, + optionsArray, + options + ) { + const sql = this.queryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); + options = options || {}; + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + async dropTrigger(tableName, triggerName, options) { + const sql = this.queryGenerator.dropTrigger(tableName, triggerName); + options = options || {}; + + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + async renameTrigger(tableName, oldTriggerName, newTriggerName, options) { + const sql = this.queryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); + options = options || {}; + + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + /** + * Create an SQL function + * + * @example + * queryInterface.createFunction( + * 'someFunction', + * [ + * {type: 'integer', name: 'param', direction: 'IN'} + * ], + * 'integer', + * 'plpgsql', + * 'RETURN param + 1;', + * [ + * 'IMMUTABLE', + * 'LEAKPROOF' + * ], + * { + * variables: + * [ + * {type: 'integer', name: 'myVar', default: 100} + * ], + * force: true + * }; + * ); + * + * @param {string} functionName Name of SQL function to create + * @param {Array} params List of parameters declared for SQL function + * @param {string} returnType SQL type of function returned value + * @param {string} language The name of the language that the function is implemented in + * @param {string} body Source code of function + * @param {Array} optionsArray Extra-options for creation + * @param {object} [options] query options + * @param {boolean} options.force If force is true, any existing functions with the same parameters will be replaced. For postgres, this means using `CREATE OR REPLACE FUNCTION` instead of `CREATE FUNCTION`. Default is false + * @param {Array} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well. + * + * @returns {Promise} + */ + async createFunction(functionName, params, returnType, language, body, optionsArray, options) { + const sql = this.queryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options); + options = options || {}; + + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + /** + * Drop an SQL function + * + * @example + * queryInterface.dropFunction( + * 'someFunction', + * [ + * {type: 'varchar', name: 'param1', direction: 'IN'}, + * {type: 'integer', name: 'param2', direction: 'INOUT'} + * ] + * ); + * + * @param {string} functionName Name of SQL function to drop + * @param {Array} params List of parameters declared for SQL function + * @param {object} [options] query options + * + * @returns {Promise} + */ + async dropFunction(functionName, params, options) { + const sql = this.queryGenerator.dropFunction(functionName, params); + options = options || {}; + + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + /** + * Rename an SQL function + * + * @example + * queryInterface.renameFunction( + * 'fooFunction', + * [ + * {type: 'varchar', name: 'param1', direction: 'IN'}, + * {type: 'integer', name: 'param2', direction: 'INOUT'} + * ], + * 'barFunction' + * ); + * + * @param {string} oldFunctionName Current name of function + * @param {Array} params List of parameters declared for SQL function + * @param {string} newFunctionName New name of function + * @param {object} [options] query options + * + * @returns {Promise} + */ + async renameFunction(oldFunctionName, params, newFunctionName, options) { + const sql = this.queryGenerator.renameFunction(oldFunctionName, params, newFunctionName); + options = options || {}; + + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + // Helper methods useful for querying + + /** + * @private + */ + ensureEnums() { + // noop by default + } + + async setIsolationLevel(transaction, value, options) { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to set isolation level for a transaction without transaction object!'); + } + + if (transaction.parent || !value) { + // Not possible to set a separate isolation level for savepoints + return; + } + + options = { ...options, transaction: transaction.parent || transaction }; + + const sql = this.queryGenerator.setIsolationLevelQuery(value, { + parent: transaction.parent + }); + + if (!sql) return; + + return await this.sequelize.query(sql, options); + } + + async startTransaction(transaction, options) { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to start a transaction without transaction object!'); + } + + options = { ...options, transaction: transaction.parent || transaction }; + options.transaction.name = transaction.parent ? transaction.name : undefined; + const sql = this.queryGenerator.startTransactionQuery(transaction); + + return await this.sequelize.query(sql, options); + } + + async deferConstraints(transaction, options) { + options = { ...options, transaction: transaction.parent || transaction }; + + const sql = this.queryGenerator.deferConstraintsQuery(options); + + if (sql) { + return await this.sequelize.query(sql, options); + } + } + + async commitTransaction(transaction, options) { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to commit a transaction without transaction object!'); + } + if (transaction.parent) { + // Savepoints cannot be committed + return; + } + + options = { + ...options, + transaction: transaction.parent || transaction, + supportsSearchPath: false, + completesTransaction: true + }; + + const sql = this.queryGenerator.commitTransactionQuery(transaction); + const promise = this.sequelize.query(sql, options); + + transaction.finished = 'commit'; + + return await promise; + } + + async rollbackTransaction(transaction, options) { + if (!transaction || !(transaction instanceof Transaction)) { + throw new Error('Unable to rollback a transaction without transaction object!'); + } + + options = { + ...options, + transaction: transaction.parent || transaction, + supportsSearchPath: false, + completesTransaction: true + }; + options.transaction.name = transaction.parent ? transaction.name : undefined; + const sql = this.queryGenerator.rollbackTransactionQuery(transaction); + const promise = this.sequelize.query(sql, options); + + transaction.finished = 'rollback'; + + return await promise; + } +} + +exports.QueryInterface = QueryInterface; diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js old mode 100755 new mode 100644 index 1d13b41dd999..7e640b83fee4 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -5,7 +5,7 @@ const SqlString = require('../../sql-string'); const QueryTypes = require('../../query-types'); const Dot = require('dottie'); const deprecations = require('../../utils/deprecations'); -const uuid = require('uuid/v4'); +const uuid = require('uuid').v4; class AbstractQuery { @@ -15,12 +15,13 @@ class AbstractQuery { this.instance = options.instance; this.model = options.model; this.sequelize = sequelize; - this.options = Object.assign({ + this.options = { plain: false, raw: false, // eslint-disable-next-line no-console - logging: console.log - }, options); + logging: console.log, + ...options + }; this.checkLoggingOption(); } @@ -44,11 +45,12 @@ class AbstractQuery { * @param {object} [options] * @private */ - static formatBindParameters(sql, values, dialect, replacementFunc, options = {}) { + static formatBindParameters(sql, values, dialect, replacementFunc, options) { if (!values) { return [sql, []]; } + options = options || {}; if (typeof replacementFunc !== 'function') { options = replacementFunc || {}; replacementFunc = undefined; @@ -82,8 +84,7 @@ class AbstractQuery { const timeZone = null; const list = Array.isArray(values); - - sql = sql.replace(/\$(\$|\w+)/g, (match, key) => { + sql = sql.replace(/\B\$(\$|\w+)/g, (match, key) => { if ('$' === key) { return options.skipUnescape ? match : key; } diff --git a/lib/dialects/mariadb/connection-manager.js b/lib/dialects/mariadb/connection-manager.js index e2cb44098b54..2c163be59005 100644 --- a/lib/dialects/mariadb/connection-manager.js +++ b/lib/dialects/mariadb/connection-manager.js @@ -1,12 +1,13 @@ 'use strict'; +const semver = require('semver'); const AbstractConnectionManager = require('../abstract/connection-manager'); const SequelizeErrors = require('../../errors'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const DataTypes = require('../../data-types').mariadb; const momentTz = require('moment-timezone'); const debug = logger.debugContext('connection:mariadb'); +const parserStore = require('../parserStore')('mariadb'); /** * MariaDB Connection Manager @@ -15,11 +16,8 @@ const debug = logger.debugContext('connection:mariadb'); * AbstractConnectionManager pooling use it to handle MariaDB specific connections * Use https://github.com/MariaDB/mariadb-connector-nodejs to connect with MariaDB server * - * @extends AbstractConnectionManager - * @returns Class * @private */ - class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { sequelize.config.port = sequelize.config.port || 3306; @@ -28,6 +26,21 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(DataTypes); } + static _typecast(field, next) { + if (parserStore.get(field.type)) { + return parserStore.get(field.type)(field, this.sequelize.options, next); + } + return next(); + } + + _refreshTypeParser(dataType) { + parserStore.refresh(dataType); + } + + _clearTypeParser() { + parserStore.clear(); + } + /** * Connect with MariaDB database based on config, Handle any errors in connection * Set the pool handlers on connection.error @@ -37,7 +50,7 @@ class ConnectionManager extends AbstractConnectionManager { * @returns {Promise} * @private */ - connect(config) { + async connect(config) { // Named timezone is not supported in mariadb, convert to offset let tzOffset = this.sequelize.options.timezone; tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') @@ -50,21 +63,13 @@ class ConnectionManager extends AbstractConnectionManager { password: config.password, database: config.database, timezone: tzOffset, - typeCast: (field, next) => { - if (this.parserStore.get(field.type)) { - return this.parserStore.get(field.type)(field, this.sequelize.options, next); - } - return next(); - }, + typeCast: ConnectionManager._typecast.bind(this), bigNumberStrings: false, supportBigNumbers: true, - foundRows: false + foundRows: false, + ...config.dialectOptions }; - if (config.dialectOptions) { - Object.assign(connectionConfig, config.dialectOptions); - } - if (!this.sequelize.config.keepDefaultTimezone) { // set timezone for this connection if (connectionConfig.initSql) { @@ -78,50 +83,49 @@ class ConnectionManager extends AbstractConnectionManager { } } - return this.lib.createConnection(connectionConfig) - .then(connection => { - this.sequelize.options.databaseVersion = connection.serverVersion(); - debug('connection acquired'); - connection.on('error', error => { - switch (error.code) { - case 'ESOCKET': - case 'ECONNRESET': - case 'EPIPE': - case 'PROTOCOL_CONNECTION_LOST': - this.pool.destroy(connection); - } - }); - return connection; - }) - .catch(err => { - switch (err.code) { - case 'ECONNREFUSED': - throw new SequelizeErrors.ConnectionRefusedError(err); - case 'ER_ACCESS_DENIED_ERROR': - case 'ER_ACCESS_DENIED_NO_PASSWORD_ERROR': - throw new SequelizeErrors.AccessDeniedError(err); - case 'ENOTFOUND': - throw new SequelizeErrors.HostNotFoundError(err); - case 'EHOSTUNREACH': - case 'ENETUNREACH': - case 'EADDRNOTAVAIL': - throw new SequelizeErrors.HostNotReachableError(err); - case 'EINVAL': - throw new SequelizeErrors.InvalidConnectionError(err); - default: - throw new SequelizeErrors.ConnectionError(err); + try { + const connection = await this.lib.createConnection(connectionConfig); + this.sequelize.options.databaseVersion = semver.coerce(connection.serverVersion()).version; + + debug('connection acquired'); + connection.on('error', error => { + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + case 'EPIPE': + case 'PROTOCOL_CONNECTION_LOST': + this.pool.destroy(connection); } }); + return connection; + } catch (err) { + switch (err.code) { + case 'ECONNREFUSED': + throw new SequelizeErrors.ConnectionRefusedError(err); + case 'ER_ACCESS_DENIED_ERROR': + case 'ER_ACCESS_DENIED_NO_PASSWORD_ERROR': + throw new SequelizeErrors.AccessDeniedError(err); + case 'ENOTFOUND': + throw new SequelizeErrors.HostNotFoundError(err); + case 'EHOSTUNREACH': + case 'ENETUNREACH': + case 'EADDRNOTAVAIL': + throw new SequelizeErrors.HostNotReachableError(err); + case 'EINVAL': + throw new SequelizeErrors.InvalidConnectionError(err); + default: + throw new SequelizeErrors.ConnectionError(err); + } + } } - disconnect(connection) { + async disconnect(connection) { // Don't disconnect connections with CLOSED state if (!connection.isValid()) { debug('connection tried to disconnect but was already at CLOSED state'); - return Promise.resolve(); + return; } - //wrap native Promise into bluebird - return Promise.resolve(connection.end()); + return await connection.end(); } validate(connection) { diff --git a/lib/dialects/mariadb/data-types.js b/lib/dialects/mariadb/data-types.js index 5979fa2841a9..aa4098535631 100644 --- a/lib/dialects/mariadb/data-types.js +++ b/lib/dialects/mariadb/data-types.js @@ -1,5 +1,6 @@ 'use strict'; +const wkx = require('wkx'); const _ = require('lodash'); const moment = require('moment-timezone'); @@ -8,6 +9,7 @@ module.exports = BaseTypes => { /** * types: [buffer_type, ...] + * * @see documentation : https://mariadb.com/kb/en/library/resultset/#field-types * @see connector implementation : https://github.com/MariaDB/mariadb-connector-nodejs/blob/master/lib/const/field-type.js */ @@ -49,7 +51,7 @@ module.exports = BaseTypes => { class DATE extends BaseTypes.DATE { toSql() { - return `DATETIME${this._length ? `(${this._length})` : ''}`; + return this._length ? `DATETIME(${this._length})` : 'DATETIME'; } _stringify(date, options) { date = this._applyTimezone(date, options); @@ -92,6 +94,17 @@ module.exports = BaseTypes => { this.sqlType = this.type; } } + static parse(value) { + value = value.buffer(); + // Empty buffer, MySQL doesn't support POINT EMPTY + // check, https://dev.mysql.com/worklog/task/?id=2381 + if (!value || value.length === 0) { + return null; + } + // For some reason, discard the first 4 bytes + value = value.slice(4); + return wkx.Geometry.parse(value).toGeoJSON({ shortCrs: true }); + } toSql() { return this.sqlType; } diff --git a/lib/dialects/mariadb/index.js b/lib/dialects/mariadb/index.js index f6e12dd7eda1..e1ff8dfb3224 100644 --- a/lib/dialects/mariadb/index.js +++ b/lib/dialects/mariadb/index.js @@ -5,6 +5,7 @@ const AbstractDialect = require('../abstract'); const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); +const { MySQLQueryInterface } = require('../mysql/query-interface'); const DataTypes = require('../../data-types').mariadb; class MariadbDialect extends AbstractDialect { @@ -12,19 +13,25 @@ class MariadbDialect extends AbstractDialect { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new MySQLQueryInterface( + sequelize, + this.queryGenerator + ); } } MariadbDialect.prototype.supports = _.merge( - _.cloneDeep(AbstractDialect.prototype.supports), { + _.cloneDeep(AbstractDialect.prototype.supports), + { 'VALUES ()': true, 'LIMIT ON UPDATE': true, lock: true, forShare: 'LOCK IN SHARE MODE', + settingIsolationLevelDuringTransaction: false, schemas: true, inserts: { ignoreDuplicates: ' IGNORE', @@ -42,13 +49,15 @@ MariadbDialect.prototype.supports = _.merge( check: false }, indexViaAlter: true, + indexHints: true, NUMERIC: true, GEOMETRY: true, JSON: true, REGEXP: true - }); + } +); -ConnectionManager.prototype.defaultVersion = '5.5.3'; +MariadbDialect.prototype.defaultVersion = '10.1.44'; // minimum supported version MariadbDialect.prototype.Query = Query; MariadbDialect.prototype.QueryGenerator = QueryGenerator; MariadbDialect.prototype.DataTypes = DataTypes; diff --git a/lib/dialects/mariadb/query-generator.js b/lib/dialects/mariadb/query-generator.js index d0c0ea5b1076..2d2048dc59ec 100644 --- a/lib/dialects/mariadb/query-generator.js +++ b/lib/dialects/mariadb/query-generator.js @@ -1,6 +1,7 @@ 'use strict'; const MySQLQueryGenerator = require('../mysql/query-generator'); +const Utils = require('./../../utils'); class MariaDBQueryGenerator extends MySQLQueryGenerator { createSchema(schema, options) { @@ -10,10 +11,13 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator { ...options }; - const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; - const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; - - return `CREATE SCHEMA IF NOT EXISTS ${this.quoteIdentifier(schema)}${charset}${collate};`; + return Utils.joinSQLFragments([ + 'CREATE SCHEMA IF NOT EXISTS', + this.quoteIdentifier(schema), + options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`, + options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`, + ';' + ]); } dropSchema(schema) { @@ -21,8 +25,22 @@ class MariaDBQueryGenerator extends MySQLQueryGenerator { } showSchemasQuery(options) { - const skip = options.skip && Array.isArray(options.skip) && options.skip.length > 0 ? options.skip : null; - return `SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA'${skip ? skip.reduce( (sql, schemaName) => sql += `,${this.escape(schemaName)}`, '') : ''});`; + const schemasToSkip = [ + '\'MYSQL\'', + '\'INFORMATION_SCHEMA\'', + '\'PERFORMANCE_SCHEMA\'' + ]; + if (options.skip && Array.isArray(options.skip) && options.skip.length > 0) { + for (const schemaName of options.skip) { + schemasToSkip.push(this.escape(schemaName)); + } + } + return Utils.joinSQLFragments([ + 'SELECT SCHEMA_NAME as schema_name', + 'FROM INFORMATION_SCHEMA.SCHEMATA', + `WHERE SCHEMA_NAME NOT IN (${schemasToSkip.join(', ')})`, + ';' + ]); } showTablesQuery(database) { diff --git a/lib/dialects/mariadb/query.js b/lib/dialects/mariadb/query.js index 331da7ee8170..12d8f9c9d5b3 100644 --- a/lib/dialects/mariadb/query.js +++ b/lib/dialects/mariadb/query.js @@ -4,10 +4,10 @@ const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); const _ = require('lodash'); const DataTypes = require('../../data-types'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const ER_DUP_ENTRY = 1062; +const ER_DEADLOCK = 1213; const ER_ROW_IS_REFERENCED = 1451; const ER_NO_REFERENCED_ROW = 1452; @@ -15,68 +15,64 @@ const debug = logger.debugContext('sql:mariadb'); class Query extends AbstractQuery { constructor(connection, sequelize, options) { - super(connection, sequelize, Object.assign({ showWarnings: false }, options)); + super(connection, sequelize, { showWarnings: false, ...options }); } static formatBindParameters(sql, values, dialect) { const bindParam = []; - const replacementFunc = (match, key, val) => { - if (val[key] !== undefined) { - bindParam.push(val[key]); + const replacementFunc = (match, key, values_) => { + if (values_[key] !== undefined) { + bindParam.push(values_[key]); return '?'; } return undefined; }; - sql = AbstractQuery.formatBindParameters(sql, values, dialect, - replacementFunc)[0]; + sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0]; return [sql, bindParam.length > 0 ? bindParam : undefined]; } - run(sql, parameters) { + async run(sql, parameters) { this.sql = sql; const { connection, options } = this; - const showWarnings = this.sequelize.options.showWarnings - || options.showWarnings; + const showWarnings = this.sequelize.options.showWarnings || options.showWarnings; const complete = this._logQuery(sql, debug, parameters); if (parameters) { debug('parameters(%j)', parameters); } - return Promise.resolve( - connection.query(this.sql, parameters) - .then(results => { - complete(); - - // Log warnings if we've got them. - if (showWarnings && results && results.warningStatus > 0) { - return this.logWarnings(results); - } - return results; - }) - .catch(err => { - // MariaDB automatically rolls-back transactions in the event of a deadlock - if (options.transaction && err.errno === 1213) { - options.transaction.finished = 'rollback'; - } - complete(); - - err.sql = sql; - err.parameters = parameters; - throw this.formatError(err); - }) - ) - // Log warnings if we've got them. - .then(results => { - if (showWarnings && results && results.warningStatus > 0) { - return this.logWarnings(results); + let results; + const errForStack = new Error(); + + try { + results = await connection.query(this.sql, parameters); + } catch (error) { + if (options.transaction && error.errno === ER_DEADLOCK) { + // MariaDB automatically rolls-back transactions in the event of a deadlock. + // However, we still initiate a manual rollback to ensure the connection gets released - see #13102. + try { + await options.transaction.rollback(); + } catch (error_) { + // Ignore errors - since MariaDB automatically rolled back, we're + // not that worried about this redundant rollback failing. } - return results; - }) - // Return formatted results... - .then(results => this.formatResults(results)); + + options.transaction.finished = 'rollback'; + } + + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error, errForStack.stack); + } finally { + complete(); + } + + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + return this.formatResults(results); } /** @@ -99,22 +95,25 @@ class Query extends AbstractQuery { formatResults(data) { let result = this.instance; - if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() - || this.isUpsertQuery()) { + if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery()) { return data.affectedRows; } + if (this.isUpsertQuery()) { + return [result, data.affectedRows === 1]; + } if (this.isInsertQuery(data)) { this.handleInsertQuery(data); if (!this.instance) { // handle bulkCreate AI primary key - if (this.model + if ( + this.model && this.model.autoIncrementAttribute && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute && this.model.rawAttributes[this.model.primaryKeyAttribute] ) { - //ONLY TRUE IF @auto_increment_increment is set to 1 !! - //Doesn't work with GALERA => each node will reserve increment (x for first server, x+1 for next node ... + // ONLY TRUE IF @auto_increment_increment is set to 1 !! + // Doesn't work with GALERA => each node will reserve increment (x for first server, x+1 for next node...) const startId = data[this.getInsertIdField()]; result = new Array(data.affectedRows); const pkField = this.model.rawAttributes[this.model.primaryKeyAttribute].field; @@ -123,6 +122,7 @@ class Query extends AbstractQuery { } return [result, data.affectedRows]; } + return [data[this.getInsertIdField()], data.affectedRows]; } } @@ -182,10 +182,11 @@ class Query extends AbstractQuery { for (const _field of Object.keys(this.model.fieldRawAttributesMap)) { const modelField = this.model.fieldRawAttributesMap[_field]; if (modelField.type instanceof DataTypes.JSON) { - //value is return as String, no JSON + // Value is returned as String, not JSON rows = rows.map(row => { - row[modelField.fieldName] = row[modelField.fieldName] ? JSON.parse( - row[modelField.fieldName]) : null; + if (row[modelField.fieldName] && typeof row[modelField.fieldName] === 'string') { + row[modelField.fieldName] = JSON.parse(row[modelField.fieldName]); + } if (DataTypes.JSON.parse) { return DataTypes.JSON.parse(modelField, this.sequelize.options, row[modelField.fieldName]); @@ -196,35 +197,31 @@ class Query extends AbstractQuery { } } - logWarnings(results) { - return this.run('SHOW WARNINGS').then(warningResults => { - const warningMessage = `MariaDB Warnings (${this.connection.uuid - || 'default'}): `; - const messages = []; - for (const _warningRow of warningResults) { - if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] - !== 'function') { - continue; - } - for (const _warningResult of _warningRow) { - if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { - messages.push(_warningResult.Message); - } else { - for (const _objectKey of _warningResult.keys()) { - messages.push( - [_objectKey, _warningResult[_objectKey]].join(': ')); - } + async logWarnings(results) { + const warningResults = await this.run('SHOW WARNINGS'); + const warningMessage = `MariaDB Warnings (${this.connection.uuid || 'default'}): `; + const messages = []; + for (const _warningRow of warningResults) { + if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') { + continue; + } + for (const _warningResult of _warningRow) { + if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { + messages.push(_warningResult.Message); + } else { + for (const _objectKey of _warningResult.keys()) { + messages.push([_objectKey, _warningResult[_objectKey]].join(': ')); } } } + } - this.sequelize.log(warningMessage + messages.join('; '), this.options); + this.sequelize.log(warningMessage + messages.join('; '), this.options); - return results; - }); + return results; } - formatError(err) { + formatError(err, errStack) { switch (err.errno) { case ER_DUP_ENTRY: { const match = err.message.match( @@ -238,9 +235,7 @@ class Query extends AbstractQuery { const uniqueKey = this.model && this.model.uniqueKeys[fieldKey]; if (uniqueKey) { - if (uniqueKey.msg) { - message = uniqueKey.msg; - } + if (uniqueKey.msg) message = uniqueKey.msg; fields = _.zipObject(uniqueKey.fields, values); } else { fields[fieldKey] = fieldVal; @@ -258,31 +253,31 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError( - { message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } case ER_ROW_IS_REFERENCED: case ER_NO_REFERENCED_ROW: { // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) const match = err.message.match( - /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/); + /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/ + ); const quoteChar = match ? match[1] : '`'; - const fields = match ? match[3].split( - new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; + const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; + return new sequelizeErrors.ForeignKeyConstraintError({ - reltype: err.errno === 1451 ? 'parent' : 'child', + reltype: err.errno === ER_ROW_IS_REFERENCED ? 'parent' : 'child', table: match ? match[4] : undefined, fields, - value: fields && fields.length && this.instance - && this.instance[fields[0]] || undefined, + value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined, index: match ? match[2] : undefined, - parent: err + parent: err, + stack: errStack }); } default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/dialects/mssql/async-queue.js b/lib/dialects/mssql/async-queue.js new file mode 100644 index 000000000000..adebefc08a8d --- /dev/null +++ b/lib/dialects/mssql/async-queue.js @@ -0,0 +1,46 @@ +'use strict'; + +const BaseError = require('../../errors/base-error'); +const ConnectionError = require('../../errors/connection-error'); + +/** + * Thrown when a connection to a database is closed while an operation is in progress + */ +class AsyncQueueError extends BaseError { + constructor(message) { + super(message); + this.name = 'SequelizeAsyncQueueError'; + } +} + +exports.AsyncQueueError = AsyncQueueError; + +class AsyncQueue { + constructor() { + this.previous = Promise.resolve(); + this.closed = false; + this.rejectCurrent = () => {}; + } + close() { + this.closed = true; + this.rejectCurrent(new ConnectionError(new AsyncQueueError('the connection was closed before this query could finish executing'))); + } + enqueue(asyncFunction) { + // This outer promise might seems superflous since down below we return asyncFunction().then(resolve, reject). + // However, this ensures that this.previous will never be a rejected promise so the queue will + // always keep going, while still communicating rejection from asyncFunction to the user. + return new Promise((resolve, reject) => { + this.previous = this.previous.then( + () => { + this.rejectCurrent = reject; + if (this.closed) { + return reject(new ConnectionError(new AsyncQueueError('the connection was closed before this query could be executed'))); + } + return asyncFunction().then(resolve, reject); + } + ); + }); + } +} + +exports.default = AsyncQueue; diff --git a/lib/dialects/mssql/connection-manager.js b/lib/dialects/mssql/connection-manager.js index bbdeb689bdf9..b5de1900d64c 100644 --- a/lib/dialects/mssql/connection-manager.js +++ b/lib/dialects/mssql/connection-manager.js @@ -1,11 +1,11 @@ 'use strict'; const AbstractConnectionManager = require('../abstract/connection-manager'); -const ResourceLock = require('./resource-lock'); -const Promise = require('../../promise'); +const AsyncQueue = require('./async-queue').default; const { logger } = require('../../utils/logger'); const sequelizeErrors = require('../../errors'); const DataTypes = require('../../data-types').mssql; +const parserStore = require('../parserStore')('mssql'); const debug = logger.debugContext('connection:mssql'); const debugTedious = logger.debugContext('connection:mssql:tedious'); @@ -17,7 +17,15 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(DataTypes); } - connect(config) { + _refreshTypeParser(dataType) { + parserStore.refresh(dataType); + } + + _clearTypeParser() { + parserStore.clear(); + } + + async connect(config) { const connectionConfig = { server: config.host, authentication: { @@ -30,7 +38,7 @@ class ConnectionManager extends AbstractConnectionManager { options: { port: parseInt(config.port, 10), database: config.database, - encrypt: false + trustServerCertificate: true } }; @@ -50,57 +58,62 @@ class ConnectionManager extends AbstractConnectionManager { Object.assign(connectionConfig.options, config.dialectOptions.options); } - return new Promise((resolve, reject) => { - const connection = new this.lib.Connection(connectionConfig); - connection.lib = this.lib; - const resourceLock = new ResourceLock(connection); - - const connectHandler = error => { - connection.removeListener('end', endHandler); - connection.removeListener('error', errorHandler); - - if (error) return reject(error); - - debug('connection acquired'); - resolve(resourceLock); - }; - - const endHandler = () => { - connection.removeListener('connect', connectHandler); - connection.removeListener('error', errorHandler); - reject(new Error('Connection was closed by remote server')); - }; - - const errorHandler = error => { - connection.removeListener('connect', connectHandler); - connection.removeListener('end', endHandler); - reject(error); - }; - - connection.once('error', errorHandler); - connection.once('end', endHandler); - connection.once('connect', connectHandler); - - /* - * Permanently attach this event before connection is even acquired - * tedious sometime emits error even after connect(with error). - * - * If we dont attach this even that unexpected error event will crash node process - * - * E.g. connectTimeout is set higher than requestTimeout - */ - connection.on('error', error => { - switch (error.code) { - case 'ESOCKET': - case 'ECONNRESET': - this.pool.destroy(resourceLock); + try { + return await new Promise((resolve, reject) => { + const connection = new this.lib.Connection(connectionConfig); + if (connection.state === connection.STATE.INITIALIZED) { + connection.connect(); } - }); + connection.queue = new AsyncQueue(); + connection.lib = this.lib; + + const connectHandler = error => { + connection.removeListener('end', endHandler); + connection.removeListener('error', errorHandler); + + if (error) return reject(error); + + debug('connection acquired'); + resolve(connection); + }; + + const endHandler = () => { + connection.removeListener('connect', connectHandler); + connection.removeListener('error', errorHandler); + reject(new Error('Connection was closed by remote server')); + }; + + const errorHandler = error => { + connection.removeListener('connect', connectHandler); + connection.removeListener('end', endHandler); + reject(error); + }; + + connection.once('error', errorHandler); + connection.once('end', endHandler); + connection.once('connect', connectHandler); + + /* + * Permanently attach this event before connection is even acquired + * tedious sometime emits error even after connect(with error). + * + * If we dont attach this even that unexpected error event will crash node process + * + * E.g. connectTimeout is set higher than requestTimeout + */ + connection.on('error', error => { + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + this.pool.destroy(connection); + } + }); - if (config.dialectOptions && config.dialectOptions.debug) { - connection.on('debug', debugTedious.log.bind(debugTedious)); - } - }).catch(error => { + if (config.dialectOptions && config.dialectOptions.debug) { + connection.on('debug', debugTedious.log.bind(debugTedious)); + } + }); + } catch (error) { if (!error.code) { throw new sequelizeErrors.ConnectionError(error); } @@ -131,22 +144,17 @@ class ConnectionManager extends AbstractConnectionManager { default: throw new sequelizeErrors.ConnectionError(error); } - }); + } } - disconnect(connectionLock) { - /** - * Abstract connection may try to disconnect raw connection used for fetching version - */ - const connection = connectionLock.unwrap - ? connectionLock.unwrap() - : connectionLock; - + async disconnect(connection) { // Don't disconnect a connection that is already disconnected if (connection.closed) { - return Promise.resolve(); + return; } + connection.queue.close(); + return new Promise(resolve => { connection.on('end', resolve); connection.close(); @@ -154,14 +162,7 @@ class ConnectionManager extends AbstractConnectionManager { }); } - validate(connectionLock) { - /** - * Abstract connection may try to validate raw connection used for fetching version - */ - const connection = connectionLock.unwrap - ? connectionLock.unwrap() - : connectionLock; - + validate(connection) { return connection && connection.loggedIn; } } diff --git a/lib/dialects/mssql/data-types.js b/lib/dialects/mssql/data-types.js index e2fdd43f139d..4aeb6a495890 100644 --- a/lib/dialects/mssql/data-types.js +++ b/lib/dialects/mssql/data-types.js @@ -23,7 +23,8 @@ module.exports = BaseTypes => { /** * types: [hex, ...] - * @see hex here https://github.com/tediousjs/tedious/blob/master/src/data-type.js + * + * @see hex here https://github.com/tediousjs/tedious/blob/master/src/data-type.ts */ BaseTypes.DATE.types.mssql = [43]; diff --git a/lib/dialects/mssql/index.js b/lib/dialects/mssql/index.js index 28c0889a2810..b8801e3abd47 100644 --- a/lib/dialects/mssql/index.js +++ b/lib/dialects/mssql/index.js @@ -6,53 +6,61 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').mssql; +const { MSSqlQueryInterface } = require('./query-interface'); class MssqlDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new MSSqlQueryInterface( + sequelize, + this.queryGenerator + ); } } -MssqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'DEFAULT': true, - 'DEFAULT VALUES': true, - 'LIMIT ON UPDATE': true, - 'ORDER NULLS': false, - lock: false, - transactions: true, - migrations: false, - returnValues: { - output: true - }, - schemas: true, - autoIncrement: { - identityInsert: true, - defaultValue: false, - update: false - }, - constraints: { - restrict: false, - default: true - }, - index: { - collate: false, - length: false, - parser: false, - type: true, - using: false, - where: true - }, - NUMERIC: true, - tmpTableTrigger: true -}); +MssqlDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + DEFAULT: true, + 'DEFAULT VALUES': true, + 'LIMIT ON UPDATE': true, + 'ORDER NULLS': false, + lock: false, + transactions: true, + migrations: false, + returnValues: { + output: true + }, + schemas: true, + autoIncrement: { + identityInsert: true, + defaultValue: false, + update: false + }, + constraints: { + restrict: false, + default: true + }, + index: { + collate: false, + length: false, + parser: false, + type: true, + using: false, + where: true + }, + NUMERIC: true, + tmpTableTrigger: true + } +); -ConnectionManager.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express +MssqlDialect.prototype.defaultVersion = '12.0.2000'; // SQL Server 2014 Express, minimum supported version MssqlDialect.prototype.Query = Query; MssqlDialect.prototype.name = 'mssql'; MssqlDialect.prototype.TICK_CHAR = '"'; diff --git a/lib/dialects/mssql/query-generator.js b/lib/dialects/mssql/query-generator.js index 4bdbe9e9de4c..94e643ee0b23 100644 --- a/lib/dialects/mssql/query-generator.js +++ b/lib/dialects/mssql/query-generator.js @@ -16,10 +16,8 @@ const throwMethodUndefined = function(methodName) { class MSSQLQueryGenerator extends AbstractQueryGenerator { createDatabaseQuery(databaseName, options) { - options = { - collate: null, - ...options - }; + options = { collate: null, ...options }; + const collation = options.collate ? `COLLATE ${this.escape(options.collate)}` : ''; return [ @@ -107,10 +105,9 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } createTableQuery(tableName, attributes, options) { - const query = (table, attrs) => `IF OBJECT_ID('${table}', 'U') IS NULL CREATE TABLE ${table} (${attrs})`, - primaryKeys = [], + const primaryKeys = [], foreignKeys = {}, - attrStr = []; + attributesClauseParts = []; let commentStr = ''; @@ -133,24 +130,22 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { if (dataType.includes('REFERENCES')) { // MSSQL doesn't support inline REFERENCES declarations: move to the end match = dataType.match(/^(.+) (REFERENCES.*)$/); - attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`); foreignKeys[attr] = match[2]; } else { - attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`); } } else if (dataType.includes('REFERENCES')) { // MSSQL doesn't support inline REFERENCES declarations: move to the end match = dataType.match(/^(.+) (REFERENCES.*)$/); - attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${match[1]}`); foreignKeys[attr] = match[2]; } else { - attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`); + attributesClauseParts.push(`${this.quoteIdentifier(attr)} ${dataType}`); } } } - - let attributesClause = attrStr.join(', '); const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); if (options.uniqueKeys) { @@ -159,22 +154,33 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { if (typeof indexName !== 'string') { indexName = `uniq_${tableName}_${columns.fields.join('_')}`; } - attributesClause += `, CONSTRAINT ${this.quoteIdentifier(indexName)} UNIQUE (${columns.fields.map(field => this.quoteIdentifier(field)).join(', ')})`; + attributesClauseParts.push(`CONSTRAINT ${ + this.quoteIdentifier(indexName) + } UNIQUE (${ + columns.fields.map(field => this.quoteIdentifier(field)).join(', ') + })`); } }); } if (pkString.length > 0) { - attributesClause += `, PRIMARY KEY (${pkString})`; + attributesClauseParts.push(`PRIMARY KEY (${pkString})`); } for (const fkey in foreignKeys) { if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) { - attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`; + attributesClauseParts.push(`FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`); } } - return `${query(this.quoteTable(tableName), attributesClause)};${commentStr}`; + const quotedTableName = this.quoteTable(tableName); + + return Utils.joinSQLFragments([ + `IF OBJECT_ID('${quotedTableName}', 'U') IS NULL`, + `CREATE TABLE ${quotedTableName} (${attributesClauseParts.join(', ')})`, + ';', + commentStr + ]); } describeTableQuery(tableName, schema) { @@ -187,18 +193,18 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { "COLUMN_DEFAULT AS 'Default',", "pk.CONSTRAINT_TYPE AS 'Constraint',", "COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',", - "prop.value AS 'Comment'", + "CAST(prop.value AS NVARCHAR) AS 'Comment'", 'FROM', 'INFORMATION_SCHEMA.TABLES t', 'INNER JOIN', 'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA', 'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ', - 'cu.column_name, tc.constraint_type ', - 'FROM information_schema.TABLE_CONSTRAINTS tc ', - 'JOIN information_schema.KEY_COLUMN_USAGE cu ', + 'cu.column_name, tc.CONSTRAINT_TYPE ', + 'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ', + 'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu ', 'ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name ', 'and tc.constraint_name=cu.constraint_name ', - 'and tc.constraint_type=\'PRIMARY KEY\') pk ', + 'and tc.CONSTRAINT_TYPE=\'PRIMARY KEY\') pk ', 'ON pk.table_schema=c.table_schema ', 'AND pk.table_name=c.table_name ', 'AND pk.column_name=c.column_name ', @@ -226,8 +232,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } dropTableQuery(tableName) { - const qouteTbl = this.quoteTable(tableName); - return `IF OBJECT_ID('${qouteTbl}', 'U') IS NOT NULL DROP TABLE ${qouteTbl};`; + const quoteTbl = this.quoteTable(tableName); + return Utils.joinSQLFragments([ + `IF OBJECT_ID('${quoteTbl}', 'U') IS NOT NULL`, + 'DROP TABLE', + quoteTbl, + ';' + ]); } addColumnQuery(table, key, dataType) { @@ -244,10 +255,15 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { delete dataType['comment']; } - const def = this.attributeToSQL(dataType, { - context: 'addColumn' - }); - return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${def};${commentStr}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { context: 'addColumn' }), + ';', + commentStr + ]); } commentTemplate(comment, table, column) { @@ -259,7 +275,13 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } removeColumnQuery(tableName, attributeName) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP COLUMN ${this.quoteIdentifier(attributeName)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP COLUMN', + this.quoteIdentifier(attributeName), + ';' + ]); } changeColumnQuery(tableName, attributes) { @@ -284,25 +306,31 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } } - let finalQuery = ''; - if (attrString.length) { - finalQuery += `ALTER COLUMN ${attrString.join(', ')}`; - finalQuery += constraintString.length ? ' ' : ''; - } - if (constraintString.length) { - finalQuery += `ADD ${constraintString.join(', ')}`; - } - - return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};${commentString}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + attrString.length && `ALTER COLUMN ${attrString.join(', ')}`, + constraintString.length && `ADD ${constraintString.join(', ')}`, + ';', + commentString + ]); } renameColumnQuery(tableName, attrBefore, attributes) { const newName = Object.keys(attributes)[0]; - return `EXEC sp_rename '${this.quoteTable(tableName)}.${attrBefore}', '${newName}', 'COLUMN';`; + return Utils.joinSQLFragments([ + 'EXEC sp_rename', + `'${this.quoteTable(tableName)}.${attrBefore}',`, + `'${newName}',`, + "'COLUMN'", + ';' + ]); } - bulkInsertQuery(tableName, attrValueHashes, options = {}, attributes = {}) { + bulkInsertQuery(tableName, attrValueHashes, options, attributes) { const quotedTable = this.quoteTable(tableName); + options = options || {}; + attributes = attributes || {}; const tuples = []; const allAttributes = []; @@ -312,7 +340,9 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { outputFragment = ''; if (options.returning) { - outputFragment = ' OUTPUT INSERTED.*'; + const returnValues = this.generateReturnValues(attributes, options); + + outputFragment = returnValues.outputFragment; } const emptyQuery = `INSERT INTO ${quotedTable}${outputFragment} DEFAULT VALUES`; @@ -435,7 +465,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { * Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row */ for (const key in clause) { - if (!clause[key]) { + if (typeof clause[key] === 'undefined' || clause[key] == null) { valid = false; break; } @@ -493,19 +523,18 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { deleteQuery(tableName, where, options = {}, model) { const table = this.quoteTable(tableName); + const whereClause = this.getWhereConditions(where, null, model, options); - let whereClause = this.getWhereConditions(where, null, model, options); - let limit = ''; - - if (options.limit) { - limit = ` TOP(${this.escape(options.limit)})`; - } - - if (whereClause) { - whereClause = ` WHERE ${whereClause}`; - } - - return `DELETE${limit} FROM ${table}${whereClause}; SELECT @@ROWCOUNT AS AFFECTEDROWS;`; + return Utils.joinSQLFragments([ + 'DELETE', + options.limit && `TOP(${this.escape(options.limit)})`, + 'FROM', + table, + whereClause && `WHERE ${whereClause}`, + ';', + 'SELECT @@ROWCOUNT AS AFFECTEDROWS', + ';' + ]); } showIndexesQuery(tableName) { @@ -617,7 +646,6 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { attribute = attributes[key]; if (attribute.references) { - if (existingConstraints.includes(attribute.references.model.toString())) { // no cascading constraints to a table more than once attribute.onDelete = ''; @@ -712,20 +740,19 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { getForeignKeyQuery(table, attributeName) { const tableName = table.tableName || table; - let sql = `${this._getForeignKeysQueryPrefix() - } WHERE TB.NAME =${wrapSingleQuote(tableName) - } AND COL.NAME =${wrapSingleQuote(attributeName)}`; - - if (table.schema) { - sql += ` AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}`; - } - - return sql; + return Utils.joinSQLFragments([ + this._getForeignKeysQueryPrefix(), + 'WHERE', + `TB.NAME =${wrapSingleQuote(tableName)}`, + 'AND', + `COL.NAME =${wrapSingleQuote(attributeName)}`, + table.schema && `AND SCHEMA_NAME(TB.SCHEMA_ID) =${wrapSingleQuote(table.schema)}` + ]); } getPrimaryKeyConstraintQuery(table, attributeName) { const tableName = wrapSingleQuote(table.tableName || table); - return [ + return Utils.joinSQLFragments([ 'SELECT K.TABLE_NAME AS tableName,', 'K.COLUMN_NAME AS columnName,', 'K.CONSTRAINT_NAME AS constraintName', @@ -737,24 +764,39 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { 'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME', 'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'', `AND K.COLUMN_NAME = ${wrapSingleQuote(attributeName)}`, - `AND K.TABLE_NAME = ${tableName};` - ].join(' '); + `AND K.TABLE_NAME = ${tableName}`, + ';' + ]); } dropForeignKeyQuery(tableName, foreignKey) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(foreignKey)}`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP', + this.quoteIdentifier(foreignKey) + ]); } getDefaultConstraintQuery(tableName, attributeName) { const quotedTable = this.quoteTable(tableName); - return 'SELECT name FROM sys.default_constraints ' + - `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U') ` + - `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}') ` + - `AND object_id = OBJECT_ID('${quotedTable}', 'U'));`; + return Utils.joinSQLFragments([ + 'SELECT name FROM sys.default_constraints', + `WHERE PARENT_OBJECT_ID = OBJECT_ID('${quotedTable}', 'U')`, + `AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('${attributeName}')`, + `AND object_id = OBJECT_ID('${quotedTable}', 'U'))`, + ';' + ]); } dropConstraintQuery(tableName, constraintName) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(constraintName)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP CONSTRAINT', + this.quoteIdentifier(constraintName), + ';' + ]); } setIsolationLevelQuery() { @@ -790,60 +832,117 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } selectFromTableFragment(options, model, attributes, tables, mainTableAs, where) { - let topFragment = ''; - let mainFragment = `SELECT ${attributes.join(', ')} FROM ${tables}`; + this._throwOnEmptyAttributes(attributes, { modelName: model && model.name, as: mainTableAs }); + + const dbVersion = this.sequelize.options.databaseVersion; + const isSQLServer2008 = semver.valid(dbVersion) && semver.lt(dbVersion, '11.0.0'); + + if (isSQLServer2008 && options.offset) { + // For earlier versions of SQL server, we need to nest several queries + // in order to emulate the OFFSET behavior. + // + // 1. The outermost query selects all items from the inner query block. + // This is due to a limitation in SQL server with the use of computed + // columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses. + // 2. The next query handles the LIMIT and OFFSET behavior by getting + // the TOP N rows of the query where the row number is > OFFSET + // 3. The innermost query is the actual set we want information from + + const offset = options.offset || 0; + const isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation; + let orders = { mainQueryOrder: [] }; + if (options.order) { + orders = this.getQueryOrders(options, model, isSubQuery); + } - // Handle SQL Server 2008 with TOP instead of LIMIT - if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) { - if (options.limit) { - topFragment = `TOP ${options.limit} `; + if (orders.mainQueryOrder.length === 0) { + orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField)); } - if (options.offset) { - const offset = options.offset || 0, - isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation; - let orders = { mainQueryOrder: [] }; - if (options.order) { - orders = this.getQueryOrders(options, model, isSubQuery); - } - if (!orders.mainQueryOrder.length) { - orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField)); + const tmpTable = mainTableAs || 'OffsetTable'; + + if (options.include) { + const subQuery = options.subQuery === undefined ? options.limit && options.hasMultiAssociation : options.subQuery; + const mainTable = { + name: mainTableAs, + quotedName: null, + as: null, + model + }; + const topLevelInfo = { + names: mainTable, + options, + subQuery + }; + + let mainJoinQueries = []; + for (const include of options.include) { + if (include.separate) { + continue; + } + const joinQueries = this.generateInclude(include, { externalAs: mainTableAs, internalAs: mainTableAs }, topLevelInfo); + mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery); } - const tmpTable = mainTableAs ? mainTableAs : 'OffsetTable'; - const whereFragment = where ? ` WHERE ${where}` : ''; - - /* - * For earlier versions of SQL server, we need to nest several queries - * in order to emulate the OFFSET behavior. - * - * 1. The outermost query selects all items from the inner query block. - * This is due to a limitation in SQL server with the use of computed - * columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses. - * 2. The next query handles the LIMIT and OFFSET behavior by getting - * the TOP N rows of the query where the row number is > OFFSET - * 3. The innermost query is the actual set we want information from - */ - const fragment = `SELECT TOP 100 PERCENT ${attributes.join(', ')} FROM ` + - `(SELECT ${topFragment}*` + - ` FROM (SELECT ROW_NUMBER() OVER (ORDER BY ${orders.mainQueryOrder.join(', ')}) as row_num, * ` + - ` FROM ${tables} AS ${tmpTable}${whereFragment})` + - ` AS ${tmpTable} WHERE row_num > ${offset})` + - ` AS ${tmpTable}`; - return fragment; + return Utils.joinSQLFragments([ + 'SELECT TOP 100 PERCENT', + attributes.join(', '), + 'FROM (', + [ + 'SELECT', + options.limit && `TOP ${options.limit}`, + '* FROM (', + [ + 'SELECT ROW_NUMBER() OVER (', + [ + 'ORDER BY', + orders.mainQueryOrder.join(', ') + ], + `) as row_num, ${tmpTable}.* FROM (`, + [ + 'SELECT DISTINCT', + `${tmpTable}.* FROM ${tables} AS ${tmpTable}`, + mainJoinQueries, + where && `WHERE ${where}` + ], + `) AS ${tmpTable}` + ], + `) AS ${tmpTable} WHERE row_num > ${offset}` + ], + `) AS ${tmpTable}` + ]); } - mainFragment = `SELECT ${topFragment}${attributes.join(', ')} FROM ${tables}`; - } - - if (mainTableAs) { - mainFragment += ` AS ${mainTableAs}`; - } - - if (options.tableHint && TableHints[options.tableHint]) { - mainFragment += ` WITH (${TableHints[options.tableHint]})`; - } - - return mainFragment; + return Utils.joinSQLFragments([ + 'SELECT TOP 100 PERCENT', + attributes.join(', '), + 'FROM (', + [ + 'SELECT', + options.limit && `TOP ${options.limit}`, + '* FROM (', + [ + 'SELECT ROW_NUMBER() OVER (', + [ + 'ORDER BY', + orders.mainQueryOrder.join(', ') + ], + `) as row_num, * FROM ${tables} AS ${tmpTable}`, + where && `WHERE ${where}` + ], + `) AS ${tmpTable} WHERE row_num > ${offset}` + ], + `) AS ${tmpTable}` + ]); + } + + return Utils.joinSQLFragments([ + 'SELECT', + isSQLServer2008 && options.limit && `TOP ${options.limit}`, + attributes.join(', '), + `FROM ${tables}`, + mainTableAs && `AS ${mainTableAs}`, + options.tableHint && TableHints[options.tableHint] && `WITH (${TableHints[options.tableHint]})` + ]); } addLimitAndOffset(options, model) { @@ -865,9 +964,19 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator { } if (options.limit || options.offset) { - if (!options.order || options.include && !orders.subQueryOrder.length) { - fragment += options.order && !isSubQuery ? ', ' : ' ORDER BY '; - fragment += `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`; + if (!options.order || !options.order.length || options.include && !orders.subQueryOrder.length) { + const tablePkFragment = `${this.quoteTable(options.tableAs || model.name)}.${this.quoteIdentifier(model.primaryKeyField)}`; + if (!options.order || !options.order.length) { + fragment += ` ORDER BY ${tablePkFragment}`; + } else { + const orderFieldNames = _.map(options.order, order => order[0]); + const primaryKeyFieldAlreadyPresent = _.includes(orderFieldNames, model.primaryKeyField); + + if (!primaryKeyFieldAlreadyPresent) { + fragment += options.order && !isSubQuery ? ', ' : ' ORDER BY '; + fragment += tablePkFragment; + } + } } if (options.offset || options.limit) { diff --git a/lib/dialects/mssql/query-interface.js b/lib/dialects/mssql/query-interface.js index fed653063be3..3d91a9796dc3 100644 --- a/lib/dialects/mssql/query-interface.js +++ b/lib/dialects/mssql/query-interface.js @@ -1,68 +1,85 @@ 'use strict'; -/** - Returns an object that treats MSSQL's inabilities to do certain queries. +const _ = require('lodash'); - @class QueryInterface - @static - @private - */ +const Utils = require('../../utils'); +const QueryTypes = require('../../query-types'); +const Op = require('../../operators'); +const { QueryInterface } = require('../abstract/query-interface'); /** - A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. + * The interface that Sequelize uses to talk with MSSQL database + */ +class MSSqlQueryInterface extends QueryInterface { + /** + * A wrapper that fixes MSSQL's inability to cleanly remove columns from existing tables if they have a default constraint. + * + * @override + */ + async removeColumn(tableName, attributeName, options) { + options = { raw: true, ...options || {} }; + const findConstraintSql = this.queryGenerator.getDefaultConstraintQuery(tableName, attributeName); + const [results0] = await this.sequelize.query(findConstraintSql, options); + if (results0.length) { + // No default constraint found -- we can cleanly remove the column + const dropConstraintSql = this.queryGenerator.dropConstraintQuery(tableName, results0[0].name); + await this.sequelize.query(dropConstraintSql, options); + } + const findForeignKeySql = this.queryGenerator.getForeignKeyQuery(tableName, attributeName); + const [results] = await this.sequelize.query(findForeignKeySql, options); + if (results.length) { + // No foreign key constraints found, so we can remove the column + const dropForeignKeySql = this.queryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); + await this.sequelize.query(dropForeignKeySql, options); + } + //Check if the current column is a primaryKey + const primaryKeyConstraintSql = this.queryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); + const [result] = await this.sequelize.query(primaryKeyConstraintSql, options); + if (result.length) { + const dropConstraintSql = this.queryGenerator.dropConstraintQuery(tableName, result[0].constraintName); + await this.sequelize.query(dropConstraintSql, options); + } + const removeSql = this.queryGenerator.removeColumnQuery(tableName, attributeName); + return this.sequelize.query(removeSql, options); + } - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} attributeName The name of the attribute that we want to remove. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + const model = options.model; + const wheres = []; - @private - */ -const removeColumn = function(qi, tableName, attributeName, options = {}) { - options = Object.assign({ raw: true }, options); + options = { ...options }; - const findConstraintSql = qi.QueryGenerator.getDefaultConstraintQuery(tableName, attributeName); - return qi.sequelize.query(findConstraintSql, options) - .then(([results]) => { - if (!results.length) { - // No default constraint found -- we can cleanly remove the column - return; - } - const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, results[0].name); - return qi.sequelize.query(dropConstraintSql, options); - }) - .then(() => { - const findForeignKeySql = qi.QueryGenerator.getForeignKeyQuery(tableName, attributeName); - return qi.sequelize.query(findForeignKeySql, options); - }) - .then(([results]) => { - if (!results.length) { - // No foreign key constraints found, so we can remove the column - return; - } - const dropForeignKeySql = qi.QueryGenerator.dropForeignKeyQuery(tableName, results[0].constraint_name); - return qi.sequelize.query(dropForeignKeySql, options); - }) - .then(() => { - //Check if the current column is a primaryKey - const primaryKeyConstraintSql = qi.QueryGenerator.getPrimaryKeyConstraintQuery(tableName, attributeName); - return qi.sequelize.query(primaryKeyConstraintSql, options); - }) - .then(([result]) => { - if (!result.length) { - return; + if (!Utils.isWhereEmpty(where)) { + wheres.push(where); + } + + // Lets combine unique keys and indexes into one + let indexes = Object.values(model.uniqueKeys).map(item => item.fields); + indexes = indexes.concat(Object.values(model._indexes).filter(item => item.unique).map(item => item.fields)); + + const attributes = Object.keys(insertValues); + for (const index of indexes) { + if (_.intersection(attributes, index).length === index.length) { + where = {}; + for (const field of index) { + where[field] = insertValues[field]; + } + wheres.push(where); } - const dropConstraintSql = qi.QueryGenerator.dropConstraintQuery(tableName, result[0].constraintName); - return qi.sequelize.query(dropConstraintSql, options); - }) - .then(() => { - const removeSql = qi.QueryGenerator.removeColumnQuery(tableName, attributeName); - return qi.sequelize.query(removeSql, options); - }); -}; + } + + where = { [Op.or]: wheres }; + + options.type = QueryTypes.UPSERT; + options.raw = true; + + const sql = this.queryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); + return await this.sequelize.query(sql, options); + } +} -module.exports = { - removeColumn -}; +exports.MSSqlQueryInterface = MSSqlQueryInterface; diff --git a/lib/dialects/mssql/query.js b/lib/dialects/mssql/query.js index c36f864079b3..bbead007705a 100644 --- a/lib/dialects/mssql/query.js +++ b/lib/dialects/mssql/query.js @@ -1,13 +1,20 @@ 'use strict'; -const Promise = require('../../promise'); const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); +const parserStore = require('../parserStore')('mssql'); const _ = require('lodash'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('sql:mssql'); +function getScale(aNum) { + if (!Number.isFinite(aNum)) return 0; + let e = 1; + while (Math.round(aNum * e) / e !== aNum) e *= 10; + return Math.log10(e); +} + class Query extends AbstractQuery { getInsertIdField() { return 'id'; @@ -26,8 +33,10 @@ class Query extends AbstractQuery { } else { paramType.type = TYPES.Numeric; //Default to a reasonable numeric precision/scale pending more sophisticated logic - paramType.typeOptions = { precision: 30, scale: 15 }; + paramType.typeOptions = { precision: 30, scale: getScale(value) }; } + } else if (typeof value === 'boolean') { + paramType.type = TYPES.Bit; } if (Buffer.isBuffer(value)) { paramType.type = TYPES.VarBinary; @@ -35,46 +44,29 @@ class Query extends AbstractQuery { return paramType; } - _run(connection, sql, parameters) { + async _run(connection, sql, parameters, errStack) { this.sql = sql; const { options } = this; const complete = this._logQuery(sql, debug, parameters); - return new Promise((resolve, reject) => { - const handleTransaction = err => { - if (err) { - reject(this.formatError(err)); - return; - } - resolve(this.formatResults()); - }; + const query = new Promise((resolve, reject) => { // TRANSACTION SUPPORT if (sql.startsWith('BEGIN TRANSACTION')) { - return connection.beginTransaction(handleTransaction, options.transaction.name, connection.lib.ISOLATION_LEVEL[options.isolationLevel]); + return connection.beginTransaction(error => error ? reject(error) : resolve([]), options.transaction.name, connection.lib.ISOLATION_LEVEL[options.isolationLevel]); } if (sql.startsWith('COMMIT TRANSACTION')) { - return connection.commitTransaction(handleTransaction); + return connection.commitTransaction(error => error ? reject(error) : resolve([])); } if (sql.startsWith('ROLLBACK TRANSACTION')) { - return connection.rollbackTransaction(handleTransaction, options.transaction.name); + return connection.rollbackTransaction(error => error ? reject(error) : resolve([]), options.transaction.name); } if (sql.startsWith('SAVE TRANSACTION')) { - return connection.saveTransaction(handleTransaction, options.transaction.name); + return connection.saveTransaction(error => error ? reject(error) : resolve([]), options.transaction.name); } - const results = []; - const request = new connection.lib.Request(sql, (err, rowCount) => { - - complete(); - if (err) { - err.sql = sql; - err.parameters = parameters; - reject(this.formatError(err)); - } else { - resolve(this.formatResults(results, rowCount)); - } - }); + const rows = []; + const request = new connection.lib.Request(sql, (err, rowCount) => err ? reject(err) : resolve([rows, rowCount])); if (parameters) { _.forOwn(parameters, (value, key) => { @@ -84,10 +76,31 @@ class Query extends AbstractQuery { } request.on('row', columns => { + rows.push(columns); + }); + + connection.execSql(request); + }); + + let rows, rowCount; + + try { + [rows, rowCount] = await query; + } catch (err) { + err.sql = sql; + err.parameters = parameters; + + throw this.formatError(err, errStack); + } + + complete(); + + if (Array.isArray(rows)) { + rows = rows.map(columns => { const row = {}; for (const column of columns) { const typeid = column.metadata.type.id; - const parse = this.sequelize.connectionManager.parserStore.get(typeid); + const parse = parserStore.get(typeid); let value = column.value; if (value !== null & !!parse) { @@ -95,16 +108,18 @@ class Query extends AbstractQuery { } row[column.metadata.colName] = value; } - - results.push(row); + return row; }); + } - connection.execSql(request); - }); + return this.formatResults(rows, rowCount); } run(sql, parameters) { - return Promise.using(this.connection.lock(), connection => this._run(connection, sql, parameters)); + const errForStack = new Error(); + return this.connection.queue.enqueue(() => + this._run(this.connection, sql, parameters, errForStack.stack) + ); } static formatBindParameters(sql, values, dialect) { @@ -139,29 +154,15 @@ class Query extends AbstractQuery { * ]) */ formatResults(data, rowCount) { - let result = this.instance; if (this.isInsertQuery(data)) { this.handleInsertQuery(data); - - if (!this.instance) { - if (this.options.plain) { - // NOTE: super contrived. This just passes the newly added query-interface - // test returning only the PK. There isn't a way in MSSQL to identify - // that a given return value is the PK, and we have no schema information - // because there was no calling Model. - const record = data[0]; - result = record[Object.keys(record)[0]]; - } else { - result = data; - } - } + return [this.instance || data, rowCount]; } - if (this.isShowTablesQuery()) { return this.handleShowTablesQuery(data); } if (this.isDescribeQuery()) { - result = {}; + const result = {}; for (const _result of data) { if (_result.Default) { _result.Default = _result.Default.replace("('", '').replace("')", '').replace(/'/g, ''); @@ -186,8 +187,8 @@ class Query extends AbstractQuery { result[_result.Name].type += `(${_result.Length})`; } } - } + return result; } if (this.isSelectQuery()) { return this.handleSelectQuery(data); @@ -195,17 +196,18 @@ class Query extends AbstractQuery { if (this.isShowIndexesQuery()) { return this.handleShowIndexesQuery(data); } - if (this.isUpsertQuery()) { - return data[0]; - } if (this.isCallQuery()) { return data[0]; } if (this.isBulkUpdateQuery()) { - return data.length; + if (this.options.returning) { + return this.handleSelectQuery(data); + } + + return rowCount; } if (this.isBulkDeleteQuery()) { - return data[0] && data[0].AFFECTEDROWS; + return data[0] ? data[0].AFFECTEDROWS : 0; } if (this.isVersionQuery()) { return data[0].version; @@ -213,18 +215,20 @@ class Query extends AbstractQuery { if (this.isForeignKeysQuery()) { return data; } - if (this.isInsertQuery() || this.isUpdateQuery()) { - return [result, rowCount]; + if (this.isUpsertQuery()) { + this.handleInsertQuery(data); + return [this.instance || data, data[0].$action === 'INSERT']; + } + if (this.isUpdateQuery()) { + return [this.instance || data, rowCount]; } if (this.isShowConstraintsQuery()) { return this.handleShowConstraintsQuery(data); } if (this.isRawQuery()) { - // MSSQL returns row data and metadata (affected rows etc) in a single object - let's standarize it, sorta - return [data, data]; + return [data, rowCount]; } - - return result; + return data; } handleShowTablesQuery(results) { @@ -247,10 +251,10 @@ class Query extends AbstractQuery { }); } - formatError(err) { + formatError(err, errStack) { let match; - match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '((.|\s)*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/); + match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'. Cannot insert duplicate key in object '.*'.(:? The duplicate key value is \((.*)\).)?/); match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'/); if (match && match.length > 1) { let fields = {}; @@ -260,12 +264,12 @@ class Query extends AbstractQuery { if (uniqueKey && !!uniqueKey.msg) { message = uniqueKey.msg; } - if (match[4]) { - const values = match[4].split(',').map(part => part.trim()); + if (match[3]) { + const values = match[3].split(',').map(part => part.trim()); if (uniqueKey) { fields = _.zipObject(uniqueKey.fields, values); } else { - fields[match[1]] = match[4]; + fields[match[1]] = match[3]; } } @@ -281,7 +285,7 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./) || @@ -291,7 +295,8 @@ class Query extends AbstractQuery { return new sequelizeErrors.ForeignKeyConstraintError({ fields: null, index: match[1], - parent: err + parent: err, + stack: errStack }); } @@ -306,11 +311,12 @@ class Query extends AbstractQuery { message: match[1], constraint, table, - parent: err + parent: err, + stack: errStack }); } - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } isShowOrDescribeQuery() { @@ -379,6 +385,19 @@ class Query extends AbstractQuery { id = id || autoIncrementAttributeAlias && results && results[0][autoIncrementAttributeAlias]; this.instance[autoIncrementAttribute] = id; + + if (this.instance.dataValues) { + for (const key in results[0]) { + if (Object.prototype.hasOwnProperty.call(results[0], key)) { + const record = results[0][key]; + + const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + + this.instance.dataValues[attr && attr.fieldName || key] = record; + } + } + } + } } } diff --git a/lib/dialects/mssql/resource-lock.js b/lib/dialects/mssql/resource-lock.js deleted file mode 100644 index 53f4229a880f..000000000000 --- a/lib/dialects/mssql/resource-lock.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -const Promise = require('../../promise'); - -class ResourceLock { - constructor(resource) { - this.resource = resource; - this.previous = Promise.resolve(resource); - } - - unwrap() { - return this.resource; - } - - lock() { - const lock = this.previous; - let resolve; - this.previous = new Promise(r => { - resolve = r; - }); - return lock.disposer(resolve); - } -} - -module.exports = ResourceLock; diff --git a/lib/dialects/mysql/connection-manager.js b/lib/dialects/mysql/connection-manager.js index 74c91644c342..1eda51826726 100644 --- a/lib/dialects/mysql/connection-manager.js +++ b/lib/dialects/mysql/connection-manager.js @@ -2,11 +2,12 @@ const AbstractConnectionManager = require('../abstract/connection-manager'); const SequelizeErrors = require('../../errors'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const DataTypes = require('../../data-types').mysql; const momentTz = require('moment-timezone'); const debug = logger.debugContext('connection:mysql'); +const parserStore = require('../parserStore')('mysql'); +const { promisify } = require('util'); /** * MySQL Connection Manager @@ -15,11 +16,8 @@ const debug = logger.debugContext('connection:mysql'); * AbstractConnectionManager pooling use it to handle MySQL specific connections * Use https://github.com/sidorares/node-mysql2 to connect with MySQL server * - * @extends AbstractConnectionManager - * @returns Class * @private */ - class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { sequelize.config.port = sequelize.config.port || 3306; @@ -28,6 +26,21 @@ class ConnectionManager extends AbstractConnectionManager { this.refreshTypeParser(DataTypes); } + _refreshTypeParser(dataType) { + parserStore.refresh(dataType); + } + + _clearTypeParser() { + parserStore.clear(); + } + + static _typecast(field, next) { + if (parserStore.get(field.type)) { + return parserStore.get(field.type)(field, this.sequelize.options, next); + } + return next(); + } + /** * Connect with MySQL database based on config, Handle any errors in connection * Set the pool handlers on connection.error @@ -37,8 +50,8 @@ class ConnectionManager extends AbstractConnectionManager { * @returns {Promise} * @private */ - connect(config) { - const connectionConfig = Object.assign({ + async connect(config) { + const connectionConfig = { host: config.host, port: config.port, user: config.username, @@ -46,92 +59,83 @@ class ConnectionManager extends AbstractConnectionManager { password: config.password, database: config.database, timezone: this.sequelize.options.timezone, - typeCast: (field, next) => { - if (this.parserStore.get(field.type)) { - return this.parserStore.get(field.type)(field, this.sequelize.options, next); - } - return next(); - }, + typeCast: ConnectionManager._typecast.bind(this), bigNumberStrings: false, - supportBigNumbers: true - }, config.dialectOptions); - - return new Promise((resolve, reject) => { - const connection = this.lib.createConnection(connectionConfig); - - const errorHandler = e => { - // clean up connect & error event if there is error - connection.removeListener('connect', connectHandler); - connection.removeListener('error', connectHandler); - reject(e); - }; - - const connectHandler = () => { - // clean up error event if connected - connection.removeListener('error', errorHandler); - resolve(connection); - }; - - // don't use connection.once for error event handling here - // mysql2 emit error two times in case handshake was failed - // first error is protocol_lost and second is timeout - // if we will use `once.error` node process will crash on 2nd error emit - connection.on('error', errorHandler); - connection.once('connect', connectHandler); - }) - .tap(() => { debug('connection acquired'); }) - .then(connection => { - connection.on('error', error => { - switch (error.code) { - case 'ESOCKET': - case 'ECONNRESET': - case 'EPIPE': - case 'PROTOCOL_CONNECTION_LOST': - this.pool.destroy(connection); - } - }); - - return new Promise((resolve, reject) => { - if (!this.sequelize.config.keepDefaultTimezone) { - // set timezone for this connection - // but named timezone are not directly supported in mysql, so get its offset first - let tzOffset = this.sequelize.options.timezone; - tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') : tzOffset; - return connection.query(`SET time_zone = '${tzOffset}'`, err => { - if (err) { reject(err); } else { resolve(connection); } - }); - } - - // return connection without executing SET time_zone query + supportBigNumbers: true, + ...config.dialectOptions + }; + + try { + const connection = await new Promise((resolve, reject) => { + const connection = this.lib.createConnection(connectionConfig); + + const errorHandler = e => { + // clean up connect & error event if there is error + connection.removeListener('connect', connectHandler); + connection.removeListener('error', connectHandler); + reject(e); + }; + + const connectHandler = () => { + // clean up error event if connected + connection.removeListener('error', errorHandler); resolve(connection); - }); - }) - .catch(err => { - switch (err.code) { - case 'ECONNREFUSED': - throw new SequelizeErrors.ConnectionRefusedError(err); - case 'ER_ACCESS_DENIED_ERROR': - throw new SequelizeErrors.AccessDeniedError(err); - case 'ENOTFOUND': - throw new SequelizeErrors.HostNotFoundError(err); - case 'EHOSTUNREACH': - throw new SequelizeErrors.HostNotReachableError(err); - case 'EINVAL': - throw new SequelizeErrors.InvalidConnectionError(err); - default: - throw new SequelizeErrors.ConnectionError(err); + }; + + // don't use connection.once for error event handling here + // mysql2 emit error two times in case handshake was failed + // first error is protocol_lost and second is timeout + // if we will use `once.error` node process will crash on 2nd error emit + connection.on('error', errorHandler); + connection.once('connect', connectHandler); + }); + + debug('connection acquired'); + connection.on('error', error => { + switch (error.code) { + case 'ESOCKET': + case 'ECONNRESET': + case 'EPIPE': + case 'PROTOCOL_CONNECTION_LOST': + this.pool.destroy(connection); } }); + + if (!this.sequelize.config.keepDefaultTimezone) { + // set timezone for this connection + // but named timezone are not directly supported in mysql, so get its offset first + let tzOffset = this.sequelize.options.timezone; + tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z') : tzOffset; + await promisify(cb => connection.query(`SET time_zone = '${tzOffset}'`, cb))(); + } + + return connection; + } catch (err) { + switch (err.code) { + case 'ECONNREFUSED': + throw new SequelizeErrors.ConnectionRefusedError(err); + case 'ER_ACCESS_DENIED_ERROR': + throw new SequelizeErrors.AccessDeniedError(err); + case 'ENOTFOUND': + throw new SequelizeErrors.HostNotFoundError(err); + case 'EHOSTUNREACH': + throw new SequelizeErrors.HostNotReachableError(err); + case 'EINVAL': + throw new SequelizeErrors.InvalidConnectionError(err); + default: + throw new SequelizeErrors.ConnectionError(err); + } + } } - disconnect(connection) { + async disconnect(connection) { // Don't disconnect connections with CLOSED state if (connection._closing) { debug('connection tried to disconnect but was already at CLOSED state'); - return Promise.resolve(); + return; } - return Promise.fromCallback(callback => connection.end(callback)); + return await promisify(callback => connection.end(callback))(); } validate(connection) { diff --git a/lib/dialects/mysql/data-types.js b/lib/dialects/mysql/data-types.js index 98e8aa0a97a8..c0beec964da9 100644 --- a/lib/dialects/mysql/data-types.js +++ b/lib/dialects/mysql/data-types.js @@ -8,6 +8,7 @@ module.exports = BaseTypes => { /** * types: [buffer_type, ...] + * * @see buffer_type here https://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-codes.html * @see hex here https://github.com/sidorares/node-mysql2/blob/master/lib/constants/types.js */ @@ -49,7 +50,7 @@ module.exports = BaseTypes => { class DATE extends BaseTypes.DATE { toSql() { - return `DATETIME${this._length ? `(${this._length})` : ''}`; + return this._length ? `DATETIME(${this._length})` : 'DATETIME'; } _stringify(date, options) { date = this._applyTimezone(date, options); @@ -109,7 +110,7 @@ module.exports = BaseTypes => { } // For some reason, discard the first 4 bytes value = value.slice(4); - return wkx.Geometry.parse(value).toGeoJSON(); + return wkx.Geometry.parse(value).toGeoJSON({ shortCrs: true }); } toSql() { return this.sqlType; diff --git a/lib/dialects/mysql/index.js b/lib/dialects/mysql/index.js index 6e612374f183..a9850b11e83b 100644 --- a/lib/dialects/mysql/index.js +++ b/lib/dialects/mysql/index.js @@ -6,48 +6,57 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').mysql; +const { MySQLQueryInterface } = require('./query-interface'); class MysqlDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new MySQLQueryInterface( + sequelize, + this.queryGenerator + ); } } -MysqlDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'VALUES ()': true, - 'LIMIT ON UPDATE': true, - lock: true, - forShare: 'LOCK IN SHARE MODE', - inserts: { - ignoreDuplicates: ' IGNORE', - updateOnDuplicate: ' ON DUPLICATE KEY UPDATE' - }, - index: { - collate: false, - length: true, - parser: true, - type: true, - using: 1 - }, - constraints: { - dropConstraint: false, - check: false - }, - indexViaAlter: true, - indexHints: true, - NUMERIC: true, - GEOMETRY: true, - JSON: true, - REGEXP: true -}); +MysqlDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + 'VALUES ()': true, + 'LIMIT ON UPDATE': true, + lock: true, + forShare: 'LOCK IN SHARE MODE', + settingIsolationLevelDuringTransaction: false, + inserts: { + ignoreDuplicates: ' IGNORE', + updateOnDuplicate: ' ON DUPLICATE KEY UPDATE' + }, + index: { + collate: false, + length: true, + parser: true, + type: true, + using: 1 + }, + constraints: { + dropConstraint: false, + check: false + }, + indexViaAlter: true, + indexHints: true, + NUMERIC: true, + GEOMETRY: true, + JSON: true, + REGEXP: true + } +); -ConnectionManager.prototype.defaultVersion = '5.6.0'; +MysqlDialect.prototype.defaultVersion = '5.7.0'; // minimum supported version MysqlDialect.prototype.Query = Query; MysqlDialect.prototype.QueryGenerator = QueryGenerator; MysqlDialect.prototype.DataTypes = DataTypes; diff --git a/lib/dialects/mysql/query-generator.js b/lib/dialects/mysql/query-generator.js index f6aa45d5cd27..a9ccf5d9a29e 100644 --- a/lib/dialects/mysql/query-generator.js +++ b/lib/dialects/mysql/query-generator.js @@ -7,21 +7,23 @@ const util = require('util'); const Op = require('../../operators'); -const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i; -const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i; -const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i; -const foreignKeyFields = 'CONSTRAINT_NAME as constraint_name,' - + 'CONSTRAINT_NAME as constraintName,' - + 'CONSTRAINT_SCHEMA as constraintSchema,' - + 'CONSTRAINT_SCHEMA as constraintCatalog,' - + 'TABLE_NAME as tableName,' - + 'TABLE_SCHEMA as tableSchema,' - + 'TABLE_SCHEMA as tableCatalog,' - + 'COLUMN_NAME as columnName,' - + 'REFERENCED_TABLE_SCHEMA as referencedTableSchema,' - + 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog,' - + 'REFERENCED_TABLE_NAME as referencedTableName,' - + 'REFERENCED_COLUMN_NAME as referencedColumnName'; +const JSON_FUNCTION_REGEX = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i; +const JSON_OPERATOR_REGEX = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i; +const TOKEN_CAPTURE_REGEX = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i; +const FOREIGN_KEY_FIELDS = [ + 'CONSTRAINT_NAME as constraint_name', + 'CONSTRAINT_NAME as constraintName', + 'CONSTRAINT_SCHEMA as constraintSchema', + 'CONSTRAINT_SCHEMA as constraintCatalog', + 'TABLE_NAME as tableName', + 'TABLE_SCHEMA as tableSchema', + 'TABLE_SCHEMA as tableCatalog', + 'COLUMN_NAME as columnName', + 'REFERENCED_TABLE_SCHEMA as referencedTableSchema', + 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog', + 'REFERENCED_TABLE_NAME as referencedTableName', + 'REFERENCED_COLUMN_NAME as referencedColumnName' +].join(','); const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']); @@ -29,27 +31,31 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { constructor(options) { super(options); - this.OperatorMap = Object.assign({}, this.OperatorMap, { + this.OperatorMap = { + ...this.OperatorMap, [Op.regexp]: 'REGEXP', [Op.notRegexp]: 'NOT REGEXP' - }); + }; } - createDatabaseQuery(databaseName, options = {}) { - options = Object.assign({ + createDatabaseQuery(databaseName, options) { + options = { charset: null, - collate: null - }, options); - - const database = this.quoteIdentifier(databaseName); - const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : ''; - const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : ''; - - return `${`CREATE DATABASE IF NOT EXISTS ${database}${charset}${collate}`.trim()};`; + collate: null, + ...options + }; + + return Utils.joinSQLFragments([ + 'CREATE DATABASE IF NOT EXISTS', + this.quoteIdentifier(databaseName), + options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`, + options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`, + ';' + ]); } dropDatabaseQuery(databaseName) { - return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName).trim()};`; + return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName)};`; } createSchema() { @@ -64,12 +70,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { return 'SELECT VERSION() as `version`'; } - createTableQuery(tableName, attributes, options = {}) { - options = Object.assign({ + createTableQuery(tableName, attributes, options) { + options = { engine: 'InnoDB', charset: null, - rowFormat: null - }, options); + rowFormat: null, + ...options + }; const primaryKeys = []; const foreignKeys = {}; @@ -103,12 +110,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { const table = this.quoteTable(tableName); let attributesClause = attrStr.join(', '); - const comment = options.comment && typeof options.comment === 'string' ? ` COMMENT ${this.escape(options.comment)}` : ''; - const engine = options.engine; - const charset = options.charset ? ` DEFAULT CHARSET=${options.charset}` : ''; - const collation = options.collate ? ` COLLATE ${options.collate}` : ''; - const rowFormat = options.rowFormat ? ` ROW_FORMAT=${options.rowFormat}` : ''; - const initialAutoIncrement = options.initialAutoIncrement ? ` AUTO_INCREMENT=${options.initialAutoIncrement}` : ''; const pkString = primaryKeys.map(pk => this.quoteIdentifier(pk)).join(', '); if (options.uniqueKeys) { @@ -132,10 +133,20 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } } - return `CREATE TABLE IF NOT EXISTS ${table} (${attributesClause}) ENGINE=${engine}${comment}${charset}${collation}${initialAutoIncrement}${rowFormat};`; + return Utils.joinSQLFragments([ + 'CREATE TABLE IF NOT EXISTS', + table, + `(${attributesClause})`, + `ENGINE=${options.engine}`, + options.comment && typeof options.comment === 'string' && `COMMENT ${this.escape(options.comment)}`, + options.charset && `DEFAULT CHARSET=${options.charset}`, + options.collate && `COLLATE ${options.collate}`, + options.initialAutoIncrement && `AUTO_INCREMENT=${options.initialAutoIncrement}`, + options.rowFormat && `ROW_FORMAT=${options.rowFormat}`, + ';' + ]); } - describeTableQuery(tableName, schema, schemaDelimiter) { const table = this.quoteTable( this.addSchema({ @@ -159,17 +170,28 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } addColumnQuery(table, key, dataType) { - const definition = this.attributeToSQL(dataType, { - context: 'addColumn', - tableName: table, - foreignKey: key - }); - - return `ALTER TABLE ${this.quoteTable(table)} ADD ${this.quoteIdentifier(key)} ${definition};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(table), + 'ADD', + this.quoteIdentifier(key), + this.attributeToSQL(dataType, { + context: 'addColumn', + tableName: table, + foreignKey: key + }), + ';' + ]); } removeColumnQuery(tableName, attributeName) { - return `ALTER TABLE ${this.quoteTable(tableName)} DROP ${this.quoteIdentifier(attributeName)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP', + this.quoteIdentifier(attributeName), + ';' + ]); } changeColumnQuery(tableName, attributes) { @@ -187,16 +209,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } } - let finalQuery = ''; - if (attrString.length) { - finalQuery += `CHANGE ${attrString.join(', ')}`; - finalQuery += constraintString.length ? ' ' : ''; - } - if (constraintString.length) { - finalQuery += `ADD ${constraintString.join(', ')}`; - } - - return `ALTER TABLE ${this.quoteTable(tableName)} ${finalQuery};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + attrString.length && `CHANGE ${attrString.join(', ')}`, + constraintString.length && `ADD ${constraintString.join(', ')}`, + ';' + ]); } renameColumnQuery(tableName, attrBefore, attributes) { @@ -207,7 +226,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { attrString.push(`\`${attrBefore}\` \`${attrName}\` ${definition}`); } - return `ALTER TABLE ${this.quoteTable(tableName)} CHANGE ${attrString.join(', ')};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'CHANGE', + attrString.join(', '), + ';' + ]); } handleSequelizeMethod(smth, tableName, factory, options, prepend) { @@ -267,17 +292,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { return value; } - upsertQuery(tableName, insertValues, updateValues, where, model, options) { - options.onDuplicate = 'UPDATE '; - - options.onDuplicate += Object.keys(updateValues).map(key => { - key = this.quoteIdentifier(key); - return `${key}=VALUES(${key})`; - }).join(', '); - - return this.insertQuery(tableName, insertValues, model.rawAttributes, options); - } - truncateTableQuery(tableName) { return `TRUNCATE ${this.quoteTable(tableName)}`; } @@ -299,15 +313,18 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { return query + limit; } - showIndexesQuery(tableName, options = {}) { - return `SHOW INDEX FROM ${this.quoteTable(tableName)}${options.database ? ` FROM \`${options.database}\`` : ''}`; + showIndexesQuery(tableName, options) { + return Utils.joinSQLFragments([ + `SHOW INDEX FROM ${this.quoteTable(tableName)}`, + options && options.database && `FROM \`${options.database}\`` + ]); } showConstraintsQuery(table, constraintName) { const tableName = table.tableName || table; const schemaName = table.schema; - let sql = [ + return Utils.joinSQLFragments([ 'SELECT CONSTRAINT_CATALOG AS constraintCatalog,', 'CONSTRAINT_NAME AS constraintName,', 'CONSTRAINT_SCHEMA AS constraintSchema,', @@ -315,18 +332,11 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { 'TABLE_NAME AS tableName,', 'TABLE_SCHEMA AS tableSchema', 'from INFORMATION_SCHEMA.TABLE_CONSTRAINTS', - `WHERE table_name='${tableName}'` - ].join(' '); - - if (constraintName) { - sql += ` AND constraint_name = '${constraintName}'`; - } - - if (schemaName) { - sql += ` AND TABLE_SCHEMA = '${schemaName}'`; - } - - return `${sql};`; + `WHERE table_name='${tableName}'`, + constraintName && `AND constraint_name = '${constraintName}'`, + schemaName && `AND TABLE_SCHEMA = '${schemaName}'`, + ';' + ]); } removeIndexQuery(tableName, indexNameOrAttributes) { @@ -336,7 +346,12 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join('_')}`); } - return `DROP INDEX ${this.quoteIdentifier(indexName)} ON ${this.quoteTable(tableName)}`; + return Utils.joinSQLFragments([ + 'DROP INDEX', + this.quoteIdentifier(indexName), + 'ON', + this.quoteTable(tableName) + ]); } attributeToSQL(attribute, options) { @@ -384,7 +399,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { } if (attribute.references) { - if (options && options.context === 'addColumn' && options.foreignKey) { const attrName = this.quoteIdentifier(options.foreignKey); const fkName = this.quoteIdentifier(`${options.tableName}_${attrName}_foreign_idx`); @@ -444,21 +458,21 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { while (currentIndex < stmt.length) { const string = stmt.substr(currentIndex); - const functionMatches = jsonFunctionRegex.exec(string); + const functionMatches = JSON_FUNCTION_REGEX.exec(string); if (functionMatches) { currentIndex += functionMatches[0].indexOf('('); hasJsonFunction = true; continue; } - const operatorMatches = jsonOperatorRegex.exec(string); + const operatorMatches = JSON_OPERATOR_REGEX.exec(string); if (operatorMatches) { currentIndex += operatorMatches[0].length; hasJsonFunction = true; continue; } - const tokenMatches = tokenCaptureRegex.exec(string); + const tokenMatches = TOKEN_CAPTURE_REGEX.exec(string); if (tokenMatches) { const capturedToken = tokenMatches[1]; if (capturedToken === '(') { @@ -495,7 +509,14 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { */ getForeignKeysQuery(table, schemaName) { const tableName = table.tableName || table; - return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}' AND REFERENCED_TABLE_NAME IS NOT NULL;`; + return Utils.joinSQLFragments([ + 'SELECT', + FOREIGN_KEY_FIELDS, + `FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}'`, + `AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}'`, + 'AND REFERENCED_TABLE_NAME IS NOT NULL', + ';' + ]); } /** @@ -511,12 +532,25 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { const quotedTableName = wrapSingleQuote(table.tableName || table); const quotedColumnName = wrapSingleQuote(columnName); - return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE` - + ` WHERE (REFERENCED_TABLE_NAME = ${quotedTableName}${table.schema - ? ` AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}` - : ''} AND REFERENCED_COLUMN_NAME = ${quotedColumnName})` - + ` OR (TABLE_NAME = ${quotedTableName}${table.schema ? - ` AND TABLE_SCHEMA = ${quotedSchemaName}` : ''} AND COLUMN_NAME = ${quotedColumnName} AND REFERENCED_TABLE_NAME IS NOT NULL)`; + return Utils.joinSQLFragments([ + 'SELECT', + FOREIGN_KEY_FIELDS, + 'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE', + 'WHERE (', + [ + `REFERENCED_TABLE_NAME = ${quotedTableName}`, + table.schema && `AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}`, + `AND REFERENCED_COLUMN_NAME = ${quotedColumnName}` + ], + ') OR (', + [ + `TABLE_NAME = ${quotedTableName}`, + table.schema && `AND TABLE_SCHEMA = ${quotedSchemaName}`, + `AND COLUMN_NAME = ${quotedColumnName}`, + 'AND REFERENCED_TABLE_NAME IS NOT NULL' + ], + ')' + ]); } /** @@ -528,8 +562,13 @@ class MySQLQueryGenerator extends AbstractQueryGenerator { * @private */ dropForeignKeyQuery(tableName, foreignKey) { - return `ALTER TABLE ${this.quoteTable(tableName)} - DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`; + return Utils.joinSQLFragments([ + 'ALTER TABLE', + this.quoteTable(tableName), + 'DROP FOREIGN KEY', + this.quoteIdentifier(foreignKey), + ';' + ]); } } diff --git a/lib/dialects/mysql/query-interface.js b/lib/dialects/mysql/query-interface.js index 3e2ce91ccff9..bcdec9d2e5a7 100644 --- a/lib/dialects/mysql/query-interface.js +++ b/lib/dialects/mysql/query-interface.js @@ -1,89 +1,89 @@ 'use strict'; -/** - Returns an object that treats MySQL's inabilities to do certain queries. +const sequelizeErrors = require('../../errors'); +const { QueryInterface } = require('../abstract/query-interface'); +const QueryTypes = require('../../query-types'); - @class QueryInterface - @static - @private +/** + * The interface that Sequelize uses to talk with MySQL/MariaDB database */ +class MySQLQueryInterface extends QueryInterface { + /** + * A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. + * + * @override + */ + async removeColumn(tableName, columnName, options) { + options = options || {}; -const Promise = require('../../promise'); -const sequelizeErrors = require('../../errors'); + const [results] = await this.sequelize.query( + this.queryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { + tableName, + schema: this.sequelize.config.database + }, columnName), + { raw: true, ...options } + ); -/** - A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint. + //Exclude primary key constraint + if (results.length && results[0].constraint_name !== 'PRIMARY') { + await Promise.all(results.map(constraint => this.sequelize.query( + this.queryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), + { raw: true, ...options } + ))); + } - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} columnName The name of the attribute that we want to remove. - @param {object} options + return await this.sequelize.query( + this.queryGenerator.removeColumnQuery(tableName, columnName), + { raw: true, ...options } + ); + } - @private - */ -function removeColumn(qi, tableName, columnName, options = {}) { - return qi.sequelize.query( - qi.QueryGenerator.getForeignKeyQuery(tableName.tableName ? tableName : { - tableName, - schema: qi.sequelize.config.database - }, columnName), - Object.assign({ raw: true }, options) - ) - .then(([results]) => { - //Exclude primary key constraint - if (!results.length || results[0].constraint_name === 'PRIMARY') { - // No foreign key constraints found, so we can remove the column - return; - } - return Promise.map(results, constraint => qi.sequelize.query( - qi.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name), - Object.assign({ raw: true }, options) - )); - }) - .then(() => qi.sequelize.query( - qi.QueryGenerator.removeColumnQuery(tableName, columnName), - Object.assign({ raw: true }, options) - )); -} + /** + * @override + */ + async upsert(tableName, insertValues, updateValues, where, options) { + options = { ...options }; -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {string} constraintName - * @param {object} options - * - * @private - */ -function removeConstraint(qi, tableName, constraintName, options) { - const sql = qi.QueryGenerator.showConstraintsQuery( - tableName.tableName ? tableName : { - tableName, - schema: qi.sequelize.config.database - }, constraintName); + options.type = QueryTypes.UPSERT; + options.updateOnDuplicate = Object.keys(updateValues); + + const model = options.model; + const sql = this.queryGenerator.insertQuery(tableName, insertValues, model.rawAttributes, options); + return await this.sequelize.query(sql, options); + } + + /** + * @override + */ + async removeConstraint(tableName, constraintName, options) { + const sql = this.queryGenerator.showConstraintsQuery( + tableName.tableName ? tableName : { + tableName, + schema: this.sequelize.config.database + }, constraintName); + + const constraints = await this.sequelize.query(sql, { ...options, + type: this.sequelize.QueryTypes.SHOWCONSTRAINTS }); - return qi.sequelize.query(sql, Object.assign({}, options, - { type: qi.sequelize.QueryTypes.SHOWCONSTRAINTS })) - .then(constraints => { - const constraint = constraints[0]; - let query; - if (!constraint || !constraint.constraintType) { - throw new sequelizeErrors.UnknownConstraintError( - { - message: `Constraint ${constraintName} on table ${tableName} does not exist`, - constraint: constraintName, - table: tableName - }); - } + const constraint = constraints[0]; + let query; + if (!constraint || !constraint.constraintType) { + throw new sequelizeErrors.UnknownConstraintError( + { + message: `Constraint ${constraintName} on table ${tableName} does not exist`, + constraint: constraintName, + table: tableName + }); + } - if (constraint.constraintType === 'FOREIGN KEY') { - query = qi.QueryGenerator.dropForeignKeyQuery(tableName, constraintName); - } else { - query = qi.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); - } + if (constraint.constraintType === 'FOREIGN KEY') { + query = this.queryGenerator.dropForeignKeyQuery(tableName, constraintName); + } else { + query = this.queryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName); + } - return qi.sequelize.query(query, options); - }); + return await this.sequelize.query(query, options); + } } -exports.removeConstraint = removeConstraint; -exports.removeColumn = removeColumn; +exports.MySQLQueryInterface = MySQLQueryInterface; diff --git a/lib/dialects/mysql/query.js b/lib/dialects/mysql/query.js index 80c001fada41..10219783398e 100644 --- a/lib/dialects/mysql/query.js +++ b/lib/dialects/mysql/query.js @@ -1,24 +1,27 @@ 'use strict'; -const Utils = require('../../utils'); const AbstractQuery = require('../abstract/query'); const sequelizeErrors = require('../../errors'); const _ = require('lodash'); const { logger } = require('../../utils/logger'); -const debug = logger.debugContext('sql:mysql'); +const ER_DUP_ENTRY = 1062; +const ER_DEADLOCK = 1213; +const ER_ROW_IS_REFERENCED = 1451; +const ER_NO_REFERENCED_ROW = 1452; +const debug = logger.debugContext('sql:mysql'); class Query extends AbstractQuery { constructor(connection, sequelize, options) { - super(connection, sequelize, Object.assign({ showWarnings: false }, options)); + super(connection, sequelize, { showWarnings: false, ...options }); } static formatBindParameters(sql, values, dialect) { const bindParam = []; - const replacementFunc = (match, key, values) => { - if (values[key] !== undefined) { - bindParam.push(values[key]); + const replacementFunc = (match, key, values_) => { + if (values_[key] !== undefined) { + bindParam.push(values_[key]); return '?'; } return undefined; @@ -27,48 +30,60 @@ class Query extends AbstractQuery { return [sql, bindParam.length > 0 ? bindParam : undefined]; } - run(sql, parameters) { + async run(sql, parameters) { this.sql = sql; const { connection, options } = this; - //do we need benchmark for this query execution const showWarnings = this.sequelize.options.showWarnings || options.showWarnings; const complete = this._logQuery(sql, debug, parameters); - return new Utils.Promise((resolve, reject) => { - const handler = (err, results) => { - complete(); + if (parameters) { + debug('parameters(%j)', parameters); + } - if (err) { - // MySQL automatically rolls-back transactions in the event of a deadlock - if (options.transaction && err.errno === 1213) { - options.transaction.finished = 'rollback'; - } - err.sql = sql; - err.parameters = parameters; + let results; + const errForStack = new Error(); - reject(this.formatError(err)); - } else { - resolve(results); - } - }; - if (parameters) { - debug('parameters(%j)', parameters); - connection.execute(sql, parameters, handler).setMaxListeners(100); + try { + if (parameters && parameters.length) { + results = await new Promise((resolve, reject) => { + connection + .execute(sql, parameters, (error, result) => error ? reject(error) : resolve(result)) + .setMaxListeners(100); + }); } else { - connection.query({ sql }, handler).setMaxListeners(100); + results = await new Promise((resolve, reject) => { + connection + .query({ sql }, (error, result) => error ? reject(error) : resolve(result)) + .setMaxListeners(100); + }); } - }) - // Log warnings if we've got them. - .then(results => { - if (showWarnings && results && results.warningStatus > 0) { - return this.logWarnings(results); + } catch (error) { + if (options.transaction && error.errno === ER_DEADLOCK) { + // MySQL automatically rolls-back transactions in the event of a deadlock. + // However, we still initiate a manual rollback to ensure the connection gets released - see #13102. + try { + await options.transaction.rollback(); + } catch (error_) { + // Ignore errors - since MySQL automatically rolled back, we're + // not that worried about this redundant rollback failing. } - return results; - }) - // Return formatted results... - .then(results => this.formatResults(results)); + + options.transaction.finished = 'rollback'; + } + + error.sql = sql; + error.parameters = parameters; + throw this.formatError(error, errForStack.stack); + } finally { + complete(); + } + + if (showWarnings && results && results.warningStatus > 0) { + await this.logWarnings(results); + } + return this.formatResults(results); } /** @@ -95,7 +110,7 @@ class Query extends AbstractQuery { this.handleInsertQuery(data); if (!this.instance) { - // handle bulkCreate AI primiary key + // handle bulkCreate AI primary key if ( data.constructor.name === 'ResultSetHeader' && this.model @@ -130,7 +145,8 @@ class Query extends AbstractQuery { allowNull: _result.Null === 'YES', defaultValue: _result.Default, primaryKey: _result.Key === 'PRI', - autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') && _result.Extra.toLowerCase() === 'auto_increment', + autoIncrement: Object.prototype.hasOwnProperty.call(_result, 'Extra') + && _result.Extra.toLowerCase() === 'auto_increment', comment: _result.Comment ? _result.Comment : null }; } @@ -142,7 +158,7 @@ class Query extends AbstractQuery { if (this.isCallQuery()) { return data[0]; } - if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery() || this.isUpsertQuery()) { + if (this.isBulkUpdateQuery() || this.isBulkDeleteQuery()) { return data.affectedRows; } if (this.isVersionQuery()) { @@ -151,6 +167,9 @@ class Query extends AbstractQuery { if (this.isForeignKeysQuery()) { return data; } + if (this.isUpsertQuery()) { + return [result, data.affectedRows === 1]; + } if (this.isInsertQuery() || this.isUpdateQuery()) { return [result, data.affectedRows]; } @@ -165,39 +184,40 @@ class Query extends AbstractQuery { return result; } - logWarnings(results) { - return this.run('SHOW WARNINGS').then(warningResults => { - const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `; - const messages = []; - for (const _warningRow of warningResults) { - if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') continue; - for (const _warningResult of _warningRow) { - if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { - messages.push(_warningResult.Message); - } else { - for (const _objectKey of _warningResult.keys()) { - messages.push([_objectKey, _warningResult[_objectKey]].join(': ')); - } + async logWarnings(results) { + const warningResults = await this.run('SHOW WARNINGS'); + const warningMessage = `MySQL Warnings (${this.connection.uuid || 'default'}): `; + const messages = []; + for (const _warningRow of warningResults) { + if (_warningRow === undefined || typeof _warningRow[Symbol.iterator] !== 'function') { + continue; + } + for (const _warningResult of _warningRow) { + if (Object.prototype.hasOwnProperty.call(_warningResult, 'Message')) { + messages.push(_warningResult.Message); + } else { + for (const _objectKey of _warningResult.keys()) { + messages.push([_objectKey, _warningResult[_objectKey]].join(': ')); } } } + } - this.sequelize.log(warningMessage + messages.join('; '), this.options); + this.sequelize.log(warningMessage + messages.join('; '), this.options); - return results; - }); + return results; } - formatError(err) { + formatError(err, errStack) { const errCode = err.errno || err.code; switch (errCode) { - case 1062: { + case ER_DUP_ENTRY: { const match = err.message.match(/Duplicate entry '([\s\S]*)' for key '?((.|\s)*?)'?$/); let fields = {}; let message = 'Validation error'; const values = match ? match[1].split('-') : undefined; - const fieldKey = match ? match[2] : undefined; + const fieldKey = match ? match[2].split('.').pop() : undefined; const fieldVal = match ? match[1] : undefined; const uniqueKey = this.model && this.model.uniqueKeys[fieldKey]; @@ -220,28 +240,31 @@ class Query extends AbstractQuery { )); }); - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } - case 1451: - case 1452: { + case ER_ROW_IS_REFERENCED: + case ER_NO_REFERENCED_ROW: { // e.g. CONSTRAINT `example_constraint_name` FOREIGN KEY (`example_id`) REFERENCES `examples` (`id`) - const match = err.message.match(/CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/); + const match = err.message.match( + /CONSTRAINT ([`"])(.*)\1 FOREIGN KEY \(\1(.*)\1\) REFERENCES \1(.*)\1 \(\1(.*)\1\)/ + ); const quoteChar = match ? match[1] : '`'; const fields = match ? match[3].split(new RegExp(`${quoteChar}, *${quoteChar}`)) : undefined; return new sequelizeErrors.ForeignKeyConstraintError({ - reltype: String(errCode) === '1451' ? 'parent' : 'child', + reltype: String(errCode) === String(ER_ROW_IS_REFERENCED) ? 'parent' : 'child', table: match ? match[4] : undefined, fields, value: fields && fields.length && this.instance && this.instance[fields[0]] || undefined, index: match ? match[2] : undefined, - parent: err + parent: err, + stack: errStack }); } default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/dialects/parserStore.js b/lib/dialects/parserStore.js new file mode 100644 index 000000000000..b8bbb8df6d15 --- /dev/null +++ b/lib/dialects/parserStore.js @@ -0,0 +1,23 @@ +'use strict'; + +const stores = new Map(); + +module.exports = dialect => { + if (!stores.has(dialect)) { + stores.set(dialect, new Map()); + } + + return { + clear() { + stores.get(dialect).clear(); + }, + refresh(dataType) { + for (const type of dataType.types[dialect]) { + stores.get(dialect).set(type, dataType.parse); + } + }, + get(type) { + return stores.get(dialect).get(type); + } + }; +}; diff --git a/lib/dialects/postgres/connection-manager.js b/lib/dialects/postgres/connection-manager.js index 6e000b4603f2..841b9e65f9a3 100644 --- a/lib/dialects/postgres/connection-manager.js +++ b/lib/dialects/postgres/connection-manager.js @@ -4,11 +4,11 @@ const _ = require('lodash'); const AbstractConnectionManager = require('../abstract/connection-manager'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('connection:pg'); -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const semver = require('semver'); const dataTypes = require('../../data-types'); const moment = require('moment-timezone'); +const { promisify } = require('util'); class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { @@ -83,7 +83,7 @@ class ConnectionManager extends AbstractConnectionManager { return this.lib.types.getTypeParser(oid, ...args); } - connect(config) { + async connect(config) { config.user = config.username; const connectionConfig = _.pick(config, [ 'user', 'password', 'host', 'database', 'port' @@ -99,7 +99,7 @@ class ConnectionManager extends AbstractConnectionManager { // see [http://www.postgresql.org/docs/9.3/static/runtime-config-logging.html#GUC-APPLICATION-NAME] 'application_name', // choose the SSL mode with the PGSSLMODE environment variable - // object format: [https://github.com/brianc/node-postgres/blob/master/lib/connection.js#L79] + // object format: [https://github.com/brianc/node-postgres/blob/ee19e74ffa6309c9c5e8e01746261a8f651661f8/lib/connection.js#L79] // see also [http://www.postgresql.org/docs/9.3/static/libpq-ssl.html] 'ssl', // In addition to the values accepted by the corresponding server, @@ -113,12 +113,19 @@ class ConnectionManager extends AbstractConnectionManager { // This should help with backends incorrectly considering idle clients to be dead and prematurely disconnecting them. // this feature has been added in pg module v6.0.0, check pg/CHANGELOG.md 'keepAlive', - // Times out queries after a set time in milliseconds. Added in pg v7.3 - 'statement_timeout' + // Times out queries after a set time in milliseconds in the database end. Added in pg v7.3 + 'statement_timeout', + // Times out queries after a set time in milliseconds in client end, query would be still running in database end. + 'query_timeout', + // Terminate any session with an open transaction that has been idle for longer than the specified duration in milliseconds. Added in pg v7.17.0 only supported in postgres >= 10 + 'idle_in_transaction_session_timeout', + // Postgres allows additional session variables to be configured in the connection string in the `options` param. + // see [https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNECT-OPTIONS] + 'options' ])); } - return new Promise((resolve, reject) => { + const connection = await new Promise((resolve, reject) => { let responded = false; const connection = new this.lib.Client(connectionConfig); @@ -130,7 +137,7 @@ class ConnectionManager extends AbstractConnectionManager { const version = semver.coerce(message.parameterValue).version; this.sequelize.options.databaseVersion = semver.valid(version) ? version - : this.defaultVersion; + : this.dialect.defaultVersion; } break; case 'standard_conforming_strings': @@ -191,73 +198,71 @@ class ConnectionManager extends AbstractConnectionManager { resolve(connection); } }); - }).tap(connection => { - let query = ''; - - if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') { - // Disable escape characters in strings - // see https://github.com/sequelize/sequelize/issues/3545 (security issue) - // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS - query += 'SET standard_conforming_strings=on;'; - } + }); - if (this.sequelize.options.clientMinMessages !== false) { - query += `SET client_min_messages TO ${this.sequelize.options.clientMinMessages};`; - } + let query = ''; - if (!this.sequelize.config.keepDefaultTimezone) { - const isZone = !!moment.tz.zone(this.sequelize.options.timezone); - if (isZone) { - query += `SET TIME ZONE '${this.sequelize.options.timezone}';`; - } else { - query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`; - } - } + if (this.sequelize.options.standardConformingStrings !== false && connection['standard_conforming_strings'] !== 'on') { + // Disable escape characters in strings + // see https://github.com/sequelize/sequelize/issues/3545 (security issue) + // see https://www.postgresql.org/docs/current/static/runtime-config-compatible.html#GUC-STANDARD-CONFORMING-STRINGS + query += 'SET standard_conforming_strings=on;'; + } - if (query) { - return connection.query(query); - } - }).tap(connection => { - if (Object.keys(this.nameOidMap).length === 0 && - this.enumOids.oids.length === 0 && - this.enumOids.arrayOids.length === 0) { - return this._refreshDynamicOIDs(connection); + if (this.sequelize.options.clientMinMessages !== false) { + query += `SET client_min_messages TO ${this.sequelize.options.clientMinMessages};`; + } + + if (!this.sequelize.config.keepDefaultTimezone) { + const isZone = !!moment.tz.zone(this.sequelize.options.timezone); + if (isZone) { + query += `SET TIME ZONE '${this.sequelize.options.timezone}';`; + } else { + query += `SET TIME ZONE INTERVAL '${this.sequelize.options.timezone}' HOUR TO MINUTE;`; } - }).tap(connection => { - // Don't let a Postgres restart (or error) to take down the whole app - connection.on('error', error => { - connection._invalid = true; - debug(`connection error ${error.code || error.message}`); - this.pool.destroy(connection); - }); + } + + if (query) { + await connection.query(query); + } + if (Object.keys(this.nameOidMap).length === 0 && + this.enumOids.oids.length === 0 && + this.enumOids.arrayOids.length === 0) { + await this._refreshDynamicOIDs(connection); + } + // Don't let a Postgres restart (or error) to take down the whole app + connection.on('error', error => { + connection._invalid = true; + debug(`connection error ${error.code || error.message}`); + this.pool.destroy(connection); }); + + return connection; } - disconnect(connection) { + async disconnect(connection) { if (connection._ending) { debug('connection tried to disconnect but was already at ENDING state'); - return Promise.resolve(); + return; } - return Promise.fromCallback(callback => connection.end(callback)); + return await promisify(callback => connection.end(callback))(); } validate(connection) { return !connection._invalid && !connection._ending; } - _refreshDynamicOIDs(connection) { + async _refreshDynamicOIDs(connection) { const databaseVersion = this.sequelize.options.databaseVersion; const supportedVersion = '8.3.0'; // Check for supported version if ( (databaseVersion && semver.gte(databaseVersion, supportedVersion)) === false) { - return Promise.resolve(); + return; } - // Refresh dynamic OIDs for some types - // These include Geometry / Geography / HStore / Enum / Citext / Range - return (connection || this.sequelize).query( + const results = await (connection || this.sequelize).query( 'WITH ranges AS (' + ' SELECT pg_range.rngtypid, pg_type.typname AS rngtypname,' + ' pg_type.typarray AS rngtyparray, pg_range.rngsubtype' + @@ -267,46 +272,46 @@ class ConnectionManager extends AbstractConnectionManager { ' ranges.rngtypname, ranges.rngtypid, ranges.rngtyparray' + ' FROM pg_type LEFT OUTER JOIN ranges ON pg_type.oid = ranges.rngsubtype' + ' WHERE (pg_type.typtype IN(\'b\', \'e\'));' - ).then(results => { - let result = Array.isArray(results) ? results.pop() : results; - - // When searchPath is prepended then two statements are executed and the result is - // an array of those two statements. First one is the SET search_path and second is - // the SELECT query result. - if (Array.isArray(result)) { - if (result[0].command === 'SET') { - result = result.pop(); - } + ); + + let result = Array.isArray(results) ? results.pop() : results; + + // When searchPath is prepended then two statements are executed and the result is + // an array of those two statements. First one is the SET search_path and second is + // the SELECT query result. + if (Array.isArray(result)) { + if (result[0].command === 'SET') { + result = result.pop(); } + } - const newNameOidMap = {}; - const newEnumOids = { oids: [], arrayOids: [] }; + const newNameOidMap = {}; + const newEnumOids = { oids: [], arrayOids: [] }; - for (const row of result.rows) { - // Mapping enums, handled separatedly - if (row.typtype === 'e') { - newEnumOids.oids.push(row.oid); - if (row.typarray) newEnumOids.arrayOids.push(row.typarray); - continue; - } + for (const row of result.rows) { + // Mapping enums, handled separatedly + if (row.typtype === 'e') { + newEnumOids.oids.push(row.oid); + if (row.typarray) newEnumOids.arrayOids.push(row.typarray); + continue; + } - // Mapping base types and their arrays - newNameOidMap[row.typname] = { oid: row.oid }; - if (row.typarray) newNameOidMap[row.typname].arrayOid = row.typarray; + // Mapping base types and their arrays + newNameOidMap[row.typname] = { oid: row.oid }; + if (row.typarray) newNameOidMap[row.typname].arrayOid = row.typarray; - // Mapping ranges(of base types) and their arrays - if (row.rngtypid) { - newNameOidMap[row.typname].rangeOid = row.rngtypid; - if (row.rngtyparray) newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray; - } + // Mapping ranges(of base types) and their arrays + if (row.rngtypid) { + newNameOidMap[row.typname].rangeOid = row.rngtypid; + if (row.rngtyparray) newNameOidMap[row.typname].arrayRangeOid = row.rngtyparray; } + } - // Replace all OID mappings. Avoids temporary empty OID mappings. - this.nameOidMap = newNameOidMap; - this.enumOids = newEnumOids; + // Replace all OID mappings. Avoids temporary empty OID mappings. + this.nameOidMap = newNameOidMap; + this.enumOids = newEnumOids; - this.refreshTypeParser(dataTypes.postgres); - }); + this.refreshTypeParser(dataTypes.postgres); } _clearDynamicOIDs() { diff --git a/lib/dialects/postgres/data-types.js b/lib/dialects/postgres/data-types.js index 3b5f222648a9..fc9ab0f420a7 100644 --- a/lib/dialects/postgres/data-types.js +++ b/lib/dialects/postgres/data-types.js @@ -28,6 +28,7 @@ module.exports = BaseTypes => { * oids: [oid], * array_oids: [oid] * } + * * @see oid here https://github.com/lib/pq/blob/master/oid/types.go */ @@ -35,6 +36,7 @@ module.exports = BaseTypes => { BaseTypes.CIDR.types.postgres = ['cidr']; BaseTypes.INET.types.postgres = ['inet']; BaseTypes.MACADDR.types.postgres = ['macaddr']; + BaseTypes.TSVECTOR.types.postgres = ['tsvector']; BaseTypes.JSON.types.postgres = ['json']; BaseTypes.JSONB.types.postgres = ['jsonb']; BaseTypes.TIME.types.postgres = ['time']; @@ -306,7 +308,7 @@ module.exports = BaseTypes => { } static parse(value) { const b = Buffer.from(value, 'hex'); - return wkx.Geometry.parse(b).toGeoJSON(); + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); } _stringify(value, options) { return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; @@ -333,7 +335,7 @@ module.exports = BaseTypes => { } static parse(value) { const b = Buffer.from(value, 'hex'); - return wkx.Geometry.parse(b).toGeoJSON(); + return wkx.Geometry.parse(b).toGeoJSON({ shortCrs: true }); } _stringify(value, options) { return `ST_GeomFromGeoJSON(${options.escape(JSON.stringify(value))})`; @@ -478,7 +480,7 @@ module.exports = BaseTypes => { if (this.type instanceof BaseTypes.ENUM) { castKey = `${Utils.addTicks( - Utils.generateEnumName(options.field.Model.getTableName(), options.field.fieldName), + Utils.generateEnumName(options.field.Model.getTableName(), options.field.field), '"' ) }[]`; } diff --git a/lib/dialects/postgres/index.js b/lib/dialects/postgres/index.js index 37ec80533931..562939ad7414 100644 --- a/lib/dialects/postgres/index.js +++ b/lib/dialects/postgres/index.js @@ -6,59 +6,69 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').postgres; +const { PostgresQueryInterface } = require('./query-interface'); class PostgresDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + this.queryInterface = new PostgresQueryInterface( + sequelize, + this.queryGenerator + ); } } -PostgresDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'DEFAULT VALUES': true, - 'EXCEPTION': true, - 'ON DUPLICATE KEY': false, - 'ORDER NULLS': true, - returnValues: { - returning: true - }, - bulkDefault: true, - schemas: true, - lock: true, - lockOf: true, - lockKey: true, - lockOuterJoinFailure: true, - skipLocked: true, - forShare: 'FOR SHARE', - index: { - concurrently: true, - using: 2, - where: true, - functionBased: true - }, - inserts: { - onConflictDoNothing: ' ON CONFLICT DO NOTHING', - updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' - }, - NUMERIC: true, - ARRAY: true, - RANGE: true, - GEOMETRY: true, - REGEXP: true, - GEOGRAPHY: true, - JSON: true, - JSONB: true, - HSTORE: true, - deferrableConstraints: true, - searchPath: true -}); +PostgresDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + 'DEFAULT VALUES': true, + EXCEPTION: true, + 'ON DUPLICATE KEY': false, + 'ORDER NULLS': true, + returnValues: { + returning: true + }, + bulkDefault: true, + schemas: true, + lock: true, + lockOf: true, + lockKey: true, + lockOuterJoinFailure: true, + skipLocked: true, + forShare: 'FOR SHARE', + index: { + concurrently: true, + using: 2, + where: true, + functionBased: true, + operator: true + }, + inserts: { + onConflictDoNothing: ' ON CONFLICT DO NOTHING', + updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' + }, + NUMERIC: true, + ARRAY: true, + RANGE: true, + GEOMETRY: true, + REGEXP: true, + GEOGRAPHY: true, + JSON: true, + JSONB: true, + HSTORE: true, + TSVECTOR: true, + deferrableConstraints: true, + searchPath: true + } +); -ConnectionManager.prototype.defaultVersion = '9.4.0'; +PostgresDialect.prototype.defaultVersion = '9.5.0'; // minimum supported version PostgresDialect.prototype.Query = Query; PostgresDialect.prototype.DataTypes = DataTypes; PostgresDialect.prototype.name = 'postgres'; diff --git a/lib/dialects/postgres/query-generator.js b/lib/dialects/postgres/query-generator.js old mode 100755 new mode 100644 index a7d1c41d8639..f5d10c949298 --- a/lib/dialects/postgres/query-generator.js +++ b/lib/dialects/postgres/query-generator.js @@ -12,11 +12,12 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return `SET search_path to ${searchPath};`; } - createDatabaseQuery(databaseName, options = {}) { - options = Object.assign({ + createDatabaseQuery(databaseName, options) { + options = { encoding: null, - collate: null - }, options); + collate: null, + ...options + }; const values = { database: this.quoteTable(databaseName), @@ -55,8 +56,8 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return 'SHOW SERVER_VERSION'; } - createTableQuery(tableName, attributes, options = {}) { - options = Object.assign({}, options); + createTableQuery(tableName, attributes, options) { + options = { ...options }; //Postgres 9.0 does not support CREATE TABLE IF NOT EXISTS, 9.1 and above do const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0); @@ -109,7 +110,8 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${quotedTable} (${attributesClause})${comments}${columnComments};`; } - dropTableQuery(tableName, options = {}) { + dropTableQuery(tableName, options) { + options = options || {}; return `DROP TABLE IF EXISTS ${this.quoteTable(tableName)}${options.cascade ? ' CASCADE' : ''};`; } @@ -242,17 +244,19 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return super.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend); } - addColumnQuery(table, key, dataType) { - - const dbDataType = this.attributeToSQL(dataType, { context: 'addColumn', table, key }); + addColumnQuery(table, key, attribute) { + const dbDataType = this.attributeToSQL(attribute, { context: 'addColumn', table, key }); + const dataType = attribute.type || attribute; const definition = this.dataTypeMapping(table, key, dbDataType); const quotedKey = this.quoteIdentifier(key); const quotedTable = this.quoteTable(this.extractTableDetails(table)); let query = `ALTER TABLE ${quotedTable} ADD COLUMN ${quotedKey} ${definition};`; - if (dataType.type && dataType.type instanceof DataTypes.ENUM || dataType instanceof DataTypes.ENUM) { + if (dataType instanceof DataTypes.ENUM) { query = this.pgEnum(table, key, dataType) + query; + } else if (dataType.type && dataType.type instanceof DataTypes.ENUM) { + query = this.pgEnum(table, key, dataType.type) + query; } return query; @@ -331,36 +335,6 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return `CREATE OR REPLACE FUNCTION pg_temp.${fnName}(${parameters}) ${returns} AS $func$ BEGIN ${body} END; $func$ LANGUAGE ${language}; SELECT * FROM pg_temp.${fnName}();`; } - exceptionFn(fnName, tableName, parameters, main, then, when, returns, language) { - when = when || 'unique_violation'; - - const body = `${main} EXCEPTION WHEN ${when} THEN ${then};`; - - return this.fn(fnName, tableName, parameters, body, returns, language); - } - - upsertQuery(tableName, insertValues, updateValues, where, model, options) { - const primaryField = this.quoteIdentifier(model.primaryKeyField); - - const upsertOptions = { - ...options, - bindParam: false - }; - const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); - const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); - - insert.query = insert.query.replace('RETURNING *', `RETURNING ${primaryField} INTO primary_key`); - update.query = update.query.replace('RETURNING *', `RETURNING ${primaryField} INTO primary_key`); - - return this.exceptionFn( - 'sequelize_upsert', - tableName, - 'OUT created boolean, OUT primary_key text', - `${insert.query} created := true;`, - `${update.query}; created := false` - ); - } - truncateTableQuery(tableName, options = {}) { return [ `TRUNCATE ${this.quoteTable(tableName)}`, @@ -601,7 +575,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { for (const key in attributes) { const attribute = attributes[key]; - result[attribute.field || key] = this.attributeToSQL(attribute, Object.assign({ key }, options || {})); + result[attribute.field || key] = this.attributeToSQL(attribute, { key, ...options }); } return result; @@ -611,7 +585,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { const decodedEventType = this.decodeTriggerEventType(eventType); const eventSpec = this.expandTriggerEventSpec(fireOnSpec); const expandedOptions = this.expandOptions(optionsArray); - const paramList = this.expandFunctionParamList(functionParams); + const paramList = this._expandFunctionParamList(functionParams); return `CREATE ${this.triggerEventTypeIsConstraint(eventType)}TRIGGER ${this.quoteIdentifier(triggerName)} ${decodedEventType} ${ eventSpec} ON ${this.quoteTable(tableName)}${expandedOptions ? ` ${expandedOptions}` : ''} EXECUTE PROCEDURE ${functionName}(${paramList});`; @@ -628,8 +602,8 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { createFunction(functionName, params, returnType, language, body, optionsArray, options) { if (!functionName || !returnType || !language || !body) throw new Error('createFunction missing some parameters. Did you pass functionName, returnType, language and body?'); - const paramList = this.expandFunctionParamList(params); - const variableList = options && options.variables ? this.expandFunctionVariableList(options.variables) : ''; + const paramList = this._expandFunctionParamList(params); + const variableList = options && options.variables ? this._expandFunctionVariableList(options.variables) : ''; const expandedOptionsArray = this.expandOptions(optionsArray); const statement = options && options.force ? 'CREATE OR REPLACE FUNCTION' : 'CREATE FUNCTION'; @@ -640,34 +614,22 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { dropFunction(functionName, params) { if (!functionName) throw new Error('requires functionName'); // RESTRICT is (currently, as of 9.2) default but we'll be explicit - const paramList = this.expandFunctionParamList(params); + const paramList = this._expandFunctionParamList(params); return `DROP FUNCTION ${functionName}(${paramList}) RESTRICT;`; } renameFunction(oldFunctionName, params, newFunctionName) { - const paramList = this.expandFunctionParamList(params); + const paramList = this._expandFunctionParamList(params); return `ALTER FUNCTION ${oldFunctionName}(${paramList}) RENAME TO ${newFunctionName};`; } - databaseConnectionUri(config) { - let uri = `${config.protocol}://${config.user}:${config.password}@${config.host}`; - if (config.port) { - uri += `:${config.port}`; - } - uri += `/${config.database}`; - if (config.ssl) { - uri += `?ssl=${config.ssl}`; - } - return uri; - } - pgEscapeAndQuote(val) { return this.quoteIdentifier(Utils.removeTicks(this.escape(val), "'")); } - expandFunctionParamList(params) { + _expandFunctionParamList(params) { if (params === undefined || !Array.isArray(params)) { - throw new Error('expandFunctionParamList: function parameters array required, including an empty one for no arguments'); + throw new Error('_expandFunctionParamList: function parameters array required, including an empty one for no arguments'); } const paramList = []; @@ -689,9 +651,9 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return paramList.join(', '); } - expandFunctionVariableList(variables) { + _expandFunctionVariableList(variables) { if (!Array.isArray(variables)) { - throw new Error('expandFunctionVariableList: function variables must be an array'); + throw new Error('_expandFunctionVariableList: function variables must be an array'); } const variableDefinitions = []; variables.forEach(variable => { @@ -760,7 +722,9 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { }).join(' OR '); } - pgEnumName(tableName, attr, options = {}) { + pgEnumName(tableName, attr, options) { + options = options || {}; + const tableDetails = this.extractTableDetails(tableName, options); let enumName = Utils.addTicks(Utils.generateEnumName(tableDetails.tableName, attr), '"'); @@ -772,7 +736,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return enumName; } - pgListEnums(tableName, attrName, options = {}) { + pgListEnums(tableName, attrName, options) { let enumName = ''; const tableDetails = this.extractTableDetails(tableName, options); @@ -834,7 +798,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator { return []; } - matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/, '')); + matches = matches.map(m => m.replace(/",$/, '').replace(/,$/, '').replace(/(^"|"$)/g, '')); return matches.slice(0, -1); } diff --git a/lib/dialects/postgres/query-interface.js b/lib/dialects/postgres/query-interface.js index 14df6910e0af..19a07e467564 100644 --- a/lib/dialects/postgres/query-interface.js +++ b/lib/dialects/postgres/query-interface.js @@ -1,61 +1,57 @@ 'use strict'; const DataTypes = require('../../data-types'); -const Promise = require('../../promise'); const QueryTypes = require('../../query-types'); +const { QueryInterface } = require('../abstract/query-interface'); +const Utils = require('../../utils'); /** - Returns an object that handles Postgres special needs to do certain queries. - - @class QueryInterface - @static - @private + * The interface that Sequelize uses to talk with Postgres database */ - -/** +class PostgresQueryInterface extends QueryInterface { + /** * Ensure enum and their values. * - * @param {QueryInterface} qi * @param {string} tableName Name of table to create * @param {object} attributes Object representing a list of normalized table attributes * @param {object} [options] * @param {Model} [model] * - * @returns {Promise} - * @private + * @protected */ -function ensureEnums(qi, tableName, attributes, options, model) { - const keys = Object.keys(attributes); - const keyLen = keys.length; - - let sql = ''; - let promises = []; - let i = 0; - - for (i = 0; i < keyLen; i++) { - const attribute = attributes[keys[i]]; - const type = attribute.type; - - if ( - type instanceof DataTypes.ENUM || - type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM - ) { - sql = qi.QueryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); - promises.push(qi.sequelize.query( - sql, - Object.assign({}, options, { plain: true, raw: true, type: QueryTypes.SELECT }) - )); + async ensureEnums(tableName, attributes, options, model) { + const keys = Object.keys(attributes); + const keyLen = keys.length; + + let sql = ''; + let promises = []; + let i = 0; + + for (i = 0; i < keyLen; i++) { + const attribute = attributes[keys[i]]; + const type = attribute.type; + + if ( + type instanceof DataTypes.ENUM || + type instanceof DataTypes.ARRAY && type.type instanceof DataTypes.ENUM //ARRAY sub type is ENUM + ) { + sql = this.queryGenerator.pgListEnums(tableName, attribute.field || keys[i], options); + promises.push(this.sequelize.query( + sql, + { ...options, plain: true, raw: true, type: QueryTypes.SELECT } + )); + } } - } - return Promise.all(promises).then(results => { + const results = await Promise.all(promises); promises = []; let enumIdx = 0; // This little function allows us to re-use the same code that prepends or appends new value to enum array const addEnumValue = (field, value, relativeValue, position = 'before', spliceStart = promises.length) => { - // reset out after/before options since it's for every enum value - const valueOptions = { ...options, before: null, after: null }; + const valueOptions = { ...options }; + valueOptions.before = null; + valueOptions.after = null; switch (position) { case 'after': @@ -68,7 +64,7 @@ function ensureEnums(qi, tableName, attributes, options, model) { } promises.splice(spliceStart, 0, () => { - return qi.sequelize.query(qi.QueryGenerator.pgEnumAdd( + return this.sequelize.query(this.queryGenerator.pgEnumAdd( tableName, field, value, valueOptions ), valueOptions); }); @@ -87,10 +83,10 @@ function ensureEnums(qi, tableName, attributes, options, model) { // If the enum type doesn't exist then create it if (!results[enumIdx]) { promises.push(() => { - return qi.sequelize.query(qi.QueryGenerator.pgEnum(tableName, field, enumType, options), Object.assign({}, options, { raw: true })); + return this.sequelize.query(this.queryGenerator.pgEnum(tableName, field, enumType, options), { ...options, raw: true }); }); } else if (!!results[enumIdx] && !!model) { - const enumVals = qi.QueryGenerator.fromArray(results[enumIdx].enum_value); + const enumVals = this.queryGenerator.fromArray(results[enumIdx].enum_value); const vals = enumType.values; // Going through already existing values allows us to make queries that depend on those values @@ -139,16 +135,112 @@ function ensureEnums(qi, tableName, attributes, options, model) { } } - return promises - .reduce((promise, asyncFunction) => promise.then(asyncFunction), Promise.resolve()) - .tap(() => { - // If ENUM processed, then refresh OIDs - if (promises.length) { - return qi.sequelize.dialect.connectionManager._refreshDynamicOIDs(); - } - }); - }); -} + const result = await promises + .reduce(async (promise, asyncFunction) => await asyncFunction(await promise), Promise.resolve()); + + // If ENUM processed, then refresh OIDs + if (promises.length) { + await this.sequelize.dialect.connectionManager._refreshDynamicOIDs(); + } + return result; + } + + /** + * @override + */ + async getForeignKeyReferencesForTable(table, options) { + const queryOptions = { + ...options, + type: QueryTypes.FOREIGNKEYS + }; + + // postgres needs some special treatment as those field names returned are all lowercase + // in order to keep same result with other dialects. + const query = this.queryGenerator.getForeignKeyReferencesQuery(table.tableName || table, this.sequelize.config.database); + const result = await this.sequelize.query(query, queryOptions); + return result.map(Utils.camelizeObjectKeys); + } + + /** + * Drop specified enum from database (Postgres only) + * + * @param {string} [enumName] Enum name to drop + * @param {object} options Query options + * + * @returns {Promise} + */ + async dropEnum(enumName, options) { + options = options || {}; + + return this.sequelize.query( + this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(enumName)), + { ...options, raw: true } + ); + } + /** + * Drop all enums from database (Postgres only) + * + * @param {object} options Query options + * + * @returns {Promise} + */ + async dropAllEnums(options) { + options = options || {}; + + const enums = await this.pgListEnums(null, options); + + return await Promise.all(enums.map(result => this.sequelize.query( + this.queryGenerator.pgEnumDrop(null, null, this.queryGenerator.pgEscapeAndQuote(result.enum_name)), + { ...options, raw: true } + ))); + } + + /** + * List all enums (Postgres only) + * + * @param {string} [tableName] Table whose enum to list + * @param {object} [options] Query options + * + * @returns {Promise} + */ + async pgListEnums(tableName, options) { + options = options || {}; + const sql = this.queryGenerator.pgListEnums(tableName); + return this.sequelize.query(sql, { ...options, plain: false, raw: true, type: QueryTypes.SELECT }); + } + + /** + * Since postgres has a special case for enums, we should drop the related + * enum type within the table and attribute + * + * @override + */ + async dropTable(tableName, options) { + await super.dropTable(tableName, options); + const promises = []; + const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); + + if (!instanceTable) { + // Do nothing when model is not available + return; + } + + const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName; + + const keys = Object.keys(instanceTable.rawAttributes); + const keyLen = keys.length; + + for (let i = 0; i < keyLen; i++) { + if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { + const sql = this.queryGenerator.pgEnumDrop(getTableName, keys[i]); + options.supportsSearchPath = false; + promises.push(this.sequelize.query(sql, { ...options, raw: true })); + } + } + + await Promise.all(promises); + } +} -exports.ensureEnums = ensureEnums; +exports.PostgresQueryInterface = PostgresQueryInterface; diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index a9f0af150ad8..3640bda38648 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -2,7 +2,6 @@ const AbstractQuery = require('../abstract/query'); const QueryTypes = require('../../query-types'); -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const _ = require('lodash'); const { logger } = require('../../utils/logger'); @@ -47,12 +46,24 @@ class Query extends AbstractQuery { return [sql, bindParam]; } - run(sql, parameters) { + async run(sql, parameters) { const { connection } = this; if (!_.isEmpty(this.options.searchPath)) { - sql = this.sequelize.getQueryInterface().QueryGenerator.setSearchPath(this.options.searchPath) + sql; + sql = this.sequelize.getQueryInterface().queryGenerator.setSearchPath(this.options.searchPath) + sql; } + + if (this.sequelize.options.minifyAliases && this.options.includeAliases) { + _.toPairs(this.options.includeAliases) + // Sorting to replace the longest aliases first to prevent alias collision + .sort((a, b) => b[1].length - a[1].length) + .forEach(([alias, original]) => { + const reg = new RegExp(_.escapeRegExp(original), 'g'); + + sql = sql.replace(reg, alias); + }); + } + this.sql = sql; const query = parameters && parameters.length @@ -61,7 +72,12 @@ class Query extends AbstractQuery { const complete = this._logQuery(sql, debug, parameters); - return query.catch(err => { + let queryResult; + const errForStack = new Error(); + + try { + queryResult = await query; + } catch (err) { // set the client so that it will be reaped if the connection resets while executing if (err.code === 'ECONNRESET') { connection._invalid = true; @@ -69,211 +85,222 @@ class Query extends AbstractQuery { err.sql = sql; err.parameters = parameters; - throw this.formatError(err); - }) - .then(queryResult => { - complete(); - - let rows = Array.isArray(queryResult) - ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), []) - : queryResult.rows; - const rowCount = Array.isArray(queryResult) - ? queryResult.reduce( - (count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, - 0 - ) - : queryResult.rowCount; - - if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) { - rows = rows - .map(row => _.toPairs(row) - .reduce((acc, [key, value]) => { - const mapping = this.options.aliasesMapping.get(key); - acc[mapping || key] = value; - return acc; - }, {}) - ); - } + throw this.formatError(err, errForStack.stack); + } - const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); - const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN'); + complete(); + + let rows = Array.isArray(queryResult) + ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), []) + : queryResult.rows; + const rowCount = Array.isArray(queryResult) + ? queryResult.reduce( + (count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, + 0 + ) + : queryResult.rowCount || 0; + + if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) { + rows = rows + .map(row => _.toPairs(row) + .reduce((acc, [key, value]) => { + const mapping = this.options.aliasesMapping.get(key); + acc[mapping || key] = value; + return acc; + }, {}) + ); + } - if (isRelNameQuery) { - return rows.map(row => ({ - name: row.relname, - tableName: row.relname.split('_')[0] - })); - } - if (isTableNameQuery) { - return rows.map(row => Object.values(row)); - } + const isTableNameQuery = sql.startsWith('SELECT table_name FROM information_schema.tables'); + const isRelNameQuery = sql.startsWith('SELECT relname FROM pg_class WHERE oid IN'); - if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { - if (rows[0].sequelize_caught_exception !== null) { - throw this.formatError({ - code: '23505', - detail: rows[0].sequelize_caught_exception - }); - } - for (const row of rows) { - delete row.sequelize_caught_exception; - } - } + if (isRelNameQuery) { + return rows.map(row => ({ + name: row.relname, + tableName: row.relname.split('_')[0] + })); + } + if (isTableNameQuery) { + return rows.map(row => Object.values(row)); + } - if (this.isShowIndexesQuery()) { - for (const row of rows) { - const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(','); - - // Map column index in table to column name - const columns = _.zipObject( - row.column_indexes, - this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.column_names) - ); - delete row.column_indexes; - delete row.column_names; - - let field; - let attribute; - - // Indkey is the order of attributes in the index, specified by a string of attribute indexes - row.fields = row.indkey.split(' ').map((indKey, index) => { - field = columns[indKey]; - // for functional indices indKey = 0 - if (!field) { - return null; - } - attribute = attributes[index]; - return { - attribute: field, - collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined, - order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined, - length: undefined - }; - }).filter(n => n !== null); - delete row.columns; + if (rows[0] && rows[0].sequelize_caught_exception !== undefined) { + if (rows[0].sequelize_caught_exception !== null) { + throw this.formatError({ + sql, + parameters, + code: '23505', + detail: rows[0].sequelize_caught_exception + }); + } + for (const row of rows) { + delete row.sequelize_caught_exception; + } + } + + if (this.isShowIndexesQuery()) { + for (const row of rows) { + const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(','); + + // Map column index in table to column name + const columns = _.zipObject( + row.column_indexes, + this.sequelize.getQueryInterface().queryGenerator.fromArray(row.column_names) + ); + delete row.column_indexes; + delete row.column_names; + + let field; + let attribute; + + // Indkey is the order of attributes in the index, specified by a string of attribute indexes + row.fields = row.indkey.split(' ').map((indKey, index) => { + field = columns[indKey]; + // for functional indices indKey = 0 + if (!field) { + return null; } - return rows; - } - if (this.isForeignKeysQuery()) { - const result = []; - for (const row of rows) { - let defParts; - if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) { - row.id = row.constraint_name; - row.table = defParts[2]; - row.from = defParts[1]; - row.to = defParts[3]; - let i; - for (i = 5; i <= 8; i += 3) { - if (/(UPDATE|DELETE)/.test(defParts[i])) { - row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1]; - } - } + attribute = attributes[index]; + return { + attribute: field, + collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : undefined, + order: attribute.includes('DESC') ? 'DESC' : attribute.includes('ASC') ? 'ASC' : undefined, + length: undefined + }; + }).filter(n => n !== null); + delete row.columns; + } + return rows; + } + if (this.isForeignKeysQuery()) { + const result = []; + for (const row of rows) { + let defParts; + if (row.condef !== undefined && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) { + row.id = row.constraint_name; + row.table = defParts[2]; + row.from = defParts[1]; + row.to = defParts[3]; + let i; + for (i = 5; i <= 8; i += 3) { + if (/(UPDATE|DELETE)/.test(defParts[i])) { + row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1]; } - result.push(row); } - return result; } - if (this.isSelectQuery()) { - let result = rows; - // Postgres will treat tables as case-insensitive, so fix the case - // of the returned values to match attributes - if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { - const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => { - m[k.toLowerCase()] = k; - return m; - }, {}); - result = rows.map(row => { - return _.mapKeys(row, (value, key) => { - const targetAttr = attrsMap[key]; - if (typeof targetAttr === 'string' && targetAttr !== key) { - return targetAttr; - } - return key; - }); - }); + result.push(row); + } + return result; + } + if (this.isSelectQuery()) { + let result = rows; + // Postgres will treat tables as case-insensitive, so fix the case + // of the returned values to match attributes + if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) { + const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => { + m[k.toLowerCase()] = k; + return m; + }, {}); + result = rows.map(row => { + return _.mapKeys(row, (value, key) => { + const targetAttr = attrsMap[key]; + if (typeof targetAttr === 'string' && targetAttr !== key) { + return targetAttr; + } + return key; + }); + }); + } + return this.handleSelectQuery(result); + } + if (QueryTypes.DESCRIBE === this.options.type) { + const result = {}; + + for (const row of rows) { + result[row.Field] = { + type: row.Type.toUpperCase(), + allowNull: row.Null === 'YES', + defaultValue: row.Default, + comment: row.Comment, + special: row.special ? this.sequelize.getQueryInterface().queryGenerator.fromArray(row.special) : [], + primaryKey: row.Constraint === 'PRIMARY KEY' + }; + + if (result[row.Field].type === 'BOOLEAN') { + result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue]; + + if (result[row.Field].defaultValue === undefined) { + result[row.Field].defaultValue = null; } - return this.handleSelectQuery(result); } - if (QueryTypes.DESCRIBE === this.options.type) { - const result = {}; - - for (const row of rows) { - result[row.Field] = { - type: row.Type.toUpperCase(), - allowNull: row.Null === 'YES', - defaultValue: row.Default, - comment: row.Comment, - special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [], - primaryKey: row.Constraint === 'PRIMARY KEY' - }; - - if (result[row.Field].type === 'BOOLEAN') { - result[row.Field].defaultValue = { 'false': false, 'true': true }[result[row.Field].defaultValue]; - - if (result[row.Field].defaultValue === undefined) { - result[row.Field].defaultValue = null; - } - } - if (typeof result[row.Field].defaultValue === 'string') { - result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, ''); + if (typeof result[row.Field].defaultValue === 'string') { + result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, ''); - if (result[row.Field].defaultValue.includes('::')) { - const split = result[row.Field].defaultValue.split('::'); - if (split[1].toLowerCase() !== 'regclass)') { - result[row.Field].defaultValue = split[0]; - } - } + if (result[row.Field].defaultValue.includes('::')) { + const split = result[row.Field].defaultValue.split('::'); + if (split[1].toLowerCase() !== 'regclass)') { + result[row.Field].defaultValue = split[0]; } } - - return result; - } - if (this.isVersionQuery()) { - return rows[0].server_version; } - if (this.isShowOrDescribeQuery()) { - return rows; - } - if (QueryTypes.BULKUPDATE === this.options.type) { - if (!this.options.returning) { - return parseInt(rowCount, 10); - } - return this.handleSelectQuery(rows); - } - if (QueryTypes.BULKDELETE === this.options.type) { - return parseInt(rowCount, 10); - } - if (this.isUpsertQuery()) { - return rows[0]; + } + + return result; + } + if (this.isVersionQuery()) { + return rows[0].server_version; + } + if (this.isShowOrDescribeQuery()) { + return rows; + } + if (QueryTypes.BULKUPDATE === this.options.type) { + if (!this.options.returning) { + return parseInt(rowCount, 10); + } + return this.handleSelectQuery(rows); + } + if (QueryTypes.BULKDELETE === this.options.type) { + return parseInt(rowCount, 10); + } + if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) { + if (this.instance && this.instance.dataValues) { + // If we are creating an instance, and we get no rows, the create failed but did not throw. + // This probably means a conflict happened and was ignored, to avoid breaking a transaction. + if (this.isInsertQuery() && rowCount === 0) { + throw new sequelizeErrors.EmptyResultError(); } - if (this.isInsertQuery() || this.isUpdateQuery()) { - if (this.instance && this.instance.dataValues) { - for (const key in rows[0]) { - if (Object.prototype.hasOwnProperty.call(rows[0], key)) { - const record = rows[0][key]; - const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + for (const key in rows[0]) { + if (Object.prototype.hasOwnProperty.call(rows[0], key)) { + const record = rows[0][key]; - this.instance.dataValues[attr && attr.fieldName || key] = record; - } - } - } + const attr = _.find(this.model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); - return [ - this.instance || rows && (this.options.plain && rows[0] || rows) || undefined, - rowCount - ]; - } - if (this.isRawQuery()) { - return [rows, queryResult]; + this.instance.dataValues[attr && attr.fieldName || key] = record; + } } - return rows; - }); + } + + if (this.isUpsertQuery()) { + return [ + this.instance, + null + ]; + } + + return [ + this.instance || rows && (this.options.plain && rows[0] || rows) || undefined, + rowCount + ]; + } + if (this.isRawQuery()) { + return [rows, queryResult]; + } + return rows; } - formatError(err) { + formatError(err, errStack) { let match; let table; let index; @@ -292,7 +319,14 @@ class Query extends AbstractQuery { table = errMessage.match(/on table "(.+?)"/); table = table ? table[1] : undefined; - return new sequelizeErrors.ForeignKeyConstraintError({ message: errMessage, fields: null, index, table, parent: err }); + return new sequelizeErrors.ForeignKeyConstraintError({ + message: errMessage, + fields: null, + index, + table, + parent: err, + stack: errStack + }); case '23505': // there are multiple different formats of error messages for this error code // this regex should check at least two @@ -321,12 +355,13 @@ class Query extends AbstractQuery { }); } - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } return new sequelizeErrors.UniqueConstraintError({ message: errMessage, - parent: err + parent: err, + stack: errStack }); case '23P01': @@ -342,7 +377,8 @@ class Query extends AbstractQuery { constraint: err.constraint, fields, table: err.table, - parent: err + parent: err, + stack: errStack }); case '42704': @@ -358,12 +394,13 @@ class Query extends AbstractQuery { constraint: index, fields, table, - parent: err + parent: err, + stack: errStack }); } // falls through default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } @@ -376,7 +413,6 @@ class Query extends AbstractQuery { } } - module.exports = Query; module.exports.Query = Query; module.exports.default = Query; diff --git a/lib/dialects/sqlite/connection-manager.js b/lib/dialects/sqlite/connection-manager.js index 9b8f6e9d804a..eb6017bbd197 100644 --- a/lib/dialects/sqlite/connection-manager.js +++ b/lib/dialects/sqlite/connection-manager.js @@ -1,11 +1,14 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const AbstractConnectionManager = require('../abstract/connection-manager'); -const Promise = require('../../promise'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('connection:sqlite'); const dataTypes = require('../../data-types').sqlite; const sequelizeErrors = require('../../errors'); +const parserStore = require('../parserStore')('sqlite'); +const { promisify } = require('util'); class ConnectionManager extends AbstractConnectionManager { constructor(dialect, sequelize) { @@ -17,54 +20,79 @@ class ConnectionManager extends AbstractConnectionManager { delete this.sequelize.options.host; } - this.connections = new Map(); - this.lib = this._loadDialectModule('sqlite3').verbose(); + this.connections = {}; + this.lib = this._loadDialectModule('sqlite3'); this.refreshTypeParser(dataTypes); } - _onProcessExit() { - const promises = []; - for (const conn of this.connections.values()) { - promises.push(Promise.fromCallback(callback => conn.close(callback))); - } + async _onProcessExit() { + await Promise.all( + Object.getOwnPropertyNames(this.connections) + .map(connection => promisify(callback => this.connections[connection].close(callback))()) + ); + return super._onProcessExit.call(this); + } + + // Expose this as a method so that the parsing may be updated when the user has added additional, custom types + _refreshTypeParser(dataType) { + parserStore.refresh(dataType); + } - return Promise - .all(promises) - .then(() => super._onProcessExit.call(this)); + _clearTypeParser() { + parserStore.clear(); } - getConnection(options = {}) { + async getConnection(options) { + options = options || {}; options.uuid = options.uuid || 'default'; - options.inMemory = (this.sequelize.options.storage || this.sequelize.options.host || ':memory:') === ':memory:' ? 1 : 0; + + if (!!this.sequelize.options.storage !== null && this.sequelize.options.storage !== undefined) { + // Check explicitely for the storage option to not be set since an empty string signals + // SQLite will create a temporary disk-based database in that case. + options.storage = this.sequelize.options.storage; + } else { + options.storage = this.sequelize.options.host || ':memory:'; + } + + options.inMemory = options.storage === ':memory:' ? 1 : 0; const dialectOptions = this.sequelize.options.dialectOptions; - options.readWriteMode = dialectOptions && dialectOptions.mode; + const defaultReadWriteMode = this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE; - if (this.connections.has(options.inMemory || options.uuid)) { - return Promise.resolve(this.connections.get(options.inMemory || options.uuid)); + options.readWriteMode = dialectOptions && dialectOptions.mode || defaultReadWriteMode; + + if (this.connections[options.inMemory || options.uuid]) { + return this.connections[options.inMemory || options.uuid]; + } + + if (!options.inMemory && (options.readWriteMode & this.lib.OPEN_CREATE) !== 0) { + // automatic path provision for `options.storage` + fs.mkdirSync(path.dirname(options.storage), { recursive: true }); } - return new Promise((resolve, reject) => { - this.connections.set(options.inMemory || options.uuid, new this.lib.Database( - this.sequelize.options.storage || this.sequelize.options.host || ':memory:', - options.readWriteMode || this.lib.OPEN_READWRITE | this.lib.OPEN_CREATE, // default mode + const connection = await new Promise((resolve, reject) => { + this.connections[options.inMemory || options.uuid] = new this.lib.Database( + options.storage, + options.readWriteMode, err => { if (err) return reject(new sequelizeErrors.ConnectionError(err)); debug(`connection acquired ${options.uuid}`); - resolve(this.connections.get(options.inMemory || options.uuid)); + resolve(this.connections[options.inMemory || options.uuid]); } - )); - }).tap(connection => { - if (this.sequelize.config.password) { - // Make it possible to define and use password for sqlite encryption plugin like sqlcipher - connection.run(`PRAGMA KEY=${this.sequelize.escape(this.sequelize.config.password)}`); - } - if (this.sequelize.options.foreignKeys !== false) { - // Make it possible to define and use foreign key constraints unless - // explicitly disallowed. It's still opt-in per relation - connection.run('PRAGMA FOREIGN_KEYS=ON'); - } + ); }); + + if (this.sequelize.config.password) { + // Make it possible to define and use password for sqlite encryption plugin like sqlcipher + connection.run(`PRAGMA KEY=${this.sequelize.escape(this.sequelize.config.password)}`); + } + if (this.sequelize.options.foreignKeys !== false) { + // Make it possible to define and use foreign key constraints unless + // explicitly disallowed. It's still opt-in per relation + connection.run('PRAGMA FOREIGN_KEYS=ON'); + } + + return connection; } releaseConnection(connection, force) { @@ -73,7 +101,7 @@ class ConnectionManager extends AbstractConnectionManager { if (connection.uuid) { connection.close(); debug(`connection released ${connection.uuid}`); - this.connections.delete(connection.uuid); + delete this.connections[connection.uuid]; } } } diff --git a/lib/dialects/sqlite/index.js b/lib/dialects/sqlite/index.js index 0b64cf63bad6..35ecbe9b5e06 100644 --- a/lib/dialects/sqlite/index.js +++ b/lib/dialects/sqlite/index.js @@ -6,45 +6,54 @@ const ConnectionManager = require('./connection-manager'); const Query = require('./query'); const QueryGenerator = require('./query-generator'); const DataTypes = require('../../data-types').sqlite; +const { SQLiteQueryInterface } = require('./query-interface'); class SqliteDialect extends AbstractDialect { constructor(sequelize) { super(); this.sequelize = sequelize; this.connectionManager = new ConnectionManager(this, sequelize); - this.QueryGenerator = new QueryGenerator({ + this.queryGenerator = new QueryGenerator({ _dialect: this, sequelize }); + + this.queryInterface = new SQLiteQueryInterface( + sequelize, + this.queryGenerator + ); } } -SqliteDialect.prototype.supports = _.merge(_.cloneDeep(AbstractDialect.prototype.supports), { - 'DEFAULT': false, - 'DEFAULT VALUES': true, - 'UNION ALL': false, - inserts: { - ignoreDuplicates: ' OR IGNORE', - updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' - }, - index: { - using: false, - where: true, - functionBased: true - }, - transactionOptions: { - type: true - }, - constraints: { - addConstraint: false, - dropConstraint: false - }, - joinTableDependent: false, - groupedLimit: false, - JSON: true -}); +SqliteDialect.prototype.supports = _.merge( + _.cloneDeep(AbstractDialect.prototype.supports), + { + DEFAULT: false, + 'DEFAULT VALUES': true, + 'UNION ALL': false, + 'RIGHT JOIN': false, + inserts: { + ignoreDuplicates: ' OR IGNORE', + updateOnDuplicate: ' ON CONFLICT DO UPDATE SET' + }, + index: { + using: false, + where: true, + functionBased: true + }, + transactionOptions: { + type: true + }, + constraints: { + addConstraint: false, + dropConstraint: false + }, + groupedLimit: false, + JSON: true + } +); -ConnectionManager.prototype.defaultVersion = '3.8.0'; +SqliteDialect.prototype.defaultVersion = '3.8.0'; // minimum supported version SqliteDialect.prototype.Query = Query; SqliteDialect.prototype.DataTypes = DataTypes; SqliteDialect.prototype.name = 'sqlite'; diff --git a/lib/dialects/sqlite/query-generator.js b/lib/dialects/sqlite/query-generator.js index 8be660d5e67c..82d65c44c828 100644 --- a/lib/dialects/sqlite/query-generator.js +++ b/lib/dialects/sqlite/query-generator.js @@ -19,7 +19,9 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { return 'SELECT sqlite_version() as `version`'; } - createTableQuery(tableName, attributes, options = {}) { + createTableQuery(tableName, attributes, options) { + options = options || {}; + const primaryKeys = []; const needsMultiplePrimaryKeys = Object.values(attributes).filter(definition => definition.includes('PRIMARY KEY')).length > 1; const attrArray = []; @@ -42,7 +44,11 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { if (needsMultiplePrimaryKeys) { primaryKeys.push(attr); - dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL'); + if (dataType.includes('NOT NULL')) { + dataTypeString = dataType.replace(' PRIMARY KEY', ''); + } else { + dataTypeString = dataType.replace('PRIMARY KEY', 'NOT NULL'); + } } } attrArray.push(`${this.quoteIdentifier(attr)} ${dataTypeString}`); @@ -173,28 +179,9 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { return 'SELECT name FROM `sqlite_master` WHERE type=\'table\' and name!=\'sqlite_sequence\';'; } - upsertQuery(tableName, insertValues, updateValues, where, model, options) { - options.ignoreDuplicates = true; - - const bind = []; - - const upsertOptions = { - ...options, - bindParam: this.bindParam(bind) - }; - const insert = this.insertQuery(tableName, insertValues, model.rawAttributes, upsertOptions); - const update = this.updateQuery(tableName, updateValues, where, upsertOptions, model.rawAttributes); - - const query = `${insert.query} ${update.query}`; - - return { query, bind }; - } - updateQuery(tableName, attrValueHash, where, options, attributes) { - options = { - ...this.options, - ...options - }; + options = options || {}; + _.defaults(options, this.options); attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options); @@ -241,11 +228,8 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { ].join(''); } - deleteQuery(tableName, where, options, model) { - options = { - ...this.options, - ...options - }; + deleteQuery(tableName, where, options = {}, model) { + _.defaults(options, this.options); let whereClause = this.getWhereConditions(where, null, model, options); @@ -262,7 +246,6 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { attributesToSQL(attributes) { const result = {}; - for (const name in attributes) { const dataType = attributes[name]; const fieldName = dataType.field || name; @@ -435,7 +418,8 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { ).join(', '); const attributeNamesExport = Object.keys(attributes).map(attr => this.quoteIdentifier(attr)).join(', '); - return `${this.createTableQuery(backupTableName, attributes).replace('CREATE TABLE', 'CREATE TEMPORARY TABLE') + // Temporary tables don't support foreign keys, so creating a temporary table will not allow foreign keys to be preserved + return `${this.createTableQuery(backupTableName, attributes) }INSERT INTO ${quotedBackupTableName} SELECT ${attributeNamesImport} FROM ${quotedTableName};` + `DROP TABLE ${quotedTableName};${ this.createTableQuery(tableName, attributes) @@ -478,7 +462,7 @@ class SQLiteQueryGenerator extends MySqlQueryGenerator { * @private */ getForeignKeysQuery(tableName) { - return `PRAGMA foreign_key_list(${tableName})`; + return `PRAGMA foreign_key_list(${this.quoteTable(this.addSchema(tableName))})`; } } diff --git a/lib/dialects/sqlite/query-interface.js b/lib/dialects/sqlite/query-interface.js index fc4cf705a86d..ca2b68653d0f 100644 --- a/lib/dialects/sqlite/query-interface.js +++ b/lib/dialects/sqlite/query-interface.js @@ -1,201 +1,238 @@ 'use strict'; -const Promise = require('../../promise'); const sequelizeErrors = require('../../errors'); const QueryTypes = require('../../query-types'); +const { QueryInterface } = require('../abstract/query-interface'); +const { cloneDeep } = require('../../utils'); +const _ = require('lodash'); /** - Returns an object that treats SQLite's inabilities to do certain queries. - - @class QueryInterface - @static - @private + * The interface that Sequelize uses to talk with SQLite database */ - -/** - A wrapper that fixes SQLite's inability to remove columns from existing tables. - It will create a backup of the table, drop the table afterwards and create a - new table with the same name but without the obsolete column. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} attributeName The name of the attribute that we want to remove. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @since 1.6.0 - @private - */ -function removeColumn(qi, tableName, attributeName, options = {}) { - return qi.describeTable(tableName, options).then(fields => { +class SQLiteQueryInterface extends QueryInterface { + /** + * A wrapper that fixes SQLite's inability to remove columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but without the obsolete column. + * + * @override + */ + async removeColumn(tableName, attributeName, options) { + options = options || {}; + + const fields = await this.describeTable(tableName, options); delete fields[attributeName]; - const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); + const sql = this.queryGenerator.removeColumnQuery(tableName, fields); const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); -} -exports.removeColumn = removeColumn; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } -/** - A wrapper that fixes SQLite's inability to change columns from existing tables. - It will create a backup of the table, drop the table afterwards and create a - new table with the same name but with a modified version of the respective column. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {object} attributes An object with the attribute's name as key and its options as value object. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @since 1.6.0 - @private - */ -function changeColumn(qi, tableName, attributes, options = {}) { - const attributeName = Object.keys(attributes)[0]; - return qi.describeTable(tableName, options).then(fields => { - fields[attributeName] = attributes[attributeName]; + /** + * A wrapper that fixes SQLite's inability to change columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but with a modified version of the respective column. + * + * @override + */ + async changeColumn(tableName, attributeName, dataTypeOrOptions, options) { + options = options || {}; + + const fields = await this.describeTable(tableName, options); + Object.assign(fields[attributeName], this.normalizeAttribute(dataTypeOrOptions)); - const sql = qi.QueryGenerator.removeColumnQuery(tableName, fields); + const sql = this.queryGenerator.removeColumnQuery(tableName, fields); const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); -} -exports.changeColumn = changeColumn; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } + + /** + * A wrapper that fixes SQLite's inability to rename columns from existing tables. + * It will create a backup of the table, drop the table afterwards and create a + * new table with the same name but with a renamed version of the respective column. + * + * @override + */ + async renameColumn(tableName, attrNameBefore, attrNameAfter, options) { + options = options || {}; + const fields = await this.assertTableHasColumn(tableName, attrNameBefore, options); -/** - A wrapper that fixes SQLite's inability to rename columns from existing tables. - It will create a backup of the table, drop the table afterwards and create a - new table with the same name but with a renamed version of the respective column. - - @param {QueryInterface} qi - @param {string} tableName The name of the table. - @param {string} attrNameBefore The name of the attribute before it was renamed. - @param {string} attrNameAfter The name of the attribute after it was renamed. - @param {object} options - @param {boolean|Function} [options.logging] A function that logs the sql queries, or false for explicitly not logging these queries - - @since 1.6.0 - @private - */ -function renameColumn(qi, tableName, attrNameBefore, attrNameAfter, options = {}) { - return qi.describeTable(tableName, options).then(fields => { fields[attrNameAfter] = { ...fields[attrNameBefore] }; delete fields[attrNameBefore]; - const sql = qi.QueryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); + const sql = this.queryGenerator.renameColumnQuery(tableName, attrNameBefore, attrNameAfter, fields); const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); -} -exports.renameColumn = renameColumn; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {string} constraintName - * @param {object} options - * - * @private - */ -function removeConstraint(qi, tableName, constraintName, options) { - let createTableSql; - - return qi.showConstraint(tableName, constraintName) - .then(constraints => { - // sqlite can't show only one constraint, so we find here the one to remove - const constraint = constraints.find(constaint => constaint.constraintName === constraintName); - - if (constraint) { - createTableSql = constraint.sql; - constraint.constraintName = qi.QueryGenerator.quoteIdentifier(constraint.constraintName); - let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; - - if (constraint.constraintType === 'FOREIGN KEY') { - const referenceTableName = qi.QueryGenerator.quoteTable(constraint.referenceTableName); - constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => qi.QueryGenerator.quoteIdentifier(columnName)); - const referenceTableKeys = constraint.referenceTableKeys.join(', '); - constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; - constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; - constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; - } + /** + * @override + */ + async removeConstraint(tableName, constraintName, options) { + let createTableSql; - createTableSql = createTableSql.replace(constraintSnippet, ''); - createTableSql += ';'; + const constraints = await this.showConstraint(tableName, constraintName); + // sqlite can't show only one constraint, so we find here the one to remove + const constraint = constraints.find(constaint => constaint.constraintName === constraintName); - return qi.describeTable(tableName, options); - } + if (!constraint) { throw new sequelizeErrors.UnknownConstraintError({ message: `Constraint ${constraintName} on table ${tableName} does not exist`, constraint: constraintName, table: tableName }); - }) - .then(fields => { - const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); + } + createTableSql = constraint.sql; + constraint.constraintName = this.queryGenerator.quoteIdentifier(constraint.constraintName); + let constraintSnippet = `, CONSTRAINT ${constraint.constraintName} ${constraint.constraintType} ${constraint.constraintCondition}`; + + if (constraint.constraintType === 'FOREIGN KEY') { + const referenceTableName = this.queryGenerator.quoteTable(constraint.referenceTableName); + constraint.referenceTableKeys = constraint.referenceTableKeys.map(columnName => this.queryGenerator.quoteIdentifier(columnName)); + const referenceTableKeys = constraint.referenceTableKeys.join(', '); + constraintSnippet += ` REFERENCES ${referenceTableName} (${referenceTableKeys})`; + constraintSnippet += ` ON UPDATE ${constraint.updateAction}`; + constraintSnippet += ` ON DELETE ${constraint.deleteAction}`; + } + + createTableSql = createTableSql.replace(constraintSnippet, ''); + createTableSql += ';'; + + const fields = await this.describeTable(tableName, options); + + const sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql); + const subQueries = sql.split(';').filter(q => q !== ''); - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); -} -exports.removeConstraint = removeConstraint; + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {object} options - * - * @private - */ -function addConstraint(qi, tableName, options) { - const constraintSnippet = qi.QueryGenerator.getConstraintSnippet(tableName, options); - const describeCreateTableSql = qi.QueryGenerator.describeCreateTableQuery(tableName); - let createTableSql; - - return qi.sequelize.query(describeCreateTableSql, Object.assign({}, options, { type: QueryTypes.SELECT, raw: true })) - .then(constraints => { - const sql = constraints[0].sql; - const index = sql.length - 1; - //Replace ending ')' with constraint snippet - Simulates String.replaceAt - //http://stackoverflow.com/questions/1431094 - createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`; - - return qi.describeTable(tableName, options); - }) - .then(fields => { - const sql = qi.QueryGenerator._alterConstraintQuery(tableName, fields, createTableSql); - const subQueries = sql.split(';').filter(q => q !== ''); - - return Promise.each(subQueries, subQuery => qi.sequelize.query(`${subQuery};`, Object.assign({ raw: true }, options))); - }); -} -exports.addConstraint = addConstraint; + /** + * @override + */ + async addConstraint(tableName, options) { + if (!options.fields) { + throw new Error('Fields must be specified through options.fields'); + } -/** - * @param {QueryInterface} qi - * @param {string} tableName - * @param {object} options Query Options - * - * @private - * @returns {Promise} - */ -function getForeignKeyReferencesForTable(qi, tableName, options) { - const database = qi.sequelize.config.database; - const query = qi.QueryGenerator.getForeignKeysQuery(tableName, database); - return qi.sequelize.query(query, options) - .then(result => { - return result.map(row => ({ - tableName, - columnName: row.from, - referencedTableName: row.table, - referencedColumnName: row.to, - tableCatalog: database, - referencedTableCatalog: database - })); - }); + if (!options.type) { + throw new Error('Constraint type must be specified through options.type'); + } + + options = cloneDeep(options); + + const constraintSnippet = this.queryGenerator.getConstraintSnippet(tableName, options); + const describeCreateTableSql = this.queryGenerator.describeCreateTableQuery(tableName); + + const constraints = await this.sequelize.query(describeCreateTableSql, { ...options, type: QueryTypes.SELECT, raw: true }); + let sql = constraints[0].sql; + const index = sql.length - 1; + //Replace ending ')' with constraint snippet - Simulates String.replaceAt + //http://stackoverflow.com/questions/1431094 + const createTableSql = `${sql.substr(0, index)}, ${constraintSnippet})${sql.substr(index + 1)};`; + + const fields = await this.describeTable(tableName, options); + sql = this.queryGenerator._alterConstraintQuery(tableName, fields, createTableSql); + const subQueries = sql.split(';').filter(q => q !== ''); + + for (const subQuery of subQueries) await this.sequelize.query(`${subQuery};`, { raw: true, ...options }); + } + + /** + * @override + */ + async getForeignKeyReferencesForTable(tableName, options) { + const database = this.sequelize.config.database; + const query = this.queryGenerator.getForeignKeysQuery(tableName, database); + const result = await this.sequelize.query(query, options); + return result.map(row => ({ + tableName, + columnName: row.from, + referencedTableName: row.table, + referencedColumnName: row.to, + tableCatalog: database, + referencedTableCatalog: database + })); + } + + /** + * @override + */ + async dropAllTables(options) { + options = options || {}; + const skip = options.skip || []; + + const tableNames = await this.showAllTables(options); + await this.sequelize.query('PRAGMA foreign_keys = OFF', options); + await this._dropAllTables(tableNames, skip, options); + await this.sequelize.query('PRAGMA foreign_keys = ON', options); + } + + /** + * @override + */ + async describeTable(tableName, options) { + let schema = null; + let schemaDelimiter = null; + + if (typeof options === 'string') { + schema = options; + } else if (typeof options === 'object' && options !== null) { + schema = options.schema || null; + schemaDelimiter = options.schemaDelimiter || null; + } + + if (typeof tableName === 'object' && tableName !== null) { + schema = tableName.schema; + tableName = tableName.tableName; + } + + const sql = this.queryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); + options = { ...options, type: QueryTypes.DESCRIBE }; + const sqlIndexes = this.queryGenerator.showIndexesQuery(tableName); + + try { + const data = await this.sequelize.query(sql, options); + /* + * If no data is returned from the query, then the table name may be wrong. + * Query generators that use information_schema for retrieving table info will just return an empty result set, + * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql). + */ + if (_.isEmpty(data)) { + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + } + + const indexes = await this.sequelize.query(sqlIndexes, options); + for (const prop in data) { + data[prop].unique = false; + } + for (const index of indexes) { + for (const field of index.fields) { + if (index.unique !== undefined) { + data[field.attribute].unique = index.unique; + } + } + } + + const foreignKeys = await this.getForeignKeyReferencesForTable(tableName, options); + for (const foreignKey of foreignKeys) { + data[foreignKey.columnName].references = { + model: foreignKey.referencedTableName, + key: foreignKey.referencedColumnName + }; + } + + return data; + } catch (e) { + if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') { + throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); + } + + throw e; + } + } } -exports.getForeignKeyReferencesForTable = getForeignKeyReferencesForTable; +exports.SQLiteQueryInterface = SQLiteQueryInterface; diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index bb0ec2053034..499cf21694f3 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -2,10 +2,10 @@ const _ = require('lodash'); const Utils = require('../../utils'); -const Promise = require('../../promise'); const AbstractQuery = require('../abstract/query'); const QueryTypes = require('../../query-types'); const sequelizeErrors = require('../../errors'); +const parserStore = require('../parserStore')('sqlite'); const { logger } = require('../../utils/logger'); const debug = logger.debugContext('sql:sqlite'); @@ -66,15 +66,15 @@ class Query extends AbstractQuery { return ret; } - _handleQueryResponse(metaData, columnTypes, err, results) { + _handleQueryResponse(metaData, columnTypes, err, results, errStack) { if (err) { err.sql = this.sql; - throw this.formatError(err); + throw this.formatError(err, errStack); } let result = this.instance; // add the inserted row id to the instance - if (this.isInsertQuery(results, metaData)) { + if (this.isInsertQuery(results, metaData) || this.isUpsertQuery()) { this.handleInsertQuery(results, metaData); if (!this.instance) { // handle bulkCreate AI primary key @@ -201,102 +201,85 @@ class Query extends AbstractQuery { if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].includes(this.options.type)) { return metaData.changes; } - if (this.options.type === QueryTypes.UPSERT) { - return undefined; - } if (this.options.type === QueryTypes.VERSION) { return results[0].version; } if (this.options.type === QueryTypes.RAW) { return [results, metaData]; } + if (this.isUpsertQuery()) { + return [result, null]; + } if (this.isUpdateQuery() || this.isInsertQuery()) { return [result, metaData.changes]; } return result; } - run(sql, parameters) { + async run(sql, parameters) { const conn = this.connection; this.sql = sql; const method = this.getDatabaseMethod(); - let complete; - if (method === 'exec') { - // exec does not support bind parameter - sql = AbstractQuery.formatBindParameters(sql, this.options.bind, this.options.dialect || 'sqlite', { skipUnescape: true })[0]; - this.sql = sql; - complete = this._logQuery(sql, debug); - } else { - complete = this._logQuery(sql, debug, parameters); - } - + const complete = this._logQuery(sql, debug, parameters); - return new Promise(resolve => { + return new Promise((resolve, reject) => conn.serialize(async () => { const columnTypes = {}; - conn.serialize(() => { - const executeSql = () => { - if (sql.startsWith('-- ')) { - return resolve(); + const errForStack = new Error(); + const executeSql = () => { + if (sql.startsWith('-- ')) { + return resolve(); + } + const query = this; + // cannot use arrow function here because the function is bound to the statement + function afterExecute(executionError, results) { + try { + complete(); + // `this` is passed from sqlite, we have no control over this. + // eslint-disable-next-line no-invalid-this + resolve(query._handleQueryResponse(this, columnTypes, executionError, results, errForStack.stack)); + return; + } catch (error) { + reject(error); } - resolve(new Promise((resolve, reject) => { - const query = this; - // cannot use arrow function here because the function is bound to the statement - function afterExecute(executionError, results) { - try { - complete(); - // `this` is passed from sqlite, we have no control over this. - // eslint-disable-next-line no-invalid-this - resolve(query._handleQueryResponse(this, columnTypes, executionError, results)); - return; - } catch (error) { - reject(error); - } - } + } - if (method === 'exec') { - // exec does not support bind parameter - conn[method](sql, afterExecute); - } else { - if (!parameters) parameters = []; - conn[method](sql, parameters, afterExecute); - } - })); - return null; - }; + if (!parameters) parameters = []; + conn[method](sql, parameters, afterExecute); - if (this.getDatabaseMethod() === 'all') { - let tableNames = []; - if (this.options && this.options.tableNames) { - tableNames = this.options.tableNames; - } else if (/FROM `(.*?)`/i.exec(this.sql)) { - tableNames.push(/FROM `(.*?)`/i.exec(this.sql)[1]); - } + return null; + }; - // If we already have the metadata for the table, there's no need to ask for it again - tableNames = tableNames.filter(tableName => !(tableName in columnTypes) && tableName !== 'sqlite_master'); + if (this.getDatabaseMethod() === 'all') { + let tableNames = []; + if (this.options && this.options.tableNames) { + tableNames = this.options.tableNames; + } else if (/FROM `(.*?)`/i.exec(this.sql)) { + tableNames.push(/FROM `(.*?)`/i.exec(this.sql)[1]); + } - if (!tableNames.length) { - return executeSql(); - } - return Promise.map(tableNames, tableName => - new Promise(resolve => { - tableName = tableName.replace(/`/g, ''); - columnTypes[tableName] = {}; - - conn.all(`PRAGMA table_info(\`${tableName}\`)`, (err, results) => { - if (!err) { - for (const result of results) { - columnTypes[tableName][result.name] = result.type; - } - } - resolve(); - }); - }) - ).then(executeSql); + // If we already have the metadata for the table, there's no need to ask for it again + tableNames = tableNames.filter(tableName => !(tableName in columnTypes) && tableName !== 'sqlite_master'); + + if (!tableNames.length) { + return executeSql(); } - return executeSql(); - }); - }); + await Promise.all(tableNames.map(tableName => + new Promise(resolve => { + tableName = tableName.replace(/`/g, ''); + columnTypes[tableName] = {}; + + conn.all(`PRAGMA table_info(\`${tableName}\`)`, (err, results) => { + if (!err) { + for (const result of results) { + columnTypes[tableName][result.name] = result.type; + } + } + resolve(); + }); + }))); + } + return executeSql(); + })); } parseConstraintsFromSql(sql) { @@ -356,7 +339,7 @@ class Query extends AbstractQuery { } type = type.replace('UNSIGNED', '').replace('ZEROFILL', ''); type = type.trim().toUpperCase(); - const parse = this.sequelize.connectionManager.parserStore.get(type); + const parse = parserStore.get(type); if (value !== null && parse) { return parse(value, { timezone: this.sequelize.options.timezone }); @@ -364,13 +347,14 @@ class Query extends AbstractQuery { return value; } - formatError(err) { + formatError(err, errStack) { switch (err.code) { case 'SQLITE_CONSTRAINT': { if (err.message.includes('FOREIGN KEY constraint failed')) { return new sequelizeErrors.ForeignKeyConstraintError({ - parent: err + parent: err, + stack: errStack }); } @@ -412,42 +396,38 @@ class Query extends AbstractQuery { }); } - return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields }); + return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } case 'SQLITE_BUSY': - return new sequelizeErrors.TimeoutError(err); + return new sequelizeErrors.TimeoutError(err, { stack: errStack }); default: - return new sequelizeErrors.DatabaseError(err); + return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } - handleShowIndexesQuery(data) { + async handleShowIndexesQuery(data) { // Sqlite returns indexes so the one that was defined last is returned first. Lets reverse that! - return Promise.map(data.reverse(), item => { + return Promise.all(data.reverse().map(async item => { item.fields = []; item.primary = false; item.unique = !!item.unique; item.constraintName = item.name; - return this.run(`PRAGMA INDEX_INFO(\`${item.name}\`)`).then(columns => { - for (const column of columns) { - item.fields[column.seqno] = { - attribute: column.name, - length: undefined, - order: undefined - }; - } + const columns = await this.run(`PRAGMA INDEX_INFO(\`${item.name}\`)`); + for (const column of columns) { + item.fields[column.seqno] = { + attribute: column.name, + length: undefined, + order: undefined + }; + } - return item; - }); - }); + return item; + })); } getDatabaseMethod() { - if (this.isUpsertQuery()) { - return 'exec'; // Needed to run multiple queries in one - } - if (this.isInsertQuery() || this.isUpdateQuery() || this.isBulkUpdateQuery() || this.sql.toLowerCase().includes('CREATE TEMPORARY TABLE'.toLowerCase()) || this.options.type === QueryTypes.BULKDELETE) { + if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery() || this.isBulkUpdateQuery() || this.sql.toLowerCase().includes('CREATE TEMPORARY TABLE'.toLowerCase()) || this.options.type === QueryTypes.BULKDELETE) { return 'run'; } return 'all'; diff --git a/lib/errors/aggregate-error.js b/lib/errors/aggregate-error.js new file mode 100644 index 000000000000..4a6eb94a4311 --- /dev/null +++ b/lib/errors/aggregate-error.js @@ -0,0 +1,34 @@ +'use strict'; + +const BaseError = require('./base-error'); + +/** + * A wrapper for multiple Errors + * + * @param {Error[]} [errors] Array of errors + * + * @property errors {Error[]} + */ +class AggregateError extends BaseError { + constructor(errors) { + super(); + this.errors = errors; + this.name = 'AggregateError'; + } + + toString() { + const message = `AggregateError of:\n${ + this.errors.map(error => + error === this + ? '[Circular AggregateError]' + : error instanceof AggregateError + ? String(error).replace(/\n$/, '').replace(/^/mg, ' ') + : String(error).replace(/^/mg, ' ').substring(2) + + ).join('\n') + }\n`; + return message; + } +} + +module.exports = AggregateError; diff --git a/lib/errors/bulk-record-error.js b/lib/errors/bulk-record-error.js index 4e30fe6d907e..c095754040ae 100644 --- a/lib/errors/bulk-record-error.js +++ b/lib/errors/bulk-record-error.js @@ -4,7 +4,7 @@ const BaseError = require('./base-error'); /** * Thrown when bulk operation fails, it represent per record level error. - * Used with Promise.AggregateError + * Used with AggregateError * * @param {Error} error Error for a given record/instance * @param {object} record DAO instance that error belongs to diff --git a/lib/errors/connection-error.js b/lib/errors/connection-error.js index 7605786f529d..4a3a8a38e989 100644 --- a/lib/errors/connection-error.js +++ b/lib/errors/connection-error.js @@ -11,6 +11,7 @@ class ConnectionError extends BaseError { this.name = 'SequelizeConnectionError'; /** * The connection specific error which triggered this one + * * @type {Error} */ this.parent = parent; diff --git a/lib/errors/database-error.js b/lib/errors/database-error.js index 5a21f6f3d710..ec09cad182b4 100644 --- a/lib/errors/database-error.js +++ b/lib/errors/database-error.js @@ -6,7 +6,7 @@ const BaseError = require('./base-error'); * A base class for all database related errors. */ class DatabaseError extends BaseError { - constructor(parent) { + constructor(parent, options) { super(parent.message); this.name = 'SequelizeDatabaseError'; /** @@ -19,14 +19,24 @@ class DatabaseError extends BaseError { this.original = parent; /** * The SQL that triggered the error + * * @type {string} */ this.sql = parent.sql; /** * The parameters for the sql that triggered the error + * * @type {Array} */ this.parameters = parent.parameters; + /** + * The stacktrace can be overridden if the original stacktrace isn't very good + * + * @type {string} + */ + if (options && options.stack) { + this.stack = options.stack; + } } } diff --git a/lib/errors/database/exclusion-constraint-error.js b/lib/errors/database/exclusion-constraint-error.js index 280399a55746..66ced0e5b9b5 100644 --- a/lib/errors/database/exclusion-constraint-error.js +++ b/lib/errors/database/exclusion-constraint-error.js @@ -6,10 +6,11 @@ const DatabaseError = require('./../database-error'); * Thrown when an exclusion constraint is violated in the database */ class ExclusionConstraintError extends DatabaseError { - constructor(options = {}) { + constructor(options) { + options = options || {}; options.parent = options.parent || { sql: '' }; - super(options.parent); + super(options.parent, { stack: options.stack }); this.name = 'SequelizeExclusionConstraintError'; this.message = options.message || options.parent.message || ''; diff --git a/lib/errors/database/foreign-key-constraint-error.js b/lib/errors/database/foreign-key-constraint-error.js index 5e053fddd8f9..a3acba352234 100644 --- a/lib/errors/database/foreign-key-constraint-error.js +++ b/lib/errors/database/foreign-key-constraint-error.js @@ -6,10 +6,11 @@ const DatabaseError = require('./../database-error'); * Thrown when a foreign key constraint is violated in the database */ class ForeignKeyConstraintError extends DatabaseError { - constructor(options = {}) { + constructor(options) { + options = options || {}; options.parent = options.parent || { sql: '' }; - super(options.parent); + super(options.parent, { stack: options.stack }); this.name = 'SequelizeForeignKeyConstraintError'; this.message = options.message || options.parent.message || 'Database Error'; diff --git a/lib/errors/database/timeout-error.js b/lib/errors/database/timeout-error.js index b67933b50f77..2342c1ab3d14 100644 --- a/lib/errors/database/timeout-error.js +++ b/lib/errors/database/timeout-error.js @@ -6,8 +6,8 @@ const DatabaseError = require('./../database-error'); * Thrown when a database query times out because of a deadlock */ class TimeoutError extends DatabaseError { - constructor(parent) { - super(parent); + constructor(parent, options) { + super(parent, options); this.name = 'SequelizeTimeoutError'; } } diff --git a/lib/errors/database/unknown-constraint-error.js b/lib/errors/database/unknown-constraint-error.js index 94e5d10bc2ae..0c1be5d47a15 100644 --- a/lib/errors/database/unknown-constraint-error.js +++ b/lib/errors/database/unknown-constraint-error.js @@ -6,10 +6,11 @@ const DatabaseError = require('./../database-error'); * Thrown when constraint name is not found in the database */ class UnknownConstraintError extends DatabaseError { - constructor(options = {}) { + constructor(options) { + options = options || {}; options.parent = options.parent || { sql: '' }; - super(options.parent); + super(options.parent, { stack: options.stack }); this.name = 'SequelizeUnknownConstraintError'; this.message = options.message || 'The specified constraint does not exist'; diff --git a/lib/errors/index.js b/lib/errors/index.js index 8ada8c76bba8..16316a5acad4 100644 --- a/lib/errors/index.js +++ b/lib/errors/index.js @@ -2,6 +2,8 @@ exports.BaseError = require('./base-error'); +exports.AggregateError = require('./aggregate-error'); +exports.AsyncQueueError = require('../dialects/mssql/async-queue').AsyncQueueError; exports.AssociationError = require('./association-error'); exports.BulkRecordError = require('./bulk-record-error'); exports.ConnectionError = require('./connection-error'); diff --git a/lib/errors/optimistic-lock-error.js b/lib/errors/optimistic-lock-error.js index f9633c8cc77c..0c0ff3eb7db4 100644 --- a/lib/errors/optimistic-lock-error.js +++ b/lib/errors/optimistic-lock-error.js @@ -6,17 +6,20 @@ const BaseError = require('./base-error'); * Thrown when attempting to update a stale model instance */ class OptimisticLockError extends BaseError { - constructor(options = {}) { + constructor(options) { + options = options || {}; options.message = options.message || `Attempting to update a stale model instance: ${options.modelName}`; super(options.message); this.name = 'SequelizeOptimisticLockError'; /** * The name of the model on which the update was attempted + * * @type {string} */ this.modelName = options.modelName; /** * The values of the attempted update + * * @type {object} */ this.values = options.values; diff --git a/lib/errors/validation-error.js b/lib/errors/validation-error.js index 185a5d76c835..3e0d1095a8b2 100644 --- a/lib/errors/validation-error.js +++ b/lib/errors/validation-error.js @@ -12,7 +12,7 @@ const BaseError = require('./base-error'); * @property errors {ValidationErrorItems[]} */ class ValidationError extends BaseError { - constructor(message, errors) { + constructor(message, errors, options) { super(message); this.name = 'SequelizeValidationError'; this.message = 'Validation Error'; @@ -30,6 +30,11 @@ class ValidationError extends BaseError { } else if (this.errors.length > 0 && this.errors[0].message) { this.message = this.errors.map(err => `${err.type || err.origin}: ${err.message}`).join(',\n'); } + + // Allow overriding the stack if the original stacktrace is uninformative + if (options && options.stack) { + this.stack = options.stack; + } } /** @@ -55,18 +60,18 @@ class ValidationError extends BaseError { */ class ValidationErrorItem { /** - * Creates new validation error item + * Creates a new ValidationError item. Instances of this class are included in the `ValidationError.errors` property. * - * @param {string} message An error message - * @param {string} type The type/origin of the validation error - * @param {string} path The field that triggered the validation error - * @param {string} value The value that generated the error - * @param {object} [inst] the DAO instance that caused the validation error - * @param {object} [validatorKey] a validation "key", used for identification + * @param {string} [message] An error message + * @param {string} [type] The type/origin of the validation error + * @param {string} [path] The field that triggered the validation error + * @param {string} [value] The value that generated the error + * @param {Model} [instance] the DAO instance that caused the validation error + * @param {string} [validatorKey] a validation "key", used for identification * @param {string} [fnName] property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable - * @param {string} [fnArgs] parameters used with the BUILT-IN validator function, if applicable + * @param {Array} [fnArgs] parameters used with the BUILT-IN validator function, if applicable */ - constructor(message, type, path, value, inst, validatorKey, fnName, fnArgs) { + constructor(message, type, path, value, instance, validatorKey, fnName, fnArgs) { /** * An error message * @@ -77,21 +82,21 @@ class ValidationErrorItem { /** * The type/origin of the validation error * - * @type {string} + * @type {string | null} */ this.type = null; /** * The field that triggered the validation error * - * @type {string} + * @type {string | null} */ this.path = path || null; /** * The value that generated the error * - * @type {string} + * @type {string | null} */ this.value = value !== undefined ? value : null; @@ -100,28 +105,28 @@ class ValidationErrorItem { /** * The DAO instance that caused the validation error * - * @type {Model} + * @type {Model | null} */ - this.instance = inst || null; + this.instance = instance || null; /** * A validation "key", used for identification * - * @type {string} + * @type {string | null} */ this.validatorKey = validatorKey || null; /** * Property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable * - * @type {string} + * @type {string | null} */ this.validatorName = fnName || null; /** * Parameters used with the BUILT-IN validator function, if applicable * - * @type {string} + * @type {Array} */ this.validatorArgs = fnArgs || []; diff --git a/lib/errors/validation/unique-constraint-error.js b/lib/errors/validation/unique-constraint-error.js index 59a13f5c24f6..e0978c09be36 100644 --- a/lib/errors/validation/unique-constraint-error.js +++ b/lib/errors/validation/unique-constraint-error.js @@ -6,11 +6,12 @@ const ValidationError = require('./../validation-error'); * Thrown when a unique constraint is violated in the database */ class UniqueConstraintError extends ValidationError { - constructor(options = {}) { + constructor(options) { + options = options || {}; options.parent = options.parent || { sql: '' }; options.message = options.message || options.parent.message || 'Validation Error'; options.errors = options.errors || {}; - super(options.message, options.errors); + super(options.message, options.errors, { stack: options.stack }); this.name = 'SequelizeUniqueConstraintError'; this.errors = options.errors; diff --git a/lib/hooks.js b/lib/hooks.js index 6ad3ff7fd6b5..69602a048b4d 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const { logger } = require('./utils/logger'); -const Promise = require('./promise'); const debug = logger.debugContext('hooks'); const hookTypes = { @@ -46,11 +45,10 @@ const hookTypes = { afterDisconnect: { params: 1, noModel: true }, beforeSync: { params: 1 }, afterSync: { params: 1 }, - beforeBulkSync: { params: 1, noModel: true }, - afterBulkSync: { params: 1, noModel: true }, + beforeBulkSync: { params: 1 }, + afterBulkSync: { params: 1 }, beforeQuery: { params: 2 }, - afterQuery: { params: 2 }, - afterCommit: { params: 1 } + afterQuery: { params: 2 } }; exports.hooks = hookTypes; @@ -68,40 +66,39 @@ const getProxiedHooks = hookType => : [hookType] ; -class Hooks { - _getHooks(hookType) { - return this.hooks[hookType] || []; - } +function getHooks(hooked, hookType) { + return (hooked.options.hooks || {})[hookType] || []; +} + +const Hooks = { /** * Process user supplied hooks definition * * @param {object} hooks hooks definition - * @param {Hooks} parent + * * @private + * @memberof Sequelize + * @memberof Sequelize.Model */ - constructor(hooks, parent) { - this.hooks = {}; - this.parent = parent; - if (!hooks) { - return; - } - _.map(hooks, (hooksArray, hookName) => { + _setupHooks(hooks) { + this.options.hooks = {}; + _.map(hooks || {}, (hooksArray, hookName) => { if (!Array.isArray(hooksArray)) hooksArray = [hooksArray]; - hooksArray.forEach(hookFn => this.add(hookName, hookFn)); + hooksArray.forEach(hookFn => this.addHook(hookName, hookFn)); }); - } + }, - run(hooks, ...hookArgs) { - if (!hooks) throw new Error('hooks.run requires at least 1 argument'); + async runHooks(hooks, ...hookArgs) { + if (!hooks) throw new Error('runHooks requires at least 1 argument'); let hookType; if (typeof hooks === 'string') { hookType = hooks; - hooks = this._getHooks(hookType); + hooks = getHooks(this, hookType); - if (this.parent) { - hooks = hooks.concat(this.parent._getHooks(hookType)); + if (this.sequelize) { + hooks = hooks.concat(getHooks(this.sequelize, hookType)); } } @@ -111,51 +108,70 @@ class Hooks { // synchronous hooks if (hookTypes[hookType] && hookTypes[hookType].sync) { - for (const hook of hooks) { + for (let hook of hooks) { + if (typeof hook === 'object') { + hook = hook.fn; + } + debug(`running hook(sync) ${hookType}`); - hook.fn(...hookArgs); + hook.apply(this, hookArgs); } return; } // asynchronous hooks (default) - return Promise.each(hooks, hook => { + for (let hook of hooks) { + if (typeof hook === 'object') { + hook = hook.fn; + } + debug(`running hook ${hookType}`); - return hook.fn(...hookArgs); - }).return(); - } + await hook.apply(this, hookArgs); + } + }, /** * Add a hook to the model * * @param {string} hookType hook name @see {@link hookTypes} + * @param {string|Function} [name] Provide a name for the hook function. It can be used to remove the hook later or to order hooks based on some sort of priority system in the future. * @param {Function} fn The hook function + * + * @memberof Sequelize + * @memberof Sequelize.Model */ - add(hookType, fn) { - if (hookTypes[hookType] && hookTypes[hookType].noModel && this.parent) { - throw new Error(`${hookType} is only applicable on a sequelize instance or static`); + addHook(hookType, name, fn) { + if (typeof name === 'function') { + fn = name; + name = null; } + debug(`adding hook ${hookType}`); // check for proxies, add them too hookType = getProxiedHooks(hookType); hookType.forEach(type => { - const hooks = this._getHooks(type); - hooks.push({ fn }); - this.hooks[type] = hooks; + const hooks = getHooks(this, type); + hooks.push(name ? { name, fn } : fn); + this.options.hooks[type] = hooks; }); return this; - } + }, /** * Remove hook from the model * * @param {string} hookType @see {@link hookTypes} - * @param {Function} fn name of hook or function reference which was attached + * @param {string|Function} name name of hook or function reference which was attached + * + * @memberof Sequelize + * @memberof Sequelize.Model */ - remove(hookType, fn) { - if (!this.has(hookType)) { + removeHook(hookType, name) { + const isReference = typeof name === 'function' ? true : false; + + if (!this.hasHook(hookType)) { return this; } @@ -165,27 +181,416 @@ class Hooks { hookType = getProxiedHooks(hookType); for (const type of hookType) { - this.hooks[type] = this.hooks[type].filter(({ fn: hookFn }) => fn !== hookFn); + this.options.hooks[type] = this.options.hooks[type].filter(hook => { + if (isReference && typeof hook === 'function') { + return hook !== name; // check if same method + } + if (!isReference && typeof hook === 'object') { + return hook.name !== name; + } + return true; + }); } return this; - } + }, /** * Check whether the mode has any hooks of this type * * @param {string} hookType @see {@link hookTypes} + * + * @alias hasHooks + * + * @memberof Sequelize + * @memberof Sequelize.Model */ - has(hookType) { - return this.hooks[hookType] && !!this.hooks[hookType].length; + hasHook(hookType) { + return this.options.hooks[hookType] && !!this.options.hooks[hookType].length; } +}; +Hooks.hasHooks = Hooks.hasHook; - /** - * Removes all hooks - */ - removeAll() { - this.hooks = {}; + +function applyTo(target, isModel = false) { + _.mixin(target, Hooks); + + for (const hook of Object.keys(hookTypes)) { + if (isModel && hookTypes[hook].noModel) { + continue; + } + target[hook] = function(name, callback) { + return this.addHook(hook, name, callback); + }; } } +exports.applyTo = applyTo; + +/** + * A hook that is run before validation + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * @name beforeValidate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after validation + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * @name afterValidate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run when validation fails + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options, error. Error is the + * SequelizeValidationError. If the callback throws an error, it will replace the original validation error. + * @name validationFailed + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before creating a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with attributes, options + * @name beforeCreate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after creating a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with attributes, options + * @name afterCreate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` + * + * @param {string} name + * @param {Function} fn A callback function that is called with attributes, options + * @name beforeSave + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before upserting + * + * @param {string} name + * @param {Function} fn A callback function that is called with attributes, options + * @name beforeUpsert + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after upserting + * + * @param {string} name + * @param {Function} fn A callback function that is called with the result of upsert(), options + * @name afterUpsert + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` + * + * @param {string} name + * @param {Function} fn A callback function that is called with attributes, options + * @name afterSave + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before destroying a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * + * @name beforeDestroy + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after destroying a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * + * @name afterDestroy + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before restoring a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * + * @name beforeRestore + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after restoring a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * + * @name afterRestore + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before updating a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * @name beforeUpdate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after updating a single instance + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance, options + * @name afterUpdate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before creating instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with instances, options + * @name beforeBulkCreate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after creating instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with instances, options + * @name afterBulkCreate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before destroying instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * + * @name beforeBulkDestroy + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after destroying instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * + * @name afterBulkDestroy + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before restoring instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * + * @name beforeBulkRestore + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after restoring instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * + * @name afterBulkRestore + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before updating instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * @name beforeBulkUpdate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run after updating instances in bulk + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * @name afterBulkUpdate + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before a find (select) query + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * @name beforeFind + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * @name beforeFindAfterExpandIncludeAll + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before a find (select) query, after all option parsing is complete + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * @name beforeFindAfterOptions + * @memberof Sequelize.Model + */ -exports.Hooks = Hooks; +/** + * A hook that is run after a find (select) query + * + * @param {string} name + * @param {Function} fn A callback function that is called with instance(s), options + * @name afterFind + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before a count query + * + * @param {string} name + * @param {Function} fn A callback function that is called with options + * @name beforeCount + * @memberof Sequelize.Model + */ + +/** + * A hook that is run before a define call + * + * @param {string} name + * @param {Function} fn A callback function that is called with attributes, options + * @name beforeDefine + * @memberof Sequelize + */ + +/** + * A hook that is run after a define call + * + * @param {string} name + * @param {Function} fn A callback function that is called with factory + * @name afterDefine + * @memberof Sequelize + */ + +/** + * A hook that is run before Sequelize() call + * + * @param {string} name + * @param {Function} fn A callback function that is called with config, options + * @name beforeInit + * @memberof Sequelize + */ + +/** + * A hook that is run after Sequelize() call + * + * @param {string} name + * @param {Function} fn A callback function that is called with sequelize + * @name afterInit + * @memberof Sequelize + */ + +/** + * A hook that is run before a connection is created + * + * @param {string} name + * @param {Function} fn A callback function that is called with config passed to connection + * @name beforeConnect + * @memberof Sequelize + */ + +/** + * A hook that is run after a connection is created + * + * @param {string} name + * @param {Function} fn A callback function that is called with the connection object and the config passed to connection + * @name afterConnect + * @memberof Sequelize + */ + +/** + * A hook that is run before a connection is disconnected + * + * @param {string} name + * @param {Function} fn A callback function that is called with the connection object + * @name beforeDisconnect + * @memberof Sequelize + */ + +/** + * A hook that is run after a connection is disconnected + * + * @param {string} name + * @param {Function} fn A callback function that is called with the connection object + * @name afterDisconnect + * @memberof Sequelize + */ + +/** + * A hook that is run before Model.sync call + * + * @param {string} name + * @param {Function} fn A callback function that is called with options passed to Model.sync + * @name beforeSync + * @memberof Sequelize + */ + +/** + * A hook that is run after Model.sync call + * + * @param {string} name + * @param {Function} fn A callback function that is called with options passed to Model.sync + * @name afterSync + * @memberof Sequelize + */ + +/** + * A hook that is run before sequelize.sync call + * + * @param {string} name + * @param {Function} fn A callback function that is called with options passed to sequelize.sync + * @name beforeBulkSync + * @memberof Sequelize + */ + +/** + * A hook that is run after sequelize.sync call + * + * @param {string} name + * @param {Function} fn A callback function that is called with options passed to sequelize.sync + * @name afterBulkSync + * @memberof Sequelize + */ diff --git a/lib/instance-validator.js b/lib/instance-validator.js index 01c682dfabd1..1e4f343a4160 100644 --- a/lib/instance-validator.js +++ b/lib/instance-validator.js @@ -3,10 +3,10 @@ const _ = require('lodash'); const Utils = require('./utils'); const sequelizeError = require('./errors'); -const Promise = require('./promise'); const DataTypes = require('./data-types'); const BelongsTo = require('./associations/belongs-to'); const validator = require('./utils/validator-extras').validator; +const { promisify } = require('util'); /** * Instance Validator. @@ -18,23 +18,25 @@ const validator = require('./utils/validator-extras').validator; */ class InstanceValidator { constructor(modelInstance, options) { - options = { ...options }; + options = { + // assign defined and default options + hooks: true, + ...options + }; if (options.fields && !options.skip) { options.skip = _.difference(Object.keys(modelInstance.constructor.rawAttributes), options.fields); + } else { + options.skip = options.skip || []; } - // assign defined and default options - this.options = { - skip: [], - hooks: true, - ...options - }; + this.options = options; this.modelInstance = modelInstance; /** * Exposes a reference to validator.js. This allows you to add custom validations using `validator.extend` + * * @name validator * @private */ @@ -62,19 +64,19 @@ class InstanceValidator { * @returns {Promise} * @private */ - _validate() { + async _validate() { if (this.inProgress) throw new Error('Validations already in progress.'); this.inProgress = true; - return Promise.all([ - this._perAttributeValidators().reflect(), - this._customValidators().reflect() - ]).then(() => { - if (this.errors.length) { - throw new sequelizeError.ValidationError(null, this.errors); - } - }); + await Promise.all([ + this._perAttributeValidators(), + this._customValidators() + ]); + + if (this.errors.length) { + throw new sequelizeError.ValidationError(null, this.errors); + } } /** @@ -87,8 +89,8 @@ class InstanceValidator { * @returns {Promise} * @private */ - validate() { - return this.options.hooks ? this._validateAndRunHooks() : this._validate(); + async validate() { + return await (this.options.hooks ? this._validateAndRunHooks() : this._validate()); } /** @@ -101,25 +103,28 @@ class InstanceValidator { * @returns {Promise} * @private */ - _validateAndRunHooks() { - const { hooks } = this.modelInstance.constructor; - return hooks.run('beforeValidate', this.modelInstance, this.options) - .then(() => - this._validate() - .catch(error => hooks.run('validationFailed', this.modelInstance, this.options, error) - .then(newError => { throw newError || error; })) - ) - .then(() => hooks.run('afterValidate', this.modelInstance, this.options)) - .return(this.modelInstance); + async _validateAndRunHooks() { + const runHooks = this.modelInstance.constructor.runHooks.bind(this.modelInstance.constructor); + await runHooks('beforeValidate', this.modelInstance, this.options); + + try { + await this._validate(); + } catch (error) { + const newError = await runHooks('validationFailed', this.modelInstance, this.options, error); + throw newError || error; + } + + await runHooks('afterValidate', this.modelInstance, this.options); + return this.modelInstance; } /** * Will run all the validators defined per attribute (built-in validators and custom validators) * - * @returns {Promise>} A promise from .reflect(). + * @returns {Promise} * @private */ - _perAttributeValidators() { + async _perAttributeValidators() { // promisify all attribute invocations const validators = []; @@ -140,35 +145,34 @@ class InstanceValidator { } if (Object.prototype.hasOwnProperty.call(this.modelInstance.validators, field)) { - validators.push(this._singleAttrValidate(value, field, rawAttribute.allowNull).reflect()); + validators.push(this._singleAttrValidate(value, field, rawAttribute.allowNull)); } }); - return Promise.all(validators); + return await Promise.all(validators); } /** * Will run all the custom validators defined in the model's options. * - * @returns {Promise>} A promise from .reflect(). + * @returns {Promise} * @private */ - _customValidators() { + async _customValidators() { const validators = []; - _.each(this.modelInstance._modelOptions.validate, (validator, validatorType) => { + _.each(this.modelInstance.constructor.options.validate, (validator, validatorType) => { if (this.options.skip.includes(validatorType)) { return; } const valprom = this._invokeCustomValidator(validator, validatorType) // errors are handled in settling, stub this - .catch(() => {}) - .reflect(); + .catch(() => {}); validators.push(valprom); }); - return Promise.all(validators); + return await Promise.all(validators); } /** @@ -182,11 +186,11 @@ class InstanceValidator { * * @returns {Promise} A promise, will always resolve, auto populates error on this.error local object. */ - _singleAttrValidate(value, field, allowNull) { + async _singleAttrValidate(value, field, allowNull) { // If value is null and allowNull is false, no validators should run (see #9143) if ((value === null || value === undefined) && !allowNull) { // The schema validator (_validateSchema) has already generated the validation error. Nothing to do here. - return Promise.resolve(); + return; } // Promisify each validator @@ -206,7 +210,7 @@ class InstanceValidator { // Custom validators should always run, except if value is null and allowNull is false (see #9143) if (typeof test === 'function') { - validators.push(this._invokeCustomValidator(test, validatorType, true, value, field).reflect()); + validators.push(this._invokeCustomValidator(test, validatorType, true, value, field)); return; } @@ -218,12 +222,14 @@ class InstanceValidator { const validatorPromise = this._invokeBuiltinValidator(value, test, validatorType, field); // errors are handled in settling, stub this validatorPromise.catch(() => {}); - validators.push(validatorPromise.reflect()); + validators.push(validatorPromise); }); return Promise - .all(validators) - .then(results => this._handleReflectedResult(field, value, results)); + .all(validators.map(validator => validator.catch(rejection => { + const isBuiltIn = !!rejection.validatorName; + this._pushError(isBuiltIn, field, rejection, value, rejection.validatorName, rejection.validatorArgs); + }))); } /** @@ -239,8 +245,7 @@ class InstanceValidator { * * @returns {Promise} A promise. */ - _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) { - let validatorFunction = null; // the validation function to call + async _invokeCustomValidator(validator, validatorType, optAttrDefined, optValue, optField) { let isAsync = false; const validatorArity = validator.length; @@ -258,17 +263,21 @@ class InstanceValidator { } if (isAsync) { - if (optAttrDefined) { - validatorFunction = Promise.promisify(validator.bind(this.modelInstance, invokeArgs)); - } else { - validatorFunction = Promise.promisify(validator.bind(this.modelInstance)); + try { + if (optAttrDefined) { + return await promisify(validator.bind(this.modelInstance, invokeArgs))(); + } + return await promisify(validator.bind(this.modelInstance))(); + } catch (e) { + return this._pushError(false, errorKey, e, optValue, validatorType); } - return validatorFunction() - .catch(e => this._pushError(false, errorKey, e, optValue, validatorType)); } - return Promise - .try(() => validator.call(this.modelInstance, invokeArgs)) - .catch(e => this._pushError(false, errorKey, e, optValue, validatorType)); + + try { + return await validator.call(this.modelInstance, invokeArgs); + } catch (e) { + return this._pushError(false, errorKey, e, optValue, validatorType); + } } /** @@ -283,21 +292,19 @@ class InstanceValidator { * * @returns {object} An object with specific keys to invoke the validator. */ - _invokeBuiltinValidator(value, test, validatorType, field) { - return Promise.try(() => { - // Cast value as string to pass new Validator.js string requirement - const valueString = String(value); - // check if Validator knows that kind of validation test - if (typeof validator[validatorType] !== 'function') { - throw new Error(`Invalid validator function: ${validatorType}`); - } + async _invokeBuiltinValidator(value, test, validatorType, field) { + // Cast value as string to pass new Validator.js string requirement + const valueString = String(value); + // check if Validator knows that kind of validation test + if (typeof validator[validatorType] !== 'function') { + throw new Error(`Invalid validator function: ${validatorType}`); + } - const validatorArgs = this._extractValidatorArgs(test, validatorType, field); + const validatorArgs = this._extractValidatorArgs(test, validatorType, field); - if (!validator[validatorType](valueString, ...validatorArgs)) { - throw Object.assign(new Error(test.msg || `Validation ${validatorType} on ${field} failed`), { validatorName: validatorType, validatorArgs }); - } - }); + if (!validator[validatorType](valueString, ...validatorArgs)) { + throw Object.assign(new Error(test.msg || `Validation ${validatorType} on ${field} failed`), { validatorName: validatorType, validatorArgs }); + } } /** @@ -368,29 +375,6 @@ class InstanceValidator { } } - - /** - * Handles the returned result of a Promise.reflect. - * - * If errors are found it populates this.error. - * - * @param {string} field The attribute name. - * @param {string|number} value The data value. - * @param {Array} promiseInspections objects. - * - * @private - */ - _handleReflectedResult(field, value, promiseInspections) { - for (const promiseInspection of promiseInspections) { - if (promiseInspection.isRejected()) { - const rejection = promiseInspection.error(); - const isBuiltIn = !!rejection.validatorName; - - this._pushError(isBuiltIn, field, rejection, value, rejection.validatorName, rejection.validatorArgs); - } - } - } - /** * Signs all errors retaining the original. * @@ -422,7 +406,9 @@ class InstanceValidator { } } /** - * @define {string} The error key for arguments as passed by custom validators + * The error key for arguments as passed by custom validators + * + * @type {string} * @private */ InstanceValidator.RAW_KEY_NAME = 'original'; diff --git a/lib/model-manager.js b/lib/model-manager.js index 5d3d47753833..3f36edcf1e3e 100644 --- a/lib/model-manager.js +++ b/lib/model-manager.js @@ -22,8 +22,12 @@ class ModelManager { delete this.sequelize.models[modelToRemove.name]; } - getModel(against, { attribute = 'name' } = {}) { - return this.models.find(model => model[attribute] === against); + getModel(against, options) { + options = _.defaults(options || {}, { + attribute: 'name' + }); + + return this.models.find(model => model[options.attribute] === against); } get all() { @@ -44,10 +48,9 @@ class ModelManager { let sorted; let dep; - options = { - reverse: true, - ...options - }; + options = _.defaults(options || {}, { + reverse: true + }); for (const model of this.models) { let deps = []; diff --git a/lib/model.js b/lib/model.js index d8005b98b6fd..9960422d2383 100644 --- a/lib/model.js +++ b/lib/model.js @@ -11,11 +11,10 @@ const BelongsToMany = require('./associations/belongs-to-many'); const InstanceValidator = require('./instance-validator'); const QueryTypes = require('./query-types'); const sequelizeErrors = require('./errors'); -const Promise = require('./promise'); const Association = require('./associations/base'); const HasMany = require('./associations/has-many'); const DataTypes = require('./data-types'); -const { Hooks } = require('./hooks'); +const Hooks = require('./hooks'); const associationsMixin = require('./associations/mixin'); const Op = require('./operators'); const { noDoubleNestedGroup } = require('./utils/deprecations'); @@ -52,12 +51,12 @@ const nonCascadingOptions = ['include', 'attributes', 'originalAttributes', 'ord * @mixes Hooks */ class Model { - static get QueryInterface() { + static get queryInterface() { return this.sequelize.getQueryInterface(); } - static get QueryGenerator() { - return this.QueryInterface.QueryGenerator; + static get queryGenerator() { + return this.queryInterface.queryGenerator; } /** @@ -84,11 +83,12 @@ class Model { * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` */ constructor(values = {}, options = {}) { - options = Object.assign({ + options = { isNewRecord: true, _schema: this.constructor._schema, - _schemaDelimiter: this.constructor._schemaDelimiter - }, options); + _schemaDelimiter: this.constructor._schemaDelimiter, + ...options + }; if (options.attributes) { options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); @@ -104,12 +104,12 @@ class Model { this.dataValues = {}; this._previousDataValues = {}; - this._changed = {}; - this._modelOptions = this.constructor.options; - this._options = options; + this._changed = new Set(); + this._options = options || {}; /** * Returns true if this instance has not yet been persisted to the database + * * @property isNewRecord * @returns {boolean} */ @@ -122,7 +122,7 @@ class Model { let defaults; let key; - values = values && { ...values }; + values = { ...values }; if (options.isNewRecord) { defaults = {}; @@ -160,12 +160,10 @@ class Model { delete defaults[this.constructor._timestampAttributes.deletedAt]; } - if (Object.keys(defaults).length) { - for (key in defaults) { - if (values[key] === undefined) { - this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.options.dialect), { raw: true }); - delete values[key]; - } + for (key in defaults) { + if (values[key] === undefined) { + this.set(key, Utils.toDefaultValue(defaults[key], this.sequelize.options.dialect), { raw: true }); + delete values[key]; } } } @@ -273,23 +271,18 @@ class Model { }; } - const existingAttributes = { ...this.rawAttributes }; - this.rawAttributes = {}; - - _.each(head, (value, attr) => { - this.rawAttributes[attr] = value; - }); - - _.each(existingAttributes, (value, attr) => { - this.rawAttributes[attr] = value; - }); - + const newRawAttributes = { + ...head, + ...this.rawAttributes + }; _.each(tail, (value, attr) => { - if (this.rawAttributes[attr] === undefined) { - this.rawAttributes[attr] = value; + if (newRawAttributes[attr] === undefined) { + newRawAttributes[attr] = value; } }); + this.rawAttributes = newRawAttributes; + if (!Object.keys(this.primaryKeys).length) { this.primaryKeys.id = this.rawAttributes.id; } @@ -492,9 +485,10 @@ class Model { })(this, includes); } - static _validateIncludedElements(options, tableNames = {}) { + static _validateIncludedElements(options, tableNames) { if (!options.model) options.model = this; + tableNames = tableNames || {}; options.includeNames = []; options.includeMap = {}; @@ -534,7 +528,7 @@ class Model { if (include.subQuery !== false && options.hasDuplicating && options.topLimit) { if (include.duplicating) { - include.subQuery = false; + include.subQuery = include.subQuery || false; include.subQueryFilter = include.hasRequired; } else { include.subQuery = include.hasRequired; @@ -544,10 +538,9 @@ class Model { include.subQuery = include.subQuery || false; if (include.duplicating) { include.subQueryFilter = include.subQuery; - include.subQuery = false; } else { include.subQueryFilter = false; - include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired; + include.subQuery = include.subQuery || include.hasParentRequired && include.hasRequired && !include.separate; } } @@ -587,7 +580,7 @@ class Model { if (include.attributes && !options.raw) { include.model._expandAttributes(include); - include.originalAttributes = this._injectDependentVirtualAttributes(include.attributes); + include.originalAttributes = include.model._injectDependentVirtualAttributes(include.attributes); include = Utils.mapFinderOptions(include, include.model); @@ -610,7 +603,9 @@ class Model { // pseudo include just needed the attribute logic, return if (include._pseudo) { - include.attributes = Object.keys(include.model.tableAttributes); + if (!include.attributes) { + include.attributes = Object.keys(include.model.tableAttributes); + } return Utils.mapFinderOptions(include, include.model); } @@ -625,7 +620,7 @@ class Model { if (!include.include) include.include = []; const through = include.association.through; - include.through = _.defaults(include.through, { + include.through = _.defaults(include.through || {}, { model: through.model, as: through.model.name, association: { @@ -635,6 +630,7 @@ class Model { parent: include }); + if (through.scope) { include.through.where = include.through.where ? { [Op.and]: [include.through.where, through.scope] } : through.scope; } @@ -761,11 +757,10 @@ class Model { throw new Error('Missing "fields" property for index definition'); } - index = { + index = _.defaults(index, { type: '', - parser: null, - ...index - }; + parser: null + }); if (index.type && index.type.toLowerCase() === 'unique') { index.unique = true; @@ -835,10 +830,6 @@ class Model { * The table columns are defined by the hash that is given as the first argument. * Each attribute of the hash represents a column. * - * For more about Validations - * - * More examples, Model Definition - * * @example * Project.init({ * columnA: { @@ -861,9 +852,13 @@ class Model { * sequelize.models.modelName // The model will now be available in models under the class name * * @see - * {@link DataTypes} + * Model Basics guide + * + * @see + * Hooks guide + * * @see - * {@link Hooks} + * Validations & Constraints guide * * @param {object} attributes An object, where each attribute is a column of the table. Each column can be either a DataType, a string or a type-description object, with the properties described below: * @param {string|DataTypes|object} attributes.column The description of a database column @@ -945,14 +940,14 @@ class Model { schema: globalOptions.schema }, options); - this.sequelize.hooks.run('beforeDefine', attributes, options); + this.sequelize.runHooks('beforeDefine', attributes, options); if (options.modelName !== this.name) { Object.defineProperty(this, 'name', { value: options.modelName }); } delete options.modelName; - this.options = Object.assign({ + this.options = { timestamps: true, validate: {}, freezeTableName: false, @@ -964,8 +959,9 @@ class Model { schemaDelimiter: '', defaultScope: {}, scopes: {}, - indexes: [] - }, options); + indexes: [], + ...options + }; // if you call "define" multiple times for the same modelName, do not clutter the factory if (this.sequelize.isDefined(this.name)) { @@ -973,6 +969,7 @@ class Model { } this.associations = {}; + this._setupHooks(options.hooks); this.underscored = this.options.underscored; @@ -1024,16 +1021,28 @@ class Model { // setup names of timestamp attributes if (this.options.timestamps) { + for (const key of ['createdAt', 'updatedAt', 'deletedAt']) { + if (!['undefined', 'string', 'boolean'].includes(typeof this.options[key])) { + throw new Error(`Value for "${key}" option must be a string or a boolean, got ${typeof this.options[key]}`); + } + if (this.options[key] === '') { + throw new Error(`Value for "${key}" option cannot be an empty string`); + } + } + if (this.options.createdAt !== false) { - this._timestampAttributes.createdAt = this.options.createdAt || 'createdAt'; + this._timestampAttributes.createdAt = + typeof this.options.createdAt === 'string' ? this.options.createdAt : 'createdAt'; this._readOnlyAttributes.add(this._timestampAttributes.createdAt); } if (this.options.updatedAt !== false) { - this._timestampAttributes.updatedAt = this.options.updatedAt || 'updatedAt'; + this._timestampAttributes.updatedAt = + typeof this.options.updatedAt === 'string' ? this.options.updatedAt : 'updatedAt'; this._readOnlyAttributes.add(this._timestampAttributes.updatedAt); } if (this.options.paranoid && this.options.deletedAt !== false) { - this._timestampAttributes.deletedAt = this.options.deletedAt || 'deletedAt'; + this._timestampAttributes.deletedAt = + typeof this.options.deletedAt === 'string' ? this.options.deletedAt : 'deletedAt'; this._readOnlyAttributes.add(this._timestampAttributes.deletedAt); } } @@ -1055,9 +1064,7 @@ class Model { this._scopeNames = ['defaultScope']; this.sequelize.modelManager.addModel(this); - this.sequelize.hooks.run('afterDefine', this); - - this.hooks = new Hooks(this.options.hooks, this.sequelize.hooks); + this.sequelize.runHooks('afterDefine', this); return this; } @@ -1273,105 +1280,97 @@ class Model { * * @returns {Promise} */ - static sync(options) { - options = Object.assign({}, this.options, options); + static async sync(options) { + options = { ...this.options, ...options }; options.hooks = options.hooks === undefined ? true : !!options.hooks; const attributes = this.tableAttributes; const rawAttributes = this.fieldRawAttributesMap; - return Promise.try(() => { - if (options.hooks) { - return this.hooks.run('beforeSync', options); - } - }).then(() => { - if (options.force) { - return this.drop(options); - } - }) - .then(() => this.QueryInterface.createTable(this.getTableName(options), attributes, options, this)) - .then(() => { - if (!options.alter) { - return; + if (options.hooks) { + await this.runHooks('beforeSync', options); + } + if (options.force) { + await this.drop(options); + } + + const tableName = this.getTableName(options); + + await this.queryInterface.createTable(tableName, attributes, options, this); + + if (options.alter) { + const tableInfos = await Promise.all([ + this.queryInterface.describeTable(tableName, options), + this.queryInterface.getForeignKeyReferencesForTable(tableName, options) + ]); + const columns = tableInfos[0]; + // Use for alter foreign keys + const foreignKeyReferences = tableInfos[1]; + const removedConstraints = {}; + + for (const columnName in attributes) { + if (!Object.prototype.hasOwnProperty.call(attributes, columnName)) continue; + if (!columns[columnName] && !columns[attributes[columnName].field]) { + await this.queryInterface.addColumn(tableName, attributes[columnName].field || columnName, attributes[columnName], options); } - return Promise.all([ - this.QueryInterface.describeTable(this.getTableName(options)), - this.QueryInterface.getForeignKeyReferencesForTable(this.getTableName(options)) - ]) - .then(tableInfos => { - const columns = tableInfos[0]; - // Use for alter foreign keys - const foreignKeyReferences = tableInfos[1]; - - const changes = []; // array of promises to run - const removedConstraints = {}; - - _.each(attributes, (columnDesc, columnName) => { - if (!columns[columnName] && !columns[attributes[columnName].field]) { - changes.push(() => this.QueryInterface.addColumn(this.getTableName(options), attributes[columnName].field || columnName, attributes[columnName])); - } - }); - _.each(columns, (columnDesc, columnName) => { - const currentAttribute = rawAttributes[columnName]; - if (!currentAttribute) { - changes.push(() => this.QueryInterface.removeColumn(this.getTableName(options), columnName, options)); - } else if (!currentAttribute.primaryKey) { - // Check foreign keys. If it's a foreign key, it should remove constraint first. - const references = currentAttribute.references; - if (currentAttribute.references) { - const database = this.sequelize.config.database; - const schema = this.sequelize.config.schema; - // Find existed foreign keys - _.each(foreignKeyReferences, foreignKeyReference => { - const constraintName = foreignKeyReference.constraintName; - if (!!constraintName - && foreignKeyReference.tableCatalog === database - && (schema ? foreignKeyReference.tableSchema === schema : true) - && foreignKeyReference.referencedTableName === references.model - && foreignKeyReference.referencedColumnName === references.key - && (schema ? foreignKeyReference.referencedTableSchema === schema : true) - && !removedConstraints[constraintName]) { - // Remove constraint on foreign keys. - changes.push(() => this.QueryInterface.removeConstraint(this.getTableName(options), constraintName, options)); - removedConstraints[constraintName] = true; - } - }); - } - changes.push(() => this.QueryInterface.changeColumn(this.getTableName(options), columnName, currentAttribute)); + } + + if (options.alter === true || typeof options.alter === 'object' && options.alter.drop !== false) { + for (const columnName in columns) { + if (!Object.prototype.hasOwnProperty.call(columns, columnName)) continue; + const currentAttribute = rawAttributes[columnName]; + if (!currentAttribute) { + await this.queryInterface.removeColumn(tableName, columnName, options); + continue; + } + if (currentAttribute.primaryKey) continue; + // Check foreign keys. If it's a foreign key, it should remove constraint first. + const references = currentAttribute.references; + if (currentAttribute.references) { + const database = this.sequelize.config.database; + const schema = this.sequelize.config.schema; + // Find existed foreign keys + for (const foreignKeyReference of foreignKeyReferences) { + const constraintName = foreignKeyReference.constraintName; + if (!!constraintName + && foreignKeyReference.tableCatalog === database + && (schema ? foreignKeyReference.tableSchema === schema : true) + && foreignKeyReference.referencedTableName === references.model + && foreignKeyReference.referencedColumnName === references.key + && (schema ? foreignKeyReference.referencedTableSchema === schema : true) + && !removedConstraints[constraintName]) { + // Remove constraint on foreign keys. + await this.queryInterface.removeConstraint(tableName, constraintName, options); + removedConstraints[constraintName] = true; } - }); - return Promise.each(changes, f => f()); - }); - }) - .then(() => this.QueryInterface.showIndex(this.getTableName(options), options)) - .then(indexes => { - indexes = this._indexes.filter(item1 => - !indexes.some(item2 => item1.name === item2.name) - ).sort((index1, index2) => { - if (this.sequelize.options.dialect === 'postgres') { - // move concurrent indexes to the bottom to avoid weird deadlocks - if (index1.concurrently === true) return 1; - if (index2.concurrently === true) return -1; + } } + await this.queryInterface.changeColumn(tableName, columnName, currentAttribute, options); + } + } + } + let indexes = await this.queryInterface.showIndex(tableName, options); + indexes = this._indexes.filter(item1 => + !indexes.some(item2 => item1.name === item2.name) + ).sort((index1, index2) => { + if (this.sequelize.options.dialect === 'postgres') { + // move concurrent indexes to the bottom to avoid weird deadlocks + if (index1.concurrently === true) return 1; + if (index2.concurrently === true) return -1; + } - return 0; - }); + return 0; + }); - return Promise.each(indexes, index => this.QueryInterface.addIndex( - this.getTableName(options), - Object.assign({ - logging: options.logging, - benchmark: options.benchmark, - transaction: options.transaction, - schema: options.schema - }, index), - this.tableName - )); - }).then(() => { - if (options.hooks) { - return this.hooks.run('afterSync', options); - } - }).return(this); + for (const index of indexes) { + await this.queryInterface.addIndex(tableName, { ...options, ...index }); + } + + if (options.hooks) { + await this.runHooks('afterSync', options); + } + + return this; } /** @@ -1384,12 +1383,12 @@ class Model { * * @returns {Promise} */ - static drop(options) { - return this.QueryInterface.dropTable(this.getTableName(options), options); + static async drop(options) { + return await this.queryInterface.dropTable(this.getTableName(options), options); } - static dropSchema(schema) { - return this.QueryInterface.dropSchema(schema); + static async dropSchema(schema) { + return await this.queryInterface.dropSchema(schema); } /** @@ -1438,7 +1437,7 @@ class Model { * @returns {string|object} */ static getTableName() { - return this.QueryGenerator.addSchema(this); + return this.queryGenerator.addSchema(this); } /** @@ -1461,9 +1460,7 @@ class Model { * @param {boolean} [options.override=false] override old scope if already defined */ static addScope(name, scope, options) { - options = Object.assign({ - override: false - }, options); + options = { override: false, ...options }; if ((name === 'defaultScope' && Object.keys(this.options.defaultScope).length > 0 || name in this.options.scopes) && options.override === false) { throw new Error(`The scope ${name} already exists. Pass { override: true } as options to silence this error`); @@ -1567,7 +1564,8 @@ class Model { if (scope) { this._conformIncludes(scope, this); - this._assignOptions(self._scope, scope); + // clone scope so it doesn't get modified + this._assignOptions(self._scope, Utils.cloneDeep(scope)); self._scopeNames.push(scopeName ? scopeName : 'defaultScope'); } else { throw new sequelizeErrors.SequelizeScopeError(`Invalid scope ${scopeName} called.`); @@ -1653,8 +1651,11 @@ class Model { * @param {object} [options.include[].on] Supply your own ON condition for the join. * @param {Array} [options.include[].attributes] A list of attributes to select from the child model * @param {boolean} [options.include[].required] If true, converts to an inner join, which means that the parent model will only be loaded if it has any matching children. True if `include.where` is set, false otherwise. + * @param {boolean} [options.include[].right] If true, converts to a right join if dialect support it. Ignored if `include.required` is true. * @param {boolean} [options.include[].separate] If true, runs a separate query to fetch the associated instances, only supported for hasMany associations * @param {number} [options.include[].limit] Limit the joined rows, only supported with include.separate=true + * @param {string} [options.include[].through.as] The alias for the join model, in case you want to give it a different name than the default one. + * @param {boolean} [options.include[].through.paranoid] If true, only non-deleted records will be returned from the join table. If false, both deleted and non-deleted records will be returned. Only applies if through model is paranoid. * @param {object} [options.include[].through.where] Filter on the join model for belongsToMany relations * @param {Array} [options.include[].through.attributes] A list of attributes to select from the join model for belongsToMany relations * @param {Array} [options.include[].include] Load further nested related models @@ -1663,7 +1664,7 @@ class Model { * @param {number} [options.limit] Limit for result * @param {number} [options.offset] Offset for result * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string|object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. See [transaction.LOCK for an example](transaction#lock) + * @param {string|object} [options.lock] Lock the selected rows. Possible options are transaction.LOCK.UPDATE and transaction.LOCK.SHARE. Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model locks with joins. * @param {boolean} [options.skipLocked] Skip locked rows. Only supported in Postgres. * @param {boolean} [options.raw] Return raw result. See sequelize.query for more information. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. @@ -1671,13 +1672,14 @@ class Model { * @param {object} [options.having] Having options * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean|Error} [options.rejectOnEmpty=false] Throws an error when no records found + * @param {boolean} [options.dotNotation] Allows including tables having the same attribute/column names - which have a dot in them. * * @see * {@link Sequelize#query} * * @returns {Promise>} */ - static findAll(options) { + static async findAll(options) { if (options !== undefined && !_.isPlainObject(options)) { throw new sequelizeErrors.QueryError('The argument passed to findAll must be an options object, use findByPk if you wish to pass a single primary key value'); } @@ -1693,87 +1695,79 @@ class Model { const tableNames = {}; tableNames[this.getTableName(options)] = true; - options = { - hooks: true, - ...Utils.cloneDeep(options) - }; + options = Utils.cloneDeep(options); + + _.defaults(options, { hooks: true }); // set rejectOnEmpty option, defaults to model options options.rejectOnEmpty = Object.prototype.hasOwnProperty.call(options, 'rejectOnEmpty') ? options.rejectOnEmpty : this.options.rejectOnEmpty; - return Promise.try(() => { - this._injectScope(options); + this._injectScope(options); - if (options.hooks) { - return this.hooks.run('beforeFind', options); - } - }).then(() => { - this._conformIncludes(options, this); - this._expandAttributes(options); - this._expandIncludeAll(options); + if (options.hooks) { + await this.runHooks('beforeFind', options); + } + this._conformIncludes(options, this); + this._expandAttributes(options); + this._expandIncludeAll(options); - if (options.hooks) { - return this.hooks.run('beforeFindAfterExpandIncludeAll', options); - } - }).then(() => { - options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); + if (options.hooks) { + await this.runHooks('beforeFindAfterExpandIncludeAll', options); + } + options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); - if (options.include) { - options.hasJoin = true; + if (options.include) { + options.hasJoin = true; - this._validateIncludedElements(options, tableNames); + this._validateIncludedElements(options, tableNames); - // If we're not raw, we have to make sure we include the primary key for de-duplication - if ( - options.attributes - && !options.raw - && this.primaryKeyAttribute - && !options.attributes.includes(this.primaryKeyAttribute) - && (!options.group || !options.hasSingleAssociation || options.hasMultiAssociation) - ) { - options.attributes = [this.primaryKeyAttribute].concat(options.attributes); - } + // If we're not raw, we have to make sure we include the primary key for de-duplication + if ( + options.attributes + && !options.raw + && this.primaryKeyAttribute + && !options.attributes.includes(this.primaryKeyAttribute) + && (!options.group || !options.hasSingleAssociation || options.hasMultiAssociation) + ) { + options.attributes = [this.primaryKeyAttribute].concat(options.attributes); } + } - if (!options.attributes) { - options.attributes = Object.keys(this.rawAttributes); - options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); - } + if (!options.attributes) { + options.attributes = Object.keys(this.rawAttributes); + options.originalAttributes = this._injectDependentVirtualAttributes(options.attributes); + } - // whereCollection is used for non-primary key updates - this.options.whereCollection = options.where || null; + // whereCollection is used for non-primary key updates + this.options.whereCollection = options.where || null; - Utils.mapFinderOptions(options, this); + Utils.mapFinderOptions(options, this); - options = this._paranoidClause(this, options); + options = this._paranoidClause(this, options); - if (options.hooks) { - return this.hooks.run('beforeFindAfterOptions', options); - } - }).then(() => { - const selectOptions = Object.assign({}, options, { tableNames: Object.keys(tableNames) }); - return this.QueryInterface.select(this, this.getTableName(selectOptions), selectOptions); - }).tap(results => { - if (options.hooks) { - return this.hooks.run('afterFind', results, options); - } - }).then(results => { + if (options.hooks) { + await this.runHooks('beforeFindAfterOptions', options); + } + const selectOptions = { ...options, tableNames: Object.keys(tableNames) }; + const results = await this.queryInterface.select(this, this.getTableName(selectOptions), selectOptions); + if (options.hooks) { + await this.runHooks('afterFind', results, options); + } - //rejectOnEmpty mode - if (_.isEmpty(results) && options.rejectOnEmpty) { - if (typeof options.rejectOnEmpty === 'function') { - throw new options.rejectOnEmpty(); - } - if (typeof options.rejectOnEmpty === 'object') { - throw options.rejectOnEmpty; - } - throw new sequelizeErrors.EmptyResultError(); + //rejectOnEmpty mode + if (_.isEmpty(results) && options.rejectOnEmpty) { + if (typeof options.rejectOnEmpty === 'function') { + throw new options.rejectOnEmpty(); } + if (typeof options.rejectOnEmpty === 'object') { + throw options.rejectOnEmpty; + } + throw new sequelizeErrors.EmptyResultError(); + } - return Model._findSeparate(results, options); - }); + return await Model._findSeparate(results, options); } static warnOnInvalidOptions(options, validColumnNames) { @@ -1806,17 +1800,17 @@ class Model { return attributes; } - static _findSeparate(results, options) { - if (!options.include || options.raw || !results) return Promise.resolve(results); + static async _findSeparate(results, options) { + if (!options.include || options.raw || !results) return results; const original = results; if (options.plain) results = [results]; if (!results.length) return original; - return Promise.map(options.include, include => { + await Promise.all(options.include.map(async include => { if (!include.separate) { - return Model._findSeparate( + return await Model._findSeparate( results.reduce((memo, result) => { let associations = result.get(include.association.as); @@ -1831,28 +1825,30 @@ class Model { } return memo; }, []), - Object.assign( - {}, - _.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain', 'scope'), - { include: include.include || [] } - ) + { + + ..._.omit(options, 'include', 'attributes', 'order', 'where', 'limit', 'offset', 'plain', 'scope'), + include: include.include || [] + } ); } - return include.association.get(results, Object.assign( - {}, - _.omit(options, nonCascadingOptions), - _.omit(include, ['parent', 'association', 'as', 'originalAttributes']) - )).then(map => { - for (const result of results) { - result.set( - include.association.as, - map[result.get(include.association.sourceKey)], - { raw: true } - ); - } + const map = await include.association.get(results, { + + ..._.omit(options, nonCascadingOptions), + ..._.omit(include, ['parent', 'association', 'as', 'originalAttributes']) }); - }).return(original); + + for (const result of results) { + result.set( + include.association.as, + map[result.get(include.association.sourceKey)], + { raw: true } + ); + } + })); + + return original; } /** @@ -1868,13 +1864,13 @@ class Model { * * @returns {Promise} */ - static findByPk(param, options) { + static async findByPk(param, options) { // return Promise resolved with null if no arguments are passed if ([null, undefined].includes(param)) { - return Promise.resolve(null); + return null; } - options = Utils.cloneDeep(options); + options = Utils.cloneDeep(options) || {}; if (typeof param === 'number' || typeof param === 'string' || Buffer.isBuffer(param)) { options.where = { @@ -1885,13 +1881,11 @@ class Model { } // Bypass a possible overloaded findOne - return this.findOne(options); + return await this.findOne(options); } /** - * Search for a single instance. This applies LIMIT 1, so the listener will always be called with a single instance. - * - * __Alias__: _find_ + * Search for a single instance. Returns the first instance found, or null if none can be found. * * @param {object} [options] A hash of options to describe the scope of the search * @param {Transaction} [options.transaction] Transaction to run query under @@ -1900,9 +1894,9 @@ class Model { * @see * {@link Model.findAll} for an explanation of options * - * @returns {Promise} + * @returns {Promise} */ - static findOne(options) { + static async findOne(options) { if (options !== undefined && !_.isPlainObject(options)) { throw new Error('The argument passed to findOne must be an options object, use findByPk if you wish to pass a single primary key value'); } @@ -1921,10 +1915,9 @@ class Model { } // Bypass a possible overloaded findAll. - return this.findAll({ - plain: true, - ...options - }); + return await this.findAll(_.defaults(options, { + plain: true + })); } /** @@ -1943,7 +1936,7 @@ class Model { * * @returns {Promise} Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in which case the complete data result is returned. */ - static aggregate(attribute, aggregateFunction, options) { + static async aggregate(attribute, aggregateFunction, options) { options = Utils.cloneDeep(options); // We need to preserve attributes here as the `injectScope` call would inject non aggregate columns. @@ -1991,12 +1984,8 @@ class Model { Utils.mapOptionFieldNames(options, this); options = this._paranoidClause(this, options); - return this.QueryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this).then( value => { - if (value === null) { - return 0; - } - return value; - }); + const value = await this.queryInterface.rawSelect(this.getTableName(options), options, aggregateFunction, this); + return value; } /** @@ -2019,47 +2008,53 @@ class Model { * * @returns {Promise} */ - static count(options) { - return Promise.try(() => { - options = { - hooks: true, - ...options, - raw: true - }; - if (options.hooks) { - return this.hooks.run('beforeCount', options); - } - }).then(() => { - let col = options.col || '*'; - if (options.include) { - col = `${this.name}.${options.col || this.primaryKeyField}`; - } - - options.plain = !options.group; - options.dataType = new DataTypes.INTEGER(); - options.includeIgnoreAttributes = false; - - // No limit, offset or order for the options max be given to count() - // Set them to null to prevent scopes setting those values - options.limit = null; - options.offset = null; - options.order = null; + static async count(options) { + options = Utils.cloneDeep(options); + options = _.defaults(options, { hooks: true }); + options.raw = true; + if (options.hooks) { + await this.runHooks('beforeCount', options); + } + let col = options.col || '*'; + if (options.include) { + col = `${this.name}.${options.col || this.primaryKeyField}`; + } + if (options.distinct && col === '*') { + col = this.primaryKeyField; + } + options.plain = !options.group; + options.dataType = new DataTypes.INTEGER(); + options.includeIgnoreAttributes = false; + + // No limit, offset or order for the options max be given to count() + // Set them to null to prevent scopes setting those values + options.limit = null; + options.offset = null; + options.order = null; + + const result = await this.aggregate(col, 'count', options); + + // When grouping is used, some dialects such as PG are returning the count as string + // --> Manually convert it to number + if (Array.isArray(result)) { + return result.map(item => ({ + ...item, + count: Number(item.count) + })); + } - return this.aggregate(col, 'count', options); - }); + return result; } /** * Find all the rows matching your query, within a specified offset / limit, and get the total number of rows matching your query. This is very useful for paging * * @example - * Model.findAndCountAll({ + * const result = await Model.findAndCountAll({ * where: ..., * limit: 12, * offset: 12 - * }).then(result => { - * ... - * }) + * }); * * # In the above example, `result.rows` will contain rows 13 through 24, while `result.count` will return the total number of rows that matched your query. * @@ -2071,7 +2066,7 @@ class Model { * include: [ * { model: Profile, required: true} * ], - * limit 3 + * limit: 3 * }); * * # Because the include for `Profile` has `required` set it will result in an inner join, and only the users who have a profile will be counted. If we remove `required` from the include, both users with and without profiles will be counted @@ -2083,9 +2078,9 @@ class Model { * @see * {@link Model.count} for a specification of count options * - * @returns {Promise<{count: number, rows: Model[]}>} + * @returns {Promise<{count: number | number[], rows: Model[]}>} */ - static findAndCountAll(options) { + static async findAndCountAll(options) { if (options !== undefined && !_.isPlainObject(options)) { throw new Error('The argument passed to findAndCountAll must be an options object, use findByPk if you wish to pass a single primary key value'); } @@ -2096,14 +2091,15 @@ class Model { countOptions.attributes = undefined; } - return Promise.all([ + const [count, rows] = await Promise.all([ this.count(countOptions), this.findAll(options) - ]) - .then(([count, rows]) => ({ - count, - rows: count === 0 ? [] : rows - })); + ]); + + return { + count, + rows: count === 0 ? [] : rows + }; } /** @@ -2117,8 +2113,8 @@ class Model { * * @returns {Promise<*>} */ - static max(field, options) { - return this.aggregate(field, 'max', options); + static async max(field, options) { + return await this.aggregate(field, 'max', options); } /** @@ -2132,8 +2128,8 @@ class Model { * * @returns {Promise<*>} */ - static min(field, options) { - return this.aggregate(field, 'min', options); + static async min(field, options) { + return await this.aggregate(field, 'min', options); } /** @@ -2147,24 +2143,31 @@ class Model { * * @returns {Promise} */ - static sum(field, options) { - return this.aggregate(field, 'sum', options); + static async sum(field, options) { + return await this.aggregate(field, 'sum', options); } /** - * Builds multiple models in one operation. + * Builds a new model instance. * - * @param {Array} valueSets An object of key value pairs or an array of such. If an array, the function will return an array of instances. - * @param {object} [options] Instance build options, - * @see - * {@link constructor} + * @param {object|Array} values An object of key value pairs or an array of such. If an array, the function will return an array of instances. + * @param {object} [options] Instance build options + * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. + * @param {boolean} [options.isNewRecord=true] Is this new record + * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` * - * @returns {Array} + * @returns {Model|Array} */ + static build(values, options) { + if (Array.isArray(values)) { + return this.bulkBuild(values, options); + } + + return new this(values, options); + } + static bulkBuild(valueSets, options) { - options = Object.assign({ - isNewRecord: true - }, options); + options = { isNewRecord: true, ...options }; if (!options.includeValidated) { this._conformIncludes(options, this); @@ -2178,40 +2181,39 @@ class Model { options.attributes = options.attributes.map(attribute => Array.isArray(attribute) ? attribute[1] : attribute); } - return valueSets.map(values => new this(values, options)); + return valueSets.map(values => this.build(values, options)); } /** * Builds a new model instance and calls save on it. - + * * @see - * {@link Model.constructor} + * {@link Model.build} * @see * {@link Model.save} * - * @param {object} values hash of data values to create new record with - * @param {object} [options] build and query options - * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. - * @param {boolean} [options.isNewRecord=true] Is this new record - * @param {Array} [options.include] an array of include options - Used to build prefetched/included model instances. See `set` - * @param {Array} [options.fields] If set, only columns matching those in fields will be saved - * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * @param {boolean} [options.validate=true] If false, validations won't be run. - * @param {boolean} [options.hooks=true] Run before and after create / update + validate hooks - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) - * @param {boolean} [options.returning=true] Return the affected rows (only for postgres) + * @param {object} values Hash of data values to create new record with + * @param {object} [options] Build and query options + * @param {boolean} [options.raw=false] If set to true, values will ignore field and virtual setters. + * @param {boolean} [options.isNewRecord=true] Is this new record + * @param {Array} [options.include] An array of include options - Used to build prefetched/included model instances. See `set` + * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. + * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. + * @param {boolean} [options.validate=true] If false, validations won't be run. + * @param {boolean} [options.hooks=true] Run before and after create / update + validate hooks + * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. + * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). + * @param {Transaction} [options.transaction] Transaction to run query under + * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) + * @param {boolean|Array} [options.returning=true] Appends RETURNING to get back all defined values; if an array of column names, append RETURNING to get back specific columns (Postgres only) * * @returns {Promise} * */ - static create(values, options) { - options = Utils.cloneDeep(options); + static async create(values, options) { + options = Utils.cloneDeep(options || {}); - return new this(values, { + return await this.build(values, { isNewRecord: true, attributes: options.fields, include: options.include, @@ -2231,7 +2233,7 @@ class Model { * * @returns {Promise} */ - static findOrBuild(options) { + static async findOrBuild(options) { if (!options || !options.where || arguments.length > 1) { throw new Error( 'Missing where attribute in the options parameter passed to findOrBuild. ' + @@ -2241,20 +2243,19 @@ class Model { let values; - return this.findOne(options).then(instance => { - if (instance === null) { - values = { ...options.defaults }; - if (_.isPlainObject(options.where)) { - values = Utils.defaults(values, options.where); - } + let instance = await this.findOne(options); + if (instance === null) { + values = { ...options.defaults }; + if (_.isPlainObject(options.where)) { + values = Utils.defaults(values, options.where); + } - instance = new this(values, options); + instance = this.build(values, options); - return [instance, true]; - } + return [instance, true]; + } - return [instance, false]; - }); + return [instance, false]; } /** @@ -2275,7 +2276,7 @@ class Model { * * @returns {Promise} */ - static findOrCreate(options) { + static async findOrCreate(options) { if (!options || !options.where || arguments.length > 1) { throw new Error( 'Missing where attribute in the options parameter passed to findOrCreate. ' + @@ -2283,7 +2284,7 @@ class Model { ); } - options = Object.assign({}, options); + options = { ...options }; if (options.defaults) { const defaults = Object.keys(options.defaults); @@ -2294,19 +2295,25 @@ class Model { } } + if (options.transaction === undefined && this.sequelize.constructor._cls) { + const t = this.sequelize.constructor._cls.get('transaction'); + if (t) { + options.transaction = t; + } + } + const internalTransaction = !options.transaction; let values; let transaction; - // Create a transaction or a savepoint, depending on whether a transaction was passed in - return this.sequelize.transaction(options).then(t => { + try { + const t = await this.sequelize.transaction(options); transaction = t; options.transaction = t; - return this.findOne(Utils.defaults({ transaction }, options)); - }).then(instance => { - if (instance !== null) { - return [instance, false]; + const found = await this.findOne(Utils.defaults({ transaction }, options)); + if (found !== null) { + return [found, false]; } values = { ...options.defaults }; @@ -2315,15 +2322,18 @@ class Model { } options.exception = true; + options.returning = true; - return this.create(values, options).then(instance => { - if (instance.get(this.primaryKeyAttribute, { raw: true }) === null) { + try { + const created = await this.create(values, options); + if (created.get(this.primaryKeyAttribute, { raw: true }) === null) { // If the query returned an empty result for the primary key, we know that this was actually a unique constraint violation throw new sequelizeErrors.UniqueConstraintError(); } - return [instance, true]; - }).catch(sequelizeErrors.UniqueConstraintError, err => { + return [created, true]; + } catch (err) { + if (!(err instanceof sequelizeErrors.UniqueConstraintError)) throw err; const flattenedWhere = Utils.flattenObjectDeep(options.where); const flattenedWhereKeys = Object.keys(flattenedWhere).map(name => _.last(name.split('.'))); const whereFields = flattenedWhereKeys.map(name => _.get(this.rawAttributes, `${name}.field`, name)); @@ -2347,25 +2357,25 @@ class Model { } // Someone must have created a matching instance inside the same transaction since we last did a find. Let's find it! - return this.findOne(Utils.defaults({ + const otherCreated = await this.findOne(Utils.defaults({ transaction: internalTransaction ? null : transaction - }, options)).then(instance => { - // Sanity check, ideally we caught this at the defaultFeilds/err.fields check - // But if we didn't and instance is null, we will throw - if (instance === null) throw err; - return [instance, false]; - }); - }); - }).finally(() => { + }, options)); + + // Sanity check, ideally we caught this at the defaultFeilds/err.fields check + // But if we didn't and instance is null, we will throw + if (otherCreated === null) throw err; + + return [otherCreated, false]; + } + } finally { if (internalTransaction && transaction) { - // If we created a transaction internally (and not just a savepoint), we should clean it up - return transaction.commit(); + await transaction.commit(); } - }); + } } /** - * A more performant findOrCreate that will not work under a transaction (at least not in postgres) + * A more performant findOrCreate that may not work under a transaction (working in postgres) * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again * * @see @@ -2377,7 +2387,7 @@ class Model { * * @returns {Promise} */ - static findCreateFind(options) { + static async findCreateFind(options) { if (!options || !options.where) { throw new Error( 'Missing where attribute in the options parameter passed to findCreateFind.' @@ -2390,13 +2400,27 @@ class Model { } - return this.findOne(options).then(result => { - if (result) return [result, false]; + const found = await this.findOne(options); + if (found) return [found, false]; - return this.create(values, options) - .then(result => [result, true]) - .catch(sequelizeErrors.UniqueConstraintError, () => this.findOne(options).then(result => [result, false])); - }); + try { + const createOptions = { ...options }; + + // To avoid breaking a postgres transaction, run the create with `ignoreDuplicates`. + if (this.sequelize.options.dialect === 'postgres' && options.transaction) { + createOptions.ignoreDuplicates = true; + } + + const created = await this.create(values, createOptions); + return [created, true]; + } catch (err) { + if (!(err instanceof sequelizeErrors.UniqueConstraintError || err instanceof sequelizeErrors.EmptyResultError)) { + throw err; + } + + const foundAgain = await this.findOne(options); + return [foundAgain, false]; + } } /** @@ -2404,92 +2428,86 @@ class Model { * * **Implementation details:** * - * * MySQL - Implemented as a single query `INSERT values ON DUPLICATE KEY UPDATE values` - * * PostgreSQL - Implemented as a temporary function with exception handling: INSERT EXCEPTION WHEN unique_constraint UPDATE - * * SQLite - Implemented as two queries `INSERT; UPDATE`. This means that the update is executed regardless of whether the row already existed or not + * * MySQL - Implemented with ON DUPLICATE KEY UPDATE` + * * PostgreSQL - Implemented with ON CONFLICT DO UPDATE. If update data contains PK field, then PK is selected as the default conflict key. Otherwise first unique constraint/index will be selected, which can satisfy conflict key requirements. + * * SQLite - Implemented with ON CONFLICT DO UPDATE * * MSSQL - Implemented as a single query using `MERGE` and `WHEN (NOT) MATCHED THEN` - * **Note** that SQLite returns undefined for created, no matter if the row was created or updated. This is because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know whether the row was inserted or not. * - * @param {object} values hash of values to upsert - * @param {object} [options] upsert options - * @param {boolean} [options.validate=true] Run validations before the row is inserted - * @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all changed fields - * @param {boolean} [options.hooks=true] Run before / after upsert hooks? - * @param {boolean} [options.returning=false] Append RETURNING * to get back auto generated values (Postgres only) - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) + * **Note** that Postgres/SQLite returns null for created, no matter if the row was created or updated * - * @returns {Promise} Returns a boolean indicating whether the row was created or updated. For MySQL/MariaDB, it returns `true` when inserted and `false` when updated. For Postgres/MSSQL with (options.returning=true), it returns record and created boolean with signature ``. + * @param {object} values hash of values to upsert + * @param {object} [options] upsert options + * @param {boolean} [options.validate=true] Run validations before the row is inserted + * @param {Array} [options.fields=Object.keys(this.attributes)] The fields to insert / update. Defaults to all changed fields + * @param {boolean} [options.hooks=true] Run before / after upsert hooks? + * @param {boolean} [options.returning=true] If true, fetches back auto generated values + * @param {Transaction} [options.transaction] Transaction to run query under + * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. + * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). + * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) + * + * @returns {Promise>} returns an array with two elements, the first being the new record and the second being `true` if it was just created or `false` if it already existed (except on Postgres and SQLite, which can't detect this and will always return `null` instead of a boolean). */ - static upsert(values, options) { - options = Object.assign({ + static async upsert(values, options) { + options = { hooks: true, - returning: false, - validate: true - }, Utils.cloneDeep(options)); - - options.model = this; + returning: true, + validate: true, + ...Utils.cloneDeep(options) + }; const createdAtAttr = this._timestampAttributes.createdAt; const updatedAtAttr = this._timestampAttributes.updatedAt; const hasPrimary = this.primaryKeyField in values || this.primaryKeyAttribute in values; - const instance = new this(values); + const instance = this.build(values); + + options.model = this; + options.instance = instance; + const changed = Array.from(instance._changed); if (!options.fields) { - options.fields = Object.keys(instance._changed); + options.fields = changed; } - return Promise.try(() => { - if (options.validate) { - return instance.validate(options); - } - }).then(() => { - // Map field names - const updatedDataValues = _.pick(instance.dataValues, Object.keys(instance._changed)); - const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this); - const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this); - const now = Utils.now(this.sequelize.options.dialect); - - // Attach createdAt - if (createdAtAttr && !updateValues[createdAtAttr]) { - const field = this.rawAttributes[createdAtAttr].field || createdAtAttr; - insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now; - } - if (updatedAtAttr && !insertValues[updatedAtAttr]) { - const field = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; - insertValues[field] = updateValues[field] = this._getDefaultTimestamp(updatedAtAttr) || now; - } + if (options.validate) { + await instance.validate(options); + } + // Map field names + const updatedDataValues = _.pick(instance.dataValues, changed); + const insertValues = Utils.mapValueFieldNames(instance.dataValues, Object.keys(instance.rawAttributes), this); + const updateValues = Utils.mapValueFieldNames(updatedDataValues, options.fields, this); + const now = Utils.now(this.sequelize.options.dialect); - // Build adds a null value for the primary key, if none was given by the user. - // We need to remove that because of some Postgres technicalities. - if (!hasPrimary && this.primaryKeyAttribute && !this.rawAttributes[this.primaryKeyAttribute].defaultValue) { - delete insertValues[this.primaryKeyField]; - delete updateValues[this.primaryKeyField]; - } + // Attach createdAt + if (createdAtAttr && !insertValues[createdAtAttr]) { + const field = this.rawAttributes[createdAtAttr].field || createdAtAttr; + insertValues[field] = this._getDefaultTimestamp(createdAtAttr) || now; + } + if (updatedAtAttr && !insertValues[updatedAtAttr]) { + const field = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; + insertValues[field] = updateValues[field] = this._getDefaultTimestamp(updatedAtAttr) || now; + } - return Promise.try(() => { - if (options.hooks) { - return this.hooks.run('beforeUpsert', values, options); - } - }) - .then(() => { - return this.QueryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), this, options); - }) - .then(([created, primaryKey]) => { - if (options.returning === true && primaryKey) { - return this.findByPk(primaryKey, options).then(record => [record, created]); - } + // Build adds a null value for the primary key, if none was given by the user. + // We need to remove that because of some Postgres technicalities. + if (!hasPrimary && this.primaryKeyAttribute && !this.rawAttributes[this.primaryKeyAttribute].defaultValue) { + delete insertValues[this.primaryKeyField]; + delete updateValues[this.primaryKeyField]; + } - return created; - }) - .tap(result => { - if (options.hooks) { - return this.hooks.run('afterUpsert', result, options); - } - }); - }); + if (options.hooks) { + await this.runHooks('beforeUpsert', values, options); + } + const result = await this.queryInterface.upsert(this.getTableName(options), insertValues, updateValues, instance.where(), options); + + const [record] = result; + record.isNewRecord = false; + + if (options.hooks) { + await this.runHooks('afterUpsert', result, options); + return result; + } + return result; } /** @@ -2499,31 +2517,32 @@ class Model { * and SQLite do not make it easy to obtain back automatically generated IDs and other default values in a way that can be mapped to multiple records. * To obtain Instances for the newly created values, you will need to query for them again. * - * If validation fails, the promise is rejected with an array-like [AggregateError](http://bluebirdjs.com/docs/api/aggregateerror.html) - * - * @param {Array} records List of objects (key/value pairs) to create instances from - * @param {object} [options] Bulk create options - * @param {Array} [options.fields] Fields to insert (defaults to all fields) - * @param {boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation - * @param {boolean} [options.hooks=true] Run before / after bulk create hooks? - * @param {boolean} [options.individualHooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if options.hooks is true. - * @param {boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by MSSQL or Postgres < 9.5) - * @param {Array} [options.updateOnDuplicate] Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {boolean|Array} [options.returning=false] If true, append RETURNING * to get back all values; if an array of column names, append RETURNING to get back specific columns (Postgres only) - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) + * If validation fails, the promise is rejected with an array-like AggregateError + * + * @param {Array} records List of objects (key/value pairs) to create instances from + * @param {object} [options] Bulk create options + * @param {Array} [options.fields] Fields to insert (defaults to all fields) + * @param {boolean} [options.validate=false] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation + * @param {boolean} [options.hooks=true] Run before / after bulk create hooks? + * @param {boolean} [options.individualHooks=false] Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if options.hooks is true. + * @param {boolean} [options.ignoreDuplicates=false] Ignore duplicate values for primary keys? (not supported by MSSQL or Postgres < 9.5) + * @param {Array} [options.updateOnDuplicate] Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. + * @param {Transaction} [options.transaction] Transaction to run query under + * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. + * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). + * @param {boolean|Array} [options.returning=false] If true, append RETURNING to get back all defined values; if an array of column names, append RETURNING to get back specific columns (Postgres only) + * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * * @returns {Promise>} */ - static bulkCreate(records, options = {}) { + static async bulkCreate(records, options = {}) { if (!records.length) { - return Promise.resolve([]); + return []; } const dialect = this.sequelize.options.dialect; const now = Utils.now(this.sequelize.options.dialect); + options = Utils.cloneDeep(options); options.model = this; @@ -2535,15 +2554,16 @@ class Model { } } - const instances = records.map(values => new this(values, { isNewRecord: true, include: options.include })); + const instances = records.map(values => this.build(values, { isNewRecord: true, include: options.include })); - const recursiveBulkCreate = (instances, options) => { - options = Object.assign({ + const recursiveBulkCreate = async (instances, options) => { + options = { validate: false, hooks: true, individualHooks: false, - ignoreDuplicates: false - }, options); + ignoreDuplicates: false, + ...options + }; if (options.returning === undefined) { if (options.association) { @@ -2554,10 +2574,10 @@ class Model { } if (options.ignoreDuplicates && ['mssql'].includes(dialect)) { - return Promise.reject(new Error(`${dialect} does not support the ignoreDuplicates option.`)); + throw new Error(`${dialect} does not support the ignoreDuplicates option.`); } if (options.updateOnDuplicate && (dialect !== 'mysql' && dialect !== 'mariadb' && dialect !== 'sqlite' && dialect !== 'postgres')) { - return Promise.reject(new Error(`${dialect} does not support the updateOnDuplicate option.`)); + throw new Error(`${dialect} does not support the updateOnDuplicate option.`); } const model = options.model; @@ -2573,53 +2593,49 @@ class Model { options.updateOnDuplicate ); } else { - return Promise.reject(new Error('updateOnDuplicate option only supports non-empty array.')); + throw new Error('updateOnDuplicate option only supports non-empty array.'); } } - return Promise.try(() => { - // Run before hook - if (options.hooks) { - return model.hooks.run('beforeBulkCreate', instances, options); - } - }).then(() => { - // Validate - if (options.validate) { - const errors = new Promise.AggregateError(); - const validateOptions = { ...options }; - validateOptions.hooks = options.individualHooks; - - return Promise.map(instances, instance => - instance.validate(validateOptions).catch(err => { - errors.push(new sequelizeErrors.BulkRecordError(err, instance)); - }) - ).then(() => { - delete options.skip; - if (errors.length) { - throw errors; - } - }); - } - }).then(() => { - if (options.individualHooks) { - // Create each instance individually - return Promise.map(instances, instance => { - const individualOptions = { ...options }; - delete individualOptions.fields; - delete individualOptions.individualHooks; - delete individualOptions.ignoreDuplicates; - individualOptions.validate = false; - individualOptions.hooks = true; + // Run before hook + if (options.hooks) { + await model.runHooks('beforeBulkCreate', instances, options); + } + // Validate + if (options.validate) { + const errors = []; + const validateOptions = { ...options }; + validateOptions.hooks = options.individualHooks; + + await Promise.all(instances.map(async instance => { + try { + await instance.validate(validateOptions); + } catch (err) { + errors.push(new sequelizeErrors.BulkRecordError(err, instance)); + } + })); - return instance.save(individualOptions); - }); + delete options.skip; + if (errors.length) { + throw new sequelizeErrors.AggregateError(errors); } + } + if (options.individualHooks) { + await Promise.all(instances.map(async instance => { + const individualOptions = { + ...options, + validate: false, + hooks: true + }; + delete individualOptions.fields; + delete individualOptions.individualHooks; + delete individualOptions.ignoreDuplicates; - return Promise.resolve().then(() => { - if (!options.include || !options.include.length) return; - - // Nested creation for BelongsTo relations - return Promise.map(options.include.filter(include => include.association instanceof BelongsTo), include => { + await instance.save(individualOptions); + })); + } else { + if (options.include && options.include.length) { + await Promise.all(options.include.filter(include => include.association instanceof BelongsTo).map(async include => { const associationInstances = []; const associationInstanceIndexToInstanceMap = []; @@ -2642,96 +2658,105 @@ class Model { logging: options.logging }).value(); - return recursiveBulkCreate(associationInstances, includeOptions).then(associationInstances => { - for (const idx in associationInstances) { - const associationInstance = associationInstances[idx]; - const instance = associationInstanceIndexToInstanceMap[idx]; + const createdAssociationInstances = await recursiveBulkCreate(associationInstances, includeOptions); + for (const idx in createdAssociationInstances) { + const associationInstance = createdAssociationInstances[idx]; + const instance = associationInstanceIndexToInstanceMap[idx]; - instance[include.association.accessors.set](associationInstance, { save: false, logging: options.logging }); - } - }); - }); - }).then(() => { - // Create all in one query - // Recreate records from instances to represent any changes made in hooks or validation - records = instances.map(instance => { - const values = instance.dataValues; - - // set createdAt/updatedAt attributes - if (createdAtAttr && !values[createdAtAttr]) { - values[createdAtAttr] = now; - if (!options.fields.includes(createdAtAttr)) { - options.fields.push(createdAtAttr); - } - } - if (updatedAtAttr && !values[updatedAtAttr]) { - values[updatedAtAttr] = now; - if (!options.fields.includes(updatedAtAttr)) { - options.fields.push(updatedAtAttr); - } + await include.association.set(instance, associationInstance, { save: false, logging: options.logging }); } + })); + } - const out = Object.assign({}, Utils.mapValueFieldNames(values, options.fields, model)); - for (const key of model._virtualAttributes) { - delete out[key]; + // Create all in one query + // Recreate records from instances to represent any changes made in hooks or validation + records = instances.map(instance => { + const values = instance.dataValues; + + // set createdAt/updatedAt attributes + if (createdAtAttr && !values[createdAtAttr]) { + values[createdAtAttr] = now; + if (!options.fields.includes(createdAtAttr)) { + options.fields.push(createdAtAttr); } - return out; - }); + } + if (updatedAtAttr && !values[updatedAtAttr]) { + values[updatedAtAttr] = now; + if (!options.fields.includes(updatedAtAttr)) { + options.fields.push(updatedAtAttr); + } + } - // Map attributes to fields for serial identification - const fieldMappedAttributes = {}; - for (const attr in model.tableAttributes) { - fieldMappedAttributes[model.rawAttributes[attr].field || attr] = model.rawAttributes[attr]; + const out = Utils.mapValueFieldNames(values, options.fields, model); + for (const key of model._virtualAttributes) { + delete out[key]; } + return out; + }); - // Map updateOnDuplicate attributes to fields - if (options.updateOnDuplicate) { - options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr); - // Get primary keys for postgres to enable updateOnDuplicate - options.upsertKeys = _.chain(model.primaryKeys).values().map('field').value(); - if (Object.keys(model.uniqueKeys).length > 0) { - options.upsertKeys = _.chain(model.uniqueKeys).values().filter(c => c.fields.length === 1).map('column').value(); + // Map attributes to fields for serial identification + const fieldMappedAttributes = {}; + for (const attr in model.tableAttributes) { + fieldMappedAttributes[model.rawAttributes[attr].field || attr] = model.rawAttributes[attr]; + } + + // Map updateOnDuplicate attributes to fields + if (options.updateOnDuplicate) { + options.updateOnDuplicate = options.updateOnDuplicate.map(attr => model.rawAttributes[attr].field || attr); + + const upsertKeys = []; + + for (const i of model._indexes) { + if (i.unique && !i.where) { // Don't infer partial indexes + upsertKeys.push(...i.fields); } } - // Map returning attributes to fields - if (options.returning && Array.isArray(options.returning)) { - options.returning = options.returning.map(attr => model.rawAttributes[attr].field || attr); + const firstUniqueKey = Object.values(model.uniqueKeys).find(c => c.fields.length > 0); + + if (firstUniqueKey && firstUniqueKey.fields) { + upsertKeys.push(...firstUniqueKey.fields); } - return model.QueryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes).then(results => { - if (Array.isArray(results)) { - results.forEach((result, i) => { - const instance = instances[i]; - - for (const key in result) { - if (!instance || key === model.primaryKeyAttribute && - instance.get(model.primaryKeyAttribute) && - ['mysql', 'mariadb', 'sqlite'].includes(dialect)) { - // The query.js for these DBs is blind, it autoincrements the - // primarykey value, even if it was set manually. Also, it can - // return more results than instances, bug?. - continue; - } - if (Object.prototype.hasOwnProperty.call(result, key)) { - const record = result[key]; + options.upsertKeys = upsertKeys.length > 0 + ? upsertKeys + : Object.values(model.primaryKeys).map(x => x.field); + } - const attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + // Map returning attributes to fields + if (options.returning && Array.isArray(options.returning)) { + options.returning = options.returning.map(attr => _.get(model.rawAttributes[attr], 'field', attr)); + } - instance.dataValues[attr && attr.fieldName || key] = record; - } - } - }); + const results = await model.queryInterface.bulkInsert(model.getTableName(options), records, options, fieldMappedAttributes); + if (Array.isArray(results)) { + results.forEach((result, i) => { + const instance = instances[i]; + + for (const key in result) { + if (!instance || key === model.primaryKeyAttribute && + instance.get(model.primaryKeyAttribute) && + ['mysql', 'mariadb', 'sqlite'].includes(dialect)) { + // The query.js for these DBs is blind, it autoincrements the + // primarykey value, even if it was set manually. Also, it can + // return more results than instances, bug?. + continue; + } + if (Object.prototype.hasOwnProperty.call(result, key)) { + const record = result[key]; + + const attr = _.find(model.rawAttributes, attribute => attribute.fieldName === key || attribute.field === key); + + instance.dataValues[attr && attr.fieldName || key] = record; + } } - return results; }); - }); - }).then(() => { - if (!options.include || !options.include.length) return; + } + } - // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.map(options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)), include => { + if (options.include && options.include.length) { + await Promise.all(options.include.filter(include => !(include.association instanceof BelongsTo || + include.parent && include.parent.association instanceof BelongsToMany)).map(async include => { const associationInstances = []; const associationInstanceIndexToInstanceMap = []; @@ -2762,73 +2787,74 @@ class Model { logging: options.logging }).value(); - return recursiveBulkCreate(associationInstances, includeOptions).then(associationInstances => { - if (include.association instanceof BelongsToMany) { - const valueSets = []; - - for (const idx in associationInstances) { - const associationInstance = associationInstances[idx]; - const instance = associationInstanceIndexToInstanceMap[idx]; + const createdAssociationInstances = await recursiveBulkCreate(associationInstances, includeOptions); + if (include.association instanceof BelongsToMany) { + const valueSets = []; - const values = {}; - values[include.association.foreignKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); - values[include.association.otherKey] = associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }); + for (const idx in createdAssociationInstances) { + const associationInstance = createdAssociationInstances[idx]; + const instance = associationInstanceIndexToInstanceMap[idx]; + const values = { + [include.association.foreignKey]: instance.get(instance.constructor.primaryKeyAttribute, { raw: true }), + [include.association.otherKey]: associationInstance.get(associationInstance.constructor.primaryKeyAttribute, { raw: true }), // Include values defined in the association - Object.assign(values, include.association.through.scope); - if (associationInstance[include.association.through.model.name]) { - for (const attr of Object.keys(include.association.through.model.rawAttributes)) { - if (include.association.through.model.rawAttributes[attr]._autoGenerated || - attr === include.association.foreignKey || - attr === include.association.otherKey || - typeof associationInstance[include.association.through.model.name][attr] === undefined) { - continue; - } - values[attr] = associationInstance[include.association.through.model.name][attr]; + ...include.association.through.scope + }; + if (associationInstance[include.association.through.model.name]) { + for (const attr of Object.keys(include.association.through.model.rawAttributes)) { + if (include.association.through.model.rawAttributes[attr]._autoGenerated || + attr === include.association.foreignKey || + attr === include.association.otherKey || + typeof associationInstance[include.association.through.model.name][attr] === undefined) { + continue; } + values[attr] = associationInstance[include.association.through.model.name][attr]; } - - valueSets.push(values); } - const throughOptions = _(Utils.cloneDeep(include)) - .omit(['association', 'attributes']) - .defaults({ - transaction: options.transaction, - logging: options.logging - }).value(); - throughOptions.model = include.association.throughModel; - const throughInstances = include.association.throughModel.bulkBuild(valueSets, throughOptions); - - return recursiveBulkCreate(throughInstances, throughOptions); + valueSets.push(values); } - }); - }); - }).then(() => { - // map fields back to attributes - instances.forEach(instance => { - for (const attr in model.rawAttributes) { - if (model.rawAttributes[attr].field && - instance.dataValues[model.rawAttributes[attr].field] !== undefined && - model.rawAttributes[attr].field !== attr - ) { - instance.dataValues[attr] = instance.dataValues[model.rawAttributes[attr].field]; - delete instance.dataValues[model.rawAttributes[attr].field]; - } - instance._previousDataValues[attr] = instance.dataValues[attr]; - instance.changed(attr, false); + + const throughOptions = _(Utils.cloneDeep(include)) + .omit(['association', 'attributes']) + .defaults({ + transaction: options.transaction, + logging: options.logging + }).value(); + throughOptions.model = include.association.throughModel; + const throughInstances = include.association.throughModel.bulkBuild(valueSets, throughOptions); + + await recursiveBulkCreate(throughInstances, throughOptions); } - instance.isNewRecord = false; - }); + })); + } - // Run after hook - if (options.hooks) { - return model.hooks.run('afterBulkCreate', instances, options); + // map fields back to attributes + instances.forEach(instance => { + for (const attr in model.rawAttributes) { + if (model.rawAttributes[attr].field && + instance.dataValues[model.rawAttributes[attr].field] !== undefined && + model.rawAttributes[attr].field !== attr + ) { + instance.dataValues[attr] = instance.dataValues[model.rawAttributes[attr].field]; + delete instance.dataValues[model.rawAttributes[attr].field]; + } + instance._previousDataValues[attr] = instance.dataValues[attr]; + instance.changed(attr, false); } - }).then(() => instances); + instance.isNewRecord = false; + }); + + // Run after hook + if (options.hooks) { + await model.runHooks('afterBulkCreate', instances, options); + } + + return instances; }; - return recursiveBulkCreate(instances, options); + return await recursiveBulkCreate(instances, options); } /** @@ -2847,10 +2873,10 @@ class Model { * @see * {@link Model.destroy} for more information */ - static truncate(options) { - options = Utils.cloneDeep(options); + static async truncate(options) { + options = Utils.cloneDeep(options) || {}; options.truncate = true; - return this.destroy(options); + return await this.destroy(options); } /** @@ -2871,7 +2897,7 @@ class Model { * * @returns {Promise} The number of destroyed rows */ - static destroy(options) { + static async destroy(options) { options = Utils.cloneDeep(options); this._injectScope(options); @@ -2884,64 +2910,61 @@ class Model { throw new Error('Expected plain object, array or sequelize method in the options.where parameter of model.destroy.'); } - options = { + options = _.defaults(options, { hooks: true, individualHooks: false, force: false, cascade: false, - restartIdentity: false, - ...options - }; + restartIdentity: false + }); + options.type = QueryTypes.BULKDELETE; Utils.mapOptionFieldNames(options, this); options.model = this; + + // Run before hook + if (options.hooks) { + await this.runHooks('beforeBulkDestroy', options); + } let instances; + // Get daos and run beforeDestroy hook on each record individually + if (options.individualHooks) { + instances = await this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }); - return Promise.try(() => { - // Run before hook - if (options.hooks) { - return this.hooks.run('beforeBulkDestroy', options); - } - }).then(() => { - // Get daos and run beforeDestroy hook on each record individually - if (options.individualHooks) { - return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark }) - .map(instance => this.hooks.run('beforeDestroy', instance, options).then(() => instance)) - .then(_instances => { - instances = _instances; - }); - } - }).then(() => { - // Run delete query (or update if paranoid) - if (this._timestampAttributes.deletedAt && !options.force) { - // Set query type appropriately when running soft delete - options.type = QueryTypes.BULKUPDATE; - - const attrValueHash = {}; - const deletedAtAttribute = this.rawAttributes[this._timestampAttributes.deletedAt]; - const field = this.rawAttributes[this._timestampAttributes.deletedAt].field; - const where = { - [field]: Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null - }; - - - attrValueHash[field] = Utils.now(this.sequelize.options.dialect); - return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); - } - return this.QueryInterface.bulkDelete(this.getTableName(options), options.where, options, this); - }).tap(() => { - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - return Promise.map(instances, instance => this.hooks.run('afterDestroy', instance, options)); - } - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.hooks.run('afterBulkDestroy', options); - } - }); + await Promise.all(instances.map(instance => this.runHooks('beforeDestroy', instance, options))); + } + let result; + // Run delete query (or update if paranoid) + if (this._timestampAttributes.deletedAt && !options.force) { + // Set query type appropriately when running soft delete + options.type = QueryTypes.BULKUPDATE; + + const attrValueHash = {}; + const deletedAtAttribute = this.rawAttributes[this._timestampAttributes.deletedAt]; + const field = this.rawAttributes[this._timestampAttributes.deletedAt].field; + const where = { + [field]: Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null + }; + + + attrValueHash[field] = Utils.now(this.sequelize.options.dialect); + result = await this.queryInterface.bulkUpdate(this.getTableName(options), attrValueHash, Object.assign(where, options.where), options, this.rawAttributes); + } else { + result = await this.queryInterface.bulkDelete(this.getTableName(options), options.where, options, this); + } + // Run afterDestroy hook on each record individually + if (options.individualHooks) { + await Promise.all( + instances.map(instance => this.runHooks('afterDestroy', instance, options)) + ); + } + // Run after hook + if (options.hooks) { + await this.runHooks('afterBulkDestroy', options); + } + return result; } /** @@ -2958,96 +2981,91 @@ class Model { * * @returns {Promise} */ - static restore(options) { + static async restore(options) { if (!this._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); - options = Object.assign({ + options = { hooks: true, - individualHooks: false - }, options); + individualHooks: false, + ...options + }; options.type = QueryTypes.RAW; options.model = this; - let instances; - Utils.mapOptionFieldNames(options, this); - return Promise.try(() => { - // Run before hook - if (options.hooks) { - return this.hooks.run('beforeBulkRestore', options); - } - }).then(() => { - // Get daos and run beforeRestore hook on each record individually - if (options.individualHooks) { - return this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }) - .map(instance => this.hooks.run('beforeRestore', instance, options).then(() => instance)) - .then(_instances => { - instances = _instances; - }); - } - }).then(() => { - // Run undelete query - const attrValueHash = {}; - const deletedAtCol = this._timestampAttributes.deletedAt; - const deletedAtAttribute = this.rawAttributes[deletedAtCol]; - const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - - attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; - options.omitNull = false; - return this.QueryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); - }).tap(() => { - // Run afterDestroy hook on each record individually - if (options.individualHooks) { - return Promise.map(instances, instance => this.hooks.run('afterRestore', instance, options)); - } - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.hooks.run('afterBulkRestore', options); - } - }); + // Run before hook + if (options.hooks) { + await this.runHooks('beforeBulkRestore', options); + } + + let instances; + // Get daos and run beforeRestore hook on each record individually + if (options.individualHooks) { + instances = await this.findAll({ where: options.where, transaction: options.transaction, logging: options.logging, benchmark: options.benchmark, paranoid: false }); + + await Promise.all(instances.map(instance => this.runHooks('beforeRestore', instance, options))); + } + // Run undelete query + const attrValueHash = {}; + const deletedAtCol = this._timestampAttributes.deletedAt; + const deletedAtAttribute = this.rawAttributes[deletedAtCol]; + const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; + + attrValueHash[deletedAtAttribute.field || deletedAtCol] = deletedAtDefaultValue; + options.omitNull = false; + const result = await this.queryInterface.bulkUpdate(this.getTableName(options), attrValueHash, options.where, options, this.rawAttributes); + // Run afterDestroy hook on each record individually + if (options.individualHooks) { + await Promise.all( + instances.map(instance => this.runHooks('afterRestore', instance, options)) + ); + } + // Run after hook + if (options.hooks) { + await this.runHooks('afterBulkRestore', options); + } + return result; } /** * Update multiple instances that match the where options. * - * @param {object} values hash of values to update - * @param {object} options update options - * @param {object} options.where Options to describe the scope of the search. - * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be updated. If false, both deleted and non-deleted records will be updated. Only applies if `options.paranoid` is true for the model. - * @param {Array} [options.fields] Fields to update (defaults to all fields) - * @param {boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation - * @param {boolean} [options.hooks=true] Run before / after bulk update hooks? - * @param {boolean} [options.sideEffects=true] Whether or not to update the side effects of any virtual setters. - * @param {boolean} [options.individualHooks=false] Run before / after update hooks?. If true, this will execute a SELECT followed by individual UPDATEs. A select is needed, because the row data needs to be passed to the hooks - * @param {boolean} [options.returning=false] Return the affected rows (only for postgres) - * @param {number} [options.limit] How many rows to update (only for mysql and mariadb, implemented as TOP(n) for MSSQL; for sqlite it is supported only when rowid is present) - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. + * @param {object} values hash of values to update + * @param {object} options update options + * @param {object} options.where Options to describe the scope of the search. + * @param {boolean} [options.paranoid=true] If true, only non-deleted records will be updated. If false, both deleted and non-deleted records will be updated. Only applies if `options.paranoid` is true for the model. + * @param {Array} [options.fields] Fields to update (defaults to all fields) + * @param {boolean} [options.validate=true] Should each row be subject to validation before it is inserted. The whole insert will fail if one row fails validation + * @param {boolean} [options.hooks=true] Run before / after bulk update hooks? + * @param {boolean} [options.sideEffects=true] Whether or not to update the side effects of any virtual setters. + * @param {boolean} [options.individualHooks=false] Run before / after update hooks?. If true, this will execute a SELECT followed by individual UPDATEs. A select is needed, because the row data needs to be passed to the hooks + * @param {boolean|Array} [options.returning=false] If true, append RETURNING to get back all defined values; if an array of column names, append RETURNING to get back specific columns (Postgres only) + * @param {number} [options.limit] How many rows to update (only for mysql and mariadb, implemented as TOP(n) for MSSQL; for sqlite it is supported only when rowid is present) + * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. + * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). + * @param {Transaction} [options.transaction] Transaction to run query under + * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. * * @returns {Promise>} The promise returns an array with one or two elements. The first element is always the number - * of affected rows, while the second element is the actual affected rows (only supported in postgres with `options.returning` true.) + * of affected rows, while the second element is the actual affected rows (only supported in postgres with `options.returning` true). * */ - static update(values, options) { + static async update(values, options) { options = Utils.cloneDeep(options); this._injectScope(options); this._optionsMustContainWhere(options); - options = this._paranoidClause(this, { + options = this._paranoidClause(this, _.defaults(options, { validate: true, hooks: true, individualHooks: false, returning: false, force: false, - sideEffects: true, - ...options - }); + sideEffects: true + })); options.type = QueryTypes.BULKUPDATE; @@ -3075,163 +3093,138 @@ class Model { options.model = this; - let instances; let valuesUse; + // Validate + if (options.validate) { + const build = this.build(values); + build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); - return Promise.try(() => { - // Validate - if (options.validate) { - const build = new this(values); - build.set(this._timestampAttributes.updatedAt, values[this._timestampAttributes.updatedAt], { raw: true }); - - if (options.sideEffects) { - values = Object.assign(values, _.pick(build.get(), build.changed())); - options.fields = _.union(options.fields, Object.keys(values)); - } - - // We want to skip validations for all other fields - options.skip = _.difference(Object.keys(this.rawAttributes), Object.keys(values)); - return build.validate(options).then(attributes => { - options.skip = undefined; - if (attributes && attributes.dataValues) { - values = _.pick(attributes.dataValues, Object.keys(values)); - } - }); + if (options.sideEffects) { + Object.assign(values, _.pick(build.get(), build.changed())); + options.fields = _.union(options.fields, Object.keys(values)); } - return null; - }).then(() => { - // Run before hook - if (options.hooks) { - options.attributes = values; - return this.hooks.run('beforeBulkUpdate', options).then(() => { - values = options.attributes; - delete options.attributes; - }); + + // We want to skip validations for all other fields + options.skip = _.difference(Object.keys(this.rawAttributes), Object.keys(values)); + const attributes = await build.validate(options); + options.skip = undefined; + if (attributes && attributes.dataValues) { + values = _.pick(attributes.dataValues, Object.keys(values)); } - return null; - }).then(() => { - valuesUse = values; + } + // Run before hook + if (options.hooks) { + options.attributes = values; + await this.runHooks('beforeBulkUpdate', options); + values = options.attributes; + delete options.attributes; + } - // Get instances and run beforeUpdate hook on each record individually - if (options.individualHooks) { - return this.findAll({ - where: options.where, - transaction: options.transaction, - logging: options.logging, - benchmark: options.benchmark, - paranoid: options.paranoid - }).then(_instances => { - instances = _instances; - if (!instances.length) { - return []; - } + valuesUse = values; - // Run beforeUpdate hooks on each record and check whether beforeUpdate hook changes values uniformly - // i.e. whether they change values for each record in the same way - let changedValues; - let different = false; + // Get instances and run beforeUpdate hook on each record individually + let instances; + let updateDoneRowByRow = false; + if (options.individualHooks) { + instances = await this.findAll({ + where: options.where, + transaction: options.transaction, + logging: options.logging, + benchmark: options.benchmark, + paranoid: options.paranoid + }); - return Promise.map(instances, instance => { - // Record updates in instances dataValues - Object.assign(instance.dataValues, values); - // Set the changed fields on the instance - _.forIn(valuesUse, (newValue, attr) => { + if (instances.length) { + // Run beforeUpdate hooks on each record and check whether beforeUpdate hook changes values uniformly + // i.e. whether they change values for each record in the same way + let changedValues; + let different = false; + + instances = await Promise.all(instances.map(async instance => { + // Record updates in instances dataValues + Object.assign(instance.dataValues, values); + // Set the changed fields on the instance + _.forIn(valuesUse, (newValue, attr) => { + if (newValue !== instance._previousDataValues[attr]) { + instance.setDataValue(attr, newValue); + } + }); + + // Run beforeUpdate hook + await this.runHooks('beforeUpdate', instance, options); + if (!different) { + const thisChangedValues = {}; + _.forIn(instance.dataValues, (newValue, attr) => { if (newValue !== instance._previousDataValues[attr]) { - instance.setDataValue(attr, newValue); + thisChangedValues[attr] = newValue; } }); - // Run beforeUpdate hook - return this.hooks.run('beforeUpdate', instance, options).then(() => { - if (!different) { - const thisChangedValues = {}; - _.forIn(instance.dataValues, (newValue, attr) => { - if (newValue !== instance._previousDataValues[attr]) { - thisChangedValues[attr] = newValue; - } - }); + if (!changedValues) { + changedValues = thisChangedValues; + } else { + different = !_.isEqual(changedValues, thisChangedValues); + } + } - if (!changedValues) { - changedValues = thisChangedValues; - } else { - different = !_.isEqual(changedValues, thisChangedValues); - } - } + return instance; + })); - return instance; - }); - }).then(_instances => { - instances = _instances; - - if (!different) { - const keys = Object.keys(changedValues); - // Hooks do not change values or change them uniformly - if (keys.length) { - // Hooks change values - record changes in valuesUse so they are executed - valuesUse = changedValues; - options.fields = _.union(options.fields, keys); - } - return; - } - // Hooks change values in a different way for each record - // Do not run original query but save each record individually - return Promise.map(instances, instance => { - const individualOptions = { ...options }; - delete individualOptions.individualHooks; - individualOptions.hooks = false; - individualOptions.validate = false; - - return instance.save(individualOptions); - }).tap(_instances => { - instances = _instances; - }); - }); - }); - } - }).then(results => { - // Update already done row-by-row - exit - if (results) { - return [results.length, results]; - } + if (!different) { + const keys = Object.keys(changedValues); + // Hooks do not change values or change them uniformly + if (keys.length) { + // Hooks change values - record changes in valuesUse so they are executed + valuesUse = changedValues; + options.fields = _.union(options.fields, keys); + } + } else { + instances = await Promise.all(instances.map(async instance => { + const individualOptions = { + ...options, + hooks: false, + validate: false + }; + delete individualOptions.individualHooks; - // only updatedAt is being passed, then skip update - if ( - _.isEmpty(valuesUse) - || Object.keys(valuesUse).length === 1 && valuesUse[this._timestampAttributes.updatedAt] - ) { - return [0]; + return instance.save(individualOptions); + })); + updateDoneRowByRow = true; + } } - + } + let result; + if (updateDoneRowByRow) { + result = [instances.length, instances]; + } else if (_.isEmpty(valuesUse) + || Object.keys(valuesUse).length === 1 && valuesUse[this._timestampAttributes.updatedAt]) { + // only updatedAt is being passed, then skip update + result = [0]; + } else { valuesUse = Utils.mapValueFieldNames(valuesUse, options.fields, this); options = Utils.mapOptionFieldNames(options, this); options.hasTrigger = this.options ? this.options.hasTrigger : false; - // Run query to update all rows - return this.QueryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes).then(affectedRows => { - if (options.returning) { - instances = affectedRows; - return [affectedRows.length, affectedRows]; - } - - return [affectedRows]; - }); - }).tap(result => { - if (options.individualHooks) { - return Promise.map(instances, instance => { - return this.hooks.run('afterUpdate', instance, options); - }).then(() => { - result[1] = instances; - }); - } - }).tap(() => { - // Run after hook - if (options.hooks) { - options.attributes = values; - return this.hooks.run('afterBulkUpdate', options).then(() => { - delete options.attributes; - }); + const affectedRows = await this.queryInterface.bulkUpdate(this.getTableName(options), valuesUse, options.where, options, this.tableAttributes); + if (options.returning) { + result = [affectedRows.length, affectedRows]; + instances = affectedRows; + } else { + result = [affectedRows]; } - }); + } + + if (options.individualHooks) { + await Promise.all(instances.map(instance => this.runHooks('afterUpdate', instance, options))); + result[1] = instances; + } + // Run after hook + if (options.hooks) { + options.attributes = values; + await this.runHooks('afterBulkUpdate', options); + delete options.attributes; + } + return result; } /** @@ -3242,8 +3235,8 @@ class Model { * * @returns {Promise} hash of attributes and their types */ - static describe(schema, options) { - return this.QueryInterface.describeTable(this.tableName, Object.assign({ schema: schema || this._schema || undefined }, options)); + static async describe(schema, options) { + return await this.queryInterface.describeTable(this.tableName, { schema: schema || this._schema || undefined, ...options }); } static _getDefaultTimestamp(attr) { @@ -3280,10 +3273,6 @@ class Model { return this.name; } - static inspect() { - return this.name; - } - static hasAlias(alias) { return Object.prototype.hasOwnProperty.call(this.associations, alias); } @@ -3305,75 +3294,89 @@ class Model { * @see * {@link Model#reload} * - * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. - * @param {object} options increment options - * @param {object} options.where conditions hash - * @param {number} [options.by=1] The number to increment by - * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. - * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {Transaction} [options.transaction] Transaction to run query under - * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) + * @param {string|Array|object} fields If a string is provided, that column is incremented by the value of `by` given in options. If an array is provided, the same is true for each column. If and object is provided, each column is incremented by the value given. + * @param {object} options increment options + * @param {object} options.where conditions hash + * @param {number} [options.by=1] The number to increment by + * @param {boolean} [options.silent=false] If true, the updatedAt timestamp will not be updated. + * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. + * @param {Transaction} [options.transaction] Transaction to run query under + * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * - * @returns {Promise} returns an array of affected rows and affected count with `options.returning: true`, whenever supported by dialect + * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect */ - static increment(fields, options = {}) { + static async increment(fields, options) { + options = options || {}; + if (typeof fields === 'string') fields = [fields]; + if (Array.isArray(fields)) { + fields = fields.map(f => { + if (this.rawAttributes[f] && this.rawAttributes[f].field && this.rawAttributes[f].field !== f) { + return this.rawAttributes[f].field; + } + return f; + }); + } + this._injectScope(options); this._optionsMustContainWhere(options); - const updatedAtAttr = this._timestampAttributes.updatedAt; - const versionAttr = this._versionAttribute; - const updatedAtAttribute = this.rawAttributes[updatedAtAttr]; options = Utils.defaults({}, options, { by: 1, - attributes: {}, where: {}, increment: true }); + const isSubtraction = !options.increment; Utils.mapOptionFieldNames(options, this); - const where = Object.assign({}, options.where); - let values = {}; + const where = { ...options.where }; - if (typeof fields === 'string') { - values[fields] = options.by; - } else if (Array.isArray(fields)) { - fields.forEach(field => { - values[field] = options.by; - }); - } else { // Assume fields is key-value pairs - values = fields; + // A plain object whose keys are the fields to be incremented and whose values are + // the amounts to be incremented by. + let incrementAmountsByField = {}; + if (Array.isArray(fields)) { + incrementAmountsByField = {}; + for (const field of fields) { + incrementAmountsByField[field] = options.by; + } + } else { + // If the `fields` argument is not an array, then we assume it already has the + // form necessary to be placed directly in the `incrementAmountsByField` variable. + incrementAmountsByField = fields; } - if (!options.silent && updatedAtAttr && !values[updatedAtAttr]) { - options.attributes[updatedAtAttribute.field || updatedAtAttr] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect); - } - if (versionAttr) { - values[versionAttr] = options.increment ? 1 : -1; + // If optimistic locking is enabled, we can take advantage that this is an + // increment/decrement operation and send it here as well. We put `-1` for + // decrementing because it will be subtracted, getting `-(-1)` which is `+1` + if (this._versionAttribute) { + incrementAmountsByField[this._versionAttribute] = isSubtraction ? -1 : 1; } - for (const attr of Object.keys(values)) { - // Field name mapping - if (this.rawAttributes[attr] && this.rawAttributes[attr].field && this.rawAttributes[attr].field !== attr) { - values[this.rawAttributes[attr].field] = values[attr]; - delete values[attr]; - } + const extraAttributesToBeUpdated = {}; + + const updatedAtAttr = this._timestampAttributes.updatedAt; + if (!options.silent && updatedAtAttr && !incrementAmountsByField[updatedAtAttr]) { + const attrName = this.rawAttributes[updatedAtAttr].field || updatedAtAttr; + extraAttributesToBeUpdated[attrName] = this._getDefaultTimestamp(updatedAtAttr) || Utils.now(this.sequelize.options.dialect); } - let promise; - if (!options.increment) { - promise = this.QueryInterface.decrement(this, this.getTableName(options), values, where, options); + const tableName = this.getTableName(options); + let affectedRows; + if (isSubtraction) { + affectedRows = await this.queryInterface.decrement( + this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options + ); } else { - promise = this.QueryInterface.increment(this, this.getTableName(options), values, where, options); + affectedRows = await this.queryInterface.increment( + this, tableName, where, incrementAmountsByField, extraAttributesToBeUpdated, options + ); } - return promise.then(affectedRows => { - if (options.returning) { - return [affectedRows, affectedRows.length]; - } + if (options.returning) { + return [affectedRows, affectedRows.length]; + } - return [affectedRows]; - }); + return [affectedRows]; } /** @@ -3398,17 +3401,15 @@ class Model { * @see * {@link Model#reload} * @since 4.36.0 - - * @returns {Promise} returns an array of affected rows and affected count with `options.returning: true`, whenever supported by dialect + * + * @returns {Promise} returns an array of affected rows and affected count with `options.returning` true, whenever supported by dialect */ - static decrement(fields, options) { - options = { + static async decrement(fields, options) { + return this.increment(fields, { by: 1, ...options, increment: false - }; - - return this.increment(fields, options); + }); } static _optionsMustContainWhere(options) { @@ -3431,7 +3432,7 @@ class Model { }, {}); if (_.size(where) === 0) { - return this._modelOptions.whereCollection; + return this.constructor.options.whereCollection; } const versionAttr = this.constructor._versionAttribute; if (checkVersion && versionAttr) { @@ -3465,7 +3466,7 @@ class Model { setDataValue(key, value) { const originalValue = this._previousDataValues[key]; - if (!Utils.isPrimitive(value) && value !== null || value !== originalValue) { + if (!_.isEqual(value, originalValue)) { this.changed(key, true); } @@ -3560,7 +3561,7 @@ class Model { * * Set can also be used to build instances for associations, if you have values for those. * When using set with associations you need to make sure the property key matches the alias of the association - * while also making sure that the proper include options have been set (from the constructor or .findOne()) + * while also making sure that the proper include options have been set (from .build() or .findOne()) * * If called with a dot.separated key on a JSON/JSONB attribute it will set the value nested and flag the entire object as changed. * @@ -3593,7 +3594,7 @@ class Model { // If raw, and we're not dealing with includes or special attributes, just set it straight on the dataValues object if (options.raw && !(this._options && this._options.include) && !(options && options.attributes) && !this.constructor._hasDateAttributes && !this.constructor._hasBooleanAttributes) { if (Object.keys(this.dataValues).length) { - this.dataValues = Object.assign(this.dataValues, values); + Object.assign(this.dataValues, values); } else { this.dataValues = values; } @@ -3642,7 +3643,7 @@ class Model { // custom setter should have changed value, get that changed value // TODO: v5 make setters return new value instead of changing internal store const newValue = this.dataValues[key]; - if (!Utils.isPrimitive(newValue) && newValue !== null || newValue !== originalValue) { + if (!_.isEqual(newValue, originalValue)) { this._previousDataValues[key] = originalValue; this.changed(key, true); } @@ -3691,11 +3692,10 @@ class Model { !options.raw && ( // True when sequelize method - value instanceof Utils.SequelizeMethod || + (value instanceof Utils.SequelizeMethod || // Check for data type type comparators - !(value instanceof Utils.SequelizeMethod) && this.constructor._dataTypeChanges[key] && this.constructor._dataTypeChanges[key].call(this, value, originalValue, options) || - // Check default - !this.constructor._dataTypeChanges[key] && (!Utils.isPrimitive(value) && value !== null || value !== originalValue) + !(value instanceof Utils.SequelizeMethod) && this.constructor._dataTypeChanges[key] && this.constructor._dataTypeChanges[key].call(this, value, originalValue, options) || // Check default + !this.constructor._dataTypeChanges[key] && !_.isEqual(value, originalValue)) ) ) { this._previousDataValues[key] = originalValue; @@ -3719,23 +3719,42 @@ class Model { * * If changed is called without an argument and no keys have changed, it will return `false`. * + * Please note that this function will return `false` when a property from a nested (for example JSON) property + * was edited manually, you must call `changed('key', true)` manually in these cases. + * Writing an entirely new object (eg. deep cloned) will be detected. + * + * @example + * ``` + * const mdl = await MyModel.findOne(); + * mdl.myJsonField.a = 1; + * console.log(mdl.changed()) => false + * mdl.save(); // this will not save anything + * mdl.changed('myJsonField', true); + * console.log(mdl.changed()) => ['myJsonField'] + * mdl.save(); // will save + * ``` + * * @param {string} [key] key to check or change status of * @param {any} [value] value to set * * @returns {boolean|Array} */ changed(key, value) { - if (key) { - if (value !== undefined) { - this._changed[key] = value; - return this; + if (key === undefined) { + if (this._changed.size > 0) { + return Array.from(this._changed); } - return this._changed[key] || false; + return false; } - - const changed = Object.keys(this.dataValues).filter(key => this.changed(key)); - - return changed.length ? changed : false; + if (value === true) { + this._changed.add(key); + return this; + } + if (value === false) { + this._changed.delete(key); + return this; + } + return this._changed.has(key); } /** @@ -3765,27 +3784,24 @@ class Model { const association = include.association; const accessor = key; const primaryKeyAttribute = include.model.primaryKeyAttribute; - let childOptions; + const childOptions = { + isNewRecord: this.isNewRecord, + include: include.include, + includeNames: include.includeNames, + includeMap: include.includeMap, + includeValidated: true, + raw: options.raw, + attributes: include.originalAttributes + }; let isEmpty; - if (!isEmpty) { - childOptions = { - isNewRecord: this.isNewRecord, - include: include.include, - includeNames: include.includeNames, - includeMap: include.includeMap, - includeValidated: true, - raw: options.raw, - attributes: include.originalAttributes - }; - } if (include.originalAttributes === undefined || include.originalAttributes.length) { if (association.isSingleAssociation) { if (Array.isArray(value)) { value = value[0]; } isEmpty = value && value[primaryKeyAttribute] === null || value === null; - this[accessor] = this.dataValues[accessor] = isEmpty ? null : new include.model(value, childOptions); + this[accessor] = this.dataValues[accessor] = isEmpty ? null : include.model.build(value, childOptions); } else { isEmpty = value[0] && value[0][primaryKeyAttribute] === null; this[accessor] = this.dataValues[accessor] = isEmpty ? [] : include.model.bulkBuild(value, childOptions); @@ -3794,10 +3810,13 @@ class Model { } /** - * Validate this instance, and if the validation passes, persist it to the database. It will only save changed fields, and do nothing if no fields have changed. + * Validates this instance, and if the validation passes, persists it to the database. * - * On success, the callback will be called with this instance. On validation error, the callback will be called with an instance of `Sequelize.ValidationError`. - * This error will have a property for each of the fields for which validation failed, with the error message for that field. + * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. * * @param {object} [options] save options * @param {string[]} [options.fields] An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved. @@ -3811,16 +3830,16 @@ class Model { * * @returns {Promise} */ - save(options) { + async save(options) { if (arguments.length > 1) { throw new Error('The second argument was removed in favor of the options object.'); } - options = { + options = Utils.cloneDeep(options); + options = _.defaults(options, { hooks: true, - validate: true, - ...Utils.cloneDeep(options) - }; + validate: true + }); if (!options.fields) { if (this.isNewRecord) { @@ -3849,10 +3868,10 @@ class Model { const now = Utils.now(this.sequelize.options.dialect); let updatedAtAttr = this.constructor._timestampAttributes.updatedAt; - if (updatedAtAttr && options.fields.length >= 1 && !options.fields.includes(updatedAtAttr)) { + if (updatedAtAttr && options.fields.length > 0 && !options.fields.includes(updatedAtAttr)) { options.fields.push(updatedAtAttr); } - if (versionAttr && options.fields.length >= 1 && !options.fields.includes(versionAttr)) { + if (versionAttr && options.fields.length > 0 && !options.fields.includes(versionAttr)) { options.fields.push(versionAttr); } @@ -3886,59 +3905,49 @@ class Model { this.dataValues[createdAtAttr] = this.constructor._getDefaultTimestamp(createdAtAttr) || now; } - return Promise.try(() => { - // Validate - if (options.validate) { - return this.validate(options); - } - }).then(() => { - // Run before hook - if (options.hooks) { - const beforeHookValues = _.pick(this.dataValues, options.fields); - let ignoreChanged = _.difference(this.changed(), options.fields); // In case of update where it's only supposed to update the passed values and the hook values - let hookChanged; - let afterHookValues; + // Validate + if (options.validate) { + await this.validate(options); + } + // Run before hook + if (options.hooks) { + const beforeHookValues = _.pick(this.dataValues, options.fields); + let ignoreChanged = _.difference(this.changed(), options.fields); // In case of update where it's only supposed to update the passed values and the hook values + let hookChanged; + let afterHookValues; - if (updatedAtAttr && options.fields.includes(updatedAtAttr)) { - ignoreChanged = _.without(ignoreChanged, updatedAtAttr); - } + if (updatedAtAttr && options.fields.includes(updatedAtAttr)) { + ignoreChanged = _.without(ignoreChanged, updatedAtAttr); + } - return this.constructor.hooks.run(`before${hook}`, this, options) - .then(() => { - if (options.defaultFields && !this.isNewRecord) { - afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged)); + await this.constructor.runHooks(`before${hook}`, this, options); + if (options.defaultFields && !this.isNewRecord) { + afterHookValues = _.pick(this.dataValues, _.difference(this.changed(), ignoreChanged)); - hookChanged = []; - for (const key of Object.keys(afterHookValues)) { - if (afterHookValues[key] !== beforeHookValues[key]) { - hookChanged.push(key); - } - } + hookChanged = []; + for (const key of Object.keys(afterHookValues)) { + if (afterHookValues[key] !== beforeHookValues[key]) { + hookChanged.push(key); + } + } - options.fields = _.uniq(options.fields.concat(hookChanged)); - } + options.fields = _.uniq(options.fields.concat(hookChanged)); + } - if (hookChanged) { - if (options.validate) { - // Validate again + if (hookChanged) { + if (options.validate) { + // Validate again - options.skip = _.difference(Object.keys(this.constructor.rawAttributes), hookChanged); - return this.validate(options).then(() => { - delete options.skip; - }); - } - } - }); + options.skip = _.difference(Object.keys(this.constructor.rawAttributes), hookChanged); + await this.validate(options); + delete options.skip; + } } - }).then(() => { - if (!options.fields.length) return this; - if (!this.isNewRecord) return this; - if (!this._options.include || !this._options.include.length) return this; - - // Nested creation for BelongsTo relations - return Promise.map(this._options.include.filter(include => include.association instanceof BelongsTo), include => { + } + if (options.fields.length && this.isNewRecord && this._options.include && this._options.include.length) { + await Promise.all(this._options.include.filter(include => include.association instanceof BelongsTo).map(async include => { const instance = this.get(include.as); - if (!instance) return Promise.resolve(); + if (!instance) return; const includeOptions = _(Utils.cloneDeep(include)) .omit(['association']) @@ -3948,128 +3957,121 @@ class Model { parentRecord: this }).value(); - return instance.save(includeOptions).then(() => this[include.association.accessors.set](instance, { save: false, logging: options.logging })); - }); - }).then(() => { - const realFields = options.fields.filter(field => !this.constructor._virtualAttributes.has(field)); - if (!realFields.length) return this; - if (!this.changed() && !this.isNewRecord) return this; + await instance.save(includeOptions); - const versionFieldName = _.get(this.constructor.rawAttributes[versionAttr], 'field') || versionAttr; - let values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor); - let query = null; - let args = []; - let where; + await this[include.association.accessors.set](instance, { save: false, logging: options.logging }); + })); + } + const realFields = options.fields.filter(field => !this.constructor._virtualAttributes.has(field)); + if (!realFields.length) return this; + if (!this.changed() && !this.isNewRecord) return this; + + const versionFieldName = _.get(this.constructor.rawAttributes[versionAttr], 'field') || versionAttr; + const values = Utils.mapValueFieldNames(this.dataValues, options.fields, this.constructor); + let query = null; + let args = []; + let where; + + if (this.isNewRecord) { + query = 'insert'; + args = [this, this.constructor.getTableName(options), values, options]; + } else { + where = this.where(true); + if (versionAttr) { + values[versionFieldName] = parseInt(values[versionFieldName], 10) + 1; + } + query = 'update'; + args = [this, this.constructor.getTableName(options), values, where, options]; + } - if (this.isNewRecord) { - query = 'insert'; - args = [this, this.constructor.getTableName(options), values, options]; + const [result, rowsUpdated] = await this.constructor.queryInterface[query](...args); + if (versionAttr) { + // Check to see that a row was updated, otherwise it's an optimistic locking error. + if (rowsUpdated < 1) { + throw new sequelizeErrors.OptimisticLockError({ + modelName: this.constructor.name, + values, + where + }); } else { - where = this.where(true); - if (versionAttr) { - values[versionFieldName] = parseInt(values[versionFieldName], 10) + 1; - } - query = 'update'; - args = [this, this.constructor.getTableName(options), values, where, options]; + result.dataValues[versionAttr] = values[versionFieldName]; } + } - return this.constructor.QueryInterface[query](...args) - .then(([result, rowsUpdated])=> { - if (versionAttr) { - // Check to see that a row was updated, otherwise it's an optimistic locking error. - if (rowsUpdated < 1) { - throw new sequelizeErrors.OptimisticLockError({ - modelName: this.constructor.name, - values, - where - }); - } else { - result.dataValues[versionAttr] = values[versionFieldName]; - } - } - - // Transfer database generated values (defaults, autoincrement, etc) - for (const attr of Object.keys(this.constructor.rawAttributes)) { - if (this.constructor.rawAttributes[attr].field && - values[this.constructor.rawAttributes[attr].field] !== undefined && - this.constructor.rawAttributes[attr].field !== attr - ) { - values[attr] = values[this.constructor.rawAttributes[attr].field]; - delete values[this.constructor.rawAttributes[attr].field]; - } - } - values = Object.assign(values, result.dataValues); - - result.dataValues = Object.assign(result.dataValues, values); - return result; - }) - .tap(() => { - if (!wasNewRecord) return this; - if (!this._options.include || !this._options.include.length) return this; + // Transfer database generated values (defaults, autoincrement, etc) + for (const attr of Object.keys(this.constructor.rawAttributes)) { + if (this.constructor.rawAttributes[attr].field && + values[this.constructor.rawAttributes[attr].field] !== undefined && + this.constructor.rawAttributes[attr].field !== attr + ) { + values[attr] = values[this.constructor.rawAttributes[attr].field]; + delete values[this.constructor.rawAttributes[attr].field]; + } + } + Object.assign(values, result.dataValues); - // Nested creation for HasOne/HasMany/BelongsToMany relations - return Promise.map(this._options.include.filter(include => !(include.association instanceof BelongsTo || - include.parent && include.parent.association instanceof BelongsToMany)), include => { - let instances = this.get(include.as); + Object.assign(result.dataValues, values); + if (wasNewRecord && this._options.include && this._options.include.length) { + await Promise.all( + this._options.include.filter(include => !(include.association instanceof BelongsTo || + include.parent && include.parent.association instanceof BelongsToMany)).map(async include => { + let instances = this.get(include.as); - if (!instances) return Promise.resolve(); - if (!Array.isArray(instances)) instances = [instances]; - if (!instances.length) return Promise.resolve(); + if (!instances) return; + if (!Array.isArray(instances)) instances = [instances]; - const includeOptions = _(Utils.cloneDeep(include)) - .omit(['association']) - .defaults({ - transaction: options.transaction, - logging: options.logging, - parentRecord: this - }).value(); + const includeOptions = _(Utils.cloneDeep(include)) + .omit(['association']) + .defaults({ + transaction: options.transaction, + logging: options.logging, + parentRecord: this + }).value(); - // Instances will be updated in place so we can safely treat HasOne like a HasMany - return Promise.map(instances, instance => { - if (include.association instanceof BelongsToMany) { - return instance.save(includeOptions).then(() => { - const values = {}; - values[include.association.foreignKey] = this.get(this.constructor.primaryKeyAttribute, { raw: true }); - values[include.association.otherKey] = instance.get(instance.constructor.primaryKeyAttribute, { raw: true }); - - // Include values defined in the association - Object.assign(values, include.association.through.scope); - if (instance[include.association.through.model.name]) { - for (const attr of Object.keys(include.association.through.model.rawAttributes)) { - if (include.association.through.model.rawAttributes[attr]._autoGenerated || - attr === include.association.foreignKey || - attr === include.association.otherKey || - typeof instance[include.association.through.model.name][attr] === undefined) { - continue; - } - values[attr] = instance[include.association.through.model.name][attr]; - } + // Instances will be updated in place so we can safely treat HasOne like a HasMany + await Promise.all(instances.map(async instance => { + if (include.association instanceof BelongsToMany) { + await instance.save(includeOptions); + const values0 = { + [include.association.foreignKey]: this.get(this.constructor.primaryKeyAttribute, { raw: true }), + [include.association.otherKey]: instance.get(instance.constructor.primaryKeyAttribute, { raw: true }), + // Include values defined in the association + ...include.association.through.scope + }; + + if (instance[include.association.through.model.name]) { + for (const attr of Object.keys(include.association.through.model.rawAttributes)) { + if (include.association.through.model.rawAttributes[attr]._autoGenerated || + attr === include.association.foreignKey || + attr === include.association.otherKey || + typeof instance[include.association.through.model.name][attr] === undefined) { + continue; } - - return include.association.throughModel.create(values, includeOptions); - }); + values0[attr] = instance[include.association.through.model.name][attr]; + } } + + await include.association.throughModel.create(values0, includeOptions); + } else { instance.set(include.association.foreignKey, this.get(include.association.sourceKey || this.constructor.primaryKeyAttribute, { raw: true }), { raw: true }); Object.assign(instance, include.association.scope); - return instance.save(includeOptions); - }); - }); - }) - .tap(result => { - // Run after hook - if (options.hooks) { - return this.constructor.hooks.run(`after${hook}`, result, options); - } + await instance.save(includeOptions); + } + })); }) - .then(result => { - for (const field of options.fields) { - result._previousDataValues[field] = result.dataValues[field]; - this.changed(field, false); - } - this.isNewRecord = false; - return result; - }); - }); + ); + } + // Run after hook + if (options.hooks) { + await this.constructor.runHooks(`after${hook}`, result, options); + } + for (const field of options.fields) { + result._previousDataValues[field] = result.dataValues[field]; + this.changed(field, false); + } + this.isNewRecord = false; + + return result; } /** @@ -4085,30 +4087,28 @@ class Model { * * @returns {Promise} */ - reload(options) { - options = Utils.defaults({}, options, { - where: this.where(), - include: this._options.include || null + async reload(options) { + options = Utils.defaults({ + where: this.where() + }, options, { + include: this._options.include || undefined }); - return this.constructor.findOne(options) - .tap(reload => { - if (!reload) { - throw new sequelizeErrors.InstanceError( - 'Instance could not be reloaded because it does not exist anymore (find call returned null)' - ); - } - }) - .then(reload => { - // update the internal options of the instance - this._options = reload._options; - // re-set instance values - this.set(reload.dataValues, { - raw: true, - reset: true && !options.attributes - }); - return this; - }); + const reloaded = await this.constructor.findOne(options); + if (!reloaded) { + throw new sequelizeErrors.InstanceError( + 'Instance could not be reloaded because it does not exist anymore (find call returned null)' + ); + } + // update the internal options of the instance + this._options = reloaded._options; + // re-set instance values + this.set(reloaded.dataValues, { + raw: true, + reset: true && !options.attributes + }); + + return this; } /** @@ -4123,7 +4123,7 @@ class Model { * * @returns {Promise} */ - validate(options) { + async validate(options) { return new InstanceValidator(this, options).validate(); } @@ -4141,12 +4141,13 @@ class Model { * * @returns {Promise} */ - update(values, options = {}) { + async update(values, options) { // Clone values so it doesn't get modified for caller scope and ignore undefined values values = _.omitBy(values, value => value === undefined); const changedBefore = this.changed() || []; + options = options || {}; if (Array.isArray(options)) options = { fields: options }; options = Utils.cloneDeep(options); @@ -4163,7 +4164,7 @@ class Model { options.defaultFields = options.fields; } - return this.save(options); + return await this.save(options); } /** @@ -4177,42 +4178,42 @@ class Model { * * @returns {Promise} */ - destroy(options) { - options = Object.assign({ + async destroy(options) { + options = { hooks: true, - force: false - }, options); - - return Promise.try(() => { - // Run before hook - if (options.hooks) { - return this.constructor.hooks.run('beforeDestroy', this, options); - } - }).then(() => { - const where = this.where(true); - - if (this.constructor._timestampAttributes.deletedAt && options.force === false) { - const attributeName = this.constructor._timestampAttributes.deletedAt; - const attribute = this.constructor.rawAttributes[attributeName]; - const defaultValue = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue') - ? attribute.defaultValue - : null; - const currentValue = this.getDataValue(attributeName); - const undefinedOrNull = currentValue == null && defaultValue == null; - if (undefinedOrNull || _.isEqual(currentValue, defaultValue)) { - // only update timestamp if it wasn't already set - this.setDataValue(attributeName, new Date()); - } + force: false, + ...options + }; - return this.save({ ...options, hooks: false }); - } - return this.constructor.QueryInterface.delete(this, this.constructor.getTableName(options), where, Object.assign({ type: QueryTypes.DELETE, limit: null }, options)); - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.constructor.hooks.run('afterDestroy', this, options); - } - }); + // Run before hook + if (options.hooks) { + await this.constructor.runHooks('beforeDestroy', this, options); + } + const where = this.where(true); + + let result; + if (this.constructor._timestampAttributes.deletedAt && options.force === false) { + const attributeName = this.constructor._timestampAttributes.deletedAt; + const attribute = this.constructor.rawAttributes[attributeName]; + const defaultValue = Object.prototype.hasOwnProperty.call(attribute, 'defaultValue') + ? attribute.defaultValue + : null; + const currentValue = this.getDataValue(attributeName); + const undefinedOrNull = currentValue == null && defaultValue == null; + if (undefinedOrNull || _.isEqual(currentValue, defaultValue)) { + // only update timestamp if it wasn't already set + this.setDataValue(attributeName, new Date()); + } + + result = await this.save({ ...options, hooks: false }); + } else { + result = await this.constructor.queryInterface.delete(this, this.constructor.getTableName(options), where, { type: QueryTypes.DELETE, limit: null, ...options }); + } + // Run after hook + if (options.hooks) { + await this.constructor.runHooks('afterDestroy', this, options); + } + return result; } /** @@ -4244,32 +4245,31 @@ class Model { * * @returns {Promise} */ - restore(options) { + async restore(options) { if (!this.constructor._timestampAttributes.deletedAt) throw new Error('Model is not paranoid'); - options = Object.assign({ + options = { hooks: true, - force: false - }, options); + force: false, + ...options + }; - return Promise.try(() => { - // Run before hook - if (options.hooks) { - return this.constructor.hooks.run('beforeRestore', this, options); - } - }).then(() => { - const deletedAtCol = this.constructor._timestampAttributes.deletedAt; - const deletedAtAttribute = this.constructor.rawAttributes[deletedAtCol]; - const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; - - this.setDataValue(deletedAtCol, deletedAtDefaultValue); - return this.save(Object.assign({}, options, { hooks: false, omitNull: false })); - }).tap(() => { - // Run after hook - if (options.hooks) { - return this.constructor.hooks.run('afterRestore', this, options); - } - }); + // Run before hook + if (options.hooks) { + await this.constructor.runHooks('beforeRestore', this, options); + } + const deletedAtCol = this.constructor._timestampAttributes.deletedAt; + const deletedAtAttribute = this.constructor.rawAttributes[deletedAtCol]; + const deletedAtDefaultValue = Object.prototype.hasOwnProperty.call(deletedAtAttribute, 'defaultValue') ? deletedAtAttribute.defaultValue : null; + + this.setDataValue(deletedAtCol, deletedAtDefaultValue); + const result = await this.save({ ...options, hooks: false, omitNull: false }); + // Run after hook + if (options.hooks) { + await this.constructor.runHooks('afterRestore', this, options); + return result; + } + return result; } /** @@ -4303,14 +4303,16 @@ class Model { * @returns {Promise} * @since 4.0.0 */ - increment(fields, options) { + async increment(fields, options) { const identifier = this.where(); options = Utils.cloneDeep(options); - options.where = Object.assign({}, options.where, identifier); + options.where = { ...options.where, ...identifier }; options.instance = this; - return this.constructor.increment(fields, options).return(this); + await this.constructor.increment(fields, options); + + return this; } /** @@ -4342,14 +4344,12 @@ class Model { * * @returns {Promise} */ - decrement(fields, options) { - options = { + async decrement(fields, options) { + return this.increment(fields, { by: 1, ...options, increment: false - }; - - return this.increment(fields, options); + }); } /** @@ -4504,5 +4504,6 @@ class Model { } Object.assign(Model, associationsMixin); +Hooks.applyTo(Model, true); module.exports = Model; diff --git a/lib/operators.js b/lib/operators.js index f6c2d0e3440b..7e8bb7cf9cdb 100644 --- a/lib/operators.js +++ b/lib/operators.js @@ -46,45 +46,46 @@ * @property join */ const Op = { - eq: Symbol.for('sequelize.operator.eq'), - ne: Symbol.for('sequelize.operator.ne'), - gte: Symbol.for('sequelize.operator.gte'), - gt: Symbol.for('sequelize.operator.gt'), - lte: Symbol.for('sequelize.operator.lte'), - lt: Symbol.for('sequelize.operator.lt'), - not: Symbol.for('sequelize.operator.not'), - is: Symbol.for('sequelize.operator.is'), - in: Symbol.for('sequelize.operator.in'), - notIn: Symbol.for('sequelize.operator.notIn'), - like: Symbol.for('sequelize.operator.like'), - notLike: Symbol.for('sequelize.operator.notLike'), - iLike: Symbol.for('sequelize.operator.iLike'), - notILike: Symbol.for('sequelize.operator.notILike'), - startsWith: Symbol.for('sequelize.operator.startsWith'), - endsWith: Symbol.for('sequelize.operator.endsWith'), - substring: Symbol.for('sequelize.operator.substring'), - regexp: Symbol.for('sequelize.operator.regexp'), - notRegexp: Symbol.for('sequelize.operator.notRegexp'), - iRegexp: Symbol.for('sequelize.operator.iRegexp'), - notIRegexp: Symbol.for('sequelize.operator.notIRegexp'), - between: Symbol.for('sequelize.operator.between'), - notBetween: Symbol.for('sequelize.operator.notBetween'), - overlap: Symbol.for('sequelize.operator.overlap'), - contains: Symbol.for('sequelize.operator.contains'), - contained: Symbol.for('sequelize.operator.contained'), - adjacent: Symbol.for('sequelize.operator.adjacent'), - strictLeft: Symbol.for('sequelize.operator.strictLeft'), - strictRight: Symbol.for('sequelize.operator.strictRight'), - noExtendRight: Symbol.for('sequelize.operator.noExtendRight'), - noExtendLeft: Symbol.for('sequelize.operator.noExtendLeft'), - and: Symbol.for('sequelize.operator.and'), - or: Symbol.for('sequelize.operator.or'), - any: Symbol.for('sequelize.operator.any'), - all: Symbol.for('sequelize.operator.all'), - values: Symbol.for('sequelize.operator.values'), - col: Symbol.for('sequelize.operator.col'), - placeholder: Symbol.for('sequelize.operator.placeholder'), - join: Symbol.for('sequelize.operator.join') + eq: Symbol.for('eq'), + ne: Symbol.for('ne'), + gte: Symbol.for('gte'), + gt: Symbol.for('gt'), + lte: Symbol.for('lte'), + lt: Symbol.for('lt'), + not: Symbol.for('not'), + is: Symbol.for('is'), + in: Symbol.for('in'), + notIn: Symbol.for('notIn'), + like: Symbol.for('like'), + notLike: Symbol.for('notLike'), + iLike: Symbol.for('iLike'), + notILike: Symbol.for('notILike'), + startsWith: Symbol.for('startsWith'), + endsWith: Symbol.for('endsWith'), + substring: Symbol.for('substring'), + regexp: Symbol.for('regexp'), + notRegexp: Symbol.for('notRegexp'), + iRegexp: Symbol.for('iRegexp'), + notIRegexp: Symbol.for('notIRegexp'), + between: Symbol.for('between'), + notBetween: Symbol.for('notBetween'), + overlap: Symbol.for('overlap'), + contains: Symbol.for('contains'), + contained: Symbol.for('contained'), + adjacent: Symbol.for('adjacent'), + strictLeft: Symbol.for('strictLeft'), + strictRight: Symbol.for('strictRight'), + noExtendRight: Symbol.for('noExtendRight'), + noExtendLeft: Symbol.for('noExtendLeft'), + and: Symbol.for('and'), + or: Symbol.for('or'), + any: Symbol.for('any'), + all: Symbol.for('all'), + values: Symbol.for('values'), + col: Symbol.for('col'), + placeholder: Symbol.for('placeholder'), + join: Symbol.for('join'), + match: Symbol.for('match') }; module.exports = Op; diff --git a/lib/promise.js b/lib/promise.js deleted file mode 100644 index 4bc16c1fee12..000000000000 --- a/lib/promise.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const Promise = require('bluebird').getNewLibraryCopy(); - -module.exports = Promise; -module.exports.Promise = Promise; -module.exports.default = Promise; diff --git a/lib/query-interface.js b/lib/query-interface.js deleted file mode 100644 index 5ba1f2d505c9..000000000000 --- a/lib/query-interface.js +++ /dev/null @@ -1,1441 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -const Utils = require('./utils'); -const DataTypes = require('./data-types'); -const SQLiteQueryInterface = require('./dialects/sqlite/query-interface'); -const MSSQLQueryInterface = require('./dialects/mssql/query-interface'); -const MySQLQueryInterface = require('./dialects/mysql/query-interface'); -const PostgresQueryInterface = require('./dialects/postgres/query-interface'); -const Transaction = require('./transaction'); -const Promise = require('./promise'); -const QueryTypes = require('./query-types'); -const Op = require('./operators'); - -/** - * The interface that Sequelize uses to talk to all databases - * - * @class QueryInterface - */ -class QueryInterface { - constructor(sequelize) { - this.sequelize = sequelize; - this.QueryGenerator = this.sequelize.dialect.QueryGenerator; - } - - /** - * Create a database - * - * @param {string} database Database name to create - * @param {object} [options] Query options - * @param {string} [options.charset] Database default character set, MYSQL only - * @param {string} [options.collate] Database default collation - * @param {string} [options.encoding] Database default character set, PostgreSQL only - * @param {string} [options.ctype] Database character classification, PostgreSQL only - * @param {string} [options.template] The name of the template from which to create the new database, PostgreSQL only - * - * @returns {Promise} - */ - createDatabase(database, options = {}) { - const sql = this.QueryGenerator.createDatabaseQuery(database, options); - return this.sequelize.query(sql, options); - } - - /** - * Drop a database - * - * @param {string} database Database name to drop - * @param {object} [options] Query options - * - * @returns {Promise} - */ - dropDatabase(database, options = {}) { - const sql = this.QueryGenerator.dropDatabaseQuery(database); - return this.sequelize.query(sql, options); - } - - /** - * Create a schema - * - * @param {string} schema Schema name to create - * @param {object} [options] Query options - * - * @returns {Promise} - */ - createSchema(schema, options = {}) { - const sql = this.QueryGenerator.createSchema(schema); - return this.sequelize.query(sql, options); - } - - /** - * Drop a schema - * - * @param {string} schema Schema name to drop - * @param {object} [options] Query options - * - * @returns {Promise} - */ - dropSchema(schema, options = {}) { - const sql = this.QueryGenerator.dropSchema(schema); - return this.sequelize.query(sql, options); - } - - /** - * Drop all schemas - * - * @param {object} [options] Query options - * - * @returns {Promise} - */ - dropAllSchemas(options = {}) { - if (!this.QueryGenerator._dialect.supports.schemas) { - return this.sequelize.drop(options); - } - return this.showAllSchemas(options).map(schemaName => this.dropSchema(schemaName, options)); - } - - /** - * Show all schemas - * - * @param {object} [options] Query options - * - * @returns {Promise} - */ - showAllSchemas(options) { - options = Object.assign({}, options, { - raw: true, - type: this.sequelize.QueryTypes.SELECT - }); - - const showSchemasSql = this.QueryGenerator.showSchemasQuery(options); - - return this.sequelize.query(showSchemasSql, options).then(schemaNames => _.flatten( - schemaNames.map(value => value.schema_name ? value.schema_name : value) - )); - } - - /** - * Return database version - * - * @param {object} [options] Query options - * @param {QueryType} [options.type] Query type - * - * @returns {Promise} - * @private - */ - databaseVersion(options) { - return this.sequelize.query( - this.QueryGenerator.versionQuery(), - Object.assign({}, options, { type: QueryTypes.VERSION }) - ); - } - - /** - * Create a table with given set of attributes - * - * ```js - * queryInterface.createTable( - * 'nameOfTheNewTable', - * { - * id: { - * type: Sequelize.INTEGER, - * primaryKey: true, - * autoIncrement: true - * }, - * createdAt: { - * type: Sequelize.DATE - * }, - * updatedAt: { - * type: Sequelize.DATE - * }, - * attr1: Sequelize.STRING, - * attr2: Sequelize.INTEGER, - * attr3: { - * type: Sequelize.BOOLEAN, - * defaultValue: false, - * allowNull: false - * }, - * //foreign key usage - * attr4: { - * type: Sequelize.INTEGER, - * references: { - * model: 'another_table_name', - * key: 'id' - * }, - * onUpdate: 'cascade', - * onDelete: 'cascade' - * } - * }, - * { - * engine: 'MYISAM', // default: 'InnoDB' - * charset: 'latin1', // default: null - * schema: 'public', // default: public, PostgreSQL only. - * comment: 'my table', // comment for table - * collate: 'latin1_danish_ci' // collation, MYSQL only - * } - * ) - * ``` - * - * @param {string} tableName Name of table to create - * @param {object} attributes Object representing a list of table attributes to create - * @param {object} [options] create table and query options - * @param {Model} [model] model class - * - * @returns {Promise} - */ - createTable(tableName, attributes, options, model) { - let sql = ''; - let promise; - - options = { ...options }; - - if (options && options.uniqueKeys) { - _.forOwn(options.uniqueKeys, uniqueKey => { - if (uniqueKey.customIndex === undefined) { - uniqueKey.customIndex = true; - } - }); - } - - if (model) { - options.uniqueKeys = options.uniqueKeys || model.uniqueKeys; - } - - attributes = _.mapValues( - attributes, - attribute => this.sequelize.normalizeAttribute(attribute) - ); - - // Postgres requires special SQL commands for ENUM/ENUM[] - if (this.sequelize.options.dialect === 'postgres') { - promise = PostgresQueryInterface.ensureEnums(this, tableName, attributes, options, model); - } else { - promise = Promise.resolve(); - } - - if ( - !tableName.schema && - (options.schema || !!model && model._schema) - ) { - tableName = this.QueryGenerator.addSchema({ - tableName, - _schema: !!model && model._schema || options.schema - }); - } - - attributes = this.QueryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' }); - sql = this.QueryGenerator.createTableQuery(tableName, attributes, options); - - return promise.then(() => this.sequelize.query(sql, options)); - } - - /** - * Drop a table from database - * - * @param {string} tableName Table name to drop - * @param {object} options Query options - * - * @returns {Promise} - */ - dropTable(tableName, options) { - // if we're forcing we should be cascading unless explicitly stated otherwise - options = { ...options }; - options.cascade = options.cascade || options.force || false; - - let sql = this.QueryGenerator.dropTableQuery(tableName, options); - - return this.sequelize.query(sql, options).then(() => { - const promises = []; - - // Since postgres has a special case for enums, we should drop the related - // enum type within the table and attribute - if (this.sequelize.options.dialect === 'postgres') { - const instanceTable = this.sequelize.modelManager.getModel(tableName, { attribute: 'tableName' }); - - if (instanceTable) { - const getTableName = (!options || !options.schema || options.schema === 'public' ? '' : `${options.schema}_`) + tableName; - - const keys = Object.keys(instanceTable.rawAttributes); - const keyLen = keys.length; - - for (let i = 0; i < keyLen; i++) { - if (instanceTable.rawAttributes[keys[i]].type instanceof DataTypes.ENUM) { - sql = this.QueryGenerator.pgEnumDrop(getTableName, keys[i]); - options.supportsSearchPath = false; - promises.push(this.sequelize.query(sql, Object.assign({}, options, { raw: true }))); - } - } - } - } - - return Promise.all(promises).get(0); - }); - } - - /** - * Drop all tables from database - * - * @param {object} [options] query options - * @param {Array} [options.skip] List of table to skip - * - * @returns {Promise} - */ - dropAllTables(options = {}) { - const skip = options.skip || []; - - const dropAllTables = tableNames => Promise.each(tableNames, tableName => { - // if tableName is not in the Array of tables names then don't drop it - if (!skip.includes(tableName.tableName || tableName)) { - return this.dropTable(tableName, Object.assign({}, options, { cascade: true }) ); - } - }); - - return this.showAllTables(options).then(tableNames => { - if (this.sequelize.options.dialect === 'sqlite') { - return this.sequelize.query('PRAGMA foreign_keys;', options).then(result => { - const foreignKeysAreEnabled = result.foreign_keys === 1; - - if (foreignKeysAreEnabled) { - return this.sequelize.query('PRAGMA foreign_keys = OFF', options) - .then(() => dropAllTables(tableNames)) - .then(() => this.sequelize.query('PRAGMA foreign_keys = ON', options)); - } - return dropAllTables(tableNames); - }); - } - return this.getForeignKeysForTables(tableNames, options).then(foreignKeys => { - const queries = []; - - tableNames.forEach(tableName => { - let normalizedTableName = tableName; - if (_.isObject(tableName)) { - normalizedTableName = `${tableName.schema}.${tableName.tableName}`; - } - - foreignKeys[normalizedTableName].forEach(foreignKey => { - queries.push(this.QueryGenerator.dropForeignKeyQuery(tableName, foreignKey)); - }); - }); - - return Promise.each(queries, q => this.sequelize.query(q, options)) - .then(() => dropAllTables(tableNames)); - }); - }); - } - - /** - * Drop specified enum from database (Postgres only) - * - * @param {string} [enumName] Enum name to drop - * @param {object} options Query options - * - * @returns {Promise} - * @private - */ - dropEnum(enumName, options = {}) { - if (this.sequelize.getDialect() !== 'postgres') { - return Promise.resolve(); - } - - return this.sequelize.query( - this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(enumName)), - Object.assign({}, options, { raw: true }) - ); - } - - /** - * Drop all enums from database (Postgres only) - * - * @param {object} options Query options - * - * @returns {Promise} - * @private - */ - dropAllEnums(options = {}) { - if (this.sequelize.getDialect() !== 'postgres') { - return Promise.resolve(); - } - - return this.pgListEnums(undefined, options).map(result => this.sequelize.query( - this.QueryGenerator.pgEnumDrop(null, null, this.QueryGenerator.pgEscapeAndQuote(result.enum_name)), - Object.assign({}, options, { raw: true }) - )); - } - - /** - * List all enums (Postgres only) - * - * @param {string} [tableName] Table whose enum to list - * @param {object} [options] Query options - * - * @returns {Promise} - * @private - */ - pgListEnums(tableName, options = {}) { - const sql = this.QueryGenerator.pgListEnums(tableName); - return this.sequelize.query(sql, Object.assign({}, options, { plain: false, raw: true, type: QueryTypes.SELECT })); - } - - /** - * Rename a table - * - * @param {string} before Current name of table - * @param {string} after New name from table - * @param {object} [options] Query options - * - * @returns {Promise} - */ - renameTable(before, after, options = {}) { - const sql = this.QueryGenerator.renameTableQuery(before, after); - return this.sequelize.query(sql, options); - } - - /** - * Get all tables in current database - * - * @param {object} [options] Query options - * @param {boolean} [options.raw=true] Run query in raw mode - * @param {QueryType} [options.type=QueryType.SHOWTABLE] query type - * - * @returns {Promise} - * @private - */ - showAllTables(options) { - options = Object.assign({}, options, { - raw: true, - type: QueryTypes.SHOWTABLES - }); - - const showTablesSql = this.QueryGenerator.showTablesQuery(this.sequelize.config.database); - return this.sequelize.query(showTablesSql, options).then(tableNames => _.flatten(tableNames)); - } - - /** - * Describe a table structure - * - * This method returns an array of hashes containing information about all attributes in the table. - * - * ```js - * { - * name: { - * type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg! - * allowNull: true, - * defaultValue: null - * }, - * isBetaMember: { - * type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg! - * allowNull: false, - * defaultValue: false - * } - * } - * ``` - * - * @param {string} tableName table name - * @param {object} [options] Query options - * - * @returns {Promise} - */ - describeTable(tableName, options) { - let schema = null; - let schemaDelimiter = null; - - if (typeof options === 'string') { - schema = options; - } else if (typeof options === 'object' && options !== null) { - schema = options.schema || null; - schemaDelimiter = options.schemaDelimiter || null; - } - - if (typeof tableName === 'object' && tableName !== null) { - schema = tableName.schema; - tableName = tableName.tableName; - } - - const sql = this.QueryGenerator.describeTableQuery(tableName, schema, schemaDelimiter); - options = Object.assign({}, options, { type: QueryTypes.DESCRIBE }); - - return this.sequelize.query(sql, options).then(data => { - /* - * If no data is returned from the query, then the table name may be wrong. - * Query generators that use information_schema for retrieving table info will just return an empty result set, - * it will not throw an error like built-ins do (e.g. DESCRIBE on MySql). - */ - if (_.isEmpty(data)) { - throw new Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); - } - - return data; - }).catch(e => { - if (e.original && e.original.code === 'ER_NO_SUCH_TABLE') { - throw Error(`No description found for "${tableName}" table. Check the table name and schema; remember, they _are_ case sensitive.`); - } - - throw e; - }); - } - - /** - * Add a new column to a table - * - * ```js - * queryInterface.addColumn('tableA', 'columnC', Sequelize.STRING, { - * after: 'columnB' // after option is only supported by MySQL - * }); - * ``` - * - * @param {string} table Table to add column to - * @param {string} key Column name - * @param {object} attribute Attribute definition - * @param {object} [options] Query options - * - * @returns {Promise} - */ - addColumn(table, key, attribute, options = {}) { - if (!table || !key || !attribute) { - throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - } - - attribute = this.sequelize.normalizeAttribute(attribute); - return this.sequelize.query(this.QueryGenerator.addColumnQuery(table, key, attribute), options); - } - - /** - * Remove a column from a table - * - * @param {string} tableName Table to remove column from - * @param {string} attributeName Column name to remove - * @param {object} [options] Query options - * - * @returns {Promise} - */ - removeColumn(tableName, attributeName, options = {}) { - switch (this.sequelize.options.dialect) { - case 'sqlite': - // sqlite needs some special treatment as it cannot drop a column - return SQLiteQueryInterface.removeColumn(this, tableName, attributeName, options); - case 'mssql': - // mssql needs special treatment as it cannot drop a column with a default or foreign key constraint - return MSSQLQueryInterface.removeColumn(this, tableName, attributeName, options); - case 'mysql': - case 'mariadb': - // mysql/mariadb need special treatment as it cannot drop a column with a foreign key constraint - return MySQLQueryInterface.removeColumn(this, tableName, attributeName, options); - default: - return this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options); - } - } - - /** - * Change a column definition - * - * @param {string} tableName Table name to change from - * @param {string} attributeName Column name - * @param {object} dataTypeOrOptions Attribute definition for new column - * @param {object} [options] Query options - * - * @returns {Promise} - */ - changeColumn(tableName, attributeName, dataTypeOrOptions, options = {}) { - const attributes = {}; - - if (Object.values(DataTypes).includes(dataTypeOrOptions)) { - attributes[attributeName] = { type: dataTypeOrOptions, allowNull: true }; - } else { - attributes[attributeName] = dataTypeOrOptions; - } - - attributes[attributeName] = this.sequelize.normalizeAttribute(attributes[attributeName]); - - if (this.sequelize.options.dialect === 'sqlite') { - // sqlite needs some special treatment as it cannot change a column - return SQLiteQueryInterface.changeColumn(this, tableName, attributes, options); - } - const query = this.QueryGenerator.attributesToSQL(attributes, { - context: 'changeColumn', - table: tableName - }); - const sql = this.QueryGenerator.changeColumnQuery(tableName, query); - - return this.sequelize.query(sql, options); - } - - /** - * Rename a column - * - * @param {string} tableName Table name whose column to rename - * @param {string} attrNameBefore Current column name - * @param {string} attrNameAfter New column name - * @param {object} [options] Query option - * - * @returns {Promise} - */ - renameColumn(tableName, attrNameBefore, attrNameAfter, options = {}) { - return this.describeTable(tableName, options).then(data => { - if (!data[attrNameBefore]) { - throw new Error(`Table ${tableName} doesn't have the column ${attrNameBefore}`); - } - - data = data[attrNameBefore] || {}; - - const _options = {}; - - _options[attrNameAfter] = { - attribute: attrNameAfter, - type: data.type, - allowNull: data.allowNull, - defaultValue: data.defaultValue - }; - - // fix: a not-null column cannot have null as default value - if (data.defaultValue === null && !data.allowNull) { - delete _options[attrNameAfter].defaultValue; - } - - if (this.sequelize.options.dialect === 'sqlite') { - // sqlite needs some special treatment as it cannot rename a column - return SQLiteQueryInterface.renameColumn(this, tableName, attrNameBefore, attrNameAfter, options); - } - const sql = this.QueryGenerator.renameColumnQuery( - tableName, - attrNameBefore, - this.QueryGenerator.attributesToSQL(_options) - ); - return this.sequelize.query(sql, options); - }); - } - - /** - * Add an index to a column - * - * @param {string|object} tableName Table name to add index on, can be a object with schema - * @param {Array} [attributes] Use options.fields instead, List of attributes to add index on - * @param {object} options indexes options - * @param {Array} options.fields List of attributes to add index on - * @param {boolean} [options.concurrently] Pass CONCURRENT so other operations run while the index is created - * @param {boolean} [options.unique] Create a unique index - * @param {string} [options.using] Useful for GIN indexes - * @param {string} [options.operator] Index operator - * @param {string} [options.type] Type of index, available options are UNIQUE|FULLTEXT|SPATIAL - * @param {string} [options.name] Name of the index. Default is
UNIQUECHECKDefault - MSSQL onlyPrimary KeyForeign KeyComposite Foreign Key
__ - * @param {object} [options.where] Where condition on index, for partial indexes - * @param {string} [rawTablename] table name, this is just for backward compatibiity - * - * @returns {Promise} - */ - addIndex(tableName, attributes, options, rawTablename) { - // Support for passing tableName, attributes, options or tableName, options (with a fields param which is the attributes) - if (!Array.isArray(attributes)) { - rawTablename = options; - options = attributes; - attributes = options.fields; - } - - if (!rawTablename) { - // Map for backwards compat - rawTablename = tableName; - } - - options = Utils.cloneDeep(options); - options.fields = attributes; - const sql = this.QueryGenerator.addIndexQuery(tableName, options, rawTablename); - return this.sequelize.query(sql, Object.assign({}, options, { supportsSearchPath: false })); - } - - /** - * Show indexes on a table - * - * @param {string} tableName table name - * @param {object} [options] Query options - * - * @returns {Promise} - * @private - */ - showIndex(tableName, options) { - const sql = this.QueryGenerator.showIndexesQuery(tableName, options); - return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWINDEXES })); - } - - getForeignKeysForTables(tableNames, options) { - if (tableNames.length === 0) { - return Promise.resolve({}); - } - - options = Object.assign({}, options, { type: QueryTypes.FOREIGNKEYS }); - - return Promise.map(tableNames, tableName => - this.sequelize.query(this.QueryGenerator.getForeignKeysQuery(tableName, this.sequelize.config.database), options) - ).then(results => { - const result = {}; - - tableNames.forEach((tableName, i) => { - if (_.isObject(tableName)) { - tableName = `${tableName.schema}.${tableName.tableName}`; - } - - result[tableName] = Array.isArray(results[i]) - ? results[i].map(r => r.constraint_name) - : [results[i] && results[i].constraint_name]; - - result[tableName] = result[tableName].filter(_.identity); - }); - - return result; - }); - } - - /** - * Get foreign key references details for the table - * - * Those details contains constraintSchema, constraintName, constraintCatalog - * tableCatalog, tableSchema, tableName, columnName, - * referencedTableCatalog, referencedTableCatalog, referencedTableSchema, referencedTableName, referencedColumnName. - * Remind: constraint informations won't return if it's sqlite. - * - * @param {string} tableName table name - * @param {object} [options] Query options - * - * @returns {Promise} - */ - getForeignKeyReferencesForTable(tableName, options) { - const queryOptions = Object.assign({}, options, { - type: QueryTypes.FOREIGNKEYS - }); - const catalogName = this.sequelize.config.database; - switch (this.sequelize.options.dialect) { - case 'sqlite': - // sqlite needs some special treatment. - return SQLiteQueryInterface.getForeignKeyReferencesForTable(this, tableName, queryOptions); - case 'postgres': - { - // postgres needs some special treatment as those field names returned are all lowercase - // in order to keep same result with other dialects. - const query = this.QueryGenerator.getForeignKeyReferencesQuery(tableName, catalogName); - return this.sequelize.query(query, queryOptions) - .then(result => result.map(Utils.camelizeObjectKeys)); - } - case 'mssql': - case 'mysql': - case 'mariadb': - default: { - const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName); - return this.sequelize.query(query, queryOptions); - } - } - } - - /** - * Remove an already existing index from a table - * - * @param {string} tableName Table name to drop index from - * @param {string} indexNameOrAttributes Index name - * @param {object} [options] Query options - * - * @returns {Promise} - */ - removeIndex(tableName, indexNameOrAttributes, options = {}) { - const sql = this.QueryGenerator.removeIndexQuery(tableName, indexNameOrAttributes); - return this.sequelize.query(sql, options); - } - - /** - * Add a constraint to a table - * - * Available constraints: - * - UNIQUE - * - DEFAULT (MSSQL only) - * - CHECK (MySQL - Ignored by the database engine ) - * - FOREIGN KEY - * - PRIMARY KEY - * - * @example - * queryInterface.addConstraint('Users', ['email'], { - * type: 'unique', - * name: 'custom_unique_constraint_name' - * }); - * - * @example - * queryInterface.addConstraint('Users', ['roles'], { - * type: 'check', - * where: { - * roles: ['user', 'admin', 'moderator', 'guest'] - * } - * }); - * - * @example - * queryInterface.addConstraint('Users', ['roles'], { - * type: 'default', - * defaultValue: 'guest' - * }); - * - * @example - * queryInterface.addConstraint('Users', ['username'], { - * type: 'primary key', - * name: 'custom_primary_constraint_name' - * }); - * - * @example - * queryInterface.addConstraint('Posts', ['username'], { - * type: 'foreign key', - * name: 'custom_fkey_constraint_name', - * references: { //Required field - * table: 'target_table_name', - * field: 'target_column_name' - * }, - * onDelete: 'cascade', - * onUpdate: 'cascade' - * }); - * - * @param {string} tableName Table name where you want to add a constraint - * @param {Array} attributes Array of column names to apply the constraint over - * @param {object} options An object to define the constraint name, type etc - * @param {string} options.type Type of constraint. One of the values in available constraints(case insensitive) - * @param {string} [options.name] Name of the constraint. If not specified, sequelize automatically creates a named constraint based on constraint type, table & column names - * @param {string} [options.defaultValue] The value for the default constraint - * @param {object} [options.where] Where clause/expression for the CHECK constraint - * @param {object} [options.references] Object specifying target table, column name to create foreign key constraint - * @param {string} [options.references.table] Target table name - * @param {string} [options.references.field] Target column name - * @param {string} [rawTablename] Table name, for backward compatibility - * - * @returns {Promise} - */ - addConstraint(tableName, attributes, options, rawTablename) { - if (!Array.isArray(attributes)) { - rawTablename = options; - options = attributes; - attributes = options.fields; - } - - if (!options.type) { - throw new Error('Constraint type must be specified through options.type'); - } - - if (!rawTablename) { - // Map for backwards compat - rawTablename = tableName; - } - - options = Utils.cloneDeep(options); - options.fields = attributes; - - if (this.sequelize.dialect.name === 'sqlite') { - return SQLiteQueryInterface.addConstraint(this, tableName, options, rawTablename); - } - const sql = this.QueryGenerator.addConstraintQuery(tableName, options, rawTablename); - return this.sequelize.query(sql, options); - } - - showConstraint(tableName, constraintName, options) { - const sql = this.QueryGenerator.showConstraintsQuery(tableName, constraintName); - return this.sequelize.query(sql, Object.assign({}, options, { type: QueryTypes.SHOWCONSTRAINTS })); - } - - /** - * Remove a constraint from a table - * - * @param {string} tableName Table name to drop constraint from - * @param {string} constraintName Constraint name - * @param {object} options Query options - * - * @returns {Promise} - */ - removeConstraint(tableName, constraintName, options = {}) { - - switch (this.sequelize.options.dialect) { - case 'mysql': - case 'mariadb': - //does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used - return MySQLQueryInterface.removeConstraint(this, tableName, constraintName, options); - case 'sqlite': - return SQLiteQueryInterface.removeConstraint(this, tableName, constraintName, options); - default: - const sql = this.QueryGenerator.removeConstraintQuery(tableName, constraintName); - return this.sequelize.query(sql, options); - } - } - - insert(instance, tableName, values, options) { - options = Utils.cloneDeep(options); - options.hasTrigger = instance && instance.constructor.options.hasTrigger; - const sql = this.QueryGenerator.insertQuery(tableName, values, instance && instance.constructor.rawAttributes, options); - - options.type = QueryTypes.INSERT; - options.instance = instance; - - return this.sequelize.query(sql, options).then(results => { - if (instance) results[0].isNewRecord = false; - return results; - }); - } - - /** - * Upsert - * - * @param {string} tableName table to upsert on - * @param {object} insertValues values to be inserted, mapped to field name - * @param {object} updateValues values to be updated, mapped to field name - * @param {object} where various conditions - * @param {Model} model Model to upsert on - * @param {object} options query options - * - * @returns {Promise} Resolves an array with - */ - upsert(tableName, insertValues, updateValues, where, model, options) { - const wheres = []; - const attributes = Object.keys(insertValues); - let indexes = []; - let indexFields; - - options = { ...options }; - - if (!Utils.isWhereEmpty(where)) { - wheres.push(where); - } - - // Lets combine unique keys and indexes into one - indexes = _.map(model.uniqueKeys, value => { - return value.fields; - }); - - model._indexes.forEach(value => { - if (value.unique) { - // fields in the index may both the strings or objects with an attribute property - lets sanitize that - indexFields = value.fields.map(field => { - if (_.isPlainObject(field)) { - return field.attribute; - } - return field; - }); - indexes.push(indexFields); - } - }); - - for (const index of indexes) { - if (_.intersection(attributes, index).length === index.length) { - where = {}; - for (const field of index) { - where[field] = insertValues[field]; - } - wheres.push(where); - } - } - - where = { [Op.or]: wheres }; - - options.type = QueryTypes.UPSERT; - options.raw = true; - - const sql = this.QueryGenerator.upsertQuery(tableName, insertValues, updateValues, where, model, options); - return this.sequelize.query(sql, options).then(result => { - switch (this.sequelize.options.dialect) { - case 'postgres': - return [result.created, result.primary_key]; - - case 'mssql': - return [ - result.$action === 'INSERT', - result[model.primaryKeyField] - ]; - - // MySQL returns 1 for inserted, 2 for updated - // http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html. - case 'mysql': - case 'mariadb': - return [result === 1, undefined]; - - default: - return [result, undefined]; - } - }); - } - - /** - * Insert multiple records into a table - * - * @example - * queryInterface.bulkInsert('roles', [{ - * label: 'user', - * createdAt: new Date(), - * updatedAt: new Date() - * }, { - * label: 'admin', - * createdAt: new Date(), - * updatedAt: new Date() - * }]); - * - * @param {string} tableName Table name to insert record to - * @param {Array} records List of records to insert - * @param {object} options Various options, please see Model.bulkCreate options - * @param {object} attributes Various attributes mapped by field name - * - * @returns {Promise} - */ - bulkInsert(tableName, records, options, attributes) { - options = { ...options }; - options.type = QueryTypes.INSERT; - - return this.sequelize.query( - this.QueryGenerator.bulkInsertQuery(tableName, records, options, attributes), - options - ).then(results => results[0]); - } - - update(instance, tableName, values, identifier, options) { - options = { ...options }; - options.hasTrigger = !!(instance && instance._modelOptions && instance._modelOptions.hasTrigger); - - const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, instance.constructor.rawAttributes); - - options.type = QueryTypes.UPDATE; - - options.instance = instance; - return this.sequelize.query(sql, options); - } - - /** - * Update multiple records of a table - * - * @example - * queryInterface.bulkUpdate('roles', { - * label: 'admin', - * }, { - * userType: 3, - * }, - * ); - * - * @param {string} tableName Table name to update - * @param {object} values Values to be inserted, mapped to field name - * @param {object} identifier A hash with conditions OR an ID as integer OR a string with conditions - * @param {object} [options] Various options, please see Model.bulkCreate options - * @param {object} [attributes] Attributes on return objects if supported by SQL dialect - * - * @returns {Promise} - */ - bulkUpdate(tableName, values, identifier, options, attributes) { - options = Utils.cloneDeep(options); - if (typeof identifier === 'object') identifier = Utils.cloneDeep(identifier); - - const sql = this.QueryGenerator.updateQuery(tableName, values, identifier, options, attributes); - const table = _.isObject(tableName) ? tableName : { tableName }; - const model = _.find(this.sequelize.modelManager.models, { tableName: table.tableName }); - - options.type = QueryTypes.BULKUPDATE; - options.model = model; - return this.sequelize.query(sql, options); - } - - delete(instance, tableName, identifier, options) { - const cascades = []; - const sql = this.QueryGenerator.deleteQuery(tableName, identifier, {}, instance.constructor); - - options = { ...options }; - - // Check for a restrict field - if (!!instance.constructor && !!instance.constructor.associations) { - const keys = Object.keys(instance.constructor.associations); - const length = keys.length; - let association; - - for (let i = 0; i < length; i++) { - association = instance.constructor.associations[keys[i]]; - if (association.options && association.options.onDelete && - association.options.onDelete.toLowerCase() === 'cascade' && - association.options.useHooks === true) { - cascades.push(association.accessors.get); - } - } - } - - return Promise.each(cascades, cascade => { - return instance[cascade](options).then(instances => { - // Check for hasOne relationship with non-existing associate ("has zero") - if (!instances) { - return Promise.resolve(); - } - - if (!Array.isArray(instances)) instances = [instances]; - - return Promise.each(instances, instance => instance.destroy(options)); - }); - }).then(() => { - options.instance = instance; - return this.sequelize.query(sql, options); - }); - } - - /** - * Delete multiple records from a table - * - * @param {string} tableName table name from where to delete records - * @param {object} where where conditions to find records to delete - * @param {object} [options] options - * @param {boolean} [options.truncate] Use truncate table command - * @param {boolean} [options.cascade=false] Only used in conjunction with TRUNCATE. Truncates all tables that have foreign-key references to the named table, or to any tables added to the group due to CASCADE. - * @param {boolean} [options.restartIdentity=false] Only used in conjunction with TRUNCATE. Automatically restart sequences owned by columns of the truncated table. - * @param {Model} [model] Model - * - * @returns {Promise} - */ - bulkDelete(tableName, where, options, model) { - options = { - limit: null, - ...Utils.cloneDeep(options) - }; - - if (options.truncate === true) { - return this.sequelize.query( - this.QueryGenerator.truncateTableQuery(tableName, options), - options - ); - } - - if (typeof where === 'object') where = Utils.cloneDeep(where); - - return this.sequelize.query( - this.QueryGenerator.deleteQuery(tableName, where, options, model), - options - ); - } - - select(model, tableName, optionsArg) { - const options = Object.assign({}, optionsArg, { type: QueryTypes.SELECT, model }); - - return this.sequelize.query( - this.QueryGenerator.selectQuery(tableName, options, model), - options - ); - } - - increment(model, tableName, values, identifier, options) { - options = Utils.cloneDeep(options); - - const sql = this.QueryGenerator.arithmeticQuery('+', tableName, values, identifier, options, options.attributes); - - options.type = QueryTypes.UPDATE; - options.model = model; - - return this.sequelize.query(sql, options); - } - - decrement(model, tableName, values, identifier, options) { - options = Utils.cloneDeep(options); - - const sql = this.QueryGenerator.arithmeticQuery('-', tableName, values, identifier, options, options.attributes); - - options.type = QueryTypes.UPDATE; - options.model = model; - - return this.sequelize.query(sql, options); - } - - rawSelect(tableName, options, attributeSelector, Model) { - options = { - raw: true, - plain: true, - type: QueryTypes.SELECT, - ...Utils.cloneDeep(options) - }; - - const sql = this.QueryGenerator.selectQuery(tableName, options, Model); - - if (attributeSelector === undefined) { - throw new Error('Please pass an attribute selector!'); - } - - return this.sequelize.query(sql, options).then(data => { - if (!options.plain) { - return data; - } - - const result = data ? data[attributeSelector] : null; - - if (!options || !options.dataType) { - return result; - } - - const dataType = options.dataType; - - if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) { - if (result !== null) { - return parseFloat(result); - } - } - if (dataType instanceof DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) { - return parseInt(result, 10); - } - if (dataType instanceof DataTypes.DATE) { - if (result !== null && !(result instanceof Date)) { - return new Date(result); - } - } - return result; - }); - } - - createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray, options = {}) { - const sql = this.QueryGenerator.createTrigger(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray); - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - dropTrigger(tableName, triggerName, options = {}) { - const sql = this.QueryGenerator.dropTrigger(tableName, triggerName); - - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - renameTrigger(tableName, oldTriggerName, newTriggerName, options = {}) { - const sql = this.QueryGenerator.renameTrigger(tableName, oldTriggerName, newTriggerName); - - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - /** - * Create an SQL function - * - * @example - * queryInterface.createFunction( - * 'someFunction', - * [ - * {type: 'integer', name: 'param', direction: 'IN'} - * ], - * 'integer', - * 'plpgsql', - * 'RETURN param + 1;', - * [ - * 'IMMUTABLE', - * 'LEAKPROOF' - * ], - * { - * variables: - * [ - * {type: 'integer', name: 'myVar', default: 100} - * ], - * force: true - * }; - * ); - * - * @param {string} functionName Name of SQL function to create - * @param {Array} params List of parameters declared for SQL function - * @param {string} returnType SQL type of function returned value - * @param {string} language The name of the language that the function is implemented in - * @param {string} body Source code of function - * @param {Array} optionsArray Extra-options for creation - * @param {object} [options] query options - * @param {boolean} options.force If force is true, any existing functions with the same parameters will be replaced. For postgres, this means using `CREATE OR REPLACE FUNCTION` instead of `CREATE FUNCTION`. Default is false - * @param {Array} options.variables List of declared variables. Each variable should be an object with string fields `type` and `name`, and optionally having a `default` field as well. - * - * @returns {Promise} - */ - createFunction(functionName, params, returnType, language, body, optionsArray, options = {}) { - const sql = this.QueryGenerator.createFunction(functionName, params, returnType, language, body, optionsArray, options); - - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - /** - * Drop an SQL function - * - * @example - * queryInterface.dropFunction( - * 'someFunction', - * [ - * {type: 'varchar', name: 'param1', direction: 'IN'}, - * {type: 'integer', name: 'param2', direction: 'INOUT'} - * ] - * ); - * - * @param {string} functionName Name of SQL function to drop - * @param {Array} params List of parameters declared for SQL function - * @param {object} [options] query options - * - * @returns {Promise} - */ - dropFunction(functionName, params, options = {}) { - const sql = this.QueryGenerator.dropFunction(functionName, params); - - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - /** - * Rename an SQL function - * - * @example - * queryInterface.renameFunction( - * 'fooFunction', - * [ - * {type: 'varchar', name: 'param1', direction: 'IN'}, - * {type: 'integer', name: 'param2', direction: 'INOUT'} - * ], - * 'barFunction' - * ); - * - * @param {string} oldFunctionName Current name of function - * @param {Array} params List of parameters declared for SQL function - * @param {string} newFunctionName New name of function - * @param {object} [options] query options - * - * @returns {Promise} - */ - renameFunction(oldFunctionName, params, newFunctionName, options = {}) { - const sql = this.QueryGenerator.renameFunction(oldFunctionName, params, newFunctionName); - - if (sql) { - return this.sequelize.query(sql, options); - } - return Promise.resolve(); - } - - // Helper methods useful for querying - - /** - * Escape an identifier (e.g. a table or attribute name) - * - * @param {string} identifier identifier to quote - * @param {boolean} [force] If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false. - * - * @private - */ - quoteIdentifier(identifier, force) { - return this.QueryGenerator.quoteIdentifier(identifier, force); - } - - quoteTable(identifier) { - return this.QueryGenerator.quoteTable(identifier); - } - - /** - * Quote array of identifiers at once - * - * @param {string[]} identifiers array of identifiers to quote - * @param {boolean} [force] If force is true,the identifier will be quoted even if the `quoteIdentifiers` option is false. - * - * @private - */ - quoteIdentifiers(identifiers, force) { - return this.QueryGenerator.quoteIdentifiers(identifiers, force); - } - - /** - * Escape a value (e.g. a string, number or date) - * - * @param {string} value string to escape - * - * @private - */ - escape(value) { - return this.QueryGenerator.escape(value); - } - - setIsolationLevel(transaction, value, options) { - if (!transaction || !(transaction instanceof Transaction)) { - throw new Error('Unable to set isolation level for a transaction without transaction object!'); - } - - if (transaction.parent || !value) { - // Not possible to set a separate isolation level for savepoints - return Promise.resolve(); - } - - options = Object.assign({}, options, { - transaction: transaction.parent || transaction - }); - - const sql = this.QueryGenerator.setIsolationLevelQuery(value, { - parent: transaction.parent - }); - - if (!sql) return Promise.resolve(); - - return this.sequelize.query(sql, options); - } - - startTransaction(transaction, options) { - if (!transaction || !(transaction instanceof Transaction)) { - throw new Error('Unable to start a transaction without transaction object!'); - } - - options = Object.assign({}, options, { - transaction: transaction.parent || transaction - }); - options.transaction.name = transaction.parent ? transaction.name : undefined; - const sql = this.QueryGenerator.startTransactionQuery(transaction); - - return this.sequelize.query(sql, options); - } - - deferConstraints(transaction, options) { - options = Object.assign({}, options, { - transaction: transaction.parent || transaction - }); - - const sql = this.QueryGenerator.deferConstraintsQuery(options); - - if (sql) { - return this.sequelize.query(sql, options); - } - - return Promise.resolve(); - } - - commitTransaction(transaction, options) { - if (!transaction || !(transaction instanceof Transaction)) { - throw new Error('Unable to commit a transaction without transaction object!'); - } - if (transaction.parent) { - // Savepoints cannot be committed - return Promise.resolve(); - } - - options = Object.assign({}, options, { - transaction: transaction.parent || transaction, - supportsSearchPath: false, - completesTransaction: true - }); - - const sql = this.QueryGenerator.commitTransactionQuery(transaction); - const promise = this.sequelize.query(sql, options); - - transaction.finished = 'commit'; - - return promise; - } - - rollbackTransaction(transaction, options) { - if (!transaction || !(transaction instanceof Transaction)) { - throw new Error('Unable to rollback a transaction without transaction object!'); - } - - options = Object.assign({}, options, { - transaction: transaction.parent || transaction, - supportsSearchPath: false, - completesTransaction: true - }); - options.transaction.name = transaction.parent ? transaction.name : undefined; - const sql = this.QueryGenerator.rollbackTransactionQuery(transaction); - const promise = this.sequelize.query(sql, options); - - transaction.finished = 'rollback'; - - return promise; - } -} - -module.exports = QueryInterface; -module.exports.QueryInterface = QueryInterface; -module.exports.default = QueryInterface; diff --git a/lib/sequelize.js b/lib/sequelize.js old mode 100755 new mode 100644 index 6c3c97411fcf..3aa6cb9cd54e --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -2,6 +2,7 @@ const url = require('url'); const path = require('path'); +const pgConnectionString = require('pg-connection-string'); const retry = require('retry-as-promised'); const _ = require('lodash'); @@ -10,14 +11,12 @@ const Model = require('./model'); const DataTypes = require('./data-types'); const Deferrable = require('./deferrable'); const ModelManager = require('./model-manager'); -const QueryInterface = require('./query-interface'); const Transaction = require('./transaction'); const QueryTypes = require('./query-types'); const TableHints = require('./table-hints'); const IndexHints = require('./index-hints'); const sequelizeErrors = require('./errors'); -const Promise = require('./promise'); -const { Hooks } = require('./hooks'); +const Hooks = require('./hooks'); const Association = require('./associations/index'); const Validator = require('./utils/validator-extras').validator; const Op = require('./operators'); @@ -132,7 +131,7 @@ class Sequelize { * @param {string} [options.username=null] The username which is used to authenticate against the database. * @param {string} [options.password=null] The password which is used to authenticate against the database. * @param {string} [options.database=null] The name of the database - * @param {string} [options.dialect] The dialect of the database you are connecting to. One of mysql, postgres, sqlite and mssql. + * @param {string} [options.dialect] The dialect of the database you are connecting to. One of mysql, postgres, sqlite, mariadb and mssql. * @param {string} [options.dialectModule=null] If specified, use this dialect library. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify 'require("pg.js")' here * @param {string} [options.dialectModulePath=null] If specified, load the dialect library from this path. For example, if you want to use pg.js instead of pg when connecting to a pg database, you should specify '/path/to/pg.js' here * @param {object} [options.dialectOptions] An object of additional options, which are passed directly to the connection library @@ -146,9 +145,9 @@ class Sequelize { * @param {string} [options.timezone='+00:00'] The timezone used when converting a date from the database into a JavaScript date. The timezone is also used to SET TIMEZONE when connecting to the server, to ensure that the result of NOW, CURRENT_TIMESTAMP and other time related functions have in the right timezone. For best cross platform performance use the format +/-HH:MM. Will also accept string versions of timezones used by moment.js (e.g. 'America/Los_Angeles'); this is useful to capture daylight savings time changes. * @param {string|boolean} [options.clientMinMessages='warning'] The PostgreSQL `client_min_messages` session parameter. Set to `false` to not override the database's default. * @param {boolean} [options.standardConformingStrings=true] The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. WARNING: Setting this to false may expose vulnerabilities and is not recommended! - * @param {Function} [options.logging=console.log] A function that gets executed every time Sequelize would log something. + * @param {Function} [options.logging=console.log] A function that gets executed every time Sequelize would log something. Function may receive multiple parameters but only first one is printed by `console.log`. To print all values use `(...msg) => console.log(msg)` * @param {boolean} [options.benchmark=false] Pass query execution time in milliseconds as second argument to logging function (options.logging). - * @param {boolean} [options.omitNull=false] A flag that defines if null values should be passed to SQL queries or not. + * @param {boolean} [options.omitNull=false] A flag that defines if null values should be passed as values to CREATE/UPDATE SQL queries or not. * @param {boolean} [options.native=false] A flag that defines if native library shall be used or not. Currently only has an effect for postgres * @param {boolean} [options.replication=false] Use read / write replication. To enable replication, pass an object, with two properties, read and write. Write should be an object (a single server for handling writes), and read an array of object (several servers to handle reads). Each read/write server can have the following properties: `host`, `port`, `username`, `password`, `database` * @param {object} [options.pool] sequelize connection pool configuration @@ -158,17 +157,18 @@ class Sequelize { * @param {number} [options.pool.acquire=60000] The maximum time, in milliseconds, that pool will try to get connection before throwing error * @param {number} [options.pool.evict=1000] The time interval, in milliseconds, after which sequelize-pool will remove idle connections. * @param {Function} [options.pool.validate] A function that validates a connection. Called with client. The default function checks that client is an object, and that its state is not disconnected + * @param {number} [options.pool.maxUses=Infinity] The number of times a connection can be used before discarding it for a replacement, [`used for eventual cluster rebalancing`](https://github.com/sequelize/sequelize-pool). * @param {boolean} [options.quoteIdentifiers=true] Set to `false` to make table names and attributes case-insensitive on Postgres and skip double quoting of them. WARNING: Setting this to false may expose vulnerabilities and is not recommended! * @param {string} [options.transactionType='DEFERRED'] Set the default transaction type. See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. * @param {string} [options.isolationLevel] Set the default transaction isolation level. See `Sequelize.Transaction.ISOLATION_LEVELS` for possible options. - * @param {object} [options.retry] Set of flags that control when a query is automatically retried. + * @param {object} [options.retry] Set of flags that control when a query is automatically retried. Accepts all options for [`retry-as-promised`](https://github.com/mickhansen/retry-as-promised). * @param {Array} [options.retry.match] Only retry a query if the error matches one of these strings. * @param {number} [options.retry.max] How many times a failing query is automatically retried. Set to 0 to disable retrying on SQL_BUSY error. * @param {boolean} [options.typeValidation=false] Run built-in type validators on insert and update, and select with where clause, e.g. validate that arguments passed to integer fields are integer-like. * @param {object} [options.operatorsAliases] String based operator alias. Pass object to limit set of aliased operators. * @param {object} [options.hooks] An object of global hook functions that are called before and after certain lifecycle events. Global hooks will run after any model-specific hooks defined for the same event (See `Sequelize.Model.init()` for a list). Additionally, `beforeConnect()`, `afterConnect()`, `beforeDisconnect()`, and `afterDisconnect()` hooks may be defined here. * @param {boolean} [options.minifyAliases=false] A flag that defines if aliases should be minified (mostly useful to avoid Postgres alias character limit of 64) - * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind patameters in log. + * @param {boolean} [options.logQueryParameters=false] A flag that defines if show bind parameters in log. */ constructor(database, username, password, options) { let config; @@ -211,21 +211,43 @@ class Sequelize { } if (urlParts.query) { - if (options.dialectOptions) + // Allow host query argument to override the url host. + // Enables specifying domain socket hosts which cannot be specified via the typical + // host part of a url. + if (urlParts.query.host) { + options.host = urlParts.query.host; + } + + if (options.dialectOptions) { Object.assign(options.dialectOptions, urlParts.query); - else + } else { options.dialectOptions = urlParts.query; + if (urlParts.query.options) { + try { + const o = JSON.parse(urlParts.query.options); + options.dialectOptions.options = o; + } catch (e) { + // Nothing to do, string is not a valid JSON + // an thus does not need any further processing + } + } + } + } + + // For postgres, we can use this helper to load certs directly from the + // connection string. + if (options.dialect === 'postgres' || options.dialect === 'postgresql') { + Object.assign(options.dialectOptions, pgConnectionString.parse(arguments[0])); } } else { // new Sequelize(database, username, password, { ... options }) options = options || {}; config = { database, username, password }; } - this.hooks = new Hooks(options.hooks); - Sequelize.hooks.run('beforeInit', config, options); + Sequelize.runHooks('beforeInit', config, options); - this.options = Object.assign({ + this.options = { dialect: null, dialectModule: null, dialectModulePath: null, @@ -258,8 +280,9 @@ class Sequelize { typeValidation: false, benchmark: false, minifyAliases: false, - logQueryParameters: false - }, options); + logQueryParameters: false, + ...options + }; if (!this.options.dialect) { throw new Error('Dialect needs to be explicitly supplied as of v4.0.0'); @@ -279,6 +302,7 @@ class Sequelize { this.options.logging = console.log; } + this._setupHooks(options.hooks); this.config = { database: config.database || this.options.database, @@ -321,13 +345,16 @@ class Sequelize { } this.dialect = new Dialect(this); - this.dialect.QueryGenerator.typeValidation = options.typeValidation; + this.dialect.queryGenerator.typeValidation = options.typeValidation; - if ('operatorsAliases' in this.options) { - throw new Error('operatorAliases support was removed in v6'); + if (_.isPlainObject(this.options.operatorsAliases)) { + deprecations.noStringOperators(); + this.dialect.queryGenerator.setOperatorsAliases(this.options.operatorsAliases); + } else if (typeof this.options.operatorsAliases === 'boolean') { + deprecations.noBoolOperatorAliases(); } - this.queryInterface = new QueryInterface(this); + this.queryInterface = this.dialect.queryInterface; /** * Models are stored here under the name given to `sequelize.define` @@ -336,9 +363,7 @@ class Sequelize { this.modelManager = new ModelManager(this); this.connectionManager = this.dialect.connectionManager; - this.importCache = {}; - - Sequelize.hooks.run('afterInit', this); + Sequelize.runHooks('afterInit', this); } /** @@ -374,7 +399,6 @@ class Sequelize { * @returns {QueryInterface} An instance (singleton) of QueryInterface. */ getQueryInterface() { - this.queryInterface = this.queryInterface || new QueryInterface(this); return this.queryInterface; } @@ -389,9 +413,8 @@ class Sequelize { * * @see * {@link Model.init} for a more comprehensive specification of the `options` and `attributes` objects. - * @see Model definition Manual related to model definition * @see - * {@link DataTypes} For a list of possible data types + * Model Basics guide * * @returns {Model} Newly defined model * @@ -453,38 +476,6 @@ class Sequelize { return !!this.modelManager.models.find(model => model.name === modelName); } - /** - * Imports a model defined in another file. Imported models are cached, so multiple - * calls to import with the same path will not load the file multiple times. - * - * @tutorial https://github.com/sequelize/express-example - * - * @param {string} importPath The path to the file that holds the model you want to import. If the part is relative, it will be resolved relatively to the calling file - * - * @returns {Model} Imported model, returned from cache if was already imported - */ - import(importPath) { - // is it a relative path? - if (path.normalize(importPath) !== path.resolve(importPath)) { - // make path relative to the caller - const callerFilename = Utils.stack()[1].getFileName(); - const callerPath = path.dirname(callerFilename); - - importPath = path.resolve(callerPath, importPath); - } - - if (!this.importCache[importPath]) { - let defineCall = arguments.length > 1 ? arguments[1] : require(importPath); - if (typeof defineCall === 'object') { - // ES6 module compatibility - defineCall = defineCall.default; - } - this.importCache[importPath] = defineCall(this, DataTypes); - } - - return this.importCache[importPath]; - } - /** * Execute a query on the DB, optionally bypassing all the Sequelize goodness. * @@ -493,13 +484,9 @@ class Sequelize { * If you are running a type of query where you don't need the metadata, for example a `SELECT` query, you can pass in a query type to make sequelize format the results: * * ```js - * sequelize.query('SELECT...').then(([results, metadata]) => { - * // Raw query - use then plus array spread - * }); + * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring * - * sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }).then(results => { - * // SELECT query - use then - * }) + * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring * ``` * * @param {string} sql @@ -513,9 +500,9 @@ class Sequelize { * @param {object|Array} [options.bind] Either an object of named bind parameter in the format `_param` or an array of unnamed bind parameter to replace `$1, $2, ...` in your SQL. * @param {boolean} [options.useMaster=false] Force the query to use the write pool, regardless of the query type. * @param {Function} [options.logging=false] A function that gets executed while running the query to log the sql. - * @param {new Model()} [options.instance] A sequelize instance used to build the return instance - * @param {Model} [options.model] A sequelize model used to build the returned model instances (used to be called callee) - * @param {object} [options.retry] Set of flags that control when a query is automatically retried. + * @param {Model} [options.instance] A sequelize model instance whose Model is to be used to build the query result + * @param {typeof Model} [options.model] A sequelize model used to build the returned model instances + * @param {object} [options.retry] Set of flags that control when a query is automatically retried. Accepts all options for [`retry-as-promised`](https://github.com/mickhansen/retry-as-promised). * @param {Array} [options.retry.match] Only retry a query if the error matches one of these strings. * @param {Integer} [options.retry.max] How many times a failing query is automatically retried. * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) @@ -525,11 +512,11 @@ class Sequelize { * * @returns {Promise} * - * @see {@link Model.constructor} for more information about instance option. + * @see {@link Model.build} for more information about instance option. */ - query(sql, options) { - options = Object.assign({}, this.options.query, options); + async query(sql, options) { + options = { ...this.options.query, ...options }; if (options.instance && !options.model) { options.model = options.instance.constructor; @@ -544,12 +531,11 @@ class Sequelize { options.fieldMap = _.get(options, 'model.fieldAttributeMap', {}); } - options = { + options = _.defaults(options, { // eslint-disable-next-line no-console logging: Object.prototype.hasOwnProperty.call(this.options, 'logging') ? this.options.logging : console.log, - searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT', - ...options - }; + searchPath: Object.prototype.hasOwnProperty.call(this.options, 'searchPath') ? this.options.searchPath : 'DEFAULT' + }); if (!options.type) { if (options.model || options.nest || options.plain) { @@ -574,76 +560,77 @@ class Sequelize { options.searchPath = 'DEFAULT'; } - return Promise.try(() => { - if (typeof sql === 'object') { - if (sql.values !== undefined) { - if (options.replacements !== undefined) { - throw new Error('Both `sql.values` and `options.replacements` cannot be set at the same time'); - } - options.replacements = sql.values; + if (typeof sql === 'object') { + if (sql.values !== undefined) { + if (options.replacements !== undefined) { + throw new Error('Both `sql.values` and `options.replacements` cannot be set at the same time'); } + options.replacements = sql.values; + } - if (sql.bind !== undefined) { - if (options.bind !== undefined) { - throw new Error('Both `sql.bind` and `options.bind` cannot be set at the same time'); - } - options.bind = sql.bind; + if (sql.bind !== undefined) { + if (options.bind !== undefined) { + throw new Error('Both `sql.bind` and `options.bind` cannot be set at the same time'); } + options.bind = sql.bind; + } - if (sql.query !== undefined) { - sql = sql.query; - } + if (sql.query !== undefined) { + sql = sql.query; } + } + + sql = sql.trim(); - sql = sql.trim(); + if (options.replacements && options.bind) { + throw new Error('Both `replacements` and `bind` cannot be set at the same time'); + } - if (options.replacements && options.bind) { - throw new Error('Both `replacements` and `bind` cannot be set at the same time'); + if (options.replacements) { + if (Array.isArray(options.replacements)) { + sql = Utils.format([sql].concat(options.replacements), this.options.dialect); + } else { + sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); } + } - if (options.replacements) { - if (Array.isArray(options.replacements)) { - sql = Utils.format([sql].concat(options.replacements), this.options.dialect); - } else { - sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect); - } + let bindParameters; + + if (options.bind) { + [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); + } + + const checkTransaction = () => { + if (options.transaction && options.transaction.finished && !options.completesTransaction) { + const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)`); + error.sql = sql; + throw error; } + }; - let bindParameters; + const retryOptions = { ...this.options.retry, ...options.retry }; - if (options.bind) { - [sql, bindParameters] = this.dialect.Query.formatBindParameters(sql, options.bind, this.options.dialect); + return retry(async () => { + if (options.transaction === undefined && Sequelize._cls) { + options.transaction = Sequelize._cls.get('transaction'); } - const checkTransaction = () => { - if (options.transaction && options.transaction.finished && !options.completesTransaction) { - const error = new Error(`${options.transaction.finished} has been called on this transaction(${options.transaction.id}), you can no longer use it. (The rejected query is attached as the 'sql' property of this error)`); - error.sql = sql; - throw error; - } - }; + checkTransaction(); - const retryOptions = Object.assign({}, this.options.retry, options.retry || {}); + const connection = await (options.transaction ? options.transaction.connection : this.connectionManager.getConnection(options)); + const query = new this.dialect.Query(connection, this, options); - return Promise.resolve(retry(() => Promise.try(() => { + try { + await this.runHooks('beforeQuery', options, query); checkTransaction(); - - return options.transaction - ? options.transaction.connection - : this.connectionManager.getConnection(options); - }).then(connection => { - const query = new this.dialect.Query(connection, this, options); - return this.hooks.run('beforeQuery', options, query) - .then(() => checkTransaction()) - .then(() => query.run(sql, bindParameters)) - .finally(() => this.hooks.run('afterQuery', options, query)) - .finally(() => { - if (!options.transaction) { - return this.connectionManager.releaseConnection(connection); - } - }); - }), retryOptions)); - }); + return await query.run(sql, bindParameters); + } finally { + await this.runHooks('afterQuery', options, query); + if (!options.transaction) { + await this.connectionManager.releaseConnection(connection); + } + } + }, retryOptions); } /** @@ -658,10 +645,10 @@ class Sequelize { * * @returns {Promise} */ - set(variables, options) { + async set(variables, options) { // Prepare options - options = Object.assign({}, this.options.set, typeof options === 'object' && options); + options = { ...this.options.set, ...typeof options === 'object' && options }; if (this.options.dialect !== 'mysql') { throw new Error('sequelize.set is only supported for mysql'); @@ -680,7 +667,7 @@ class Sequelize { `SET ${ _.map(variables, (v, k) => `@${k} := ${typeof v === 'string' ? `"${v}"` : v}`).join(', ')}`; - return this.query(query, options); + return await this.query(query, options); } /** @@ -691,7 +678,7 @@ class Sequelize { * @returns {string} */ escape(value) { - return this.getQueryInterface().escape(value); + return this.dialect.queryGenerator.escape(value); } /** @@ -709,8 +696,8 @@ class Sequelize { * * @returns {Promise} */ - createSchema(schema, options) { - return this.getQueryInterface().createSchema(schema, options); + async createSchema(schema, options) { + return await this.getQueryInterface().createSchema(schema, options); } /** @@ -724,8 +711,8 @@ class Sequelize { * * @returns {Promise} */ - showAllSchemas(options) { - return this.getQueryInterface().showAllSchemas(options); + async showAllSchemas(options) { + return await this.getQueryInterface().showAllSchemas(options); } /** @@ -740,8 +727,8 @@ class Sequelize { * * @returns {Promise} */ - dropSchema(schema, options) { - return this.getQueryInterface().dropSchema(schema, options); + async dropSchema(schema, options) { + return await this.getQueryInterface().dropSchema(schema, options); } /** @@ -755,8 +742,8 @@ class Sequelize { * * @returns {Promise} */ - dropAllSchemas(options) { - return this.getQueryInterface().dropAllSchemas(options); + async dropAllSchemas(options) { + return await this.getQueryInterface().dropAllSchemas(options); } /** @@ -769,54 +756,53 @@ class Sequelize { * @param {string} [options.schema='public'] The schema that the tables should be created in. This can be overridden for each table in sequelize.define * @param {string} [options.searchPath=DEFAULT] An optional parameter to specify the schema search_path (Postgres only) * @param {boolean} [options.hooks=true] If hooks is true then beforeSync, afterSync, beforeBulkSync, afterBulkSync hooks will be called - * @param {boolean} [options.alter=false] Alters tables to fit models. Not recommended for production use. Deletes data in columns that were removed or had their type changed in the model. + * @param {boolean|object} [options.alter=false] Alters tables to fit models. Provide an object for additional configuration. Not recommended for production use. If not further configured deletes data in columns that were removed or had their type changed in the model. + * @param {boolean} [options.alter.drop=true] Prevents any drop statements while altering a table when set to `false` * * @returns {Promise} */ - sync(options) { + async sync(options) { options = { ...this.options, ...this.options.sync, - ...options + ...options, + hooks: options ? options.hooks !== false : true }; - options.hooks = options.hooks === undefined ? true : !!options.hooks; if (options.match) { if (!options.match.test(this.config.database)) { - return Promise.reject(new Error(`Database "${this.config.database}" does not match sync match parameter "${options.match}"`)); + throw new Error(`Database "${this.config.database}" does not match sync match parameter "${options.match}"`); } } - return Promise.try(() => { - if (options.hooks) { - return this.hooks.run('beforeBulkSync', options); - } - }).then(() => { - if (options.force) { - return this.drop(options); - } - }).then(() => { - const models = []; - - // Topologically sort by foreign key constraints to give us an appropriate - // creation order - this.modelManager.forEachModel(model => { - if (model) { - models.push(model); - } else { - // DB should throw an SQL error if referencing non-existent table - } - }); - - // no models defined, just authenticate - if (!models.length) return this.authenticate(options); + if (options.hooks) { + await this.runHooks('beforeBulkSync', options); + } + if (options.force) { + await this.drop(options); + } + const models = []; - return Promise.each(models, model => model.sync(options)); - }).then(() => { - if (options.hooks) { - return this.hooks.run('afterBulkSync', options); + // Topologically sort by foreign key constraints to give us an appropriate + // creation order + this.modelManager.forEachModel(model => { + if (model) { + models.push(model); + } else { + // DB should throw an SQL error if referencing non-existent table } - }).return(this); + }); + + // no models defined, just authenticate + if (!models.length) { + await this.authenticate(options); + } else { + for (const model of models) await model.sync(options); + } + if (options.hooks) { + await this.runHooks('afterBulkSync', options); + } + return this; } /** @@ -830,7 +816,7 @@ class Sequelize { * @see * {@link Model.truncate} for more information */ - truncate(options) { + async truncate(options) { const models = []; this.modelManager.forEachModel(model => { @@ -839,12 +825,11 @@ class Sequelize { } }, { reverse: false }); - const truncateModel = model => model.truncate(options); - if (options && options.cascade) { - return Promise.each(models, truncateModel); + for (const model of models) await model.truncate(options); + } else { + await Promise.all(models.map(model => model.truncate(options))); } - return Promise.map(models, truncateModel); } /** @@ -859,7 +844,7 @@ class Sequelize { * * @returns {Promise} */ - drop(options) { + async drop(options) { const models = []; this.modelManager.forEachModel(model => { @@ -868,7 +853,7 @@ class Sequelize { } }, { reverse: false }); - return Promise.each(models, model => model.drop(options)); + for (const model of models) await model.drop(options); } /** @@ -878,18 +863,21 @@ class Sequelize { * * @returns {Promise} */ - authenticate(options) { - options = Object.assign({ + async authenticate(options) { + options = { raw: true, plain: true, - type: QueryTypes.SELECT - }, options); + type: QueryTypes.SELECT, + ...options + }; - return this.query('SELECT 1+1 AS result', options).return(); + await this.query('SELECT 1+1 AS result', options); + + return; } - databaseVersion(options) { - return this.getQueryInterface().databaseVersion(options); + async databaseVersion(options) { + return await this.getQueryInterface().databaseVersion(options); } /** @@ -1035,7 +1023,7 @@ class Sequelize { * {@link Model.findAll} * * @param {object} attr The attribute, which can be either an attribute object from `Model.rawAttributes` or a sequelize object, for example an instance of `sequelize.fn`. For simple string attributes, use the POJO syntax - * @param {Symbol} [comparator='Op.eq'] operator + * @param {symbol} [comparator='Op.eq'] operator * @param {string|object} logic The condition. Can be both a simply type, or a further condition (`or`, `and`, `.literal` etc.) * @since v2.0.0-dev3 */ @@ -1046,13 +1034,39 @@ class Sequelize { /** * Start a transaction. When using transactions, you should pass the transaction in the options argument in order for the query to happen under that transaction @see {@link Transaction} * + * If you have [CLS](https://github.com/Jeff-Lewis/cls-hooked) enabled, the transaction will automatically be passed to any query that runs within the callback + * * @example - * sequelize.transaction().then(transaction => { - * return User.findOne(..., {transaction}) - * .then(user => user.update(..., {transaction})) - * .then(() => transaction.commit()) - * .catch(() => transaction.rollback()); - * }) + * + * try { + * const transaction = await sequelize.transaction(); + * const user = await User.findOne(..., { transaction }); + * await user.update(..., { transaction }); + * await transaction.commit(); + * } catch { + * await transaction.rollback() + * } + * + * @example + * + * try { + * await sequelize.transaction(transaction => { // Note that we pass a callback rather than awaiting the call with no arguments + * const user = await User.findOne(..., {transaction}); + * await user.update(..., {transaction}); + * }); + * // Committed + * } catch(err) { + * // Rolled back + * console.error(err); + * } + * @example + * + * const cls = require('cls-hooked'); + * const namespace = cls.createNamespace('....'); + * const Sequelize = require('sequelize'); + * Sequelize.useCLS(namespace); + * + * // Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace * * @param {object} [options] Transaction options * @param {string} [options.type='DEFERRED'] See `Sequelize.Transaction.TYPES` for possible options. Sqlite only. @@ -1063,7 +1077,7 @@ class Sequelize { * * @returns {Promise} */ - transaction(options, autoCallback) { + async transaction(options, autoCallback) { if (typeof options === 'function') { autoCallback = options; options = undefined; @@ -1071,19 +1085,70 @@ class Sequelize { const transaction = new Transaction(this, options); - if (!autoCallback) return transaction.prepareEnvironment(false).return(transaction); + if (!autoCallback) { + await transaction.prepareEnvironment(false); + return transaction; + } // autoCallback provided - return transaction.prepareEnvironment() - .then(() => autoCallback(transaction)) - .tap(() => transaction.commit()) - .catch(err => { - // Rollback transaction if not already finished (commit, rollback, etc) - // and reject with original error (ignore any error in rollback) - return Promise.try(() => { - if (!transaction.finished) return transaction.rollback().catch(() => {}); - }).throw(err); - }); + return Sequelize._clsRun(async () => { + try { + await transaction.prepareEnvironment(); + const result = await autoCallback(transaction); + await transaction.commit(); + return await result; + } catch (err) { + try { + if (!transaction.finished) { + await transaction.rollback(); + } else { + // release the connection, even if we don't need to rollback + await transaction.cleanup(); + } + } catch (err0) { + // ignore + } + throw err; + } + }); + } + + /** + * Use CLS (Continuation Local Storage) with Sequelize. With Continuation + * Local Storage, all queries within the transaction callback will + * automatically receive the transaction object. + * + * CLS namespace provided is stored as `Sequelize._cls` + * + * @param {object} ns CLS namespace + * @returns {object} Sequelize constructor + */ + static useCLS(ns) { + // check `ns` is valid CLS namespace + if (!ns || typeof ns !== 'object' || typeof ns.bind !== 'function' || typeof ns.run !== 'function') throw new Error('Must provide CLS namespace'); + + // save namespace as `Sequelize._cls` + Sequelize._cls = ns; + + // return Sequelize for chaining + return this; + } + + /** + * Run function in CLS context. + * If no CLS context in use, just runs the function normally + * + * @private + * @param {Function} fn Function to run + * @returns {*} Return value of function + */ + static _clsRun(fn) { + const ns = Sequelize._cls; + if (!ns) return fn(); + + let res; + ns.run(context => res = fn(context)); + return res; } log(...args) { @@ -1188,8 +1253,6 @@ class Sequelize { } } -Sequelize.hooks = new Hooks(); - // Aliases Sequelize.prototype.fn = Sequelize.fn; Sequelize.prototype.col = Sequelize.col; @@ -1206,6 +1269,8 @@ Sequelize.prototype.validate = Sequelize.prototype.authenticate; */ Sequelize.version = require('../package.json').version; +Sequelize.options = { hooks: {} }; + /** * @private */ @@ -1213,29 +1278,28 @@ Sequelize.Utils = Utils; /** * Operators symbols to be used for querying data + * * @see {@link Operators} */ Sequelize.Op = Op; -/** - * A handy reference to the bluebird Promise class - */ -Sequelize.Promise = Promise; - /** * Available table hints to be used for querying data in mssql for table hints + * * @see {@link TableHints} */ Sequelize.TableHints = TableHints; /** * Available index hints to be used for querying data in mysql for index hints + * * @see {@link IndexHints} */ Sequelize.IndexHints = IndexHints; /** * A reference to the sequelize transaction class. Use this to access isolationLevels and types when creating a transaction + * * @see {@link Transaction} * @see {@link Sequelize.transaction} */ @@ -1243,18 +1307,21 @@ Sequelize.Transaction = Transaction; /** * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. + * * @see {@link Sequelize} */ Sequelize.prototype.Sequelize = Sequelize; /** * Available query types for use with `sequelize.query` + * * @see {@link QueryTypes} */ Sequelize.prototype.QueryTypes = Sequelize.QueryTypes = QueryTypes; /** * Exposes the validator.js object, so you can extend it with custom validation functions. The validator is exposed both on the instance, and on the constructor. + * * @see https://github.com/chriso/validator.js */ Sequelize.prototype.Validator = Sequelize.Validator = Validator; @@ -1268,6 +1335,7 @@ for (const dataType in DataTypes) { /** * A reference to the deferrable collection. Use this to access the different deferrable options. + * * @see {@link Transaction.Deferrable} * @see {@link Sequelize#transaction} */ @@ -1275,16 +1343,25 @@ Sequelize.Deferrable = Deferrable; /** * A reference to the sequelize association class. + * * @see {@link Association} */ Sequelize.prototype.Association = Sequelize.Association = Association; /** * Provide alternative version of `inflection` module to be used by `Utils.pluralize` etc. + * * @param {object} _inflection - `inflection` module */ Sequelize.useInflection = Utils.useInflection; +/** + * Allow hooks to be defined on Sequelize + on sequelize instance as universal hooks to run on all models + * and on Sequelize/sequelize methods e.g. Sequelize(), Sequelize#define() + */ +Hooks.applyTo(Sequelize); +Hooks.applyTo(Sequelize.prototype); + /** * Expose various errors available */ diff --git a/lib/transaction.js b/lib/transaction.js index 83647f025396..f8e32f18df92 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -1,8 +1,5 @@ 'use strict'; -const Promise = require('./promise'); -const { Hooks } = require('./hooks'); - /** * The transaction object is used to identify a running transaction. * It is created by calling `Sequelize.transaction()`. @@ -21,19 +18,20 @@ class Transaction { * @param {string} [options.isolationLevel] Sets the isolation level of the transaction. * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. PostgreSQL only */ - constructor(sequelize, options = {}) { - this.hooks = new Hooks(); + constructor(sequelize, options) { this.sequelize = sequelize; this.savepoints = []; + this._afterCommitHooks = []; // get dialect specific transaction options - const generateTransactionId = this.sequelize.dialect.QueryGenerator.generateTransactionId; + const generateTransactionId = this.sequelize.dialect.queryGenerator.generateTransactionId; - this.options = Object.assign({ + this.options = { type: sequelize.options.transactionType, isolationLevel: sequelize.options.isolationLevel, - readOnly: false - }, options); + readOnly: false, + ...options + }; this.parent = this.options.transaction; @@ -53,22 +51,20 @@ class Transaction { * * @returns {Promise} */ - commit() { + async commit() { if (this.finished) { - return Promise.reject(new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`)); + throw new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`); } - return this - .sequelize - .getQueryInterface() - .commitTransaction(this, this.options) - .finally(() => { - this.finished = 'commit'; - if (!this.parent) { - return this.cleanup(); - } - return null; - }).tap(() => this.hooks.run('afterCommit', this)); + try { + return await this.sequelize.getQueryInterface().commitTransaction(this, this.options); + } finally { + this.finished = 'commit'; + this.cleanup(); + for (const hook of this._afterCommitHooks) { + await hook.apply(this, [this]); + } + } } /** @@ -76,30 +72,39 @@ class Transaction { * * @returns {Promise} */ - rollback() { + async rollback() { if (this.finished) { - return Promise.reject(new Error(`Transaction cannot be rolled back because it has been finished with state: ${this.finished}`)); + throw new Error(`Transaction cannot be rolled back because it has been finished with state: ${this.finished}`); } if (!this.connection) { - return Promise.reject(new Error('Transaction cannot be rolled back because it never started')); + throw new Error('Transaction cannot be rolled back because it never started'); } - return this - .sequelize - .getQueryInterface() - .rollbackTransaction(this, this.options) - .finally(() => { - if (!this.parent) { - return this.cleanup(); - } - return this; - }); + try { + return await this + .sequelize + .getQueryInterface() + .rollbackTransaction(this, this.options); + } finally { + this.cleanup(); + } } - prepareEnvironment() { + /** + * Called to acquire a connection to use and set the correct options on the connection. + * We should ensure all of the environment that's set up is cleaned up in `cleanup()` below. + * + * @param {boolean} useCLS Defaults to true: Use CLS (Continuation Local Storage) with Sequelize. With CLS, all queries within the transaction callback will automatically receive the transaction object. + * @returns {Promise} + */ + async prepareEnvironment(useCLS) { let connectionPromise; + if (useCLS === undefined) { + useCLS = true; + } + if (this.parent) { connectionPromise = Promise.resolve(this.parent.connection); } else { @@ -110,50 +115,86 @@ class Transaction { connectionPromise = this.sequelize.connectionManager.getConnection(acquireOptions); } - return connectionPromise - .then(connection => { - this.connection = connection; - this.connection.uuid = this.id; - }) - .then(() => { - return this.begin() - .then(() => this.setDeferrable()) - .then(() => this.setIsolationLevel()) - .catch(setupErr => this.rollback().finally(() => { - throw setupErr; - })); - }); - } + let result; + const connection = await connectionPromise; + this.connection = connection; + this.connection.uuid = this.id; + + try { + await this.begin(); + result = await this.setDeferrable(); + } catch (setupErr) { + try { + result = await this.rollback(); + } finally { + throw setupErr; // eslint-disable-line no-unsafe-finally + } + } + + if (useCLS && this.sequelize.constructor._cls) { + this.sequelize.constructor._cls.set('transaction', this); + } - begin() { - return this - .sequelize - .getQueryInterface() - .startTransaction(this, this.options); + return result; } - setDeferrable() { + async setDeferrable() { if (this.options.deferrable) { - return this + return await this .sequelize .getQueryInterface() .deferConstraints(this, this.options); } } - setIsolationLevel() { - return this - .sequelize - .getQueryInterface() - .setIsolationLevel(this, this.options.isolationLevel, this.options); + async begin() { + const queryInterface = this.sequelize.getQueryInterface(); + + if ( this.sequelize.dialect.supports.settingIsolationLevelDuringTransaction ) { + await queryInterface.startTransaction(this, this.options); + return queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); + } + + await queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options); + + return queryInterface.startTransaction(this, this.options); } cleanup() { + // Don't release the connection if there's a parent transaction or + // if we've already cleaned up + if (this.parent || this.connection.uuid === undefined) return; + + this._clearCls(); const res = this.sequelize.connectionManager.releaseConnection(this.connection); this.connection.uuid = undefined; return res; } + _clearCls() { + const cls = this.sequelize.constructor._cls; + + if (cls) { + if (cls.get('transaction') === this) { + cls.set('transaction', null); + } + } + } + + /** + * A hook that is run after a transaction is committed + * + * @param {Function} fn A callback function that is called with the committed transaction + * @name afterCommit + * @memberof Sequelize.Transaction + */ + afterCommit(fn) { + if (!fn || typeof fn !== 'function') { + throw new Error('"fn" must be a function'); + } + this._afterCommitHooks.push(fn); + } + /** * Types can be set per-transaction by passing `options.type` to `sequelize.transaction`. * Default to `DEFERRED` but you can override the default type by passing `options.transactionType` in `new Sequelize`. @@ -162,13 +203,14 @@ class Transaction { * Pass in the desired level as the first argument: * * @example - * return sequelize.transaction({type: Sequelize.Transaction.TYPES.EXCLUSIVE}, transaction => { - * // your transactions - * }).then(result => { + * try { + * await sequelize.transaction({ type: Sequelize.Transaction.TYPES.EXCLUSIVE }, transaction => { + * // your transactions + * }); * // transaction has been committed. Do something after the commit if required. - * }).catch(err => { + * } catch(err) { * // do something with the err. - * }); + * } * * @property DEFERRED * @property IMMEDIATE @@ -189,13 +231,14 @@ class Transaction { * Pass in the desired level as the first argument: * * @example - * return sequelize.transaction({isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE}, transaction => { - * // your transactions - * }).then(result => { + * try { + * const result = await sequelize.transaction({isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE}, transaction => { + * // your transactions + * }); * // transaction has been committed. Do something after the commit if required. - * }).catch(err => { + * } catch(err) { * // do something with the err. - * }); + * } * * @property READ_UNCOMMITTED * @property READ_COMMITTED diff --git a/lib/utils.js b/lib/utils.js index e5442b6e0bbe..65b4c75dcf93 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,17 +3,16 @@ const DataTypes = require('./data-types'); const SqlString = require('./sql-string'); const _ = require('lodash'); -const uuidv1 = require('uuid/v1'); -const uuidv4 = require('uuid/v4'); -const Promise = require('./promise'); +const baseIsNative = require('lodash/_baseIsNative'); +const uuidv1 = require('uuid').v1; +const uuidv4 = require('uuid').v4; const operators = require('./operators'); const operatorsSet = new Set(Object.values(operators)); let inflection = require('inflection'); -exports.classToInvokable = require('./utils/classToInvokable').classToInvokable; - -exports.Promise = Promise; +exports.classToInvokable = require('./utils/class-to-invokable').classToInvokable; +exports.joinSQLFragments = require('./utils/join-sql-fragments').joinSQLFragments; function useInflection(_inflection) { inflection = _inflection; @@ -50,9 +49,14 @@ exports.isPrimitive = isPrimitive; // Same concept as _.merge, but don't overwrite properties that have already been assigned function mergeDefaults(a, b) { - return _.mergeWith(a, b, objectValue => { + return _.mergeWith(a, b, (objectValue, sourceValue) => { // If it's an object, let _ handle it this time, we will be called again for each property if (!_.isPlainObject(objectValue) && objectValue !== undefined) { + // _.isNative includes a check for core-js and throws an error if present. + // Depending on _baseIsNative bypasses the core-js check. + if (_.isFunction(objectValue) && baseIsNative(objectValue)) { + return sourceValue || objectValue; + } return objectValue; } }); @@ -124,7 +128,8 @@ function formatNamedParameters(sql, parameters, dialect) { exports.formatNamedParameters = formatNamedParameters; function cloneDeep(obj, onlyPlain) { - return _.cloneDeepWith(obj || {}, elem => { + obj = obj || {}; + return _.cloneDeepWith(obj, elem => { // Do not try to customize cloning of arrays or POJOs if (Array.isArray(elem) || _.isPlainObject(elem)) { return undefined; @@ -181,6 +186,7 @@ exports.mapOptionFieldNames = mapOptionFieldNames; function mapWhereFieldNames(attributes, Model) { if (attributes) { + attributes = cloneDeep(attributes); getComplexKeys(attributes).forEach(attribute => { const rawAttribute = Model.rawAttributes[attribute]; @@ -265,7 +271,7 @@ function toDefaultValue(value, dialect) { return now(dialect); } if (Array.isArray(value)) { - return value.slice(0); + return value.slice(); } if (_.isPlainObject(value)) { return { ...value }; @@ -295,9 +301,10 @@ function defaultValueSchemable(value) { } exports.defaultValueSchemable = defaultValueSchemable; -function removeNullValuesFromHash(hash, omitNull, options = {}) { +function removeNullValuesFromHash(hash, omitNull, options) { let result = hash; + options = options || {}; options.allowNull = options.allowNull || []; if (omitNull) { @@ -316,17 +323,6 @@ function removeNullValuesFromHash(hash, omitNull, options = {}) { } exports.removeNullValuesFromHash = removeNullValuesFromHash; -function stack() { - const orig = Error.prepareStackTrace; - Error.prepareStackTrace = (_, stack) => stack; - const err = new Error(); - Error.captureStackTrace(err, stack); - const errStack = err.stack; - Error.prepareStackTrace = orig; - return errStack; -} -exports.stack = stack; - const dialects = new Set(['mariadb', 'mysql', 'postgres', 'sqlite', 'mssql']); function now(dialect) { @@ -408,6 +404,7 @@ exports.flattenObjectDeep = flattenObjectDeep; /** * Utility functions for representing SQL functions, and columns that should be escaped. * Please do not use these functions directly, use Sequelize.fn and Sequelize.col instead. + * * @private */ class SequelizeMethod {} @@ -490,7 +487,7 @@ exports.Where = Where; * getOperators * * @param {object} obj - * @returns {Array} All operators properties of obj + * @returns {Array} All operators properties of obj * @private */ function getOperators(obj) { @@ -502,7 +499,7 @@ exports.getOperators = getOperators; * getComplexKeys * * @param {object} obj - * @returns {Array} All keys including operators + * @returns {Array} All keys including operators * @private */ function getComplexKeys(obj) { diff --git a/lib/utils/classToInvokable.js b/lib/utils/class-to-invokable.js similarity index 100% rename from lib/utils/classToInvokable.js rename to lib/utils/class-to-invokable.js diff --git a/lib/utils/deprecations.js b/lib/utils/deprecations.js index 6d0cb467ecd7..ac9d62216c71 100644 --- a/lib/utils/deprecations.js +++ b/lib/utils/deprecations.js @@ -6,5 +6,7 @@ const noop = () => {}; exports.noRawAttributes = deprecate(noop, 'Use sequelize.fn / sequelize.literal to construct attributes', 'SEQUELIZE0001'); exports.noTrueLogging = deprecate(noop, 'The logging-option should be either a function or false. Default: console.log', 'SEQUELIZE0002'); -exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://docs.sequelizejs.com/manual/querying.html#operators', 'SEQUELIZE0003'); +exports.noStringOperators = deprecate(noop, 'String based operators are deprecated. Please use Symbol based operators for better security, read more at https://sequelize.org/master/manual/querying.html#operators', 'SEQUELIZE0003'); +exports.noBoolOperatorAliases = deprecate(noop, 'A boolean value was passed to options.operatorsAliases. This is a no-op with v5 and should be removed.', 'SEQUELIZE0004'); exports.noDoubleNestedGroup = deprecate(noop, 'Passing a double nested nested array to `group` is unsupported and will be removed in v6.', 'SEQUELIZE0005'); +exports.unsupportedEngine = deprecate(noop, 'This database engine version is not supported, please update your database server. More information https://github.com/sequelize/sequelize/blob/main/ENGINE.md', 'SEQUELIZE0006'); diff --git a/lib/utils/join-sql-fragments.js b/lib/utils/join-sql-fragments.js new file mode 100644 index 000000000000..a53f61b8702f --- /dev/null +++ b/lib/utils/join-sql-fragments.js @@ -0,0 +1,83 @@ +'use strict'; + +function doesNotWantLeadingSpace(str) { + return /^[;,)]/.test(str); +} +function doesNotWantTrailingSpace(str) { + return /\($/.test(str); +} + +/** + * Joins an array of strings with a single space between them, + * except for: + * + * - Strings starting with ';', ',' and ')', which do not get a leading space. + * - Strings ending with '(', which do not get a trailing space. + * + * @param {string[]} parts + * @returns {string} + * @private + */ +function singleSpaceJoinHelper(parts) { + return parts.reduce(({ skipNextLeadingSpace, result }, part) => { + if (skipNextLeadingSpace || doesNotWantLeadingSpace(part)) { + result += part.trim(); + } else { + result += ` ${part.trim()}`; + } + return { + skipNextLeadingSpace: doesNotWantTrailingSpace(part), + result + }; + }, { + skipNextLeadingSpace: true, + result: '' + }).result; +} + +/** + * Joins an array with a single space, auto trimming when needed. + * + * Certain elements do not get leading/trailing spaces. + * + * @param {any[]} array The array to be joined. Falsy values are skipped. If an + * element is another array, this function will be called recursively on that array. + * Otherwise, if a non-string, non-falsy value is present, a TypeError will be thrown. + * + * @returns {string} The joined string. + * + * @private + */ +function joinSQLFragments(array) { + if (array.length === 0) return ''; + + // Skip falsy fragments + array = array.filter(x => x); + + // Resolve recursive calls + array = array.map(fragment => { + if (Array.isArray(fragment)) { + return joinSQLFragments(fragment); + } + return fragment; + }); + + // Ensure strings + for (const fragment of array) { + if (fragment && typeof fragment !== 'string') { + const error = new TypeError(`Tried to construct a SQL string with a non-string, non-falsy fragment (${fragment}).`); + error.args = array; + error.fragment = fragment; + throw error; + } + } + + // Trim fragments + array = array.map(x => x.trim()); + + // Skip full-whitespace fragments (empty after the above trim) + array = array.filter(x => x !== ''); + + return singleSpaceJoinHelper(array); +} +exports.joinSQLFragments = joinSQLFragments; diff --git a/lib/utils/logger.js b/lib/utils/logger.js index 13c160decfba..35944c806fec 100644 --- a/lib/utils/logger.js +++ b/lib/utils/logger.js @@ -14,10 +14,11 @@ const util = require('util'); class Logger { constructor(config) { - this.config = Object.assign({ + this.config = { context: 'sequelize', - debug: true - }, config); + debug: true, + ...config + }; } warn(message) { @@ -26,7 +27,7 @@ class Logger { } inspect(value) { - return util.inspect(value, false, 3); + return util.inspect(value, false, 1); } debugContext(name) { diff --git a/package.json b/package.json index ac0ae3ab9474..18cd5fce87a6 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,8 @@ "name": "sequelize", "description": "Multi dialect ORM for Node.JS", "version": "0.0.0-development", - "author": "Sascha Depold ", - "contributors": [ - "Sascha Depold ", - "Jan Aagaard Meier ", - "Daniel Durante ", - "Mick Hansen ", - "Sushant Dhiman " + "maintainers": [ + "Pedro Augusto de Paula Barbosa " ], "repository": { "type": "git", @@ -21,69 +16,102 @@ "main": "index.js", "types": "types", "engines": { - "node": ">=6.0.0" + "node": ">=10.0.0" }, "files": [ "lib", "types/index.d.ts", - "types/lib" + "types/lib", + "types/type-helpers" ], "license": "MIT", "dependencies": { - "bluebird": "^3.7.0", "debug": "^4.1.1", "dottie": "^2.0.0", - "inflection": "1.12.0", - "lodash": "^4.17.15", - "moment": "^2.24.0", - "moment-timezone": "^0.5.26", + "inflection": "1.13.1", + "lodash": "^4.17.20", + "moment": "^2.26.0", + "moment-timezone": "^0.5.31", + "pg-connection-string": "^2.5.0", "retry-as-promised": "^3.2.0", - "semver": "^6.3.0", - "sequelize-pool": "^2.3.0", + "semver": "^7.3.2", + "sequelize-pool": "^6.0.0", "toposort-class": "^1.0.1", - "uuid": "^3.3.3", - "validator": "^10.11.0", - "wkx": "^0.4.8" + "uuid": "^8.1.0", + "validator": "^13.7.0", + "wkx": "^0.5.0" }, "devDependencies": { - "@commitlint/cli": "^8.2.0", - "@commitlint/config-angular": "^8.2.0", - "@types/bluebird": "^3.5.26", - "@types/node": "^12.7.8", - "@types/validator": "^10.11.3", - "big-integer": "^1.6.45", + "@commitlint/cli": "^11.0.0", + "@commitlint/config-angular": "^11.0.0", + "@types/node": "^12.12.42", + "@types/validator": "^13.1.4", + "acorn": "^8.0.4", + "axios": ">=0.21.2", "chai": "^4.x", "chai-as-promised": "^7.x", - "chai-datetime": "^1.x", - "chai-spies": "^1.x", - "cross-env": "^6.0.3", - "env-cmd": "^10.0.1", + "chai-datetime": "^1.6.0", + "cheerio": "^1.0.0-rc.3", + "cls-hooked": "^4.2.2", + "cross-env": "^7.0.2", + "delay": "^4.3.0", "esdoc": "^1.1.0", + "esdoc-ecmascript-proposal-plugin": "^1.0.0", "esdoc-inject-style-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0", - "eslint": "^6.4.0", - "eslint-plugin-jsdoc": "^8.7.0", - "eslint-plugin-mocha": "^6.1.1", - "fs-jetpack": "^2.2.2", - "husky": "^3.0.8", - "js-combinatorics": "^0.5.4", + "eslint": "^6.8.0", + "eslint-plugin-jsdoc": "^20.4.0", + "eslint-plugin-mocha": "^6.2.2", + "expect-type": "^0.11.0", + "fs-jetpack": "^4.1.0", + "husky": "^4.2.5", + "js-combinatorics": "^0.5.5", "lcov-result-merger": "^3.0.0", - "lint-staged": "^9.4.2", - "mariadb": "^2.1.1", - "markdownlint-cli": "^0.18.0", - "mocha": "^6.2.1", - "mysql2": "^1.7.0", - "nyc": "^14.1.1", - "pg": "^7.12.1", + "lint-staged": "^10.2.6", + "mariadb": "^2.3.1", + "markdownlint-cli": "^0.26.0", + "marked": "^1.1.0", + "mocha": "^7.1.2", + "mysql2": "^2.1.0", + "nth-check": ">=2.0.1", + "nyc": "^15.0.0", + "p-map": "^4.0.0", + "p-props": "^4.0.0", + "p-settle": "^4.1.1", + "p-timeout": "^4.0.0", + "path-parse": ">=1.0.7", + "pg": "^8.2.1", "pg-hstore": "^2.x", - "pg-types": "^2.2.0", - "rimraf": "^3.0.0", - "semantic-release": "^15.13.24", - "sinon": "^7.5.0", + "rimraf": "^3.0.2", + "semantic-release": "^17.3.0", + "semantic-release-fail-on-major-bump": "^1.0.0", + "semver-regex": ">=3.1.3", + "sinon": "^9.0.2", "sinon-chai": "^3.3.0", - "sqlite3": "^4.1.0", - "tedious": "6.0.0", - "typescript": "^3.6.3" + "sqlite3": "^4.2.0", + "tar": ">=4.4.18", + "tedious": "8.3.0", + "typescript": "^4.1.3" + }, + "peerDependenciesMeta": { + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } }, "keywords": [ "mysql", @@ -91,18 +119,39 @@ "sqlite", "postgresql", "postgres", + "pg", "mssql", + "sql", + "sqlserver", "orm", "nodejs", - "object relational mapper" + "object relational mapper", + "database", + "db" ], - "options": { - "env_cmd": "./test/config/.docker.env" - }, "commitlint": { "extends": [ "@commitlint/config-angular" - ] + ], + "rules": { + "type-enum": [ + 2, + "always", + [ + "build", + "ci", + "docs", + "feat", + "fix", + "perf", + "refactor", + "revert", + "style", + "test", + "meta" + ] + ] + } }, "lint-staged": { "*.js": "eslint" @@ -113,52 +162,79 @@ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, + "release": { + "plugins": [ + "@semantic-release/commit-analyzer", + "semantic-release-fail-on-major-bump", + "@semantic-release/release-notes-generator", + "@semantic-release/npm", + "@semantic-release/github" + ], + "branches": [ + "v6" + ] + }, + "publishConfig": { + "tag": "latest" + }, "scripts": { - "lint": "eslint lib test --quiet", + "----------------------------------------- static analysis -----------------------------------------": "", + "lint": "eslint lib test --quiet --fix", "lint-docs": "markdownlint docs", + "test-typings": "tsc -b types/tsconfig.json && tsc -b types/test/tsconfig.json", + "----------------------------------------- documentation -------------------------------------------": "", + "docs": "rimraf esdoc && esdoc -c docs/esdoc-config.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER && node docs/run-docs-transforms.js && node docs/redirects/create-redirects.js && rimraf esdoc/file esdoc/source.html", + "----------------------------------------- tests ---------------------------------------------------": "", + "test-unit": "mocha \"test/unit/**/*.test.js\"", + "test-integration": "mocha \"test/integration/**/*.test.js\"", + "teaser": "node test/teaser.js", "test": "npm run teaser && npm run test-unit && npm run test-integration", - "test-docker": "npm run test-docker-unit && npm run test-docker-integration", - "test-docker-unit": "npm run test-unit", - "test-docker-integration": "env-cmd $npm_package_options_env_cmd npm run test-integration", - "docs": "esdoc -c docs/esdoc-config.js && node docs/run-docs-transforms.js && cp docs/favicon.ico esdoc/favicon.ico && cp docs/ROUTER.txt esdoc/ROUTER", - "teaser": "node scripts/teaser", - "test-unit": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.js\"", + "----------------------------------------- coverage ------------------------------------------------": "", + "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", + "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", + "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", + "merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"", + "----------------------------------------- local test dbs ------------------------------------------": "", + "start-mariadb": "bash dev/mariadb/10.3/start.sh", + "start-mysql": "bash dev/mysql/5.7/start.sh", + "start-mysql-8": "bash dev/mysql/8.0/start.sh", + "start-postgres": "bash dev/postgres/10/start.sh", + "start-mssql": "bash dev/mssql/2019/start.sh", + "stop-mariadb": "bash dev/mariadb/10.3/stop.sh", + "stop-mysql": "bash dev/mysql/5.7/stop.sh", + "stop-postgres": "bash dev/postgres/10/stop.sh", + "stop-mssql": "bash dev/mssql/2019/stop.sh", + "restart-mariadb": "npm run start-mariadb", + "restart-mysql": "npm run start-mysql", + "restart-postgres": "npm run start-postgres", + "restart-mssql": "npm run start-mssql", + "----------------------------------------- local tests ---------------------------------------------": "", "test-unit-mariadb": "cross-env DIALECT=mariadb npm run test-unit", "test-unit-mysql": "cross-env DIALECT=mysql npm run test-unit", "test-unit-postgres": "cross-env DIALECT=postgres npm run test-unit", "test-unit-postgres-native": "cross-env DIALECT=postgres-native npm run test-unit", "test-unit-sqlite": "cross-env DIALECT=sqlite npm run test-unit", "test-unit-mssql": "cross-env DIALECT=mssql npm run test-unit", - "test-unit-all": "npm run test-unit-mariadb && npm run test-unit-mysql && npm run test-unit-postgres && npm run test-unit-postgres-native && npm run test-unit-mssql && npm run test-unit-sqlite", - "test-integration": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --exit --check-leaks --colors -t 30000 --reporter spec \"test/integration/**/*.test.js\"", "test-integration-mariadb": "cross-env DIALECT=mariadb npm run test-integration", "test-integration-mysql": "cross-env DIALECT=mysql npm run test-integration", "test-integration-postgres": "cross-env DIALECT=postgres npm run test-integration", "test-integration-postgres-native": "cross-env DIALECT=postgres-native npm run test-integration", "test-integration-sqlite": "cross-env DIALECT=sqlite npm run test-integration", "test-integration-mssql": "cross-env DIALECT=mssql npm run test-integration", - "test-integration-all": "npm run test-integration-mariadb && npm run test-integration-mysql && npm run test-integration-postgres && npm run test-integration-postgres-native && npm run test-integration-mssql && npm run test-integration-sqlite", "test-mariadb": "cross-env DIALECT=mariadb npm test", "test-mysql": "cross-env DIALECT=mysql npm test", "test-sqlite": "cross-env DIALECT=sqlite npm test", "test-postgres": "cross-env DIALECT=postgres npm test", - "test-pgsql": "npm run test-postgres", "test-postgres-native": "cross-env DIALECT=postgres-native npm test", - "test-postgresn": "npm run test-postgres-native", "test-mssql": "cross-env DIALECT=mssql npm test", - "test-all": "npm run test-mariadb && npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql", - "test-typings": "tsc -b types/tsconfig.json && tsc -b types/test/tsconfig.json", - "cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage", - "cover-integration": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha --require scripts/mocha-bootload -t 30000 --exit \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"", - "cover-unit": "cross-env COVERAGE=true nyc --reporter=lcovonly mocha --require scripts/mocha-bootload -t 30000 --exit \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"", - "merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"", - "sscce": "env-cmd $npm_package_options_env_cmd node sscce.js", - "sscce-mariadb": "cross-env DIALECT=mariadb npm run sscce", - "sscce-mysql": "cross-env DIALECT=mysql npm run sscce", - "sscce-postgres": "cross-env DIALECT=postgres npm run sscce", - "sscce-sqlite": "cross-env DIALECT=sqlite npm run sscce", - "sscce-mssql": "cross-env DIALECT=mssql npm run sscce", - "setup-mssql": "env-cmd $npm_package_options_env_cmd ./scripts/setup-mssql", - "semantic-release": "semantic-release" + "----------------------------------------- development ---------------------------------------------": "", + "sscce": "node sscce.js", + "sscce-mariadb": "cross-env DIALECT=mariadb node sscce.js", + "sscce-mysql": "cross-env DIALECT=mysql node sscce.js", + "sscce-postgres": "cross-env DIALECT=postgres node sscce.js", + "sscce-postgres-native": "cross-env DIALECT=postgres-native node sscce.js", + "sscce-sqlite": "cross-env DIALECT=sqlite node sscce.js", + "sscce-mssql": "cross-env DIALECT=mssql node sscce.js", + "---------------------------------------------------------------------------------------------------": "" } } diff --git a/scripts/appveyor-setup.ps1 b/scripts/appveyor-setup.ps1 deleted file mode 100644 index 412a7ef57823..000000000000 --- a/scripts/appveyor-setup.ps1 +++ /dev/null @@ -1,50 +0,0 @@ - -Set-Service sqlbrowser -StartupType auto -Start-Service sqlbrowser - -[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null -[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | Out-Null - -$serverName = $env:COMPUTERNAME -$instanceName = 'SQL2017' -$smo = 'Microsoft.SqlServer.Management.Smo.' -$wmi = new-object ($smo + 'Wmi.ManagedComputer') - -# Enable TCP/IP -$uri = "ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']" -$Tcp = $wmi.GetSmoObject($uri) -$Tcp.IsEnabled = $true -$TCP.alter() - -Start-Service "MSSQL`$$instanceName" - -$ipall = $wmi.GetSmoObject("ManagedComputer[@Name='$serverName']/ServerInstance[@Name='$instanceName']/ServerProtocol[@Name='Tcp']/IPAddress[@Name='IPAll']") -$port = $ipall.IPAddressProperties.Item("TcpPort").Value - -$config = @{ - host = "localhost" - username = "sa" - password = "Password12!" - port = $port - database = "sequelize_test" - dialectOptions = @{ - options = @{ - requestTimeout = 25000 - cryptoCredentialsDetails = @{ - ciphers = "RC4-MD5" - } - } - } - pool = @{ - max = 5 - idle = 3000 - } -} - -$json = $config | ConvertTo-Json -Depth 3 - -# Create sequelize_test database -sqlcmd -S "(local)" -U "sa" -P "Password12!" -d "master" -Q "CREATE DATABASE [sequelize_test]; ALTER DATABASE [sequelize_test] SET READ_COMMITTED_SNAPSHOT ON;" - -# cannot use Out-File because it outputs a BOM -[IO.File]::WriteAllLines((Join-Path $pwd "test\config\mssql.json"), $json) diff --git a/scripts/mocha-bootload b/scripts/mocha-bootload deleted file mode 100644 index 6cd71e1c672b..000000000000 --- a/scripts/mocha-bootload +++ /dev/null @@ -1 +0,0 @@ -require('any-promise/register/bluebird'); \ No newline at end of file diff --git a/scripts/setup-mssql b/scripts/setup-mssql deleted file mode 100755 index 477ed24d490c..000000000000 --- a/scripts/setup-mssql +++ /dev/null @@ -1 +0,0 @@ -docker exec mssql /bin/bash -c '/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U "sa" -d "master" -P '"'$SEQ_MSSQL_PW'"' -Q "DROP DATABASE ['$SEQ_MSSQL_DB']; CREATE DATABASE ['$SEQ_MSSQL_DB']; ALTER DATABASE ['$SEQ_MSSQL_DB'] SET READ_COMMITTED_SNAPSHOT ON;"' \ No newline at end of file diff --git a/sscce.js b/sscce.js new file mode 100644 index 000000000000..0c68f7d88102 --- /dev/null +++ b/sscce.js @@ -0,0 +1,31 @@ +'use strict'; + +// See https://github.com/papb/sequelize-sscce as another option for running SSCCEs. + +const { createSequelizeInstance } = require('./dev/sscce-helpers'); +const { Model, DataTypes } = require('.'); + +const { expect } = require('chai'); // You can use `expect` on your SSCCE! + +const sequelize = createSequelizeInstance({ benchmark: true }); + +class User extends Model {} +User.init({ + username: DataTypes.STRING, + birthday: DataTypes.DATE +}, { sequelize, modelName: 'user' }); + +(async () => { + await sequelize.sync({ force: true }); + + const jane = await User.create({ + username: 'janedoe', + birthday: new Date(1980, 6, 20) + }); + + console.log('\nJane:', jane.toJSON()); + + await sequelize.close(); + + expect(jane.username).to.equal('janedoe'); +})(); diff --git a/sscce_template.js b/sscce_template.js deleted file mode 100644 index cc4bc04e383e..000000000000 --- a/sscce_template.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -/* - * Copy this file to ./sscce.js - * Add code from issue - * npm run sscce-{dialect} - */ - -const Sequelize = require('./index'); -const sequelize = require('./test/support').createSequelizeInstance(); diff --git a/test/config/config.js b/test/config/config.js index 0ad16363b8f5..b15453ab3db6 100644 --- a/test/config/config.js +++ b/test/config/config.js @@ -1,37 +1,18 @@ 'use strict'; -const fs = require('fs'); -let mssqlConfig; -try { - mssqlConfig = JSON.parse(fs.readFileSync(`${__dirname}/mssql.json`, 'utf8')); -} catch (e) { - // ignore -} -const env = process.env; +const { env } = process; module.exports = { - username: env.SEQ_USER || 'root', - password: env.SEQ_PW || null, - database: env.SEQ_DB || 'sequelize_test', - host: env.SEQ_HOST || '127.0.0.1', - pool: { - max: env.SEQ_POOL_MAX || 5, - idle: env.SEQ_POOL_IDLE || 30000 - }, - - rand() { - return parseInt(Math.random() * 999, 10); - }, - - mssql: mssqlConfig || { + mssql: { + host: env.SEQ_MSSQL_HOST || env.SEQ_HOST || 'localhost', + username: env.SEQ_MSSQL_USER || env.SEQ_USER || 'SA', + password: env.SEQ_MSSQL_PW || env.SEQ_PW || 'Password12!', + port: env.SEQ_MSSQL_PORT || env.SEQ_PORT || 22019, database: env.SEQ_MSSQL_DB || env.SEQ_DB || 'sequelize_test', - username: env.SEQ_MSSQL_USER || env.SEQ_USER || 'sequelize', - password: env.SEQ_MSSQL_PW || env.SEQ_PW || 'nEGkLma26gXVHFUAHJxcmsrK', - host: env.SEQ_MSSQL_HOST || env.SEQ_HOST || '127.0.0.1', - port: env.SEQ_MSSQL_PORT || env.SEQ_PORT || 1433, dialectOptions: { options: { - requestTimeout: 60000 + encrypt: false, + requestTimeout: 25000 } }, pool: { @@ -40,13 +21,12 @@ module.exports = { } }, - //make idle time small so that tests exit promptly mysql: { database: env.SEQ_MYSQL_DB || env.SEQ_DB || 'sequelize_test', - username: env.SEQ_MYSQL_USER || env.SEQ_USER || 'root', - password: env.SEQ_MYSQL_PW || env.SEQ_PW || null, + username: env.SEQ_MYSQL_USER || env.SEQ_USER || 'sequelize_test', + password: env.SEQ_MYSQL_PW || env.SEQ_PW || 'sequelize_test', host: env.MYSQL_PORT_3306_TCP_ADDR || env.SEQ_MYSQL_HOST || env.SEQ_HOST || '127.0.0.1', - port: env.MYSQL_PORT_3306_TCP_PORT || env.SEQ_MYSQL_PORT || env.SEQ_PORT || 3306, + port: env.MYSQL_PORT_3306_TCP_PORT || env.SEQ_MYSQL_PORT || env.SEQ_PORT || 20057, pool: { max: env.SEQ_MYSQL_POOL_MAX || env.SEQ_POOL_MAX || 5, idle: env.SEQ_MYSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 @@ -55,10 +35,10 @@ module.exports = { mariadb: { database: env.SEQ_MARIADB_DB || env.SEQ_DB || 'sequelize_test', - username: env.SEQ_MARIADB_USER || env.SEQ_USER || 'root', - password: env.SEQ_MARIADB_PW || env.SEQ_PW || null, + username: env.SEQ_MARIADB_USER || env.SEQ_USER || 'sequelize_test', + password: env.SEQ_MARIADB_PW || env.SEQ_PW || 'sequelize_test', host: env.MARIADB_PORT_3306_TCP_ADDR || env.SEQ_MARIADB_HOST || env.SEQ_HOST || '127.0.0.1', - port: env.MARIADB_PORT_3306_TCP_PORT || env.SEQ_MARIADB_PORT || env.SEQ_PORT || 3306, + port: env.MARIADB_PORT_3306_TCP_PORT || env.SEQ_MARIADB_PORT || env.SEQ_PORT || 21103, pool: { max: env.SEQ_MARIADB_POOL_MAX || env.SEQ_POOL_MAX || 5, idle: env.SEQ_MARIADB_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 @@ -69,10 +49,10 @@ module.exports = { postgres: { database: env.SEQ_PG_DB || env.SEQ_DB || 'sequelize_test', - username: env.SEQ_PG_USER || env.SEQ_USER || 'postgres', - password: env.SEQ_PG_PW || env.SEQ_PW || 'postgres', + username: env.SEQ_PG_USER || env.SEQ_USER || 'sequelize_test', + password: env.SEQ_PG_PW || env.SEQ_PW || 'sequelize_test', host: env.POSTGRES_PORT_5432_TCP_ADDR || env.SEQ_PG_HOST || env.SEQ_HOST || '127.0.0.1', - port: env.POSTGRES_PORT_5432_TCP_PORT || env.SEQ_PG_PORT || env.SEQ_PORT || 5432, + port: env.POSTGRES_PORT_5432_TCP_PORT || env.SEQ_PG_PORT || env.SEQ_PORT || 23010, pool: { max: env.SEQ_PG_POOL_MAX || env.SEQ_POOL_MAX || 5, idle: env.SEQ_PG_POOL_IDLE || env.SEQ_POOL_IDLE || 3000 diff --git a/test/integration/associations/alias.test.js b/test/integration/associations/alias.test.js index e838d048c134..b53067d25958 100644 --- a/test/integration/associations/alias.test.js +++ b/test/integration/associations/alias.test.js @@ -2,83 +2,71 @@ const chai = require('chai'), expect = chai.expect, - Support = require('../support'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise; + Support = require('../support'); describe(Support.getTestDialectTeaser('Alias'), () => { - it('should uppercase the first letter in alias getter, but not in eager loading', function() { + it('should uppercase the first letter in alias getter, but not in eager loading', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: 'assignments', foreignKey: 'userId' }); Task.belongsTo(User, { as: 'owner', foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getAssignments).to.be.ok; - - return Task.create({ id: 1, userId: 1 }); - }).then(task => { - expect(task.getOwner).to.be.ok; - - return Promise.all([ - User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'assignments' }] }), - Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'owner' }] }) - ]); - }).then(([user, task]) => { - expect(user.assignments).to.be.ok; - expect(task.owner).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getAssignments).to.be.ok; + + const task0 = await Task.create({ id: 1, userId: 1 }); + expect(task0.getOwner).to.be.ok; + + const [user, task] = await Promise.all([ + User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'assignments' }] }), + Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'owner' }] }) + ]); + + expect(user.assignments).to.be.ok; + expect(task.owner).to.be.ok; }); - it('shouldnt touch the passed alias', function() { + it('shouldnt touch the passed alias', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: 'ASSIGNMENTS', foreignKey: 'userId' }); Task.belongsTo(User, { as: 'OWNER', foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getASSIGNMENTS).to.be.ok; - - return Task.create({ id: 1, userId: 1 }); - }).then(task => { - expect(task.getOWNER).to.be.ok; - - return Promise.all([ - User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'ASSIGNMENTS' }] }), - Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'OWNER' }] }) - ]); - }).then(([user, task]) => { - expect(user.ASSIGNMENTS).to.be.ok; - expect(task.OWNER).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getASSIGNMENTS).to.be.ok; + + const task0 = await Task.create({ id: 1, userId: 1 }); + expect(task0.getOWNER).to.be.ok; + + const [user, task] = await Promise.all([ + User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'ASSIGNMENTS' }] }), + Task.findOne({ where: { id: 1 }, include: [{ model: User, as: 'OWNER' }] }) + ]); + + expect(user.ASSIGNMENTS).to.be.ok; + expect(task.OWNER).to.be.ok; }); - it('should allow me to pass my own plural and singular forms to hasMany', function() { + it('should allow me to pass my own plural and singular forms to hasMany', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}); User.hasMany(Task, { as: { singular: 'task', plural: 'taskz' } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getTaskz).to.be.ok; - expect(user.addTask).to.be.ok; - expect(user.addTaskz).to.be.ok; - }).then(() => { - return User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'taskz' }] }); - }).then(user => { - expect(user.taskz).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getTaskz).to.be.ok; + expect(user0.addTask).to.be.ok; + expect(user0.addTaskz).to.be.ok; + const user = await User.findOne({ where: { id: 1 }, include: [{ model: Task, as: 'taskz' }] }); + expect(user.taskz).to.be.ok; }); - it('should allow me to define plural and singular forms on the model', function() { + it('should allow me to define plural and singular forms on the model', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}, { name: { @@ -89,16 +77,12 @@ describe(Support.getTestDialectTeaser('Alias'), () => { User.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 1 }); - }).then(user => { - expect(user.getAssignments).to.be.ok; - expect(user.addAssignment).to.be.ok; - expect(user.addAssignments).to.be.ok; - }).then(() => { - return User.findOne({ where: { id: 1 }, include: [Task] }); - }).then(user => { - expect(user.assignments).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create({ id: 1 }); + expect(user0.getAssignments).to.be.ok; + expect(user0.addAssignment).to.be.ok; + expect(user0.addAssignments).to.be.ok; + const user = await User.findOne({ where: { id: 1 }, include: [Task] }); + expect(user.assignments).to.be.ok; }); }); diff --git a/test/integration/associations/belongs-to-many.test.js b/test/integration/associations/belongs-to-many.test.js index 3e6c0c6ec4c1..03725fea1528 100644 --- a/test/integration/associations/belongs-to-many.test.js +++ b/test/integration/associations/belongs-to-many.test.js @@ -7,151 +7,135 @@ const chai = require('chai'), Sequelize = require('../../../index'), _ = require('lodash'), sinon = require('sinon'), - Promise = Sequelize.Promise, Op = Sequelize.Op, current = Support.sequelize, dialect = Support.getTestDialect(); describe(Support.getTestDialectTeaser('BelongsToMany'), () => { describe('getAssociations', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); this.User.belongsToMany(this.Task, { through: 'UserTasks' }); this.Task.belongsToMany(this.User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - this.tasks = [task1, task2]; - this.user = john; - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + this.tasks = [task1, task2]; + this.user = john; + + return john.setTasks([task1, task2]); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.belongsToMany(ctx.Label, { through: 'ArticleLabels' }); - ctx.Label.belongsToMany(ctx.Article, { through: 'ArticleLabels' }); - - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }), - ctx.sequelize.transaction() - ]); - }).then(([article, label, t]) => { - ctx.t = t; - return article.setLabels([label], { transaction: t }); - }).then(() => { - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return articles[0].getLabels(); - }).then(labels => { - expect(labels).to.have.length(0); - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return articles[0].getLabels({ transaction: ctx.t }); - }).then(labels => { - expect(labels).to.have.length(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.belongsToMany(Label, { through: 'ArticleLabels' }); + Label.belongsToMany(Article, { through: 'ArticleLabels' }); + + await sequelize.sync({ force: true }); + + const [article, label, t] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }), + sequelize.transaction() + ]); + + await article.setLabels([label], { transaction: t }); + const articles0 = await Article.findAll({ transaction: t }); + const labels0 = await articles0[0].getLabels(); + expect(labels0).to.have.length(0); + const articles = await Article.findAll({ transaction: t }); + const labels = await articles[0].getLabels({ transaction: t }); + expect(labels).to.have.length(1); + await t.rollback(); }); } - it('gets all associated objects with all fields', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks(); - }).then(tasks => { - Object.keys(tasks[0].rawAttributes).forEach(attr => { - expect(tasks[0]).to.have.property(attr); - }); + it('gets all associated objects with all fields', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + Object.keys(tasks[0].rawAttributes).forEach(attr => { + expect(tasks[0]).to.have.property(attr); }); }); - it('gets all associated objects when no options are passed', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - }); + it('gets all associated objects when no options are passed', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + expect(tasks).to.have.length(2); }); - it('only get objects that fulfill the options', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks({ - where: { - active: true - } - }); - }).then(tasks => { - expect(tasks).to.have.length(1); + it('only get objects that fulfill the options', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + + const tasks = await john.getTasks({ + where: { + active: true + } }); + + expect(tasks).to.have.length(1); }); - it('supports a where not in', function() { - return this.User.findOne({ + it('supports a where not in', async function() { + const john = await this.User.findOne({ where: { username: 'John' } - }).then(john => { - return john.getTasks({ - where: { - title: { - [Op.not]: ['Get rich'] - } + }); + + const tasks = await john.getTasks({ + where: { + title: { + [Op.not]: ['Get rich'] } - }); - }).then(tasks => { - expect(tasks).to.have.length(1); + } }); + + expect(tasks).to.have.length(1); }); - it('supports a where not in on the primary key', function() { - return this.User.findOne({ + it('supports a where not in on the primary key', async function() { + const john = await this.User.findOne({ where: { username: 'John' } - }).then(john => { - return john.getTasks({ - where: { - id: { - [Op.not]: [this.tasks[0].get('id')] - } + }); + + const tasks = await john.getTasks({ + where: { + id: { + [Op.not]: [this.tasks[0].get('id')] } - }); - }).then(tasks => { - expect(tasks).to.have.length(1); + } }); + + expect(tasks).to.have.length(1); }); - it('only gets objects that fulfill options with a formatted value', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks({ where: { active: true } }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + it('only gets objects that fulfill options with a formatted value', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks({ where: { active: true } }); + expect(tasks).to.have.length(1); }); - it('get associated objects with an eager load', function() { - return this.User.findOne({ where: { username: 'John' }, include: [this.Task] }).then(john => { - expect(john.Tasks).to.have.length(2); - }); + it('get associated objects with an eager load', async function() { + const john = await this.User.findOne({ where: { username: 'John' }, include: [this.Task] }); + expect(john.Tasks).to.have.length(2); }); - it('get associated objects with an eager load with conditions but not required', function() { + it('get associated objects with an eager load with conditions but not required', async function() { const Label = this.sequelize.define('Label', { 'title': DataTypes.STRING, 'isActive': DataTypes.BOOLEAN }), Task = this.Task, User = this.User; @@ -159,21 +143,21 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.hasMany(Label); Label.belongsTo(Task); - return Label.sync({ force: true }).then(() => { - return User.findOne({ - where: { username: 'John' }, - include: [ - { model: Task, required: false, include: [ - { model: Label, required: false, where: { isActive: true } } - ] } - ] - }); - }).then(john => { - expect(john.Tasks).to.have.length(2); + await Label.sync({ force: true }); + + const john = await User.findOne({ + where: { username: 'John' }, + include: [ + { model: Task, required: false, include: [ + { model: Label, required: false, where: { isActive: true } } + ] } + ] }); + + expect(john.Tasks).to.have.length(2); }); - it('should support schemas', function() { + it('should support schemas', async function() { const AcmeUser = this.sequelize.define('User', { username: DataTypes.STRING }).schema('acme', '_'), @@ -189,42 +173,32 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { AcmeUser.belongsToMany(AcmeProject, { through: AcmeProjectUsers }); AcmeProject.belongsToMany(AcmeUser, { through: AcmeProjectUsers }); - const ctx = {}; - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('acme'); - }).then(() => { - return Promise.all([ - AcmeUser.sync({ force: true }), - AcmeProject.sync({ force: true }) - ]); - }).then(() => { - return AcmeProjectUsers.sync({ force: true }); - }).then(() => { - return AcmeUser.create(); - }).then(u => { - ctx.u = u; - return AcmeProject.create(); - }).then(p => { - return ctx.u.addProject(p, { through: { status: 'active', data: 42 } }); - }).then(() => { - return ctx.u.getProjects(); - }).then(projects => { - expect(projects).to.have.length(1); - const project = projects[0]; - expect(project.ProjectUsers).to.be.ok; - expect(project.status).not.to.exist; - expect(project.ProjectUsers.status).to.equal('active'); - return this.sequelize.dropSchema('acme').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('acme'); - } - }); - }); - }); - }); - - it('supports custom primary keys and foreign keys', function() { + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('acme'); + + await Promise.all([ + AcmeUser.sync({ force: true }), + AcmeProject.sync({ force: true }) + ]); + + await AcmeProjectUsers.sync({ force: true }); + const u = await AcmeUser.create(); + const p = await AcmeProject.create(); + await u.addProject(p, { through: { status: 'active', data: 42 } }); + const projects = await u.getProjects(); + expect(projects).to.have.length(1); + const project = projects[0]; + expect(project.ProjectUsers).to.be.ok; + expect(project.status).not.to.exist; + expect(project.ProjectUsers.status).to.equal('active'); + await this.sequelize.dropSchema('acme'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('acme'); + } + }); + + it('supports custom primary keys and foreign keys', async function() { const User = this.sequelize.define('User', { 'id_user': { type: DataTypes.UUID, @@ -256,23 +230,18 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { as: 'groups', through: User_has_Group, foreignKey: 'id_user' }); Group.belongsToMany(User, { as: 'users', through: User_has_Group, foreignKey: 'id_group' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - Group.create() - ).then(([user, group]) => { - return user.addGroup(group); - }).then(() => { - return User.findOne({ - where: {} - }).then(user => { - return user.getGroups(); - }); - }); + await this.sequelize.sync({ force: true }); + const [user0, group] = await Promise.all([User.create(), Group.create()]); + await user0.addGroup(group); + + const user = await User.findOne({ + where: {} }); + + await user.getGroups(); }); - it('supports primary key attributes with different field and attribute names', function() { + it('supports primary key attributes with different field and attribute names', async function() { const User = this.sequelize.define('User', { userSecondId: { type: DataTypes.UUID, @@ -306,35 +275,27 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: User_has_Group }); Group.belongsToMany(User, { through: User_has_Group }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - Group.create() - ).then(([user, group]) => { - return user.addGroup(group); - }).then(() => { - return Promise.join( - User.findOne({ - where: {}, - include: [Group] - }), - User.findAll({ - include: [Group] - }) - ); - }).then(([user, users]) => { - expect(user.Groups.length).to.be.equal(1); - expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.equal(user.userSecondId); - expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(user.Groups[0].groupSecondId); - expect(users.length).to.be.equal(1); - expect(users[0].toJSON()).to.be.eql(user.toJSON()); - }); - }); + await this.sequelize.sync({ force: true }); + const [user0, group] = await Promise.all([User.create(), Group.create()]); + await user0.addGroup(group); + + const [user, users] = await Promise.all([User.findOne({ + where: {}, + include: [Group] + }), User.findAll({ + include: [Group] + })]); + + expect(user.Groups.length).to.be.equal(1); + expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(user.Groups[0].User_has_Group.UserUserSecondId).to.be.equal(user.userSecondId); + expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(user.Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(user.Groups[0].groupSecondId); + expect(users.length).to.be.equal(1); + expect(users[0].toJSON()).to.be.eql(user.toJSON()); }); - it('supports non primary key attributes for joins (sourceKey only)', function() { + it('supports non primary key attributes for joins (sourceKey only)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -386,53 +347,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); - }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (targetKey only)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (targetKey only)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -478,53 +429,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: 'usergroups', targetKey: 'userSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); - }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.GroupId).to.be.ok; - expect(users[0].Groups[0].usergroups.GroupId).to.be.equal(users[0].Groups[0].id); - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.GroupId).to.be.ok; - expect(users[1].Groups[0].usergroups.GroupId).to.be.equal(users[1].Groups[0].id); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.GroupId).to.be.ok; - expect(groups[0].Users[0].usergroups.GroupId).to.be.equal(groups[0].id); - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.GroupId).to.be.ok; - expect(groups[1].Users[0].usergroups.GroupId).to.be.equal(groups[1].id); - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (sourceKey and targetKey)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.GroupId).to.be.ok; + expect(users[0].Groups[0].usergroups.GroupId).to.be.equal(users[0].Groups[0].id); + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.GroupId).to.be.ok; + expect(users[1].Groups[0].usergroups.GroupId).to.be.equal(users[1].Groups[0].id); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.GroupId).to.be.ok; + expect(groups[0].Users[0].usergroups.GroupId).to.be.equal(groups[0].id); + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.GroupId).to.be.ok; + expect(groups[1].Users[0].usergroups.GroupId).to.be.equal(groups[1].id); + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (sourceKey and targetKey)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -576,53 +517,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId', targetKey: 'groupSecondId' }); Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); - }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (custom through model)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[0].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(users[1].Groups[0].usergroups.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].usergroups.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (custom through model)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -685,53 +616,111 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: User_has_Group, sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: User_has_Group, sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); - }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; - expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.ok; - expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); - }); + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(users[0].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(users[0].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(users[1].Groups[0].User_has_Group.UserUserSecondId).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(users[1].Groups[0].User_has_Group.GroupGroupSecondId).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(groups[0].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(groups[0].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.ok; + expect(groups[1].Users[0].User_has_Group.GroupGroupSecondId).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.ok; + expect(groups[1].Users[0].User_has_Group.UserUserSecondId).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins for getting associations (sourceKey/targetKey)', async function() { + const User = this.sequelize.define('User', { + userId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: DataTypes.UUIDV4 + }, + userSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + field: 'user_second_id' + } + }, { + tableName: 'tbl_user', + indexes: [ + { + unique: true, + fields: ['user_second_id'] + } + ] + }); + + const Group = this.sequelize.define('Group', { + groupId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + defaultValue: DataTypes.UUIDV4 + }, + groupSecondId: { + type: DataTypes.UUID, + allowNull: false, + defaultValue: DataTypes.UUIDV4, + field: 'group_second_id' + } + }, { + tableName: 'tbl_group', + indexes: [ + { + unique: true, + fields: ['group_second_id'] + } + ] }); + + User.belongsToMany(Group, { through: 'usergroups', sourceKey: 'userSecondId', targetKey: 'groupSecondId' }); + Group.belongsToMany(User, { through: 'usergroups', sourceKey: 'groupSecondId', targetKey: 'userSecondId' }); + + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [groups1, groups2, users1, users2] = await Promise.all( + [user1.getGroups(), user2.getGroups(), group1.getUsers(), group2.getUsers()] + ); + + expect(groups1.length).to.be.equal(1); + expect(groups1[0].id).to.be.equal(group1.id); + expect(groups2.length).to.be.equal(1); + expect(groups2[0].id).to.be.equal(group2.id); + expect(users1.length).to.be.equal(1); + expect(users1[0].id).to.be.equal(user1.id); + expect(users2.length).to.be.equal(1); + expect(users2[0].id).to.be.equal(user2.id); }); - it('supports non primary key attributes for joins (custom foreignKey)', function() { + it('supports non primary key attributes for joins (custom foreignKey)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -783,53 +772,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: 'usergroups', foreignKey: 'userId2', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: 'usergroups', foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); - }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].usergroups.userId2).to.be.ok; - expect(users[0].Groups[0].usergroups.userId2).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].usergroups.groupId2).to.be.ok; - expect(users[0].Groups[0].usergroups.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].usergroups.userId2).to.be.ok; - expect(users[1].Groups[0].usergroups.userId2).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].usergroups.groupId2).to.be.ok; - expect(users[1].Groups[0].usergroups.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].usergroups.groupId2).to.be.ok; - expect(groups[0].Users[0].usergroups.groupId2).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].usergroups.userId2).to.be.ok; - expect(groups[0].Users[0].usergroups.userId2).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].usergroups.groupId2).to.be.ok; - expect(groups[1].Users[0].usergroups.groupId2).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].usergroups.userId2).to.be.ok; - expect(groups[1].Users[0].usergroups.userId2).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports non primary key attributes for joins (custom foreignKey, custom through model)', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].usergroups.userId2).to.be.ok; + expect(users[0].Groups[0].usergroups.userId2).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].usergroups.groupId2).to.be.ok; + expect(users[0].Groups[0].usergroups.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].usergroups.userId2).to.be.ok; + expect(users[1].Groups[0].usergroups.userId2).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].usergroups.groupId2).to.be.ok; + expect(users[1].Groups[0].usergroups.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].usergroups.groupId2).to.be.ok; + expect(groups[0].Users[0].usergroups.groupId2).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].usergroups.userId2).to.be.ok; + expect(groups[0].Users[0].usergroups.userId2).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].usergroups.groupId2).to.be.ok; + expect(groups[1].Users[0].usergroups.groupId2).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].usergroups.userId2).to.be.ok; + expect(groups[1].Users[0].usergroups.userId2).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports non primary key attributes for joins (custom foreignKey, custom through model)', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -902,53 +881,43 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: User_has_Group, foreignKey: 'userId2', sourceKey: 'userSecondId' }); Group.belongsToMany(User, { through: User_has_Group, foreignKey: 'groupId2', sourceKey: 'groupSecondId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - User.create(), - Group.create(), - Group.create() - ).then(([user1, user2, group1, group2]) => { - return Promise.join(user1.addGroup(group1), user2.addGroup(group2)); - }).then(() => { - return Promise.join( - User.findAll({ - where: {}, - include: [Group] - }), - Group.findAll({ - include: [User] - }) - ); - }).then(([users, groups]) => { - expect(users.length).to.be.equal(2); - expect(users[0].Groups.length).to.be.equal(1); - expect(users[1].Groups.length).to.be.equal(1); - expect(users[0].Groups[0].User_has_Group.userId2).to.be.ok; - expect(users[0].Groups[0].User_has_Group.userId2).to.be.equal(users[0].userSecondId); - expect(users[0].Groups[0].User_has_Group.groupId2).to.be.ok; - expect(users[0].Groups[0].User_has_Group.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); - expect(users[1].Groups[0].User_has_Group.userId2).to.be.ok; - expect(users[1].Groups[0].User_has_Group.userId2).to.be.equal(users[1].userSecondId); - expect(users[1].Groups[0].User_has_Group.groupId2).to.be.ok; - expect(users[1].Groups[0].User_has_Group.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); - - expect(groups.length).to.be.equal(2); - expect(groups[0].Users.length).to.be.equal(1); - expect(groups[1].Users.length).to.be.equal(1); - expect(groups[0].Users[0].User_has_Group.groupId2).to.be.ok; - expect(groups[0].Users[0].User_has_Group.groupId2).to.be.equal(groups[0].groupSecondId); - expect(groups[0].Users[0].User_has_Group.userId2).to.be.ok; - expect(groups[0].Users[0].User_has_Group.userId2).to.be.equal(groups[0].Users[0].userSecondId); - expect(groups[1].Users[0].User_has_Group.groupId2).to.be.ok; - expect(groups[1].Users[0].User_has_Group.groupId2).to.be.equal(groups[1].groupSecondId); - expect(groups[1].Users[0].User_has_Group.userId2).to.be.ok; - expect(groups[1].Users[0].User_has_Group.userId2).to.be.equal(groups[1].Users[0].userSecondId); - }); - }); - }); - - it('supports primary key attributes with different field names where parent include is required', function() { + await this.sequelize.sync({ force: true }); + const [user1, user2, group1, group2] = await Promise.all([User.create(), User.create(), Group.create(), Group.create()]); + await Promise.all([user1.addGroup(group1), user2.addGroup(group2)]); + + const [users, groups] = await Promise.all([User.findAll({ + where: {}, + include: [Group] + }), Group.findAll({ + include: [User] + })]); + + expect(users.length).to.be.equal(2); + expect(users[0].Groups.length).to.be.equal(1); + expect(users[1].Groups.length).to.be.equal(1); + expect(users[0].Groups[0].User_has_Group.userId2).to.be.ok; + expect(users[0].Groups[0].User_has_Group.userId2).to.be.equal(users[0].userSecondId); + expect(users[0].Groups[0].User_has_Group.groupId2).to.be.ok; + expect(users[0].Groups[0].User_has_Group.groupId2).to.be.equal(users[0].Groups[0].groupSecondId); + expect(users[1].Groups[0].User_has_Group.userId2).to.be.ok; + expect(users[1].Groups[0].User_has_Group.userId2).to.be.equal(users[1].userSecondId); + expect(users[1].Groups[0].User_has_Group.groupId2).to.be.ok; + expect(users[1].Groups[0].User_has_Group.groupId2).to.be.equal(users[1].Groups[0].groupSecondId); + + expect(groups.length).to.be.equal(2); + expect(groups[0].Users.length).to.be.equal(1); + expect(groups[1].Users.length).to.be.equal(1); + expect(groups[0].Users[0].User_has_Group.groupId2).to.be.ok; + expect(groups[0].Users[0].User_has_Group.groupId2).to.be.equal(groups[0].groupSecondId); + expect(groups[0].Users[0].User_has_Group.userId2).to.be.ok; + expect(groups[0].Users[0].User_has_Group.userId2).to.be.equal(groups[0].Users[0].userSecondId); + expect(groups[1].Users[0].User_has_Group.groupId2).to.be.ok; + expect(groups[1].Users[0].User_has_Group.groupId2).to.be.equal(groups[1].groupSecondId); + expect(groups[1].Users[0].User_has_Group.userId2).to.be.ok; + expect(groups[1].Users[0].User_has_Group.userId2).to.be.equal(groups[1].Users[0].userSecondId); + }); + + it('supports primary key attributes with different field names where parent include is required', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -996,43 +965,29 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Company.belongsToMany(Group, { through: Company_has_Group }); Group.belongsToMany(Company, { through: Company_has_Group }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create(), - Group.create(), - Company.create() - ).then(([user, group, company]) => { - return Promise.join( - user.setCompany(company), - company.addGroup(group) - ); - }).then(() => { - return Promise.join( - User.findOne({ - where: {}, - include: [ - { model: Company, include: [Group] } - ] - }), - User.findAll({ - include: [ - { model: Company, include: [Group] } - ] - }), - User.findOne({ - where: {}, - include: [ - { model: Company, required: true, include: [Group] } - ] - }), - User.findAll({ - include: [ - { model: Company, required: true, include: [Group] } - ] - }) - ); - }); - }); + await this.sequelize.sync({ force: true }); + const [user, group, company] = await Promise.all([User.create(), Group.create(), Company.create()]); + await Promise.all([user.setCompany(company), company.addGroup(group)]); + + await Promise.all([User.findOne({ + where: {}, + include: [ + { model: Company, include: [Group] } + ] + }), User.findAll({ + include: [ + { model: Company, include: [Group] } + ] + }), User.findOne({ + where: {}, + include: [ + { model: Company, required: true, include: [Group] } + ] + }), User.findAll({ + include: [ + { model: Company, required: true, include: [Group] } + ] + })]); }); }); @@ -1063,123 +1018,114 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = ctx.sequelize.define('Article', { - pk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - title: DataTypes.STRING - }); - ctx.Label = ctx.sequelize.define('Label', { - sk: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - text: DataTypes.STRING - }); - ctx.ArticleLabel = ctx.sequelize.define('ArticleLabel'); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + + const Article = sequelize.define('Article', { + pk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + title: DataTypes.STRING + }); - ctx.Article.belongsToMany(ctx.Label, { through: ctx.ArticleLabel }); - ctx.Label.belongsToMany(ctx.Article, { through: ctx.ArticleLabel }); + const Label = sequelize.define('Label', { + sk: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + text: DataTypes.STRING + }); - return ctx.sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - ctx.label = label; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.setLabels([ctx.label], { transaction: t }); - }).then(() => { - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return Promise.all([ - articles[0].hasLabels([ctx.label]), - articles[0].hasLabels([ctx.label], { transaction: ctx.t }) - ]); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.true; + const ArticleLabel = sequelize.define('ArticleLabel'); - return ctx.t.rollback(); - }); + Article.belongsToMany(Label, { through: ArticleLabel }); + Label.belongsToMany(Article, { through: ArticleLabel }); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.setLabels([label], { transaction: t }); + const articles = await Article.findAll({ transaction: t }); + + const [hasLabel1, hasLabel2] = await Promise.all([ + articles[0].hasLabels([label]), + articles[0].hasLabels([label], { transaction: t }) + ]); + + expect(hasLabel1).to.be.false; + expect(hasLabel2).to.be.true; + + await t.rollback(); }); } - it('answers false if only some labels have been assigned', function() { - return Promise.all([ + it('answers false if only some labels have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return article.hasLabels([label1, label2]); - }); - }).then(result => { - expect(result).to.be.false; - }); + ]); + + await article.addLabel(label1); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.false; }); - it('answers false if only some labels have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers false if only some labels have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabels([label1]).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.false; - }); - }); - }); + ]); + + await article.addLabels([label1]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.false; }); - it('answers true if all label have been assigned', function() { - return Promise.all([ + it('answers true if all label have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([label1, label2]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.true; }); - it('answers true if all label have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers true if all label have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.true; }); - it('answers true for labels that have been assigned multitple times', function() { + it('answers true for labels that have been assigned multitple times', async function() { this.ArticleLabel = this.sequelize.define('ArticleLabel', { id: { primaryKey: true, @@ -1197,31 +1143,35 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.Article.belongsToMany(this.Label, { through: { model: this.ArticleLabel, unique: false } }); this.Label.belongsToMany(this.Article, { through: { model: this.ArticleLabel, unique: false } }); - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ])) - .then(([article, label1, label2]) => Promise.all([ - article, - label1, - label2, - article.addLabel(label1, { - through: { relevance: 1 } - }), - article.addLabel(label2, { - through: { relevance: .54 } - }), - article.addLabel(label2, { - through: { relevance: .99 } - }) - ])) - .then(([article, label1, label2]) => article.hasLabels([label1, label2])) - .then(result => expect(result).to.be.true); + await this.sequelize.sync({ force: true }); + + const [article0, label10, label20] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }) + ]); + + const [article, label1, label2] = await Promise.all([ + article0, + label10, + label20, + article0.addLabel(label10, { + through: { relevance: 1 } + }), + article0.addLabel(label20, { + through: { relevance: .54 } + }), + article0.addLabel(label20, { + through: { relevance: .99 } + }) + ]); + + const result = await article.hasLabels([label1, label2]); + + await expect(result).to.be.true; }); - it('answers true for labels that have been assigned multitple times when passing a primary key instead of an object', function() { + it('answers true for labels that have been assigned multitple times when passing a primary key instead of an object', async function() { this.ArticleLabel = this.sequelize.define('ArticleLabel', { id: { primaryKey: true, @@ -1239,36 +1189,100 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.Article.belongsToMany(this.Label, { through: { model: this.ArticleLabel, unique: false } }); this.Label.belongsToMany(this.Article, { through: { model: this.ArticleLabel, unique: false } }); - return this.sequelize.sync({ force: true }) - .then(() => Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness' }), - this.Label.create({ text: 'Epicness' }) - ])) - .then(([article, label1, label2]) => Promise.all([ - article, - label1, - label2, - article.addLabel(label1, { - through: { relevance: 1 } - }), - article.addLabel(label2, { - through: { relevance: .54 } - }), - article.addLabel(label2, { - through: { relevance: .99 } - }) - ])) - .then(([article, label1, label2]) => article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ])) - .then(result => expect(result).to.be.true); + await this.sequelize.sync({ force: true }); + + const [article0, label10, label20] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness' }), + this.Label.create({ text: 'Epicness' }) + ]); + + const [article, label1, label2] = await Promise.all([ + article0, + label10, + label20, + article0.addLabel(label10, { + through: { relevance: 1 } + }), + article0.addLabel(label20, { + through: { relevance: .54 } + }), + article0.addLabel(label20, { + through: { relevance: .99 } + }) + ]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + await expect(result).to.be.true; }); }); - describe('countAssociations', () => { + describe('hasAssociations with binary key', () => { beforeEach(function() { + const keyDataType = dialect === 'mysql' || dialect === 'mariadb' ? 'BINARY(255)' : DataTypes.BLOB('tiny'); + this.Article = this.sequelize.define('Article', { + id: { + type: keyDataType, + primaryKey: true + } + }); + this.Label = this.sequelize.define('Label', { + id: { + type: keyDataType, + primaryKey: true + } + }); + this.ArticleLabel = this.sequelize.define('ArticleLabel'); + + this.Article.belongsToMany(this.Label, { through: this.ArticleLabel }); + this.Label.belongsToMany(this.Article, { through: this.ArticleLabel }); + + return this.sequelize.sync({ force: true }); + }); + + it('answers true for labels that have been assigned', async function() { + const [article0, label0] = await Promise.all([ + this.Article.create({ + id: Buffer.alloc(255) + }), + this.Label.create({ + id: Buffer.alloc(255) + }) + ]); + + const [article, label] = await Promise.all([ + article0, + label0, + article0.addLabel(label0, { + through: 'ArticleLabel' + }) + ]); + + const result = await article.hasLabels([label]); + await expect(result).to.be.true; + }); + + it('answer false for labels that have not been assigned', async function() { + const [article, label] = await Promise.all([ + this.Article.create({ + id: Buffer.alloc(255) + }), + this.Label.create({ + id: Buffer.alloc(255) + }) + ]); + + const result = await article.hasLabels([label]); + await expect(result).to.be.false; + }); + }); + + describe('countAssociations', () => { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); @@ -1291,32 +1305,29 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.User.belongsToMany(this.Task, { through: this.UserTask }); this.Task.belongsToMany(this.User, { through: this.UserTask }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - this.tasks = [task1, task2]; - this.user = john; - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + this.tasks = [task1, task2]; + this.user = john; + + return john.setTasks([task1, task2]); }); - it('should count all associations', function() { - return expect(this.user.countTasks({})).to.eventually.equal(2); + it('should count all associations', async function() { + expect(await this.user.countTasks({})).to.equal(2); }); - it('should count filtered associations', function() { - return expect(this.user.countTasks({ - where: { - active: true - } - })).to.eventually.equal(1); + it('should count filtered associations', async function() { + expect(await this.user.countTasks({ where: { active: true } })).to.equal(1); }); - it('should count scoped associations', function() { + it('should count scoped associations', async function() { this.User.belongsToMany(this.Task, { as: 'activeTasks', through: this.UserTask, @@ -1325,12 +1336,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { } }); - return expect(this.user.countActiveTasks({})).to.eventually.equal(1); + expect(await this.user.countActiveTasks({})).to.equal(1); }); - it('should count scoped through associations', function() { - const user = this.user; - + it('should count scoped through associations', async function() { this.User.belongsToMany(this.Task, { as: 'startedTasks', through: { @@ -1341,83 +1350,63 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { } }); - return Promise.join( - this.Task.create().then(task => { - return user.addTask(task, { - through: { started: true } - }); - }), - this.Task.create().then(task => { - return user.addTask(task, { - through: { started: true } - }); - }) - ).then(() => { - return expect(user.countStartedTasks({})).to.eventually.equal(2); - }); + for (let i = 0; i < 2; i++) { + await this.user.addTask(await this.Task.create(), { + through: { started: true } + }); + } + + expect(await this.user.countStartedTasks({})).to.equal(2); }); }); describe('setAssociations', () => { - it('clears associations when passing null to the set-method', function() { + it('clears associations when passing null to the set-method', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.task = task; - return task.setUsers([user]); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); + await this.sequelize.sync({ force: true }); - return ctx.task.setUsers(null); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(0); - }); + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await task.setUsers([user]); + const _users0 = await task.getUsers(); + expect(_users0).to.have.length(1); + + await task.setUsers(null); + const _users = await task.getUsers(); + expect(_users).to.have.length(0); }); - it('should be able to set twice with custom primary keys', function() { + it('should be able to set twice with custom primary keys', async function() { const User = this.sequelize.define('User', { uid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: DataTypes.STRING }), Task = this.sequelize.define('Task', { tid: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - User.create({ username: 'bar' }), - Task.create({ title: 'task' }) - ]); - }).then(([user1, user2, task]) => { - ctx.task = task; - ctx.user1 = user1; - ctx.user2 = user2; - return task.setUsers([user1]); - }).then(() => { - ctx.user2.user_has_task = { usertitle: 'Something' }; - return ctx.task.setUsers([ctx.user1, ctx.user2]); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(2); - }); - }); - - it('joins an association with custom primary keys', function() { + await this.sequelize.sync({ force: true }); + + const [user1, user2, task] = await Promise.all([ + User.create({ username: 'foo' }), + User.create({ username: 'bar' }), + Task.create({ title: 'task' }) + ]); + + await task.setUsers([user1]); + user2.user_has_task = { usertitle: 'Something' }; + await task.setUsers([user1, user2]); + const _users = await task.getUsers(); + expect(_users).to.have.length(2); + }); + + it('joins an association with custom primary keys', async function() { const Group = this.sequelize.define('group', { group_id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING(64) @@ -1430,52 +1419,45 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Group.belongsToMany(Member, { through: 'group_members', foreignKey: 'group_id', otherKey: 'member_id' }); Member.belongsToMany(Group, { through: 'group_members', foreignKey: 'member_id', otherKey: 'group_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.create({ group_id: 1, name: 'Group1' }), - Member.create({ member_id: 10, email: 'team@sequelizejs.com' }) - ]); - }).then(([group, member]) => { - return group.addMember(member).return(group); - }).then(group => { - return group.getMembers(); - }).then(members => { - expect(members).to.be.instanceof(Array); - expect(members).to.have.length(1); - expect(members[0].member_id).to.equal(10); - expect(members[0].email).to.equal('team@sequelizejs.com'); - }); + await this.sequelize.sync({ force: true }); + + const [group0, member] = await Promise.all([ + Group.create({ group_id: 1, name: 'Group1' }), + Member.create({ member_id: 10, email: 'team@sequelizejs.com' }) + ]); + + await group0.addMember(member); + const group = group0; + const members = await group.getMembers(); + expect(members).to.be.instanceof(Array); + expect(members).to.have.length(1); + expect(members[0].member_id).to.equal(10); + expect(members[0].email).to.equal('team@sequelizejs.com'); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 5, title: 'wat' }) - ]); - }).then(([user, task1, task2]) => { - ctx.user = user; - ctx.task2 = task2; - return user.addTask(task1.id); - }).then(() => { - return ctx.user.setTasks([ctx.task2.id]); - }).then(() => { - return ctx.user.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].title).to.equal('wat'); - }); + await this.sequelize.sync({ force: true }); + + const [user, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 5, title: 'wat' }) + ]); + + await user.addTask(task1.id); + await user.setTasks([task2.id]); + const tasks = await user.getTasks(); + expect(tasks).to.have.length(1); + expect(tasks[0].title).to.equal('wat'); }); - it('using scope to set associations', function() { + it('using scope to set associations', async function() { const ItemTag = this.sequelize.define('ItemTag', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, tag_id: { type: DataTypes.INTEGER, unique: false }, @@ -1505,31 +1487,30 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { foreignKey: 'taggable_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Post.create({ name: 'post1' }), - Comment.create({ name: 'comment1' }), - Tag.create({ name: 'tag1' }) - ]); - }).then(([post, comment, tag]) => { - this.post = post; - this.comment = comment; - this.tag = tag; - return this.post.setTags([this.tag]); - }).then(() => { - return this.comment.setTags([this.tag]); - }).then(() => { - return Promise.all([ - this.post.getTags(), - this.comment.getTags() - ]); - }).then(([postTags, commentTags]) => { - expect(postTags).to.have.length(1); - expect(commentTags).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + + const [post, comment, tag] = await Promise.all([ + Post.create({ name: 'post1' }), + Comment.create({ name: 'comment1' }), + Tag.create({ name: 'tag1' }) + ]); + + this.post = post; + this.comment = comment; + this.tag = tag; + await this.post.setTags([this.tag]); + await this.comment.setTags([this.tag]); + + const [postTags, commentTags] = await Promise.all([ + this.post.getTags(), + this.comment.getTags() + ]); + + expect(postTags).to.have.length(1); + expect(commentTags).to.have.length(1); }); - it('updating association via set associations with scope', function() { + it('updating association via set associations with scope', async function() { const ItemTag = this.sequelize.define('ItemTag', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, tag_id: { type: DataTypes.INTEGER, unique: false }, @@ -1559,35 +1540,33 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { foreignKey: 'taggable_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Post.create({ name: 'post1' }), - Comment.create({ name: 'comment1' }), - Tag.create({ name: 'tag1' }), - Tag.create({ name: 'tag2' }) - ]); - }).then(([post, comment, tag, secondTag]) => { - this.post = post; - this.comment = comment; - this.tag = tag; - this.secondTag = secondTag; - return this.post.setTags([this.tag, this.secondTag]); - }).then(() => { - return this.comment.setTags([this.tag, this.secondTag]); - }).then(() => { - return this.post.setTags([this.tag]); - }).then(() => { - return Promise.all([ - this.post.getTags(), - this.comment.getTags() - ]); - }).then(([postTags, commentTags]) => { - expect(postTags).to.have.length(1); - expect(commentTags).to.have.length(2); - }); + await this.sequelize.sync({ force: true }); + + const [post, comment, tag, secondTag] = await Promise.all([ + Post.create({ name: 'post1' }), + Comment.create({ name: 'comment1' }), + Tag.create({ name: 'tag1' }), + Tag.create({ name: 'tag2' }) + ]); + + this.post = post; + this.comment = comment; + this.tag = tag; + this.secondTag = secondTag; + await this.post.setTags([this.tag, this.secondTag]); + await this.comment.setTags([this.tag, this.secondTag]); + await this.post.setTags([this.tag]); + + const [postTags, commentTags] = await Promise.all([ + this.post.getTags(), + this.comment.getTags() + ]); + + expect(postTags).to.have.length(1); + expect(commentTags).to.have.length(2); }); - it('should catch EmptyResultError when rejectOnEmpty is set', function() { + it('should catch EmptyResultError when rejectOnEmpty is set', async function() { const User = this.sequelize.define( 'User', { username: DataTypes.STRING }, @@ -1601,81 +1580,66 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 51, title: 'following up' }) - ]); - }).then(([user, task1, task2]) => { - return user.setTasks([task1, task2]).return(user); - }).then(user => { - return user.getTasks(); - }).then(userTasks => { - expect(userTasks).to.be.an('array').that.has.a.lengthOf(2); - expect(userTasks[0]).to.be.an.instanceOf(Task); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 51, title: 'following up' }) + ]); + + await user0.setTasks([task1, task2]); + const user = user0; + const userTasks = await user.getTasks(); + expect(userTasks).to.be.an('array').that.has.a.lengthOf(2); + expect(userTasks[0]).to.be.an.instanceOf(Task); }); }); describe('createAssociations', () => { - it('creates a new associated object', function() { + it('creates a new associated object', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return task.createUser({ username: 'foo' }); - }).then(createdUser => { - expect(createdUser).to.be.instanceof(User); - expect(createdUser.username).to.equal('foo'); - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + const task = await Task.create({ title: 'task' }); + const createdUser = await task.createUser({ username: 'foo' }); + expect(createdUser).to.be.instanceof(User); + expect(createdUser.username).to.equal('foo'); + const _users = await task.getUsers(); + expect(_users).to.have.length(1); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.User = sequelize.define('User', { username: DataTypes.STRING }); - ctx.Task = sequelize.define('Task', { title: DataTypes.STRING }); - - ctx.User.belongsToMany(ctx.Task, { through: 'UserTasks' }); - ctx.Task.belongsToMany(ctx.User, { through: 'UserTasks' }); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Task.create({ title: 'task' }), - ctx.sequelize.transaction() - ]); - }).then(([task, t]) => { - ctx.task = task; - ctx.t = t; - return task.createUser({ username: 'foo' }, { transaction: t }); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(0); - - return ctx.task.getUsers({ transaction: ctx.t }); - }).then(users => { - expect(users).to.have.length(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); + + await sequelize.sync({ force: true }); + + const [task, t] = await Promise.all([ + Task.create({ title: 'task' }), + sequelize.transaction() + ]); + + await task.createUser({ username: 'foo' }, { transaction: t }); + const users0 = await task.getUsers(); + expect(users0).to.have.length(0); + + const users = await task.getUsers({ transaction: t }); + expect(users).to.have.length(1); + await t.rollback(); }); } - it('supports setting through table attributes', function() { + it('supports setting through table attributes', async function() { const User = this.sequelize.define('user', {}), Group = this.sequelize.define('group', {}), UserGroups = this.sequelize.define('user_groups', { @@ -1685,176 +1649,150 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { through: UserGroups }); Group.belongsToMany(User, { through: UserGroups }); - return this.sequelize.sync({ force: true }).then(() => { - return Group.create({}); - }).then(group => { - return Promise.join( - group.createUser({ id: 1 }, { through: { isAdmin: true } }), - group.createUser({ id: 2 }, { through: { isAdmin: false } }), - () => { - return UserGroups.findAll(); - } - ); - }).then(userGroups => { - userGroups.sort((a, b) => { - return a.userId < b.userId ? - 1 : 1; - }); - expect(userGroups[0].userId).to.equal(1); - expect(userGroups[0].isAdmin).to.be.ok; - expect(userGroups[1].userId).to.equal(2); - expect(userGroups[1].isAdmin).not.to.be.ok; + await this.sequelize.sync({ force: true }); + const group = await Group.create({}); + + await Promise.all([ + group.createUser({ id: 1 }, { through: { isAdmin: true } }), + group.createUser({ id: 2 }, { through: { isAdmin: false } }) + ]); + + const userGroups = await UserGroups.findAll(); + userGroups.sort((a, b) => { + return a.userId < b.userId ? - 1 : 1; }); + expect(userGroups[0].userId).to.equal(1); + expect(userGroups[0].isAdmin).to.be.ok; + expect(userGroups[1].userId).to.equal(2); + expect(userGroups[1].isAdmin).not.to.be.ok; }); - it('supports using the field parameter', function() { + it('supports using the field parameter', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return task.createUser({ username: 'foo' }, { fields: ['username'] }); - }).then(createdUser => { - expect(createdUser).to.be.instanceof(User); - expect(createdUser.username).to.equal('foo'); - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + const task = await Task.create({ title: 'task' }); + const createdUser = await task.createUser({ username: 'foo' }, { fields: ['username'] }); + expect(createdUser).to.be.instanceof(User); + expect(createdUser.username).to.equal('foo'); + const _users = await task.getUsers(); + expect(_users).to.have.length(1); }); }); describe('addAssociations', () => { - it('supports both single instance and array', function() { + it('supports both single instance and array', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 52, title: 'get done' }) - ]); - }).then(([user, task1, task2]) => { - return Promise.all([ - user.addTask(task1), - user.addTask([task2]) - ]).return(user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - expect(tasks.find(item => item.title === 'get started')).to.be.ok; - expect(tasks.find(item => item.title === 'get done')).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 52, title: 'get done' }) + ]); + + await Promise.all([ + user0.addTask(task1), + user0.addTask([task2]) + ]); + + const user = user0; + const tasks = await user.getTasks(); + expect(tasks).to.have.length(2); + expect(tasks.find(item => item.title === 'get started')).to.be.ok; + expect(tasks.find(item => item.title === 'get done')).to.be.ok; }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.User = sequelize.define('User', { username: DataTypes.STRING }); - ctx.Task = sequelize.define('Task', { title: DataTypes.STRING }); - - ctx.User.belongsToMany(ctx.Task, { through: 'UserTasks' }); - ctx.Task.belongsToMany(ctx.User, { through: 'UserTasks' }); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.User.create({ username: 'foo' }), - ctx.Task.create({ title: 'task' }), - ctx.sequelize.transaction() - ]); - }).then(([user, task, t]) => { - ctx.task = task; - ctx.user = user; - ctx.t = t; - return task.addUser(user, { transaction: t }); - }).then(() => { - return ctx.task.hasUser(ctx.user); - }).then(hasUser => { - expect(hasUser).to.be.false; - return ctx.task.hasUser(ctx.user, { transaction: ctx.t }); - }).then(hasUser => { - expect(hasUser).to.be.true; - return ctx.t.rollback(); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); - it('supports transactions when updating a through model', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.User = sequelize.define('User', { username: DataTypes.STRING }); - ctx.Task = sequelize.define('Task', { title: DataTypes.STRING }); + User.belongsToMany(Task, { through: 'UserTasks' }); + Task.belongsToMany(User, { through: 'UserTasks' }); - ctx.UserTask = sequelize.define('UserTask', { - status: Sequelize.STRING - }); + await sequelize.sync({ force: true }); - ctx.User.belongsToMany(ctx.Task, { through: ctx.UserTask }); - ctx.Task.belongsToMany(ctx.User, { through: ctx.UserTask }); - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.User.create({ username: 'foo' }), - ctx.Task.create({ title: 'task' }), - ctx.sequelize.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED }) - ]); - }).then(([user, task, t]) => { - ctx.task = task; - ctx.user = user; - ctx.t = t; - return task.addUser(user, { through: { status: 'pending' } }); // Create without transaction, so the old value is accesible from outside the transaction - }).then(() => { - return ctx.task.addUser(ctx.user, { transaction: ctx.t, through: { status: 'completed' } }); // Add an already exisiting user in a transaction, updating a value in the join table - }).then(() => { - return Promise.all([ - ctx.user.getTasks(), - ctx.user.getTasks({ transaction: ctx.t }) - ]); - }).then(([tasks, transactionTasks]) => { - expect(tasks[0].UserTask.status).to.equal('pending'); - expect(transactionTasks[0].UserTask.status).to.equal('completed'); + const [user, task, t] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }), + sequelize.transaction() + ]); - return ctx.t.rollback(); + await task.addUser(user, { transaction: t }); + const hasUser0 = await task.hasUser(user); + expect(hasUser0).to.be.false; + const hasUser = await task.hasUser(user, { transaction: t }); + expect(hasUser).to.be.true; + await t.rollback(); + }); + + it('supports transactions when updating a through model', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: DataTypes.STRING }); + const Task = sequelize.define('Task', { title: DataTypes.STRING }); + + const UserTask = sequelize.define('UserTask', { + status: Sequelize.STRING }); + + User.belongsToMany(Task, { through: UserTask }); + Task.belongsToMany(User, { through: UserTask }); + await sequelize.sync({ force: true }); + + const [user, task, t] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }), + sequelize.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED }) + ]); + + await task.addUser(user, { through: { status: 'pending' } }); // Create without transaction, so the old value is accesible from outside the transaction + await task.addUser(user, { transaction: t, through: { status: 'completed' } }); // Add an already exisiting user in a transaction, updating a value in the join table + + const [tasks, transactionTasks] = await Promise.all([ + user.getTasks(), + user.getTasks({ transaction: t }) + ]); + + expect(tasks[0].UserTask.status).to.equal('pending'); + expect(transactionTasks[0].UserTask.status).to.equal('completed'); + + await t.rollback(); }); } - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }) - ]); - }).then(([user, task]) => { - return user.addTask(task.id).return(user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks[0].title).to.equal('get started'); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }) + ]); + + await user0.addTask(task.id); + const user = user0; + const tasks = await user.getTasks(); + expect(tasks[0].title).to.equal('get started'); }); - it('should not pass indexes to the join table', function() { + it('should not pass indexes to the join table', async function() { const User = this.sequelize.define( 'User', { username: DataTypes.STRING }, @@ -1883,10 +1821,10 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { //create associations User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should catch EmptyResultError when rejectOnEmpty is set', function() { + it('should catch EmptyResultError when rejectOnEmpty is set', async function() { const User = this.sequelize.define( 'User', { username: DataTypes.STRING }, @@ -1900,21 +1838,20 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }) - ]); - }).then(([user, task]) => { - return user.addTask(task).return(user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks[0].title).to.equal('get started'); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }) + ]); + + await user0.addTask(task); + const user = user0; + const tasks = await user.getTasks(); + expect(tasks[0].title).to.equal('get started'); }); - it('should returns array of intermediate table', function() { + it('should returns array of intermediate table', async function() { const User = this.sequelize.define('User'); const Task = this.sequelize.define('Task'); const UserTask = this.sequelize.define('UserTask'); @@ -1922,94 +1859,87 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Task, { through: UserTask }); Task.belongsToMany(User, { through: UserTask }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create(), - Task.create() - ]).then(([user, task]) => { - return user.addTask(task); - }).then(userTasks => { - expect(userTasks).to.be.an('array').that.has.a.lengthOf(1); - expect(userTasks[0]).to.be.an.instanceOf(UserTask); - }); - }); + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create(), + Task.create() + ]); + + const userTasks = await user.addTask(task); + expect(userTasks).to.be.an('array').that.has.a.lengthOf(1); + expect(userTasks[0]).to.be.an.instanceOf(UserTask); }); }); describe('addMultipleAssociations', () => { - it('supports both single instance and array', function() { + it('supports both single instance and array', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 12 }), - Task.create({ id: 50, title: 'get started' }), - Task.create({ id: 52, title: 'get done' }) - ]); - }).then(([user, task1, task2]) => { - return Promise.all([ - user.addTasks(task1), - user.addTasks([task2]) - ]).return(user); - }).then(user => { - return user.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - expect(tasks.some(item => { return item.title === 'get started'; })).to.be.ok; - expect(tasks.some(item => { return item.title === 'get done'; })).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1, task2] = await Promise.all([ + User.create({ id: 12 }), + Task.create({ id: 50, title: 'get started' }), + Task.create({ id: 52, title: 'get done' }) + ]); + + await Promise.all([ + user0.addTasks(task1), + user0.addTasks([task2]) + ]); + + const user = user0; + const tasks = await user.getTasks(); + expect(tasks).to.have.length(2); + expect(tasks.some(item => { return item.title === 'get started'; })).to.be.ok; + expect(tasks.some(item => { return item.title === 'get done'; })).to.be.ok; }); - it('adds associations without removing the current ones', function() { + it('adds associations without removing the current ones', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'foo ' }, - { username: 'bar ' }, - { username: 'baz ' } - ]).then(() => { - return Promise.all([ - Task.create({ title: 'task' }), - User.findAll() - ]); - }).then(([task, users]) => { - ctx.task = task; - ctx.users = users; - return task.setUsers([users[0]]); - }).then(() => { - return ctx.task.addUsers([ctx.users[1], ctx.users[2]]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(3); - - // Re-add user 0's object, this should be harmless - // Re-add user 0's id, this should be harmless - return Promise.all([ - expect(ctx.task.addUsers([ctx.users[0]])).not.to.be.rejected, - expect(ctx.task.addUsers([ctx.users[0].id])).not.to.be.rejected - ]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(3); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([ + { username: 'foo ' }, + { username: 'bar ' }, + { username: 'baz ' } + ]); + + const [task, users1] = await Promise.all([ + Task.create({ title: 'task' }), + User.findAll() + ]); + + const users = users1; + await task.setUsers([users1[0]]); + await task.addUsers([users[1], users[2]]); + const users0 = await task.getUsers(); + expect(users0).to.have.length(3); + + // Re-add user 0's object, this should be harmless + // Re-add user 0's id, this should be harmless + + await Promise.all([ + expect(task.addUsers([users[0]])).not.to.be.rejected, + expect(task.addUsers([users[0].id])).not.to.be.rejected + ]); + + expect(await task.getUsers()).to.have.length(3); }); }); describe('through model validations', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: Sequelize.STRING }); @@ -2034,27 +1964,27 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Project.belongsToMany(Employee, { as: 'Participants', through: Participation }); Employee.belongsToMany(Project, { as: 'Participations', through: Participation }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Project.create({ name: 'project 1' }), - Employee.create({ name: 'employee 1' }) - ]).then(([project, employee]) => { - this.project = project; - this.employee = employee; - }); - }); + await this.sequelize.sync({ force: true }); + + const [project, employee] = await Promise.all([ + Project.create({ name: 'project 1' }), + Employee.create({ name: 'employee 1' }) + ]); + + this.project = project; + this.employee = employee; }); - it('runs on add', function() { - return expect(this.project.addParticipant(this.employee, { through: { role: '' } })).to.be.rejected; + it('runs on add', async function() { + await expect(this.project.addParticipant(this.employee, { through: { role: '' } })).to.be.rejected; }); - it('runs on set', function() { - return expect(this.project.setParticipants([this.employee], { through: { role: '' } })).to.be.rejected; + it('runs on set', async function() { + await expect(this.project.setParticipants([this.employee], { through: { role: '' } })).to.be.rejected; }); - it('runs on create', function() { - return expect(this.project.createParticipant({ name: 'employee 2' }, { through: { role: '' } })).to.be.rejected; + it('runs on create', async function() { + await expect(this.project.createParticipant({ name: 'employee 2' }, { through: { role: '' } })).to.be.rejected; }); }); @@ -2069,38 +1999,39 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('uses one insert into statement', function() { + it('uses one insert into statement', async function() { const spy = sinon.spy(); - return Promise.all([ + const [user, task1, task2] = await Promise.all([ this.User.create({ username: 'foo' }), this.Task.create({ id: 12, title: 'task1' }), this.Task.create({ id: 15, title: 'task2' }) - ]).then(([user, task1, task2]) => { - return user.setTasks([task1, task2], { - logging: spy - }); - }).then(() => { - expect(spy.calledTwice).to.be.ok; + ]); + + await user.setTasks([task1, task2], { + logging: spy }); + + expect(spy.calledTwice).to.be.ok; }); - it('uses one delete from statement', function() { + it('uses one delete from statement', async function() { const spy = sinon.spy(); - return Promise.all([ + const [user0, task1, task2] = await Promise.all([ this.User.create({ username: 'foo' }), this.Task.create({ title: 'task1' }), this.Task.create({ title: 'task2' }) - ]).then(([user, task1, task2]) => { - return user.setTasks([task1, task2]).return(user); - }).then(user => { - return user.setTasks(null, { - logging: spy - }); - }).then(() => { - expect(spy.calledTwice).to.be.ok; + ]); + + await user0.setTasks([task1, task2]); + const user = user0; + + await user.setTasks(null, { + logging: spy }); + + expect(spy.calledTwice).to.be.ok; }); }); // end optimization using bulk create, destroy and update @@ -2121,7 +2052,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('should work with non integer primary keys', function() { + it('should work with non integer primary keys', async function() { const Beacons = this.sequelize.define('Beacon', { id: { primaryKey: true, @@ -2143,7 +2074,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Beacons.belongsToMany(Users, { through: 'UserBeacons' }); Users.belongsToMany(Beacons, { through: 'UserBeacons' }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); it('makes join table non-paranoid by default', () => { @@ -2165,6 +2096,36 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { expect(association.through.model.options.paranoid).not.to.be.ok; }); }); + + it('should allow creation of a paranoid join table', () => { + const paranoidSequelize = Support.createSequelizeInstance({ + define: { + paranoid: true + } + }), + ParanoidUser = paranoidSequelize.define('ParanoidUser', {}), + ParanoidTask = paranoidSequelize.define('ParanoidTask', {}); + + ParanoidUser.belongsToMany(ParanoidTask, { + through: { + model: 'UserTasks', + paranoid: true + } + }); + ParanoidTask.belongsToMany(ParanoidUser, { + through: { + model: 'UserTasks', + paranoid: true + } + }); + + expect(ParanoidUser.options.paranoid).to.be.ok; + expect(ParanoidTask.options.paranoid).to.be.ok; + + _.forEach(ParanoidUser.associations, association => { + expect(association.through.model.options.paranoid).to.be.ok; + }); + }); }); describe('foreign keys', () => { @@ -2259,78 +2220,81 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); }); - it('should correctly get associations even after a child instance is deleted', function() { + it('should correctly get associations even after a child instance is deleted', async function() { const spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.User.create({ name: 'Matt' }), - this.Project.create({ name: 'Good Will Hunting' }), - this.Project.create({ name: 'The Departed' }) - ); - }).then(([user, project1, project2]) => { - return user.addProjects([project1, project2], { - logging: spy - }).return(user); - }).then(user => { - expect(spy).to.have.been.calledTwice; - spy.resetHistory(); - return Promise.join( - user, - user.getProjects({ - logging: spy - }) - ); - }).then(([user, projects]) => { - expect(spy.calledOnce).to.be.ok; - const project = projects[0]; - expect(project).to.be.ok; - return project.destroy().return(user); - }).then(user => { - return this.User.findOne({ - where: { id: user.id }, - include: [{ model: this.Project, as: 'Projects' }] - }); - }).then(user => { - const projects = user.Projects, - project = projects[0]; + await this.sequelize.sync({ force: true }); + + const [user3, project1, project2] = await Promise.all([ + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }), + this.Project.create({ name: 'The Departed' }) + ]); + + await user3.addProjects([project1, project2], { + logging: spy + }); + + const user2 = user3; + expect(spy).to.have.been.calledTwice; + spy.resetHistory(); + + const [user1, projects0] = await Promise.all([user2, user2.getProjects({ + logging: spy + })]); - expect(project).to.be.ok; + expect(spy.calledOnce).to.be.ok; + const project0 = projects0[0]; + expect(project0).to.be.ok; + await project0.destroy(); + const user0 = user1; + + const user = await this.User.findOne({ + where: { id: user0.id }, + include: [{ model: this.Project, as: 'Projects' }] }); + + const projects = user.Projects, + project = projects[0]; + + expect(project).to.be.ok; }); - it('should correctly get associations when doubly linked', function() { + it('should correctly get associations when doubly linked', async function() { const spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ name: 'Matt' }), - this.Project.create({ name: 'Good Will Hunting' }) - ]); - }).then(([user, project]) => { - this.user = user; - this.project = project; - return user.addProject(project, { logging: spy }).return(user); - }).then(user => { - expect(spy.calledTwice).to.be.ok; // Once for SELECT, once for INSERT - spy.resetHistory(); - return user.getProjects({ - logging: spy - }); - }).then(projects => { - const project = projects[0]; - expect(spy.calledOnce).to.be.ok; - spy.resetHistory(); + await this.sequelize.sync({ force: true }); + + const [user0, project0] = await Promise.all([ + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }) + ]); - expect(project).to.be.ok; - return this.user.removeProject(project, { - logging: spy - }).return(project); - }).then(() => { - expect(spy).to.have.been.calledOnce; + this.user = user0; + this.project = project0; + await user0.addProject(project0, { logging: spy }); + const user = user0; + expect(spy.calledTwice).to.be.ok; // Once for SELECT, once for INSERT + spy.resetHistory(); + + const projects = await user.getProjects({ + logging: spy }); + + const project = projects[0]; + expect(spy.calledOnce).to.be.ok; + spy.resetHistory(); + + expect(project).to.be.ok; + + await this.user.removeProject(project, { + logging: spy + }); + + await project; + expect(spy).to.have.been.calledOnce; }); - it('should be able to handle nested includes properly', function() { + it('should be able to handle nested includes properly', async function() { this.Group = this.sequelize.define('Group', { groupName: DataTypes.STRING }); this.Group.belongsToMany(this.User, { @@ -2358,41 +2322,41 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Group.create({ groupName: 'The Illuminati' }), - this.User.create({ name: 'Matt' }), - this.Project.create({ name: 'Good Will Hunting' }) - ); - }).then(([group, user, project]) => { - return user.addProject(project).then(() => { - return group.addUser(user).return(group); - }); - }).then(group => { - // get the group and include both the users in the group and their project's - return this.Group.findAll({ - where: { id: group.id }, - include: [ - { - model: this.User, - as: 'Users', - include: [ - { model: this.Project, as: 'Projects' } - ] - } - ] - }); - }).then(groups => { - const group = groups[0]; - expect(group).to.be.ok; + await this.sequelize.sync({ force: true }); - const user = group.Users[0]; - expect(user).to.be.ok; + const [group1, user0, project0] = await Promise.all([ + this.Group.create({ groupName: 'The Illuminati' }), + this.User.create({ name: 'Matt' }), + this.Project.create({ name: 'Good Will Hunting' }) + ]); - const project = user.Projects[0]; - expect(project).to.be.ok; - expect(project.name).to.equal('Good Will Hunting'); + await user0.addProject(project0); + await group1.addUser(user0); + const group0 = group1; + + // get the group and include both the users in the group and their project's + const groups = await this.Group.findAll({ + where: { id: group0.id }, + include: [ + { + model: this.User, + as: 'Users', + include: [ + { model: this.Project, as: 'Projects' } + ] + } + ] }); + + const group = groups[0]; + expect(group).to.be.ok; + + const user = group.Users[0]; + expect(user).to.be.ok; + + const project = user.Projects[0]; + expect(project).to.be.ok; + expect(project.name).to.equal('Good Will Hunting'); }); }); @@ -2451,15 +2415,16 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('without sync', () => { - beforeEach(function() { - return this.sequelize.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }).then(() => { - return this.sequelize.queryInterface.createTable('tasks', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); - }).then(() => { - return this.sequelize.queryInterface.createTable('users_tasks', { TaskId: DataTypes.INTEGER, UserId: DataTypes.INTEGER, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); - }); + beforeEach(async function() { + await this.sequelize.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); + await this.sequelize.queryInterface.createTable('tasks', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, title: DataTypes.STRING, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE }); + return this.sequelize.queryInterface.createTable( + 'users_tasks', + { TaskId: DataTypes.INTEGER, UserId: DataTypes.INTEGER, createdAt: DataTypes.DATE, updatedAt: DataTypes.DATE } + ); }); - it('removes all associations', function() { + it('removes all associations', async function() { this.UsersTasks = this.sequelize.define('UsersTasks', {}, { tableName: 'users_tasks' }); this.User.belongsToMany(this.Task, { through: this.UsersTasks }); @@ -2467,115 +2432,215 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { expect(Object.keys(this.UsersTasks.primaryKeys).sort()).to.deep.equal(['TaskId', 'UserId']); - return Promise.all([ + const [user0, task] = await Promise.all([ this.User.create({ username: 'foo' }), this.Task.create({ title: 'foo' }) - ]).then(([user, task]) => { - return user.addTask(task).return(user); - }).then(user => { - return user.setTasks(null); - }).then(result => { - expect(result).to.be.ok; - }); + ]); + + await user0.addTask(task); + const user = user0; + const result = await user.setTasks(null); + expect(result).to.be.ok; }); }); }); describe('through', () => { - beforeEach(function() { - this.User = this.sequelize.define('User', {}); - this.Project = this.sequelize.define('Project', {}); - this.UserProjects = this.sequelize.define('UserProjects', { - status: DataTypes.STRING, - data: DataTypes.INTEGER + describe('paranoid', () => { + beforeEach(async function() { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', {}, { + paranoid: true + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + await this.sequelize.sync(); + + this.users = await Promise.all([ + this.User.create(), + this.User.create(), + this.User.create() + ]); + + this.projects = await Promise.all([ + this.Project.create(), + this.Project.create(), + this.Project.create() + ]); + }); + + it('gets only non-deleted records by default', async function() { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + ProjectId: this.projects[0].id + } + }); + + const result = await this.users[0].getProjects(); + + expect(result.length).to.equal(2); + }); + + it('returns both deleted and non-deleted records with paranoid=false', async function() { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + ProjectId: this.projects[0].id + } + }); + + const result = await this.users[0].getProjects({ through: { paranoid: false } }); + + expect(result.length).to.equal(3); }); - this.User.belongsToMany(this.Project, { through: this.UserProjects }); - this.Project.belongsToMany(this.User, { through: this.UserProjects }); + it('hasAssociation also respects paranoid option', async function() { + await this.users[0].addProjects(this.projects); + await this.UserProjects.destroy({ + where: { + ProjectId: this.projects[0].id + } + }); - return this.sequelize.sync(); + expect( + await this.users[0].hasProjects(this.projects[0], { through: { paranoid: false } }) + ).to.equal(true); + + expect( + await this.users[0].hasProjects(this.projects[0]) + ).to.equal(false); + + expect( + await this.users[0].hasProjects(this.projects[1]) + ).to.equal(true); + + expect( + await this.users[0].hasProjects(this.projects) + ).to.equal(false); + }); }); describe('fetching from join table', () => { - it('should contain the data from the join table on .UserProjects a DAO', function() { - return Promise.all([ + beforeEach(function() { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', { + status: DataTypes.STRING, + data: DataTypes.INTEGER + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + return this.sequelize.sync(); + }); + + it('should contain the data from the join table on .UserProjects a DAO', async function() { + const [user0, project0] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).return(user); - }).then(user => { - return user.getProjects(); - }).then(projects => { - const project = projects[0]; - - expect(project.UserProjects).to.be.ok; - expect(project.status).not.to.exist; - expect(project.UserProjects.status).to.equal('active'); - expect(project.UserProjects.data).to.equal(42); - }); + ]); + + await user0.addProject(project0, { through: { status: 'active', data: 42 } }); + const user = user0; + const projects = await user.getProjects(); + const project = projects[0]; + + expect(project.UserProjects).to.be.ok; + expect(project.status).not.to.exist; + expect(project.UserProjects.status).to.equal('active'); + expect(project.UserProjects.data).to.equal(42); }); - it('should be able to limit the join table attributes returned', function() { - return Promise.all([ + it('should be able to alias the default name of the join table', async function() { + const [user, project0] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([user, project]) => { - return user.addProject(project, { through: { status: 'active', data: 42 } }).return(user); - }).then(user => { - return user.getProjects({ joinTableAttributes: ['status'] }); - }).then(projects => { - const project = projects[0]; - - expect(project.UserProjects).to.be.ok; - expect(project.status).not.to.exist; - expect(project.UserProjects.status).to.equal('active'); - expect(project.UserProjects.data).not.to.exist; + ]); + + await user.addProject(project0, { through: { status: 'active', data: 42 } }); + + const users = await this.User.findAll({ + include: [{ + model: this.Project, + through: { + as: 'myProject' + } + }] }); + + const project = users[0].Projects[0]; + + expect(project.UserProjects).not.to.exist; + expect(project.status).not.to.exist; + expect(project.myProject).to.be.ok; + expect(project.myProject.status).to.equal('active'); + expect(project.myProject.data).to.equal(42); + }); + + it('should be able to limit the join table attributes returned', async function() { + const [user0, project0] = await Promise.all([ + this.User.create(), + this.Project.create() + ]); + + await user0.addProject(project0, { through: { status: 'active', data: 42 } }); + const user = user0; + const projects = await user.getProjects({ joinTableAttributes: ['status'] }); + const project = projects[0]; + + expect(project.UserProjects).to.be.ok; + expect(project.status).not.to.exist; + expect(project.UserProjects.status).to.equal('active'); + expect(project.UserProjects.data).not.to.exist; }); }); describe('inserting in join table', () => { + beforeEach(function() { + this.User = this.sequelize.define('User', {}); + this.Project = this.sequelize.define('Project', {}); + this.UserProjects = this.sequelize.define('UserProjects', { + status: DataTypes.STRING, + data: DataTypes.INTEGER + }); + + this.User.belongsToMany(this.Project, { through: this.UserProjects }); + this.Project.belongsToMany(this.User, { through: this.UserProjects }); + + return this.sequelize.sync(); + }); + describe('add', () => { - it('should insert data provided on the object into the join table', function() { - const ctx = { - UserProjects: this.UserProjects - }; - return Promise.all([ + it('should insert data provided on the object into the join table', async function() { + const [u, p] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([u, p]) => { - ctx.u = u; - ctx.p = p; - p.UserProjects = { status: 'active' }; - - return u.addProject(p); - }).then(() => { - return ctx.UserProjects.findOne({ where: { UserId: ctx.u.id, ProjectId: ctx.p.id } }); - }).then(up => { - expect(up.status).to.equal('active'); - }); + ]); + + p.UserProjects = { status: 'active' }; + + await u.addProject(p); + const up = await this.UserProjects.findOne({ where: { UserId: u.id, ProjectId: p.id } }); + expect(up.status).to.equal('active'); }); - it('should insert data provided as a second argument into the join table', function() { - const ctx = { - UserProjects: this.UserProjects - }; - return Promise.all([ + it('should insert data provided as a second argument into the join table', async function() { + const [u, p] = await Promise.all([ this.User.create(), this.Project.create() - ]).then(([u, p]) => { - ctx.u = u; - ctx.p = p; - - return u.addProject(p, { through: { status: 'active' } }); - }).then(() => { - return ctx.UserProjects.findOne({ where: { UserId: ctx.u.id, ProjectId: ctx.p.id } }); - }).then(up => { - expect(up.status).to.equal('active'); - }); + ]); + + await u.addProject(p, { through: { status: 'active' } }); + const up = await this.UserProjects.findOne({ where: { UserId: u.id, ProjectId: p.id } }); + expect(up.status).to.equal('active'); }); - it('should be able to add twice (second call result in UPDATE call) without any attributes (and timestamps off) on the through model', function() { + it('should be able to add twice (second call result in UPDATE call) without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2583,20 +2648,14 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Worker.belongsToMany(Task, { through: WorkerTasks }); Task.belongsToMany(Worker, { through: WorkerTasks }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Worker.create({ id: 1337 }); - }).then(worker => { - ctx.worker = worker; - return Task.create({ id: 7331 }); - }).then(() => { - return ctx.worker.addTask(ctx.task); - }).then(() => { - return ctx.worker.addTask(ctx.task); - }); + await this.sequelize.sync({ force: true }); + const worker = await Worker.create({ id: 1337 }); + const task = await Task.create({ id: 7331 }); + await worker.addTask(task); + await worker.addTask(task); }); - it('should be able to add twice (second call result in UPDATE call) with custom primary keys and without any attributes (and timestamps off) on the through model', function() { + it('should be able to add twice (second call result in UPDATE call) with custom primary keys and without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', { id: { type: DataTypes.INTEGER, @@ -2625,87 +2684,77 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Worker.belongsToMany(Task, { through: WorkerTasks }); Task.belongsToMany(Worker, { through: WorkerTasks }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Worker.create({ id: 1337 }); - }).then(worker => { - ctx.worker = worker; - return Task.create({ id: 7331 }); - }).then(task => { - ctx.task = task; - return ctx.worker.addTask(ctx.task); - }).then(() => { - return ctx.worker.addTask(ctx.task); - }); + await this.sequelize.sync({ force: true }); + const worker = await Worker.create({ id: 1337 }); + const task = await Task.create({ id: 7331 }); + await worker.addTask(task); + await worker.addTask(task); }); - it('should be able to create an instance along with its many-to-many association which has an extra column in the junction table', function() { + it('should be able to create an instance along with its many-to-many association which has an extra column in the junction table', async function() { const Foo = this.sequelize.define('foo', { name: Sequelize.STRING }); const Bar = this.sequelize.define('bar', { name: Sequelize.STRING }); const FooBar = this.sequelize.define('foobar', { baz: Sequelize.STRING }); Foo.belongsToMany(Bar, { through: FooBar }); Bar.belongsToMany(Foo, { through: FooBar }); - return this.sequelize.sync({ force: true }).then(() => { - return Foo.create({ - name: 'foo...', - bars: [ - { - name: 'bar...', - foobar: { - baz: 'baz...' - } + await this.sequelize.sync({ force: true }); + + const foo0 = await Foo.create({ + name: 'foo...', + bars: [ + { + name: 'bar...', + foobar: { + baz: 'baz...' } - ] - }, { - include: Bar - }); - }).then(foo => { - expect(foo.name).to.equal('foo...'); - expect(foo.bars).to.have.length(1); - expect(foo.bars[0].name).to.equal('bar...'); - expect(foo.bars[0].foobar).to.not.equal(null); - expect(foo.bars[0].foobar.baz).to.equal('baz...'); - - return Foo.findOne({ include: Bar }); - }).then(foo => { - expect(foo.name).to.equal('foo...'); - expect(foo.bars).to.have.length(1); - expect(foo.bars[0].name).to.equal('bar...'); - expect(foo.bars[0].foobar).to.not.equal(null); - expect(foo.bars[0].foobar.baz).to.equal('baz...'); + } + ] + }, { + include: Bar }); + + expect(foo0.name).to.equal('foo...'); + expect(foo0.bars).to.have.length(1); + expect(foo0.bars[0].name).to.equal('bar...'); + expect(foo0.bars[0].foobar).to.not.equal(null); + expect(foo0.bars[0].foobar.baz).to.equal('baz...'); + + const foo = await Foo.findOne({ include: Bar }); + expect(foo.name).to.equal('foo...'); + expect(foo.bars).to.have.length(1); + expect(foo.bars[0].name).to.equal('bar...'); + expect(foo.bars[0].foobar).to.not.equal(null); + expect(foo.bars[0].foobar.baz).to.equal('baz...'); }); }); describe('set', () => { - it('should be able to combine properties on the associated objects, and default values', function() { - const ctx = {}; - return Promise.all([ + it('should be able to combine properties on the associated objects, and default values', async function() { + await this.Project.bulkCreate([{}, {}]); + + const [user, projects] = await Promise.all([ this.User.create(), - this.Project.bulkCreate([{}, {}]).then(() => { - return this.Project.findAll(); - }) - ]).then(([user, projects]) => { - ctx.user = user; - ctx.p1 = projects[0]; - ctx.p2 = projects[1]; - - ctx.p1.UserProjects = { status: 'inactive' }; - - return user.setProjects([ctx.p1, ctx.p2], { through: { status: 'active' } }); - }).then(() => { - return Promise.all([ - this.UserProjects.findOne({ where: { UserId: ctx.user.id, ProjectId: ctx.p1.id } }), - this.UserProjects.findOne({ where: { UserId: ctx.user.id, ProjectId: ctx.p2.id } }) - ]); - }).then(([up1, up2]) => { - expect(up1.status).to.equal('inactive'); - expect(up2.status).to.equal('active'); - }); + await this.Project.findAll() + ]); + + const p1 = projects[0]; + const p2 = projects[1]; + + p1.UserProjects = { status: 'inactive' }; + + await user.setProjects([p1, p2], { through: { status: 'active' } }); + + const [up1, up2] = await Promise.all([ + this.UserProjects.findOne({ where: { UserId: user.id, ProjectId: p1.id } }), + this.UserProjects.findOne({ where: { UserId: user.id, ProjectId: p2.id } }) + ]); + + expect(up1.status).to.equal('inactive'); + expect(up2.status).to.equal('active'); }); - it('should be able to set twice (second call result in UPDATE calls) without any attributes (and timestamps off) on the through model', function() { + it('should be able to set twice (second call result in UPDATE calls) without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2713,44 +2762,45 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Worker.belongsToMany(Task, { through: WorkerTasks }); Task.belongsToMany(Worker, { through: WorkerTasks }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Worker.create(), - Task.bulkCreate([{}, {}]).then(() => { - return Task.findAll(); - }) - ]); - }).then(([worker, tasks]) => { - return worker.setTasks(tasks).return([worker, tasks]); - }).then(([worker, tasks]) => { - return worker.setTasks(tasks); - }); + await this.sequelize.sync({ force: true }); + + const [worker0, tasks0] = await Promise.all([ + Worker.create(), + Task.bulkCreate([{}, {}]).then(() => { + return Task.findAll(); + }) + ]); + + await worker0.setTasks(tasks0); + const [worker, tasks] = [worker0, tasks0]; + + await worker.setTasks(tasks); }); }); describe('query with through.where', () => { - it('should support query the through model', function() { - return this.User.create().then(user => { - return Promise.all([ - user.createProject({}, { through: { status: 'active', data: 1 } }), - user.createProject({}, { through: { status: 'inactive', data: 2 } }), - user.createProject({}, { through: { status: 'inactive', data: 3 } }) - ]).then(() => { - return Promise.all([ - user.getProjects({ through: { where: { status: 'active' } } }), - user.countProjects({ through: { where: { status: 'inactive' } } }) - ]); - }); - }).then(([activeProjects, inactiveProjectCount]) => { - expect(activeProjects).to.have.lengthOf(1); - expect(inactiveProjectCount).to.eql(2); - }); + it('should support query the through model', async function() { + const user = await this.User.create(); + + await Promise.all([ + user.createProject({}, { through: { status: 'active', data: 1 } }), + user.createProject({}, { through: { status: 'inactive', data: 2 } }), + user.createProject({}, { through: { status: 'inactive', data: 3 } }) + ]); + + const [activeProjects, inactiveProjectCount] = await Promise.all([ + user.getProjects({ through: { where: { status: 'active' } } }), + user.countProjects({ through: { where: { status: 'inactive' } } }) + ]); + + expect(activeProjects).to.have.lengthOf(1); + expect(inactiveProjectCount).to.eql(2); }); }); }); describe('removing from the join table', () => { - it('should remove a single entry without any attributes (and timestamps off) on the through model', function() { + it('should remove a single entry without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2759,28 +2809,25 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.belongsToMany(Worker, { through: WorkerTasks }); // Test setup - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ - Worker.create({}), - Task.bulkCreate([{}, {}, {}]).then(() => { - return Task.findAll(); - }) - ]); - }).then(([worker, tasks]) => { - // Set all tasks, then remove one task by instance, then remove one task by id, then return all tasks - return worker.setTasks(tasks).then(() => { - return worker.removeTask(tasks[0]); - }).then(() => { - return worker.removeTask(tasks[1].id); - }).then(() => { - return worker.getTasks(); - }); - }).then(tasks => { - expect(tasks.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + + const [worker, tasks0] = await Promise.all([ + Worker.create({}), + Task.bulkCreate([{}, {}, {}]).then(() => { + return Task.findAll(); + }) + ]); + + // Set all tasks, then remove one task by instance, then remove one task by id, then return all tasks + await worker.setTasks(tasks0); + + await worker.removeTask(tasks0[0]); + await worker.removeTask(tasks0[1].id); + const tasks = await worker.getTasks(); + expect(tasks.length).to.equal(1); }); - it('should remove multiple entries without any attributes (and timestamps off) on the through model', function() { + it('should remove multiple entries without any attributes (and timestamps off) on the through model', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }), Task = this.sequelize.define('Task', {}, { timestamps: false }), WorkerTasks = this.sequelize.define('WorkerTasks', {}, { timestamps: false }); @@ -2789,25 +2836,22 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { Task.belongsToMany(Worker, { through: WorkerTasks }); // Test setup - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ - Worker.create({}), - Task.bulkCreate([{}, {}, {}, {}, {}]).then(() => { - return Task.findAll(); - }) - ]); - }).then(([worker, tasks]) => { - // Set all tasks, then remove two tasks by instance, then remove two tasks by id, then return all tasks - return worker.setTasks(tasks).then(() => { - return worker.removeTasks([tasks[0], tasks[1]]); - }).then(() => { - return worker.removeTasks([tasks[2].id, tasks[3].id]); - }).then(() => { - return worker.getTasks(); - }); - }).then(tasks => { - expect(tasks.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + + const [worker, tasks0] = await Promise.all([ + Worker.create({}), + Task.bulkCreate([{}, {}, {}, {}, {}]).then(() => { + return Task.findAll(); + }) + ]); + + // Set all tasks, then remove two tasks by instance, then remove two tasks by id, then return all tasks + await worker.setTasks(tasks0); + + await worker.removeTasks([tasks0[0], tasks0[1]]); + await worker.removeTasks([tasks0[2].id, tasks0[3].id]); + const tasks = await worker.getTasks(); + expect(tasks.length).to.equal(1); }); }); }); @@ -2827,18 +2871,17 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('correctly uses bId in A', function() { - const a1 = new this.A({ name: 'a1' }), - b1 = new this.B({ name: 'b1' }); + it('correctly uses bId in A', async function() { + const a1 = this.A.build({ name: 'a1' }), + b1 = this.B.build({ name: 'b1' }); - return a1 - .save() - .then(() => { return b1.save(); }) - .then(() => { return a1.setRelation1(b1); }) - .then(() => { return this.A.findOne({ where: { name: 'a1' } }); }) - .then(a => { - expect(a.relation1Id).to.be.eq(b1.id); - }); + await a1 + .save(); + + await b1.save(); + await a1.setRelation1(b1); + const a = await this.A.findOne({ where: { name: 'a1' } }); + expect(a.relation1Id).to.be.eq(b1.id); }); }); @@ -2851,42 +2894,39 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('correctly uses bId in A', function() { - const a1 = new this.A({ name: 'a1' }), - b1 = new this.B({ name: 'b1' }); + it('correctly uses bId in A', async function() { + const a1 = this.A.build({ name: 'a1' }), + b1 = this.B.build({ name: 'b1' }); - return a1 - .save() - .then(() => { return b1.save(); }) - .then(() => { return b1.setRelation1(a1); }) - .then(() => { return this.B.findOne({ where: { name: 'b1' } }); }) - .then(b => { - expect(b.relation1Id).to.be.eq(a1.id); - }); + await a1 + .save(); + + await b1.save(); + await b1.setRelation1(a1); + const b = await this.B.findOne({ where: { name: 'b1' } }); + expect(b.relation1Id).to.be.eq(a1.id); }); }); }); describe('alias', () => { - it('creates the join table when through is a string', function() { + it('creates the join table when through is a string', async function() { const User = this.sequelize.define('User', {}); const Group = this.sequelize.define('Group', {}); User.belongsToMany(Group, { as: 'MyGroups', through: 'group_user' }); Group.belongsToMany(User, { as: 'MyUsers', through: 'group_user' }); - return this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showAllTables(); - }).then(result => { - if (dialect === 'mssql' || dialect === 'mariadb') { - result = result.map(v => v.tableName); - } + await this.sequelize.sync({ force: true }); + let result = await this.sequelize.getQueryInterface().showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + result = result.map(v => v.tableName); + } - expect(result.includes('group_user')).to.be.true; - }); + expect(result.includes('group_user')).to.be.true; }); - it('creates the join table when through is a model', function() { + it('creates the join table when through is a model', async function() { const User = this.sequelize.define('User', {}); const Group = this.sequelize.define('Group', {}); const UserGroup = this.sequelize.define('GroupUser', {}, { tableName: 'user_groups' }); @@ -2894,15 +2934,13 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(Group, { as: 'MyGroups', through: UserGroup }); Group.belongsToMany(User, { as: 'MyUsers', through: UserGroup }); - return this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showAllTables(); - }).then(result => { - if (dialect === 'mssql' || dialect === 'mariadb') { - result = result.map(v => v.tableName); - } + await this.sequelize.sync({ force: true }); + let result = await this.sequelize.getQueryInterface().showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + result = result.map(v => v.tableName); + } - expect(result).to.include('user_groups'); - }); + expect(result).to.include('user_groups'); }); it('correctly identifies its counterpart when through is a string', function() { @@ -2949,17 +2987,19 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { return this.sequelize.sync({ force: true }); }); - it('correctly sets user and owner', function() { - const p1 = new this.Project({ projectName: 'p1' }), - u1 = new this.User({ name: 'u1' }), - u2 = new this.User({ name: 'u2' }); + it('correctly sets user and owner', async function() { + const p1 = this.Project.build({ projectName: 'p1' }), + u1 = this.User.build({ name: 'u1' }), + u2 = this.User.build({ name: 'u2' }); + + await p1 + .save(); - return p1 - .save() - .then(() => { return u1.save(); }) - .then(() => { return u2.save(); }) - .then(() => { return p1.setUsers([u1]); }) - .then(() => { return p1.setOwners([u2]); }); + await u1.save(); + await u2.save(); + await p1.setUsers([u1]); + + await p1.setOwners([u2]); }); }); }); @@ -2971,159 +3011,138 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { this.UserTasks = this.sequelize.define('tasksusers', { userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER }); }); - it('can cascade deletes both ways by default', function() { + it('can cascade deletes both ways by default', async function() { this.User.belongsToMany(this.Task, { through: 'tasksusers' }); this.Task.belongsToMany(this.User, { through: 'tasksusers' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ + await this.sequelize.sync({ force: true }); + + const [user1, task1, user2, task2] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }), + this.User.create({ id: 89, username: 'bar' }), + this.Task.create({ id: 42, title: 'kast' }) + ]); + + await Promise.all([ + user1.setTasks([task1]), + task2.setUsers([user2]) + ]); + + await Promise.all([ + user1.destroy(), + task2.destroy() + ]); + + const [tu1, tu2] = await Promise.all([ + this.sequelize.model('tasksusers').findAll({ where: { userId: user1.id } }), + this.sequelize.model('tasksusers').findAll({ where: { taskId: task2.id } }), + this.User.findOne({ + where: this.sequelize.or({ username: 'Franz Joseph' }), + include: [{ + model: this.Task, + where: { + title: { + [Op.ne]: 'task' + } + } + }] + }) + ]); + + expect(tu1).to.have.length(0); + expect(tu2).to.have.length(0); + }); + + if (current.dialect.supports.constraints.restrict) { + + it('can restrict deletes both ways', async function() { + this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); + this.Task.belongsToMany(this.User, { onDelete: 'RESTRICT', through: 'tasksusers' }); + + await this.sequelize.sync({ force: true }); + + const [user1, task1, user2, task2] = await Promise.all([ this.User.create({ id: 67, username: 'foo' }), this.Task.create({ id: 52, title: 'task' }), this.User.create({ id: 89, username: 'bar' }), this.Task.create({ id: 42, title: 'kast' }) ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ + + await Promise.all([ user1.setTasks([task1]), task2.setUsers([user2]) ]); - }).then(() => { - return Promise.all([ - ctx.user1.destroy(), - ctx.task2.destroy() - ]); - }).then(() => { - return Promise.all([ - this.sequelize.model('tasksusers').findAll({ where: { userId: ctx.user1.id } }), - this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }), - this.User.findOne({ - where: this.sequelize.or({ username: 'Franz Joseph' }), - include: [{ - model: this.Task, - where: { - title: { - [Op.ne]: 'task' - } - } - }] - }) - ]); - }).then(([tu1, tu2]) => { - expect(tu1).to.have.length(0); - expect(tu2).to.have.length(0); - }); - }); - - if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes both ways', function() { - this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { onDelete: 'RESTRICT', through: 'tasksusers' }); - - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }), - this.User.create({ id: 89, username: 'bar' }), - this.Task.create({ id: 42, title: 'kast' }) - ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ - user1.setTasks([task1]), - task2.setUsers([user2]) - ]); - }).then(() => { - return Promise.all([ - expect(ctx.user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint - expect(ctx.task2.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError) - ]); - }); + await Promise.all([ + expect(user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint + expect(task2.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError) + ]); }); - it('can cascade and restrict deletes', function() { + it('can cascade and restrict deletes', async function() { this.User.belongsToMany(this.Task, { onDelete: 'RESTRICT', through: 'tasksusers' }); this.Task.belongsToMany(this.User, { onDelete: 'CASCADE', through: 'tasksusers' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.join( - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }), - this.User.create({ id: 89, username: 'bar' }), - this.Task.create({ id: 42, title: 'kast' }) - ); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Sequelize.Promise.join( - user1.setTasks([task1]), - task2.setUsers([user2]) - ); - }).then(() => { - return Sequelize.Promise.join( - expect(ctx.user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint - ctx.task2.destroy() - ); - }).then(() => { - return this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }); - }).then(usertasks => { - // This should not exist because deletes cascade - expect(usertasks).to.have.length(0); - }); - }); - - } - - it('should be possible to remove all constraints', function() { - this.User.belongsToMany(this.Task, { constraints: false, through: 'tasksusers' }); - this.Task.belongsToMany(this.User, { constraints: false, through: 'tasksusers' }); + await this.sequelize.sync({ force: true }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ + const [user1, task1, user2, task2] = await Promise.all([ this.User.create({ id: 67, username: 'foo' }), this.Task.create({ id: 52, title: 'task' }), this.User.create({ id: 89, username: 'bar' }), this.Task.create({ id: 42, title: 'kast' }) ]); - }).then(([user1, task1, user2, task2]) => { - ctx.user1 = user1; - ctx.task1 = task1; - ctx.user2 = user2; - ctx.task2 = task2; - return Promise.all([ + + await Promise.all([ user1.setTasks([task1]), task2.setUsers([user2]) ]); - }).then(() => { - return Promise.all([ - ctx.user1.destroy(), - ctx.task2.destroy() - ]); - }).then(() => { - return Promise.all([ - this.sequelize.model('tasksusers').findAll({ where: { userId: ctx.user1.id } }), - this.sequelize.model('tasksusers').findAll({ where: { taskId: ctx.task2.id } }) + + await Promise.all([ + expect(user1.destroy()).to.have.been.rejectedWith(Sequelize.ForeignKeyConstraintError), // Fails because of RESTRICT constraint + task2.destroy() ]); - }).then(([ut1, ut2]) => { - expect(ut1).to.have.length(1); - expect(ut2).to.have.length(1); + + const usertasks = await this.sequelize.model('tasksusers').findAll({ where: { taskId: task2.id } }); + // This should not exist because deletes cascade + expect(usertasks).to.have.length(0); }); + + } + + it('should be possible to remove all constraints', async function() { + this.User.belongsToMany(this.Task, { constraints: false, through: 'tasksusers' }); + this.Task.belongsToMany(this.User, { constraints: false, through: 'tasksusers' }); + + await this.sequelize.sync({ force: true }); + + const [user1, task1, user2, task2] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }), + this.User.create({ id: 89, username: 'bar' }), + this.Task.create({ id: 42, title: 'kast' }) + ]); + + await Promise.all([ + user1.setTasks([task1]), + task2.setUsers([user2]) + ]); + + await Promise.all([ + user1.destroy(), + task2.destroy() + ]); + + const [ut1, ut2] = await Promise.all([ + this.sequelize.model('tasksusers').findAll({ where: { userId: user1.id } }), + this.sequelize.model('tasksusers').findAll({ where: { taskId: task2.id } }) + ]); + + expect(ut1).to.have.length(1); + expect(ut2).to.have.length(1); }); - it('create custom unique identifier', function() { + it('create custom unique identifier', async function() { this.UserTasksLong = this.sequelize.define('table_user_task_with_very_long_name', { id_user_very_long_field: { type: DataTypes.INTEGER(1) @@ -3146,10 +3165,9 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { uniqueKey: 'custom_user_group_unique' }); - return this.sequelize.sync({ force: true }).then(() => { - expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique'); - expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_task_very_long_field.unique).to.equal('custom_user_group_unique'); - }); + await this.sequelize.sync({ force: true }); + expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_user_very_long_field.unique).to.equal('custom_user_group_unique'); + expect(this.Task.associations.MyUsers.through.model.rawAttributes.id_task_very_long_field.unique).to.equal('custom_user_group_unique'); }); }); @@ -3183,7 +3201,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); describe('thisAssociations', () => { - it('should work with this reference', function() { + it('should work with this reference', async function() { const User = this.sequelize.define('User', { name: Sequelize.STRING(100) }), @@ -3192,24 +3210,22 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { User.belongsToMany(User, { through: Follow, as: 'User' }); User.belongsToMany(User, { through: Follow, as: 'Fan' }); - return this.sequelize.sync({ force: true }) - .then(() => { - return Sequelize.Promise.all([ - User.create({ name: 'Khsama' }), - User.create({ name: 'Vivek' }), - User.create({ name: 'Satya' }) - ]); - }) - .then(users => { - return Sequelize.Promise.all([ - users[0].addFan(users[1]), - users[1].addUser(users[2]), - users[2].addFan(users[0]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([ + User.create({ name: 'Khsama' }), + User.create({ name: 'Vivek' }), + User.create({ name: 'Satya' }) + ]); + + await Promise.all([ + users[0].addFan(users[1]), + users[1].addUser(users[2]), + users[2].addFan(users[0]) + ]); }); - it('should work with custom this reference', function() { + it('should work with custom this reference', async function() { const User = this.sequelize.define('User', { name: Sequelize.STRING(100) }), @@ -3232,21 +3248,19 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { through: 'Invites' }); - return this.sequelize.sync({ force: true }) - .then(() => { - return Sequelize.Promise.all([ - User.create({ name: 'Jalrangi' }), - User.create({ name: 'Sargrahi' }) - ]); - }) - .then(users => { - return Sequelize.Promise.all([ - users[0].addFollower(users[1]), - users[1].addFollower(users[0]), - users[0].addInvitee(users[1]), - users[1].addInvitee(users[0]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([ + User.create({ name: 'Jalrangi' }), + User.create({ name: 'Sargrahi' }) + ]); + + await Promise.all([ + users[0].addFollower(users[1]), + users[1].addFollower(users[0]), + users[0].addInvitee(users[1]), + users[1].addInvitee(users[0]) + ]); }); it('should setup correct foreign keys', function() { @@ -3301,60 +3315,62 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghats' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); - }).then(() => { - return this.Hat.findOne({ - where: { name: 'Baz' }, - include: [{ model: this.Individual, as: 'hatwornbys' }] - }); - }).then(hat => { - expect(hat.name).to.equal('Baz'); - expect(hat.hatwornbys.length).to.equal(1); - expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); - }); - }); - - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); - }).then(() => { - return this.Hat.findOne({ - where: { name: 'Baz' }, - include: [{ all: true }] - }); - }).then(hat => { - expect(hat.name).to.equal('Baz'); - expect(hat.hatwornbys.length).to.equal(1); - expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat0] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat0); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghats' }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); + + const hat = await this.Hat.findOne({ + where: { name: 'Baz' }, + include: [{ model: this.Individual, as: 'hatwornbys' }] + }); + + expect(hat.name).to.equal('Baz'); + expect(hat.hatwornbys.length).to.equal(1); + expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); + }); + + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat0] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat0); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] + }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); + + const hat = await this.Hat.findOne({ + where: { name: 'Baz' }, + include: [{ all: true }] + }); + + expect(hat.name).to.equal('Baz'); + expect(hat.hatwornbys.length).to.equal(1); + expect(hat.hatwornbys[0].name).to.equal('Foo Bar'); }); }); }); diff --git a/test/integration/associations/belongs-to.test.js b/test/integration/associations/belongs-to.test.js index 4537576f52ff..e9cbaeaacb31 100644 --- a/test/integration/associations/belongs-to.test.js +++ b/test/integration/associations/belongs-to.test.js @@ -6,7 +6,6 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, current = Support.sequelize, dialect = Support.getTestDialect(); @@ -28,133 +27,108 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { describe('get', () => { describe('multiple', () => { - it('should fetch associations for multiple instances', function() { + it('should fetch associations for multiple instances', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', {}); Task.User = Task.belongsTo(User, { as: 'user' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Task.create({ - id: 1, - user: { id: 1 } - }, { - include: [Task.User] - }), - Task.create({ - id: 2, - user: { id: 2 } - }, { - include: [Task.User] - }), - Task.create({ - id: 3 - }) - ); - }).then(tasks => { - return Task.User.get(tasks).then(result => { - expect(result[tasks[0].id].id).to.equal(tasks[0].user.id); - expect(result[tasks[1].id].id).to.equal(tasks[1].user.id); - expect(result[tasks[2].id]).to.be.undefined; - }); - }); + await this.sequelize.sync({ force: true }); + + const tasks = await Promise.all([Task.create({ + id: 1, + user: { id: 1 } + }, { + include: [Task.User] + }), Task.create({ + id: 2, + user: { id: 2 } + }, { + include: [Task.User] + }), Task.create({ + id: 3 + })]); + + const result = await Task.User.get(tasks); + expect(result[tasks[0].id].id).to.equal(tasks[0].user.id); + expect(result[tasks[1].id].id).to.equal(tasks[1].user.id); + expect(result[tasks[2].id]).to.be.undefined; }); }); }); describe('getAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.belongsTo(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return Group.findAll({ transaction: t }).then(groups => { - return groups[0].getUser({ transaction: t }).then(associatedUser => { - expect(associatedUser).to.be.not.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + const groups0 = await Group.findAll({ transaction: t }); + const associatedUser0 = await groups0[0].getUser({ transaction: t }); + expect(associatedUser0).to.be.not.null; + await t.rollback(); }); } - it('should be able to handle a where object that\'s a first class citizen.', function() { + it('should be able to handle a where object that\'s a first class citizen.', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }); Task.belongsTo(User); - return User.sync({ force: true }).then(() => { - // Can't use Promise.all cause of foreign key references - return Task.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - User.create({ username: 'bar', gender: 'female' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - }).then(([userA, , task]) => { - return task.setUserXYZ(userA).then(() => { - return task.getUserXYZ({ where: { gender: 'female' } }); - }); - }).then(user => { - expect(user).to.be.null; - }); + await User.sync({ force: true }); + // Can't use Promise.all cause of foreign key references + await Task.sync({ force: true }); + + const [userA, , task] = await Promise.all([ + User.create({ username: 'foo', gender: 'male' }), + User.create({ username: 'bar', gender: 'female' }), + Task.create({ title: 'task', status: 'inactive' }) + ]); + + await task.setUserXYZ(userA); + const user = await task.getUserXYZ({ where: { gender: 'female' } }); + expect(user).to.be.null; }); - it('supports schemas', function() { + it('supports schemas', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }).schema('archive'), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }).schema('archive'); Task.belongsTo(User); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('archive'); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - }).then(([user, task]) => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ(); - }); - }).then(user => { - expect(user).to.be.ok; - return this.sequelize.dropSchema('archive').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('archive'); - } - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('archive'); + await User.sync({ force: true }); + await Task.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ username: 'foo', gender: 'male' }), + Task.create({ title: 'task', status: 'inactive' }) + ]); + + await task.setUserXYZ(user0); + const user = await task.getUserXYZ(); + expect(user).to.be.ok; + await this.sequelize.dropSchema('archive'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('archive'); + } }); - it('supports schemas when defining custom foreign key attribute #9029', function() { + it('supports schemas when defining custom foreign key attribute #9029', async function() { const User = this.sequelize.define('UserXYZ', { uid: { type: Sequelize.INTEGER, @@ -172,160 +146,120 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.belongsTo(User, { foreignKey: 'user_id' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('archive'); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return User.create({}); - }).then(user => { - return Task.create({}).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ(); - }); - }); - }).then(user => { - expect(user).to.be.ok; - return this.sequelize.dropSchema('archive'); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('archive'); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user0 = await User.create({}); + const task = await Task.create({}); + await task.setUserXYZ(user0); + const user = await task.getUserXYZ(); + expect(user).to.be.ok; + + await this.sequelize.dropSchema('archive'); }); }); describe('setAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.belongsTo(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + await t.rollback(); }); } - it('can set the association with declared primary keys...', function() { + it('can set the association with declared primary keys...', async function() { const User = this.sequelize.define('UserXYZ', { user_id: { type: DataTypes.INTEGER, primaryKey: true }, username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { task_id: { type: DataTypes.INTEGER, primaryKey: true }, title: DataTypes.STRING }); Task.belongsTo(User, { foreignKey: 'user_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ user_id: 1, username: 'foo' }).then(user => { - return Task.create({ task_id: 1, title: 'task' }).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ().then(user => { - expect(user).not.to.be.null; - - return task.setUserXYZ(null).then(() => { - return task.getUserXYZ().then(user => { - expect(user).to.be.null; - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ user_id: 1, username: 'foo' }); + const task = await Task.create({ task_id: 1, title: 'task' }); + await task.setUserXYZ(user); + const user1 = await task.getUserXYZ(); + expect(user1).not.to.be.null; + + await task.setUserXYZ(null); + const user0 = await task.getUserXYZ(); + expect(user0).to.be.null; }); - it('clears the association if null is passed', function() { + it('clears the association if null is passed', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ().then(user => { - expect(user).not.to.be.null; - - return task.setUserXYZ(null).then(() => { - return task.getUserXYZ().then(user => { - expect(user).to.be.null; - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUserXYZ(user); + const user1 = await task.getUserXYZ(); + expect(user1).not.to.be.null; + + await task.setUserXYZ(null); + const user0 = await task.getUserXYZ(); + expect(user0).to.be.null; }); - it('should throw a ForeignKeyConstraintError if the associated record does not exist', function() { + it('should throw a ForeignKeyConstraintError if the associated record does not exist', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.create({ title: 'task' }).then(task => { - return expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const task = await Task.create({ title: 'task' }); + + await expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id: 15, username: 'jansemand' }).then(user => { - return Task.create({}).then(task => { - return task.setUserXYZ(user.id).then(() => { - return task.getUserXYZ().then(user => { - expect(user.username).to.equal('jansemand'); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ id: 15, username: 'jansemand' }); + const task = await Task.create({}); + await task.setUserXYZ(user.id); + const user0 = await task.getUserXYZ(); + expect(user0.username).to.equal('jansemand'); }); - it('should support logging', function() { + it('should support logging', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), Task = this.sequelize.define('TaskXYZ', { title: DataTypes.STRING }), spy = sinon.spy(); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return Task.create({}).then(task => { - return task.setUserXYZ(user, { logging: spy }).then(() => { - expect(spy.called).to.be.ok; - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create(); + const task = await Task.create({}); + await task.setUserXYZ(user, { logging: spy }); + expect(spy.called).to.be.ok; }); - it('should not clobber atributes', function() { + it('should not clobber atributes', async function() { const Comment = this.sequelize.define('comment', { text: DataTypes.STRING }); @@ -337,23 +271,22 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Post.hasOne(Comment); Comment.belongsTo(Post); - return this.sequelize.sync().then(() => { - return Post.create({ - title: 'Post title' - }).then(post => { - return Comment.create({ - text: 'OLD VALUE' - }).then(comment => { - comment.text = 'UPDATED VALUE'; - return comment.setPost(post).then(() => { - expect(comment.text).to.equal('UPDATED VALUE'); - }); - }); - }); + await this.sequelize.sync(); + + const post = await Post.create({ + title: 'Post title' + }); + + const comment = await Comment.create({ + text: 'OLD VALUE' }); + + comment.text = 'UPDATED VALUE'; + await comment.setPost(post); + expect(comment.text).to.equal('UPDATED VALUE'); }); - it('should set the foreign key value without saving when using save: false', function() { + it('should set the foreign key value without saving when using save: false', async function() { const Comment = this.sequelize.define('comment', { text: DataTypes.STRING }); @@ -365,89 +298,66 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Post.hasMany(Comment, { foreignKey: 'post_id' }); Comment.belongsTo(Post, { foreignKey: 'post_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Post.create(), - Comment.create() - ).then(([post, comment]) => { - expect(comment.get('post_id')).not.to.be.ok; + await this.sequelize.sync({ force: true }); + const [post, comment] = await Promise.all([Post.create(), Comment.create()]); + expect(comment.get('post_id')).not.to.be.ok; - const setter = comment.setPost(post, { save: false }); + const setter = await comment.setPost(post, { save: false }); - expect(setter).to.be.undefined; - expect(comment.get('post_id')).to.equal(post.get('id')); - expect(comment.changed('post_id')).to.be.true; - }); - }); + expect(setter).to.be.undefined; + expect(comment.get('post_id')).to.equal(post.get('id')); + expect(comment.changed('post_id')).to.be.true; }); - it('supports setting same association twice', function() { - const Home = this.sequelize.define('home', {}), - User = this.sequelize.define('user'); + it('supports setting same association twice', async function() { + const Home = this.sequelize.define('home', {}); + const User = this.sequelize.define('user'); Home.belongsTo(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Home.create(), - User.create() - ]); - }).then(([home, user]) => { - ctx.home = home; - ctx.user = user; - return home.setUser(user); - }).then(() => { - return ctx.home.setUser(ctx.user); - }).then(() => { - return expect(ctx.home.getUser()).to.eventually.have.property('id', ctx.user.get('id')); - }); + await this.sequelize.sync({ force: true }); + const [home, user] = await Promise.all([ + Home.create(), + User.create() + ]); + await home.setUser(user); + expect(await home.getUser()).to.have.property('id', user.id); }); }); describe('createAssociation', () => { - it('creates an associated model instance', function() { + it('creates an associated model instance', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ title: 'task' }).then(task => { - return task.createUser({ username: 'bob' }).then(user => { - expect(user).not.to.be.null; - expect(user.username).to.equal('bob'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const task = await Task.create({ title: 'task' }); + const user = await task.createUser({ username: 'bob' }); + expect(user).not.to.be.null; + expect(user.username).to.equal('bob'); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.belongsTo(User); - - return sequelize.sync({ force: true }).then(() => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.createUser({ username: 'foo' }, { transaction: t }).then(() => { - return group.getUser().then(user => { - expect(user).to.be.null; - - return group.getUser({ transaction: t }).then(user => { - expect(user).not.to.be.null; - - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.belongsTo(User); + + await sequelize.sync({ force: true }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.createUser({ username: 'foo' }, { transaction: t }); + const user = await group.getUser(); + expect(user).to.be.null; + + const user0 = await group.getUser({ transaction: t }); + expect(user0).not.to.be.null; + + await t.rollback(); }); } }); @@ -473,7 +383,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { expect(User.rawAttributes.AccountId.field).to.equal('AccountId'); }); - it('should support specifying the field of a foreign key', function() { + it('should support specifying the field of a foreign key', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }, { underscored: false }), Account = this.sequelize.define('Account', { title: Sequelize.STRING }, { underscored: false }); @@ -487,31 +397,29 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { expect(User.rawAttributes.AccountId).to.exist; expect(User.rawAttributes.AccountId.field).to.equal('account_id'); - return Account.sync({ force: true }).then(() => { - // Can't use Promise.all cause of foreign key references - return User.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Account.create({ title: 'pepsico' }) - ]); - }).then(([user, account]) => { - return user.setAccount(account).then(() => { - return user.getAccount(); - }); - }).then(user => { - expect(user).to.not.be.null; - return User.findOne({ - where: { username: 'foo' }, - include: [Account] - }); - }).then(user => { - // the sql query should correctly look at account_id instead of AccountId - expect(user.Account).to.exist; + await Account.sync({ force: true }); + // Can't use Promise.all cause of foreign key references + await User.sync({ force: true }); + + const [user1, account] = await Promise.all([ + User.create({ username: 'foo' }), + Account.create({ title: 'pepsico' }) + ]); + + await user1.setAccount(account); + const user0 = await user1.getAccount(); + expect(user0).to.not.be.null; + + const user = await User.findOne({ + where: { username: 'foo' }, + include: [Account] }); + + // the sql query should correctly look at account_id instead of AccountId + expect(user.Account).to.exist; }); - it('should set foreignKey on foreign table', function() { + it('should set foreignKey on foreign table', async function() { const Mail = this.sequelize.define('mail', {}, { timestamps: false }); const Entry = this.sequelize.define('entry', {}, { timestamps: false }); const User = this.sequelize.define('user', {}, { timestamps: false }); @@ -558,230 +466,192 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { } }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({})) - .then(() => Mail.create({})) - .then(mail => - Entry.create({ mailId: mail.id, ownerId: 1 }) - .then(() => Entry.create({ mailId: mail.id, ownerId: 1 })) - // set recipients - .then(() => mail.setRecipients([1])) - ) - .then(() => Entry.findAndCountAll({ - offset: 0, - limit: 10, - order: [['id', 'DESC']], - include: [ - { - association: Entry.associations.mail, - include: [ - { - association: Mail.associations.recipients, - through: { - where: { - recipientId: 1 - } - }, - required: true - } - ], - required: true - } - ] - })).then(result => { - expect(result.count).to.equal(2); - expect(result.rows[0].get({ plain: true })).to.deep.equal( - { - id: 2, - ownerId: 1, - mailId: 1, - mail: { - id: 1, - recipients: [{ - id: 1, - MailRecipients: { - mailId: 1, + await this.sequelize.sync({ force: true }); + await User.create({}); + const mail = await Mail.create({}); + await Entry.create({ mailId: mail.id, ownerId: 1 }); + await Entry.create({ mailId: mail.id, ownerId: 1 }); + // set recipients + await mail.setRecipients([1]); + + const result = await Entry.findAndCountAll({ + offset: 0, + limit: 10, + order: [['id', 'DESC']], + include: [ + { + association: Entry.associations.mail, + include: [ + { + association: Mail.associations.recipients, + through: { + where: { recipientId: 1 } - }] + }, + required: true } - } - ); - }); + ], + required: true + } + ] + }); + + expect(result.count).to.equal(2); + expect(result.rows[0].get({ plain: true })).to.deep.equal( + { + id: 2, + ownerId: 1, + mailId: 1, + mail: { + id: 1, + recipients: [{ + id: 1, + MailRecipients: { + mailId: 1, + recipientId: 1 + } + }] + } + } + ); }); }); describe('foreign key constraints', () => { - it('are enabled by default', function() { + it('are enabled by default', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User); // defaults to SET NULL - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(null); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(null); }); - it('sets to NO ACTION if allowNull: false', function() { + it('sets to NO ACTION if allowNull: false', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { foreignKey: { allowNull: false } }); // defaults to NO ACTION - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', UserId: user.id }).then(() => { - return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + await Task.create({ title: 'task', UserId: user.id }); + await expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); }); - it('should be possible to disable them', function() { + it('should be possible to disable them', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); Task.belongsTo(User, { constraints: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(user.id); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(user.id); }); - it('can cascade deletes', function() { + it('can cascade deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onDelete: 'cascade' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return user.destroy().then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(0); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(0); }); if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes', function() { + it('can restrict deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onDelete: 'restrict' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + await expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); }); - it('can restrict updates', function() { + it('can restrict updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onUpdate: 'restrict' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); - return expect( - user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - // Should fail due to FK restriction - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + + await expect( + user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) + ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + + // Should fail due to FK restriction + const tasks = await Task.findAll(); + + expect(tasks).to.have.length(1); }); } // NOTE: mssql does not support changing an autoincrement primary key if (Support.getTestDialect() !== 'mssql') { - it('can cascade updates', function() { + it('can cascade updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); Task.belongsTo(User, { onUpdate: 'cascade' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return task.setUser(user).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - .then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].UserId).to.equal(999); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUser(user); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); + expect(tasks[0].UserId).to.equal(999); }); } }); describe('association column', () => { - it('has correct type and name for non-id primary keys with non-integer type', function() { + it('has correct type and name for non-id primary keys with non-integer type', async function() { const User = this.sequelize.define('UserPKBT', { username: { type: DataTypes.STRING @@ -797,36 +667,33 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING); - }); + await this.sequelize.sync({ force: true }); + expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(DataTypes.STRING); }); - it('should support a non-primary key as the association column on a target without a primary key', function() { + it('should support a non-primary key as the association column on a target without a primary key', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, unique: true } }); const Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.removeAttribute('id'); Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'username', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'username', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); - it('should support a non-primary unique key as the association column', function() { + it('should support a non-primary unique key as the association column', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -840,24 +707,22 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'user_name', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'user_name', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); - it('should support a non-primary key as the association column with a field option', function() { + it('should support a non-primary key as the association column with a field option', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -870,24 +735,22 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { User.removeAttribute('id'); Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'the_user_name_field', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'the_user_name_field', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); - it('should support a non-primary key as the association column in a table with a composite primary key', function() { + it('should support a non-primary key as the association column in a table with a composite primary key', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -909,26 +772,24 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob', age: 18, weight: 40 })) - .then(newUser => Task.create({ title: 'some task' }) - .then(newTask => newTask.setUser(newUser))) - .then(() => Task.findOne({ where: { title: 'some task' } })) - .then(foundTask => foundTask.getUser()) - .then(foundUser => expect(foundUser.username).to.equal('bob')) - .then(() => this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks')) - .then(foreignKeysDescriptions => { - expect(foreignKeysDescriptions[0]).to.includes({ - referencedColumnName: 'the_user_name_field', - referencedTableName: 'Users', - columnName: 'user_name' - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob', age: 18, weight: 40 }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + const foundTask = await Task.findOne({ where: { title: 'some task' } }); + const foundUser = await foundTask.getUser(); + await expect(foundUser.username).to.equal('bob'); + const foreignKeysDescriptions = await this.sequelize.getQueryInterface().getForeignKeyReferencesForTable('Tasks'); + expect(foreignKeysDescriptions[0]).to.includes({ + referencedColumnName: 'the_user_name_field', + referencedTableName: 'Users', + columnName: 'user_name' + }); }); }); describe('association options', () => { - it('can specify data type for auto-generated relational keys', function() { + it('can specify data type for auto-generated relational keys', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), dataTypes = [DataTypes.INTEGER, DataTypes.BIGINT, DataTypes.STRING], Tasks = {}; @@ -939,10 +800,9 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { Tasks[dataType].belongsTo(User, { foreignKey: 'userId', keyType: dataType, constraints: false }); }); - return this.sequelize.sync({ force: true }).then(() => { - dataTypes.forEach(dataType => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); + await this.sequelize.sync({ force: true }); + dataTypes.forEach(dataType => { + expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); }); }); @@ -1035,51 +895,53 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghat' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ - model: this.Hat, - as: { singular: 'personwearinghat' } - }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual1, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual1.setPersonwearinghat(hat); + + const individual0 = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghat' }] + }); + + expect(individual0.name).to.equal('Foo Bar'); + expect(individual0.personwearinghat.name).to.equal('Baz'); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ + model: this.Hat, + as: { singular: 'personwearinghat' } + }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.setPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); }); }); diff --git a/test/integration/associations/has-many.test.js b/test/integration/associations/has-many.test.js index 8a549d0d3e36..1a566847ffaf 100644 --- a/test/integration/associations/has-many.test.js +++ b/test/integration/associations/has-many.test.js @@ -7,7 +7,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), moment = require('moment'), sinon = require('sinon'), - Promise = Sequelize.Promise, Op = Sequelize.Op, current = Support.sequelize, _ = require('lodash'), @@ -28,86 +27,83 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); describe('count', () => { - it('should not fail due to ambiguous field', function() { + it('should not fail due to ambiguous field', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); User.hasMany(Task); const subtasks = Task.hasMany(Task, { as: 'subtasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'John', - Tasks: [{ - title: 'Get rich', active: true - }] - }, { - include: [Task] - }); - }).then(user => { - return Promise.join( - user.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }), - user.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }) - ).return(user); - }).then(user => { - return expect(user.countTasks({ - attributes: [Task.primaryKeyField, 'title'], - include: [{ - attributes: [], - association: subtasks, - where: { - active: true - } - }], - group: this.sequelize.col(Task.name.concat('.', Task.primaryKeyField)) - })).to.eventually.equal(1); + await this.sequelize.sync({ force: true }); + + const user0 = await User.create({ + username: 'John', + Tasks: [{ + title: 'Get rich', active: true + }] + }, { + include: [Task] }); + + await Promise.all([ + user0.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }), + user0.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }) + ]); + + const user = user0; + + await expect(user.countTasks({ + attributes: [Task.primaryKeyField, 'title'], + include: [{ + attributes: [], + association: subtasks, + where: { + active: true + } + }], + group: this.sequelize.col(Task.name.concat('.', Task.primaryKeyField)) + })).to.eventually.equal(1); }); }); describe('get', () => { if (current.dialect.supports.groupedLimit) { describe('multiple', () => { - it('should fetch associations for multiple instances', function() { + it('should fetch associations for multiple instances', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', {}); User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 3 - }) - ); - }).then(users => { - return User.Tasks.get(users).then(result => { - expect(result[users[0].id].length).to.equal(3); - expect(result[users[1].id].length).to.equal(1); - expect(result[users[2].id].length).to.equal(0); - }); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 3 + })]); + + const result = await User.Tasks.get(users); + expect(result[users[0].id].length).to.equal(3); + expect(result[users[1].id].length).to.equal(1); + expect(result[users[2].id].length).to.equal(0); }); - it('should fetch associations for multiple instances with limit and order', function() { + it('should fetch associations for multiple instances with limit and order', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { title: DataTypes.STRING @@ -115,47 +111,44 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - tasks: [ - { title: 'b' }, - { title: 'd' }, - { title: 'c' }, - { title: 'a' } - ] - }, { - include: [User.Tasks] - }), - User.create({ - tasks: [ - { title: 'a' }, - { title: 'c' }, - { title: 'b' } - ] - }, { - include: [User.Tasks] - }) - ); - }).then(users => { - return User.Tasks.get(users, { - limit: 2, - order: [ - ['title', 'ASC'] - ] - }).then(result => { - expect(result[users[0].id].length).to.equal(2); - expect(result[users[0].id][0].title).to.equal('a'); - expect(result[users[0].id][1].title).to.equal('b'); - - expect(result[users[1].id].length).to.equal(2); - expect(result[users[1].id][0].title).to.equal('a'); - expect(result[users[1].id][1].title).to.equal('b'); - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([User.create({ + tasks: [ + { title: 'b' }, + { title: 'd' }, + { title: 'c' }, + { title: 'a' } + ] + }, { + include: [User.Tasks] + }), User.create({ + tasks: [ + { title: 'a' }, + { title: 'c' }, + { title: 'b' } + ] + }, { + include: [User.Tasks] + })]); + + const result = await User.Tasks.get(users, { + limit: 2, + order: [ + ['title', 'ASC'] + ] }); + + expect(result[users[0].id].length).to.equal(2); + expect(result[users[0].id][0].title).to.equal('a'); + expect(result[users[0].id][1].title).to.equal('b'); + + expect(result[users[1].id].length).to.equal(2); + expect(result[users[1].id][0].title).to.equal('a'); + expect(result[users[1].id][1].title).to.equal('b'); }); - it('should fetch multiple layers of associations with limit and order with separate=true', function() { + it('should fetch multiple layers of associations with limit and order with separate=true', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { title: DataTypes.STRING @@ -167,100 +160,97 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - { title: 'b', subtasks: [ - { title: 'c' }, - { title: 'a' } - ] }, - { title: 'd' }, - { title: 'c', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'a', subtasks: [ - { title: 'c' }, - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }), - User.create({ - id: 2, - tasks: [ - { title: 'a', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'c', subtasks: [ - { title: 'a' } - ] }, - { title: 'b', subtasks: [ - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }) - ); - }).then(() => { - return User.findAll({ - include: [{ - association: User.Tasks, - limit: 2, - order: [['title', 'ASC']], - separate: true, - as: 'tasks', - include: [ - { - association: Task.SubTasks, - order: [['title', 'DESC']], - separate: true, - as: 'subtasks' - } - ] - }], - order: [ - ['id', 'ASC'] + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + { title: 'b', subtasks: [ + { title: 'c' }, + { title: 'a' } + ] }, + { title: 'd' }, + { title: 'c', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'a', subtasks: [ + { title: 'c' }, + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + }), User.create({ + id: 2, + tasks: [ + { title: 'a', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'c', subtasks: [ + { title: 'a' } + ] }, + { title: 'b', subtasks: [ + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + })]); + + const users = await User.findAll({ + include: [{ + association: User.Tasks, + limit: 2, + order: [['title', 'ASC']], + separate: true, + as: 'tasks', + include: [ + { + association: Task.SubTasks, + order: [['title', 'DESC']], + separate: true, + as: 'subtasks' + } ] - }).then(users => { - expect(users[0].tasks.length).to.equal(2); - - expect(users[0].tasks[0].title).to.equal('a'); - expect(users[0].tasks[0].subtasks.length).to.equal(3); - expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[0].tasks[1].title).to.equal('b'); - expect(users[0].tasks[1].subtasks.length).to.equal(2); - expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); - - expect(users[1].tasks.length).to.equal(2); - expect(users[1].tasks[0].title).to.equal('a'); - expect(users[1].tasks[0].subtasks.length).to.equal(3); - expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[1].tasks[1].title).to.equal('b'); - expect(users[1].tasks[1].subtasks.length).to.equal(2); - expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); - expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); - }); + }], + order: [ + ['id', 'ASC'] + ] }); + + expect(users[0].tasks.length).to.equal(2); + + expect(users[0].tasks[0].title).to.equal('a'); + expect(users[0].tasks[0].subtasks.length).to.equal(3); + expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[0].tasks[1].title).to.equal('b'); + expect(users[0].tasks[1].subtasks.length).to.equal(2); + expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); + + expect(users[1].tasks.length).to.equal(2); + expect(users[1].tasks[0].title).to.equal('a'); + expect(users[1].tasks[0].subtasks.length).to.equal(3); + expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[1].tasks[1].title).to.equal('b'); + expect(users[1].tasks[1].subtasks.length).to.equal(2); + expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); + expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); }); - it('should fetch associations for multiple instances with limit and order and a belongsTo relation', function() { + it('should fetch associations for multiple instances with limit and order and a belongsTo relation', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { title: DataTypes.STRING, @@ -274,52 +264,49 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.Category = Task.belongsTo(Category, { as: 'category', foreignKey: 'categoryId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - tasks: [ - { title: 'b', category: {} }, - { title: 'd', category: {} }, - { title: 'c', category: {} }, - { title: 'a', category: {} } - ] - }, { - include: [{ association: User.Tasks, include: [Task.Category] }] - }), - User.create({ - tasks: [ - { title: 'a', category: {} }, - { title: 'c', category: {} }, - { title: 'b', category: {} } - ] - }, { - include: [{ association: User.Tasks, include: [Task.Category] }] - }) - ); - }).then(users => { - return User.Tasks.get(users, { - limit: 2, - order: [ - ['title', 'ASC'] - ], - include: [Task.Category] - }).then(result => { - expect(result[users[0].id].length).to.equal(2); - expect(result[users[0].id][0].title).to.equal('a'); - expect(result[users[0].id][0].category).to.be.ok; - expect(result[users[0].id][1].title).to.equal('b'); - expect(result[users[0].id][1].category).to.be.ok; - - expect(result[users[1].id].length).to.equal(2); - expect(result[users[1].id][0].title).to.equal('a'); - expect(result[users[1].id][0].category).to.be.ok; - expect(result[users[1].id][1].title).to.equal('b'); - expect(result[users[1].id][1].category).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const users = await Promise.all([User.create({ + tasks: [ + { title: 'b', category: {} }, + { title: 'd', category: {} }, + { title: 'c', category: {} }, + { title: 'a', category: {} } + ] + }, { + include: [{ association: User.Tasks, include: [Task.Category] }] + }), User.create({ + tasks: [ + { title: 'a', category: {} }, + { title: 'c', category: {} }, + { title: 'b', category: {} } + ] + }, { + include: [{ association: User.Tasks, include: [Task.Category] }] + })]); + + const result = await User.Tasks.get(users, { + limit: 2, + order: [ + ['title', 'ASC'] + ], + include: [Task.Category] }); + + expect(result[users[0].id].length).to.equal(2); + expect(result[users[0].id][0].title).to.equal('a'); + expect(result[users[0].id][0].category).to.be.ok; + expect(result[users[0].id][1].title).to.equal('b'); + expect(result[users[0].id][1].category).to.be.ok; + + expect(result[users[1].id].length).to.equal(2); + expect(result[users[1].id][0].title).to.equal('a'); + expect(result[users[1].id][0].category).to.be.ok; + expect(result[users[1].id][1].title).to.equal('b'); + expect(result[users[1].id][1].category).to.be.ok; }); - it('supports schemas', function() { + it('supports schemas', async function() { const User = this.sequelize.define('User', {}).schema('work'), Task = this.sequelize.define('Task', { title: DataTypes.STRING @@ -331,112 +318,103 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.SubTasks = Task.hasMany(SubTask, { as: 'subtasks' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('work'); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return SubTask.sync({ force: true }); - }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - { title: 'b', subtasks: [ - { title: 'c' }, - { title: 'a' } - ] }, - { title: 'd' }, - { title: 'c', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'a', subtasks: [ - { title: 'c' }, - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }), - User.create({ - id: 2, - tasks: [ - { title: 'a', subtasks: [ - { title: 'b' }, - { title: 'a' }, - { title: 'c' } - ] }, - { title: 'c', subtasks: [ - { title: 'a' } - ] }, - { title: 'b', subtasks: [ - { title: 'a' }, - { title: 'b' } - ] } - ] - }, { - include: [{ association: User.Tasks, include: [Task.SubTasks] }] - }) - ); - }).then(() => { - return User.findAll({ - include: [{ - association: User.Tasks, - limit: 2, - order: [['title', 'ASC']], - separate: true, - as: 'tasks', - include: [ - { - association: Task.SubTasks, - order: [['title', 'DESC']], - separate: true, - as: 'subtasks' - } - ] - }], - order: [ - ['id', 'ASC'] + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('work'); + await User.sync({ force: true }); + await Task.sync({ force: true }); + await SubTask.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + { title: 'b', subtasks: [ + { title: 'c' }, + { title: 'a' } + ] }, + { title: 'd' }, + { title: 'c', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'a', subtasks: [ + { title: 'c' }, + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + }), User.create({ + id: 2, + tasks: [ + { title: 'a', subtasks: [ + { title: 'b' }, + { title: 'a' }, + { title: 'c' } + ] }, + { title: 'c', subtasks: [ + { title: 'a' } + ] }, + { title: 'b', subtasks: [ + { title: 'a' }, + { title: 'b' } + ] } + ] + }, { + include: [{ association: User.Tasks, include: [Task.SubTasks] }] + })]); + + const users = await User.findAll({ + include: [{ + association: User.Tasks, + limit: 2, + order: [['title', 'ASC']], + separate: true, + as: 'tasks', + include: [ + { + association: Task.SubTasks, + order: [['title', 'DESC']], + separate: true, + as: 'subtasks' + } ] - }).then(users => { - expect(users[0].tasks.length).to.equal(2); - - expect(users[0].tasks[0].title).to.equal('a'); - expect(users[0].tasks[0].subtasks.length).to.equal(3); - expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[0].tasks[1].title).to.equal('b'); - expect(users[0].tasks[1].subtasks.length).to.equal(2); - expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); - expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); - - expect(users[1].tasks.length).to.equal(2); - expect(users[1].tasks[0].title).to.equal('a'); - expect(users[1].tasks[0].subtasks.length).to.equal(3); - expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); - expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); - expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); - - expect(users[1].tasks[1].title).to.equal('b'); - expect(users[1].tasks[1].subtasks.length).to.equal(2); - expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); - expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); - return this.sequelize.dropSchema('work').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || schemas === 'mariadb') { - expect(schemas).to.be.empty; - } - }); - }); - }); + }], + order: [ + ['id', 'ASC'] + ] }); + + expect(users[0].tasks.length).to.equal(2); + + expect(users[0].tasks[0].title).to.equal('a'); + expect(users[0].tasks[0].subtasks.length).to.equal(3); + expect(users[0].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[0].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[0].tasks[1].title).to.equal('b'); + expect(users[0].tasks[1].subtasks.length).to.equal(2); + expect(users[0].tasks[1].subtasks[0].title).to.equal('c'); + expect(users[0].tasks[1].subtasks[1].title).to.equal('a'); + + expect(users[1].tasks.length).to.equal(2); + expect(users[1].tasks[0].title).to.equal('a'); + expect(users[1].tasks[0].subtasks.length).to.equal(3); + expect(users[1].tasks[0].subtasks[0].title).to.equal('c'); + expect(users[1].tasks[0].subtasks[1].title).to.equal('b'); + expect(users[1].tasks[0].subtasks[2].title).to.equal('a'); + + expect(users[1].tasks[1].title).to.equal('b'); + expect(users[1].tasks[1].subtasks.length).to.equal(2); + expect(users[1].tasks[1].subtasks[0].title).to.equal('b'); + expect(users[1].tasks[1].subtasks[1].title).to.equal('a'); + await this.sequelize.dropSchema('work'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || schemas === 'mariadb') { + expect(schemas).to.be.empty; + } }); }); } @@ -480,95 +458,82 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - let Article, Label, sequelize, article, label, t; - return Support.prepareTransactionTest(this.sequelize).then(_sequelize => { - sequelize = _sequelize; - Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - Article.hasMany(Label); - - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - Article.create({ title: 'foo' }), - Label.create({ text: 'bar' }) - ]); - }).then(([_article, _label]) => { - article = _article; - label = _label; - return sequelize.transaction(); - }).then(_t => { - t = _t; - return article.setLabels([label], { transaction: t }); - }).then(() => { - return Article.findAll({ transaction: t }); - }).then(articles => { - return articles[0].hasLabel(label).then(hasLabel => { - expect(hasLabel).to.be.false; - }); - }).then(() => { - return Article.findAll({ transaction: t }); - }).then(articles => { - return articles[0].hasLabel(label, { transaction: t }).then(hasLabel => { - expect(hasLabel).to.be.true; - return t.rollback(); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.setLabels([label], { transaction: t }); + const articles0 = await Article.findAll({ transaction: t }); + const hasLabel0 = await articles0[0].hasLabel(label); + expect(hasLabel0).to.be.false; + const articles = await Article.findAll({ transaction: t }); + const hasLabel = await articles[0].hasLabel(label, { transaction: t }); + expect(hasLabel).to.be.true; + await t.rollback(); }); } - it('does not have any labels assigned to it initially', function() { - return Promise.all([ + it('does not have any labels assigned to it initially', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return Promise.all([ - article.hasLabel(label1), - article.hasLabel(label2) - ]); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.false; - }); + ]); + + const [hasLabel1, hasLabel2] = await Promise.all([ + article.hasLabel(label1), + article.hasLabel(label2) + ]); + + expect(hasLabel1).to.be.false; + expect(hasLabel2).to.be.false; }); - it('answers true if the label has been assigned', function() { - return Promise.all([ + it('answers true if the label has been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return Promise.all([ - article.hasLabel(label1), - article.hasLabel(label2) - ]); - }); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.true; - expect(hasLabel2).to.be.false; - }); + ]); + + await article.addLabel(label1); + + const [hasLabel1, hasLabel2] = await Promise.all([ + article.hasLabel(label1), + article.hasLabel(label2) + ]); + + expect(hasLabel1).to.be.true; + expect(hasLabel2).to.be.false; }); - it('answers correctly if the label has been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers correctly if the label has been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return Promise.all([ - article.hasLabel(label1[this.Label.primaryKeyAttribute]), - article.hasLabel(label2[this.Label.primaryKeyAttribute]) - ]); - }); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.true; - expect(hasLabel2).to.be.false; - }); + ]); + + await article.addLabel(label1); + + const [hasLabel1, hasLabel2] = await Promise.all([ + article.hasLabel(label1[this.Label.primaryKeyAttribute]), + article.hasLabel(label2[this.Label.primaryKeyAttribute]) + ]); + + expect(hasLabel1).to.be.true; + expect(hasLabel2).to.be.false; }); }); @@ -598,310 +563,249 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.hasMany(ctx.Label); - - return ctx.sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - ctx.label = label; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.setLabels([ctx.label], { transaction: t }); - }).then(() => { - return ctx.Article.findAll({ transaction: ctx.t }); - }).then(articles => { - return Promise.all([ - articles[0].hasLabels([ctx.label]), - articles[0].hasLabels([ctx.label], { transaction: ctx.t }) - ]); - }).then(([hasLabel1, hasLabel2]) => { - expect(hasLabel1).to.be.false; - expect(hasLabel2).to.be.true; - - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.setLabels([label], { transaction: t }); + const articles = await Article.findAll({ transaction: t }); + + const [hasLabel1, hasLabel2] = await Promise.all([ + articles[0].hasLabels([label]), + articles[0].hasLabels([label], { transaction: t }) + ]); + + expect(hasLabel1).to.be.false; + expect(hasLabel2).to.be.true; + + await t.rollback(); }); } - it('answers false if only some labels have been assigned', function() { - return Promise.all([ + it('answers false if only some labels have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return article.hasLabels([label1, label2]); - }); - }).then(result => { - expect(result).to.be.false; - }); + ]); + + await article.addLabel(label1); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.false; }); - it('answers false if only some labels have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers false if only some labels have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.addLabel(label1).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.false; - }); - }); - }); + ]); + + await article.addLabel(label1); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.false; }); - it('answers true if all label have been assigned', function() { - return Promise.all([ + it('answers true if all label have been assigned', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([label1, label2]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + const result = await article.hasLabels([label1, label2]); + expect(result).to.be.true; }); - it('answers true if all label have been assigned when passing a primary key instead of an object', function() { - return Promise.all([ + it('answers true if all label have been assigned when passing a primary key instead of an object', async function() { + const [article, label1, label2] = await Promise.all([ this.Article.create({ title: 'Article' }), this.Label.create({ text: 'Awesomeness' }), this.Label.create({ text: 'Epicness' }) - ]).then(([article, label1, label2]) => { - return article.setLabels([label1, label2]).then(() => { - return article.hasLabels([ - label1[this.Label.primaryKeyAttribute], - label2[this.Label.primaryKeyAttribute] - ]).then(result => { - expect(result).to.be.true; - }); - }); - }); + ]); + + await article.setLabels([label1, label2]); + + const result = await article.hasLabels([ + label1[this.Label.primaryKeyAttribute], + label2[this.Label.primaryKeyAttribute] + ]); + + expect(result).to.be.true; }); }); describe('setAssociations', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.hasMany(ctx.Label); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }), - ctx.sequelize.transaction() - ]); - }).then(([article, label, t]) => { - ctx.article = article; - ctx. t = t; - return article.setLabels([label], { transaction: t }); - }).then(() => { - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: undefined }); - }).then(labels => { - expect(labels.length).to.equal(0); - - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: ctx.t }); - }).then(labels => { - expect(labels.length).to.equal(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label, t] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }), + sequelize.transaction() + ]); + + await article.setLabels([label], { transaction: t }); + const labels0 = await Label.findAll({ where: { ArticleId: article.id }, transaction: undefined }); + expect(labels0.length).to.equal(0); + + const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t }); + expect(labels.length).to.equal(1); + await t.rollback(); }); } - it('clears associations when passing null to the set-method', function() { + it('clears associations when passing null to the set-method', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.task = task; - return task.setUsers([user]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(1); - - return ctx.task.setUsers(null); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(0); - }); + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await task.setUsers([user]); + const users0 = await task.getUsers(); + expect(users0).to.have.length(1); + + await task.setUsers(null); + const users = await task.getUsers(); + expect(users).to.have.length(0); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const Article = this.sequelize.define('Article', { title: DataTypes.STRING }), Label = this.sequelize.define('Label', { text: DataTypes.STRING }); Article.hasMany(Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Article.create({}), - Label.create({ text: 'label one' }), - Label.create({ text: 'label two' }) - ]); - }).then(([article, label1, label2]) => { - ctx.article = article; - ctx.label1 = label1; - ctx.label2 = label2; - return article.addLabel(label1.id); - }).then(() => { - return ctx.article.setLabels([ctx.label2.id]); - }).then(() => { - return ctx.article.getLabels(); - }).then(labels => { - expect(labels).to.have.length(1); - expect(labels[0].text).to.equal('label two'); - }); + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + Article.create({}), + Label.create({ text: 'label one' }), + Label.create({ text: 'label two' }) + ]); + + await article.addLabel(label1.id); + await article.setLabels([label2.id]); + const labels = await article.getLabels(); + expect(labels).to.have.length(1); + expect(labels[0].text).to.equal('label two'); }); }); describe('addAssociations', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - ctx.Article.hasMany(ctx.Label); - - ctx.sequelize = sequelize; - return sequelize.sync({ force: true }); - }).then(() => { - return Promise.all([ - ctx.Article.create({ title: 'foo' }), - ctx.Label.create({ text: 'bar' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - ctx.label = label; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.addLabel(ctx.label, { transaction: ctx.t }); - }).then(() => { - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: undefined }); - }).then(labels => { - expect(labels.length).to.equal(0); - - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: ctx.t }); - }).then(labels => { - expect(labels.length).to.equal(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({ title: 'foo' }), + Label.create({ text: 'bar' }) + ]); + + const t = await sequelize.transaction(); + await article.addLabel(label, { transaction: t }); + const labels0 = await Label.findAll({ where: { ArticleId: article.id }, transaction: undefined }); + expect(labels0.length).to.equal(0); + + const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t }); + expect(labels.length).to.equal(1); + await t.rollback(); }); } - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), Label = this.sequelize.define('Label', { 'text': DataTypes.STRING }); Article.hasMany(Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Article.create({}), - Label.create({ text: 'label one' }) - ]); - }).then(([article, label]) => { - ctx.article = article; - return article.addLabel(label.id); - }).then(() => { - return ctx.article.getLabels(); - }).then(labels => { - expect(labels[0].text).to.equal('label one'); // Make sure that we didn't modify one of the other attributes while building / saving a new instance - }); + await this.sequelize.sync({ force: true }); + + const [article, label] = await Promise.all([ + Article.create({}), + Label.create({ text: 'label one' }) + ]); + + await article.addLabel(label.id); + const labels = await article.getLabels(); + expect(labels[0].text).to.equal('label one'); // Make sure that we didn't modify one of the other attributes while building / saving a new instance }); }); describe('addMultipleAssociations', () => { - it('adds associations without removing the current ones', function() { + it('adds associations without removing the current ones', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'foo ' }, - { username: 'bar ' }, - { username: 'baz ' } - ]); - }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return User.findAll(); - }).then(users => { - ctx.users = users; - return ctx.task.setUsers([users[0]]); - }).then(() => { - return ctx.task.addUsers([ctx.users[1], ctx.users[2]]); - }).then(() => { - return ctx.task.getUsers(); - }).then(users => { - expect(users).to.have.length(3); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([ + { username: 'foo ' }, + { username: 'bar ' }, + { username: 'baz ' } + ]); + + const task = await Task.create({ title: 'task' }); + const users0 = await User.findAll(); + const users = users0; + await task.setUsers([users0[0]]); + await task.addUsers([users[1], users[2]]); + expect(await task.getUsers()).to.have.length(3); }); - it('handles decent sized bulk creates', function() { + it('handles decent sized bulk creates', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING, num: DataTypes.INTEGER, status: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - const users = _.range(1000).map(i => ({ username: `user${i}`, num: i, status: 'live' })); - return User.bulkCreate(users); - }).then(() => { - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return User.findAll(); - }).then(users=> { - expect(users).to.have.length(1000); - }); + await this.sequelize.sync({ force: true }); + const users0 = _.range(1000).map(i => ({ username: `user${i}`, num: i, status: 'live' })); + await User.bulkCreate(users0); + await Task.create({ title: 'task' }); + const users = await User.findAll(); + expect(users).to.have.length(1000); }); }); - it('clears associations when passing null to the set-method with omitNull set to true', function() { + it('clears associations when passing null to the set-method with omitNull set to true', async function() { this.sequelize.options.omitNull = true; const User = this.sequelize.define('User', { username: DataTypes.STRING }), @@ -909,49 +813,38 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.hasMany(User); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }); - }).then(user => { - ctx.user = user; - return Task.create({ title: 'task' }); - }).then(task => { - ctx.task = task; - return task.setUsers([ctx.user]); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { - expect(_users).to.have.length(1); - - return ctx.task.setUsers(null); - }).then(() => { - return ctx.task.getUsers(); - }).then(_users => { + try { + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await task.setUsers([user]); + const _users0 = await task.getUsers(); + expect(_users0).to.have.length(1); + + await task.setUsers(null); + const _users = await task.getUsers(); expect(_users).to.have.length(0); - }).finally(() => { + } finally { this.sequelize.options.omitNull = false; - }); + } }); describe('createAssociations', () => { - it('creates a new associated object', function() { + it('creates a new associated object', async function() { const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), Label = this.sequelize.define('Label', { 'text': DataTypes.STRING }); Article.hasMany(Label); - return this.sequelize.sync({ force: true }).then(() => { - return Article.create({ title: 'foo' }); - }).then(article => { - return article.createLabel({ text: 'bar' }).return(article); - }).then(article => { - return Label.findAll({ where: { ArticleId: article.id } }); - }).then(labels => { - expect(labels.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + const article0 = await Article.create({ title: 'foo' }); + await article0.createLabel({ text: 'bar' }); + const article = article0; + const labels = await Label.findAll({ where: { ArticleId: article.id } }); + expect(labels.length).to.equal(1); }); - it('creates the object with the association directly', function() { + it('creates the object with the association directly', async function() { const spy = sinon.spy(); const Article = this.sequelize.define('Article', { @@ -964,53 +857,36 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Article.hasMany(Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Article.create({ title: 'foo' }); - }).then(article => { - ctx.article = article; - return article.createLabel({ text: 'bar' }, { logging: spy }); - }).then(label => { - expect(spy.calledOnce).to.be.true; - expect(label.ArticleId).to.equal(ctx.article.id); - }); + await this.sequelize.sync({ force: true }); + const article = await Article.create({ title: 'foo' }); + const label = await article.createLabel({ text: 'bar' }, { logging: spy }); + expect(spy.calledOnce).to.be.true; + expect(label.ArticleId).to.equal(article.id); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - const ctx = {}; - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - ctx.sequelize = sequelize; - ctx.Article = sequelize.define('Article', { 'title': DataTypes.STRING }); - ctx.Label = sequelize.define('Label', { 'text': DataTypes.STRING }); - - ctx.Article.hasMany(ctx.Label); - - return sequelize.sync({ force: true }); - }).then(() => { - return ctx.Article.create({ title: 'foo' }); - }).then(article => { - ctx.article = article; - return ctx.sequelize.transaction(); - }).then(t => { - ctx.t = t; - return ctx.article.createLabel({ text: 'bar' }, { transaction: ctx.t }); - }).then(() => { - return ctx.Label.findAll(); - }).then(labels => { - expect(labels.length).to.equal(0); - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id } }); - }).then(labels => { - expect(labels.length).to.equal(0); - return ctx.Label.findAll({ where: { ArticleId: ctx.article.id }, transaction: ctx.t }); - }).then(labels => { - expect(labels.length).to.equal(1); - return ctx.t.rollback(); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Article = sequelize.define('Article', { 'title': DataTypes.STRING }); + const Label = sequelize.define('Label', { 'text': DataTypes.STRING }); + + Article.hasMany(Label); + + await sequelize.sync({ force: true }); + const article = await Article.create({ title: 'foo' }); + const t = await sequelize.transaction(); + await article.createLabel({ text: 'bar' }, { transaction: t }); + const labels1 = await Label.findAll(); + expect(labels1.length).to.equal(0); + const labels0 = await Label.findAll({ where: { ArticleId: article.id } }); + expect(labels0.length).to.equal(0); + const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t }); + expect(labels.length).to.equal(1); + await t.rollback(); }); } - it('supports passing the field option', function() { + it('supports passing the field option', async function() { const Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }), @@ -1020,41 +896,40 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Article.hasMany(Label); - return this.sequelize.sync({ force: true }).then(() => { - return Article.create(); - }).then(article => { - return article.createLabel({ - text: 'yolo' - }, { - fields: ['text'] - }).return(article); - }).then(article => { - return article.getLabels(); - }).then(labels => { - expect(labels.length).to.be.ok; + await this.sequelize.sync({ force: true }); + const article0 = await Article.create(); + + await article0.createLabel({ + text: 'yolo' + }, { + fields: ['text'] }); + + const article = article0; + const labels = await article.getLabels(); + expect(labels.length).to.be.ok; }); }); describe('getting assocations with options', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); this.User.hasMany(this.Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + return john.setTasks([task1, task2]); }); - it('should treat the where object of associations as a first class citizen', function() { + it('should treat the where object of associations as a first class citizen', async function() { this.Article = this.sequelize.define('Article', { 'title': DataTypes.STRING }); @@ -1065,44 +940,36 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { this.Article.hasMany(this.Label); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.Article.create({ title: 'Article' }), - this.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }), - this.Label.create({ text: 'Epicness', until: '2014-01-03 01:00:00' }) - ]); - }).then(([article, label1, label2]) => { - ctx.article = article; - return article.setLabels([label1, label2]); - }).then(() => { - return ctx.article.getLabels({ where: { until: { [Op.gt]: moment('2014-01-02').toDate() } } }); - }).then(labels => { - expect(labels).to.be.instanceof(Array); - expect(labels).to.have.length(1); - expect(labels[0].text).to.equal('Epicness'); - }); + await this.sequelize.sync({ force: true }); + + const [article, label1, label2] = await Promise.all([ + this.Article.create({ title: 'Article' }), + this.Label.create({ text: 'Awesomeness', until: '2014-01-01 01:00:00' }), + this.Label.create({ text: 'Epicness', until: '2014-01-03 01:00:00' }) + ]); + + await article.setLabels([label1, label2]); + const labels = await article.getLabels({ where: { until: { [Op.gt]: moment('2014-01-02').toDate() } } }); + expect(labels).to.be.instanceof(Array); + expect(labels).to.have.length(1); + expect(labels[0].text).to.equal('Epicness'); }); - it('gets all associated objects when no options are passed', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks(); - }).then(tasks => { - expect(tasks).to.have.length(2); - }); + it('gets all associated objects when no options are passed', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks(); + expect(tasks).to.have.length(2); }); - it('only get objects that fulfill the options', function() { - return this.User.findOne({ where: { username: 'John' } }).then(john => { - return john.getTasks({ where: { active: true }, limit: 10, order: [['id', 'DESC']] }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + it('only get objects that fulfill the options', async function() { + const john = await this.User.findOne({ where: { username: 'John' } }); + const tasks = await john.getTasks({ where: { active: true }, limit: 10, order: [['id', 'DESC']] }); + expect(tasks).to.have.length(1); }); }); describe('countAssociations', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }); this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN }); @@ -1110,31 +977,32 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ username: 'John' }), - this.Task.create({ title: 'Get rich', active: true }), - this.Task.create({ title: 'Die trying', active: false }) - ]); - }).then(([john, task1, task2]) => { - this.user = john; - return john.setTasks([task1, task2]); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1, task2] = await Promise.all([ + this.User.create({ username: 'John' }), + this.Task.create({ title: 'Get rich', active: true }), + this.Task.create({ title: 'Die trying', active: false }) + ]); + + this.user = john; + + return john.setTasks([task1, task2]); }); - it('should count all associations', function() { - return expect(this.user.countTasks({})).to.eventually.equal(2); + it('should count all associations', async function() { + await expect(this.user.countTasks({})).to.eventually.equal(2); }); - it('should count filtered associations', function() { - return expect(this.user.countTasks({ + it('should count filtered associations', async function() { + await expect(this.user.countTasks({ where: { active: true } })).to.eventually.equal(1); }); - it('should count scoped associations', function() { + it('should count scoped associations', async function() { this.User.hasMany(this.Task, { foreignKey: 'userId', as: 'activeTasks', @@ -1143,199 +1011,188 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { } }); - return expect(this.user.countActiveTasks({})).to.eventually.equal(1); + await expect(this.user.countActiveTasks({})).to.eventually.equal(1); }); }); describe('thisAssociations', () => { - it('should work with alias', function() { + it('should work with alias', async function() { const Person = this.sequelize.define('Group', {}); Person.hasMany(Person, { as: 'Children' }); - return this.sequelize.sync(); + await this.sequelize.sync(); }); }); }); describe('foreign key constraints', () => { describe('1:m', () => { - it('sets null by default', function() { + it('sets null by default', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - return user.setTasks([task]).then(() => { - return user.destroy().then(() => { - return task.reload(); - }); - }); - }).then(task => { - expect(task.UserId).to.equal(null); - }); + await this.sequelize.sync({ force: true }); + + const [user, task0] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user.setTasks([task0]); + await user.destroy(); + const task = await task0.reload(); + expect(task.UserId).to.equal(null); }); - it('sets to CASCADE if allowNull: false', function() { + it('sets to CASCADE if allowNull: false', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { foreignKey: { allowNull: false } }); // defaults to CASCADE - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', UserId: user.id }).then(() => { - return user.destroy().then(() => { - return Task.findAll(); - }); - }); - }).then(tasks => { - expect(tasks).to.be.empty; - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + await Task.create({ title: 'task', UserId: user.id }); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.be.empty; }); - it('should be possible to remove all constraints', function() { + it('should be possible to remove all constraints', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { constraints: false }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.user = user; - ctx.task = task; - return user.setTasks([task]); - }).then(() => { - return ctx.user.destroy(); - }).then(() => { - return ctx.task.reload(); - }).then(task => { - expect(task.UserId).to.equal(ctx.user.id); - }); + await this.sequelize.sync({ force: true }); + + const [user, task0] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + const task = task0; + await user.setTasks([task0]); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(user.id); }); - it('can cascade deletes', function() { + it('can cascade deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onDelete: 'cascade' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.user = user; - ctx.task = task; - return user.setTasks([task]); - }).then(() => { - return ctx.user.destroy(); - }).then(() => { - return Task.findAll(); - }).then(tasks => { - expect(tasks).to.have.length(0); - }); + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user.setTasks([task]); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(0); }); // NOTE: mssql does not support changing an autoincrement primary key if (dialect !== 'mssql') { - it('can cascade updates', function() { + it('can cascade updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onUpdate: 'cascade' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - return user.setTasks([task]).return(user); - }).then(user => { - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); - }).then(() => { - return Task.findAll(); - }).then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].UserId).to.equal(999); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user0.setTasks([task]); + const user = user0; + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); + expect(tasks[0].UserId).to.equal(999); }); } if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes', function() { + it('can restrict deletes', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onDelete: 'restrict' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - ctx.user = user; - ctx.task = task; - return user.setTasks([task]); - }).then(() => { - return ctx.user.destroy().catch(Sequelize.ForeignKeyConstraintError, () => { - // Should fail due to FK violation - return Task.findAll(); - }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + let tasks; + await this.sequelize.sync({ force: true }); + + const [user, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user.setTasks([task]); + + try { + tasks = await user.destroy(); + } catch (err) { + if (!(err instanceof Sequelize.ForeignKeyConstraintError)) + throw err; + + // Should fail due to FK violation + tasks = await Task.findAll(); + } + + expect(tasks).to.have.length(1); }); - it('can restrict updates', function() { + it('can restrict updates', async function() { const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), User = this.sequelize.define('User', { username: DataTypes.STRING }); User.hasMany(Task, { onUpdate: 'restrict' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - Task.create({ title: 'task' }) - ]); - }).then(([user, task]) => { - return user.setTasks([task]).return(user); - }).then(user => { - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - .catch(Sequelize.ForeignKeyConstraintError, () => { - // Should fail due to FK violation - return Task.findAll(); - }); - }).then(tasks => { - expect(tasks).to.have.length(1); - }); + let tasks; + await this.sequelize.sync({ force: true }); + + const [user0, task] = await Promise.all([ + User.create({ username: 'foo' }), + Task.create({ title: 'task' }) + ]); + + await user0.setTasks([task]); + const user = user0; + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + + try { + tasks = await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + } catch (err) { + if (!(err instanceof Sequelize.ForeignKeyConstraintError)) + throw err; + + // Should fail due to FK violation + tasks = await Task.findAll(); + } + + expect(tasks).to.have.length(1); }); } }); @@ -1362,24 +1219,23 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { expect(Account.rawAttributes.UserId.field).to.equal('UserId'); }); - it('can specify data type for auto-generated relational keys', function() { + it('can specify data type for auto-generated relational keys', async function() { const User = this.sequelize.define('UserXYZ', { username: DataTypes.STRING }), dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING], Tasks = {}; - return Promise.each(dataTypes, dataType => { + for (const dataType of dataTypes) { const tableName = `TaskXYZ_${dataType.key}`; Tasks[dataType] = this.sequelize.define(tableName, { title: DataTypes.STRING }); User.hasMany(Tasks[dataType], { foreignKey: 'userId', keyType: dataType, constraints: false }); - return Tasks[dataType].sync({ force: true }).then(() => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); - }); + await Tasks[dataType].sync({ force: true }); + expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); + } }); - it('infers the keyType if none provided', function() { + it('infers the keyType if none provided', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.STRING, primaryKey: true }, username: DataTypes.STRING @@ -1390,9 +1246,8 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { User.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - expect(Task.rawAttributes.UserId.type instanceof DataTypes.STRING).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + expect(Task.rawAttributes.UserId.type instanceof DataTypes.STRING).to.be.ok; }); describe('allows the user to provide an attribute definition object as foreignKey', () => { @@ -1461,7 +1316,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { .throw('Naming collision between attribute \'user\' and association \'user\' on model user. To remedy this, change either foreignKey or as in your association definition'); }); - it('should ignore group from ancestor on deep separated query', function() { + it('should ignore group from ancestor on deep separated query', async function() { const User = this.sequelize.define('user', { userId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, username: Sequelize.STRING @@ -1478,29 +1333,27 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { Task.hasMany(Job, { foreignKey: 'taskId' }); User.hasMany(Task, { foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }) - .then(() => { - return User.create({ - username: 'John Doe', - tasks: [ - { title: 'Task #1', jobs: [{ title: 'Job #1' }, { title: 'Job #2' }] }, - { title: 'Task #2', jobs: [{ title: 'Job #3' }, { title: 'Job #4' }] } - ] - }, { include: [{ model: Task, include: [Job] }] }); - }) - .then(() => { - return User.findAndCountAll({ - attributes: ['userId'], - include: [ - { model: Task, separate: true, include: [{ model: Job, separate: true }] } - ], - group: [['userId']] - }); - }) - .then(({ count, rows }) => { - expect(count.length).to.equal(1); - expect(rows[0].tasks[0].jobs.length).to.equal(2); - }); + await this.sequelize.sync({ force: true }); + + await User.create({ + username: 'John Doe', + tasks: [ + { title: 'Task #1', jobs: [{ title: 'Job #1' }, { title: 'Job #2' }] }, + { title: 'Task #2', jobs: [{ title: 'Job #3' }, { title: 'Job #4' }] } + ] + }, { include: [{ model: Task, include: [Job] }] }); + + const { count, rows } = await User.findAndCountAll({ + attributes: ['userId'], + include: [ + { model: Task, separate: true, include: [{ model: Job, separate: true }] } + ], + group: [['userId']] + }); + + expect(count.length).to.equal(1); + expect(count).to.deep.equal([{ userId: 1, count: 1 }]); + expect(rows[0].tasks[0].jobs.length).to.equal(2); }); }); @@ -1521,49 +1374,39 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { return this.sequelize.sync({ force: true }); }); - it('should use sourceKey', function() { + it('should use sourceKey', async function() { const User = this.User, Task = this.Task; - return User.create({ username: 'John', email: 'john@example.com' }).then(user => { - return Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }).then(() => { - return user.getTasks().then(tasks => { - expect(tasks.length).to.equal(1); - expect(tasks[0].title).to.equal('Fix PR'); - }); - }); - }); + const user = await User.create({ username: 'John', email: 'john@example.com' }); + await Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }); + const tasks = await user.getTasks(); + expect(tasks.length).to.equal(1); + expect(tasks[0].title).to.equal('Fix PR'); }); - it('should count related records', function() { + it('should count related records', async function() { const User = this.User, Task = this.Task; - return User.create({ username: 'John', email: 'john@example.com' }).then(user => { - return Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }).then(() => { - return user.countTasks().then(tasksCount => { - expect(tasksCount).to.equal(1); - }); - }); - }); + const user = await User.create({ username: 'John', email: 'john@example.com' }); + await Task.create({ title: 'Fix PR', userEmail: 'john@example.com' }); + const tasksCount = await user.countTasks(); + expect(tasksCount).to.equal(1); }); - it('should set right field when add relative', function() { + it('should set right field when add relative', async function() { const User = this.User, Task = this.Task; - return User.create({ username: 'John', email: 'john@example.com' }).then(user => { - return Task.create({ title: 'Fix PR' }).then(task => { - return user.addTask(task).then(() => { - return user.hasTask(task.id).then(hasTask => { - expect(hasTask).to.be.true; - }); - }); - }); - }); + const user = await User.create({ username: 'John', email: 'john@example.com' }); + const task = await Task.create({ title: 'Fix PR' }); + await user.addTask(task); + const hasTask = await user.hasTask(task.id); + expect(hasTask).to.be.true; }); - it('should create with nested associated models', function() { + it('should create with nested associated models', async function() { const User = this.User, values = { username: 'John', @@ -1571,27 +1414,22 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { tasks: [{ title: 'Fix new PR' }] }; - return User.create(values, { include: ['tasks'] }) - .then(user => { - // Make sure tasks are defined for created user - expect(user).to.have.property('tasks'); - expect(user.tasks).to.be.an('array'); - expect(user.tasks).to.lengthOf(1); - expect(user.tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); - - return User.findOne({ where: { email: values.email } }); - }) - .then(user => - user.getTasks() - .then(tasks => { - // Make sure tasks relationship is successful - expect(tasks).to.be.an('array'); - expect(tasks).to.lengthOf(1); - expect(tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); - })); + const user0 = await User.create(values, { include: ['tasks'] }); + // Make sure tasks are defined for created user + expect(user0).to.have.property('tasks'); + expect(user0.tasks).to.be.an('array'); + expect(user0.tasks).to.lengthOf(1); + expect(user0.tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); + + const user = await User.findOne({ where: { email: values.email } }); + const tasks = await user.getTasks(); + // Make sure tasks relationship is successful + expect(tasks).to.be.an('array'); + expect(tasks).to.lengthOf(1); + expect(tasks[0].title).to.be.equal(values.tasks[0].title, 'task title is correct'); }); - it('should create nested associations with symmetric getters/setters on FK', function() { + it('should create nested associations with symmetric getters/setters on FK', async function() { // Dummy getter/setter to test they are symmetric function toCustomFormat(string) { return string && `FORMAT-${string}`; @@ -1642,20 +1480,18 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { id: 'sJn369d8Em', children: [{ id: 'dgeQAQaW7A' }] }; - return this.sequelize.sync({ force: true }) - .then(() => Parent.create(values, { include: { model: Child, as: 'children' } })) - .then(father => { - // Make sure tasks are defined for created user - expect(father.id).to.be.equal('sJn369d8Em'); - expect(father.get('id', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); - - expect(father).to.have.property('children'); - expect(father.children).to.be.an('array'); - expect(father.children).to.lengthOf(1); - - expect(father.children[0].parent).to.be.equal('sJn369d8Em'); - expect(father.children[0].get('parent', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); - }); + await this.sequelize.sync({ force: true }); + const father = await Parent.create(values, { include: { model: Child, as: 'children' } }); + // Make sure tasks are defined for created user + expect(father.id).to.be.equal('sJn369d8Em'); + expect(father.get('id', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); + + expect(father).to.have.property('children'); + expect(father.children).to.be.an('array'); + expect(father.children).to.lengthOf(1); + + expect(father.children[0].parent).to.be.equal('sJn369d8Em'); + expect(father.children[0].get('parent', { raw: true })).to.be.equal('FORMAT-sJn369d8Em'); }); }); @@ -1676,27 +1512,27 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { return this.sequelize.sync({ force: true }); }); - it('should use the specified sourceKey instead of the primary key', function() { - return this.User.create({ username: 'John', email: 'john@example.com' }).then(() => - this.Task.bulkCreate([ - { title: 'Active Task', userEmail: 'john@example.com', taskStatus: 'Active' }, - { title: 'Inactive Task', userEmail: 'john@example.com', taskStatus: 'Inactive' } - ]) - ).then(() => - this.User.findOne({ - include: [ - { - model: this.Task, - where: { taskStatus: 'Active' } - } - ], - where: { username: 'John' } - }) - ).then(user => { - expect(user).to.be.ok; - expect(user.Tasks.length).to.equal(1); - expect(user.Tasks[0].title).to.equal('Active Task'); + it('should use the specified sourceKey instead of the primary key', async function() { + await this.User.create({ username: 'John', email: 'john@example.com' }); + + await this.Task.bulkCreate([ + { title: 'Active Task', userEmail: 'john@example.com', taskStatus: 'Active' }, + { title: 'Inactive Task', userEmail: 'john@example.com', taskStatus: 'Inactive' } + ]); + + const user = await this.User.findOne({ + include: [ + { + model: this.Task, + where: { taskStatus: 'Active' } + } + ], + where: { username: 'John' } }); + + expect(user).to.be.ok; + expect(user.Tasks.length).to.equal(1); + expect(user.Tasks[0].title).to.equal('Active Task'); }); }); @@ -1716,42 +1552,44 @@ describe(Support.getTestDialectTeaser('HasMany'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghats' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghats' }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.addPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghats.length).to.equal(1); - expect(individual.personwearinghats[0].name).to.equal('Baz'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.addPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghats.length).to.equal(1); + expect(individual.personwearinghats[0].name).to.equal('Baz'); }); }); }); diff --git a/test/integration/associations/has-one.test.js b/test/integration/associations/has-one.test.js index e1a2deabdd86..99697f7a86af 100644 --- a/test/integration/associations/has-one.test.js +++ b/test/integration/associations/has-one.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, current = Support.sequelize, dialect = Support.getTestDialect(); @@ -26,362 +25,276 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { describe('get', () => { describe('multiple', () => { - it('should fetch associations for multiple instances', function() { + it('should fetch associations for multiple instances', async function() { const User = this.sequelize.define('User', {}), Player = this.sequelize.define('Player', {}); Player.User = Player.hasOne(User, { as: 'user' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Player.create({ - id: 1, - user: {} - }, { - include: [Player.User] - }), - Player.create({ - id: 2, - user: {} - }, { - include: [Player.User] - }), - Player.create({ - id: 3 - }) - ); - }).then(players => { - return Player.User.get(players).then(result => { - expect(result[players[0].id].id).to.equal(players[0].user.id); - expect(result[players[1].id].id).to.equal(players[1].user.id); - expect(result[players[2].id]).to.equal(null); - }); - }); + await this.sequelize.sync({ force: true }); + + const players = await Promise.all([Player.create({ + id: 1, + user: {} + }, { + include: [Player.User] + }), Player.create({ + id: 2, + user: {} + }, { + include: [Player.User] + }), Player.create({ + id: 3 + })]); + + const result = await Player.User.get(players); + expect(result[players[0].id].id).to.equal(players[0].user.id); + expect(result[players[1].id].id).to.equal(players[1].user.id); + expect(result[players[2].id]).to.equal(null); }); }); }); describe('getAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.hasOne(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(fakeUser => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return Group.findAll({ transaction: t }).then(groups => { - return groups[0].getUser({ transaction: t }).then(associatedUser => { - expect(associatedUser).not.to.be.null; - expect(associatedUser.id).to.equal(user.id); - expect(associatedUser.id).not.to.equal(fakeUser.id); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.hasOne(User); + + await sequelize.sync({ force: true }); + const fakeUser = await User.create({ username: 'foo' }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + const groups0 = await Group.findAll({ transaction: t }); + const associatedUser0 = await groups0[0].getUser({ transaction: t }); + expect(associatedUser0).not.to.be.null; + expect(associatedUser0.id).to.equal(user.id); + expect(associatedUser0.id).not.to.equal(fakeUser.id); + await t.rollback(); }); } - it('should be able to handle a where object that\'s a first class citizen.', function() { + it('should be able to handle a where object that\'s a first class citizen.', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }); User.hasOne(Task); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', status: 'inactive' }).then(task => { - return user.setTaskXYZ(task).then(() => { - return user.getTaskXYZ({ where: { status: 'active' } }).then(task => { - expect(task).to.be.null; - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task', status: 'inactive' }); + await user.setTaskXYZ(task); + const task0 = await user.getTaskXYZ({ where: { status: 'active' } }); + expect(task0).to.be.null; }); - it('supports schemas', function() { + it('supports schemas', async function() { const User = this.sequelize.define('User', { username: Support.Sequelize.STRING }).schema('admin'), Group = this.sequelize.define('Group', { name: Support.Sequelize.STRING }).schema('admin'); Group.hasOne(User); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('admin'); - }).then(() => { - return Group.sync({ force: true }); - }).then(() => { - return User.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo' }), - User.create({ username: 'foo' }), - Group.create({ name: 'bar' }) - ]); - }).then(([fakeUser, user, group]) => { - return group.setUser(user).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).not.to.be.null; - expect(associatedUser.id).to.equal(user.id); - expect(associatedUser.id).not.to.equal(fakeUser.id); - }); - }); - }); - }).then(() => { - return this.sequelize.dropSchema('admin').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('admin'); - } - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('admin'); + await Group.sync({ force: true }); + await User.sync({ force: true }); + + const [fakeUser, user, group] = await Promise.all([ + User.create({ username: 'foo' }), + User.create({ username: 'foo' }), + Group.create({ name: 'bar' }) + ]); + + await group.setUser(user); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).not.to.be.null; + expect(associatedUser.id).to.equal(user.id); + expect(associatedUser.id).not.to.equal(fakeUser.id); + await this.sequelize.dropSchema('admin'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('admin'); + } }); }); describe('setAssociation', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }), - Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); - - Group.hasOne(User); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Group.create({ name: 'bar' }).then(group => { - return sequelize.transaction().then(t => { - return group.setUser(user, { transaction: t }).then(() => { - return Group.findAll().then(groups => { - return groups[0].getUser().then(associatedUser => { - expect(associatedUser).to.be.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }), + Group = sequelize.define('Group', { name: Support.Sequelize.STRING }); + + Group.hasOne(User); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const group = await Group.create({ name: 'bar' }); + const t = await sequelize.transaction(); + await group.setUser(user, { transaction: t }); + const groups = await Group.findAll(); + const associatedUser = await groups[0].getUser(); + expect(associatedUser).to.be.null; + await t.rollback(); }); } - it('can set an association with predefined primary keys', function() { + it('can set an association with predefined primary keys', async function() { const User = this.sequelize.define('UserXYZZ', { userCoolIdTag: { type: Sequelize.INTEGER, primaryKey: true }, username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZZ', { taskOrSomething: { type: Sequelize.INTEGER, primaryKey: true }, title: Sequelize.STRING }); User.hasOne(Task, { foreignKey: 'userCoolIdTag' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ userCoolIdTag: 1, username: 'foo' }).then(user => { - return Task.create({ taskOrSomething: 1, title: 'bar' }).then(task => { - return user.setTaskXYZZ(task).then(() => { - return user.getTaskXYZZ().then(task => { - expect(task).not.to.be.null; - - return user.setTaskXYZZ(null).then(() => { - return user.getTaskXYZZ().then(_task => { - expect(_task).to.be.null; - }); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ userCoolIdTag: 1, username: 'foo' }); + const task = await Task.create({ taskOrSomething: 1, title: 'bar' }); + await user.setTaskXYZZ(task); + const task0 = await user.getTaskXYZZ(); + expect(task0).not.to.be.null; + + await user.setTaskXYZZ(null); + const _task = await user.getTaskXYZZ(); + expect(_task).to.be.null; }); - it('clears the association if null is passed', function() { + it('clears the association if null is passed', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTaskXYZ(task).then(() => { - return user.getTaskXYZ().then(task => { - expect(task).not.to.equal(null); - - return user.setTaskXYZ(null).then(() => { - return user.getTaskXYZ().then(task => { - expect(task).to.equal(null); - }); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTaskXYZ(task); + const task1 = await user.getTaskXYZ(); + expect(task1).not.to.equal(null); + + await user.setTaskXYZ(null); + const task0 = await user.getTaskXYZ(); + expect(task0).to.equal(null); }); - it('should throw a ForeignKeyConstraintError if the associated record does not exist', function() { + it('should throw a ForeignKeyConstraintError if the associated record does not exist', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.create({ title: 'task' }).then(task => { - return expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + await expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const task = await Task.create({ title: 'task' }); + + await expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); - it('supports passing the primary key instead of an object', function() { + it('supports passing the primary key instead of an object', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - return Task.create({ id: 19, title: 'task it!' }).then(task => { - return user.setTaskXYZ(task.id).then(() => { - return user.getTaskXYZ().then(task => { - expect(task.title).to.equal('task it!'); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + const task = await Task.create({ id: 19, title: 'task it!' }); + await user.setTaskXYZ(task.id); + const task0 = await user.getTaskXYZ(); + expect(task0.title).to.equal('task it!'); }); - it('supports updating with a primary key instead of an object', function() { + it('supports updating with a primary key instead of an object', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING }); User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ id: 1, username: 'foo' }), - Task.create({ id: 20, title: 'bar' }) - ]); - }) - .then(([user, task]) => { - return user.setTaskXYZ(task.id) - .then(() => user.getTaskXYZ()) - .then(task => { - expect(task).not.to.be.null; - return Promise.all([ - user, - Task.create({ id: 2, title: 'bar2' }) - ]); - }); - }) - .then(([user, task2]) => { - return user.setTaskXYZ(task2.id) - .then(() => user.getTaskXYZ()) - .then(task => { - expect(task).not.to.be.null; - }); - }); + await this.sequelize.sync({ force: true }); + + const [user0, task1] = await Promise.all([ + User.create({ id: 1, username: 'foo' }), + Task.create({ id: 20, title: 'bar' }) + ]); + + await user0.setTaskXYZ(task1.id); + const task0 = await user0.getTaskXYZ(); + expect(task0).not.to.be.null; + + const [user, task2] = await Promise.all([ + user0, + Task.create({ id: 2, title: 'bar2' }) + ]); + + await user.setTaskXYZ(task2.id); + const task = await user.getTaskXYZ(); + expect(task).not.to.be.null; }); - it('supports setting same association twice', function() { + it('supports setting same association twice', async function() { const Home = this.sequelize.define('home', {}), User = this.sequelize.define('user'); User.hasOne(Home); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Home.create(), - User.create() - ]); - }).then(([home, user]) => { - ctx.home = home; - ctx.user = user; - return user.setHome(home); - }).then(() => { - return ctx.user.setHome(ctx.home); - }).then(() => { - return expect(ctx.user.getHome()).to.eventually.have.property('id', ctx.home.get('id')); - }); + await this.sequelize.sync({ force: true }); + + const [home, user] = await Promise.all([ + Home.create(), + User.create() + ]); + + await user.setHome(home); + await user.setHome(home); + await expect(user.getHome()).to.eventually.have.property('id', home.get('id')); }); }); describe('createAssociation', () => { - it('creates an associated model instance', function() { + it('creates an associated model instance', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }), Task = this.sequelize.define('Task', { title: Sequelize.STRING }); User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - return user.createTask({ title: 'task' }).then(() => { - return user.getTask().then(task => { - expect(task).not.to.be.null; - expect(task.title).to.equal('task'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + await user.createTask({ title: 'task' }); + const task = await user.getTask(); + expect(task).not.to.be.null; + expect(task.title).to.equal('task'); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }), - Group = sequelize.define('Group', { name: Sequelize.STRING }); - - User.hasOne(Group); - - return sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - return sequelize.transaction().then(t => { - return user.createGroup({ name: 'testgroup' }, { transaction: t }).then(() => { - return User.findAll().then(users => { - return users[0].getGroup().then(group => { - expect(group).to.be.null; - return User.findAll({ transaction: t }).then(users => { - return users[0].getGroup({ transaction: t }).then(group => { - expect(group).to.be.not.null; - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }), + Group = sequelize.define('Group', { name: Sequelize.STRING }); + + User.hasOne(Group); + + await sequelize.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + const t = await sequelize.transaction(); + await user.createGroup({ name: 'testgroup' }, { transaction: t }); + const users = await User.findAll(); + const group = await users[0].getGroup(); + expect(group).to.be.null; + const users0 = await User.findAll({ transaction: t }); + const group0 = await users0[0].getGroup({ transaction: t }); + expect(group0).to.be.not.null; + await t.rollback(); }); } @@ -408,7 +321,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { expect(User.rawAttributes.AccountId.field).to.equal('AccountId'); }); - it('should support specifying the field of a foreign key', function() { + it('should support specifying the field of a foreign key', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }), Task = this.sequelize.define('TaskXYZ', { title: Sequelize.STRING, status: Sequelize.STRING }); @@ -421,223 +334,194 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { expect(User.rawAttributes.taskId).to.exist; expect(User.rawAttributes.taskId.field).to.equal('task_id'); - return Task.sync({ force: true }).then(() => { - // Can't use Promise.all cause of foreign key references - return User.sync({ force: true }); - }).then(() => { - return Promise.all([ - User.create({ username: 'foo', gender: 'male' }), - Task.create({ title: 'task', status: 'inactive' }) - ]); - }).then(([user, task]) => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ(); - }); - }).then(user => { - // the sql query should correctly look at task_id instead of taskId - expect(user).to.not.be.null; - return Task.findOne({ - where: { title: 'task' }, - include: [User] - }); - }).then(task => { - expect(task.UserXYZ).to.exist; + await Task.sync({ force: true }); + await User.sync({ force: true }); + + const [user0, task0] = await Promise.all([ + User.create({ username: 'foo', gender: 'male' }), + Task.create({ title: 'task', status: 'inactive' }) + ]); + + await task0.setUserXYZ(user0); + const user = await task0.getUserXYZ(); + // the sql query should correctly look at task_id instead of taskId + expect(user).to.not.be.null; + + const task = await Task.findOne({ + where: { title: 'task' }, + include: [User] }); + + expect(task.UserXYZ).to.exist; + }); + + it('should support custom primary key field name in sub queries', async function() { + const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING, gender: Sequelize.STRING }), + Task = this.sequelize.define('TaskXYZ', { id: { + field: 'Id', + type: Sequelize.INTEGER, + autoIncrement: true, + primaryKey: true + }, title: Sequelize.STRING, status: Sequelize.STRING }); + + Task.hasOne(User); + + await Task.sync({ force: true }); + await User.sync({ force: true }); + + const task0 = await Task.create({ title: 'task', status: 'inactive', User: { username: 'foo', gender: 'male' } }, { include: User }); + await expect(task0.reload({ subQuery: true })).to.not.eventually.be.rejected; }); }); describe('foreign key constraints', () => { - it('are enabled by default', function() { + it('are enabled by default', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task); // defaults to set NULL - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(null); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(null); }); - it('sets to CASCADE if allowNull: false', function() { + it('sets to CASCADE if allowNull: false', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { foreignKey: { allowNull: false } }); // defaults to CASCADE - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task', UserId: user.id }).then(() => { - return user.destroy().then(() => { - return Task.findAll(); - }); - }); - }).then(tasks => { - expect(tasks).to.be.empty; - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ username: 'foo' }); + await Task.create({ title: 'task', UserId: user.id }); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.be.empty; }); - it('should be possible to disable them', function() { + it('should be possible to disable them', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { constraints: false }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return user.destroy().then(() => { - return task.reload().then(() => { - expect(task.UserId).to.equal(user.id); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await user.destroy(); + await task.reload(); + expect(task.UserId).to.equal(user.id); }); - it('can cascade deletes', function() { + it('can cascade deletes', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onDelete: 'cascade' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return user.destroy().then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(0); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await user.destroy(); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(0); }); - it('works when cascading a delete with hooks but there is no associate (i.e. "has zero")', function() { + it('works when cascading a delete with hooks but there is no associate (i.e. "has zero")', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onDelete: 'cascade', hooks: true }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return user.destroy(); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + + await user.destroy(); }); // NOTE: mssql does not support changing an autoincrement primary key if (Support.getTestDialect() !== 'mssql') { - it('can cascade updates', function() { + it('can cascade updates', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onUpdate: 'cascade' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); - return user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - expect(tasks[0].UserId).to.equal(999); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + await user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); + expect(tasks[0].UserId).to.equal(999); }); } if (current.dialect.supports.constraints.restrict) { - it('can restrict deletes', function() { + it('can restrict deletes', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onDelete: 'restrict' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - return expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + await expect(user.destroy()).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + const tasks = await Task.findAll(); + expect(tasks).to.have.length(1); }); - it('can restrict updates', function() { + it('can restrict updates', async function() { const Task = this.sequelize.define('Task', { title: Sequelize.STRING }), User = this.sequelize.define('User', { username: Sequelize.STRING }); User.hasOne(Task, { onUpdate: 'restrict' }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return Task.create({ title: 'task' }).then(task => { - return user.setTask(task).then(() => { - - // Changing the id of a DAO requires a little dance since - // the `UPDATE` query generated by `save()` uses `id` in the - // `WHERE` clause - - const tableName = user.sequelize.getQueryInterface().QueryGenerator.addSchema(user.constructor); - return expect( - user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) - ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError).then(() => { - // Should fail due to FK restriction - return Task.findAll().then(tasks => { - expect(tasks).to.have.length(1); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const task = await Task.create({ title: 'task' }); + await user.setTask(task); + + // Changing the id of a DAO requires a little dance since + // the `UPDATE` query generated by `save()` uses `id` in the + // `WHERE` clause + + const tableName = user.sequelize.getQueryInterface().queryGenerator.addSchema(user.constructor); + + await expect( + user.sequelize.getQueryInterface().update(user, tableName, { id: 999 }, { id: user.id }) + ).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + + // Should fail due to FK restriction + const tasks = await Task.findAll(); + + expect(tasks).to.have.length(1); }); } @@ -645,7 +529,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { }); describe('association column', () => { - it('has correct type for non-id primary keys with non-integer type', function() { + it('has correct type for non-id primary keys with non-integer type', async function() { const User = this.sequelize.define('UserPKBT', { username: { type: Sequelize.STRING @@ -661,12 +545,11 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { Group.hasOne(User); - return this.sequelize.sync({ force: true }).then(() => { - expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(Sequelize.STRING); - }); + await this.sequelize.sync({ force: true }); + expect(User.rawAttributes.GroupPKBTName.type).to.an.instanceof(Sequelize.STRING); }); - it('should support a non-primary key as the association column on a target with custom primary key', function() { + it('should support a non-primary key as the association column on a target with custom primary key', async function() { const User = this.sequelize.define('User', { user_name: { unique: true, @@ -681,22 +564,16 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { User.hasOne(Task, { foreignKey: 'username', sourceKey: 'user_name' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ user_name: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newUser.setTask(newTask).then(() => { - return User.findOne({ where: { user_name: 'bob' } }).then(foundUser => { - return foundUser.getTask().then(foundTask => { - expect(foundTask.title).to.equal('some task'); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ user_name: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newUser.setTask(newTask); + const foundUser = await User.findOne({ where: { user_name: 'bob' } }); + const foundTask = await foundUser.getTask(); + expect(foundTask.title).to.equal('some task'); }); - it('should support a non-primary unique key as the association column', function() { + it('should support a non-primary unique key as the association column', async function() { const User = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -711,22 +588,16 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newUser.setTask(newTask).then(() => { - return User.findOne({ where: { username: 'bob' } }).then(foundUser => { - return foundUser.getTask().then(foundTask => { - expect(foundTask.title).to.equal('some task'); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newUser.setTask(newTask); + const foundUser = await User.findOne({ where: { username: 'bob' } }); + const foundTask = await foundUser.getTask(); + expect(foundTask.title).to.equal('some task'); }); - it('should support a non-primary unique key as the association column with a field option', function() { + it('should support a non-primary unique key as the association column with a field option', async function() { const User = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -742,38 +613,31 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { User.hasOne(Task, { foreignKey: 'username', sourceKey: 'username' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newUser.setTask(newTask).then(() => { - return User.findOne({ where: { username: 'bob' } }).then(foundUser => { - return foundUser.getTask().then(foundTask => { - expect(foundTask.title).to.equal('some task'); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newUser.setTask(newTask); + const foundUser = await User.findOne({ where: { username: 'bob' } }); + const foundTask = await foundUser.getTask(); + expect(foundTask.title).to.equal('some task'); }); }); describe('Association options', () => { - it('can specify data type for autogenerated relational keys', function() { + it('can specify data type for autogenerated relational keys', async function() { const User = this.sequelize.define('UserXYZ', { username: Sequelize.STRING }), dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT, Sequelize.STRING], Tasks = {}; - return Promise.map(dataTypes, dataType => { + await Promise.all(dataTypes.map(async dataType => { const tableName = `TaskXYZ_${dataType.key}`; Tasks[dataType] = this.sequelize.define(tableName, { title: Sequelize.STRING }); User.hasOne(Tasks[dataType], { foreignKey: 'userId', keyType: dataType, constraints: false }); - return Tasks[dataType].sync({ force: true }).then(() => { - expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); - }); - }); + await Tasks[dataType].sync({ force: true }); + expect(Tasks[dataType].rawAttributes.userId.type).to.be.an.instanceof(dataType); + })); }); describe('allows the user to provide an attribute definition object as foreignKey', () => { @@ -898,51 +762,53 @@ describe(Support.getTestDialectTeaser('HasOne'), () => { }); }); - it('should load with an alias', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ model: this.Hat, as: 'personwearinghat' }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ - model: this.Hat, - as: { singular: 'personwearinghat' } - }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load with an alias', async function() { + await this.sequelize.sync({ force: true }); + + const [individual1, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual1.setPersonwearinghat(hat); + + const individual0 = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ model: this.Hat, as: 'personwearinghat' }] + }); + + expect(individual0.name).to.equal('Foo Bar'); + expect(individual0.personwearinghat.name).to.equal('Baz'); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ + model: this.Hat, + as: { singular: 'personwearinghat' } + }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); - it('should load all', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Individual.create({ name: 'Foo Bar' }), - this.Hat.create({ name: 'Baz' })); - }).then(([individual, hat]) => { - return individual.setPersonwearinghat(hat); - }).then(() => { - return this.Individual.findOne({ - where: { name: 'Foo Bar' }, - include: [{ all: true }] - }); - }).then(individual => { - expect(individual.name).to.equal('Foo Bar'); - expect(individual.personwearinghat.name).to.equal('Baz'); + it('should load all', async function() { + await this.sequelize.sync({ force: true }); + + const [individual0, hat] = await Promise.all([ + this.Individual.create({ name: 'Foo Bar' }), + this.Hat.create({ name: 'Baz' }) + ]); + + await individual0.setPersonwearinghat(hat); + + const individual = await this.Individual.findOne({ + where: { name: 'Foo Bar' }, + include: [{ all: true }] }); + + expect(individual.name).to.equal('Foo Bar'); + expect(individual.personwearinghat.name).to.equal('Baz'); }); }); }); diff --git a/test/integration/associations/multiple-level-filters.test.js b/test/integration/associations/multiple-level-filters.test.js index 17baadd9eebd..267cbbe6249a 100644 --- a/test/integration/associations/multiple-level-filters.test.js +++ b/test/integration/associations/multiple-level-filters.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { - it('can filter through belongsTo', function() { + it('can filter through belongsTo', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -17,55 +17,54 @@ describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { Task.belongsTo(Project); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - UserId: 1, - title: 'republic' - }, { - UserId: 2, - title: 'empire' - }]).then(() => { - return Task.bulkCreate([{ - ProjectId: 1, - title: 'fight empire' - }, { - ProjectId: 1, - title: 'stablish republic' - }, { - ProjectId: 2, - title: 'destroy rebel alliance' - }, { - ProjectId: 2, - title: 'rule everything' - }]).then(() => { - return Task.findAll({ - include: [ - { - model: Project, - include: [ - { model: User, where: { username: 'leia' } } - ], - required: true - } - ] - }).then(tasks => { - - expect(tasks.length).to.be.equal(2); - expect(tasks[0].title).to.be.equal('fight empire'); - expect(tasks[1].title).to.be.equal('stablish republic'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + UserId: 1, + title: 'republic' + }, { + UserId: 2, + title: 'empire' + }]); + + await Task.bulkCreate([{ + ProjectId: 1, + title: 'fight empire' + }, { + ProjectId: 1, + title: 'stablish republic' + }, { + ProjectId: 2, + title: 'destroy rebel alliance' + }, { + ProjectId: 2, + title: 'rule everything' + }]); + + const tasks = await Task.findAll({ + include: [ + { + model: Project, + include: [ + { model: User, where: { username: 'leia' } } + ], + required: true + } + ] }); + + expect(tasks.length).to.be.equal(2); + expect(tasks[0].title).to.be.equal('fight empire'); + expect(tasks[1].title).to.be.equal('stablish republic'); }); - it('avoids duplicated tables in query', function() { + it('avoids duplicated tables in query', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -76,57 +75,57 @@ describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { Task.belongsTo(Project); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - UserId: 1, - title: 'republic' - }, { - UserId: 2, - title: 'empire' - }]).then(() => { - return Task.bulkCreate([{ - ProjectId: 1, - title: 'fight empire' - }, { - ProjectId: 1, - title: 'stablish republic' - }, { - ProjectId: 2, - title: 'destroy rebel alliance' - }, { - ProjectId: 2, - title: 'rule everything' - }]).then(() => { - return Task.findAll({ - include: [ - { - model: Project, - include: [ - { model: User, where: { - username: 'leia', - id: 1 - } } - ], - required: true - } - ] - }).then(tasks => { - expect(tasks.length).to.be.equal(2); - expect(tasks[0].title).to.be.equal('fight empire'); - expect(tasks[1].title).to.be.equal('stablish republic'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + UserId: 1, + title: 'republic' + }, { + UserId: 2, + title: 'empire' + }]); + + await Task.bulkCreate([{ + ProjectId: 1, + title: 'fight empire' + }, { + ProjectId: 1, + title: 'stablish republic' + }, { + ProjectId: 2, + title: 'destroy rebel alliance' + }, { + ProjectId: 2, + title: 'rule everything' + }]); + + const tasks = await Task.findAll({ + include: [ + { + model: Project, + include: [ + { model: User, where: { + username: 'leia', + id: 1 + } } + ], + required: true + } + ] }); + + expect(tasks.length).to.be.equal(2); + expect(tasks[0].title).to.be.equal('fight empire'); + expect(tasks[1].title).to.be.equal('stablish republic'); }); - it('can filter through hasMany', function() { + it('can filter through hasMany', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -137,92 +136,87 @@ describe(Support.getTestDialectTeaser('Multiple Level Filters'), () => { Task.belongsTo(Project); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - UserId: 1, - title: 'republic' - }, { - UserId: 2, - title: 'empire' - }]).then(() => { - return Task.bulkCreate([{ - ProjectId: 1, - title: 'fight empire' - }, { - ProjectId: 1, - title: 'stablish republic' - }, { - ProjectId: 2, - title: 'destroy rebel alliance' - }, { - ProjectId: 2, - title: 'rule everything' - }]).then(() => { - return User.findAll({ - include: [ - { - model: Project, - include: [ - { model: Task, where: { title: 'fight empire' } } - ], - required: true - } - ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('leia'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + UserId: 1, + title: 'republic' + }, { + UserId: 2, + title: 'empire' + }]); + + await Task.bulkCreate([{ + ProjectId: 1, + title: 'fight empire' + }, { + ProjectId: 1, + title: 'stablish republic' + }, { + ProjectId: 2, + title: 'destroy rebel alliance' + }, { + ProjectId: 2, + title: 'rule everything' + }]); + + const users = await User.findAll({ + include: [ + { + model: Project, + include: [ + { model: Task, where: { title: 'fight empire' } } + ], + required: true + } + ] }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('leia'); }); - it('can filter through hasMany connector', function() { + it('can filter through hasMany connector', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }), Project = this.sequelize.define('Project', { title: DataTypes.STRING }); Project.belongsToMany(User, { through: 'user_project' }); User.belongsToMany(Project, { through: 'user_project' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'leia' - }, { - username: 'vader' - }]).then(() => { - return Project.bulkCreate([{ - title: 'republic' - }, { - title: 'empire' - }]).then(() => { - return User.findByPk(1).then(user => { - return Project.findByPk(1).then(project => { - return user.setProjects([project]).then(() => { - return User.findByPk(2).then(user => { - return Project.findByPk(2).then(project => { - return user.setProjects([project]).then(() => { - return User.findAll({ - include: [ - { model: Project, where: { title: 'republic' } } - ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('leia'); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([{ + username: 'leia' + }, { + username: 'vader' + }]); + + await Project.bulkCreate([{ + title: 'republic' + }, { + title: 'empire' + }]); + + const user = await User.findByPk(1); + const project = await Project.findByPk(1); + await user.setProjects([project]); + const user0 = await User.findByPk(2); + const project0 = await Project.findByPk(2); + await user0.setProjects([project0]); + + const users = await User.findAll({ + include: [ + { model: Project, where: { title: 'republic' } } + ] }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('leia'); }); }); diff --git a/test/integration/associations/scope.test.js b/test/integration/associations/scope.test.js index bc55d320fa08..bbf2a178ef00 100644 --- a/test/integration/associations/scope.test.js +++ b/test/integration/associations/scope.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, Op = Sequelize.Op; describe(Support.getTestDialectTeaser('associations'), () => { @@ -20,6 +19,7 @@ describe(Support.getTestDialectTeaser('associations'), () => { commentable: Sequelize.STRING, commentable_id: Sequelize.INTEGER, isMain: { + field: 'is_main', type: Sequelize.BOOLEAN, defaultValue: false } @@ -97,238 +97,212 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); describe('1:1', () => { - it('should create, find and include associations with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Post.create(), - this.Comment.create({ - title: 'I am a comment' - }), - this.Comment.create({ - title: 'I am a main comment', - isMain: true - }) - ); - }).then(([post]) => { - this.post = post; - return post.createComment({ - title: 'I am a post comment' - }); - }).then(comment => { - expect(comment.get('commentable')).to.equal('post'); - expect(comment.get('isMain')).to.be.false; - return this.Post.scope('withMainComment').findByPk(this.post.get('id')); - }).then(post => { - expect(post.mainComment).to.be.null; - return post.createMainComment({ - title: 'I am a main post comment' - }); - }).then(mainComment => { - this.mainComment = mainComment; - expect(mainComment.get('commentable')).to.equal('post'); - expect(mainComment.get('isMain')).to.be.true; - return this.Post.scope('withMainComment').findByPk(this.post.id); - }).then(post => { - expect(post.mainComment.get('id')).to.equal(this.mainComment.get('id')); - return post.getMainComment(); - }).then(mainComment => { - expect(mainComment.get('commentable')).to.equal('post'); - expect(mainComment.get('isMain')).to.be.true; - return this.Comment.create({ - title: 'I am a future main comment' - }); - }).then(comment => { - return this.post.setMainComment(comment); - }).then(() => { - return this.post.getMainComment(); - }).then(mainComment => { - expect(mainComment.get('commentable')).to.equal('post'); - expect(mainComment.get('isMain')).to.be.true; - expect(mainComment.get('title')).to.equal('I am a future main comment'); + it('should create, find and include associations with scope values', async function() { + await this.sequelize.sync({ force: true }); + + const [post1] = await Promise.all([this.Post.create(), this.Comment.create({ + title: 'I am a comment' + }), this.Comment.create({ + title: 'I am a main comment', + isMain: true + })]); + + this.post = post1; + + const comment0 = await post1.createComment({ + title: 'I am a post comment' + }); + + expect(comment0.get('commentable')).to.equal('post'); + expect(comment0.get('isMain')).to.be.false; + const post0 = await this.Post.scope('withMainComment').findByPk(this.post.get('id')); + expect(post0.mainComment).to.be.null; + + const mainComment1 = await post0.createMainComment({ + title: 'I am a main post comment' }); + + this.mainComment = mainComment1; + expect(mainComment1.get('commentable')).to.equal('post'); + expect(mainComment1.get('isMain')).to.be.true; + const post = await this.Post.scope('withMainComment').findByPk(this.post.id); + expect(post.mainComment.get('id')).to.equal(this.mainComment.get('id')); + const mainComment0 = await post.getMainComment(); + expect(mainComment0.get('commentable')).to.equal('post'); + expect(mainComment0.get('isMain')).to.be.true; + + const comment = await this.Comment.create({ + title: 'I am a future main comment' + }); + + await this.post.setMainComment(comment); + const mainComment = await this.post.getMainComment(); + expect(mainComment.get('commentable')).to.equal('post'); + expect(mainComment.get('isMain')).to.be.true; + expect(mainComment.get('title')).to.equal('I am a future main comment'); }); - it('should create included association with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return this.Post.create({ - mainComment: { - title: 'I am a main comment created with a post' - } - }, { - include: [{ model: this.Comment, as: 'mainComment' }] - }); - }).then(post => { - expect(post.mainComment.get('commentable')).to.equal('post'); - expect(post.mainComment.get('isMain')).to.be.true; - return this.Post.scope('withMainComment').findByPk(post.id); - }).then(post => { - expect(post.mainComment.get('commentable')).to.equal('post'); - expect(post.mainComment.get('isMain')).to.be.true; + it('should create included association with scope values', async function() { + await this.sequelize.sync({ force: true }); + + const post0 = await this.Post.create({ + mainComment: { + title: 'I am a main comment created with a post' + } + }, { + include: [{ model: this.Comment, as: 'mainComment' }] }); + + expect(post0.mainComment.get('commentable')).to.equal('post'); + expect(post0.mainComment.get('isMain')).to.be.true; + const post = await this.Post.scope('withMainComment').findByPk(post0.id); + expect(post.mainComment.get('commentable')).to.equal('post'); + expect(post.mainComment.get('isMain')).to.be.true; }); }); describe('1:M', () => { - it('should create, find and include associations with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Post.create(), - this.Image.create(), - this.Question.create(), - this.Comment.create({ - title: 'I am a image comment' - }), - this.Comment.create({ - title: 'I am a question comment' - }) - ); - }).then(([post, image, question, commentA, commentB]) => { - this.post = post; - this.image = image; - this.question = question; - return Promise.join( - post.createComment({ - title: 'I am a post comment' - }), - image.addComment(commentA), - question.setComments([commentB]) - ); - }).then(() => { - return this.Comment.findAll(); - }).then(comments => { - comments.forEach(comment => { - expect(comment.get('commentable')).to.be.ok; - }); - expect(comments.map(comment => { - return comment.get('commentable'); - }).sort()).to.deep.equal(['image', 'post', 'question']); - }).then(() => { - return Promise.join( - this.post.getComments(), - this.image.getComments(), - this.question.getComments() - ); - }).then(([postComments, imageComments, questionComments]) => { - expect(postComments.length).to.equal(1); - expect(postComments[0].get('title')).to.equal('I am a post comment'); - expect(imageComments.length).to.equal(1); - expect(imageComments[0].get('title')).to.equal('I am a image comment'); - expect(questionComments.length).to.equal(1); - expect(questionComments[0].get('title')).to.equal('I am a question comment'); - - return [postComments[0], imageComments[0], questionComments[0]]; - }).then(([postComment, imageComment, questionComment]) => { - return Promise.join( - postComment.getItem(), - imageComment.getItem(), - questionComment.getItem() - ); - }).then(([post, image, question]) => { - expect(post).to.be.instanceof(this.Post); - expect(image).to.be.instanceof(this.Image); - expect(question).to.be.instanceof(this.Question); - }).then(() => { - return Promise.join( - this.Post.findOne({ - include: [this.Comment] - }), - this.Image.findOne({ - include: [this.Comment] - }), - this.Question.findOne({ - include: [this.Comment] - }) - ); - }).then(([post, image, question]) => { - expect(post.comments.length).to.equal(1); - expect(post.comments[0].get('title')).to.equal('I am a post comment'); - expect(image.comments.length).to.equal(1); - expect(image.comments[0].get('title')).to.equal('I am a image comment'); - expect(question.comments.length).to.equal(1); - expect(question.comments[0].get('title')).to.equal('I am a question comment'); + it('should create, find and include associations with scope values', async function() { + await this.sequelize.sync({ force: true }); + + const [post1, image1, question1, commentA, commentB] = await Promise.all([ + this.Post.create(), + this.Image.create(), + this.Question.create(), + this.Comment.create({ + title: 'I am a image comment' + }), + this.Comment.create({ + title: 'I am a question comment' + }) + ]); + + this.post = post1; + this.image = image1; + this.question = question1; + + await Promise.all([post1.createComment({ + title: 'I am a post comment' + }), image1.addComment(commentA), question1.setComments([commentB])]); + + const comments = await this.Comment.findAll(); + comments.forEach(comment => { + expect(comment.get('commentable')).to.be.ok; }); + expect(comments.map(comment => { + return comment.get('commentable'); + }).sort()).to.deep.equal(['image', 'post', 'question']); + + const [postComments, imageComments, questionComments] = await Promise.all([ + this.post.getComments(), + this.image.getComments(), + this.question.getComments() + ]); + + expect(postComments.length).to.equal(1); + expect(postComments[0].get('title')).to.equal('I am a post comment'); + expect(imageComments.length).to.equal(1); + expect(imageComments[0].get('title')).to.equal('I am a image comment'); + expect(questionComments.length).to.equal(1); + expect(questionComments[0].get('title')).to.equal('I am a question comment'); + + const [postComment, imageComment, questionComment] = [postComments[0], imageComments[0], questionComments[0]]; + const [post0, image0, question0] = await Promise.all([postComment.getItem(), imageComment.getItem(), questionComment.getItem()]); + expect(post0).to.be.instanceof(this.Post); + expect(image0).to.be.instanceof(this.Image); + expect(question0).to.be.instanceof(this.Question); + + const [post, image, question] = await Promise.all([this.Post.findOne({ + include: [this.Comment] + }), this.Image.findOne({ + include: [this.Comment] + }), this.Question.findOne({ + include: [this.Comment] + })]); + + expect(post.comments.length).to.equal(1); + expect(post.comments[0].get('title')).to.equal('I am a post comment'); + expect(image.comments.length).to.equal(1); + expect(image.comments[0].get('title')).to.equal('I am a image comment'); + expect(question.comments.length).to.equal(1); + expect(question.comments[0].get('title')).to.equal('I am a question comment'); }); - it('should make the same query if called multiple time (#4470)', function() { + it('should make the same query if called multiple time (#4470)', async function() { const logs = []; const logging = function(log) { //removing 'executing( || 'default'}) :' from logs logs.push(log.substring(log.indexOf(':') + 1)); }; - return this.sequelize.sync({ force: true }).then(() => { - return this.Post.create(); - }).then(post => { - return post.createComment({ - title: 'I am a post comment' - }); - }).then(() => { - return this.Post.scope('withComments').findAll({ - logging - }); - }).then(() => { - return this.Post.scope('withComments').findAll({ - logging - }); - }).then(() => { - expect(logs[0]).to.equal(logs[1]); + await this.sequelize.sync({ force: true }); + const post = await this.Post.create(); + + await post.createComment({ + title: 'I am a post comment' + }); + + await this.Post.scope('withComments').findAll({ + logging }); + + await this.Post.scope('withComments').findAll({ + logging + }); + + expect(logs[0]).to.equal(logs[1]); }); - it('should created included association with scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return this.Post.create({ - comments: [{ - title: 'I am a comment created with a post' - }, { - title: 'I am a second comment created with a post' - }] + it('should created included association with scope values', async function() { + await this.sequelize.sync({ force: true }); + let post = await this.Post.create({ + comments: [{ + title: 'I am a comment created with a post' }, { - include: [{ model: this.Comment, as: 'comments' }] - }); - }).then(post => { - this.post = post; - return post.comments; - }).each(comment => { + title: 'I am a second comment created with a post' + }] + }, { + include: [{ model: this.Comment, as: 'comments' }] + }); + this.post = post; + for (const comment of post.comments) { expect(comment.get('commentable')).to.equal('post'); - }).then(() => { - return this.Post.scope('withComments').findByPk(this.post.id); - }).then(post => { - return post.getComments(); - }).each(comment => { + } + post = await this.Post.scope('withComments').findByPk(this.post.id); + for (const comment of post.comments) { expect(comment.get('commentable')).to.equal('post'); - }); + } }); - it('should include associations with operator scope values', function() { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Post.create(), - this.Comment.create({ - title: 'I am a blue comment', - type: 'blue' - }), - this.Comment.create({ - title: 'I am a red comment', - type: 'red' - }), - this.Comment.create({ - title: 'I am a green comment', - type: 'green' - }) - ); - }).then(([post, commentA, commentB, commentC]) => { - this.post = post; - return post.addComments([commentA, commentB, commentC]); - }).then(() => { - return this.Post.findByPk(this.post.id, { - include: [{ - model: this.Comment, - as: 'coloredComments' - }] - }); - }).then(post => { - expect(post.coloredComments.length).to.equal(2); - for (const comment of post.coloredComments) { - expect(comment.type).to.match(/blue|green/); - } + it('should include associations with operator scope values', async function() { + await this.sequelize.sync({ force: true }); + + const [post0, commentA, commentB, commentC] = await Promise.all([this.Post.create(), this.Comment.create({ + title: 'I am a blue comment', + type: 'blue' + }), this.Comment.create({ + title: 'I am a red comment', + type: 'red' + }), this.Comment.create({ + title: 'I am a green comment', + type: 'green' + })]); + + this.post = post0; + await post0.addComments([commentA, commentB, commentC]); + + const post = await this.Post.findByPk(this.post.id, { + include: [{ + model: this.Comment, + as: 'coloredComments' + }] }); + + expect(post.coloredComments.length).to.equal(2); + for (const comment of post.coloredComments) { + expect(comment.type).to.match(/blue|green/); + } + }); + it('should not mutate scope when running SELECT query (#12868)', async function() { + await this.sequelize.sync({ force: true }); + await this.Post.findOne({ where: {}, include: [{ association: this.Post.associations.mainComment, attributes: ['id'], required: true, where: {} }] }); + expect(this.Post.associations.mainComment.scope.isMain).to.equal(true); }); }); @@ -347,103 +321,95 @@ describe(Support.getTestDialectTeaser('associations'), () => { this.Post.belongsToMany(this.Tag, { as: 'tags', through: this.PostTag, scope: { type: 'tag' } }); }); - it('should create, find and include associations with scope values', function() { - return Promise.join( - this.Post.sync({ force: true }), - this.Tag.sync({ force: true }) - ).then(() => { - return this.PostTag.sync({ force: true }); - }).then(() => { - return Promise.join( - this.Post.create(), - this.Post.create(), - this.Post.create(), - this.Tag.create({ type: 'category' }), - this.Tag.create({ type: 'category' }), - this.Tag.create({ type: 'tag' }), - this.Tag.create({ type: 'tag' }) - ); - }).then(([postA, postB, postC, categoryA, categoryB, tagA, tagB]) => { - this.postA = postA; - this.postB = postB; - this.postC = postC; - - return Promise.join( - postA.addCategory(categoryA), - postB.setCategories([categoryB]), - postC.createCategory(), - postA.createTag(), - postB.addTag(tagA), - postC.setTags([tagB]) - ); - }).then(() => { - return Promise.join( - this.postA.getCategories(), - this.postA.getTags(), - this.postB.getCategories(), - this.postB.getTags(), - this.postC.getCategories(), - this.postC.getTags() - ); - }).then(([postACategories, postATags, postBCategories, postBTags, postCCategories, postCTags]) => { - expect(postACategories.length).to.equal(1); - expect(postATags.length).to.equal(1); - expect(postBCategories.length).to.equal(1); - expect(postBTags.length).to.equal(1); - expect(postCCategories.length).to.equal(1); - expect(postCTags.length).to.equal(1); - - expect(postACategories[0].get('type')).to.equal('category'); - expect(postATags[0].get('type')).to.equal('tag'); - expect(postBCategories[0].get('type')).to.equal('category'); - expect(postBTags[0].get('type')).to.equal('tag'); - expect(postCCategories[0].get('type')).to.equal('category'); - expect(postCTags[0].get('type')).to.equal('tag'); - }).then(() => { - return Promise.join( - this.Post.findOne({ - where: { - id: this.postA.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }), - this.Post.findOne({ - where: { - id: this.postB.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }), - this.Post.findOne({ - where: { - id: this.postC.get('id') - }, - include: [ - { model: this.Tag, as: 'tags' }, - { model: this.Tag, as: 'categories' } - ] - }) - ); - }).then(([postA, postB, postC]) => { - expect(postA.get('categories').length).to.equal(1); - expect(postA.get('tags').length).to.equal(1); - expect(postB.get('categories').length).to.equal(1); - expect(postB.get('tags').length).to.equal(1); - expect(postC.get('categories').length).to.equal(1); - expect(postC.get('tags').length).to.equal(1); - - expect(postA.get('categories')[0].get('type')).to.equal('category'); - expect(postA.get('tags')[0].get('type')).to.equal('tag'); - expect(postB.get('categories')[0].get('type')).to.equal('category'); - expect(postB.get('tags')[0].get('type')).to.equal('tag'); - expect(postC.get('categories')[0].get('type')).to.equal('category'); - expect(postC.get('tags')[0].get('type')).to.equal('tag'); - }); + it('should create, find and include associations with scope values', async function() { + await Promise.all([this.Post.sync({ force: true }), this.Tag.sync({ force: true })]); + await this.PostTag.sync({ force: true }); + + const [postA0, postB0, postC0, categoryA, categoryB, tagA, tagB] = await Promise.all([ + this.Post.create(), + this.Post.create(), + this.Post.create(), + this.Tag.create({ type: 'category' }), + this.Tag.create({ type: 'category' }), + this.Tag.create({ type: 'tag' }), + this.Tag.create({ type: 'tag' }) + ]); + + this.postA = postA0; + this.postB = postB0; + this.postC = postC0; + + await Promise.all([ + postA0.addCategory(categoryA), + postB0.setCategories([categoryB]), + postC0.createCategory(), + postA0.createTag(), + postB0.addTag(tagA), + postC0.setTags([tagB]) + ]); + + const [postACategories, postATags, postBCategories, postBTags, postCCategories, postCTags] = await Promise.all([ + this.postA.getCategories(), + this.postA.getTags(), + this.postB.getCategories(), + this.postB.getTags(), + this.postC.getCategories(), + this.postC.getTags() + ]); + + expect(postACategories.length).to.equal(1); + expect(postATags.length).to.equal(1); + expect(postBCategories.length).to.equal(1); + expect(postBTags.length).to.equal(1); + expect(postCCategories.length).to.equal(1); + expect(postCTags.length).to.equal(1); + + expect(postACategories[0].get('type')).to.equal('category'); + expect(postATags[0].get('type')).to.equal('tag'); + expect(postBCategories[0].get('type')).to.equal('category'); + expect(postBTags[0].get('type')).to.equal('tag'); + expect(postCCategories[0].get('type')).to.equal('category'); + expect(postCTags[0].get('type')).to.equal('tag'); + + const [postA, postB, postC] = await Promise.all([this.Post.findOne({ + where: { + id: this.postA.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + }), this.Post.findOne({ + where: { + id: this.postB.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + }), this.Post.findOne({ + where: { + id: this.postC.get('id') + }, + include: [ + { model: this.Tag, as: 'tags' }, + { model: this.Tag, as: 'categories' } + ] + })]); + + expect(postA.get('categories').length).to.equal(1); + expect(postA.get('tags').length).to.equal(1); + expect(postB.get('categories').length).to.equal(1); + expect(postB.get('tags').length).to.equal(1); + expect(postC.get('categories').length).to.equal(1); + expect(postC.get('tags').length).to.equal(1); + + expect(postA.get('categories')[0].get('type')).to.equal('category'); + expect(postA.get('tags')[0].get('type')).to.equal('tag'); + expect(postB.get('categories')[0].get('type')).to.equal('category'); + expect(postB.get('tags')[0].get('type')).to.equal('tag'); + expect(postC.get('categories')[0].get('type')).to.equal('category'); + expect(postC.get('tags')[0].get('type')).to.equal('tag'); }); }); @@ -535,101 +501,80 @@ describe(Support.getTestDialectTeaser('associations'), () => { }); }); - it('should create, find and include associations with scope values', function() { - return Promise.join( + it('should create, find and include associations with scope values', async function() { + await Promise.all([ this.Post.sync({ force: true }), this.Image.sync({ force: true }), this.Question.sync({ force: true }), this.Tag.sync({ force: true }) - ).then(() => { - return this.ItemTag.sync({ force: true }); - }).then(() => { - return Promise.join( - this.Post.create(), - this.Image.create(), - this.Question.create(), - this.Tag.create({ name: 'tagA' }), - this.Tag.create({ name: 'tagB' }), - this.Tag.create({ name: 'tagC' }) - ); - }).then(([post, image, question, tagA, tagB, tagC]) => { - this.post = post; - this.image = image; - this.question = question; - return Promise.join( - post.setTags([tagA]).then(() => { - return Promise.join( - post.createTag({ name: 'postTag' }), - post.addTag(tagB) - ); - }), - image.setTags([tagB]).then(() => { - return Promise.join( - image.createTag({ name: 'imageTag' }), - image.addTag(tagC) - ); - }), - question.setTags([tagC]).then(() => { - return Promise.join( - question.createTag({ name: 'questionTag' }), - question.addTag(tagA) - ); - }) - ); - }).then(() => { - return Promise.join( - this.post.getTags(), - this.image.getTags(), - this.question.getTags() - ).then(([postTags, imageTags, questionTags]) => { - expect(postTags.length).to.equal(3); - expect(imageTags.length).to.equal(3); - expect(questionTags.length).to.equal(3); - - expect(postTags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); - - expect(imageTags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); - - expect(questionTags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); - }).then(() => { - return Promise.join( - this.Post.findOne({ - where: {}, - include: [this.Tag] - }), - this.Image.findOne({ - where: {}, - include: [this.Tag] - }), - this.Question.findOne({ - where: {}, - include: [this.Tag] - }) - ).then(([post, image, question]) => { - expect(post.tags.length).to.equal(3); - expect(image.tags.length).to.equal(3); - expect(question.tags.length).to.equal(3); - - expect(post.tags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); - - expect(image.tags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); - - expect(question.tags.map(tag => { - return tag.name; - }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); - }); - }); - }); + ]); + + await this.ItemTag.sync({ force: true }); + + const [post0, image0, question0, tagA, tagB, tagC] = await Promise.all([ + this.Post.create(), + this.Image.create(), + this.Question.create(), + this.Tag.create({ name: 'tagA' }), + this.Tag.create({ name: 'tagB' }), + this.Tag.create({ name: 'tagC' }) + ]); + + this.post = post0; + this.image = image0; + this.question = question0; + + await Promise.all([post0.setTags([tagA]).then(async () => { + return Promise.all([post0.createTag({ name: 'postTag' }), post0.addTag(tagB)]); + }), image0.setTags([tagB]).then(async () => { + return Promise.all([image0.createTag({ name: 'imageTag' }), image0.addTag(tagC)]); + }), question0.setTags([tagC]).then(async () => { + return Promise.all([question0.createTag({ name: 'questionTag' }), question0.addTag(tagA)]); + })]); + + const [postTags, imageTags, questionTags] = await Promise.all([this.post.getTags(), this.image.getTags(), this.question.getTags()]); + expect(postTags.length).to.equal(3); + expect(imageTags.length).to.equal(3); + expect(questionTags.length).to.equal(3); + + expect(postTags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); + + expect(imageTags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); + + expect(questionTags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); + + const [post, image, question] = await Promise.all([this.Post.findOne({ + where: {}, + include: [this.Tag] + }), this.Image.findOne({ + where: {}, + include: [this.Tag] + }), this.Question.findOne({ + where: {}, + include: [this.Tag] + })]); + + expect(post.tags.length).to.equal(3); + expect(image.tags.length).to.equal(3); + expect(question.tags.length).to.equal(3); + + expect(post.tags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['postTag', 'tagA', 'tagB']); + + expect(image.tags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['imageTag', 'tagB', 'tagC']); + + expect(question.tags.map(tag => { + return tag.name; + }).sort()).to.deep.equal(['questionTag', 'tagA', 'tagC']); }); }); }); diff --git a/test/integration/associations/self.test.js b/test/integration/associations/self.test.js index 7d1835b3cd2b..dc6a094039d3 100644 --- a/test/integration/associations/self.test.js +++ b/test/integration/associations/self.test.js @@ -1,13 +1,12 @@ 'use strict'; -const { expect } = require('chai'); -const Support = require('../support'); -const DataTypes = require('../../../lib/data-types'); -const Sequelize = require('../../../index'); -const Promise = Sequelize.Promise; +const chai = require('chai'), + expect = chai.expect, + Support = require('../support'), + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Self'), () => { - it('supports freezeTableName', function() { + it('supports freezeTableName', async function() { const Group = this.sequelize.define('Group', {}, { tableName: 'user_group', timestamps: false, @@ -16,35 +15,35 @@ describe(Support.getTestDialectTeaser('Self'), () => { }); Group.belongsTo(Group, { as: 'Parent', foreignKey: 'parent_id' }); - return Group.sync({ force: true }).then(() => { - return Group.findAll({ - include: [{ - model: Group, - as: 'Parent' - }] - }); + await Group.sync({ force: true }); + + await Group.findAll({ + include: [{ + model: Group, + as: 'Parent' + }] }); }); - it('can handle 1:m associations', function() { + it('can handle 1:m associations', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); Person.hasMany(Person, { as: 'Children', foreignKey: 'parent_id' }); expect(Person.rawAttributes.parent_id).to.be.ok; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Person.create({ name: 'Mary' }), - Person.create({ name: 'John' }), - Person.create({ name: 'Chris' }) - ]); - }).then(([mary, john, chris]) => { - return mary.setChildren([john, chris]); - }); + await this.sequelize.sync({ force: true }); + + const [mary, john, chris] = await Promise.all([ + Person.create({ name: 'Mary' }), + Person.create({ name: 'John' }), + Person.create({ name: 'Chris' }) + ]); + + await mary.setChildren([john, chris]); }); - it('can handle n:m associations', function() { + it('can handle n:m associations', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); Person.belongsToMany(Person, { as: 'Parents', through: 'Family', foreignKey: 'ChildId', otherKey: 'PersonId' }); @@ -59,24 +58,21 @@ describe(Support.getTestDialectTeaser('Self'), () => { expect(foreignIdentifiers).to.have.members(['PersonId', 'ChildId']); expect(rawAttributes).to.have.members(['createdAt', 'updatedAt', 'PersonId', 'ChildId']); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Person.create({ name: 'Mary' }), - Person.create({ name: 'John' }), - Person.create({ name: 'Chris' }) - ]).then(([mary, john, chris]) => { - return mary.setParents([john]).then(() => { - return chris.addParent(john); - }).then(() => { - return john.getChilds(); - }).then(children => { - expect(children.map(v => v.id)).to.have.members([mary.id, chris.id]); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [mary, john, chris] = await Promise.all([ + Person.create({ name: 'Mary' }), + Person.create({ name: 'John' }), + Person.create({ name: 'Chris' }) + ]); + + await mary.setParents([john]); + await chris.addParent(john); + const children = await john.getChilds(); + expect(children.map(v => v.id)).to.have.members([mary.id, chris.id]); }); - it('can handle n:m associations with pre-defined through table', function() { + it('can handle n:m associations with pre-defined through table', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING }); const Family = this.sequelize.define('Family', { preexisting_child: { @@ -102,47 +98,49 @@ describe(Support.getTestDialectTeaser('Self'), () => { expect(rawAttributes).to.have.members(['preexisting_parent', 'preexisting_child']); let count = 0; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Person.create({ name: 'Mary' }), - Person.create({ name: 'John' }), - Person.create({ name: 'Chris' }) - ]); - }).then(([mary, john, chris]) => { - this.mary = mary; - this.chris = chris; - this.john = john; - return mary.setParents([john], { - logging(sql) { - if (sql.match(/INSERT/)) { - count++; - expect(sql).to.have.string('preexisting_child'); - expect(sql).to.have.string('preexisting_parent'); - } - } - }); - }).then(() => { - return this.mary.addParent(this.chris, { - logging(sql) { - if (sql.match(/INSERT/)) { - count++; - expect(sql).to.have.string('preexisting_child'); - expect(sql).to.have.string('preexisting_parent'); - } + await this.sequelize.sync({ force: true }); + + const [mary, john, chris] = await Promise.all([ + Person.create({ name: 'Mary' }), + Person.create({ name: 'John' }), + Person.create({ name: 'Chris' }) + ]); + + this.mary = mary; + this.chris = chris; + this.john = john; + + await mary.setParents([john], { + logging(sql) { + if (sql.match(/INSERT/)) { + count++; + expect(sql).to.have.string('preexisting_child'); + expect(sql).to.have.string('preexisting_parent'); } - }); - }).then(() => { - return this.john.getChildren({ - logging(sql) { + } + }); + + await this.mary.addParent(this.chris, { + logging(sql) { + if (sql.match(/INSERT/)) { count++; - const whereClause = sql.split('FROM')[1]; // look only in the whereClause - expect(whereClause).to.have.string('preexisting_child'); - expect(whereClause).to.have.string('preexisting_parent'); + expect(sql).to.have.string('preexisting_child'); + expect(sql).to.have.string('preexisting_parent'); } - }); - }).then(children => { - expect(count).to.be.equal(3); - expect(children.map(v => v.id)).to.have.members([this.mary.id]); + } }); + + const children = await this.john.getChildren({ + logging(sql) { + count++; + const whereClause = sql.split('FROM')[1]; + // look only in the whereClause + expect(whereClause).to.have.string('preexisting_child'); + expect(whereClause).to.have.string('preexisting_parent'); + } + }); + + expect(count).to.be.equal(3); + expect(children.map(v => v.id)).to.have.members([this.mary.id]); }); }); diff --git a/test/integration/cls.test.js b/test/integration/cls.test.js new file mode 100644 index 000000000000..746d43996842 --- /dev/null +++ b/test/integration/cls.test.js @@ -0,0 +1,181 @@ +'use strict'; + +const chai = require('chai'), + expect = chai.expect, + Support = require('./support'), + Sequelize = Support.Sequelize, + cls = require('cls-hooked'), + current = Support.sequelize, + delay = require('delay'), + sinon = require('sinon'); + +if (current.dialect.supports.transactions) { + describe(Support.getTestDialectTeaser('CLS (Async hooks)'), () => { + before(() => { + current.constructor.useCLS(cls.createNamespace('sequelize')); + }); + + after(() => { + cls.destroyNamespace('sequelize'); + delete Sequelize._cls; + }); + + beforeEach(async function() { + this.sequelize = await Support.prepareTransactionTest(this.sequelize); + this.ns = cls.getNamespace('sequelize'); + this.User = this.sequelize.define('user', { + name: Sequelize.STRING + }); + await this.sequelize.sync({ force: true }); + }); + + describe('context', () => { + it('does not use continuation storage on manually managed transactions', async function() { + await Sequelize._clsRun(async () => { + const transaction = await this.sequelize.transaction(); + expect(this.ns.get('transaction')).not.to.be.ok; + await transaction.rollback(); + }); + }); + + it('supports several concurrent transactions', async function() { + let t1id, t2id; + await Promise.all([ + this.sequelize.transaction(async () => { + t1id = this.ns.get('transaction').id; + }), + this.sequelize.transaction(async () => { + t2id = this.ns.get('transaction').id; + }) + ]); + expect(t1id).to.be.ok; + expect(t2id).to.be.ok; + expect(t1id).not.to.equal(t2id); + }); + + it('supports nested promise chains', async function() { + await this.sequelize.transaction(async () => { + const tid = this.ns.get('transaction').id; + + await this.User.findAll(); + expect(this.ns.get('transaction').id).to.be.ok; + expect(this.ns.get('transaction').id).to.equal(tid); + }); + }); + + it('does not leak variables to the outer scope', async function() { + // This is a little tricky. We want to check the values in the outer scope, when the transaction has been successfully set up, but before it has been comitted. + // We can't just call another function from inside that transaction, since that would transfer the context to that function - exactly what we are trying to prevent; + + let transactionSetup = false, + transactionEnded = false; + + const clsTask = this.sequelize.transaction(async () => { + transactionSetup = true; + await delay(500); + expect(this.ns.get('transaction')).to.be.ok; + transactionEnded = true; + }); + + await new Promise(resolve => { + // Wait for the transaction to be setup + const interval = setInterval(() => { + if (transactionSetup) { + clearInterval(interval); + resolve(); + } + }, 200); + }); + expect(transactionEnded).not.to.be.ok; + + expect(this.ns.get('transaction')).not.to.be.ok; + + // Just to make sure it didn't change between our last check and the assertion + expect(transactionEnded).not.to.be.ok; + await clsTask; // ensure we don't leak the promise + }); + + it('does not leak variables to the following promise chain', async function() { + await this.sequelize.transaction(() => {}); + expect(this.ns.get('transaction')).not.to.be.ok; + }); + + it('does not leak outside findOrCreate', async function() { + await this.User.findOrCreate({ + where: { + name: 'Kafka' + }, + logging(sql) { + if (/default/.test(sql)) { + throw new Error('The transaction was not properly assigned'); + } + } + }); + + await this.User.findAll(); + }); + }); + + describe('sequelize.query integration', () => { + it('automagically uses the transaction in all calls', async function() { + await this.sequelize.transaction(async () => { + await this.User.create({ name: 'bob' }); + return Promise.all([ + expect(this.User.findAll({ transaction: null })).to.eventually.have.length(0), + expect(this.User.findAll({})).to.eventually.have.length(1) + ]); + }); + }); + + it('automagically uses the transaction in all calls with async/await', async function() { + await this.sequelize.transaction(async () => { + await this.User.create({ name: 'bob' }); + expect(await this.User.findAll({ transaction: null })).to.have.length(0); + expect(await this.User.findAll({})).to.have.length(1); + }); + }); + }); + + it('CLS namespace is stored in Sequelize._cls', function() { + expect(Sequelize._cls).to.equal(this.ns); + }); + + it('promises returned by sequelize.query are correctly patched', async function() { + await this.sequelize.transaction(async t => { + await this.sequelize.query('select 1', { type: Sequelize.QueryTypes.SELECT }); + return expect(this.ns.get('transaction')).to.equal(t); + } + ); + }); + + it('custom logging with benchmarking has correct CLS context', async function() { + const logger = sinon.spy(() => { + return this.ns.get('value'); + }); + const sequelize = Support.createSequelizeInstance({ + logging: logger, + benchmark: true + }); + + const result = this.ns.runPromise(async () => { + this.ns.set('value', 1); + await delay(500); + return sequelize.query('select 1;'); + }); + + await this.ns.runPromise(() => { + this.ns.set('value', 2); + return sequelize.query('select 2;'); + }); + + await result; + + expect(logger.calledTwice).to.be.true; + expect(logger.firstCall.args[0]).to.be.match(/Executed \((\d*|default)\): select 2/); + expect(logger.firstCall.returnValue).to.be.equal(2); + expect(logger.secondCall.args[0]).to.be.match(/Executed \((\d*|default)\): select 1/); + expect(logger.secondCall.returnValue).to.be.equal(1); + + }); + }); +} diff --git a/test/integration/configuration.test.js b/test/integration/configuration.test.js index d43289aaf6a7..96f8bafd66da 100644 --- a/test/integration/configuration.test.js +++ b/test/integration/configuration.test.js @@ -7,7 +7,8 @@ const chai = require('chai'), dialect = Support.getTestDialect(), Sequelize = Support.Sequelize, fs = require('fs'), - path = require('path'); + path = require('path'), + { promisify } = require('util'); let sqlite3; if (dialect === 'sqlite') { @@ -16,19 +17,37 @@ if (dialect === 'sqlite') { describe(Support.getTestDialectTeaser('Configuration'), () => { describe('Connections problems should fail with a nice message', () => { - it('when we don\'t have the correct server details', () => { - const seq = new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, { storage: '/path/to/no/where/land', logging: false, host: '0.0.0.1', port: config[dialect].port, dialect }); + it('when we don\'t have the correct server details', async () => { + const options = { + logging: false, + host: 'localhost', + port: 19999, // Wrong port + dialect + }; + + const constructorArgs = [ + config[dialect].database, + config[dialect].username, + config[dialect].password, + options + ]; + + let willBeRejectedWithArgs = [[Sequelize.HostNotReachableError, Sequelize.InvalidConnectionError]]; + if (dialect === 'sqlite') { + options.storage = '/path/to/no/where/land'; + options.dialectOptions = { mode: sqlite3.OPEN_READONLY }; // SQLite doesn't have a breakdown of error codes, so we are unable to discern between the different types of errors. - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file'); + willBeRejectedWithArgs = [Sequelize.ConnectionError, 'SQLITE_CANTOPEN: unable to open database file']; } - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith([Sequelize.HostNotReachableError, Sequelize.InvalidConnectionError]); + + const seq = new Sequelize(...constructorArgs); + await expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(...willBeRejectedWithArgs); }); - it('when we don\'t have the correct login information', () => { + it('when we don\'t have the correct login information', async () => { if (dialect === 'mssql') { - // NOTE: Travis seems to be having trouble with this test against the - // AWS instance. Works perfectly fine on a local setup. + // TODO: GitHub Actions seems to be having trouble with this test. Works perfectly fine on a local setup. expect(true).to.be.true; return; } @@ -36,9 +55,10 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { const seq = new Sequelize(config[dialect].database, config[dialect].username, 'fakepass123', { logging: false, host: config[dialect].host, port: 1, dialect }); if (dialect === 'sqlite') { // SQLite doesn't require authentication and `select 1 as hello` is a valid query, so this should be fulfilled not rejected for it. - return expect(seq.query('select 1 as hello')).to.eventually.be.fulfilled; + await expect(seq.query('select 1 as hello')).to.eventually.be.fulfilled; + } else { + await expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionRefusedError, 'connect ECONNREFUSED'); } - return expect(seq.query('select 1 as hello')).to.eventually.be.rejectedWith(Sequelize.ConnectionRefusedError, 'connect ECONNREFUSED'); }); it('when we don\'t have a valid dialect.', () => { @@ -50,74 +70,71 @@ describe(Support.getTestDialectTeaser('Configuration'), () => { describe('Instantiation with arguments', () => { if (dialect === 'sqlite') { - it('should respect READONLY / READWRITE connection modes', () => { + it('should respect READONLY / READWRITE connection modes', async () => { const p = path.join(__dirname, '../tmp', 'foo.sqlite'); const createTableFoo = 'CREATE TABLE foo (faz TEXT);'; const createTableBar = 'CREATE TABLE bar (baz TEXT);'; - const testAccess = Sequelize.Promise.method(() => { - return Sequelize.Promise.promisify(fs.access)(p, fs.R_OK | fs.W_OK); - }); + const testAccess = () => { + return promisify(fs.access)(p, fs.R_OK | fs.W_OK); + }; - return Sequelize.Promise.promisify(fs.unlink)(p) - .catch(err => { + try { + try { + await promisify(fs.unlink)(p); + } catch (err) { expect(err.code).to.equal('ENOENT'); - }) - .then(() => { - const sequelizeReadOnly = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READONLY - } - }); - const sequelizeReadWrite = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READWRITE - } - }); - - expect(sequelizeReadOnly.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READONLY); - expect(sequelizeReadWrite.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READWRITE); - - return Sequelize.Promise.join( - sequelizeReadOnly.query(createTableFoo) - .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file'), - sequelizeReadWrite.query(createTableFoo) - .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file') - ); - }) - .then(() => { + } + + const sequelizeReadOnly0 = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READONLY + } + }); + const sequelizeReadWrite0 = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READWRITE + } + }); + + expect(sequelizeReadOnly0.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READONLY); + expect(sequelizeReadWrite0.config.dialectOptions.mode).to.equal(sqlite3.OPEN_READWRITE); + + await Promise.all([ + sequelizeReadOnly0.query(createTableFoo) + .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file'), + sequelizeReadWrite0.query(createTableFoo) + .should.be.rejectedWith(Error, 'SQLITE_CANTOPEN: unable to open database file') + ]); + // By default, sqlite creates a connection that's READWRITE | CREATE - const sequelize = new Sequelize('sqlite://foo', { - storage: p - }); - return sequelize.query(createTableFoo); - }) - .then(testAccess) - .then(() => { - const sequelizeReadOnly = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READONLY - } - }); - const sequelizeReadWrite = new Sequelize('sqlite://foo', { - storage: p, - dialectOptions: { - mode: sqlite3.OPEN_READWRITE - } - }); - - return Sequelize.Promise.join( - sequelizeReadOnly.query(createTableBar) - .should.be.rejectedWith(Error, 'SQLITE_READONLY: attempt to write a readonly database'), - sequelizeReadWrite.query(createTableBar) - ); - }) - .finally(() => { - return Sequelize.Promise.promisify(fs.unlink)(p); + const sequelize = new Sequelize('sqlite://foo', { + storage: p }); + await testAccess(await sequelize.query(createTableFoo)); + const sequelizeReadOnly = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READONLY + } + }); + const sequelizeReadWrite = new Sequelize('sqlite://foo', { + storage: p, + dialectOptions: { + mode: sqlite3.OPEN_READWRITE + } + }); + + await Promise.all([ + sequelizeReadOnly.query(createTableBar) + .should.be.rejectedWith(Error, 'SQLITE_READONLY: attempt to write a readonly database'), + sequelizeReadWrite.query(createTableBar) + ]); + } finally { + await promisify(fs.unlink)(p); + } }); } }); diff --git a/test/integration/data-types.test.js b/test/integration/data-types.test.js index 127c49c41683..ddd8e3a5b52d 100644 --- a/test/integration/data-types.test.js +++ b/test/integration/data-types.test.js @@ -12,7 +12,6 @@ const chai = require('chai'), uuid = require('uuid'), DataTypes = require('../../lib/data-types'), dialect = Support.getTestDialect(), - BigInt = require('big-integer'), semver = require('semver'); describe(Support.getTestDialectTeaser('DataTypes'), () => { @@ -22,7 +21,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { this.sequelize.connectionManager.refreshTypeParser(DataTypes[dialect]); // Reload custom parsers }); - it('allows me to return values from a custom parse function', () => { + it('allows me to return values from a custom parse function', async () => { const parse = Sequelize.DATE.parse = sinon.spy(value => { return moment(value, 'YYYY-MM-DD HH:mm:ss'); }); @@ -42,23 +41,23 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { timestamps: false }); - return current.sync({ force: true }).then(() => { - return User.create({ - dateField: moment('2011 10 31', 'YYYY MM DD') - }); - }).then(() => { - return User.findAll().get(0); - }).then(user => { - expect(parse).to.have.been.called; - expect(stringify).to.have.been.called; + await current.sync({ force: true }); - expect(moment.isMoment(user.dateField)).to.be.ok; - - delete Sequelize.DATE.parse; + await User.create({ + dateField: moment('2011 10 31', 'YYYY MM DD') }); + + const obj = await User.findAll(); + const user = obj[0]; + expect(parse).to.have.been.called; + expect(stringify).to.have.been.called; + + expect(moment.isMoment(user.dateField)).to.be.ok; + + delete Sequelize.DATE.parse; }); - const testSuccess = function(Type, value, options) { + const testSuccess = async function(Type, value, options) { const parse = Type.constructor.parse = sinon.spy(value => { return value; }); @@ -79,29 +78,27 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { timestamps: false }); - return current.sync({ force: true }).then(() => { - - current.refreshTypes(); + await current.sync({ force: true }); - return User.create({ - field: value - }); - }).then(() => { - return User.findAll().get(0); - }).then(() => { - expect(parse).to.have.been.called; - if (options && options.useBindParam) { - expect(bindParam).to.have.been.called; - } else { - expect(stringify).to.have.been.called; - } + current.refreshTypes(); - delete Type.constructor.parse; - delete Type.constructor.prototype.stringify; - if (options && options.useBindParam) { - delete Type.constructor.prototype.bindParam; - } + await User.create({ + field: value }); + + await User.findAll(); + expect(parse).to.have.been.called; + if (options && options.useBindParam) { + expect(bindParam).to.have.been.called; + } else { + expect(stringify).to.have.been.called; + } + + delete Type.constructor.parse; + delete Type.constructor.prototype.stringify; + if (options && options.useBindParam) { + delete Type.constructor.prototype.bindParam; + } }; const testFailure = function(Type) { @@ -115,229 +112,231 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { }; if (current.dialect.supports.JSON) { - it('calls parse and stringify for JSON', () => { + it('calls parse and stringify for JSON', async () => { const Type = new Sequelize.JSON(); - return testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); + await testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); }); } if (current.dialect.supports.JSONB) { - it('calls parse and stringify for JSONB', () => { + it('calls parse and stringify for JSONB', async () => { const Type = new Sequelize.JSONB(); - return testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); + await testSuccess(Type, { test: 42, nested: { foo: 'bar' } }); }); } if (current.dialect.supports.HSTORE) { - it('calls parse and bindParam for HSTORE', () => { + it('calls parse and bindParam for HSTORE', async () => { const Type = new Sequelize.HSTORE(); - return testSuccess(Type, { test: 42, nested: false }, { useBindParam: true }); + await testSuccess(Type, { test: 42, nested: false }, { useBindParam: true }); }); } if (current.dialect.supports.RANGE) { - it('calls parse and bindParam for RANGE', () => { + it('calls parse and bindParam for RANGE', async () => { const Type = new Sequelize.RANGE(new Sequelize.INTEGER()); - return testSuccess(Type, [1, 2], { useBindParam: true }); + await testSuccess(Type, [1, 2], { useBindParam: true }); }); } - it('calls parse and stringify for DATE', () => { + it('calls parse and stringify for DATE', async () => { const Type = new Sequelize.DATE(); - return testSuccess(Type, new Date()); + await testSuccess(Type, new Date()); }); - it('calls parse and stringify for DATEONLY', () => { + it('calls parse and stringify for DATEONLY', async () => { const Type = new Sequelize.DATEONLY(); - return testSuccess(Type, moment(new Date()).format('YYYY-MM-DD')); + await testSuccess(Type, moment(new Date()).format('YYYY-MM-DD')); }); - it('calls parse and stringify for TIME', () => { + it('calls parse and stringify for TIME', async () => { const Type = new Sequelize.TIME(); - return testSuccess(Type, moment(new Date()).format('HH:mm:ss')); + await testSuccess(Type, moment(new Date()).format('HH:mm:ss')); }); - it('calls parse and stringify for BLOB', () => { + it('calls parse and stringify for BLOB', async () => { const Type = new Sequelize.BLOB(); - return testSuccess(Type, 'foobar', { useBindParam: true }); + await testSuccess(Type, 'foobar', { useBindParam: true }); }); - it('calls parse and stringify for CHAR', () => { + it('calls parse and stringify for CHAR', async () => { const Type = new Sequelize.CHAR(); - return testSuccess(Type, 'foobar'); + await testSuccess(Type, 'foobar'); }); - it('calls parse and stringify/bindParam for STRING', () => { + it('calls parse and stringify/bindParam for STRING', async () => { const Type = new Sequelize.STRING(); // mssql has a _bindParam function that checks if STRING was created with // the boolean param (if so it outputs a Buffer bind param). This override // isn't needed for other dialects if (dialect === 'mssql') { - return testSuccess(Type, 'foobar', { useBindParam: true }); + await testSuccess(Type, 'foobar', { useBindParam: true }); + } else { + await testSuccess(Type, 'foobar'); } - return testSuccess(Type, 'foobar'); }); - it('calls parse and stringify for TEXT', () => { + it('calls parse and stringify for TEXT', async () => { const Type = new Sequelize.TEXT(); if (dialect === 'mssql') { // Text uses nvarchar, same type as string testFailure(Type); } else { - return testSuccess(Type, 'foobar'); + await testSuccess(Type, 'foobar'); } }); - it('calls parse and stringify for BOOLEAN', () => { + it('calls parse and stringify for BOOLEAN', async () => { const Type = new Sequelize.BOOLEAN(); - return testSuccess(Type, true); + await testSuccess(Type, true); }); - it('calls parse and stringify for INTEGER', () => { + it('calls parse and stringify for INTEGER', async () => { const Type = new Sequelize.INTEGER(); - return testSuccess(Type, 1); + await testSuccess(Type, 1); }); - it('calls parse and stringify for DECIMAL', () => { + it('calls parse and stringify for DECIMAL', async () => { const Type = new Sequelize.DECIMAL(); - return testSuccess(Type, 1.5); + await testSuccess(Type, 1.5); }); - it('calls parse and stringify for BIGINT', () => { + it('calls parse and stringify for BIGINT', async () => { const Type = new Sequelize.BIGINT(); if (dialect === 'mssql') { // Same type as integer testFailure(Type); } else { - return testSuccess(Type, 1); + await testSuccess(Type, 1); } }); - it('should handle JS BigInt type', function() { + it('should handle JS BigInt type', async function() { const User = this.sequelize.define('user', { age: Sequelize.BIGINT }); - const age = BigInt(Number.MAX_SAFE_INTEGER).add(Number.MAX_SAFE_INTEGER); + const age = BigInt(Number.MAX_SAFE_INTEGER) * 2n; - return User.sync({ force: true }).then(() => { - return User.create({ age }); - }).then(user => { - expect(BigInt(user.age).toString()).to.equal(age.toString()); - return User.findAll({ - where: { age } - }); - }).then(users => { - expect(users).to.have.lengthOf(1); - expect(BigInt(users[0].age).toString()).to.equal(age.toString()); + await User.sync({ force: true }); + const user = await User.create({ age }); + expect(BigInt(user.age).toString()).to.equal(age.toString()); + + const users = await User.findAll({ + where: { age } }); + + expect(users).to.have.lengthOf(1); + expect(BigInt(users[0].age).toString()).to.equal(age.toString()); }); if (dialect === 'mysql') { - it('should handle TINYINT booleans', function() { + it('should handle TINYINT booleans', async function() { const User = this.sequelize.define('user', { id: { type: Sequelize.TINYINT, primaryKey: true }, isRegistered: Sequelize.TINYINT }); - return User.sync({ force: true }).then(() => { - return User.create({ id: 1, isRegistered: true }); - }).then(registeredUser => { - expect(registeredUser.isRegistered).to.equal(true); - return User.findOne({ - where: { - id: 1, - isRegistered: true - } - }); - }).then(registeredUser => { - expect(registeredUser).to.be.ok; - expect(registeredUser.isRegistered).to.equal(1); - - return User.create({ id: 2, isRegistered: false }); - }).then(unregisteredUser => { - expect(unregisteredUser.isRegistered).to.equal(false); - return User.findOne({ - where: { - id: 2, - isRegistered: false - } - }); - }).then(unregisteredUser => { - expect(unregisteredUser).to.be.ok; - expect(unregisteredUser.isRegistered).to.equal(0); + await User.sync({ force: true }); + const registeredUser0 = await User.create({ id: 1, isRegistered: true }); + expect(registeredUser0.isRegistered).to.equal(true); + + const registeredUser = await User.findOne({ + where: { + id: 1, + isRegistered: true + } + }); + + expect(registeredUser).to.be.ok; + expect(registeredUser.isRegistered).to.equal(1); + + const unregisteredUser0 = await User.create({ id: 2, isRegistered: false }); + expect(unregisteredUser0.isRegistered).to.equal(false); + + const unregisteredUser = await User.findOne({ + where: { + id: 2, + isRegistered: false + } }); + + expect(unregisteredUser).to.be.ok; + expect(unregisteredUser.isRegistered).to.equal(0); }); } - it('calls parse and bindParam for DOUBLE', () => { + it('calls parse and bindParam for DOUBLE', async () => { const Type = new Sequelize.DOUBLE(); - return testSuccess(Type, 1.5, { useBindParam: true }); + await testSuccess(Type, 1.5, { useBindParam: true }); }); - it('calls parse and bindParam for FLOAT', () => { + it('calls parse and bindParam for FLOAT', async () => { const Type = new Sequelize.FLOAT(); if (dialect === 'postgres') { // Postgres doesn't have float, maps to either decimal or double testFailure(Type); } else { - return testSuccess(Type, 1.5, { useBindParam: true }); + await testSuccess(Type, 1.5, { useBindParam: true }); } }); - it('calls parse and bindParam for REAL', () => { + it('calls parse and bindParam for REAL', async () => { const Type = new Sequelize.REAL(); - return testSuccess(Type, 1.5, { useBindParam: true }); + await testSuccess(Type, 1.5, { useBindParam: true }); }); - it('calls parse and stringify for UUID', () => { + it('calls parse and stringify for UUID', async () => { const Type = new Sequelize.UUID(); // there is no dialect.supports.UUID yet if (['postgres', 'sqlite'].includes(dialect)) { - return testSuccess(Type, uuid.v4()); + await testSuccess(Type, uuid.v4()); + } else { + // No native uuid type + testFailure(Type); } - // No native uuid type - testFailure(Type); }); - it('calls parse and stringify for CIDR', () => { + it('calls parse and stringify for CIDR', async () => { const Type = new Sequelize.CIDR(); if (['postgres'].includes(dialect)) { - return testSuccess(Type, '10.1.2.3/32'); + await testSuccess(Type, '10.1.2.3/32'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for INET', () => { + it('calls parse and stringify for INET', async () => { const Type = new Sequelize.INET(); if (['postgres'].includes(dialect)) { - return testSuccess(Type, '127.0.0.1'); + await testSuccess(Type, '127.0.0.1'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for CITEXT', () => { + it('calls parse and stringify for CITEXT', async () => { const Type = new Sequelize.CITEXT(); if (dialect === 'sqlite') { @@ -346,38 +345,53 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { } if (dialect === 'postgres') { - return testSuccess(Type, 'foobar'); + await testSuccess(Type, 'foobar'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for MACADDR', () => { + it('calls parse and stringify for MACADDR', async () => { const Type = new Sequelize.MACADDR(); if (['postgres'].includes(dialect)) { - return testSuccess(Type, '01:23:45:67:89:ab'); + await testSuccess(Type, '01:23:45:67:89:ab'); + } else { + testFailure(Type); } - testFailure(Type); }); - it('calls parse and stringify for ENUM', () => { + if (current.dialect.supports.TSVECTOR) { + it('calls parse and stringify for TSVECTOR', async () => { + const Type = new Sequelize.TSVECTOR(); + + if (['postgres'].includes(dialect)) { + await testSuccess(Type, 'swagger'); + } else { + testFailure(Type); + } + }); + } + + it('calls parse and stringify for ENUM', async () => { const Type = new Sequelize.ENUM('hat', 'cat'); if (['postgres'].includes(dialect)) { - return testSuccess(Type, 'hat'); + await testSuccess(Type, 'hat'); + } else { + testFailure(Type); } - testFailure(Type); }); if (current.dialect.supports.GEOMETRY) { - it('calls parse and bindParam for GEOMETRY', () => { + it('calls parse and bindParam for GEOMETRY', async () => { const Type = new Sequelize.GEOMETRY(); - return testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }, { useBindParam: true }); + await testSuccess(Type, { type: 'Point', coordinates: [125.6, 10.1] }, { useBindParam: true }); }); - it('should parse an empty GEOMETRY field', () => { + it('should parse an empty GEOMETRY field', async () => { const Type = new Sequelize.GEOMETRY(); // MySQL 5.7 or above doesn't support POINT EMPTY @@ -385,7 +399,7 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { return; } - return new Sequelize.Promise((resolve, reject) => { + const runTests = await new Promise((resolve, reject) => { if (/^postgres/.test(dialect)) { current.query('SELECT PostGIS_Lib_Version();') .then(result => { @@ -398,37 +412,36 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { } else { resolve(true); } - }).then(runTests => { - if (current.dialect.supports.GEOMETRY && runTests) { - current.refreshTypes(); - - const User = current.define('user', { field: Type }, { timestamps: false }); - const point = { type: 'Point', coordinates: [] }; - - return current.sync({ force: true }).then(() => { - return User.create({ - //insert a empty GEOMETRY type - field: point - }); - }).then(() => { - //This case throw unhandled exception - return User.findAll(); - }).then(users =>{ - if (dialect === 'mysql' || dialect === 'mariadb') { - // MySQL will return NULL, because they lack EMPTY geometry data support. - expect(users[0].field).to.be.eql(null); - } else if (dialect === 'postgres' || dialect === 'postgres-native') { - //Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996 - expect(users[0].field).to.be.deep.eql({ type: 'Point', coordinates: [0, 0] }); - } else { - expect(users[0].field).to.be.deep.eql(point); - } - }); - } }); + + if (current.dialect.supports.GEOMETRY && runTests) { + current.refreshTypes(); + + const User = current.define('user', { field: Type }, { timestamps: false }); + const point = { type: 'Point', coordinates: [] }; + + await current.sync({ force: true }); + + await User.create({ + //insert a empty GEOMETRY type + field: point + }); + + //This case throw unhandled exception + const users = await User.findAll(); + if (dialect === 'mysql' || dialect === 'mariadb') { + // MySQL will return NULL, because they lack EMPTY geometry data support. + expect(users[0].field).to.be.eql(null); + } else if (dialect === 'postgres' || dialect === 'postgres-native') { + //Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996 + expect(users[0].field).to.be.deep.eql({ type: 'Point', coordinates: [0, 0] }); + } else { + expect(users[0].field).to.be.deep.eql(point); + } + } }); - it('should parse null GEOMETRY field', () => { + it('should parse null GEOMETRY field', async () => { const Type = new Sequelize.GEOMETRY(); current.refreshTypes(); @@ -436,48 +449,46 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const User = current.define('user', { field: Type }, { timestamps: false }); const point = null; - return current.sync({ force: true }).then(() => { - return User.create({ - // insert a null GEOMETRY type - field: point - }); - }).then(() => { - //This case throw unhandled exception - return User.findAll(); - }).then(users =>{ - expect(users[0].field).to.be.eql(null); + await current.sync({ force: true }); + + await User.create({ + // insert a null GEOMETRY type + field: point }); + + //This case throw unhandled exception + const users = await User.findAll(); + expect(users[0].field).to.be.eql(null); }); } if (dialect === 'postgres' || dialect === 'sqlite') { // postgres actively supports IEEE floating point literals, and sqlite doesn't care what we throw at it - it('should store and parse IEEE floating point literals (NaN and Infinity)', function() { + it('should store and parse IEEE floating point literals (NaN and Infinity)', async function() { const Model = this.sequelize.define('model', { float: Sequelize.FLOAT, double: Sequelize.DOUBLE, real: Sequelize.REAL }); - return Model.sync({ force: true }).then(() => { - return Model.create({ - id: 1, - float: NaN, - double: Infinity, - real: -Infinity - }); - }).then(() => { - return Model.findOne({ where: { id: 1 } }); - }).then(user => { - expect(user.get('float')).to.be.NaN; - expect(user.get('double')).to.eq(Infinity); - expect(user.get('real')).to.eq(-Infinity); + await Model.sync({ force: true }); + + await Model.create({ + id: 1, + float: NaN, + double: Infinity, + real: -Infinity }); + + const user = await Model.findOne({ where: { id: 1 } }); + expect(user.get('float')).to.be.NaN; + expect(user.get('double')).to.eq(Infinity); + expect(user.get('real')).to.eq(-Infinity); }); } if (dialect === 'postgres' || dialect === 'mysql') { - it('should parse DECIMAL as string', function() { + it('should parse DECIMAL as string', async function() { const Model = this.sequelize.define('model', { decimal: Sequelize.DECIMAL, decimalPre: Sequelize.DECIMAL(10, 4), @@ -495,31 +506,28 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { decimalWithFloatParser: 0.12345678 }; - return Model.sync({ force: true }).then(() => { - return Model.create(sampleData); - }).then(() => { - return Model.findByPk(1); - }).then(user => { - /** - * MYSQL default precision is 10 and scale is 0 - * Thus test case below will return number without any fraction values - */ - if (dialect === 'mysql') { - expect(user.get('decimal')).to.be.eql('12345678'); - } else { - expect(user.get('decimal')).to.be.eql('12345678.12345678'); - } + await Model.sync({ force: true }); + await Model.create(sampleData); + const user = await Model.findByPk(1); + /** + * MYSQL default precision is 10 and scale is 0 + * Thus test case below will return number without any fraction values + */ + if (dialect === 'mysql') { + expect(user.get('decimal')).to.be.eql('12345678'); + } else { + expect(user.get('decimal')).to.be.eql('12345678.12345678'); + } - expect(user.get('decimalPre')).to.be.eql('123456.1234'); - expect(user.get('decimalWithParser')).to.be.eql('12345678123456781.123456781234567'); - expect(user.get('decimalWithIntParser')).to.be.eql('1.2340'); - expect(user.get('decimalWithFloatParser')).to.be.eql('0.12345678'); - }); + expect(user.get('decimalPre')).to.be.eql('123456.1234'); + expect(user.get('decimalWithParser')).to.be.eql('12345678123456781.123456781234567'); + expect(user.get('decimalWithIntParser')).to.be.eql('1.2340'); + expect(user.get('decimalWithFloatParser')).to.be.eql('0.12345678'); }); } if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { - it('should parse BIGINT as string', function() { + it('should parse BIGINT as string', async function() { const Model = this.sequelize.define('model', { jewelPurity: Sequelize.BIGINT }); @@ -529,19 +537,16 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { jewelPurity: '9223372036854775807' }; - return Model.sync({ force: true }).then(() => { - return Model.create(sampleData); - }).then(() => { - return Model.findByPk(1); - }).then(user => { - expect(user.get('jewelPurity')).to.be.eql(sampleData.jewelPurity); - expect(user.get('jewelPurity')).to.be.string; - }); + await Model.sync({ force: true }); + await Model.create(sampleData); + const user = await Model.findByPk(1); + expect(user.get('jewelPurity')).to.be.eql(sampleData.jewelPurity); + expect(user.get('jewelPurity')).to.be.string; }); } if (dialect === 'postgres') { - it('should return Int4 range properly #5747', function() { + it('should return Int4 range properly #5747', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.INTEGER), @@ -550,19 +555,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { } }); - return Model.sync({ force: true }) - .then(() => Model.create({ interval: [1, 4] }) ) - .then(() => Model.findAll() ) - .then(([m]) => { - expect(m.interval[0].value).to.be.eql(1); - expect(m.interval[1].value).to.be.eql(4); - }); + await Model.sync({ force: true }); + await Model.create({ interval: [1, 4] }); + const [m] = await Model.findAll(); + expect(m.interval[0].value).to.be.eql(1); + expect(m.interval[1].value).to.be.eql(4); }); } if (current.dialect.supports.RANGE) { - it('should allow date ranges to be generated with default bounds inclusion #8176', function() { + it('should allow date ranges to be generated with default bounds inclusion #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -574,19 +577,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDate2 = new Date(testDate1.getTime() + 10000); const testDateRange = [testDate1, testDate2]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne()) - .then(m => { - expect(m).to.exist; - expect(m.interval[0].value).to.be.eql(testDate1); - expect(m.interval[1].value).to.be.eql(testDate2); - expect(m.interval[0].inclusive).to.be.eql(true); - expect(m.interval[1].inclusive).to.be.eql(false); - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + const m = await Model.findOne(); + expect(m).to.exist; + expect(m.interval[0].value).to.be.eql(testDate1); + expect(m.interval[1].value).to.be.eql(testDate2); + expect(m.interval[0].inclusive).to.be.eql(true); + expect(m.interval[1].inclusive).to.be.eql(false); }); - it('should allow date ranges to be generated using a single range expression to define bounds inclusion #8176', function() { + it('should allow date ranges to be generated using a single range expression to define bounds inclusion #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -598,19 +599,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDate2 = new Date(testDate1.getTime() + 10000); const testDateRange = [{ value: testDate1, inclusive: false }, { value: testDate2, inclusive: true }]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne()) - .then(m => { - expect(m).to.exist; - expect(m.interval[0].value).to.be.eql(testDate1); - expect(m.interval[1].value).to.be.eql(testDate2); - expect(m.interval[0].inclusive).to.be.eql(false); - expect(m.interval[1].inclusive).to.be.eql(true); - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + const m = await Model.findOne(); + expect(m).to.exist; + expect(m.interval[0].value).to.be.eql(testDate1); + expect(m.interval[1].value).to.be.eql(testDate2); + expect(m.interval[0].inclusive).to.be.eql(false); + expect(m.interval[1].inclusive).to.be.eql(true); }); - it('should allow date ranges to be generated using a composite range expression #8176', function() { + it('should allow date ranges to be generated using a composite range expression #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -622,19 +621,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDate2 = new Date(testDate1.getTime() + 10000); const testDateRange = [testDate1, { value: testDate2, inclusive: true }]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne()) - .then(m => { - expect(m).to.exist; - expect(m.interval[0].value).to.be.eql(testDate1); - expect(m.interval[1].value).to.be.eql(testDate2); - expect(m.interval[0].inclusive).to.be.eql(true); - expect(m.interval[1].inclusive).to.be.eql(true); - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + const m = await Model.findOne(); + expect(m).to.exist; + expect(m.interval[0].value).to.be.eql(testDate1); + expect(m.interval[1].value).to.be.eql(testDate2); + expect(m.interval[0].inclusive).to.be.eql(true); + expect(m.interval[1].inclusive).to.be.eql(true); }); - it('should correctly return ranges when using predicates that define bounds inclusion #8176', function() { + it('should correctly return ranges when using predicates that define bounds inclusion #8176', async function() { const Model = this.sequelize.define('M', { interval: { type: Sequelize.RANGE(Sequelize.DATE), @@ -647,101 +644,90 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { const testDateRange = [testDate1, testDate2]; const dateRangePredicate = [{ value: testDate1, inclusive: true }, { value: testDate1, inclusive: true }]; - return Model.sync({ force: true }) - .then(() => Model.create({ interval: testDateRange })) - .then(() => Model.findOne({ - where: { - interval: { [Op.overlap]: dateRangePredicate } - } - })) - .then(m => { - expect(m).to.exist; - }); + await Model.sync({ force: true }); + await Model.create({ interval: testDateRange }); + + const m = await Model.findOne({ + where: { + interval: { [Op.overlap]: dateRangePredicate } + } + }); + + expect(m).to.exist; }); } - it('should allow spaces in ENUM', function() { + it('should allow spaces in ENUM', async function() { const Model = this.sequelize.define('user', { name: Sequelize.STRING, type: Sequelize.ENUM(['action', 'mecha', 'canon', 'class s']) }); - return Model.sync({ force: true }).then(() => { - return Model.create({ name: 'sakura', type: 'class s' }); - }).then(record => { - expect(record.type).to.be.eql('class s'); - }); + await Model.sync({ force: true }); + const record = await Model.create({ name: 'sakura', type: 'class s' }); + expect(record.type).to.be.eql('class s'); }); - it('should return YYYY-MM-DD format string for DATEONLY', function() { + it('should return YYYY-MM-DD format string for DATEONLY', async function() { const Model = this.sequelize.define('user', { stamp: Sequelize.DATEONLY }); const testDate = moment().format('YYYY-MM-DD'); const newDate = new Date(); - return Model.sync({ force: true }) - .then(() => Model.create({ stamp: testDate })) - .then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + await Model.sync({ force: true }); + const record4 = await Model.create({ stamp: testDate }); + expect(typeof record4.stamp).to.be.eql('string'); + expect(record4.stamp).to.be.eql(testDate); - return Model.findByPk(record.id); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + const record3 = await Model.findByPk(record4.id); + expect(typeof record3.stamp).to.be.eql('string'); + expect(record3.stamp).to.be.eql(testDate); - return record.update({ - stamp: testDate - }); - }).then(record => { - return record.reload(); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); - - return record.update({ - stamp: newDate - }); - }).then(record => { - return record.reload(); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - const recordDate = new Date(record.stamp); - expect(recordDate.getUTCFullYear()).to.equal(newDate.getUTCFullYear()); - expect(recordDate.getUTCDate()).to.equal(newDate.getUTCDate()); - expect(recordDate.getUTCMonth()).to.equal(newDate.getUTCMonth()); - }); + const record2 = await record3.update({ + stamp: testDate + }); + + const record1 = await record2.reload(); + expect(typeof record1.stamp).to.be.eql('string'); + expect(record1.stamp).to.be.eql(testDate); + + const record0 = await record1.update({ + stamp: newDate + }); + + const record = await record0.reload(); + expect(typeof record.stamp).to.be.eql('string'); + const recordDate = new Date(record.stamp); + expect(recordDate.getUTCFullYear()).to.equal(newDate.getUTCFullYear()); + expect(recordDate.getUTCDate()).to.equal(newDate.getUTCDate()); + expect(recordDate.getUTCMonth()).to.equal(newDate.getUTCMonth()); }); - it('should return set DATEONLY field to NULL correctly', function() { + it('should return set DATEONLY field to NULL correctly', async function() { const Model = this.sequelize.define('user', { stamp: Sequelize.DATEONLY }); const testDate = moment().format('YYYY-MM-DD'); - return Model.sync({ force: true }) - .then(() => Model.create({ stamp: testDate })) - .then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + await Model.sync({ force: true }); + const record2 = await Model.create({ stamp: testDate }); + expect(typeof record2.stamp).to.be.eql('string'); + expect(record2.stamp).to.be.eql(testDate); - return Model.findByPk(record.id); - }).then(record => { - expect(typeof record.stamp).to.be.eql('string'); - expect(record.stamp).to.be.eql(testDate); + const record1 = await Model.findByPk(record2.id); + expect(typeof record1.stamp).to.be.eql('string'); + expect(record1.stamp).to.be.eql(testDate); - return record.update({ - stamp: null - }); - }).then(record => { - return record.reload(); - }).then(record => { - expect(record.stamp).to.be.eql(null); - }); + const record0 = await record1.update({ + stamp: null + }); + + const record = await record0.reload(); + expect(record.stamp).to.be.eql(null); }); - it('should be able to cast buffer as boolean', function() { + it('should be able to cast buffer as boolean', async function() { const ByteModel = this.sequelize.define('Model', { byteToBool: this.sequelize.Sequelize.BLOB }, { @@ -754,18 +740,17 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => { timestamps: false }); - return ByteModel.sync({ + await ByteModel.sync({ force: true - }).then(() => { - return ByteModel.create({ - byteToBool: Buffer.from([true]) - }); - }).then(byte => { - expect(byte.byteToBool).to.be.ok; + }); - return BoolModel.findByPk(byte.id); - }).then(bool => { - expect(bool.byteToBool).to.be.true; + const byte = await ByteModel.create({ + byteToBool: Buffer.from([true]) }); + + expect(byte.byteToBool).to.be.ok; + + const bool = await BoolModel.findByPk(byte.id); + expect(bool.byteToBool).to.be.true; }); }); diff --git a/test/integration/dialects/abstract/connection-manager.test.js b/test/integration/dialects/abstract/connection-manager.test.js index 4b0785a83c85..05214e9d8608 100644 --- a/test/integration/dialects/abstract/connection-manager.test.js +++ b/test/integration/dialects/abstract/connection-manager.test.js @@ -1,12 +1,13 @@ 'use strict'; -const chai = require('chai'); -const expect = chai.expect; -const Support = require('../../support'); -const sinon = require('sinon'); -const Config = require('../../../config/config'); -const ConnectionManager = require('../../../../lib/dialects/abstract/connection-manager'); -const Pool = require('sequelize-pool').Pool; +const chai = require('chai'), + expect = chai.expect, + deprecations = require('../../../../lib/utils/deprecations'), + Support = require('../../support'), + sinon = require('sinon'), + Config = require('../../../config/config'), + ConnectionManager = require('../../../../lib/dialects/abstract/connection-manager'), + Pool = require('sequelize-pool').Pool; const baseConf = Config[Support.getTestDialect()]; const poolEntry = { @@ -15,12 +16,11 @@ const poolEntry = { pool: {} }; -describe('Connection Manager', () => { +describe(Support.getTestDialectTeaser('Connection Manager'), () => { let sandbox; beforeEach(() => { sandbox = sinon.createSandbox(); - sandbox.usingPromise(require('bluebird')); }); afterEach(() => { @@ -32,7 +32,7 @@ describe('Connection Manager', () => { replication: null }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); connectionManager.initPools(); expect(connectionManager.pool).to.be.instanceOf(Pool); @@ -48,14 +48,14 @@ describe('Connection Manager', () => { } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); connectionManager.initPools(); expect(connectionManager.pool.read).to.be.instanceOf(Pool); expect(connectionManager.pool.write).to.be.instanceOf(Pool); }); - it('should round robin calls to the read pool', () => { + it('should round robin calls to the read pool', async () => { if (Support.getTestDialect() === 'sqlite') { return; } @@ -72,7 +72,7 @@ describe('Connection Manager', () => { } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); const res = { queryType: 'read' @@ -80,7 +80,7 @@ describe('Connection Manager', () => { const connectStub = sandbox.stub(connectionManager, '_connect').resolves(res); sandbox.stub(connectionManager, '_disconnect').resolves(res); - sandbox.stub(sequelize, 'databaseVersion').resolves(res); + sandbox.stub(sequelize, 'databaseVersion').resolves(sequelize.dialect.defaultVersion); connectionManager.initPools(); const queryOptions = { @@ -91,39 +91,64 @@ describe('Connection Manager', () => { const _getConnection = connectionManager.getConnection.bind(connectionManager, queryOptions); - return _getConnection() - .then(_getConnection) - .then(_getConnection) - .then(() => { - chai.expect(connectStub.callCount).to.equal(4); - - // First call is the get connection for DB versions - ignore - const calls = connectStub.getCalls(); - chai.expect(calls[1].args[0].host).to.eql('slave1'); - chai.expect(calls[2].args[0].host).to.eql('slave2'); - chai.expect(calls[3].args[0].host).to.eql('slave1'); - }); + await _getConnection(); + await _getConnection(); + await _getConnection(); + chai.expect(connectStub.callCount).to.equal(4); + + // First call is the get connection for DB versions - ignore + const calls = connectStub.getCalls(); + chai.expect(calls[1].args[0].host).to.eql('slave1'); + chai.expect(calls[2].args[0].host).to.eql('slave2'); + chai.expect(calls[3].args[0].host).to.eql('slave1'); }); - it('should allow forced reads from the write pool', () => { - const master = { ...poolEntry }; - master.host = 'the-boss'; + it('should trigger deprecation for non supported engine version', async () => { + const deprecationStub = sandbox.stub(deprecations, 'unsupportedEngine'); + const sequelize = Support.createSequelizeInstance(); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); + + sandbox.stub(sequelize, 'databaseVersion').resolves('0.0.1'); + + const res = { + queryType: 'read' + }; + + sandbox.stub(connectionManager, '_connect').resolves(res); + sandbox.stub(connectionManager, '_disconnect').resolves(res); + connectionManager.initPools(); + + const queryOptions = { + priority: 0, + type: 'SELECT', + useMaster: true + }; + + await connectionManager.getConnection(queryOptions); + chai.expect(deprecationStub).to.have.been.calledOnce; + }); + + + it('should allow forced reads from the write pool', async () => { + const main = { ...poolEntry }; + main.host = 'the-boss'; const options = { replication: { - write: master, + write: main, read: [{ ...poolEntry }] } }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); const res = { queryType: 'read' }; + const connectStub = sandbox.stub(connectionManager, '_connect').resolves(res); sandbox.stub(connectionManager, '_disconnect').resolves(res); - sandbox.stub(sequelize, 'databaseVersion').resolves(res); + sandbox.stub(sequelize, 'databaseVersion').resolves(sequelize.dialect.defaultVersion); connectionManager.initPools(); const queryOptions = { @@ -132,30 +157,26 @@ describe('Connection Manager', () => { useMaster: true }; - return connectionManager.getConnection(queryOptions) - .then(() => { - chai.expect(connectStub).to.have.been.calledTwice; // Once to get DB version, and once to actually get the connection. - const calls = connectStub.getCalls(); - chai.expect(calls[1].args[0].host).to.eql('the-boss'); - }); + await connectionManager.getConnection(queryOptions); + chai.expect(connectStub).to.have.been.calledTwice; // Once to get DB version, and once to actually get the connection. + const calls = connectStub.getCalls(); + chai.expect(calls[1].args[0].host).to.eql('the-boss'); }); - it('should clear the pool after draining it', () => { + it('should clear the pool after draining it', async () => { const options = { replication: null }; const sequelize = Support.createSequelizeInstance(options); - const connectionManager = new ConnectionManager(Support.getTestDialect(), sequelize); + const connectionManager = new ConnectionManager(sequelize.dialect, sequelize); connectionManager.initPools(); const poolDrainSpy = sandbox.spy(connectionManager.pool, 'drain'); const poolClearSpy = sandbox.spy(connectionManager.pool, 'destroyAllNow'); - return connectionManager.close().then(() => { - expect(poolDrainSpy.calledOnce).to.be.true; - expect(poolClearSpy.calledOnce).to.be.true; - }); + await connectionManager.close(); + expect(poolDrainSpy.calledOnce).to.be.true; + expect(poolClearSpy.calledOnce).to.be.true; }); - }); diff --git a/test/integration/dialects/mariadb/associations.test.js b/test/integration/dialects/mariadb/associations.test.js index 1466f6a75997..0256e4387f83 100644 --- a/test/integration/dialects/mariadb/associations.test.js +++ b/test/integration/dialects/mariadb/associations.test.js @@ -11,33 +11,30 @@ if (dialect !== 'mariadb') return; describe('[MariaDB Specific] Associations', () => { describe('many-to-many', () => { describe('where tables have the same prefix', () => { - it('should create a table wp_table1wp_table2s', function() { + it('should create a table wp_table1wp_table2s', async function() { const Table2 = this.sequelize.define('wp_table2', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('wp_table1', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'wp_table1swp_table2s' }); Table2.belongsToMany(Table1, { through: 'wp_table1swp_table2s' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }).then(() => { - expect(this.sequelize.modelManager.getModel( - 'wp_table1swp_table2s')).to.exist; - }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); + expect(this.sequelize.modelManager.getModel( + 'wp_table1swp_table2s')).to.exist; }); }); describe('when join table name is specified', () => { - beforeEach(function() { + beforeEach(async function() { const Table2 = this.sequelize.define('ms_table1', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('ms_table2', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'table1_to_table2' }); Table2.belongsToMany(Table1, { through: 'table1_to_table2' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); }); it('should not use only a specified name', function() { @@ -50,7 +47,7 @@ describe('[MariaDB Specific] Associations', () => { }); describe('HasMany', () => { - beforeEach(function() { + beforeEach(async function() { //prevent periods from occurring in the table name since they are used to delimit (table.column) this.User = this.sequelize.define(`User${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); this.Task = this.sequelize.define(`Task${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); @@ -68,10 +65,9 @@ describe('[MariaDB Specific] Associations', () => { tasks[i] = { name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }) - .then(() => this.User.bulkCreate(users)) - .then(() => this.Task.bulkCreate(tasks)); - + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); }); }); diff --git a/test/integration/dialects/mariadb/connector-manager.test.js b/test/integration/dialects/mariadb/connector-manager.test.js index 5357a597cbca..391f4573cb16 100644 --- a/test/integration/dialects/mariadb/connector-manager.test.js +++ b/test/integration/dialects/mariadb/connector-manager.test.js @@ -13,17 +13,15 @@ if (dialect !== 'mariadb') { describe('[MARIADB Specific] Connection Manager', () => { - it('has existing init SQL', () => { + it('has existing init SQL', async () => { const sequelize = Support.createSequelizeInstance( { dialectOptions: { initSql: 'SET @myUserVariable=\'myValue\'' } }); - return sequelize.query('SELECT @myUserVariable') - .then(res => { - expect(res[0]).to.deep.equal([{ '@myUserVariable': 'myValue' }]); - sequelize.close(); - }); + const res = await sequelize.query('SELECT @myUserVariable'); + expect(res[0]).to.deep.equal([{ '@myUserVariable': 'myValue' }]); + sequelize.close(); }); - it('has existing init SQL array', () => { + it('has existing init SQL array', async () => { const sequelize = Support.createSequelizeInstance( { dialectOptions: { @@ -31,12 +29,10 @@ describe('[MARIADB Specific] Connection Manager', () => { 'SET @myUserVariable2=\'myValue\''] } }); - return sequelize.query('SELECT @myUserVariable1, @myUserVariable2') - .then(res => { - expect(res[0]).to.deep.equal( - [{ '@myUserVariable1': 'myValue', '@myUserVariable2': 'myValue' }]); - sequelize.close(); - }); + const res = await sequelize.query('SELECT @myUserVariable1, @myUserVariable2'); + expect(res[0]).to.deep.equal( + [{ '@myUserVariable1': 'myValue', '@myUserVariable2': 'myValue' }]); + sequelize.close(); }); @@ -44,29 +40,29 @@ describe('[MARIADB Specific] Connection Manager', () => { describe('Errors', () => { const testHost = env.MARIADB_PORT_3306_TCP_ADDR || env.SEQ_MARIADB_HOST || env.SEQ_HOST || '127.0.0.1'; - it('Connection timeout', () => { + it('Connection timeout', async () => { const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535, dialectOptions: { connectTimeout: 500 } }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.SequelizeConnectionError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.SequelizeConnectionError); }); - it('ECONNREFUSED', () => { + it('ECONNREFUSED', async () => { const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535 }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); }); - it('ENOTFOUND', () => { + it('ENOTFOUND', async () => { const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); }); - it('EHOSTUNREACH', () => { + it('EHOSTUNREACH', async () => { const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); }); - it('ER_ACCESS_DENIED_ERROR | ELOGIN', () => { + it('ER_ACCESS_DENIED_ERROR | ELOGIN', async () => { const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); }); }); diff --git a/test/integration/dialects/mariadb/dao-factory.test.js b/test/integration/dialects/mariadb/dao-factory.test.js index ac07715c6adb..e2fd3f589630 100644 --- a/test/integration/dialects/mariadb/dao-factory.test.js +++ b/test/integration/dialects/mariadb/dao-factory.test.js @@ -4,19 +4,18 @@ const chai = require('chai'), expect = chai.expect, Support = require('../../support'), dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'), - config = require('../../../config/config'); + DataTypes = require('../../../../lib/data-types'); if (dialect !== 'mariadb') return; describe('[MariaDB Specific] DAOFactory', () => { describe('constructor', () => { it('handles extended attributes (unique)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, unique: true } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' @@ -24,11 +23,11 @@ describe('[MariaDB Specific] DAOFactory', () => { }); it('handles extended attributes (default)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, defaultValue: 'foo' } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) DEFAULT \'foo\'', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' @@ -36,11 +35,11 @@ describe('[MariaDB Specific] DAOFactory', () => { }); it('handles extended attributes (null)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, allowNull: false } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' @@ -48,29 +47,29 @@ describe('[MariaDB Specific] DAOFactory', () => { }); it('handles extended attributes (primaryKey)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, primaryKey: true } }, { timestamps: false }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal( { username: 'VARCHAR(255) PRIMARY KEY' }); }); it('adds timestamps', function() { - const User1 = this.sequelize.define(`User${config.rand()}`, {}); - const User2 = this.sequelize.define(`User${config.rand()}`, {}, + const User1 = this.sequelize.define(`User${Support.rand()}`, {}); + const User2 = this.sequelize.define(`User${Support.rand()}`, {}, { timestamps: true }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User1.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User2.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', @@ -79,10 +78,10 @@ describe('[MariaDB Specific] DAOFactory', () => { }); it('adds deletedAt if paranoid', function() { - const User = this.sequelize.define(`User${config.rand()}`, {}, + const User = this.sequelize.define(`User${Support.rand()}`, {}, { paranoid: true }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', @@ -92,10 +91,10 @@ describe('[MariaDB Specific] DAOFactory', () => { }); it('underscores timestamps if underscored', function() { - const User = this.sequelize.define(`User${config.rand()}`, {}, + const User = this.sequelize.define(`User${Support.rand()}`, {}, { paranoid: true, underscored: true }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', @@ -105,13 +104,13 @@ describe('[MariaDB Specific] DAOFactory', () => { }); it('omits text fields with defaultValues', function() { - const User = this.sequelize.define(`User${config.rand()}`, + const User = this.sequelize.define(`User${Support.rand()}`, { name: { type: DataTypes.TEXT, defaultValue: 'helloworld' } }); expect(User.rawAttributes.name.type.toString()).to.equal('TEXT'); }); it('omits blobs fields with defaultValues', function() { - const User = this.sequelize.define(`User${config.rand()}`, + const User = this.sequelize.define(`User${Support.rand()}`, { name: { type: DataTypes.STRING.BINARY, defaultValue: 'helloworld' } }); expect(User.rawAttributes.name.type.toString()).to.equal( 'VARCHAR(255) BINARY'); @@ -120,12 +119,12 @@ describe('[MariaDB Specific] DAOFactory', () => { describe('primaryKeys', () => { it('determines the correct primaryKeys', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { foo: { type: DataTypes.STRING, primaryKey: true }, bar: DataTypes.STRING }); expect( - this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL( + this.sequelize.getQueryInterface().queryGenerator.attributesToSQL( User.primaryKeys)).to.deep.equal( { 'foo': 'VARCHAR(255) PRIMARY KEY' }); }); diff --git a/test/integration/dialects/mariadb/dao.test.js b/test/integration/dialects/mariadb/dao.test.js index e07a1d493440..1bfc543aab2f 100644 --- a/test/integration/dialects/mariadb/dao.test.js +++ b/test/integration/dialects/mariadb/dao.test.js @@ -8,14 +8,14 @@ const chai = require('chai'), if (dialect !== 'mariadb') return; describe('[MariaDB Specific] DAO', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize.options.quoteIdentifiers = true; this.User = this.sequelize.define('User', { username: DataTypes.STRING, email: DataTypes.STRING, location: DataTypes.GEOMETRY() }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); afterEach(function() { @@ -24,112 +24,99 @@ describe('[MariaDB Specific] DAO', () => { describe('integers', () => { describe('integer', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.INTEGER }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: 2147483647 }).then(user => { - expect(user.aNumber).to.equal(2147483647); - return User.findOne({ where: { aNumber: 2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(2147483647); - }); - }); + const user = await User.create({ aNumber: 2147483647 }); + expect(user.aNumber).to.equal(2147483647); + const _user = await User.findOne({ where: { aNumber: 2147483647 } }); + expect(_user.aNumber).to.equal(2147483647); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: -2147483647 }).then(user => { - expect(user.aNumber).to.equal(-2147483647); - return User.findOne({ where: { aNumber: -2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(-2147483647); - }); - }); + const user = await User.create({ aNumber: -2147483647 }); + expect(user.aNumber).to.equal(-2147483647); + const _user = await User.findOne({ where: { aNumber: -2147483647 } }); + expect(_user.aNumber).to.equal(-2147483647); }); }); describe('bigint', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.BIGINT }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: '9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('9223372036854775807'); - return User.findOne({ where: { aNumber: '9223372036854775807' } }).then( - _user => { - return expect(_user.aNumber.toString()).to.equal( - '9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '9223372036854775807' }); + expect(user.aNumber).to.equal('9223372036854775807'); + const _user = await User.findOne({ where: { aNumber: '9223372036854775807' } }); + + await expect(_user.aNumber.toString()).to.equal('9223372036854775807'); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: '-9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('-9223372036854775807'); - return User.findOne( - { where: { aNumber: '-9223372036854775807' } }).then(_user => { - return expect(_user.aNumber.toString()).to.equal( - '-9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '-9223372036854775807' }); + expect(user.aNumber).to.equal('-9223372036854775807'); + + const _user = await User.findOne( + { where: { aNumber: '-9223372036854775807' } }); + + await expect(_user.aNumber.toString()).to.equal('-9223372036854775807'); }); }); }); - it('should save geometry correctly', function() { + it('should save geometry correctly', async function() { const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return this.User.create( - { username: 'user', email: 'foo@bar.com', location: point }).then( - newUser => { - expect(newUser.location).to.deep.eql(point); - }); + + const newUser = await this.User.create( + { username: 'user', email: 'foo@bar.com', location: point }); + + expect(newUser.location).to.deep.eql(point); }); - it('should update geometry correctly', function() { + it('should update geometry correctly', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }; const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] }; - return User.create( - { username: 'user', email: 'foo@bar.com', location: point1 }) - .then(oldUser => { - return User.update({ location: point2 }, - { where: { username: oldUser.username } }) - .then(() => { - return User.findOne({ where: { username: oldUser.username } }); - }) - .then(updatedUser => { - expect(updatedUser.location).to.deep.eql(point2); - }); - }); + + const oldUser = await User.create( + { username: 'user', email: 'foo@bar.com', location: point1 }); + + await User.update({ location: point2 }, + { where: { username: oldUser.username } }); + + const updatedUser = await User.findOne({ where: { username: oldUser.username } }); + expect(updatedUser.location).to.deep.eql(point2); }); - it('should read geometry correctly', function() { + it('should read geometry correctly', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create( - { username: 'user', email: 'foo@bar.com', location: point }).then( - user => { - return User.findOne({ where: { username: user.username } }); - }).then(user => { - expect(user.location).to.deep.eql(point); - }); + const user0 = await User.create( + { username: 'user', email: 'foo@bar.com', location: point }); + + const user = await User.findOne({ where: { username: user0.username } }); + expect(user.location).to.deep.eql(point); }); }); diff --git a/test/integration/dialects/mariadb/errors.test.js b/test/integration/dialects/mariadb/errors.test.js index 4971ea2635a7..33b2b62042da 100644 --- a/test/integration/dialects/mariadb/errors.test.js +++ b/test/integration/dialects/mariadb/errors.test.js @@ -9,12 +9,16 @@ const chai = require('chai'), if (dialect !== 'mariadb') return; describe('[MariaDB Specific] Errors', () => { - const validateError = (promise, errClass, errValues) => { - const wanted = Object.assign({}, errValues); + const validateError = async (promise, errClass, errValues) => { + const wanted = { ...errValues }; - return expect(promise).to.have.been.rejectedWith(errClass).then(() => - promise.catch(err => Object.keys(wanted).forEach( - k => expect(err[k]).to.eql(wanted[k])))); + await expect(promise).to.have.been.rejectedWith(errClass); + + try { + return await promise; + } catch (err) { + return Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])); + } }; describe('ForeignKeyConstraintError', () => { @@ -33,50 +37,51 @@ describe('[MariaDB Specific] Errors', () => { { foreignKey: 'primaryUserId', as: 'primaryUsers' }); }); - it('in context of DELETE restriction', function() { + it('in context of DELETE restriction', async function() { const ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }) - ]); - }).then(([user1, task1]) => { - ctx.user1 = user1; - ctx.task1 = task1; - return user1.setTasks([task1]); - }).then(() => { - return Promise.all([ - validateError(ctx.user1.destroy(), ForeignKeyConstraintError, { - fields: ['userId'], - table: 'users', - value: undefined, - index: 'tasksusers_ibfk_1', - reltype: 'parent' - }), - validateError(ctx.task1.destroy(), ForeignKeyConstraintError, { - fields: ['taskId'], - table: 'tasks', - value: undefined, - index: 'tasksusers_ibfk_2', - reltype: 'parent' - }) - ]); - }); + await this.sequelize.sync({ force: true }); + + const [user1, task1] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }) + ]); + + await user1.setTasks([task1]); + + await Promise.all([ + validateError(user1.destroy(), ForeignKeyConstraintError, { + fields: ['userId'], + table: 'users', + value: undefined, + index: 'tasksusers_ibfk_1', + reltype: 'parent' + }), + validateError(task1.destroy(), ForeignKeyConstraintError, { + fields: ['taskId'], + table: 'tasks', + value: undefined, + index: 'tasksusers_ibfk_2', + reltype: 'parent' + }) + ]); }); - it('in context of missing relation', function() { + it('in context of missing relation', async function() { const ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError; - return this.sequelize.sync({ force: true }).then(() => - validateError(this.Task.create({ title: 'task', primaryUserId: 5 }), - ForeignKeyConstraintError, { - fields: ['primaryUserId'], - table: 'users', - value: 5, - index: 'tasks_ibfk_1', - reltype: 'child' - })); + await this.sequelize.sync({ force: true }); + + await validateError( + this.Task.create({ title: 'task', primaryUserId: 5 }), + ForeignKeyConstraintError, + { + fields: ['primaryUserId'], + table: 'users', + value: 5, + index: 'tasks_ibfk_1', + reltype: 'child' + } + ); }); }); diff --git a/test/integration/dialects/mariadb/query-interface.test.js b/test/integration/dialects/mariadb/query-interface.test.js index ce1c492f1577..7386047cb281 100644 --- a/test/integration/dialects/mariadb/query-interface.test.js +++ b/test/integration/dialects/mariadb/query-interface.test.js @@ -9,19 +9,13 @@ if (dialect.match(/^mariadb/)) { describe('QueryInterface', () => { describe('databases', () => { - it('should create and drop database', function() { - return this.sequelize.query('SHOW DATABASES') - .then(res => { - const databaseNumber = res[0].length; - return this.sequelize.getQueryInterface().createDatabase('myDB') - .then(() => { - return this.sequelize.query('SHOW DATABASES'); - }) - .then(databases => { - expect(databases[0]).to.have.length(databaseNumber + 1); - return this.sequelize.getQueryInterface().dropDatabase('myDB'); - }); - }); + it('should create and drop database', async function() { + const res = await this.sequelize.query('SHOW DATABASES'); + const databaseNumber = res[0].length; + await this.sequelize.getQueryInterface().createDatabase('myDB'); + const databases = await this.sequelize.query('SHOW DATABASES'); + expect(databases[0]).to.have.length(databaseNumber + 1); + await this.sequelize.getQueryInterface().dropDatabase('myDB'); }); }); }); diff --git a/test/integration/dialects/mssql/connection-manager.test.js b/test/integration/dialects/mssql/connection-manager.test.js index f71e6f77447f..7410fef0cfc9 100644 --- a/test/integration/dialects/mssql/connection-manager.test.js +++ b/test/integration/dialects/mssql/connection-manager.test.js @@ -9,24 +9,24 @@ const dialect = Support.getTestDialect(); if (dialect.match(/^mssql/)) { describe('[MSSQL Specific] Connection Manager', () => { describe('Errors', () => { - it('ECONNREFUSED', () => { + it('ECONNREFUSED', async () => { const sequelize = Support.createSequelizeInstance({ host: '127.0.0.1', port: 34237 }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError); }); - it('ENOTFOUND', () => { + it('ENOTFOUND', async () => { const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError); }); - it('EHOSTUNREACH', () => { + it('EHOSTUNREACH', async () => { const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' }); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError); }); - it('ER_ACCESS_DENIED_ERROR | ELOGIN', () => { + it('ER_ACCESS_DENIED_ERROR | ELOGIN', async () => { const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options); - return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); + await expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError); }); }); }); diff --git a/test/integration/dialects/mssql/query-queue.test.js b/test/integration/dialects/mssql/query-queue.test.js index a083bc076599..d757dedfe201 100644 --- a/test/integration/dialects/mssql/query-queue.test.js +++ b/test/integration/dialects/mssql/query-queue.test.js @@ -2,27 +2,29 @@ const chai = require('chai'), expect = chai.expect, - Promise = require('../../../../lib/promise'), DataTypes = require('../../../../lib/data-types'), Support = require('../../support'), + Sequelize = require('../../../../lib/sequelize'), + ConnectionError = require('../../../../lib/errors/connection-error'), + { AsyncQueueError } = require('../../../../lib/dialects/mssql/async-queue'), dialect = Support.getTestDialect(); if (dialect.match(/^mssql/)) { describe('[MSSQL Specific] Query Queue', () => { - beforeEach(function() { + beforeEach(async function() { const User = this.User = this.sequelize.define('User', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'John' }); - }); + await this.sequelize.sync({ force: true }); + + await User.create({ username: 'John' }); }); - it('should queue concurrent requests to a connection', function() { + it('should queue concurrent requests to a connection', async function() { const User = this.User; - return expect(this.sequelize.transaction(t => { + await expect(this.sequelize.transaction(async t => { return Promise.all([ User.findOne({ transaction: t @@ -33,5 +35,82 @@ if (dialect.match(/^mssql/)) { ]); })).not.to.be.rejected; }); + + it('requests that reject should not affect future requests', async function() { + const User = this.User; + + await expect(this.sequelize.transaction(async t => { + await expect(User.create({ + username: new Date() + })).to.be.rejected; + await expect(User.findOne({ + transaction: t + })).not.to.be.rejected; + })).not.to.be.rejected; + }); + + it('closing the connection should reject pending requests', async function() { + const User = this.User; + + let promise; + + await expect(this.sequelize.transaction(t => + promise = Promise.all([ + expect(this.sequelize.dialect.connectionManager.disconnect(t.connection)).to.be.fulfilled, + expect(User.findOne({ + transaction: t + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed') + .and.have.property('parent').that.instanceOf(AsyncQueueError), + expect(User.findOne({ + transaction: t + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed') + .and.have.property('parent').that.instanceOf(AsyncQueueError) + ]) + )).to.be.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed'); + + await expect(promise).not.to.be.rejected; + }); + + it('closing the connection should reject in-progress requests', async function() { + const User = this.User; + + let promise; + + await expect(this.sequelize.transaction(async t => { + const wrappedExecSql = t.connection.execSql; + t.connection.execSql = (...args) => { + this.sequelize.dialect.connectionManager.disconnect(t.connection); + return wrappedExecSql(...args); + }; + return promise = expect(User.findOne({ + transaction: t + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could finish executing') + .and.have.property('parent').that.instanceOf(AsyncQueueError); + })).to.be.eventually.rejectedWith(ConnectionError, 'the connection was closed before this query could be executed') + .and.have.property('parent').that.instanceOf(AsyncQueueError); + + await expect(promise).not.to.be.rejected; + }); + + describe('unhandled rejections', () => { + it("unhandled rejection should occur if user doesn't catch promise returned from query", async function() { + const User = this.User; + const rejectionPromise = Support.nextUnhandledRejection(); + User.create({ + username: new Date() + }); + await expect(rejectionPromise).to.be.rejectedWith( + Sequelize.ValidationError, 'string violation: username cannot be an array or an object'); + }); + + it('no unhandled rejections should occur as long as user catches promise returned from query', async function() { + const User = this.User; + const unhandledRejections = Support.captureUnhandledRejections(); + await expect(User.create({ + username: new Date() + })).to.be.rejectedWith(Sequelize.ValidationError); + expect(unhandledRejections).to.deep.equal([]); + }); + }); }); } diff --git a/test/integration/dialects/mssql/regressions.test.js b/test/integration/dialects/mssql/regressions.test.js index 5529ff9a5e5b..9ff1b4a07691 100644 --- a/test/integration/dialects/mssql/regressions.test.js +++ b/test/integration/dialects/mssql/regressions.test.js @@ -2,14 +2,15 @@ const chai = require('chai'), expect = chai.expect, + sinon = require('sinon'), Support = require('../../support'), Sequelize = Support.Sequelize, Op = Sequelize.Op, dialect = Support.getTestDialect(); if (dialect.match(/^mssql/)) { - describe('[MSSQL Specific] Regressions', () => { - it('does not duplicate columns in ORDER BY statement, #9008', function() { + describe(Support.getTestDialectTeaser('Regressions'), () => { + it('does not duplicate columns in ORDER BY statement, #9008', async function() { const LoginLog = this.sequelize.define('LoginLog', { ID: { field: 'id', @@ -45,91 +46,206 @@ if (dialect.match(/^mssql/)) { foreignKey: 'UserID' }); - return this.sequelize.sync({ force: true }) - .then(() => User.bulkCreate([ - { UserName: 'Vayom' }, - { UserName: 'Shaktimaan' }, - { UserName: 'Nikita' }, - { UserName: 'Aryamaan' } - ], { returning: true })) - .then(([vyom, shakti, nikita, arya]) => { - return Sequelize.Promise.all([ - vyom.createLoginLog(), - shakti.createLoginLog(), - nikita.createLoginLog(), - arya.createLoginLog() - ]); - }).then(() => { - return LoginLog.findAll({ - include: [ - { - model: User, - where: { - UserName: { - [Op.like]: '%maan%' - } - } + await this.sequelize.sync({ force: true }); + + const [vyom, shakti, nikita, arya] = await User.bulkCreate([ + { UserName: 'Vayom' }, + { UserName: 'Shaktimaan' }, + { UserName: 'Nikita' }, + { UserName: 'Aryamaan' } + ], { returning: true }); + + await Promise.all([ + vyom.createLoginLog(), + shakti.createLoginLog(), + nikita.createLoginLog(), + arya.createLoginLog() + ]); + + const logs = await LoginLog.findAll({ + include: [ + { + model: User, + where: { + UserName: { + [Op.like]: '%maan%' } - ], - order: [[User, 'UserName', 'DESC']], - offset: 0, - limit: 10 - }); - }).then(logs => { - expect(logs).to.have.length(2); - expect(logs[0].User.get('UserName')).to.equal('Shaktimaan'); - expect(logs[1].User.get('UserName')).to.equal('Aryamaan'); - }); - }); - }); + } + } + ], + order: [[User, 'UserName', 'DESC']], + offset: 0, + limit: 10 + }); - it('sets the varchar(max) length correctly on describeTable', function() { - const Users = this.sequelize.define('_Users', { - username: Sequelize.STRING('MAX') - }, { freezeTableName: true }); + expect(logs).to.have.length(2); + expect(logs[0].User.get('UserName')).to.equal('Shaktimaan'); + expect(logs[1].User.get('UserName')).to.equal('Aryamaan'); - return Users.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().describeTable('_Users').then(metadata => { - const username = metadata.username; - expect(username.type).to.include('(MAX)'); + // #11258 and similar + const otherLogs = await LoginLog.findAll({ + include: [ + { + model: User, + where: { + UserName: { + [Op.like]: '%maan%' + } + } + } + ], + order: [['id', 'DESC']], + offset: 0, + limit: 10 + }); + + expect(otherLogs).to.have.length(2); + expect(otherLogs[0].User.get('UserName')).to.equal('Aryamaan'); + expect(otherLogs[1].User.get('UserName')).to.equal('Shaktimaan'); + + // Separate queries can apply order freely + const separateUsers = await User.findAll({ + include: [ + { + model: LoginLog, + separate: true, + order: [ + 'id' + ] + } + ], + where: { + UserName: { + [Op.like]: '%maan%' + } + }, + order: ['UserName', ['UserID', 'DESC']], + offset: 0, + limit: 10 }); + + expect(separateUsers).to.have.length(2); + expect(separateUsers[0].get('UserName')).to.equal('Aryamaan'); + expect(separateUsers[0].get('LoginLogs')).to.have.length(1); + expect(separateUsers[1].get('UserName')).to.equal('Shaktimaan'); + expect(separateUsers[1].get('LoginLogs')).to.have.length(1); }); - }); - it('sets the char(10) length correctly on describeTable', function() { - const Users = this.sequelize.define('_Users', { - username: Sequelize.CHAR(10) - }, { freezeTableName: true }); + it('allow referencing FK to different tables in a schema with onDelete, #10125', async function() { + const Child = this.sequelize.define( + 'Child', + {}, + { + timestamps: false, + freezeTableName: true, + schema: 'a' + } + ); + const Toys = this.sequelize.define( + 'Toys', + {}, + { + timestamps: false, + freezeTableName: true, + schema: 'a' + } + ); + const Parent = this.sequelize.define( + 'Parent', + {}, + { + timestamps: false, + freezeTableName: true, + schema: 'a' + } + ); + + Child.hasOne(Toys, { + onDelete: 'CASCADE' + }); - return Users.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().describeTable('_Users').then(metadata => { - const username = metadata.username; - expect(username.type).to.include('(10)'); + Parent.hasOne(Toys, { + onDelete: 'CASCADE' }); + + const spy = sinon.spy(); + + await this.sequelize.queryInterface.createSchema('a'); + await this.sequelize.sync({ + force: true, + logging: spy + }); + + expect(spy).to.have.been.called; + const log = spy.args.find(arg => arg[0].includes('IF OBJECT_ID(\'[a].[Toys]\', \'U\') IS NULL CREATE TABLE'))[0]; + + expect(log.match(/ON DELETE CASCADE/g).length).to.equal(2); }); - }); - it('saves value bigger than 2147483647, #11245', function() { - const BigIntTable = this.sequelize.define('BigIntTable', { - business_id: { - type: Sequelize.BIGINT, - allowNull: false - } - }, { - freezeTableName: true + it('sets the varchar(max) length correctly on describeTable', async function() { + const Users = this.sequelize.define('_Users', { + username: Sequelize.STRING('MAX') + }, { freezeTableName: true }); + + await Users.sync({ force: true }); + const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); + const username = metadata.username; + expect(username.type).to.include('(MAX)'); + }); + + it('sets the char(10) length correctly on describeTable', async function() { + const Users = this.sequelize.define('_Users', { + username: Sequelize.CHAR(10) + }, { freezeTableName: true }); + + await Users.sync({ force: true }); + const metadata = await this.sequelize.getQueryInterface().describeTable('_Users'); + const username = metadata.username; + expect(username.type).to.include('(10)'); }); - const bigIntValue = 2147483648; - - return BigIntTable.sync({ force: true }) - .then(() => { - return BigIntTable.create({ - business_id: bigIntValue - }); - }) - .then(() => BigIntTable.findOne()) - .then(record => { - expect(Number(record.business_id)).to.equals(bigIntValue); + it('saves value bigger than 2147483647, #11245', async function() { + const BigIntTable = this.sequelize.define('BigIntTable', { + business_id: { + type: Sequelize.BIGINT, + allowNull: false + } + }, { + freezeTableName: true + }); + + const bigIntValue = 2147483648; + + await BigIntTable.sync({ force: true }); + + await BigIntTable.create({ + business_id: bigIntValue }); + + const record = await BigIntTable.findOne(); + expect(Number(record.business_id)).to.equals(bigIntValue); + }); + + it('saves boolean is true, #12090', async function() { + const BooleanTable = this.sequelize.define('BooleanTable', { + status: { + type: Sequelize.BOOLEAN, + allowNull: false + } + }, { + freezeTableName: true + }); + + const value = true; + + await BooleanTable.sync({ force: true }); + + await BooleanTable.create({ + status: value + }); + + const record = await BooleanTable.findOne(); + expect(record.status).to.equals(value); + }); }); } diff --git a/test/integration/dialects/mysql/associations.test.js b/test/integration/dialects/mysql/associations.test.js index bc6971161bdd..0c16f6219fa5 100644 --- a/test/integration/dialects/mysql/associations.test.js +++ b/test/integration/dialects/mysql/associations.test.js @@ -10,30 +10,27 @@ if (dialect === 'mysql') { describe('[MYSQL Specific] Associations', () => { describe('many-to-many', () => { describe('where tables have the same prefix', () => { - it('should create a table wp_table1wp_table2s', function() { + it('should create a table wp_table1wp_table2s', async function() { const Table2 = this.sequelize.define('wp_table2', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('wp_table1', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'wp_table1swp_table2s' }); Table2.belongsToMany(Table1, { through: 'wp_table1swp_table2s' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }).then(() => { - expect(this.sequelize.modelManager.getModel('wp_table1swp_table2s')).to.exist; - }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); + expect(this.sequelize.modelManager.getModel('wp_table1swp_table2s')).to.exist; }); }); describe('when join table name is specified', () => { - beforeEach(function() { + beforeEach(async function() { const Table2 = this.sequelize.define('ms_table1', { foo: DataTypes.STRING }), Table1 = this.sequelize.define('ms_table2', { foo: DataTypes.STRING }); Table1.belongsToMany(Table2, { through: 'table1_to_table2' }); Table2.belongsToMany(Table1, { through: 'table1_to_table2' }); - return Table1.sync({ force: true }).then(() => { - return Table2.sync({ force: true }); - }); + await Table1.sync({ force: true }); + await Table2.sync({ force: true }); }); it('should not use only a specified name', function() { @@ -44,7 +41,7 @@ if (dialect === 'mysql') { }); describe('HasMany', () => { - beforeEach(function() { + beforeEach(async function() { //prevent periods from occurring in the table name since they are used to delimit (table.column) this.User = this.sequelize.define(`User${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); this.Task = this.sequelize.define(`Task${Math.ceil(Math.random() * 10000000)}`, { name: DataTypes.STRING }); @@ -62,70 +59,48 @@ if (dialect === 'mysql') { tasks[i] = { name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate(users).then(() => { - return this.Task.bulkCreate(tasks); - }); - }); + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); }); describe('addDAO / getModel', () => { - beforeEach(function() { + beforeEach(async function() { this.user = null; this.task = null; - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.task = _tasks[0]; - }); - }); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.task = _tasks[0]; }); - it('should correctly add an association to the dao', function() { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(0); - return this.user.addTask(this.task).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(1); - }); - }); - }); + it('should correctly add an association to the dao', async function() { + expect(await this.user.getTasks()).to.have.length(0); + await this.user.addTask(this.task); + expect(await this.user.getTasks()).to.have.length(1); }); }); describe('removeDAO', () => { - beforeEach(function() { + beforeEach(async function() { this.user = null; this.tasks = null; - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.tasks = _tasks; - }); - }); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.tasks = _tasks; }); - it('should correctly remove associated objects', function() { - return this.user.getTasks().then(__tasks => { - expect(__tasks.length).to.equal(0); - return this.user.setTasks(this.tasks).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(this.tasks.length); - return this.user.removeTask(this.tasks[0]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks.length).to.equal(this.tasks.length - 1); - return this.user.removeTasks([this.tasks[1], this.tasks[2]]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length - 3); - }); - }); - }); - }); - }); - }); - }); + it('should correctly remove associated objects', async function() { + expect(await this.user.getTasks()).to.have.length(0); + await this.user.setTasks(this.tasks); + expect(await this.user.getTasks()).to.have.length(this.tasks.length); + await this.user.removeTask(this.tasks[0]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 1); + await this.user.removeTasks([this.tasks[1], this.tasks[2]]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 3); }); }); }); diff --git a/test/integration/dialects/mysql/connector-manager.test.js b/test/integration/dialects/mysql/connector-manager.test.js index a73823de1894..ec0d1549ca24 100644 --- a/test/integration/dialects/mysql/connector-manager.test.js +++ b/test/integration/dialects/mysql/connector-manager.test.js @@ -8,31 +8,32 @@ const DataTypes = require('../../../../lib/data-types'); if (dialect === 'mysql') { describe('[MYSQL Specific] Connection Manager', () => { - it('-FOUND_ROWS can be suppressed to get back legacy behavior', () => { + it('-FOUND_ROWS can be suppressed to get back legacy behavior', async () => { const sequelize = Support.createSequelizeInstance({ dialectOptions: { flags: '' } }); const User = sequelize.define('User', { username: DataTypes.STRING }); - return User.sync({ force: true }) - .then(() => User.create({ id: 1, username: 'jozef' })) - .then(() => User.update({ username: 'jozef' }, { - where: { - id: 1 - } - })) - // https://github.com/sequelize/sequelize/issues/7184 - .then(([affectedCount]) => affectedCount.should.equal(1)); + await User.sync({ force: true }); + await User.create({ id: 1, username: 'jozef' }); + + const [affectedCount] = await User.update({ username: 'jozef' }, { + where: { + id: 1 + } + }); + + // https://github.com/sequelize/sequelize/issues/7184 + await affectedCount.should.equal(1); }); - it('should acquire a valid connection when keepDefaultTimezone is true', () => { + it('should acquire a valid connection when keepDefaultTimezone is true', async () => { const sequelize = Support.createSequelizeInstance({ keepDefaultTimezone: true, pool: { min: 1, max: 1, handleDisconnects: true, idle: 5000 } }); const cm = sequelize.connectionManager; - return sequelize - .sync() - .then(() => cm.getConnection()) - .then(connection => { - expect(cm.validate(connection)).to.be.ok; - return cm.releaseConnection(connection); - }); + + await sequelize.sync(); + + const connection = await cm.getConnection(); + expect(cm.validate(connection)).to.be.ok; + await cm.releaseConnection(connection); }); }); } diff --git a/test/integration/dialects/mysql/dao-factory.test.js b/test/integration/dialects/mysql/dao-factory.test.js index 4f0c58f98b25..e6215c00da7b 100644 --- a/test/integration/dialects/mysql/dao-factory.test.js +++ b/test/integration/dialects/mysql/dao-factory.test.js @@ -4,77 +4,76 @@ const chai = require('chai'), expect = chai.expect, Support = require('../../support'), dialect = Support.getTestDialect(), - DataTypes = require('../../../../lib/data-types'), - config = require('../../../config/config'); + DataTypes = require('../../../../lib/data-types'); if (dialect === 'mysql') { describe('[MYSQL Specific] DAOFactory', () => { describe('constructor', () => { it('handles extended attributes (unique)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, unique: true } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) UNIQUE', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); }); it('handles extended attributes (default)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, defaultValue: 'foo' } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: "VARCHAR(255) DEFAULT 'foo'", id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: "VARCHAR(255) DEFAULT 'foo'", id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); }); it('handles extended attributes (null)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, allowNull: false } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) NOT NULL', id: 'INTEGER NOT NULL auto_increment PRIMARY KEY' }); }); it('handles extended attributes (primaryKey)', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { username: { type: DataTypes.STRING, primaryKey: true } }, { timestamps: false }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ username: 'VARCHAR(255) PRIMARY KEY' }); }); it('adds timestamps', function() { - const User1 = this.sequelize.define(`User${config.rand()}`, {}); - const User2 = this.sequelize.define(`User${config.rand()}`, {}, { timestamps: true }); + const User1 = this.sequelize.define(`User${Support.rand()}`, {}); + const User2 = this.sequelize.define(`User${Support.rand()}`, {}, { timestamps: true }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User1.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User2.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User1.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User2.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); }); it('adds deletedAt if paranoid', function() { - const User = this.sequelize.define(`User${config.rand()}`, {}, { paranoid: true }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); + const User = this.sequelize.define(`User${Support.rand()}`, {}, { paranoid: true }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deletedAt: 'DATETIME', updatedAt: 'DATETIME NOT NULL', createdAt: 'DATETIME NOT NULL' }); }); it('underscores timestamps if underscored', function() { - const User = this.sequelize.define(`User${config.rand()}`, {}, { paranoid: true, underscored: true }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', updated_at: 'DATETIME NOT NULL', created_at: 'DATETIME NOT NULL' }); + const User = this.sequelize.define(`User${Support.rand()}`, {}, { paranoid: true, underscored: true }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.rawAttributes)).to.deep.equal({ id: 'INTEGER NOT NULL auto_increment PRIMARY KEY', deleted_at: 'DATETIME', updated_at: 'DATETIME NOT NULL', created_at: 'DATETIME NOT NULL' }); }); it('omits text fields with defaultValues', function() { - const User = this.sequelize.define(`User${config.rand()}`, { name: { type: DataTypes.TEXT, defaultValue: 'helloworld' } }); + const User = this.sequelize.define(`User${Support.rand()}`, { name: { type: DataTypes.TEXT, defaultValue: 'helloworld' } }); expect(User.rawAttributes.name.type.toString()).to.equal('TEXT'); }); it('omits blobs fields with defaultValues', function() { - const User = this.sequelize.define(`User${config.rand()}`, { name: { type: DataTypes.STRING.BINARY, defaultValue: 'helloworld' } }); + const User = this.sequelize.define(`User${Support.rand()}`, { name: { type: DataTypes.STRING.BINARY, defaultValue: 'helloworld' } }); expect(User.rawAttributes.name.type.toString()).to.equal('VARCHAR(255) BINARY'); }); }); describe('primaryKeys', () => { it('determines the correct primaryKeys', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + const User = this.sequelize.define(`User${Support.rand()}`, { foo: { type: DataTypes.STRING, primaryKey: true }, bar: DataTypes.STRING }); - expect(this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(User.primaryKeys)).to.deep.equal({ 'foo': 'VARCHAR(255) PRIMARY KEY' }); + expect(this.sequelize.getQueryInterface().queryGenerator.attributesToSQL(User.primaryKeys)).to.deep.equal({ 'foo': 'VARCHAR(255) PRIMARY KEY' }); }); }); }); diff --git a/test/integration/dialects/mysql/errors.test.js b/test/integration/dialects/mysql/errors.test.js index 787ee2f48208..13b7ff376488 100644 --- a/test/integration/dialects/mysql/errors.test.js +++ b/test/integration/dialects/mysql/errors.test.js @@ -10,11 +10,16 @@ const DataTypes = require('../../../../lib/data-types'); if (dialect === 'mysql') { describe('[MYSQL Specific] Errors', () => { - const validateError = (promise, errClass, errValues) => { - const wanted = Object.assign({}, errValues); + const validateError = async (promise, errClass, errValues) => { + const wanted = { ...errValues }; - return expect(promise).to.have.been.rejectedWith(errClass).then(() => - promise.catch(err => Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])))); + await expect(promise).to.have.been.rejectedWith(errClass); + + try { + return await promise; + } catch (err) { + return Object.keys(wanted).forEach(k => expect(err[k]).to.eql(wanted[k])); + } }; describe('ForeignKeyConstraintError', () => { @@ -29,46 +34,48 @@ if (dialect === 'mysql') { this.Task.belongsTo(this.User, { foreignKey: 'primaryUserId', as: 'primaryUsers' }); }); - it('in context of DELETE restriction', function() { - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.User.create({ id: 67, username: 'foo' }), - this.Task.create({ id: 52, title: 'task' }) - ]); - }).then(([user1, task1]) => { - ctx.user1 = user1; - ctx.task1 = task1; - return user1.setTasks([task1]); - }).then(() => { - return Promise.all([ - validateError(ctx.user1.destroy(), Sequelize.ForeignKeyConstraintError, { - fields: ['userId'], - table: 'users', - value: undefined, - index: 'tasksusers_ibfk_1', - reltype: 'parent' - }), - validateError(ctx.task1.destroy(), Sequelize.ForeignKeyConstraintError, { - fields: ['taskId'], - table: 'tasks', - value: undefined, - index: 'tasksusers_ibfk_2', - reltype: 'parent' - }) - ]); - }); + it('in context of DELETE restriction', async function() { + await this.sequelize.sync({ force: true }); + + const [user1, task1] = await Promise.all([ + this.User.create({ id: 67, username: 'foo' }), + this.Task.create({ id: 52, title: 'task' }) + ]); + + await user1.setTasks([task1]); + + await Promise.all([ + validateError(user1.destroy(), Sequelize.ForeignKeyConstraintError, { + fields: ['userId'], + table: 'users', + value: undefined, + index: 'tasksusers_ibfk_1', + reltype: 'parent' + }), + validateError(task1.destroy(), Sequelize.ForeignKeyConstraintError, { + fields: ['taskId'], + table: 'tasks', + value: undefined, + index: 'tasksusers_ibfk_2', + reltype: 'parent' + }) + ]); }); - it('in context of missing relation', function() { - return this.sequelize.sync({ force: true }).then(() => - validateError(this.Task.create({ title: 'task', primaryUserId: 5 }), Sequelize.ForeignKeyConstraintError, { + it('in context of missing relation', async function() { + await this.sequelize.sync({ force: true }); + + await validateError( + this.Task.create({ title: 'task', primaryUserId: 5 }), + Sequelize.ForeignKeyConstraintError, + { fields: ['primaryUserId'], table: 'users', value: 5, index: 'tasks_ibfk_1', reltype: 'child' - })); + } + ); }); }); }); diff --git a/test/integration/dialects/mysql/warning.test.js b/test/integration/dialects/mysql/warning.test.js index e9b0a54bc4cc..9b1a63a97da8 100644 --- a/test/integration/dialects/mysql/warning.test.js +++ b/test/integration/dialects/mysql/warning.test.js @@ -11,7 +11,7 @@ describe(Support.getTestDialectTeaser('Warning'), () => { // We can only test MySQL warnings when using MySQL. if (dialect === 'mysql') { describe('logging', () => { - it('logs warnings when there are warnings', () => { + it('logs warnings when there are warnings', async () => { const logger = sinon.spy(console, 'log'); const sequelize = Support.createSequelizeInstance({ logging: logger, @@ -23,20 +23,17 @@ describe(Support.getTestDialectTeaser('Warning'), () => { name: Sequelize.DataTypes.STRING(1, true) }); - return sequelize.sync({ force: true }).then(() => { - return sequelize.authenticate(); - }).then(() => { - return sequelize.query("SET SESSION sql_mode='';"); - }).then(() => { - return Model.create({ - name: 'very-long-long-name' - }); - }).then(() => { - // last log is warning message - expect(logger.args[logger.args.length - 1][0]).to.be.match(/^MySQL Warnings \(default\):.*/m); - }, () => { - expect.fail(); + await sequelize.sync({ force: true }); + await sequelize.authenticate(); + await sequelize.query("SET SESSION sql_mode='';"); + + await Model.create({ + name: 'very-long-long-name' }); + + // last log is warning message + expect(logger.args[logger.args.length - 1][0]).to.be.match(/^MySQL Warnings \(default\):.*/m); + logger.restore(); }); }); } diff --git a/test/integration/dialects/postgres/associations.test.js b/test/integration/dialects/postgres/associations.test.js index 6bf71cdb3824..d2fdd1ea5651 100644 --- a/test/integration/dialects/postgres/associations.test.js +++ b/test/integration/dialects/postgres/associations.test.js @@ -4,7 +4,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../../support'), dialect = Support.getTestDialect(), - config = require('../../../config/config'), DataTypes = require('../../../../lib/data-types'); if (dialect.match(/^postgres/)) { @@ -43,10 +42,10 @@ if (dialect.match(/^postgres/)) { describe('HasMany', () => { describe('addDAO / getModel', () => { - beforeEach(function() { + beforeEach(async function() { //prevent periods from occurring in the table name since they are used to delimit (table.column) - this.User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING }); - this.Task = this.sequelize.define(`Task${config.rand()}`, { name: DataTypes.STRING }); + this.User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING }); + this.Task = this.sequelize.define(`Task${Support.rand()}`, { name: DataTypes.STRING }); this.users = null; this.tasks = null; @@ -61,40 +60,30 @@ if (dialect.match(/^postgres/)) { tasks[i] = { name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate(users).then(() => { - return this.Task.bulkCreate(tasks).then(() => { - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.task = _tasks[0]; - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.task = _tasks[0]; }); - it('should correctly add an association to the dao', function() { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(0); - return this.user.addTask(this.task).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(1); - }); - }); - }); + it('should correctly add an association to the dao', async function() { + expect(await this.user.getTasks()).to.have.length(0); + await this.user.addTask(this.task); + expect(await this.user.getTasks()).to.have.length(1); }); }); describe('removeDAO', () => { - it('should correctly remove associated objects', function() { + it('should correctly remove associated objects', async function() { const users = [], tasks = []; //prevent periods from occurring in the table name since they are used to delimit (table.column) - this.User = this.sequelize.define(`User${config.rand()}`, { name: DataTypes.STRING }); - this.Task = this.sequelize.define(`Task${config.rand()}`, { name: DataTypes.STRING }); + this.User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING }); + this.Task = this.sequelize.define(`Task${Support.rand()}`, { name: DataTypes.STRING }); this.users = null; this.tasks = null; @@ -106,39 +95,23 @@ if (dialect.match(/^postgres/)) { tasks[i] = { id: i + 1, name: `Task${Math.random()}` }; } - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate(users).then(() => { - return this.Task.bulkCreate(tasks).then(() => { - return this.User.findAll().then(_users => { - return this.Task.findAll().then(_tasks => { - this.user = _users[0]; - this.task = _tasks[0]; - this.users = _users; - this.tasks = _tasks; - - return this.user.getTasks().then(__tasks => { - expect(__tasks).to.have.length(0); - return this.user.setTasks(this.tasks).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length); - return this.user.removeTask(this.tasks[0]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length - 1); - return this.user.removeTasks([this.tasks[1], this.tasks[2]]).then(() => { - return this.user.getTasks().then(_tasks => { - expect(_tasks).to.have.length(this.tasks.length - 3); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.User.bulkCreate(users); + await this.Task.bulkCreate(tasks); + const _users = await this.User.findAll(); + const _tasks = await this.Task.findAll(); + this.user = _users[0]; + this.task = _tasks[0]; + this.users = _users; + this.tasks = _tasks; + + expect(await this.user.getTasks()).to.have.length(0); + await this.user.setTasks(this.tasks); + expect(await this.user.getTasks()).to.have.length(this.tasks.length); + await this.user.removeTask(this.tasks[0]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 1); + await this.user.removeTasks([this.tasks[1], this.tasks[2]]); + expect(await this.user.getTasks()).to.have.length(this.tasks.length - 3); }); }); }); diff --git a/test/integration/dialects/postgres/connection-manager.test.js b/test/integration/dialects/postgres/connection-manager.test.js index 80f16d227c69..67778d8e8a04 100644 --- a/test/integration/dialects/postgres/connection-manager.test.js +++ b/test/integration/dialects/postgres/connection-manager.test.js @@ -8,45 +8,52 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES] Sequelize', () => { - function checkTimezoneParsing(baseOptions) { - const options = Object.assign({}, baseOptions, { timezone: 'Asia/Kolkata', timestamps: true }); + async function checkTimezoneParsing(baseOptions) { + const options = { ...baseOptions, timezone: 'Asia/Kolkata', timestamps: true }; const sequelize = Support.createSequelizeInstance(options); const tzTable = sequelize.define('tz_table', { foo: DataTypes.STRING }); - return tzTable.sync({ force: true }).then(() => { - return tzTable.create({ foo: 'test' }).then(row => { - expect(row).to.be.not.null; - }); - }); + await tzTable.sync({ force: true }); + const row = await tzTable.create({ foo: 'test' }); + expect(row).to.be.not.null; } - it('should correctly parse the moment based timezone while fetching hstore oids', function() { - return checkTimezoneParsing(this.sequelize.options); + it('should correctly parse the moment based timezone while fetching hstore oids', async function() { + await checkTimezoneParsing(this.sequelize.options); }); - it('should set client_min_messages to warning by default', () => { - return Support.sequelize.query('SHOW client_min_messages') - .then(result => { - expect(result[0].client_min_messages).to.equal('warning'); - }); + it('should set client_min_messages to warning by default', async () => { + const result = await Support.sequelize.query('SHOW client_min_messages'); + expect(result[0].client_min_messages).to.equal('warning'); }); - it('should allow overriding client_min_messages', () => { + it('should allow overriding client_min_messages', async () => { const sequelize = Support.createSequelizeInstance({ clientMinMessages: 'ERROR' }); - return sequelize.query('SHOW client_min_messages') - .then(result => { - expect(result[0].client_min_messages).to.equal('error'); - }); + const result = await sequelize.query('SHOW client_min_messages'); + expect(result[0].client_min_messages).to.equal('error'); }); - it('should not set client_min_messages if clientMinMessages is false', () => { + it('should not set client_min_messages if clientMinMessages is false', async () => { const sequelize = Support.createSequelizeInstance({ clientMinMessages: false }); - return sequelize.query('SHOW client_min_messages') - .then(result => { - // `notice` is Postgres's default - expect(result[0].client_min_messages).to.equal('notice'); - }); + const result = await sequelize.query('SHOW client_min_messages'); + // `notice` is Postgres's default + expect(result[0].client_min_messages).to.equal('notice'); + }); + + it('should time out the query request when the query runs beyond the configured query_timeout', async () => { + const sequelize = Support.createSequelizeInstance({ + dialectOptions: { query_timeout: 100 } + }); + const error = await sequelize.query('select pg_sleep(2)').catch(e => e); + expect(error.message).to.equal('Query read timeout'); }); + + it('should allow overriding session variables through the `options` param', async () => { + const sequelize = Support.createSequelizeInstance({ dialectOptions: { options: '-csearch_path=abc' } }); + const result = await sequelize.query('SHOW search_path'); + expect(result[0].search_path).to.equal('abc'); + }); + }); describe('Dynamic OIDs', () => { @@ -79,66 +86,63 @@ if (dialect.match(/^postgres/)) { return User.sync({ force: true }); } - it('should fetch regular dynamic oids and create parsers', () => { + it('should fetch regular dynamic oids and create parsers', async () => { const sequelize = Support.sequelize; - return reloadDynamicOIDs(sequelize).then(() => { - dynamicTypesToCheck.forEach(type => { - expect(type.types.postgres, - `DataType.${type.key}.types.postgres`).to.not.be.empty; + await reloadDynamicOIDs(sequelize); + dynamicTypesToCheck.forEach(type => { + expect(type.types.postgres, + `DataType.${type.key}.types.postgres`).to.not.be.empty; - for (const name of type.types.postgres) { - const entry = sequelize.connectionManager.nameOidMap[name]; - const oidParserMap = sequelize.connectionManager.oidParserMap; + for (const name of type.types.postgres) { + const entry = sequelize.connectionManager.nameOidMap[name]; + const oidParserMap = sequelize.connectionManager.oidParserMap; - expect(entry.oid, `nameOidMap[${name}].oid`).to.be.a('number'); - expect(entry.arrayOid, `nameOidMap[${name}].arrayOid`).to.be.a('number'); + expect(entry.oid, `nameOidMap[${name}].oid`).to.be.a('number'); + expect(entry.arrayOid, `nameOidMap[${name}].arrayOid`).to.be.a('number'); - expect(oidParserMap.get(entry.oid), - `oidParserMap.get(nameOidMap[${name}].oid)`).to.be.a('function'); - expect(oidParserMap.get(entry.arrayOid), - `oidParserMap.get(nameOidMap[${name}].arrayOid)`).to.be.a('function'); - } + expect(oidParserMap.get(entry.oid), + `oidParserMap.get(nameOidMap[${name}].oid)`).to.be.a('function'); + expect(oidParserMap.get(entry.arrayOid), + `oidParserMap.get(nameOidMap[${name}].arrayOid)`).to.be.a('function'); + } - }); }); }); - it('should fetch enum dynamic oids and create parsers', () => { + it('should fetch enum dynamic oids and create parsers', async () => { const sequelize = Support.sequelize; - return reloadDynamicOIDs(sequelize).then(() => { - const enumOids = sequelize.connectionManager.enumOids; - const oidParserMap = sequelize.connectionManager.oidParserMap; - - expect(enumOids.oids, 'enumOids.oids').to.not.be.empty; - expect(enumOids.arrayOids, 'enumOids.arrayOids').to.not.be.empty; - - for (const oid of enumOids.oids) { - expect(oidParserMap.get(oid), 'oidParserMap.get(enumOids.oids)').to.be.a('function'); - } - for (const arrayOid of enumOids.arrayOids) { - expect(oidParserMap.get(arrayOid), 'oidParserMap.get(enumOids.arrayOids)').to.be.a('function'); - } - }); + await reloadDynamicOIDs(sequelize); + const enumOids = sequelize.connectionManager.enumOids; + const oidParserMap = sequelize.connectionManager.oidParserMap; + + expect(enumOids.oids, 'enumOids.oids').to.not.be.empty; + expect(enumOids.arrayOids, 'enumOids.arrayOids').to.not.be.empty; + + for (const oid of enumOids.oids) { + expect(oidParserMap.get(oid), 'oidParserMap.get(enumOids.oids)').to.be.a('function'); + } + for (const arrayOid of enumOids.arrayOids) { + expect(oidParserMap.get(arrayOid), 'oidParserMap.get(enumOids.arrayOids)').to.be.a('function'); + } }); - it('should fetch range dynamic oids and create parsers', () => { + it('should fetch range dynamic oids and create parsers', async () => { const sequelize = Support.sequelize; - return reloadDynamicOIDs(sequelize).then(() => { - for (const baseKey in expCastTypes) { - const name = expCastTypes[baseKey]; - const entry = sequelize.connectionManager.nameOidMap[name]; - const oidParserMap = sequelize.connectionManager.oidParserMap; - - for (const key of ['rangeOid', 'arrayRangeOid']) { - expect(entry[key], `nameOidMap[${name}][${key}]`).to.be.a('number'); - } + await reloadDynamicOIDs(sequelize); + for (const baseKey in expCastTypes) { + const name = expCastTypes[baseKey]; + const entry = sequelize.connectionManager.nameOidMap[name]; + const oidParserMap = sequelize.connectionManager.oidParserMap; - expect(oidParserMap.get(entry.rangeOid), - `oidParserMap.get(nameOidMap[${name}].rangeOid)`).to.be.a('function'); - expect(oidParserMap.get(entry.arrayRangeOid), - `oidParserMap.get(nameOidMap[${name}].arrayRangeOid)`).to.be.a('function'); + for (const key of ['rangeOid', 'arrayRangeOid']) { + expect(entry[key], `nameOidMap[${name}][${key}]`).to.be.a('number'); } - }); + + expect(oidParserMap.get(entry.rangeOid), + `oidParserMap.get(nameOidMap[${name}].rangeOid)`).to.be.a('function'); + expect(oidParserMap.get(entry.arrayRangeOid), + `oidParserMap.get(nameOidMap[${name}].arrayRangeOid)`).to.be.a('function'); + } }); }); diff --git a/test/integration/dialects/postgres/dao.test.js b/test/integration/dialects/postgres/dao.test.js index 6dab43bca149..39eb80ad50ee 100644 --- a/test/integration/dialects/postgres/dao.test.js +++ b/test/integration/dialects/postgres/dao.test.js @@ -5,14 +5,13 @@ const chai = require('chai'), Support = require('../../support'), Sequelize = Support.Sequelize, Op = Sequelize.Op, - Promise = Sequelize.Promise, dialect = Support.getTestDialect(), DataTypes = require('../../../../lib/data-types'), sequelize = require('../../../../lib/sequelize'); if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] DAO', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize.options.quoteIdentifiers = true; this.User = this.sequelize.define('User', { username: DataTypes.STRING, @@ -36,15 +35,15 @@ if (dialect.match(/^postgres/)) { holidays: DataTypes.ARRAY(DataTypes.RANGE(DataTypes.DATE)), location: DataTypes.GEOMETRY() }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); afterEach(function() { this.sequelize.options.quoteIdentifiers = true; }); - it('should be able to search within an array', function() { - return this.User.findAll({ + it('should be able to search within an array', async function() { + await this.User.findAll({ where: { email: ['hello', 'world'] }, @@ -55,143 +54,116 @@ if (dialect.match(/^postgres/)) { }); }); - it('should be able to update a field with type ARRAY(JSON)', function() { - return this.User.create({ + it('should be able to update a field with type ARRAY(JSON)', async function() { + const userInstance = await this.User.create({ username: 'bob', email: ['myemail@email.com'], friends: [{ name: 'John Smith' }] - }).then(userInstance => { - expect(userInstance.friends).to.have.length(1); - expect(userInstance.friends[0].name).to.equal('John Smith'); - - return userInstance.update({ - friends: [{ - name: 'John Smythe' - }] - }); - }).get('friends') - .tap(friends => { - expect(friends).to.have.length(1); - expect(friends[0].name).to.equal('John Smythe'); - }); + }); + + expect(userInstance.friends).to.have.length(1); + expect(userInstance.friends[0].name).to.equal('John Smith'); + + const obj = await userInstance.update({ + friends: [{ + name: 'John Smythe' + }] + }); + + const friends = await obj['friends']; + expect(friends).to.have.length(1); + expect(friends[0].name).to.equal('John Smythe'); + await friends; }); - it('should be able to find a record while searching in an array', function() { - return this.User.bulkCreate([ + it('should be able to find a record while searching in an array', async function() { + await this.User.bulkCreate([ { username: 'bob', email: ['myemail@email.com'] }, { username: 'tony', email: ['wrongemail@email.com'] } - ]).then(() => { - return this.User.findAll({ where: { email: ['myemail@email.com'] } }).then(user => { - expect(user).to.be.instanceof(Array); - expect(user).to.have.length(1); - expect(user[0].username).to.equal('bob'); - }); - }); + ]); + + const user = await this.User.findAll({ where: { email: ['myemail@email.com'] } }); + expect(user).to.be.instanceof(Array); + expect(user).to.have.length(1); + expect(user[0].username).to.equal('bob'); }); describe('json', () => { - it('should be able to retrieve a row with ->> operator', function() { - return Sequelize.Promise.all([ + it('should be able to retrieve a row with ->> operator', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ where: sequelize.json("emergency_contact->>'name'", 'kate'), attributes: ['username', 'emergency_contact'] }); - }) - .then(user => { - expect(user.emergency_contact.name).to.equal('kate'); - }); + this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]); + + const user = await this.User.findOne({ where: sequelize.json("emergency_contact->>'name'", 'kate'), attributes: ['username', 'emergency_contact'] }); + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using the nested query language', function() { - return Sequelize.Promise.all([ + it('should be able to query using the nested query language', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ - where: sequelize.json({ emergency_contact: { name: 'kate' } }) - }); - }) - .then(user => { - expect(user.emergency_contact.name).to.equal('kate'); - }); + this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]); + + const user = await this.User.findOne({ + where: sequelize.json({ emergency_contact: { name: 'kate' } }) + }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using dot syntax', function() { - return Sequelize.Promise.all([ + it('should be able to query using dot syntax', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ where: sequelize.json('emergency_contact.name', 'joe') }); - }) - .then(user => { - expect(user.emergency_contact.name).to.equal('joe'); - }); + this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } })]); + + const user = await this.User.findOne({ where: sequelize.json('emergency_contact.name', 'joe') }); + expect(user.emergency_contact.name).to.equal('joe'); }); - it('should be able to query using dot syntax with uppercase name', function() { - return Sequelize.Promise.all([ + it('should be able to query using dot syntax with uppercase name', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), - this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })]) - .then(() => { - return this.User.findOne({ - attributes: [[sequelize.json('emergencyContact.name'), 'contactName']], - where: sequelize.json('emergencyContact.name', 'joe') - }); - }) - .then(user => { - expect(user.get('contactName')).to.equal('joe'); - }); + this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } })]); + + const user = await this.User.findOne({ + attributes: [[sequelize.json('emergencyContact.name'), 'contactName']], + where: sequelize.json('emergencyContact.name', 'joe') + }); + + expect(user.get('contactName')).to.equal('joe'); }); - it('should be able to store values that require JSON escaping', function() { + it('should be able to store values that require JSON escaping', async function() { const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values"; - return this.User.create({ username: 'swen', emergency_contact: { value: text } }) - .then(user => { - expect(user.isNewRecord).to.equal(false); - }) - .then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }) - .then(() => { - return this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); - }) - .then(user => { - expect(user.username).to.equal('swen'); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: { value: text } }); + expect(user0.isNewRecord).to.equal(false); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); - it('should be able to findOrCreate with values that require JSON escaping', function() { + it('should be able to findOrCreate with values that require JSON escaping', async function() { const text = "Multi-line '$string' needing \"escaping\" for $$ and $1 type values"; - return this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } }) - .then(user => { - expect(!user.isNewRecord).to.equal(true); - }) - .then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }) - .then(() => { - return this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); - }) - .then(user => { - expect(user.username).to.equal('swen'); - }); + const user0 = await this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } }); + expect(!user0.isNewRecord).to.equal(true); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); }); describe('hstore', () => { - it('should tell me that a column is hstore and not USER-DEFINED', function() { - return this.sequelize.queryInterface.describeTable('Users').then(table => { - expect(table.settings.type).to.equal('HSTORE'); - expect(table.document.type).to.equal('HSTORE'); - }); + it('should tell me that a column is hstore and not USER-DEFINED', async function() { + const table = await this.sequelize.queryInterface.describeTable('Users'); + expect(table.settings.type).to.equal('HSTORE'); + expect(table.document.type).to.equal('HSTORE'); }); - it('should NOT stringify hstore with insert', function() { - return this.User.create({ + it('should NOT stringify hstore with insert', async function() { + await this.User.create({ username: 'bob', email: ['myemail@email.com'], settings: { mailing: false, push: 'facebook', frequency: 3 } @@ -203,7 +175,7 @@ if (dialect.match(/^postgres/)) { }); }); - it('should not rename hstore fields', function() { + it('should not rename hstore fields', async function() { const Equipment = this.sequelize.define('Equipment', { grapplingHook: { type: DataTypes.STRING, @@ -214,21 +186,21 @@ if (dialect.match(/^postgres/)) { } }); - return Equipment.sync({ force: true }).then(() => { - return Equipment.findAll({ - where: { - utilityBelt: { - grapplingHook: true - } - }, - logging(sql) { - expect(sql).to.contains(' WHERE "Equipment"."utilityBelt" = \'"grapplingHook"=>"true"\';'); + await Equipment.sync({ force: true }); + + await Equipment.findAll({ + where: { + utilityBelt: { + grapplingHook: true } - }); + }, + logging(sql) { + expect(sql).to.contains(' WHERE "Equipment"."utilityBelt" = \'"grapplingHook"=>"true"\';'); + } }); }); - it('should not rename json fields', function() { + it('should not rename json fields', async function() { const Equipment = this.sequelize.define('Equipment', { grapplingHook: { type: DataTypes.STRING, @@ -239,62 +211,61 @@ if (dialect.match(/^postgres/)) { } }); - return Equipment.sync({ force: true }).then(() => { - return Equipment.findAll({ - where: { - utilityBelt: { - grapplingHook: true - } - }, - logging(sql) { - expect(sql).to.contains(' WHERE CAST(("Equipment"."utilityBelt"#>>\'{grapplingHook}\') AS BOOLEAN) = true;'); + await Equipment.sync({ force: true }); + + await Equipment.findAll({ + where: { + utilityBelt: { + grapplingHook: true } - }); + }, + logging(sql) { + expect(sql).to.contains(' WHERE CAST(("Equipment"."utilityBelt"#>>\'{grapplingHook}\') AS BOOLEAN) = true;'); + } }); }); }); describe('range', () => { - it('should tell me that a column is range and not USER-DEFINED', function() { - return this.sequelize.queryInterface.describeTable('Users').then(table => { - expect(table.course_period.type).to.equal('TSTZRANGE'); - expect(table.available_amount.type).to.equal('INT4RANGE'); - }); + it('should tell me that a column is range and not USER-DEFINED', async function() { + const table = await this.sequelize.queryInterface.describeTable('Users'); + expect(table.course_period.type).to.equal('TSTZRANGE'); + expect(table.available_amount.type).to.equal('INT4RANGE'); }); }); describe('enums', () => { - it('should be able to create enums with escape values', function() { + it('should be able to create enums with escape values', async function() { const User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', '1970\'s') }); - return User.sync({ force: true }); + await User.sync({ force: true }); }); - it('should be able to ignore enum types that already exist', function() { + it('should be able to ignore enum types that already exist', async function() { const User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', 'meh') }); - return User.sync({ force: true }).then(() => { - return User.sync(); - }); + await User.sync({ force: true }); + + await User.sync(); }); - it('should be able to create/drop enums multiple times', function() { + it('should be able to create/drop enums multiple times', async function() { const User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', 'meh') }); - return User.sync({ force: true }).then(() => { - return User.sync({ force: true }); - }); + await User.sync({ force: true }); + + await User.sync({ force: true }); }); - it('should be able to create/drop multiple enums multiple times', function() { + it('should be able to create/drop multiple enums multiple times', async function() { const DummyModel = this.sequelize.define('Dummy-pg', { username: DataTypes.STRING, theEnumOne: { @@ -315,16 +286,14 @@ if (dialect.match(/^postgres/)) { } }); - return DummyModel.sync({ force: true }).then(() => { - // now sync one more time: - return DummyModel.sync({ force: true }).then(() => { - // sync without dropping - return DummyModel.sync(); - }); - }); + await DummyModel.sync({ force: true }); + // now sync one more time: + await DummyModel.sync({ force: true }); + // sync without dropping + await DummyModel.sync(); }); - it('should be able to create/drop multiple enums multiple times with field name (#7812)', function() { + it('should be able to create/drop multiple enums multiple times with field name (#7812)', async function() { const DummyModel = this.sequelize.define('Dummy-pg', { username: DataTypes.STRING, theEnumOne: { @@ -347,55 +316,47 @@ if (dialect.match(/^postgres/)) { } }); - return DummyModel.sync({ force: true }).then(() => { - // now sync one more time: - return DummyModel.sync({ force: true }).then(() => { - // sync without dropping - return DummyModel.sync(); - }); - }); + await DummyModel.sync({ force: true }); + // now sync one more time: + await DummyModel.sync({ force: true }); + // sync without dropping + await DummyModel.sync(); }); - it('should be able to add values to enum types', function() { + it('should be able to add values to enum types', async function() { let User = this.sequelize.define('UserEnums', { mood: DataTypes.ENUM('happy', 'sad', 'meh') }); - return User.sync({ force: true }).then(() => { - User = this.sequelize.define('UserEnums', { - mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful') - }); - - return User.sync(); - }).then(() => { - return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); - }).then(enums => { - expect(enums).to.have.length(1); - expect(enums[0].enum_value).to.equal('{neutral,happy,sad,ecstatic,meh,joyful}'); + await User.sync({ force: true }); + User = this.sequelize.define('UserEnums', { + mood: DataTypes.ENUM('neutral', 'happy', 'sad', 'ecstatic', 'meh', 'joyful') }); + + await User.sync(); + const enums = await this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); + expect(enums).to.have.length(1); + expect(enums[0].enum_value).to.equal('{neutral,happy,sad,ecstatic,meh,joyful}'); }); - it('should be able to add multiple values with different order', function() { + it('should be able to add multiple values with different order', async function() { let User = this.sequelize.define('UserEnums', { priority: DataTypes.ENUM('1', '2', '6') }); - return User.sync({ force: true }).then(() => { - User = this.sequelize.define('UserEnums', { - priority: DataTypes.ENUM('0', '1', '2', '3', '4', '5', '6', '7') - }); - - return User.sync(); - }).then(() => { - return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); - }).then(enums => { - expect(enums).to.have.length(1); - expect(enums[0].enum_value).to.equal('{0,1,2,3,4,5,6,7}'); + await User.sync({ force: true }); + User = this.sequelize.define('UserEnums', { + priority: DataTypes.ENUM('0', '1', '2', '3', '4', '5', '6', '7') }); + + await User.sync(); + const enums = await this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); + expect(enums).to.have.length(1); + expect(enums[0].enum_value).to.equal('{0,1,2,3,4,5,6,7}'); }); describe('ARRAY(ENUM)', () => { - it('should be able to ignore enum types that already exist', function() { + it('should be able to ignore enum types that already exist', async function() { const User = this.sequelize.define('UserEnums', { permissions: DataTypes.ARRAY(DataTypes.ENUM([ 'access', @@ -405,10 +366,12 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }).then(() => User.sync()); + await User.sync({ force: true }); + + await User.sync(); }); - it('should be able to create/drop enums multiple times', function() { + it('should be able to create/drop enums multiple times', async function() { const User = this.sequelize.define('UserEnums', { permissions: DataTypes.ARRAY(DataTypes.ENUM([ 'access', @@ -418,10 +381,12 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }).then(() => User.sync({ force: true })); + await User.sync({ force: true }); + + await User.sync({ force: true }); }); - it('should be able to add values to enum types', function() { + it('should be able to add values to enum types', async function() { let User = this.sequelize.define('UserEnums', { permissions: DataTypes.ARRAY(DataTypes.ENUM([ 'access', @@ -431,23 +396,20 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }).then(() => { - User = this.sequelize.define('UserEnums', { - permissions: DataTypes.ARRAY( - DataTypes.ENUM('view', 'access', 'edit', 'write', 'check', 'delete') - ) - }); - - return User.sync(); - }).then(() => { - return this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); - }).then(enums => { - expect(enums).to.have.length(1); - expect(enums[0].enum_value).to.equal('{view,access,edit,write,check,delete}'); + await User.sync({ force: true }); + User = this.sequelize.define('UserEnums', { + permissions: DataTypes.ARRAY( + DataTypes.ENUM('view', 'access', 'edit', 'write', 'check', 'delete') + ) }); + + await User.sync(); + const enums = await this.sequelize.getQueryInterface().pgListEnums(User.getTableName()); + expect(enums).to.have.length(1); + expect(enums[0].enum_value).to.equal('{view,access,edit,write,check,delete}'); }); - it('should be able to insert new record', function() { + it('should be able to insert new record', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, type: DataTypes.ENUM('A', 'B', 'C'), @@ -460,24 +422,50 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }) - .then(() => { - return User.create({ - name: 'file.exe', - type: 'C', - owners: ['userA', 'userB'], - permissions: ['access', 'write'] - }); - }) - .then(user => { - expect(user.name).to.equal('file.exe'); - expect(user.type).to.equal('C'); - expect(user.owners).to.deep.equal(['userA', 'userB']); - expect(user.permissions).to.deep.equal(['access', 'write']); - }); + await User.sync({ force: true }); + + const user = await User.create({ + name: 'file.exe', + type: 'C', + owners: ['userA', 'userB'], + permissions: ['access', 'write'] + }); + + expect(user.name).to.equal('file.exe'); + expect(user.type).to.equal('C'); + expect(user.owners).to.deep.equal(['userA', 'userB']); + expect(user.permissions).to.deep.equal(['access', 'write']); }); - it('should fail when trying to insert foreign element on ARRAY(ENUM)', function() { + it('should be able to insert a new record even with a redefined field name', async function() { + const User = this.sequelize.define('UserEnums', { + name: DataTypes.STRING, + type: DataTypes.ENUM('A', 'B', 'C'), + owners: DataTypes.ARRAY(DataTypes.STRING), + specialPermissions: { + type: DataTypes.ARRAY(DataTypes.ENUM([ + 'access', + 'write', + 'check', + 'delete' + ])), + field: 'special_permissions' + } + }); + + await User.sync({ force: true }); + + const user = await User.bulkCreate([{ + name: 'file.exe', + type: 'C', + owners: ['userA', 'userB'], + specialPermissions: ['access', 'write'] + }]); + + expect(user.length).to.equal(1); + }); + + it('should fail when trying to insert foreign element on ARRAY(ENUM)', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, type: DataTypes.ENUM('A', 'B', 'C'), @@ -490,7 +478,7 @@ if (dialect.match(/^postgres/)) { ])) }); - return expect(User.sync({ force: true }).then(() => { + await expect(User.sync({ force: true }).then(() => { return User.create({ name: 'file.exe', type: 'C', @@ -500,7 +488,7 @@ if (dialect.match(/^postgres/)) { })).to.be.rejectedWith(/invalid input value for enum "enum_UserEnums_permissions": "cosmic_ray_disk_access"/); }); - it('should be able to find records', function() { + it('should be able to find records', async function() { const User = this.sequelize.define('UserEnums', { name: DataTypes.STRING, type: DataTypes.ENUM('A', 'B', 'C'), @@ -512,120 +500,109 @@ if (dialect.match(/^postgres/)) { ])) }); - return User.sync({ force: true }) - .then(() => { - return User.bulkCreate([{ - name: 'file1.exe', - type: 'C', - permissions: ['access', 'write'] - }, { - name: 'file2.exe', - type: 'A', - permissions: ['access', 'check'] - }, { - name: 'file3.exe', - type: 'B', - permissions: ['access', 'write', 'delete'] - }]); - }) - .then(() => { - return User.findAll({ - where: { - type: { - [Op.in]: ['A', 'C'] - }, - permissions: { - [Op.contains]: ['write'] - } - } - }); - }) - .then(users => { - expect(users.length).to.equal(1); - expect(users[0].name).to.equal('file1.exe'); - expect(users[0].type).to.equal('C'); - expect(users[0].permissions).to.deep.equal(['access', 'write']); - }); + await User.sync({ force: true }); + + await User.bulkCreate([{ + name: 'file1.exe', + type: 'C', + permissions: ['access', 'write'] + }, { + name: 'file2.exe', + type: 'A', + permissions: ['access', 'check'] + }, { + name: 'file3.exe', + type: 'B', + permissions: ['access', 'write', 'delete'] + }]); + + const users = await User.findAll({ + where: { + type: { + [Op.in]: ['A', 'C'] + }, + permissions: { + [Op.contains]: ['write'] + } + } + }); + + expect(users.length).to.equal(1); + expect(users[0].name).to.equal('file1.exe'); + expect(users[0].type).to.equal('C'); + expect(users[0].permissions).to.deep.equal(['access', 'write']); }); }); }); describe('integers', () => { describe('integer', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.INTEGER }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: 2147483647 }).then(user => { - expect(user.aNumber).to.equal(2147483647); - return User.findOne({ where: { aNumber: 2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(2147483647); - }); - }); + const user = await User.create({ aNumber: 2147483647 }); + expect(user.aNumber).to.equal(2147483647); + const _user = await User.findOne({ where: { aNumber: 2147483647 } }); + expect(_user.aNumber).to.equal(2147483647); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: -2147483647 }).then(user => { - expect(user.aNumber).to.equal(-2147483647); - return User.findOne({ where: { aNumber: -2147483647 } }).then(_user => { - expect(_user.aNumber).to.equal(-2147483647); - }); - }); + const user = await User.create({ aNumber: -2147483647 }); + expect(user.aNumber).to.equal(-2147483647); + const _user = await User.findOne({ where: { aNumber: -2147483647 } }); + expect(_user.aNumber).to.equal(-2147483647); }); }); describe('bigint', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: DataTypes.BIGINT }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('positive', function() { + it('positive', async function() { const User = this.User; - return User.create({ aNumber: '9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('9223372036854775807'); - return User.findOne({ where: { aNumber: '9223372036854775807' } }).then(_user => { - expect(_user.aNumber).to.equal('9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '9223372036854775807' }); + expect(user.aNumber).to.equal('9223372036854775807'); + const _user = await User.findOne({ where: { aNumber: '9223372036854775807' } }); + expect(_user.aNumber).to.equal('9223372036854775807'); }); - it('negative', function() { + it('negative', async function() { const User = this.User; - return User.create({ aNumber: '-9223372036854775807' }).then(user => { - expect(user.aNumber).to.equal('-9223372036854775807'); - return User.findOne({ where: { aNumber: '-9223372036854775807' } }).then(_user => { - expect(_user.aNumber).to.equal('-9223372036854775807'); - }); - }); + const user = await User.create({ aNumber: '-9223372036854775807' }); + expect(user.aNumber).to.equal('-9223372036854775807'); + const _user = await User.findOne({ where: { aNumber: '-9223372036854775807' } }); + expect(_user.aNumber).to.equal('-9223372036854775807'); }); }); }); describe('timestamps', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { dates: DataTypes.ARRAY(DataTypes.DATE) }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should use bind params instead of "TIMESTAMP WITH TIME ZONE"', function() { - return this.User.create({ + it('should use bind params instead of "TIMESTAMP WITH TIME ZONE"', async function() { + await this.User.create({ dates: [] }, { logging(sql) { @@ -637,127 +614,105 @@ if (dialect.match(/^postgres/)) { }); describe('model', () => { - it('create handles array correctly', function() { - return this.User - .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }) - .then(oldUser => { - expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com']); - }); + it('create handles array correctly', async function() { + const oldUser = await this.User + .create({ username: 'user', email: ['foo@bar.com', 'bar@baz.com'] }); + + expect(oldUser.email).to.contain.members(['foo@bar.com', 'bar@baz.com']); }); - it('should save hstore correctly', function() { - return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' } }).then(newUser => { - // Check to see if the default value for an hstore field works - expect(newUser.document).to.deep.equal({ default: "'value'" }); - expect(newUser.settings).to.deep.equal({ created: '"value"' }); + it('should save hstore correctly', async function() { + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { created: '"value"' } }); + // Check to see if the default value for an hstore field works + expect(newUser.document).to.deep.equal({ default: "'value'" }); + expect(newUser.settings).to.deep.equal({ created: '"value"' }); - // Check to see if updating an hstore field works - return newUser.update({ settings: { should: 'update', to: 'this', first: 'place' } }).then(oldUser => { - // Postgres always returns keys in alphabetical order (ascending) - expect(oldUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); - }); - }); + // Check to see if updating an hstore field works + const oldUser = await newUser.update({ settings: { should: 'update', to: 'this', first: 'place' } }); + // Postgres always returns keys in alphabetical order (ascending) + expect(oldUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); }); - it('should save hstore array correctly', function() { + it('should save hstore array correctly', async function() { const User = this.User; - return this.User.create({ + await this.User.create({ username: 'bob', email: ['myemail@email.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }, { number: '8675309', type: "Jenny's" }, { number: '5555554321', type: '"home\n"' }] - }).then(() => { - return User.findByPk(1).then(user => { - expect(user.phones.length).to.equal(4); - expect(user.phones[1].number).to.equal('987654321'); - expect(user.phones[2].type).to.equal("Jenny's"); - expect(user.phones[3].type).to.equal('"home\n"'); - }); }); + + const user = await User.findByPk(1); + expect(user.phones.length).to.equal(4); + expect(user.phones[1].number).to.equal('987654321'); + expect(user.phones[2].type).to.equal("Jenny's"); + expect(user.phones[3].type).to.equal('"home\n"'); }); - it('should bulkCreate with hstore property', function() { + it('should bulkCreate with hstore property', async function() { const User = this.User; - return this.User.bulkCreate([{ + await this.User.bulkCreate([{ username: 'bob', email: ['myemail@email.com'], settings: { mailing: true, push: 'facebook', frequency: 3 } - }]).then(() => { - return User.findByPk(1).then(user => { - expect(user.settings.mailing).to.equal('true'); - }); - }); + }]); + + const user = await User.findByPk(1); + expect(user.settings.mailing).to.equal('true'); }); - it('should update hstore correctly', function() { - return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }).then(newUser => { - // Check to see if the default value for an hstore field works - expect(newUser.document).to.deep.equal({ default: "'value'" }); - expect(newUser.settings).to.deep.equal({ test: '"value"' }); - - // Check to see if updating an hstore field works - return this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: newUser.where() }).then(() => { - return newUser.reload().then(() => { - // Postgres always returns keys in alphabetical order (ascending) - expect(newUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); - }); - }); - }); + it('should update hstore correctly', async function() { + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }); + // Check to see if the default value for an hstore field works + expect(newUser.document).to.deep.equal({ default: "'value'" }); + expect(newUser.settings).to.deep.equal({ test: '"value"' }); + + // Check to see if updating an hstore field works + await this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: newUser.where() }); + await newUser.reload(); + // Postgres always returns keys in alphabetical order (ascending) + expect(newUser.settings).to.deep.equal({ first: 'place', should: 'update', to: 'this' }); }); - it('should update hstore correctly and return the affected rows', function() { - return this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }).then(oldUser => { - // Update the user and check that the returned object's fields have been parsed by the hstore library - return this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: oldUser.where(), returning: true }).then(([count, users]) => { - expect(count).to.equal(1); - expect(users[0].settings).to.deep.equal({ should: 'update', to: 'this', first: 'place' }); - }); - }); + it('should update hstore correctly and return the affected rows', async function() { + const oldUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }); + // Update the user and check that the returned object's fields have been parsed by the hstore library + const [count, users] = await this.User.update({ settings: { should: 'update', to: 'this', first: 'place' } }, { where: oldUser.where(), returning: true }); + expect(count).to.equal(1); + expect(users[0].settings).to.deep.equal({ should: 'update', to: 'this', first: 'place' }); }); - it('should read hstore correctly', function() { + it('should read hstore correctly', async function() { const data = { username: 'user', email: ['foo@bar.com'], settings: { test: '"value"' } }; - return this.User.create(data) - .then(() => { - return this.User.findOne({ where: { username: 'user' } }); - }) - .then(user => { - // Check that the hstore fields are the same when retrieving the user - expect(user.settings).to.deep.equal(data.settings); - }); + await this.User.create(data); + const user = await this.User.findOne({ where: { username: 'user' } }); + // Check that the hstore fields are the same when retrieving the user + expect(user.settings).to.deep.equal(data.settings); }); - it('should read an hstore array correctly', function() { + it('should read an hstore array correctly', async function() { const data = { username: 'user', email: ['foo@bar.com'], phones: [{ number: '123456789', type: 'mobile' }, { number: '987654321', type: 'landline' }] }; - return this.User.create(data) - .then(() => { - // Check that the hstore fields are the same when retrieving the user - return this.User.findOne({ where: { username: 'user' } }); - }).then(user => { - expect(user.phones).to.deep.equal(data.phones); - }); + await this.User.create(data); + // Check that the hstore fields are the same when retrieving the user + const user = await this.User.findOne({ where: { username: 'user' } }); + expect(user.phones).to.deep.equal(data.phones); }); - it('should read hstore correctly from multiple rows', function() { - return this.User - .create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' } }) - .then(() => { - return this.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another: '"example"' } }); - }) - .then(() => { - // Check that the hstore fields are the same when retrieving the user - return this.User.findAll({ order: ['username'] }); - }) - .then(users => { - expect(users[0].settings).to.deep.equal({ test: '"value"' }); - expect(users[1].settings).to.deep.equal({ another: '"example"' }); - }); + it('should read hstore correctly from multiple rows', async function() { + await this.User + .create({ username: 'user1', email: ['foo@bar.com'], settings: { test: '"value"' } }); + + await this.User.create({ username: 'user2', email: ['foo2@bar.com'], settings: { another: '"example"' } }); + // Check that the hstore fields are the same when retrieving the user + const users = await this.User.findAll({ order: ['username'] }); + expect(users[0].settings).to.deep.equal({ test: '"value"' }); + expect(users[1].settings).to.deep.equal({ another: '"example"' }); }); - it('should read hstore correctly from included models as well', function() { + it('should read hstore correctly from included models as well', async function() { const HstoreSubmodel = this.sequelize.define('hstoreSubmodel', { someValue: DataTypes.HSTORE }); @@ -765,179 +720,156 @@ if (dialect.match(/^postgres/)) { this.User.hasMany(HstoreSubmodel); - return this.sequelize - .sync({ force: true }) - .then(() => { - return this.User.create({ username: 'user1' }) - .then(user => { - return HstoreSubmodel.create({ someValue: submodelValue }) - .then(submodel => { - return user.setHstoreSubmodels([submodel]); - }); - }); - }) - .then(() => { - return this.User.findOne({ where: { username: 'user1' }, include: [HstoreSubmodel] }); - }) - .then(user => { - expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok; - expect(user.hstoreSubmodels.length).to.equal(1); - expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue); - }); + await this.sequelize + .sync({ force: true }); + + const user0 = await this.User.create({ username: 'user1' }); + const submodel = await HstoreSubmodel.create({ someValue: submodelValue }); + await user0.setHstoreSubmodels([submodel]); + const user = await this.User.findOne({ where: { username: 'user1' }, include: [HstoreSubmodel] }); + expect(user.hasOwnProperty('hstoreSubmodels')).to.be.ok; + expect(user.hstoreSubmodels.length).to.equal(1); + expect(user.hstoreSubmodels[0].someValue).to.deep.equal(submodelValue); }); - it('should save range correctly', function() { + it('should save range correctly', async function() { const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - return this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(newUser => { - // Check to see if the default value for a range field works - - expect(newUser.acceptable_marks.length).to.equal(2); - expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound - expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound - expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive - expect(newUser.course_period[0].value instanceof Date).to.be.ok; // lower bound - expect(newUser.course_period[1].value instanceof Date).to.be.ok; // upper bound - expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound - expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound - expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive - - // Check to see if updating a range field works - return newUser.update({ acceptable_marks: [0.8, 0.9] }) - .then(() => newUser.reload()) // Ensure the acceptable_marks array is loaded with the complete range definition - .then(() => { - expect(newUser.acceptable_marks.length).to.equal(2); - expect(newUser.acceptable_marks[0].value).to.equal('0.8'); // lower bound - expect(newUser.acceptable_marks[1].value).to.equal('0.9'); // upper bound - }); - }); + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }); + // Check to see if the default value for a range field works + + expect(newUser.acceptable_marks.length).to.equal(2); + expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound + expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound + expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive + expect(newUser.course_period[0].value instanceof Date).to.be.ok; // lower bound + expect(newUser.course_period[1].value instanceof Date).to.be.ok; // upper bound + expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound + expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound + expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive + + // Check to see if updating a range field works + await newUser.update({ acceptable_marks: [0.8, 0.9] }); + await newUser.reload(); // Ensure the acceptable_marks array is loaded with the complete range definition + expect(newUser.acceptable_marks.length).to.equal(2); + expect(newUser.acceptable_marks[0].value).to.equal('0.8'); // lower bound + expect(newUser.acceptable_marks[1].value).to.equal('0.9'); // upper bound }); - it('should save range array correctly', function() { + it('should save range array correctly', async function() { const User = this.User; const holidays = [ [new Date(2015, 3, 1), new Date(2015, 3, 15)], [new Date(2015, 8, 1), new Date(2015, 9, 15)] ]; - return User.create({ + await User.create({ username: 'bob', email: ['myemail@email.com'], holidays - }).then(() => { - return User.findByPk(1).then(user => { - expect(user.holidays.length).to.equal(2); - expect(user.holidays[0].length).to.equal(2); - expect(user.holidays[0][0].value instanceof Date).to.be.ok; - expect(user.holidays[0][1].value instanceof Date).to.be.ok; - expect(user.holidays[0][0].value).to.equalTime(holidays[0][0]); - expect(user.holidays[0][1].value).to.equalTime(holidays[0][1]); - expect(user.holidays[1].length).to.equal(2); - expect(user.holidays[1][0].value instanceof Date).to.be.ok; - expect(user.holidays[1][1].value instanceof Date).to.be.ok; - expect(user.holidays[1][0].value).to.equalTime(holidays[1][0]); - expect(user.holidays[1][1].value).to.equalTime(holidays[1][1]); - }); }); + + const user = await User.findByPk(1); + expect(user.holidays.length).to.equal(2); + expect(user.holidays[0].length).to.equal(2); + expect(user.holidays[0][0].value instanceof Date).to.be.ok; + expect(user.holidays[0][1].value instanceof Date).to.be.ok; + expect(user.holidays[0][0].value).to.equalTime(holidays[0][0]); + expect(user.holidays[0][1].value).to.equalTime(holidays[0][1]); + expect(user.holidays[1].length).to.equal(2); + expect(user.holidays[1][0].value instanceof Date).to.be.ok; + expect(user.holidays[1][1].value instanceof Date).to.be.ok; + expect(user.holidays[1][0].value).to.equalTime(holidays[1][0]); + expect(user.holidays[1][1].value).to.equalTime(holidays[1][1]); }); - it('should bulkCreate with range property', function() { + it('should bulkCreate with range property', async function() { const User = this.User; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - return User.bulkCreate([{ + await User.bulkCreate([{ username: 'bob', email: ['myemail@email.com'], course_period: period - }]).then(() => { - return User.findByPk(1).then(user => { - expect(user.course_period[0].value instanceof Date).to.be.ok; - expect(user.course_period[1].value instanceof Date).to.be.ok; - expect(user.course_period[0].value).to.equalTime(period[0]); // lower bound - expect(user.course_period[1].value).to.equalTime(period[1]); // upper bound - expect(user.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(user.course_period[1].inclusive).to.deep.equal(false); // exclusive - }); - }); + }]); + + const user = await User.findByPk(1); + expect(user.course_period[0].value instanceof Date).to.be.ok; + expect(user.course_period[1].value instanceof Date).to.be.ok; + expect(user.course_period[0].value).to.equalTime(period[0]); // lower bound + expect(user.course_period[1].value).to.equalTime(period[1]); // upper bound + expect(user.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(user.course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should update range correctly', function() { + it('should update range correctly', async function() { const User = this.User; const period = [new Date(2015, 0, 1), new Date(2015, 11, 31)]; - return User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }).then(newUser => { - // Check to see if the default value for a range field works - expect(newUser.acceptable_marks.length).to.equal(2); - expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound - expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound - expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive - expect(newUser.course_period[0].value instanceof Date).to.be.ok; - expect(newUser.course_period[1].value instanceof Date).to.be.ok; - expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound - expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound - expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive - - - const period2 = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; - - // Check to see if updating a range field works - return User.update({ course_period: period2 }, { where: newUser.where() }).then(() => { - return newUser.reload().then(() => { - expect(newUser.course_period[0].value instanceof Date).to.be.ok; - expect(newUser.course_period[1].value instanceof Date).to.be.ok; - expect(newUser.course_period[0].value).to.equalTime(period2[0]); // lower bound - expect(newUser.course_period[1].value).to.equalTime(period2[1]); // upper bound - expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive - }); - }); - }); + const newUser = await User.create({ username: 'user', email: ['foo@bar.com'], course_period: period }); + // Check to see if the default value for a range field works + expect(newUser.acceptable_marks.length).to.equal(2); + expect(newUser.acceptable_marks[0].value).to.equal('0.65'); // lower bound + expect(newUser.acceptable_marks[1].value).to.equal('1'); // upper bound + expect(newUser.acceptable_marks[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.acceptable_marks[1].inclusive).to.deep.equal(false); // exclusive + expect(newUser.course_period[0].value instanceof Date).to.be.ok; + expect(newUser.course_period[1].value instanceof Date).to.be.ok; + expect(newUser.course_period[0].value).to.equalTime(period[0]); // lower bound + expect(newUser.course_period[1].value).to.equalTime(period[1]); // upper bound + expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive + + + const period2 = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; + + // Check to see if updating a range field works + await User.update({ course_period: period2 }, { where: newUser.where() }); + await newUser.reload(); + expect(newUser.course_period[0].value instanceof Date).to.be.ok; + expect(newUser.course_period[1].value instanceof Date).to.be.ok; + expect(newUser.course_period[0].value).to.equalTime(period2[0]); // lower bound + expect(newUser.course_period[1].value).to.equalTime(period2[1]); // upper bound + expect(newUser.course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(newUser.course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should update range correctly and return the affected rows', function() { + it('should update range correctly and return the affected rows', async function() { const User = this.User; const period = [new Date(2015, 1, 1), new Date(2015, 10, 30)]; - return User.create({ + const oldUser = await User.create({ username: 'user', email: ['foo@bar.com'], course_period: [new Date(2015, 0, 1), new Date(2015, 11, 31)] - }).then(oldUser => { - // Update the user and check that the returned object's fields have been parsed by the range parser - return User.update({ course_period: period }, { where: oldUser.where(), returning: true }) - .then(([count, users]) => { - expect(count).to.equal(1); - expect(users[0].course_period[0].value instanceof Date).to.be.ok; - expect(users[0].course_period[1].value instanceof Date).to.be.ok; - expect(users[0].course_period[0].value).to.equalTime(period[0]); // lower bound - expect(users[0].course_period[1].value).to.equalTime(period[1]); // upper bound - expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive - }); }); + + // Update the user and check that the returned object's fields have been parsed by the range parser + const [count, users] = await User.update({ course_period: period }, { where: oldUser.where(), returning: true }); + expect(count).to.equal(1); + expect(users[0].course_period[0].value instanceof Date).to.be.ok; + expect(users[0].course_period[1].value instanceof Date).to.be.ok; + expect(users[0].course_period[0].value).to.equalTime(period[0]); // lower bound + expect(users[0].course_period[1].value).to.equalTime(period[1]); // upper bound + expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should read range correctly', function() { + it('should read range correctly', async function() { const User = this.User; const course_period = [{ value: new Date(2015, 1, 1), inclusive: false }, { value: new Date(2015, 10, 30), inclusive: false }]; const data = { username: 'user', email: ['foo@bar.com'], course_period }; - return User.create(data) - .then(() => { - return User.findOne({ where: { username: 'user' } }); - }) - .then(user => { - // Check that the range fields are the same when retrieving the user - expect(user.course_period).to.deep.equal(data.course_period); - }); + await User.create(data); + const user = await User.findOne({ where: { username: 'user' } }); + // Check that the range fields are the same when retrieving the user + expect(user.course_period).to.deep.equal(data.course_period); }); - it('should read range array correctly', function() { + it('should read range array correctly', async function() { const User = this.User; const holidays = [ [{ value: new Date(2015, 3, 1, 10), inclusive: true }, { value: new Date(2015, 3, 15), inclusive: true }], @@ -945,44 +877,36 @@ if (dialect.match(/^postgres/)) { ]; const data = { username: 'user', email: ['foo@bar.com'], holidays }; - return User.create(data) - .then(() => { - // Check that the range fields are the same when retrieving the user - return User.findOne({ where: { username: 'user' } }); - }).then(user => { - expect(user.holidays).to.deep.equal(data.holidays); - }); + await User.create(data); + // Check that the range fields are the same when retrieving the user + const user = await User.findOne({ where: { username: 'user' } }); + expect(user.holidays).to.deep.equal(data.holidays); }); - it('should read range correctly from multiple rows', function() { + it('should read range correctly from multiple rows', async function() { const User = this.User; const periods = [ [new Date(2015, 0, 1), new Date(2015, 11, 31)], [new Date(2016, 0, 1), new Date(2016, 11, 31)] ]; - return User - .create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0] }) - .then(() => { - return User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1] }); - }) - .then(() => { - // Check that the range fields are the same when retrieving the user - return User.findAll({ order: ['username'] }); - }) - .then(users => { - expect(users[0].course_period[0].value).to.equalTime(periods[0][0]); // lower bound - expect(users[0].course_period[1].value).to.equalTime(periods[0][1]); // upper bound - expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive - expect(users[1].course_period[0].value).to.equalTime(periods[1][0]); // lower bound - expect(users[1].course_period[1].value).to.equalTime(periods[1][1]); // upper bound - expect(users[1].course_period[0].inclusive).to.deep.equal(true); // inclusive - expect(users[1].course_period[1].inclusive).to.deep.equal(false); // exclusive - }); + await User + .create({ username: 'user1', email: ['foo@bar.com'], course_period: periods[0] }); + + await User.create({ username: 'user2', email: ['foo2@bar.com'], course_period: periods[1] }); + // Check that the range fields are the same when retrieving the user + const users = await User.findAll({ order: ['username'] }); + expect(users[0].course_period[0].value).to.equalTime(periods[0][0]); // lower bound + expect(users[0].course_period[1].value).to.equalTime(periods[0][1]); // upper bound + expect(users[0].course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(users[0].course_period[1].inclusive).to.deep.equal(false); // exclusive + expect(users[1].course_period[0].value).to.equalTime(periods[1][0]); // lower bound + expect(users[1].course_period[1].value).to.equalTime(periods[1][1]); // upper bound + expect(users[1].course_period[0].inclusive).to.deep.equal(true); // inclusive + expect(users[1].course_period[1].inclusive).to.deep.equal(false); // exclusive }); - it('should read range correctly from included models as well', function() { + it('should read range correctly from included models as well', async function() { const period = [new Date(2016, 0, 1), new Date(2016, 11, 31)]; const HolidayDate = this.sequelize.define('holidayDate', { period: DataTypes.RANGE(DataTypes.DATE) @@ -990,64 +914,51 @@ if (dialect.match(/^postgres/)) { this.User.hasMany(HolidayDate); - return this.sequelize - .sync({ force: true }) - .then(() => { - return this.User - .create({ username: 'user', email: ['foo@bar.com'] }) - .then(user => { - return HolidayDate.create({ period }) - .then(holidayDate => { - return user.setHolidayDates([holidayDate]); - }); - }); - }) - .then(() => { - return this.User.findOne({ where: { username: 'user' }, include: [HolidayDate] }); - }) - .then(user => { - expect(user.hasOwnProperty('holidayDates')).to.be.ok; - expect(user.holidayDates.length).to.equal(1); - expect(user.holidayDates[0].period.length).to.equal(2); - expect(user.holidayDates[0].period[0].value).to.equalTime(period[0]); - expect(user.holidayDates[0].period[1].value).to.equalTime(period[1]); - }); + await this.sequelize + .sync({ force: true }); + + const user0 = await this.User + .create({ username: 'user', email: ['foo@bar.com'] }); + + const holidayDate = await HolidayDate.create({ period }); + await user0.setHolidayDates([holidayDate]); + const user = await this.User.findOne({ where: { username: 'user' }, include: [HolidayDate] }); + expect(user.hasOwnProperty('holidayDates')).to.be.ok; + expect(user.holidayDates.length).to.equal(1); + expect(user.holidayDates[0].period.length).to.equal(2); + expect(user.holidayDates[0].period[0].value).to.equalTime(period[0]); + expect(user.holidayDates[0].period[1].value).to.equalTime(period[1]); }); }); - it('should save geometry correctly', function() { + it('should save geometry correctly', async function() { const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return this.User.create({ username: 'user', email: ['foo@bar.com'], location: point }).then(newUser => { - expect(newUser.location).to.deep.eql(point); - }); + const newUser = await this.User.create({ username: 'user', email: ['foo@bar.com'], location: point }); + expect(newUser.location).to.deep.eql(point); }); - it('should update geometry correctly', function() { + it('should update geometry correctly', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }; const point2 = { type: 'Point', coordinates: [39.828333, -77.232222] }; - return User.create({ username: 'user', email: ['foo@bar.com'], location: point1 }).then(oldUser => { - return User.update({ location: point2 }, { where: { username: oldUser.username }, returning: true }).then(([, updatedUsers]) => { - expect(updatedUsers[0].location).to.deep.eql(point2); - }); - }); + const oldUser = await User.create({ username: 'user', email: ['foo@bar.com'], location: point1 }); + const [, updatedUsers] = await User.update({ location: point2 }, { where: { username: oldUser.username }, returning: true }); + expect(updatedUsers[0].location).to.deep.eql(point2); }); - it('should read geometry correctly', function() { + it('should read geometry correctly', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create({ username: 'user', email: ['foo@bar.com'], location: point }).then(user => { - return User.findOne({ where: { username: user.username } }); - }).then(user => { - expect(user.location).to.deep.eql(point); - }); + const user0 = await User.create({ username: 'user', email: ['foo@bar.com'], location: point }); + const user = await User.findOne({ where: { username: user0.username } }); + expect(user.location).to.deep.eql(point); }); describe('[POSTGRES] Unquoted identifiers', () => { - it('can insert and select', function() { + it('can insert and select', async function() { this.sequelize.options.quoteIdentifiers = false; - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; this.User = this.sequelize.define('Userxs', { username: DataTypes.STRING, @@ -1056,44 +967,42 @@ if (dialect.match(/^postgres/)) { quoteIdentifiers: false }); - return this.User.sync({ force: true }).then(() => { - return this.User - .create({ username: 'user', fullName: 'John Smith' }) - .then(user => { - // We can insert into a table with non-quoted identifiers - expect(user.id).to.exist; - expect(user.id).not.to.be.null; - expect(user.username).to.equal('user'); - expect(user.fullName).to.equal('John Smith'); - - // We can query by non-quoted identifiers - return this.User.findOne({ - where: { fullName: 'John Smith' } - }).then(user2 => { - // We can map values back to non-quoted identifiers - expect(user2.id).to.equal(user.id); - expect(user2.username).to.equal('user'); - expect(user2.fullName).to.equal('John Smith'); - - // We can query and aggregate by non-quoted identifiers - return this.User - .count({ - where: { fullName: 'John Smith' } - }) - .then(count => { - this.sequelize.options.quoteIndentifiers = true; - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true; - this.sequelize.options.logging = false; - expect(count).to.equal(1); - }); - }); - }); + await this.User.sync({ force: true }); + + const user = await this.User + .create({ username: 'user', fullName: 'John Smith' }); + + // We can insert into a table with non-quoted identifiers + expect(user.id).to.exist; + expect(user.id).not.to.be.null; + expect(user.username).to.equal('user'); + expect(user.fullName).to.equal('John Smith'); + + // We can query by non-quoted identifiers + const user2 = await this.User.findOne({ + where: { fullName: 'John Smith' } }); + + // We can map values back to non-quoted identifiers + expect(user2.id).to.equal(user.id); + expect(user2.username).to.equal('user'); + expect(user2.fullName).to.equal('John Smith'); + + // We can query and aggregate by non-quoted identifiers + const count = await this.User + .count({ + where: { fullName: 'John Smith' } + }); + + this.sequelize.options.quoteIndentifiers = true; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; + this.sequelize.options.logging = false; + expect(count).to.equal(1); }); - it('can select nested include', function() { + it('can select nested include', async function() { this.sequelize.options.quoteIdentifiers = false; - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = false; + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = false; this.Professor = this.sequelize.define('Professor', { fullName: DataTypes.STRING }, { @@ -1118,115 +1027,105 @@ if (dialect.match(/^postgres/)) { this.Class.belongsTo(this.Professor); this.Class.belongsToMany(this.Student, { through: this.ClassStudent }); this.Student.belongsToMany(this.Class, { through: this.ClassStudent }); - return this.Professor.sync({ force: true }) - .then(() => { - return this.Student.sync({ force: true }); - }) - .then(() => { - return this.Class.sync({ force: true }); - }) - .then(() => { - return this.ClassStudent.sync({ force: true }); - }) - .then(() => { - return this.Professor.bulkCreate([ - { - id: 1, - fullName: 'Albus Dumbledore' - }, - { - id: 2, - fullName: 'Severus Snape' - } - ]); - }) - .then(() => { - return this.Class.bulkCreate([ - { - id: 1, - name: 'Transfiguration', - ProfessorId: 1 - }, - { - id: 2, - name: 'Potions', - ProfessorId: 2 - }, - { - id: 3, - name: 'Defence Against the Dark Arts', - ProfessorId: 2 - } - ]); - }) - .then(() => { - return this.Student.bulkCreate([ - { - id: 1, - fullName: 'Harry Potter' - }, - { - id: 2, - fullName: 'Ron Weasley' - }, - { - id: 3, - fullName: 'Ginny Weasley' - }, + + try { + await this.Professor.sync({ force: true }); + await this.Student.sync({ force: true }); + await this.Class.sync({ force: true }); + await this.ClassStudent.sync({ force: true }); + + await this.Professor.bulkCreate([ + { + id: 1, + fullName: 'Albus Dumbledore' + }, + { + id: 2, + fullName: 'Severus Snape' + } + ]); + + await this.Class.bulkCreate([ + { + id: 1, + name: 'Transfiguration', + ProfessorId: 1 + }, + { + id: 2, + name: 'Potions', + ProfessorId: 2 + }, + { + id: 3, + name: 'Defence Against the Dark Arts', + ProfessorId: 2 + } + ]); + + await this.Student.bulkCreate([ + { + id: 1, + fullName: 'Harry Potter' + }, + { + id: 2, + fullName: 'Ron Weasley' + }, + { + id: 3, + fullName: 'Ginny Weasley' + }, + { + id: 4, + fullName: 'Hermione Granger' + } + ]); + + await Promise.all([ + this.Student.findByPk(1) + .then(Harry => { + return Harry.setClasses([1, 2, 3]); + }), + this.Student.findByPk(2) + .then(Ron => { + return Ron.setClasses([1, 2]); + }), + this.Student.findByPk(3) + .then(Ginny => { + return Ginny.setClasses([2, 3]); + }), + this.Student.findByPk(4) + .then(Hermione => { + return Hermione.setClasses([1, 2, 3]); + }) + ]); + + const professors = await this.Professor.findAll({ + include: [ { - id: 4, - fullName: 'Hermione Granger' + model: this.Class, + include: [ + { + model: this.Student + } + ] } - ]); - }) - .then(() => { - return Promise.all([ - this.Student.findByPk(1) - .then(Harry => { - return Harry.setClasses([1, 2, 3]); - }), - this.Student.findByPk(2) - .then(Ron => { - return Ron.setClasses([1, 2]); - }), - this.Student.findByPk(3) - .then(Ginny => { - return Ginny.setClasses([2, 3]); - }), - this.Student.findByPk(4) - .then(Hermione => { - return Hermione.setClasses([1, 2, 3]); - }) - ]); - }) - .then(() => { - return this.Professor.findAll({ - include: [ - { - model: this.Class, - include: [ - { - model: this.Student - } - ] - } - ], - order: [ - ['id'], - [this.Class, 'id'], - [this.Class, this.Student, 'id'] - ] - }); - }) - .then(professors => { - expect(professors.length).to.eql(2); - expect(professors[0].fullName).to.eql('Albus Dumbledore'); - expect(professors[0].Classes.length).to.eql(1); - expect(professors[0].Classes[0].Students.length).to.eql(3); - }) - .finally(() => { - this.sequelize.getQueryInterface().QueryGenerator.options.quoteIdentifiers = true; + ], + order: [ + ['id'], + [this.Class, 'id'], + [this.Class, this.Student, 'id'] + ] }); + + expect(professors.length).to.eql(2); + expect(professors[0].fullName).to.eql('Albus Dumbledore'); + expect(professors[0].Classes.length).to.eql(1); + expect(professors[0].Classes[0].Students.length).to.eql(3); + } finally { + this.sequelize.getQueryInterface().queryGenerator.options.quoteIdentifiers = true; + } }); }); }); diff --git a/test/integration/dialects/postgres/data-types.test.js b/test/integration/dialects/postgres/data-types.test.js index c4978b9ccb5b..17de0200af52 100644 --- a/test/integration/dialects/postgres/data-types.test.js +++ b/test/integration/dialects/postgres/data-types.test.js @@ -6,6 +6,7 @@ const Support = require('../../support'); const dialect = Support.getTestDialect(); const DataTypes = require('../../../../lib/data-types'); + if (dialect === 'postgres') { describe('[POSTGRES Specific] Data Types', () => { describe('DATE/DATEONLY Validate and Stringify', () => { @@ -71,7 +72,7 @@ if (dialect === 'postgres') { describe('DATE SQL', () => { // create dummy user - it('should be able to create and update records with Infinity/-Infinity', function() { + it('should be able to create and update records with Infinity/-Infinity', async function() { this.sequelize.options.typeValidation = true; const date = new Date(); @@ -96,69 +97,68 @@ if (dialect === 'postgres') { timestamps: true }); - return User.sync({ + await User.sync({ force: true - }).then(() => { - return User.create({ - username: 'bob', - anotherTime: Infinity - }, { - validate: true - }); - }).then(user => { - expect(user.username).to.equal('bob'); - expect(user.beforeTime).to.equal(-Infinity); - expect(user.sometime).to.be.withinTime(date, new Date()); - expect(user.anotherTime).to.equal(Infinity); - expect(user.afterTime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: this.sequelize.fn('NOW') - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.be.withinTime(date, new Date()); - - // find - return User.findAll(); - }).then(users => { - expect(users[0].beforeTime).to.equal(-Infinity); - expect(users[0].sometime).to.not.equal(Infinity); - expect(users[0].afterTime).to.equal(Infinity); - - return users[0].update({ - sometime: date - }); - }).then(user => { - expect(user.sometime).to.equalTime(date); - - return user.update({ - sometime: date - }); - }).then(user => { - expect(user.sometime).to.equalTime(date); }); + + const user4 = await User.create({ + username: 'bob', + anotherTime: Infinity + }, { + validate: true + }); + + expect(user4.username).to.equal('bob'); + expect(user4.beforeTime).to.equal(-Infinity); + expect(user4.sometime).to.be.withinTime(date, new Date()); + expect(user4.anotherTime).to.equal(Infinity); + expect(user4.afterTime).to.equal(Infinity); + + const user3 = await user4.update({ + sometime: Infinity + }, { + returning: true + }); + + expect(user3.sometime).to.equal(Infinity); + + const user2 = await user3.update({ + sometime: Infinity + }); + + expect(user2.sometime).to.equal(Infinity); + + const user1 = await user2.update({ + sometime: this.sequelize.fn('NOW') + }, { + returning: true + }); + + expect(user1.sometime).to.be.withinTime(date, new Date()); + + // find + const users = await User.findAll(); + expect(users[0].beforeTime).to.equal(-Infinity); + expect(users[0].sometime).to.not.equal(Infinity); + expect(users[0].afterTime).to.equal(Infinity); + + const user0 = await users[0].update({ + sometime: date + }); + + expect(user0.sometime).to.equalTime(date); + + const user = await user0.update({ + sometime: date + }); + + expect(user.sometime).to.equalTime(date); }); }); describe('DATEONLY SQL', () => { // create dummy user - it('should be able to create and update records with Infinity/-Infinity', function() { + it('should be able to create and update records with Infinity/-Infinity', async function() { this.sequelize.options.typeValidation = true; const date = new Date(); @@ -183,64 +183,63 @@ if (dialect === 'postgres') { timestamps: true }); - return User.sync({ + await User.sync({ force: true - }).then(() => { - return User.create({ - username: 'bob', - anotherTime: Infinity - }, { - validate: true - }); - }).then(user => { - expect(user.username).to.equal('bob'); - expect(user.beforeTime).to.equal(-Infinity); - expect(new Date(user.sometime)).to.be.withinDate(date, new Date()); - expect(user.anotherTime).to.equal(Infinity); - expect(user.afterTime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: Infinity - }); - }).then(user => { - expect(user.sometime).to.equal(Infinity); - - return user.update({ - sometime: this.sequelize.fn('NOW') - }, { - returning: true - }); - }).then(user => { - expect(user.sometime).to.not.equal(Infinity); - expect(new Date(user.sometime)).to.be.withinDate(date, new Date()); - - // find - return User.findAll(); - }).then(users => { - expect(users[0].beforeTime).to.equal(-Infinity); - expect(users[0].sometime).to.not.equal(Infinity); - expect(users[0].afterTime).to.equal(Infinity); - - return users[0].update({ - sometime: '1969-07-20' - }); - }).then(user => { - expect(user.sometime).to.equal('1969-07-20'); - - return user.update({ - sometime: '1969-07-20' - }); - }).then(user => { - expect(user.sometime).to.equal('1969-07-20'); }); + + const user4 = await User.create({ + username: 'bob', + anotherTime: Infinity + }, { + validate: true + }); + + expect(user4.username).to.equal('bob'); + expect(user4.beforeTime).to.equal(-Infinity); + expect(new Date(user4.sometime)).to.be.withinDate(date, new Date()); + expect(user4.anotherTime).to.equal(Infinity); + expect(user4.afterTime).to.equal(Infinity); + + const user3 = await user4.update({ + sometime: Infinity + }, { + returning: true + }); + + expect(user3.sometime).to.equal(Infinity); + + const user2 = await user3.update({ + sometime: Infinity + }); + + expect(user2.sometime).to.equal(Infinity); + + const user1 = await user2.update({ + sometime: this.sequelize.fn('NOW') + }, { + returning: true + }); + + expect(user1.sometime).to.not.equal(Infinity); + expect(new Date(user1.sometime)).to.be.withinDate(date, new Date()); + + // find + const users = await User.findAll(); + expect(users[0].beforeTime).to.equal(-Infinity); + expect(users[0].sometime).to.not.equal(Infinity); + expect(users[0].afterTime).to.equal(Infinity); + + const user0 = await users[0].update({ + sometime: '1969-07-20' + }); + + expect(user0.sometime).to.equal('1969-07-20'); + + const user = await user0.update({ + sometime: '1969-07-20' + }); + + expect(user.sometime).to.equal('1969-07-20'); }); }); diff --git a/test/integration/dialects/postgres/error.test.js b/test/integration/dialects/postgres/error.test.js index 8c850a1c8e27..425a599cbb2d 100644 --- a/test/integration/dialects/postgres/error.test.js +++ b/test/integration/dialects/postgres/error.test.js @@ -11,18 +11,18 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] ExclusionConstraintError', () => { const constraintName = 'overlap_period'; - beforeEach(function() { + beforeEach(async function() { this.Booking = this.sequelize.define('Booking', { roomNo: DataTypes.INTEGER, period: DataTypes.RANGE(DataTypes.DATE) }); - return this.Booking - .sync({ force: true }) - .then(() => { - return this.sequelize.query( - `ALTER TABLE "${this.Booking.tableName}" ADD CONSTRAINT ${constraintName} EXCLUDE USING gist ("roomNo" WITH =, period WITH &&)` - ); - }); + + await this.Booking + .sync({ force: true }); + + await this.sequelize.query( + `ALTER TABLE "${this.Booking.tableName}" ADD CONSTRAINT ${constraintName} EXCLUDE USING gist ("roomNo" WITH =, period WITH &&)` + ); }); it('should contain error specific properties', () => { @@ -40,23 +40,22 @@ if (dialect.match(/^postgres/)) { }); }); - it('should throw ExclusionConstraintError when "period" value overlaps existing', function() { + it('should throw ExclusionConstraintError when "period" value overlaps existing', async function() { const Booking = this.Booking; - return Booking + await Booking .create({ roomNo: 1, guestName: 'Incognito Visitor', period: [new Date(2015, 0, 1), new Date(2015, 0, 3)] - }) - .then(() => { - return expect(Booking - .create({ - roomNo: 1, - guestName: 'Frequent Visitor', - period: [new Date(2015, 0, 2), new Date(2015, 0, 5)] - })).to.eventually.be.rejectedWith(Sequelize.ExclusionConstraintError); }); + + await expect(Booking + .create({ + roomNo: 1, + guestName: 'Frequent Visitor', + period: [new Date(2015, 0, 2), new Date(2015, 0, 5)] + })).to.eventually.be.rejectedWith(Sequelize.ExclusionConstraintError); }); }); diff --git a/test/integration/dialects/postgres/query-interface.test.js b/test/integration/dialects/postgres/query-interface.test.js index 2c6fe19cc00e..066f684f03d8 100644 --- a/test/integration/dialects/postgres/query-interface.test.js +++ b/test/integration/dialects/postgres/query-interface.test.js @@ -16,275 +16,259 @@ if (dialect.match(/^postgres/)) { }); describe('createSchema', () => { - beforeEach(function() { + beforeEach(async function() { // make sure we don't have a pre-existing schema called testSchema. - return this.queryInterface.dropSchema('testschema').reflect(); + await this.queryInterface.dropSchema('testschema').catch(() => {}); }); - it('creates a schema', function() { - return this.queryInterface.createSchema('testschema') - .then(() => this.sequelize.query(` + it('creates a schema', async function() { + await this.queryInterface.createSchema('testschema'); + + const res = await this.sequelize.query(` SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'testschema'; - `, { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res, 'query results').to.not.be.empty; - expect(res[0].schema_name).to.be.equal('testschema'); - }); + `, { type: this.sequelize.QueryTypes.SELECT }); + + expect(res, 'query results').to.not.be.empty; + expect(res[0].schema_name).to.be.equal('testschema'); }); - it('works even when schema exists', function() { - return this.queryInterface.createSchema('testschema') - .then(() => this.queryInterface.createSchema('testschema')) - .then(() => this.sequelize.query(` + it('works even when schema exists', async function() { + await this.queryInterface.createSchema('testschema'); + await this.queryInterface.createSchema('testschema'); + + const res = await this.sequelize.query(` SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'testschema'; - `, { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res, 'query results').to.not.be.empty; - expect(res[0].schema_name).to.be.equal('testschema'); - }); + `, { type: this.sequelize.QueryTypes.SELECT }); + + expect(res, 'query results').to.not.be.empty; + expect(res[0].schema_name).to.be.equal('testschema'); }); }); describe('databaseVersion', () => { - it('reports version', function() { - return this.queryInterface.databaseVersion() - .then(res => { - // check that result matches expected version number format. example 9.5.4 - expect(res).to.match(/\d\.\d/); - }); + it('reports version', async function() { + const res = await this.queryInterface.databaseVersion(); + // check that result matches expected version number format. example 9.5.4 + expect(res).to.match(/\d\.\d/); }); }); describe('renameFunction', () => { - beforeEach(function() { + beforeEach(async function() { // ensure the function names we'll use don't exist before we start. // then setup our function to rename - return this.queryInterface.dropFunction('rftest1', []) - .reflect() - .then(() => this.queryInterface.dropFunction('rftest2', [])) - .reflect() - .then(() => this.queryInterface.createFunction('rftest1', [], 'varchar', 'plpgsql', 'return \'testreturn\';', {})); + await this.queryInterface.dropFunction('rftest1', []) + .catch(() => {}); + + await this.queryInterface.dropFunction('rftest2', []) + .catch(() => {}); + + await this.queryInterface.createFunction('rftest1', [], 'varchar', 'plpgsql', 'return \'testreturn\';', {}); }); - it('renames a function', function() { - return this.queryInterface.renameFunction('rftest1', [], 'rftest2') - .then(() => this.sequelize.query('select rftest2();', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].rftest2).to.be.eql('testreturn'); - }); + it('renames a function', async function() { + await this.queryInterface.renameFunction('rftest1', [], 'rftest2'); + const res = await this.sequelize.query('select rftest2();', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].rftest2).to.be.eql('testreturn'); }); }); describe('createFunction', () => { - beforeEach(function() { + beforeEach(async function() { // make sure we don't have a pre-existing function called create_job // this is needed to cover the edge case of afterEach not getting called because of an unexpected issue or stopage with the // test suite causing a failure of afterEach's cleanup to be called. - return this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) + await this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. - .reflect(); + .catch(() => {}); }); - after(function() { + after(async function() { // cleanup - return this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) + await this.queryInterface.dropFunction('create_job', [{ type: 'varchar', name: 'test' }]) // suppress errors here. if create_job doesn't exist thats ok. - .reflect(); + .catch(() => {}); }); - it('creates a stored procedure', function() { + it('creates a stored procedure', async function() { const body = 'return test;'; const options = {}; // make our call to create a function - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options) - // validate - .then(() => this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].create_job).to.be.eql('test'); - }); + await this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options); + // validate + const res = await this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].create_job).to.be.eql('test'); }); - it('treats options as optional', function() { + it('treats options as optional', async function() { const body = 'return test;'; // run with null options parameter - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, null) - // validate - .then(() => this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].create_job).to.be.eql('test'); - }); + await this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, null); + // validate + const res = await this.sequelize.query('select create_job(\'test\');', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].create_job).to.be.eql('test'); }); - it('produces an error when missing expected parameters', function() { + it('produces an error when missing expected parameters', async function() { const body = 'return 1;'; const options = {}; - return Promise.all([ + await Promise.all([ // requires functionName - expect(() => { - return this.queryInterface.createFunction(null, [{ name: 'test' }], 'integer', 'plpgsql', body, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), + expect(this.queryInterface.createFunction(null, [{ name: 'test' }], 'integer', 'plpgsql', body, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), // requires Parameters array - expect(() => { - return this.queryInterface.createFunction('create_job', null, 'integer', 'plpgsql', body, options); - }).to.throw(/function parameters array required/), + expect(this.queryInterface.createFunction('create_job', null, 'integer', 'plpgsql', body, options)) + .to.be.rejectedWith(/function parameters array required/), // requires returnType - expect(() => { - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], null, 'plpgsql', body, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), + expect(this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], null, 'plpgsql', body, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), // requires type in parameter array - expect(() => { - return this.queryInterface.createFunction('create_job', [{ name: 'test' }], 'integer', 'plpgsql', body, options); - }).to.throw(/function or trigger used with a parameter without any type/), + expect(this.queryInterface.createFunction('create_job', [{ name: 'test' }], 'integer', 'plpgsql', body, options)) + .to.be.rejectedWith(/function or trigger used with a parameter without any type/), // requires language - expect(() => { - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', null, body, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), + expect(this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', null, body, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/), // requires body - expect(() => { - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', null, options); - }).to.throw(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/) + expect(this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', null, options)) + .to.be.rejectedWith(/createFunction missing some parameters. Did you pass functionName, returnType, language and body/) ]); }); - it('overrides a function', function() { + it('overrides a function', async function() { const first_body = 'return \'first\';'; const second_body = 'return \'second\';'; // create function - return this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', first_body, null) - // override - .then(() => this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', second_body, null, { force: true })) - // validate - .then(() => this.sequelize.query("select create_job('abc');", { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].create_job).to.be.eql('second'); - }); + await this.queryInterface.createFunction('create_job', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', first_body, null); + // override + await this.queryInterface.createFunction( + 'create_job', + [{ type: 'varchar', name: 'test' }], + 'varchar', + 'plpgsql', + second_body, + null, + { force: true } + ); + // validate + const res = await this.sequelize.query("select create_job('abc');", { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].create_job).to.be.eql('second'); }); it('produces an error when options.variables is missing expected parameters', function() { const body = 'return 1;'; - expect(() => { - const options = { variables: 100 }; - return this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], options); - }).to.throw(/expandFunctionVariableList: function variables must be an array/); - - expect(() => { - const options = { variables: [{ name: 'myVar' }] }; - return this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], options); - }).to.throw(/function variable must have a name and type/); - - expect(() => { - const options = { variables: [{ type: 'integer' }] }; - return this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], options); - }).to.throw(/function variable must have a name and type/); + expect(this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: 100 })) + .to.be.rejectedWith(/expandFunctionVariableList: function variables must be an array/); + + expect(this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: [{ name: 'myVar' }] })) + .to.be.rejectedWith(/function variable must have a name and type/); + + expect(this.queryInterface.createFunction('test_func', [], 'integer', 'plpgsql', body, [], { variables: [{ type: 'integer' }] })) + .to.be.rejectedWith(/function variable must have a name and type/); }); - it('uses declared variables', function() { + it('uses declared variables', async function() { const body = 'RETURN myVar + 1;'; const options = { variables: [{ type: 'integer', name: 'myVar', default: 100 }] }; - return this.queryInterface.createFunction('add_one', [], 'integer', 'plpgsql', body, [], options) - .then(() => this.sequelize.query('select add_one();', { type: this.sequelize.QueryTypes.SELECT })) - .then(res => { - expect(res[0].add_one).to.be.eql(101); - }); + await this.queryInterface.createFunction('add_one', [], 'integer', 'plpgsql', body, [], options); + const res = await this.sequelize.query('select add_one();', { type: this.sequelize.QueryTypes.SELECT }); + expect(res[0].add_one).to.be.eql(101); }); }); describe('dropFunction', () => { - beforeEach(function() { + beforeEach(async function() { const body = 'return test;'; const options = {}; // make sure we have a droptest function in place. - return this.queryInterface.createFunction('droptest', [{ type: 'varchar', name: 'test' }], 'varchar', 'plpgsql', body, options) + await this.queryInterface.createFunction( + 'droptest', + [{ type: 'varchar', name: 'test' }], + 'varchar', + 'plpgsql', + body, + options + ) // suppress errors.. this could fail if the function is already there.. thats ok. - .reflect(); + .catch(() => {}); }); - it('can drop a function', function() { - return expect( - // call drop function - this.queryInterface.dropFunction('droptest', [{ type: 'varchar', name: 'test' }]) - // now call the function we attempted to drop.. if dropFunction worked as expect it should produce an error. - .then(() => { - // call the function we attempted to drop.. if it is still there then throw an error informing that the expected behavior is not met. - return this.sequelize.query('select droptest(\'test\');', { type: this.sequelize.QueryTypes.SELECT }); - }) + it('can drop a function', async function() { + // call drop function + await this.queryInterface.dropFunction('droptest', [{ type: 'varchar', name: 'test' }]); + await expect( + // now call the function we attempted to drop.. if dropFunction worked as expect it should produce an error. + this.sequelize.query('select droptest(\'test\');', { type: this.sequelize.QueryTypes.SELECT }) // test that we did get the expected error indicating that droptest was properly removed. ).to.be.rejectedWith(/.*function droptest.* does not exist/); }); - it('produces an error when missing expected parameters', function() { - return Promise.all([ - expect(() => { - return this.queryInterface.dropFunction(); - }).to.throw(/.*requires functionName/), + it('produces an error when missing expected parameters', async function() { + await Promise.all([ + expect(this.queryInterface.dropFunction()) + .to.be.rejectedWith(/.*requires functionName/), - expect(() => { - return this.queryInterface.dropFunction('droptest'); - }).to.throw(/.*function parameters array required/), + expect(this.queryInterface.dropFunction('droptest')) + .to.be.rejectedWith(/.*function parameters array required/), - expect(() => { - return this.queryInterface.dropFunction('droptest', [{ name: 'test' }]); - }).to.be.throw(/.*function or trigger used with a parameter without any type/) + expect(this.queryInterface.dropFunction('droptest', [{ name: 'test' }])) + .to.be.rejectedWith(/.*function or trigger used with a parameter without any type/) ]); }); }); describe('indexes', () => { - beforeEach(function() { - return this.queryInterface.dropTable('Group') - .then(() => this.queryInterface.createTable('Group', { - username: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN, - from: DataTypes.STRING - })); + beforeEach(async function() { + await this.queryInterface.dropTable('Group'); + + await this.queryInterface.createTable('Group', { + username: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN, + from: DataTypes.STRING + }); }); - it('supports newlines', function() { - return this.queryInterface.addIndex('Group', [this.sequelize.literal(`( + it('supports newlines', async function() { + await this.queryInterface.addIndex('Group', [this.sequelize.literal(`( CASE "username" WHEN 'foo' THEN 'bar' ELSE 'baz' END - )`)], { name: 'group_username_case' }) - .then(() => this.queryInterface.showIndex('Group')) - .then(indexes => { - const indexColumns = _.uniq(indexes.map(index => index.name)); + )`)], { name: 'group_username_case' }); + + const indexes = await this.queryInterface.showIndex('Group'); + const indexColumns = _.uniq(indexes.map(index => index.name)); - expect(indexColumns).to.include('group_username_case'); - }); + expect(indexColumns).to.include('group_username_case'); }); - it('adds, reads and removes a named functional index to the table', function() { - return this.queryInterface.addIndex('Group', [this.sequelize.fn('lower', this.sequelize.col('username'))], { + it('adds, reads and removes a named functional index to the table', async function() { + await this.queryInterface.addIndex('Group', [this.sequelize.fn('lower', this.sequelize.col('username'))], { name: 'group_username_lower' - }) - .then(() => this.queryInterface.showIndex('Group')) - .then(indexes => { - const indexColumns = _.uniq(indexes.map(index => index.name)); - - expect(indexColumns).to.include('group_username_lower'); - }) - .then(() => this.queryInterface.removeIndex('Group', 'group_username_lower')) - .then(() => this.queryInterface.showIndex('Group')) - .then(indexes => { - const indexColumns = _.uniq(indexes.map(index => index.name)); - expect(indexColumns).to.be.empty; - }); + }); + + const indexes0 = await this.queryInterface.showIndex('Group'); + const indexColumns0 = _.uniq(indexes0.map(index => index.name)); + + expect(indexColumns0).to.include('group_username_lower'); + await this.queryInterface.removeIndex('Group', 'group_username_lower'); + const indexes = await this.queryInterface.showIndex('Group'); + const indexColumns = _.uniq(indexes.map(index => index.name)); + expect(indexColumns).to.be.empty; }); }); }); diff --git a/test/integration/dialects/postgres/query.test.js b/test/integration/dialects/postgres/query.test.js index 3f6597eb0c9d..5f4265653a6e 100644 --- a/test/integration/dialects/postgres/query.test.js +++ b/test/integration/dialects/postgres/query.test.js @@ -12,7 +12,7 @@ if (dialect.match(/^postgres/)) { const taskAlias = 'AnActualVeryLongAliasThatShouldBreakthePostgresLimitOfSixtyFourCharacters'; const teamAlias = 'Toto'; - const executeTest = (options, test) => { + const executeTest = async (options, test) => { const sequelize = Support.createSequelizeInstance(options); const User = sequelize.define('User', { name: DataTypes.STRING, updatedAt: DataTypes.DATE }, { underscored: true }); @@ -23,45 +23,84 @@ if (dialect.match(/^postgres/)) { User.belongsToMany(Team, { as: teamAlias, foreignKey: 'teamId', through: 'UserTeam' }); Team.belongsToMany(User, { foreignKey: 'userId', through: 'UserTeam' }); - return sequelize.sync({ force: true }).then(() => { - return Team.create({ name: 'rocket' }).then(team => { - return Task.create({ title: 'SuperTask' }).then(task => { - return User.create({ name: 'test', task_id: task.id, updatedAt: new Date() }).then(user => { - return user[`add${teamAlias}`](team).then(() => { - return User.findOne({ - include: [ - { - model: Task, - as: taskAlias - }, - { - model: Team, - as: teamAlias - } - ] - }).then(test); - }); - }); - }); - }); - }); + await sequelize.sync({ force: true }); + const team = await Team.create({ name: 'rocket' }); + const task = await Task.create({ title: 'SuperTask' }); + const user = await User.create({ name: 'test', task_id: task.id, updatedAt: new Date() }); + await user[`add${teamAlias}`](team); + return test(await User.findOne({ + include: [ + { + model: Task, + as: taskAlias + }, + { + model: Team, + as: teamAlias + } + ] + })); }; - it('should throw due to alias being truncated', function() { - const options = Object.assign({}, this.sequelize.options, { minifyAliases: false }); + it('should throw due to alias being truncated', async function() { + const options = { ...this.sequelize.options, minifyAliases: false }; - return executeTest(options, res => { + await executeTest(options, res => { expect(res[taskAlias]).to.not.exist; }); }); - it('should be able to retrieve include due to alias minifying', function() { - const options = Object.assign({}, this.sequelize.options, { minifyAliases: true }); + it('should be able to retrieve include due to alias minifying', async function() { + const options = { ...this.sequelize.options, minifyAliases: true }; - return executeTest(options, res => { + await executeTest(options, res => { expect(res[taskAlias].title).to.be.equal('SuperTask'); }); }); + + it('should throw due to table name being truncated', async () => { + const sequelize = Support.createSequelizeInstance({ minifyAliases: true }); + + const User = sequelize.define('user_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', + { + name: DataTypes.STRING, + email: DataTypes.STRING + }, + { + tableName: 'user' + } + ); + const Project = sequelize.define('project_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', + { + name: DataTypes.STRING + }, + { + tableName: 'project' + } + ); + const Company = sequelize.define('company_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit', + { + name: DataTypes.STRING + }, + { + tableName: 'company' + } + ); + User.hasMany(Project, { foreignKey: 'userId' }); + Project.belongsTo(Company, { foreignKey: 'companyId' }); + + await sequelize.sync({ force: true }); + const comp = await Company.create({ name: 'Sequelize' }); + const user = await User.create({ name: 'standard user' }); + await Project.create({ name: 'Manhattan', companyId: comp.id, userId: user.id }); + + await User.findAll({ + include: { + model: Project, + include: Company + } + }); + }); }); } \ No newline at end of file diff --git a/test/integration/dialects/postgres/range.test.js b/test/integration/dialects/postgres/range.test.js index 9e33dca9373f..c7e7268f1ec2 100644 --- a/test/integration/dialects/postgres/range.test.js +++ b/test/integration/dialects/postgres/range.test.js @@ -184,16 +184,16 @@ if (dialect.match(/^postgres/)) { expect(range.parse('some_non_array')).to.deep.equal('some_non_array'); }); - it('should handle native postgres timestamp format', () => { + it('should handle native postgres timestamp format', async () => { // Make sure nameOidMap is loaded - return Support.sequelize.connectionManager.getConnection().then(connection => { - Support.sequelize.connectionManager.releaseConnection(connection); + const connection = await Support.sequelize.connectionManager.getConnection(); - const tsName = DataTypes.postgres.DATE.types.postgres[0], - tsOid = Support.sequelize.connectionManager.nameOidMap[tsName].oid, - parser = pg.types.getTypeParser(tsOid); - expect(range.parse('(2016-01-01 08:00:00-04,)', parser)[0].value.toISOString()).to.equal('2016-01-01T12:00:00.000Z'); - }); + Support.sequelize.connectionManager.releaseConnection(connection); + + const tsName = DataTypes.postgres.DATE.types.postgres[0], + tsOid = Support.sequelize.connectionManager.nameOidMap[tsName].oid, + parser = pg.types.getTypeParser(tsOid); + expect(range.parse('(2016-01-01 08:00:00-04,)', parser)[0].value.toISOString()).to.equal('2016-01-01T12:00:00.000Z'); }); }); diff --git a/test/integration/dialects/postgres/regressions.test.js b/test/integration/dialects/postgres/regressions.test.js index b3ad1b0961a3..963e9110fe75 100644 --- a/test/integration/dialects/postgres/regressions.test.js +++ b/test/integration/dialects/postgres/regressions.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), if (dialect.match(/^postgres/)) { describe('[POSTGRES Specific] Regressions', () => { - it('properly fetch OIDs after sync, #8749', function() { + it('properly fetch OIDs after sync, #8749', async function() { const User = this.sequelize.define('User', { active: Sequelize.BOOLEAN }); @@ -27,24 +27,18 @@ if (dialect.match(/^postgres/)) { User.hasMany(Media); Media.belongsTo(User); - return this.sequelize - .sync({ force: true }) - .then(() => User.create({ active: true })) - .then(user => { - expect(user.active).to.be.true; - expect(user.get('active')).to.be.true; - - return User.findOne(); - }) - .then(user => { - expect(user.active).to.be.true; - expect(user.get('active')).to.be.true; - - return User.findOne({ raw: true }); - }) - .then(user => { - expect(user.active).to.be.true; - }); + await this.sequelize.sync({ force: true }); + + const user1 = await User.create({ active: true }); + expect(user1.active).to.be.true; + expect(user1.get('active')).to.be.true; + + const user0 = await User.findOne(); + expect(user0.active).to.be.true; + expect(user0.get('active')).to.be.true; + + const user = await User.findOne({ raw: true }); + expect(user.active).to.be.true; }); }); } diff --git a/test/integration/dialects/sqlite/connection-manager.test.js b/test/integration/dialects/sqlite/connection-manager.test.js index fe62034d7930..064fa12a3921 100644 --- a/test/integration/dialects/sqlite/connection-manager.test.js +++ b/test/integration/dialects/sqlite/connection-manager.test.js @@ -1,50 +1,65 @@ 'use strict'; const chai = require('chai'); -const fs = require('fs'); -const path = require('path'); +const jetpack = require('fs-jetpack').cwd(__dirname); const expect = chai.expect; const Support = require('../../support'); const dialect = Support.getTestDialect(); const DataTypes = require('../../../../lib/data-types'); const fileName = `${Math.random()}_test.sqlite`; +const directoryName = `${Math.random()}_test_directory`; +const nestedFileName = jetpack.path(directoryName, 'subdirectory', 'test.sqlite'); if (dialect === 'sqlite') { describe('[SQLITE Specific] Connection Manager', () => { after(() => { - fs.unlinkSync(path.join(__dirname, fileName)); + jetpack.remove(fileName); + jetpack.remove(directoryName); }); - it('close connection and remove journal and wal files', function() { + it('close connection and remove journal and wal files', async function() { const sequelize = Support.createSequelizeInstance({ - storage: path.join(__dirname, fileName) + storage: jetpack.path(fileName) }); const User = sequelize.define('User', { username: DataTypes.STRING }); - return User - .sync({ force: true }) - .then(() => sequelize.query('PRAGMA journal_mode = WAL')) - .then(() => User.create({ username: 'user1' })) - .then(() => { - return sequelize.transaction(transaction => { - return User.create({ username: 'user2' }, { transaction }); - }); - }) - .then(() => { - expect(fs.existsSync(path.join(__dirname, fileName))).to.be.true; - expect(fs.existsSync(path.join(__dirname, `${fileName}-shm`)), 'shm file should exists').to.be.true; - expect(fs.existsSync(path.join(__dirname, `${fileName}-wal`)), 'wal file should exists').to.be.true; - - return sequelize.close(); - }) - .then(() => { - expect(fs.existsSync(path.join(__dirname, fileName))).to.be.true; - expect(fs.existsSync(path.join(__dirname, `${fileName}-shm`)), 'shm file exists').to.be.false; - expect(fs.existsSync(path.join(__dirname, `${fileName}-wal`)), 'wal file exists').to.be.false; - - return this.sequelize.query('PRAGMA journal_mode = DELETE'); - }); + await User.sync({ force: true }); + + await sequelize.query('PRAGMA journal_mode = WAL'); + await User.create({ username: 'user1' }); + + await sequelize.transaction(transaction => { + return User.create({ username: 'user2' }, { transaction }); + }); + + expect(jetpack.exists(fileName)).to.be.equal('file'); + expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); + expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + + // move wal file content to main database + // so those files can be removed on connection close + // https://www.sqlite.org/wal.html#ckpt + await sequelize.query('PRAGMA wal_checkpoint'); + + // wal, shm files exist after checkpoint + expect(jetpack.exists(`${fileName}-shm`), 'shm file should exists').to.be.equal('file'); + expect(jetpack.exists(`${fileName}-wal`), 'wal file should exists').to.be.equal('file'); + + await sequelize.close(); + expect(jetpack.exists(fileName)).to.be.equal('file'); + expect(jetpack.exists(`${fileName}-shm`), 'shm file exists').to.be.false; + expect(jetpack.exists(`${fileName}-wal`), 'wal file exists').to.be.false; + + await this.sequelize.query('PRAGMA journal_mode = DELETE'); + }); + + it('automatic path provision for `options.storage`', async () => { + await Support.createSequelizeInstance({ storage: nestedFileName }) + .define('User', { username: DataTypes.STRING }) + .sync({ force: true }); + + expect(jetpack.exists(nestedFileName)).to.be.equal('file'); }); }); } diff --git a/test/integration/dialects/sqlite/dao-factory.test.js b/test/integration/dialects/sqlite/dao-factory.test.js index c55e1c995217..c73030137f0d 100644 --- a/test/integration/dialects/sqlite/dao-factory.test.js +++ b/test/integration/dialects/sqlite/dao-factory.test.js @@ -14,14 +14,14 @@ if (dialect === 'sqlite') { this.sequelize.options.storage = ':memory:'; }); - beforeEach(function() { + beforeEach(async function() { this.sequelize.options.storage = dbFile; this.User = this.sequelize.define('User', { age: DataTypes.INTEGER, name: DataTypes.STRING, bio: DataTypes.TEXT }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); storages.forEach(storage => { @@ -33,137 +33,128 @@ if (dialect === 'sqlite') { }); describe('create', () => { - it('creates a table entry', function() { - return this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }).then(user => { - expect(user.age).to.equal(21); - expect(user.name).to.equal('John Wayne'); - expect(user.bio).to.equal('noot noot'); - - return this.User.findAll().then(users => { - const usernames = users.map(user => { - return user.name; - }); - expect(usernames).to.contain('John Wayne'); - }); + it('creates a table entry', async function() { + const user = await this.User.create({ age: 21, name: 'John Wayne', bio: 'noot noot' }); + expect(user.age).to.equal(21); + expect(user.name).to.equal('John Wayne'); + expect(user.bio).to.equal('noot noot'); + + const users = await this.User.findAll(); + const usernames = users.map(user => { + return user.name; }); + expect(usernames).to.contain('John Wayne'); }); - it('should allow the creation of an object with options as attribute', function() { + it('should allow the creation of an object with options as attribute', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING, options: DataTypes.TEXT }); - return Person.sync({ force: true }).then(() => { - const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); + await Person.sync({ force: true }); + const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); - return Person.create({ - name: 'John Doe', - options - }).then(people => { - expect(people.options).to.deep.equal(options); - }); + const people = await Person.create({ + name: 'John Doe', + options }); + + expect(people.options).to.deep.equal(options); }); - it('should allow the creation of an object with a boolean (true) as attribute', function() { + it('should allow the creation of an object with a boolean (true) as attribute', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING, has_swag: DataTypes.BOOLEAN }); - return Person.sync({ force: true }).then(() => { - return Person.create({ - name: 'John Doe', - has_swag: true - }).then(people => { - expect(people.has_swag).to.be.ok; - }); + await Person.sync({ force: true }); + + const people = await Person.create({ + name: 'John Doe', + has_swag: true }); + + expect(people.has_swag).to.be.ok; }); - it('should allow the creation of an object with a boolean (false) as attribute', function() { + it('should allow the creation of an object with a boolean (false) as attribute', async function() { const Person = this.sequelize.define('Person', { name: DataTypes.STRING, has_swag: DataTypes.BOOLEAN }); - return Person.sync({ force: true }).then(() => { - return Person.create({ - name: 'John Doe', - has_swag: false - }).then(people => { - expect(people.has_swag).to.not.be.ok; - }); + await Person.sync({ force: true }); + + const people = await Person.create({ + name: 'John Doe', + has_swag: false }); + + expect(people.has_swag).to.not.be.ok; }); }); describe('.findOne', () => { - beforeEach(function() { - return this.User.create({ name: 'user', bio: 'footbar' }); + beforeEach(async function() { + await this.User.create({ name: 'user', bio: 'footbar' }); }); - it('finds normal lookups', function() { - return this.User.findOne({ where: { name: 'user' } }).then(user => { - expect(user.name).to.equal('user'); - }); + it('finds normal lookups', async function() { + const user = await this.User.findOne({ where: { name: 'user' } }); + expect(user.name).to.equal('user'); }); - it.skip('should make aliased attributes available', function() { - return this.User.findOne({ + it.skip('should make aliased attributes available', async function() { // eslint-disable-line mocha/no-skipped-tests + const user = await this.User.findOne({ where: { name: 'user' }, attributes: ['id', ['name', 'username']] - }).then(user => { - expect(user.username).to.equal('user'); }); + + expect(user.username).to.equal('user'); }); }); describe('.all', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { name: 'user', bio: 'foobar' }, { name: 'user', bio: 'foobar' } ]); }); - it('should return all users', function() { - return this.User.findAll().then(users => { - expect(users).to.have.length(2); - }); + it('should return all users', async function() { + const users = await this.User.findAll(); + expect(users).to.have.length(2); }); }); describe('.min', () => { - it('should return the min value', function() { + it('should return the min value', async function() { const users = []; for (let i = 2; i < 5; i++) { users[users.length] = { age: i }; } - return this.User.bulkCreate(users).then(() => { - return this.User.min('age').then(min => { - expect(min).to.equal(2); - }); - }); + await this.User.bulkCreate(users); + const min = await this.User.min('age'); + expect(min).to.equal(2); }); }); describe('.max', () => { - it('should return the max value', function() { + it('should return the max value', async function() { const users = []; for (let i = 2; i <= 5; i++) { users[users.length] = { age: i }; } - return this.User.bulkCreate(users).then(() => { - return this.User.max('age').then(min => { - expect(min).to.equal(5); - }); - }); + await this.User.bulkCreate(users); + const min = await this.User.max('age'); + expect(min).to.equal(5); }); }); }); diff --git a/test/integration/dialects/sqlite/dao.test.js b/test/integration/dialects/sqlite/dao.test.js index ba5cd0d11af6..47eb3d286ead 100644 --- a/test/integration/dialects/sqlite/dao.test.js +++ b/test/integration/dialects/sqlite/dao.test.js @@ -10,7 +10,7 @@ const chai = require('chai'), if (dialect === 'sqlite') { describe('[SQLITE Specific] DAO', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, emergency_contact: DataTypes.JSON, @@ -28,90 +28,90 @@ if (dialect === 'sqlite') { }); this.User.hasMany(this.Project); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('findAll', () => { - it('handles dates correctly', function() { - const user = new this.User({ username: 'user' }); + it('handles dates correctly', async function() { + const user = this.User.build({ username: 'user' }); user.dataValues.createdAt = new Date(2011, 4, 4); - return user.save().then(() => { - return this.User.create({ username: 'new user' }).then(() => { - return this.User.findAll({ - where: { createdAt: { [Op.gt]: new Date(2012, 1, 1) } } - }).then(users => { - expect(users).to.have.length(1); - }); - }); + await user.save(); + await this.User.create({ username: 'new user' }); + + const users = await this.User.findAll({ + where: { createdAt: { [Op.gt]: new Date(2012, 1, 1) } } }); + + expect(users).to.have.length(1); }); - it('handles dates with aliasses correctly #3611', function() { - return this.User.create({ + it('handles dates with aliasses correctly #3611', async function() { + await this.User.create({ dateField: new Date(2010, 10, 10) - }).then(() => { - return this.User.findAll().get(0); - }).then(user => { - expect(user.get('dateField')).to.be.an.instanceof(Date); - expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10)); }); + + const obj = await this.User.findAll(); + const user = await obj[0]; + expect(user.get('dateField')).to.be.an.instanceof(Date); + expect(user.get('dateField')).to.equalTime(new Date(2010, 10, 10)); }); - it('handles dates in includes correctly #2644', function() { - return this.User.create({ + it('handles dates in includes correctly #2644', async function() { + await this.User.create({ projects: [ { dateField: new Date(1990, 5, 5) } ] - }, { include: [this.Project] }).then(() => { - return this.User.findAll({ - include: [this.Project] - }).get(0); - }).then(user => { - expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date); - expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5)); + }, { include: [this.Project] }); + + const obj = await this.User.findAll({ + include: [this.Project] }); + + const user = await obj[0]; + expect(user.projects[0].get('dateField')).to.be.an.instanceof(Date); + expect(user.projects[0].get('dateField')).to.equalTime(new Date(1990, 5, 5)); }); }); describe('json', () => { - it('should be able to retrieve a row with json_extract function', function() { - return Sequelize.Promise.all([ + it('should be able to retrieve a row with json_extract function', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json('json_extract(emergency_contact, \'$.name\')', 'kate'), - attributes: ['username', 'emergency_contact'] - }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('kate'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json('json_extract(emergency_contact, \'$.name\')', 'kate'), + attributes: ['username', 'emergency_contact'] }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to retrieve a row by json_type function', function() { - return Sequelize.Promise.all([ + it('should be able to retrieve a row by json_type function', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: ['kate', 'joe'] }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json('json_type(emergency_contact)', 'array'), - attributes: ['username', 'emergency_contact'] - }); - }).then(user => { - expect(user.username).to.equal('anna'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json('json_type(emergency_contact)', 'array'), + attributes: ['username', 'emergency_contact'] }); + + expect(user.username).to.equal('anna'); }); }); describe('regression tests', () => { - it('do not crash while parsing unique constraint errors', function() { + it('do not crash while parsing unique constraint errors', async function() { const Payments = this.sequelize.define('payments', {}); - return Payments.sync({ force: true }).then(() => { - return expect(Payments.bulkCreate([{ id: 1 }, { id: 1 }], { ignoreDuplicates: false })).to.eventually.be.rejected; - }); + await Payments.sync({ force: true }); + + await expect(Payments.bulkCreate([{ id: 1 }, { id: 1 }], { ignoreDuplicates: false })).to.eventually.be.rejected; }); }); }); diff --git a/test/integration/dialects/sqlite/sqlite-master.test.js b/test/integration/dialects/sqlite/sqlite-master.test.js index 7afa54a8a25c..ced16581138a 100644 --- a/test/integration/dialects/sqlite/sqlite-master.test.js +++ b/test/integration/dialects/sqlite/sqlite-master.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), if (dialect === 'sqlite') { describe('[SQLITE Specific] sqlite_master raw queries', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize.define('SomeTable', { someColumn: DataTypes.INTEGER }, { @@ -16,46 +16,40 @@ if (dialect === 'sqlite') { timestamps: false }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should be able to select with tbl_name filter', function() { - return this.sequelize.query('SELECT * FROM sqlite_master WHERE tbl_name=\'SomeTable\'') - .then(result => { - const rows = result[0]; - expect(rows).to.have.length(1); - const row = rows[0]; - expect(row).to.have.property('type', 'table'); - expect(row).to.have.property('name', 'SomeTable'); - expect(row).to.have.property('tbl_name', 'SomeTable'); - expect(row).to.have.property('sql'); - }); + it('should be able to select with tbl_name filter', async function() { + const result = await this.sequelize.query('SELECT * FROM sqlite_master WHERE tbl_name=\'SomeTable\''); + const rows = result[0]; + expect(rows).to.have.length(1); + const row = rows[0]; + expect(row).to.have.property('type', 'table'); + expect(row).to.have.property('name', 'SomeTable'); + expect(row).to.have.property('tbl_name', 'SomeTable'); + expect(row).to.have.property('sql'); }); - it('should be able to select *', function() { - return this.sequelize.query('SELECT * FROM sqlite_master') - .then(result => { - const rows = result[0]; - expect(rows).to.have.length(2); - rows.forEach(row => { - expect(row).to.have.property('type'); - expect(row).to.have.property('name'); - expect(row).to.have.property('tbl_name'); - expect(row).to.have.property('rootpage'); - expect(row).to.have.property('sql'); - }); - }); + it('should be able to select *', async function() { + const result = await this.sequelize.query('SELECT * FROM sqlite_master'); + const rows = result[0]; + expect(rows).to.have.length(2); + rows.forEach(row => { + expect(row).to.have.property('type'); + expect(row).to.have.property('name'); + expect(row).to.have.property('tbl_name'); + expect(row).to.have.property('rootpage'); + expect(row).to.have.property('sql'); + }); }); - it('should be able to select just "sql" column and get rows back', function() { - return this.sequelize.query('SELECT sql FROM sqlite_master WHERE tbl_name=\'SomeTable\'') - .then(result => { - const rows = result[0]; - expect(rows).to.have.length(1); - const row = rows[0]; - expect(row).to.have.property('sql', - 'CREATE TABLE `SomeTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `someColumn` INTEGER)'); - }); + it('should be able to select just "sql" column and get rows back', async function() { + const result = await this.sequelize.query('SELECT sql FROM sqlite_master WHERE tbl_name=\'SomeTable\''); + const rows = result[0]; + expect(rows).to.have.length(1); + const row = rows[0]; + expect(row).to.have.property('sql', + 'CREATE TABLE `SomeTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `someColumn` INTEGER)'); }); }); } diff --git a/test/integration/error.test.js b/test/integration/error.test.js index e7500cfe3ae5..0f0304f80917 100644 --- a/test/integration/error.test.js +++ b/test/integration/error.test.js @@ -255,7 +255,7 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { }); describe('OptimisticLockError', () => { - it('got correct error type and message', function() { + it('got correct error type and message', async function() { const Account = this.sequelize.define('Account', { number: { type: Sequelize.INTEGER @@ -264,22 +264,21 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { version: true }); - return Account.sync({ force: true }).then(() => { - const result = Account.create({ number: 1 }).then(accountA => { - return Account.findByPk(accountA.id).then(accountB => { - accountA.number += 1; - return accountA.save().then(() => { return accountB; }); - }); - }).then(accountB => { - accountB.number += 1; - return accountB.save(); - }); - - return Promise.all([ - expect(result).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError), - expect(result).to.eventually.be.rejectedWith('Attempting to update a stale model instance: Account') - ]); - }); + await Account.sync({ force: true }); + const result = (async () => { + const accountA = await Account.create({ number: 1 }); + const accountB0 = await Account.findByPk(accountA.id); + accountA.number += 1; + await accountA.save(); + const accountB = await accountB0; + accountB.number += 1; + return await accountB.save(); + })(); + + await Promise.all([ + expect(result).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError), + expect(result).to.eventually.be.rejectedWith('Attempting to update a stale model instance: Account') + ]); }); }); @@ -295,7 +294,7 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } ].forEach(constraintTest => { - it(`Can be intercepted as ${constraintTest.type} using .catch`, function() { + it(`Can be intercepted as ${constraintTest.type} using .catch`, async function() { const spy = sinon.spy(), User = this.sequelize.define('user', { first_name: { @@ -309,18 +308,22 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { }); const record = { first_name: 'jan', last_name: 'meier' }; - return this.sequelize.sync({ force: true }).then(() => { - return User.create(record); - }).then(() => { - return User.create(record).catch(constraintTest.exception, spy); - }).then(() => { - expect(spy).to.have.been.calledOnce; - }); + await this.sequelize.sync({ force: true }); + await User.create(record); + + try { + await User.create(record); + } catch (err) { + if (!(err instanceof constraintTest.exception)) throw err; + await spy(err); + } + + expect(spy).to.have.been.calledOnce; }); }); - it('Supports newlines in keys', function() { + it('Supports newlines in keys', async function() { const spy = sinon.spy(), User = this.sequelize.define('user', { name: { @@ -329,17 +332,20 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ name: 'jan' }); - }).then(() => { - // If the error was successfully parsed, we can catch it! - return User.create({ name: 'jan' }).catch(Sequelize.UniqueConstraintError, spy); - }).then(() => { - expect(spy).to.have.been.calledOnce; - }); + await this.sequelize.sync({ force: true }); + await User.create({ name: 'jan' }); + + try { + await User.create({ name: 'jan' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + await spy(err); + } + + expect(spy).to.have.been.calledOnce; }); - it('Works when unique keys are not defined in sequelize', function() { + it('Works when unique keys are not defined in sequelize', async function() { let User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -347,23 +353,21 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } }, { timestamps: false }); - return this.sequelize.sync({ force: true }).then(() => { - // Now let's pretend the index was created by someone else, and sequelize doesn't know about it - User = this.sequelize.define('user', { - name: Sequelize.STRING - }, { timestamps: false }); - - return User.create({ name: 'jan' }); - }).then(() => { - // It should work even though the unique key is not defined in the model - return expect(User.create({ name: 'jan' })).to.be.rejectedWith(Sequelize.UniqueConstraintError); - }).then(() => { - // And when the model is not passed at all - return expect(this.sequelize.query('INSERT INTO users (name) VALUES (\'jan\')')).to.be.rejectedWith(Sequelize.UniqueConstraintError); - }); + await this.sequelize.sync({ force: true }); + // Now let's pretend the index was created by someone else, and sequelize doesn't know about it + User = this.sequelize.define('user', { + name: Sequelize.STRING + }, { timestamps: false }); + + await User.create({ name: 'jan' }); + // It should work even though the unique key is not defined in the model + await expect(User.create({ name: 'jan' })).to.be.rejectedWith(Sequelize.UniqueConstraintError); + + // And when the model is not passed at all + await expect(this.sequelize.query('INSERT INTO users (name) VALUES (\'jan\')')).to.be.rejectedWith(Sequelize.UniqueConstraintError); }); - it('adds parent and sql properties', function() { + it('adds parent and sql properties', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -371,28 +375,22 @@ describe(Support.getTestDialectTeaser('Sequelize Errors'), () => { } }, { timestamps: false }); - return this.sequelize.sync({ force: true }) - .then(() => { - return User.create({ name: 'jan' }); - }).then(() => { - // Unique key - return expect(User.create({ name: 'jan' })).to.be.rejected; - }).then(error => { - expect(error).to.be.instanceOf(Sequelize.UniqueConstraintError); - expect(error).to.have.property('parent'); - expect(error).to.have.property('original'); - expect(error).to.have.property('sql'); - - return User.create({ id: 2, name: 'jon' }); - }).then(() => { - // Primary key - return expect(User.create({ id: 2, name: 'jon' })).to.be.rejected; - }).then(error => { - expect(error).to.be.instanceOf(Sequelize.UniqueConstraintError); - expect(error).to.have.property('parent'); - expect(error).to.have.property('original'); - expect(error).to.have.property('sql'); - }); + await this.sequelize.sync({ force: true }); + await User.create({ name: 'jan' }); + // Unique key + const error0 = await expect(User.create({ name: 'jan' })).to.be.rejected; + expect(error0).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(error0).to.have.property('parent'); + expect(error0).to.have.property('original'); + expect(error0).to.have.property('sql'); + + await User.create({ id: 2, name: 'jon' }); + // Primary key + const error = await expect(User.create({ id: 2, name: 'jon' })).to.be.rejected; + expect(error).to.be.instanceOf(Sequelize.UniqueConstraintError); + expect(error).to.have.property('parent'); + expect(error).to.have.property('original'); + expect(error).to.have.property('sql'); }); }); }); diff --git a/test/integration/hooks/associations.test.js b/test/integration/hooks/associations.test.js index d5b959d44b28..9140abb762d4 100644 --- a/test/integration/hooks/associations.test.js +++ b/test/integration/hooks/associations.test.js @@ -3,14 +3,12 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise, DataTypes = require('../../../lib/data-types'), sinon = require('sinon'), dialect = Support.getTestDialect(); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -32,14 +30,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('associations', () => { describe('1:1', () => { describe('cascade onUpdate', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -51,54 +49,49 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasOne(this.Tasks, { onUpdate: 'cascade', hooks: true }); this.Tasks.belongsTo(this.Projects); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); - it('on success', function() { + it('on success', async function() { let beforeHook = false, afterHook = false; - this.Tasks.hooks.add('beforeUpdate', () => { + this.Tasks.beforeUpdate(async () => { beforeHook = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('afterUpdate', () => { + this.Tasks.afterUpdate(async () => { afterHook = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return project.update({ id: 2 }).then(() => { - expect(beforeHook).to.be.true; - expect(afterHook).to.be.true; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await project.update({ id: 2 }); + expect(beforeHook).to.be.true; + expect(afterHook).to.be.true; }); - it('on error', function() { - this.Tasks.hooks.add('afterUpdate', () => { - return Promise.reject(new Error('Whoops!')); + it('on error', async function() { + this.Tasks.afterUpdate(async () => { + throw new Error('Whoops!'); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).catch(err => { - expect(err).to.be.instanceOf(Error); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + try { + await project.setTask(task); + } catch (err) { + expect(err).to.be.instanceOf(Error); + } }); }); describe('cascade onDelete', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -110,80 +103,69 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasOne(this.Tasks, { onDelete: 'CASCADE', hooks: true }); this.Tasks.belongsTo(this.Projects); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeDestroy', beforeTask); - this.Tasks.hooks.add('afterDestroy', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).to.have.been.calledOnce; - }); - }); - }); - }); + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeDestroy(beforeTask); + this.Tasks.afterDestroy(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await project.destroy(); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).to.have.been.calledOnce; + expect(afterTask).to.have.been.calledOnce; }); - it('with errors', function() { + it('with errors', async function() { const CustomErrorText = 'Whoops!'; let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.reject(new Error(CustomErrorText)); + throw new Error(CustomErrorText); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; }); }); }); describe('no cascade update', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -195,45 +177,40 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasOne(this.Tasks); this.Tasks.belongsTo(this.Projects); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); - it('on success', function() { + it('on success', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.Tasks.hooks.add('beforeUpdate', beforeHook); - this.Tasks.hooks.add('afterUpdate', afterHook); + this.Tasks.beforeUpdate(beforeHook); + this.Tasks.afterUpdate(afterHook); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.setTask(task).then(() => { - return project.update({ id: 2 }).then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.setTask(task); + await project.update({ id: 2 }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); - it('on error', function() { - this.Tasks.hooks.add('afterUpdate', () => { + it('on error', async function() { + this.Tasks.afterUpdate(() => { throw new Error('Whoops!'); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return expect(project.setTask(task)).to.be.rejected; - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + await expect(project.setTask(task)).to.be.rejected; }); }); describe('no cascade delete', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -245,62 +222,59 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasMany(this.Tasks); this.Tasks.belongsTo(this.Projects); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeUpdate', beforeTask); - this.Tasks.hooks.add('afterUpdate', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.removeTask(task).then(() => { - expect(beforeProject).to.have.been.called; - expect(afterProject).to.have.been.called; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeUpdate(beforeTask); + this.Tasks.afterUpdate(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.removeTask(task); + expect(beforeProject).to.have.been.called; + expect(afterProject).to.have.been.called; + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeUpdate', () => { + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeUpdate(() => { beforeTask(); throw new Error('Whoops!'); }); - this.Tasks.hooks.add('afterUpdate', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).not.to.have.been.called; - }); - }); - }); + this.Tasks.afterUpdate(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + try { + await project.addTask(task); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).to.have.been.calledOnce; + expect(afterTask).not.to.have.been.called; + } }); }); }); @@ -308,7 +282,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('1:M', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -320,82 +294,75 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasMany(this.Tasks, { onDelete: 'cascade', hooks: true }); this.Tasks.belongsTo(this.Projects, { hooks: true }); - return this.Projects.sync({ force: true }).then(() => { - return this.Tasks.sync({ force: true }); - }); + await this.Projects.sync({ force: true }); + + await this.Tasks.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeDestroy', beforeTask); - this.Tasks.hooks.add('afterDestroy', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).to.have.been.calledOnce; - expect(afterTask).to.have.been.calledOnce; - }); - }); - }); - }); + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeDestroy(beforeTask); + this.Tasks.afterDestroy(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.destroy(); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).to.have.been.calledOnce; + expect(afterTask).to.have.been.calledOnce; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + + try { + await project.destroy(); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; + } }); }); }); describe('no cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -407,72 +374,66 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.hasMany(this.Tasks); this.Tasks.belongsTo(this.Projects); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeUpdate', beforeTask); - this.Tasks.hooks.add('afterUpdate', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.removeTask(task).then(() => { - expect(beforeProject).to.have.been.called; - expect(afterProject).to.have.been.called; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeUpdate(beforeTask); + this.Tasks.afterUpdate(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.removeTask(task); + expect(beforeProject).to.have.been.called; + expect(afterProject).to.have.been.called; + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeUpdate', () => { + this.Tasks.beforeUpdate(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.hooks.add('afterUpdate', () => { + this.Tasks.afterUpdate(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + + try { + await project.addTask(task); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; + } }); }); }); @@ -480,7 +441,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('M:M', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -492,80 +453,69 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.belongsToMany(this.Tasks, { cascade: 'onDelete', through: 'projects_and_tasks', hooks: true }); this.Tasks.belongsToMany(this.Projects, { cascade: 'onDelete', through: 'projects_and_tasks', hooks: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeDestroy', beforeTask); - this.Tasks.hooks.add('afterDestroy', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - // Since Sequelize does not cascade M:M, these should be false - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeDestroy(beforeTask); + this.Tasks.afterDestroy(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.destroy(); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + // Since Sequelize does not cascade M:M, these should be false + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.destroy().then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.false; - expect(afterTask).to.be.false; - }); - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.destroy(); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.false; + expect(afterTask).to.be.false; }); }); }); describe('no cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -577,71 +527,61 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.Projects.belongsToMany(this.Tasks, { hooks: true, through: 'project_tasks' }); this.Tasks.belongsToMany(this.Projects, { hooks: true, through: 'project_tasks' }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { const beforeProject = sinon.spy(), afterProject = sinon.spy(), beforeTask = sinon.spy(), afterTask = sinon.spy(); - this.Projects.hooks.add('beforeCreate', beforeProject); - this.Projects.hooks.add('afterCreate', afterProject); - this.Tasks.hooks.add('beforeUpdate', beforeTask); - this.Tasks.hooks.add('afterUpdate', afterTask); - - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - return project.removeTask(task).then(() => { - expect(beforeProject).to.have.been.calledOnce; - expect(afterProject).to.have.been.calledOnce; - expect(beforeTask).not.to.have.been.called; - expect(afterTask).not.to.have.been.called; - }); - }); - }); - }); + this.Projects.beforeCreate(beforeProject); + this.Projects.afterCreate(afterProject); + this.Tasks.beforeUpdate(beforeTask); + this.Tasks.afterUpdate(afterTask); + + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + await project.removeTask(task); + expect(beforeProject).to.have.been.calledOnce; + expect(afterProject).to.have.been.calledOnce; + expect(beforeTask).not.to.have.been.called; + expect(afterTask).not.to.have.been.called; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, afterTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeUpdate', () => { + this.Tasks.beforeUpdate(async () => { beforeTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.Tasks.hooks.add('afterUpdate', () => { + this.Tasks.afterUpdate(async () => { afterTask = true; - return Promise.resolve(); }); - return this.Projects.create({ title: 'New Project' }).then(project => { - return this.Tasks.create({ title: 'New Task' }).then(task => { - return project.addTask(task).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.false; - expect(afterTask).to.be.false; - }); - }); - }); + const project = await this.Projects.create({ title: 'New Project' }); + const task = await this.Tasks.create({ title: 'New Task' }); + await project.addTask(task); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.false; + expect(afterTask).to.be.false; }); }); }); @@ -652,7 +592,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('multiple 1:M', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -674,11 +614,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.MiniTasks.belongsTo(this.Projects, { hooks: true }); this.MiniTasks.belongsTo(this.Tasks, { hooks: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -686,55 +626,46 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - this.MiniTasks.hooks.add('beforeDestroy', () => { + this.MiniTasks.beforeDestroy(async () => { beforeMiniTask = true; - return Promise.resolve(); }); - this.MiniTasks.hooks.add('afterDestroy', () => { + this.MiniTasks.afterDestroy(async () => { afterMiniTask = true; - return Promise.resolve(); }); - return Sequelize.Promise.all([ + const [project0, minitask] = await Promise.all([ this.Projects.create({ title: 'New Project' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, minitask]) => { - return project.addMiniTask(minitask); - }).then(project => { - return project.destroy(); - }).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.false; - expect(afterTask).to.be.false; - expect(beforeMiniTask).to.be.true; - expect(afterMiniTask).to.be.true; - }); + ]); + const project = await project0.addMiniTask(minitask); + await project.destroy(); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.false; + expect(afterTask).to.be.false; + expect(beforeMiniTask).to.be.true; + expect(afterMiniTask).to.be.true; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -742,51 +673,47 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - this.MiniTasks.hooks.add('beforeDestroy', () => { + this.MiniTasks.beforeDestroy(async () => { beforeMiniTask = true; - return Promise.reject(new Error('Whoops!')); + throw new Error('Whoops!'); }); - this.MiniTasks.hooks.add('afterDestroy', () => { + this.MiniTasks.afterDestroy(async () => { afterMiniTask = true; - return Promise.resolve(); }); - return Sequelize.Promise.all([ - this.Projects.create({ title: 'New Project' }), - this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, minitask]) => { - return project.addMiniTask(minitask); - }).then(project => { - return project.destroy(); - }).catch(() => { + try { + const [project0, minitask] = await Promise.all([ + this.Projects.create({ title: 'New Project' }), + this.MiniTasks.create({ mini_title: 'New MiniTask' }) + ]); + + const project = await project0.addMiniTask(minitask); + await project.destroy(); + } catch (err) { expect(beforeProject).to.be.true; expect(afterProject).to.be.true; expect(beforeTask).to.be.false; expect(afterTask).to.be.false; expect(beforeMiniTask).to.be.true; expect(afterMiniTask).to.be.false; - }); + } }); }); }); @@ -794,7 +721,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { describe('multiple 1:M sequential hooks', () => { describe('cascade', () => { - beforeEach(function() { + beforeEach(async function() { this.Projects = this.sequelize.define('Project', { title: DataTypes.STRING }); @@ -816,11 +743,11 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { this.MiniTasks.belongsTo(this.Projects, { hooks: true }); this.MiniTasks.belongsTo(this.Tasks, { hooks: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#remove', () => { - it('with no errors', function() { + it('with no errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -828,58 +755,52 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { beforeMiniTask = false, afterMiniTask = false; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(async () => { beforeProject = true; - return Promise.resolve(); }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(async () => { afterProject = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(async () => { beforeTask = true; - return Promise.resolve(); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(async () => { afterTask = true; - return Promise.resolve(); }); - this.MiniTasks.hooks.add('beforeDestroy', () => { + this.MiniTasks.beforeDestroy(async () => { beforeMiniTask = true; - return Promise.resolve(); }); - this.MiniTasks.hooks.add('afterDestroy', () => { + this.MiniTasks.afterDestroy(async () => { afterMiniTask = true; - return Promise.resolve(); }); - return Sequelize.Promise.all([ + const [project0, task, minitask] = await Promise.all([ this.Projects.create({ title: 'New Project' }), this.Tasks.create({ title: 'New Task' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, task, minitask]) => { - return Sequelize.Promise.all([ - task.addMiniTask(minitask), - project.addTask(task) - ]).return(project); - }).then(project => { - return project.destroy(); - }).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.true; - expect(beforeMiniTask).to.be.true; - expect(afterMiniTask).to.be.true; - }); + ]); + + await Promise.all([ + task.addMiniTask(minitask), + project0.addTask(task) + ]); + + const project = project0; + await project.destroy(); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.true; + expect(beforeMiniTask).to.be.true; + expect(afterMiniTask).to.be.true; }); - it('with errors', function() { + it('with errors', async function() { let beforeProject = false, afterProject = false, beforeTask = false, @@ -888,50 +809,50 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { afterMiniTask = false; const CustomErrorText = 'Whoops!'; - this.Projects.hooks.add('beforeCreate', () => { + this.Projects.beforeCreate(() => { beforeProject = true; }); - this.Projects.hooks.add('afterCreate', () => { + this.Projects.afterCreate(() => { afterProject = true; }); - this.Tasks.hooks.add('beforeDestroy', () => { + this.Tasks.beforeDestroy(() => { beforeTask = true; throw new Error(CustomErrorText); }); - this.Tasks.hooks.add('afterDestroy', () => { + this.Tasks.afterDestroy(() => { afterTask = true; }); - this.MiniTasks.hooks.add('beforeDestroy', () => { + this.MiniTasks.beforeDestroy(() => { beforeMiniTask = true; }); - this.MiniTasks.hooks.add('afterDestroy', () => { + this.MiniTasks.afterDestroy(() => { afterMiniTask = true; }); - return Sequelize.Promise.all([ + const [project0, task, minitask] = await Promise.all([ this.Projects.create({ title: 'New Project' }), this.Tasks.create({ title: 'New Task' }), this.MiniTasks.create({ mini_title: 'New MiniTask' }) - ]).then(([project, task, minitask]) => { - return Sequelize.Promise.all([ - task.addMiniTask(minitask), - project.addTask(task) - ]).return(project); - }).then(project => { - return expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText).then(() => { - expect(beforeProject).to.be.true; - expect(afterProject).to.be.true; - expect(beforeTask).to.be.true; - expect(afterTask).to.be.false; - expect(beforeMiniTask).to.be.false; - expect(afterMiniTask).to.be.false; - }); - }); + ]); + + await Promise.all([ + task.addMiniTask(minitask), + project0.addTask(task) + ]); + + const project = project0; + await expect(project.destroy()).to.eventually.be.rejectedWith(CustomErrorText); + expect(beforeProject).to.be.true; + expect(afterProject).to.be.true; + expect(beforeTask).to.be.true; + expect(afterTask).to.be.false; + expect(beforeMiniTask).to.be.false; + expect(afterMiniTask).to.be.false; }); }); }); diff --git a/test/integration/hooks/bulkOperation.test.js b/test/integration/hooks/bulkOperation.test.js index a8fd02d85848..040955024d80 100644 --- a/test/integration/hooks/bulkOperation.test.js +++ b/test/integration/hooks/bulkOperation.test.js @@ -4,11 +4,10 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -30,47 +29,47 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#bulkCreate', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.hooks.add('beforeBulkCreate', beforeBulk); + this.User.beforeBulkCreate(beforeBulk); - this.User.hooks.add('afterBulkCreate', afterBulk); + this.User.afterBulkCreate(afterBulk); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + ]); + + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { - this.User.hooks.add('beforeBulkCreate', () => { + it('should return an error from before', async function() { + this.User.beforeBulkCreate(() => { throw new Error('Whoops!'); }); - return expect(this.User.bulkCreate([ + await expect(this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } ])).to.be.rejected; }); - it('should return an error from after', function() { - this.User.hooks.add('afterBulkCreate', () => { + it('should return an error from after', async function() { + this.User.afterBulkCreate(() => { throw new Error('Whoops!'); }); - return expect(this.User.bulkCreate([ + await expect(this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } ])).to.be.rejected; @@ -78,7 +77,7 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -94,126 +93,119 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should run the afterCreate/beforeCreate functions for each item created successfully', function() { + it('should run the afterCreate/beforeCreate functions for each item created successfully', async function() { let beforeBulkCreate = false, afterBulkCreate = false; - this.User.hooks.add('beforeBulkCreate', () => { + this.User.beforeBulkCreate(async () => { beforeBulkCreate = true; - return Promise.resolve(); }); - this.User.hooks.add('afterBulkCreate', () => { + this.User.afterBulkCreate(async () => { afterBulkCreate = true; - return Promise.resolve(); }); - this.User.hooks.add('beforeCreate', user => { + this.User.beforeCreate(async user => { user.beforeHookTest = true; - return Promise.resolve(); }); - this.User.hooks.add('afterCreate', user => { + this.User.afterCreate(async user => { user.username = `User${user.id}`; - return Promise.resolve(); }); - return this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }).then(records => { - records.forEach(record => { - expect(record.username).to.equal(`User${record.id}`); - expect(record.beforeHookTest).to.be.true; - }); - expect(beforeBulkCreate).to.be.true; - expect(afterBulkCreate).to.be.true; + const records = await this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }); + records.forEach(record => { + expect(record.username).to.equal(`User${record.id}`); + expect(record.beforeHookTest).to.be.true; }); + expect(beforeBulkCreate).to.be.true; + expect(afterBulkCreate).to.be.true; }); - it('should run the afterCreate/beforeCreate functions for each item created with an error', function() { + it('should run the afterCreate/beforeCreate functions for each item created with an error', async function() { let beforeBulkCreate = false, afterBulkCreate = false; - this.User.hooks.add('beforeBulkCreate', () => { + this.User.beforeBulkCreate(async () => { beforeBulkCreate = true; - return Promise.resolve(); }); - this.User.hooks.add('afterBulkCreate', () => { + this.User.afterBulkCreate(async () => { afterBulkCreate = true; - return Promise.resolve(); }); - this.User.hooks.add('beforeCreate', () => { - return Promise.reject(new Error('You shall not pass!')); + this.User.beforeCreate(async () => { + throw new Error('You shall not pass!'); }); - this.User.hooks.add('afterCreate', user => { + this.User.afterCreate(async user => { user.username = `User${user.id}`; - return Promise.resolve(); }); - return this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }).catch(err => { + try { + await this.User.bulkCreate([{ aNumber: 5 }, { aNumber: 7 }, { aNumber: 3 }], { fields: ['aNumber'], individualHooks: true }); + } catch (err) { expect(err).to.be.instanceOf(Error); expect(beforeBulkCreate).to.be.true; expect(afterBulkCreate).to.be.false; - }); + } }); }); }); describe('#bulkUpdate', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.hooks.add('beforeBulkUpdate', beforeBulk); - this.User.hooks.add('afterBulkUpdate', afterBulk); + this.User.beforeBulkUpdate(beforeBulk); + this.User.afterBulkUpdate(afterBulk); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - return this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); - }); + ]); + + await this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { - this.User.hooks.add('beforeBulkUpdate', () => { + it('should return an error from before', async function() { + this.User.beforeBulkUpdate(() => { throw new Error('Whoops!'); }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - return expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; - }); + ]); + + await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; }); - it('should return an error from after', function() { - this.User.hooks.add('afterBulkUpdate', () => { + it('should return an error from after', async function() { + this.User.afterBulkUpdate(() => { throw new Error('Whoops!'); }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { username: 'Cheech', mood: 'sad' }, { username: 'Chong', mood: 'sad' } - ]).then(() => { - return expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; - }); + ]); + + await expect(this.User.update({ mood: 'happy' }, { where: { mood: 'sad' } })).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -229,123 +221,122 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should run the after/before functions for each item created successfully', function() { + it('should run the after/before functions for each item created successfully', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.hooks.add('beforeBulkUpdate', beforeBulk); + this.User.beforeBulkUpdate(beforeBulk); - this.User.hooks.add('afterBulkUpdate', afterBulk); + this.User.afterBulkUpdate(afterBulk); - this.User.hooks.add('beforeUpdate', user => { + this.User.beforeUpdate(user => { expect(user.changed()).to.not.be.empty; user.beforeHookTest = true; }); - this.User.hooks.add('afterUpdate', user => { + this.User.afterUpdate(user => { user.username = `User${user.id}`; }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }).then(([, records]) => { - records.forEach(record => { - expect(record.username).to.equal(`User${record.id}`); - expect(record.beforeHookTest).to.be.true; - }); - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + ]); + + const [, records] = await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); + records.forEach(record => { + expect(record.username).to.equal(`User${record.id}`); + expect(record.beforeHookTest).to.be.true; }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); - it('should run the after/before functions for each item created successfully changing some data before updating', function() { - this.User.hooks.add('beforeUpdate', user => { + it('should run the after/before functions for each item created successfully changing some data before updating', async function() { + this.User.beforeUpdate(user => { expect(user.changed()).to.not.be.empty; if (user.get('id') === 1) { user.set('aNumber', user.get('aNumber') + 3); } }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }).then(([, records]) => { - records.forEach(record => { - expect(record.aNumber).to.equal(10 + (record.id === 1 ? 3 : 0)); - }); - }); + ]); + + const [, records] = await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); + records.forEach(record => { + expect(record.aNumber).to.equal(10 + (record.id === 1 ? 3 : 0)); }); }); - it('should run the after/before functions for each item created with an error', function() { + it('should run the after/before functions for each item created with an error', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.hooks.add('beforeBulkUpdate', beforeBulk); + this.User.beforeBulkUpdate(beforeBulk); - this.User.hooks.add('afterBulkUpdate', afterBulk); + this.User.afterBulkUpdate(afterBulk); - this.User.hooks.add('beforeUpdate', () => { + this.User.beforeUpdate(() => { throw new Error('You shall not pass!'); }); - this.User.hooks.add('afterUpdate', user => { + this.User.afterUpdate(user => { user.username = `User${user.id}`; }); - return this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { - return this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(err.message).to.be.equal('You shall not pass!'); - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).not.to.have.been.called; - }); - }); + await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }); + + try { + await this.User.update({ aNumber: 10 }, { where: { aNumber: 1 }, individualHooks: true }); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(err.message).to.be.equal('You shall not pass!'); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).not.to.have.been.called; + } }); }); }); describe('#bulkDestroy', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.User.hooks.add('beforeBulkDestroy', beforeBulk); - this.User.hooks.add('afterBulkDestroy', afterBulk); + this.User.beforeBulkDestroy(beforeBulk); + this.User.afterBulkDestroy(afterBulk); - return this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + await this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { - this.User.hooks.add('beforeBulkDestroy', () => { + it('should return an error from before', async function() { + this.User.beforeBulkDestroy(() => { throw new Error('Whoops!'); }); - return expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; + await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; }); - it('should return an error from after', function() { - this.User.hooks.add('afterBulkDestroy', () => { + it('should return an error from after', async function() { + this.User.afterBulkDestroy(() => { throw new Error('Whoops!'); }); - return expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; + await expect(this.User.destroy({ where: { username: 'Cheech', mood: 'sad' } })).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -361,131 +352,124 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should run the after/before functions for each item created successfully', function() { + it('should run the after/before functions for each item created successfully', async function() { let beforeBulk = false, afterBulk = false, beforeHook = false, afterHook = false; - this.User.hooks.add('beforeBulkDestroy', () => { + this.User.beforeBulkDestroy(async () => { beforeBulk = true; - return Promise.resolve(); }); - this.User.hooks.add('afterBulkDestroy', () => { + this.User.afterBulkDestroy(async () => { afterBulk = true; - return Promise.resolve(); }); - this.User.hooks.add('beforeDestroy', () => { + this.User.beforeDestroy(async () => { beforeHook = true; - return Promise.resolve(); }); - this.User.hooks.add('afterDestroy', () => { + this.User.afterDestroy(async () => { afterHook = true; - return Promise.resolve(); }); - return this.User.bulkCreate([ + await this.User.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }).then(() => { - expect(beforeBulk).to.be.true; - expect(afterBulk).to.be.true; - expect(beforeHook).to.be.true; - expect(afterHook).to.be.true; - }); - }); + ]); + + await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }); + expect(beforeBulk).to.be.true; + expect(afterBulk).to.be.true; + expect(beforeHook).to.be.true; + expect(afterHook).to.be.true; }); - it('should run the after/before functions for each item created with an error', function() { + it('should run the after/before functions for each item created with an error', async function() { let beforeBulk = false, afterBulk = false, beforeHook = false, afterHook = false; - this.User.hooks.add('beforeBulkDestroy', () => { + this.User.beforeBulkDestroy(async () => { beforeBulk = true; - return Promise.resolve(); }); - this.User.hooks.add('afterBulkDestroy', () => { + this.User.afterBulkDestroy(async () => { afterBulk = true; - return Promise.resolve(); }); - this.User.hooks.add('beforeDestroy', () => { + this.User.beforeDestroy(async () => { beforeHook = true; - return Promise.reject(new Error('You shall not pass!')); + throw new Error('You shall not pass!'); }); - this.User.hooks.add('afterDestroy', () => { + this.User.afterDestroy(async () => { afterHook = true; - return Promise.resolve(); }); - return this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { - return this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }).catch(err => { - expect(err).to.be.instanceOf(Error); - expect(beforeBulk).to.be.true; - expect(beforeHook).to.be.true; - expect(afterBulk).to.be.false; - expect(afterHook).to.be.false; - }); - }); + await this.User.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }); + + try { + await this.User.destroy({ where: { aNumber: 1 }, individualHooks: true }); + } catch (err) { + expect(err).to.be.instanceOf(Error); + expect(beforeBulk).to.be.true; + expect(beforeHook).to.be.true; + expect(afterBulk).to.be.false; + expect(afterHook).to.be.false; + } }); }); }); describe('#bulkRestore', () => { - beforeEach(function() { - return this.ParanoidUser.bulkCreate([ + beforeEach(async function() { + await this.ParanoidUser.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' } - ]).then(() => { - return this.ParanoidUser.destroy({ truncate: true }); - }); + ]); + + await this.ParanoidUser.destroy({ truncate: true }); }); describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(); - this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk); - this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk); + this.ParanoidUser.beforeBulkRestore(beforeBulk); + this.ParanoidUser.afterBulkRestore(afterBulk); - return this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - }); + await this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { - this.ParanoidUser.hooks.add('beforeBulkRestore', () => { + it('should return an error from before', async function() { + this.ParanoidUser.beforeBulkRestore(() => { throw new Error('Whoops!'); }); - return expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; + await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; }); - it('should return an error from after', function() { - this.ParanoidUser.hooks.add('afterBulkRestore', () => { + it('should return an error from after', async function() { + this.ParanoidUser.afterBulkRestore(() => { throw new Error('Whoops!'); }); - return expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; + await expect(this.ParanoidUser.restore({ where: { username: 'adam', mood: 'happy' } })).to.be.rejected; }); }); describe('with the {individualHooks: true} option', () => { - beforeEach(function() { + beforeEach(async function() { this.ParanoidUser = this.sequelize.define('ParanoidUser', { aNumber: { type: DataTypes.INTEGER, @@ -495,60 +479,58 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.ParanoidUser.sync({ force: true }); + await this.ParanoidUser.sync({ force: true }); }); - it('should run the after/before functions for each item restored successfully', function() { + it('should run the after/before functions for each item restored successfully', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(), beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk); - this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk); - this.ParanoidUser.hooks.add('beforeRestore', beforeHook); - this.ParanoidUser.hooks.add('afterRestore', afterHook); + this.ParanoidUser.beforeBulkRestore(beforeBulk); + this.ParanoidUser.afterBulkRestore(afterBulk); + this.ParanoidUser.beforeRestore(beforeHook); + this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.bulkCreate([ + await this.ParanoidUser.bulkCreate([ { aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 } - ]).then(() => { - return this.ParanoidUser.destroy({ where: { aNumber: 1 } }); - }).then(() => { - return this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); - }).then(() => { - expect(beforeBulk).to.have.been.calledOnce; - expect(afterBulk).to.have.been.calledOnce; - expect(beforeHook).to.have.been.calledThrice; - expect(afterHook).to.have.been.calledThrice; - }); + ]); + + await this.ParanoidUser.destroy({ where: { aNumber: 1 } }); + await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); + expect(beforeBulk).to.have.been.calledOnce; + expect(afterBulk).to.have.been.calledOnce; + expect(beforeHook).to.have.been.calledThrice; + expect(afterHook).to.have.been.calledThrice; }); - it('should run the after/before functions for each item restored with an error', function() { + it('should run the after/before functions for each item restored with an error', async function() { const beforeBulk = sinon.spy(), afterBulk = sinon.spy(), beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.hooks.add('beforeBulkRestore', beforeBulk); - this.ParanoidUser.hooks.add('afterBulkRestore', afterBulk); - this.ParanoidUser.hooks.add('beforeRestore', () => { + this.ParanoidUser.beforeBulkRestore(beforeBulk); + this.ParanoidUser.afterBulkRestore(afterBulk); + this.ParanoidUser.beforeRestore(async () => { beforeHook(); - return Promise.reject(new Error('You shall not pass!')); + throw new Error('You shall not pass!'); }); - this.ParanoidUser.hooks.add('afterRestore', afterHook); + this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }).then(() => { - return this.ParanoidUser.destroy({ where: { aNumber: 1 } }); - }).then(() => { - return this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); - }).catch(err => { + try { + await this.ParanoidUser.bulkCreate([{ aNumber: 1 }, { aNumber: 1 }, { aNumber: 1 }], { fields: ['aNumber'] }); + await this.ParanoidUser.destroy({ where: { aNumber: 1 } }); + await this.ParanoidUser.restore({ where: { aNumber: 1 }, individualHooks: true }); + } catch (err) { expect(err).to.be.instanceOf(Error); expect(beforeBulk).to.have.been.calledOnce; expect(beforeHook).to.have.been.calledThrice; expect(afterBulk).not.to.have.been.called; expect(afterHook).not.to.have.been.called; - }); + } }); }); }); diff --git a/test/integration/hooks/count.test.js b/test/integration/hooks/count.test.js index d734802c6af4..a97a2084c0fc 100644 --- a/test/integration/hooks/count.test.js +++ b/test/integration/hooks/count.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -17,12 +17,12 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#count', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' }, { username: 'joe', mood: 'happy' } @@ -30,35 +30,34 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); describe('on success', () => { - it('hook runs', function() { + it('hook runs', async function() { let beforeHook = false; - this.User.hooks.add('beforeCount', () => { + this.User.beforeCount(() => { beforeHook = true; }); - return this.User.count().then(count => { - expect(count).to.equal(3); - expect(beforeHook).to.be.true; - }); + const count = await this.User.count(); + expect(count).to.equal(3); + expect(beforeHook).to.be.true; }); - it('beforeCount hook can change options', function() { - this.User.hooks.add('beforeCount', options => { + it('beforeCount hook can change options', async function() { + this.User.beforeCount(options => { options.where.username = 'adam'; }); - return expect(this.User.count({ where: { username: 'joe' } })).to.eventually.equal(1); + await expect(this.User.count({ where: { username: 'joe' } })).to.eventually.equal(1); }); }); describe('on error', () => { - it('in beforeCount hook returns error', function() { - this.User.hooks.add('beforeCount', () => { + it('in beforeCount hook returns error', async function() { + this.User.beforeCount(() => { throw new Error('Oops!'); }); - return expect(this.User.count({ where: { username: 'adam' } })).to.be.rejectedWith('Oops!'); + await expect(this.User.count({ where: { username: 'adam' } })).to.be.rejectedWith('Oops!'); }); }); }); diff --git a/test/integration/hooks/create.test.js b/test/integration/hooks/create.test.js index b9d43d944979..da684deff9f6 100644 --- a/test/integration/hooks/create.test.js +++ b/test/integration/hooks/create.test.js @@ -5,11 +5,10 @@ const chai = require('chai'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), Sequelize = Support.Sequelize, - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -20,79 +19,76 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#create', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.hooks.add('beforeCreate', beforeHook); - this.User.hooks.add('afterCreate', afterHook); - this.User.hooks.add('beforeSave', beforeSave); - this.User.hooks.add('afterSave', afterSave); + this.User.beforeCreate(beforeHook); + this.User.afterCreate(afterHook); + this.User.beforeSave(beforeSave); + this.User.afterSave(afterSave); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterSave).to.have.been.calledOnce; - }); + await this.User.create({ username: 'Toni', mood: 'happy' }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledOnce; + expect(afterSave).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), beforeSave = sinon.spy(), afterHook = sinon.spy(), afterSave = sinon.spy(); - this.User.hooks.add('beforeCreate', () => { + this.User.beforeCreate(() => { beforeHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('afterCreate', afterHook); - this.User.hooks.add('beforeSave', beforeSave); - this.User.hooks.add('afterSave', afterSave); - - return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - expect(beforeSave).not.to.have.been.called; - expect(afterSave).not.to.have.been.called; - }); + this.User.afterCreate(afterHook); + this.User.beforeSave(beforeSave); + this.User.afterSave(afterSave); + + await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; + expect(beforeSave).not.to.have.been.called; + expect(afterSave).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), beforeSave = sinon.spy(), afterHook = sinon.spy(), afterSave = sinon.spy(); - this.User.hooks.add('beforeCreate', beforeHook); - this.User.hooks.add('afterCreate', () => { + this.User.beforeCreate(beforeHook); + this.User.afterCreate(() => { afterHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('beforeSave', beforeSave); - this.User.hooks.add('afterSave', afterSave); - - return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterSave).not.to.have.been.called; - }); + this.User.beforeSave(beforeSave); + this.User.afterSave(afterSave); + + await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledOnce; + expect(afterSave).not.to.have.been.called; }); }); - it('should not trigger hooks on parent when using N:M association setters', function() { + it('should not trigger hooks on parent when using N:M association setters', async function() { const A = this.sequelize.define('A', { name: Sequelize.STRING }); @@ -102,105 +98,98 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { let hookCalled = 0; - A.hooks.add('afterCreate', () => { + A.addHook('afterCreate', async () => { hookCalled++; - return Promise.resolve(); }); B.belongsToMany(A, { through: 'a_b' }); A.belongsToMany(B, { through: 'a_b' }); - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ - A.create({ name: 'a' }), - B.create({ name: 'b' }) - ]).then(([a, b]) => { - return a.addB(b).then(() => { - expect(hookCalled).to.equal(1); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [a, b] = await Promise.all([ + A.create({ name: 'a' }), + B.create({ name: 'b' }) + ]); + + await a.addB(b); + expect(hookCalled).to.equal(1); }); describe('preserves changes to instance', () => { - it('beforeValidate', function() { + it('beforeValidate', async function() { let hookCalled = 0; - this.User.hooks.add('beforeValidate', user => { + this.User.beforeValidate(user => { user.mood = 'happy'; hookCalled++; }); - return this.User.create({ mood: 'sad', username: 'leafninja' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('leafninja'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ mood: 'sad', username: 'leafninja' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('leafninja'); + expect(hookCalled).to.equal(1); }); - it('afterValidate', function() { + it('afterValidate', async function() { let hookCalled = 0; - this.User.hooks.add('afterValidate', user => { + this.User.afterValidate(user => { user.mood = 'neutral'; hookCalled++; }); - return this.User.create({ mood: 'sad', username: 'fireninja' }).then(user => { - expect(user.mood).to.equal('neutral'); - expect(user.username).to.equal('fireninja'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ mood: 'sad', username: 'fireninja' }); + expect(user.mood).to.equal('neutral'); + expect(user.username).to.equal('fireninja'); + expect(hookCalled).to.equal(1); }); - it('beforeCreate', function() { + it('beforeCreate', async function() { let hookCalled = 0; - this.User.hooks.add('beforeCreate', user => { + this.User.beforeCreate(user => { user.mood = 'happy'; hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('akira'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ username: 'akira' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('akira'); + expect(hookCalled).to.equal(1); }); - it('beforeSave', function() { + it('beforeSave', async function() { let hookCalled = 0; - this.User.hooks.add('beforeSave', user => { + this.User.beforeSave(user => { user.mood = 'happy'; hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('akira'); - expect(hookCalled).to.equal(1); - }); + const user = await this.User.create({ username: 'akira' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('akira'); + expect(hookCalled).to.equal(1); }); - it('beforeSave with beforeCreate', function() { + it('beforeSave with beforeCreate', async function() { let hookCalled = 0; - this.User.hooks.add('beforeCreate', user => { + this.User.beforeCreate(user => { user.mood = 'sad'; hookCalled++; }); - this.User.hooks.add('beforeSave', user => { + this.User.beforeSave(user => { user.mood = 'happy'; hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('akira'); - expect(hookCalled).to.equal(2); - }); + const user = await this.User.create({ username: 'akira' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('akira'); + expect(hookCalled).to.equal(2); }); }); }); diff --git a/test/integration/hooks/destroy.test.js b/test/integration/hooks/destroy.test.js index 85431bdab4dc..6dfa3e7f61c3 100644 --- a/test/integration/hooks/destroy.test.js +++ b/test/integration/hooks/destroy.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,62 +18,56 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#destroy', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeDestroy', beforeHook); - this.User.hooks.add('afterDestroy', afterHook); + this.User.beforeDestroy(beforeHook); + this.User.afterDestroy(afterHook); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeDestroy', () => { + this.User.beforeDestroy(() => { beforeHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('afterDestroy', afterHook); + this.User.afterDestroy(afterHook); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.destroy()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.destroy()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeDestroy', beforeHook); - this.User.hooks.add('afterDestroy', () => { + this.User.beforeDestroy(beforeHook); + this.User.afterDestroy(() => { afterHook(); throw new Error('Whoops!'); }); - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.destroy()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.destroy()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); @@ -96,26 +90,22 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - it('sets other changed values when soft deleting and a beforeDestroy hooks kicks in', function() { - return this.ParanoidUser.sync({ force: true }) - .then(() => this.ParanoidUser.create({ username: 'user1' })) - .then(user => user.destroy()) - .then(() => this.ParanoidUser.findOne({ paranoid: false })) - .then(user => { - expect(user.updatedBy).to.equal(1); - }); + it('sets other changed values when soft deleting and a beforeDestroy hooks kicks in', async function() { + await this.ParanoidUser.sync({ force: true }); + const user0 = await this.ParanoidUser.create({ username: 'user1' }); + await user0.destroy(); + const user = await this.ParanoidUser.findOne({ paranoid: false }); + expect(user.updatedBy).to.equal(1); }); - it('should not throw error when a beforeDestroy hook changes a virtual column', function() { - this.ParanoidUser.hooks.add('beforeDestroy', instance => instance.virtualField = 2); + it('should not throw error when a beforeDestroy hook changes a virtual column', async function() { + this.ParanoidUser.beforeDestroy(instance => instance.virtualField = 2); - return this.ParanoidUser.sync({ force: true }) - .then(() => this.ParanoidUser.create({ username: 'user1' })) - .then(user => user.destroy()) - .then(() => this.ParanoidUser.findOne({ paranoid: false })) - .then(user => { - expect(user.virtualField).to.equal(0); - }); + await this.ParanoidUser.sync({ force: true }); + const user0 = await this.ParanoidUser.create({ username: 'user1' }); + await user0.destroy(); + const user = await this.ParanoidUser.findOne({ paranoid: false }); + expect(user.virtualField).to.equal(0); }); }); }); diff --git a/test/integration/hooks/find.test.js b/test/integration/hooks/find.test.js index 8685a9264e77..e0e87a68dfc3 100644 --- a/test/integration/hooks/find.test.js +++ b/test/integration/hooks/find.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,138 +18,141 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#find', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'adam', mood: 'happy' }, { username: 'joe', mood: 'sad' } ]); }); - it('allow changing attributes via beforeFind #5675', function() { - this.User.hooks.add('beforeFind', options => { + it('allow changing attributes via beforeFind #5675', async function() { + this.User.beforeFind(options => { options.attributes = { - include: ['id'] + include: [['id', 'my_id']] }; }); - return this.User.findAll({}); + await this.User.findAll({}); }); describe('on success', () => { - it('all hooks run', function() { + it('all hooks run', async function() { let beforeHook = false, beforeHook2 = false, beforeHook3 = false, afterHook = false; - this.User.hooks.add('beforeFind', () => { + this.User.beforeFind(() => { beforeHook = true; }); - this.User.hooks.add('beforeFindAfterExpandIncludeAll', () => { + this.User.beforeFindAfterExpandIncludeAll(() => { beforeHook2 = true; }); - this.User.hooks.add('beforeFindAfterOptions', () => { + this.User.beforeFindAfterOptions(() => { beforeHook3 = true; }); - this.User.hooks.add('afterFind', () => { + this.User.afterFind(() => { afterHook = true; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('happy'); - expect(beforeHook).to.be.true; - expect(beforeHook2).to.be.true; - expect(beforeHook3).to.be.true; - expect(afterHook).to.be.true; - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('happy'); + expect(beforeHook).to.be.true; + expect(beforeHook2).to.be.true; + expect(beforeHook3).to.be.true; + expect(afterHook).to.be.true; }); - it('beforeFind hook can change options', function() { - this.User.hooks.add('beforeFind', options => { + it('beforeFind hook can change options', async function() { + this.User.beforeFind(options => { options.where.username = 'joe'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); - it('beforeFindAfterExpandIncludeAll hook can change options', function() { - this.User.hooks.add('beforeFindAfterExpandIncludeAll', options => { + it('beforeFindAfterExpandIncludeAll hook can change options', async function() { + this.User.beforeFindAfterExpandIncludeAll(options => { options.where.username = 'joe'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); - it('beforeFindAfterOptions hook can change options', function() { - this.User.hooks.add('beforeFindAfterOptions', options => { + it('beforeFindAfterOptions hook can change options', async function() { + this.User.beforeFindAfterOptions(options => { options.where.username = 'joe'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); - it('afterFind hook can change results', function() { - this.User.hooks.add('afterFind', user => { + it('afterFind hook can change results', async function() { + this.User.afterFind(user => { user.mood = 'sad'; }); - return this.User.findOne({ where: { username: 'adam' } }).then(user => { - expect(user.mood).to.equal('sad'); - }); + const user = await this.User.findOne({ where: { username: 'adam' } }); + expect(user.mood).to.equal('sad'); }); }); describe('on error', () => { - it('in beforeFind hook returns error', function() { - this.User.hooks.add('beforeFind', () => { + it('in beforeFind hook returns error', async function() { + this.User.beforeFind(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); - it('in beforeFindAfterExpandIncludeAll hook returns error', function() { - this.User.hooks.add('beforeFindAfterExpandIncludeAll', () => { + it('in beforeFindAfterExpandIncludeAll hook returns error', async function() { + this.User.beforeFindAfterExpandIncludeAll(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); - it('in beforeFindAfterOptions hook returns error', function() { - this.User.hooks.add('beforeFindAfterOptions', () => { + it('in beforeFindAfterOptions hook returns error', async function() { + this.User.beforeFindAfterOptions(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); - it('in afterFind hook returns error', function() { - this.User.hooks.add('afterFind', () => { + it('in afterFind hook returns error', async function() { + this.User.afterFind(() => { throw new Error('Oops!'); }); - return this.User.findOne({ where: { username: 'adam' } }).catch(err => { + try { + await this.User.findOne({ where: { username: 'adam' } }); + } catch (err) { expect(err.message).to.equal('Oops!'); - }); + } }); }); }); diff --git a/test/integration/hooks/hooks.test.js b/test/integration/hooks/hooks.test.js index f0cf59e58f9e..5314bcc5687a 100644 --- a/test/integration/hooks/hooks.test.js +++ b/test/integration/hooks/hooks.test.js @@ -6,11 +6,10 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'), Sequelize = Support.Sequelize, dialect = Support.getTestDialect(), - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -32,18 +31,18 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#define', () => { before(function() { - this.sequelize.hooks.add('beforeDefine', (attributes, options) => { + this.sequelize.addHook('beforeDefine', (attributes, options) => { options.modelName = 'bar'; options.name.plural = 'barrs'; attributes.type = DataTypes.STRING; }); - this.sequelize.hooks.add('afterDefine', factory => { + this.sequelize.addHook('afterDefine', factory => { factory.options.name.singular = 'barr'; }); @@ -67,19 +66,19 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); after(function() { - this.sequelize.hooks.removeAll(); + this.sequelize.options.hooks = {}; this.sequelize.modelManager.removeModel(this.model); }); }); describe('#init', () => { before(function() { - Sequelize.hooks.add('beforeInit', (config, options) => { + Sequelize.addHook('beforeInit', (config, options) => { config.database = 'db2'; options.host = 'server9'; }); - Sequelize.hooks.add('afterInit', sequelize => { + Sequelize.addHook('afterInit', sequelize => { sequelize.options.protocol = 'udp'; }); @@ -99,323 +98,309 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); after(() => { - Sequelize.hooks.removeAll(); + Sequelize.options.hooks = {}; }); }); describe('passing DAO instances', () => { describe('beforeValidate / afterValidate', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeValidate(user) { + async beforeValidate(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterValidate(user) { + async afterValidate(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); + await User.sync({ force: true }); + await User.create({ username: 'bob' }); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); describe('beforeCreate / afterCreate', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeCreate(user) { + async beforeCreate(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterCreate(user) { + async afterCreate(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); + await User.sync({ force: true }); + await User.create({ username: 'bob' }); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); describe('beforeDestroy / afterDestroy', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeDestroy(user) { + async beforeDestroy(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterDestroy(user) { + async afterDestroy(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - return user.destroy().then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + await user.destroy(); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); describe('beforeUpdate / afterUpdate', () => { - it('should pass a DAO instance to the hook', function() { + it('should pass a DAO instance to the hook', async function() { let beforeHooked = false; let afterHooked = false; const User = this.sequelize.define('User', { username: DataTypes.STRING }, { hooks: { - beforeUpdate(user) { + async beforeUpdate(user) { expect(user).to.be.instanceof(User); beforeHooked = true; - return Promise.resolve(); }, - afterUpdate(user) { + async afterUpdate(user) { expect(user).to.be.instanceof(User); afterHooked = true; - return Promise.resolve(); } } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(user => { - user.username = 'bawb'; - return user.save({ fields: ['username'] }).then(() => { - expect(beforeHooked).to.be.true; - expect(afterHooked).to.be.true; - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ username: 'bob' }); + user.username = 'bawb'; + await user.save({ fields: ['username'] }); + expect(beforeHooked).to.be.true; + expect(afterHooked).to.be.true; }); }); }); describe('Model#sync', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeSync', beforeHook); - this.User.hooks.add('afterSync', afterHook); + this.User.beforeSync(beforeHook); + this.User.afterSync(afterHook); - return this.User.sync().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await this.User.sync(); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); - it('should not run hooks when "hooks = false" option passed', function() { + it('should not run hooks when "hooks = false" option passed', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeSync', beforeHook); - this.User.hooks.add('afterSync', afterHook); + this.User.beforeSync(beforeHook); + this.User.afterSync(afterHook); - return this.User.sync({ hooks: false }).then(() => { - expect(beforeHook).to.not.have.been.called; - expect(afterHook).to.not.have.been.called; - }); + await this.User.sync({ hooks: false }); + expect(beforeHook).to.not.have.been.called; + expect(afterHook).to.not.have.been.called; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeSync', () => { + this.User.beforeSync(() => { beforeHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('afterSync', afterHook); + this.User.afterSync(afterHook); - return expect(this.User.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); + await expect(this.User.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeSync', beforeHook); - this.User.hooks.add('afterSync', () => { + this.User.beforeSync(beforeHook); + this.User.afterSync(() => { afterHook(); throw new Error('Whoops!'); }); - return expect(this.User.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await expect(this.User.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); }); describe('sequelize#sync', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), modelBeforeHook = sinon.spy(), modelAfterHook = sinon.spy(); - this.sequelize.hooks.add('beforeBulkSync', beforeHook); - this.User.hooks.add('beforeSync', modelBeforeHook); - this.User.hooks.add('afterSync', modelAfterHook); - this.sequelize.hooks.add('afterBulkSync', afterHook); + this.sequelize.beforeBulkSync(beforeHook); + this.User.beforeSync(modelBeforeHook); + this.User.afterSync(modelAfterHook); + this.sequelize.afterBulkSync(afterHook); - return this.sequelize.sync().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(modelBeforeHook).to.have.been.calledOnce; - expect(modelAfterHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await this.sequelize.sync(); + expect(beforeHook).to.have.been.calledOnce; + expect(modelBeforeHook).to.have.been.calledOnce; + expect(modelAfterHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); - it('should not run hooks if "hooks = false" option passed', function() { + it('should not run hooks if "hooks = false" option passed', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), modelBeforeHook = sinon.spy(), modelAfterHook = sinon.spy(); - this.sequelize.hooks.add('beforeBulkSync', beforeHook); - this.User.hooks.add('beforeSync', modelBeforeHook); - this.User.hooks.add('afterSync', modelAfterHook); - this.sequelize.hooks.add('afterBulkSync', afterHook); + this.sequelize.beforeBulkSync(beforeHook); + this.User.beforeSync(modelBeforeHook); + this.User.afterSync(modelAfterHook); + this.sequelize.afterBulkSync(afterHook); - return this.sequelize.sync({ hooks: false }).then(() => { - expect(beforeHook).to.not.have.been.called; - expect(modelBeforeHook).to.not.have.been.called; - expect(modelAfterHook).to.not.have.been.called; - expect(afterHook).to.not.have.been.called; - }); + await this.sequelize.sync({ hooks: false }); + expect(beforeHook).to.not.have.been.called; + expect(modelBeforeHook).to.not.have.been.called; + expect(modelAfterHook).to.not.have.been.called; + expect(afterHook).to.not.have.been.called; }); afterEach(function() { - this.sequelize.hooks.removeAll(); + this.sequelize.options.hooks = {}; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.sequelize.hooks.add('beforeBulkSync', () => { + this.sequelize.beforeBulkSync(() => { beforeHook(); throw new Error('Whoops!'); }); - this.sequelize.hooks.add('afterBulkSync', afterHook); + this.sequelize.afterBulkSync(afterHook); - return expect(this.sequelize.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); + await expect(this.sequelize.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.sequelize.hooks.add('beforeBulkSync', beforeHook); - this.sequelize.hooks.add('afterBulkSync', () => { + this.sequelize.beforeBulkSync(beforeHook); + this.sequelize.afterBulkSync(() => { afterHook(); throw new Error('Whoops!'); }); - return expect(this.sequelize.sync()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await expect(this.sequelize.sync()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); afterEach(function() { - this.sequelize.hooks.removeAll(); + this.sequelize.options.hooks = {}; }); }); }); describe('#removal', () => { - it('should be able to remove by reference', function() { + it('should be able to remove by name', async function() { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); - this.User.hooks.add('beforeCreate', sasukeHook); - this.User.hooks.add('beforeCreate', narutoHook); - - return this.User.create({ username: 'makunouchi' }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.hooks.remove('beforeCreate', sasukeHook); - return this.User.create({ username: 'sendo' }); - }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); + this.User.addHook('beforeCreate', 'sasuke', sasukeHook); + this.User.addHook('beforeCreate', 'naruto', narutoHook); + + await this.User.create({ username: 'makunouchi' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledOnce; + this.User.removeHook('beforeCreate', 'sasuke'); + await this.User.create({ username: 'sendo' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledTwice; }); - it('should be able to remove proxies', function() { + it('should be able to remove by reference', async function() { const sasukeHook = sinon.spy(), narutoHook = sinon.spy(); - this.User.hooks.add('beforeSave', sasukeHook); - this.User.hooks.add('beforeSave', narutoHook); - - return this.User.create({ username: 'makunouchi' }).then(user => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledOnce; - this.User.hooks.remove('beforeSave', sasukeHook); - return user.update({ username: 'sendo' }); - }).then(() => { - expect(sasukeHook).to.have.been.calledOnce; - expect(narutoHook).to.have.been.calledTwice; - }); + this.User.addHook('beforeCreate', sasukeHook); + this.User.addHook('beforeCreate', narutoHook); + + await this.User.create({ username: 'makunouchi' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledOnce; + this.User.removeHook('beforeCreate', sasukeHook); + await this.User.create({ username: 'sendo' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledTwice; + }); + + it('should be able to remove proxies', async function() { + const sasukeHook = sinon.spy(), + narutoHook = sinon.spy(); + + this.User.addHook('beforeSave', sasukeHook); + this.User.addHook('beforeSave', narutoHook); + + const user = await this.User.create({ username: 'makunouchi' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledOnce; + this.User.removeHook('beforeSave', sasukeHook); + await user.update({ username: 'sendo' }); + expect(sasukeHook).to.have.been.calledOnce; + expect(narutoHook).to.have.been.calledTwice; }); }); }); diff --git a/test/integration/hooks/restore.test.js b/test/integration/hooks/restore.test.js index f79280de748a..c1665d774f59 100644 --- a/test/integration/hooks/restore.test.js +++ b/test/integration/hooks/restore.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -29,68 +29,59 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#restore', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.hooks.add('beforeRestore', beforeHook); - this.ParanoidUser.hooks.add('afterRestore', afterHook); + this.ParanoidUser.beforeRestore(beforeHook); + this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - return user.restore().then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); - }); + const user = await this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + await user.restore(); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.hooks.add('beforeRestore', () => { + this.ParanoidUser.beforeRestore(() => { beforeHook(); throw new Error('Whoops!'); }); - this.ParanoidUser.hooks.add('afterRestore', afterHook); + this.ParanoidUser.afterRestore(afterHook); - return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - return expect(user.restore()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); - }); - }); + const user = await this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + await expect(user.restore()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.ParanoidUser.hooks.add('beforeRestore', beforeHook); - this.ParanoidUser.hooks.add('afterRestore', () => { + this.ParanoidUser.beforeRestore(beforeHook); + this.ParanoidUser.afterRestore(() => { afterHook(); throw new Error('Whoops!'); }); - return this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.destroy().then(() => { - return expect(user.restore()).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); - }); - }); + const user = await this.ParanoidUser.create({ username: 'Toni', mood: 'happy' }); + await user.destroy(); + await expect(user.restore()).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); }); diff --git a/test/integration/hooks/updateAttributes.test.js b/test/integration/hooks/updateAttributes.test.js index 2eb6237318ea..eec8ec895fac 100644 --- a/test/integration/hooks/updateAttributes.test.js +++ b/test/integration/hooks/updateAttributes.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), sinon = require('sinon'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,150 +18,134 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#update', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.hooks.add('beforeUpdate', beforeHook); - this.User.hooks.add('afterUpdate', afterHook); - this.User.hooks.add('beforeSave', beforeSave); - this.User.hooks.add('afterSave', afterSave); - - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return user.update({ username: 'Chong' }).then(user => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledTwice; - expect(afterSave).to.have.been.calledTwice; - expect(user.username).to.equal('Chong'); - }); - }); + this.User.beforeUpdate(beforeHook); + this.User.afterUpdate(afterHook); + this.User.beforeSave(beforeSave); + this.User.afterSave(afterSave); + + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + const user0 = await user.update({ username: 'Chong' }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledTwice; + expect(afterSave).to.have.been.calledTwice; + expect(user0.username).to.equal('Chong'); }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.hooks.add('beforeUpdate', () => { + this.User.beforeUpdate(() => { beforeHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('afterUpdate', afterHook); - this.User.hooks.add('beforeSave', beforeSave); - this.User.hooks.add('afterSave', afterSave); - - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - expect(afterSave).to.have.been.calledOnce; - }); - }); + this.User.afterUpdate(afterHook); + this.User.beforeSave(beforeSave); + this.User.afterSave(afterSave); + + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.update({ username: 'Chong' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; + expect(afterSave).to.have.been.calledOnce; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(), beforeSave = sinon.spy(), afterSave = sinon.spy(); - this.User.hooks.add('beforeUpdate', beforeHook); - this.User.hooks.add('afterUpdate', () => { + this.User.beforeUpdate(beforeHook); + this.User.afterUpdate(() => { afterHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('beforeSave', beforeSave); - this.User.hooks.add('afterSave', afterSave); - - return this.User.create({ username: 'Toni', mood: 'happy' }).then(user => { - return expect(user.update({ username: 'Chong' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - expect(beforeSave).to.have.been.calledTwice; - expect(afterSave).to.have.been.calledOnce; - }); - }); + this.User.beforeSave(beforeSave); + this.User.afterSave(afterSave); + + const user = await this.User.create({ username: 'Toni', mood: 'happy' }); + await expect(user.update({ username: 'Chong' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; + expect(beforeSave).to.have.been.calledTwice; + expect(afterSave).to.have.been.calledOnce; }); }); describe('preserves changes to instance', () => { - it('beforeValidate', function() { - - this.User.hooks.add('beforeValidate', user => { + it('beforeValidate', async function() { + this.User.beforeValidate(user => { user.mood = 'happy'; }); - return this.User.create({ username: 'fireninja', mood: 'invalid' }).then(user => { - return user.update({ username: 'hero' }); - }).then(user => { - expect(user.username).to.equal('hero'); - expect(user.mood).to.equal('happy'); - }); + const user0 = await this.User.create({ username: 'fireninja', mood: 'invalid' }); + const user = await user0.update({ username: 'hero' }); + expect(user.username).to.equal('hero'); + expect(user.mood).to.equal('happy'); }); - it('afterValidate', function() { - - this.User.hooks.add('afterValidate', user => { + it('afterValidate', async function() { + this.User.afterValidate(user => { user.mood = 'sad'; }); - return this.User.create({ username: 'fireninja', mood: 'nuetral' }).then(user => { - return user.update({ username: 'spider' }); - }).then(user => { - expect(user.username).to.equal('spider'); - expect(user.mood).to.equal('sad'); - }); + const user0 = await this.User.create({ username: 'fireninja', mood: 'nuetral' }); + const user = await user0.update({ username: 'spider' }); + expect(user.username).to.equal('spider'); + expect(user.mood).to.equal('sad'); }); - it('beforeSave', function() { + it('beforeSave', async function() { let hookCalled = 0; - this.User.hooks.add('beforeSave', user => { + this.User.beforeSave(user => { user.mood = 'happy'; hookCalled++; }); - return this.User.create({ username: 'fireninja', mood: 'nuetral' }).then(user => { - return user.update({ username: 'spider', mood: 'sad' }); - }).then(user => { - expect(user.username).to.equal('spider'); - expect(user.mood).to.equal('happy'); - expect(hookCalled).to.equal(2); - }); + const user0 = await this.User.create({ username: 'fireninja', mood: 'nuetral' }); + const user = await user0.update({ username: 'spider', mood: 'sad' }); + expect(user.username).to.equal('spider'); + expect(user.mood).to.equal('happy'); + expect(hookCalled).to.equal(2); }); - it('beforeSave with beforeUpdate', function() { + it('beforeSave with beforeUpdate', async function() { let hookCalled = 0; - this.User.hooks.add('beforeUpdate', user => { + this.User.beforeUpdate(user => { user.mood = 'sad'; hookCalled++; }); - this.User.hooks.add('beforeSave', user => { + this.User.beforeSave(user => { user.mood = 'happy'; hookCalled++; }); - return this.User.create({ username: 'akira' }).then(user => { - return user.update({ username: 'spider', mood: 'sad' }); - }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('spider'); - expect(hookCalled).to.equal(3); - }); + const user0 = await this.User.create({ username: 'akira' }); + const user = await user0.update({ username: 'spider', mood: 'sad' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('spider'); + expect(hookCalled).to.equal(3); }); }); }); diff --git a/test/integration/hooks/upsert.test.js b/test/integration/hooks/upsert.test.js index 26fd79aeb596..dd7b2cb0f51c 100644 --- a/test/integration/hooks/upsert.test.js +++ b/test/integration/hooks/upsert.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), if (Support.sequelize.dialect.supports.upserts) { describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -20,73 +20,69 @@ if (Support.sequelize.dialect.supports.upserts) { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#upsert', () => { describe('on success', () => { - it('should run hooks', function() { + it('should run hooks', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeUpsert', beforeHook); - this.User.hooks.add('afterUpsert', afterHook); + this.User.beforeUpsert(beforeHook); + this.User.afterUpsert(afterHook); - return this.User.upsert({ username: 'Toni', mood: 'happy' }).then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await this.User.upsert({ username: 'Toni', mood: 'happy' }); + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('on error', () => { - it('should return an error from before', function() { + it('should return an error from before', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeUpsert', () => { + this.User.beforeUpsert(() => { beforeHook(); throw new Error('Whoops!'); }); - this.User.hooks.add('afterUpsert', afterHook); + this.User.afterUpsert(afterHook); - return expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).not.to.have.been.called; - }); + await expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).not.to.have.been.called; }); - it('should return an error from after', function() { + it('should return an error from after', async function() { const beforeHook = sinon.spy(), afterHook = sinon.spy(); - this.User.hooks.add('beforeUpsert', beforeHook); - this.User.hooks.add('afterUpsert', () => { + this.User.beforeUpsert(beforeHook); + this.User.afterUpsert(() => { afterHook(); throw new Error('Whoops!'); }); - return expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected.then(() => { - expect(beforeHook).to.have.been.calledOnce; - expect(afterHook).to.have.been.calledOnce; - }); + await expect(this.User.upsert({ username: 'Toni', mood: 'happy' })).to.be.rejected; + expect(beforeHook).to.have.been.calledOnce; + expect(afterHook).to.have.been.calledOnce; }); }); describe('preserves changes to values', () => { - it('beforeUpsert', function() { + it('beforeUpsert', async function() { let hookCalled = 0; const valuesOriginal = { mood: 'sad', username: 'leafninja' }; - this.User.hooks.add('beforeUpsert', values => { + this.User.beforeUpsert(values => { values.mood = 'happy'; hookCalled++; }); - return this.User.upsert(valuesOriginal).then(() => { - expect(valuesOriginal.mood).to.equal('happy'); - expect(hookCalled).to.equal(1); - }); + await this.User.upsert(valuesOriginal); + expect(valuesOriginal.mood).to.equal('happy'); + expect(hookCalled).to.equal(1); }); }); }); diff --git a/test/integration/hooks/validate.test.js b/test/integration/hooks/validate.test.js index 6876e1a16752..d3a6d301eb80 100644 --- a/test/integration/hooks/validate.test.js +++ b/test/integration/hooks/validate.test.js @@ -7,7 +7,7 @@ const Support = require('../support'); const DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Hooks'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -18,31 +18,30 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { values: ['happy', 'sad', 'neutral'] } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('#validate', () => { describe('#create', () => { - it('should return the user', function() { - this.User.hooks.add('beforeValidate', user => { + it('should return the user', async function() { + this.User.beforeValidate(user => { user.username = 'Bob'; user.mood = 'happy'; }); - this.User.hooks.add('afterValidate', user => { + this.User.afterValidate(user => { user.username = 'Toni'; }); - return this.User.create({ mood: 'ecstatic' }).then(user => { - expect(user.mood).to.equal('happy'); - expect(user.username).to.equal('Toni'); - }); + const user = await this.User.create({ mood: 'ecstatic' }); + expect(user.mood).to.equal('happy'); + expect(user.username).to.equal('Toni'); }); }); describe('#3534, hooks modifications', () => { - it('fields modified in hooks are saved', function() { - this.User.hooks.add('afterValidate', user => { + it('fields modified in hooks are saved', async function() { + this.User.afterValidate(user => { //if username is defined and has more than 5 char user.username = user.username ? user.username.length < 5 ? null : user.username @@ -51,99 +50,89 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); - this.User.hooks.add('beforeValidate', user => { + this.User.beforeValidate(user => { user.mood = user.mood || 'neutral'; }); - return this.User.create({ username: 'T', mood: 'neutral' }).then(user => { - expect(user.mood).to.equal('neutral'); - expect(user.username).to.equal('Samorost 3'); - - //change attributes - user.mood = 'sad'; - user.username = 'Samorost Good One'; - - return user.save(); - }).then(uSaved => { - expect(uSaved.mood).to.equal('sad'); - expect(uSaved.username).to.equal('Samorost Good One'); - - //change attributes, expect to be replaced by hooks - uSaved.username = 'One'; - - return uSaved.save(); - }).then(uSaved => { - //attributes were replaced by hooks ? - expect(uSaved.mood).to.equal('sad'); - expect(uSaved.username).to.equal('Samorost 3'); - return this.User.findByPk(uSaved.id); - }).then(uFetched => { - expect(uFetched.mood).to.equal('sad'); - expect(uFetched.username).to.equal('Samorost 3'); - - uFetched.mood = null; - uFetched.username = 'New Game is Needed'; - - return uFetched.save(); - }).then(uFetchedSaved => { - expect(uFetchedSaved.mood).to.equal('neutral'); - expect(uFetchedSaved.username).to.equal('New Game is Needed'); - - return this.User.findByPk(uFetchedSaved.id); - }).then(uFetched => { - expect(uFetched.mood).to.equal('neutral'); - expect(uFetched.username).to.equal('New Game is Needed'); - - //expect to be replaced by hooks - uFetched.username = 'New'; - uFetched.mood = 'happy'; - return uFetched.save(); - }).then(uFetchedSaved => { - expect(uFetchedSaved.mood).to.equal('happy'); - expect(uFetchedSaved.username).to.equal('Samorost 3'); - }); + const user = await this.User.create({ username: 'T', mood: 'neutral' }); + expect(user.mood).to.equal('neutral'); + expect(user.username).to.equal('Samorost 3'); + + //change attributes + user.mood = 'sad'; + user.username = 'Samorost Good One'; + + const uSaved0 = await user.save(); + expect(uSaved0.mood).to.equal('sad'); + expect(uSaved0.username).to.equal('Samorost Good One'); + + //change attributes, expect to be replaced by hooks + uSaved0.username = 'One'; + + const uSaved = await uSaved0.save(); + //attributes were replaced by hooks ? + expect(uSaved.mood).to.equal('sad'); + expect(uSaved.username).to.equal('Samorost 3'); + const uFetched0 = await this.User.findByPk(uSaved.id); + expect(uFetched0.mood).to.equal('sad'); + expect(uFetched0.username).to.equal('Samorost 3'); + + uFetched0.mood = null; + uFetched0.username = 'New Game is Needed'; + + const uFetchedSaved0 = await uFetched0.save(); + expect(uFetchedSaved0.mood).to.equal('neutral'); + expect(uFetchedSaved0.username).to.equal('New Game is Needed'); + + const uFetched = await this.User.findByPk(uFetchedSaved0.id); + expect(uFetched.mood).to.equal('neutral'); + expect(uFetched.username).to.equal('New Game is Needed'); + + //expect to be replaced by hooks + uFetched.username = 'New'; + uFetched.mood = 'happy'; + const uFetchedSaved = await uFetched.save(); + expect(uFetchedSaved.mood).to.equal('happy'); + expect(uFetchedSaved.username).to.equal('Samorost 3'); }); }); describe('on error', () => { - it('should emit an error from after hook', function() { - this.User.hooks.add('afterValidate', user => { + it('should emit an error from after hook', async function() { + this.User.afterValidate(user => { user.mood = 'ecstatic'; throw new Error('Whoops! Changed user.mood!'); }); - return expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejectedWith('Whoops! Changed user.mood!'); + await expect(this.User.create({ username: 'Toni', mood: 'happy' })).to.be.rejectedWith('Whoops! Changed user.mood!'); }); - it('should call validationFailed hook', function() { + it('should call validationFailed hook', async function() { const validationFailedHook = sinon.spy(); - this.User.hooks.add('validationFailed', validationFailedHook); + this.User.validationFailed(validationFailedHook); - return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(() => { - expect(validationFailedHook).to.have.been.calledOnce; - }); + await expect(this.User.create({ mood: 'happy' })).to.be.rejected; + expect(validationFailedHook).to.have.been.calledOnce; }); - it('should not replace the validation error in validationFailed hook by default', function() { + it('should not replace the validation error in validationFailed hook by default', async function() { const validationFailedHook = sinon.stub(); - this.User.hooks.add('validationFailed', validationFailedHook); + this.User.validationFailed(validationFailedHook); - return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => { - expect(err.name).to.equal('SequelizeValidationError'); - }); + const err = await expect(this.User.create({ mood: 'happy' })).to.be.rejected; + expect(err.name).to.equal('SequelizeValidationError'); }); - it('should replace the validation error if validationFailed hook creates a new error', function() { + it('should replace the validation error if validationFailed hook creates a new error', async function() { const validationFailedHook = sinon.stub().throws(new Error('Whoops!')); - this.User.hooks.add('validationFailed', validationFailedHook); + this.User.validationFailed(validationFailedHook); - return expect(this.User.create({ mood: 'happy' })).to.be.rejected.then(err => { - expect(err.message).to.equal('Whoops!'); - }); + const err = await expect(this.User.create({ mood: 'happy' })).to.be.rejected; + expect(err.message).to.equal('Whoops!'); }); }); }); diff --git a/test/integration/include.test.js b/test/integration/include.test.js old mode 100755 new mode 100644 index dc14b958fd65..7b049f9e2124 --- a/test/integration/include.test.js +++ b/test/integration/include.test.js @@ -2,12 +2,13 @@ const chai = require('chai'), Sequelize = require('../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('./support'), DataTypes = require('../../lib/data-types'), _ = require('lodash'), - dialect = Support.getTestDialect(); + dialect = Support.getTestDialect(), + current = Support.sequelize, + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -15,149 +16,144 @@ const sortById = function(a, b) { describe(Support.getTestDialectTeaser('Include'), () => { describe('find', () => { - it('should support an empty belongsTo include', function() { + it('should support an empty belongsTo include', async function() { const Company = this.sequelize.define('Company', {}), User = this.sequelize.define('User', {}); User.belongsTo(Company, { as: 'Employer' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create(); - }).then(() => { - return User.findOne({ - include: [{ model: Company, as: 'Employer' }] - }).then(user => { - expect(user).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + await User.create(); + + const user = await User.findOne({ + include: [{ model: Company, as: 'Employer' }] }); + + expect(user).to.be.ok; }); - it('should support a belongsTo association reference', function() { + it('should support a belongsTo association reference', async function() { const Company = this.sequelize.define('Company', {}), User = this.sequelize.define('User', {}), Employer = User.belongsTo(Company, { as: 'Employer' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create(); - }).then(() => { - return User.findOne({ - include: [Employer] - }).then(user => { - expect(user).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + await User.create(); + + const user = await User.findOne({ + include: [Employer] }); + + expect(user).to.be.ok; }); - it('should support to use associations with Sequelize.col', function() { + it('should support to use associations with Sequelize.col', async function() { const Table1 = this.sequelize.define('Table1'); const Table2 = this.sequelize.define('Table2'); const Table3 = this.sequelize.define('Table3', { value: DataTypes.INTEGER }); Table1.hasOne(Table2, { foreignKey: 'Table1Id' }); Table2.hasMany(Table3, { as: 'Tables3', foreignKey: 'Table2Id' }); - return this.sequelize.sync({ force: true }).then(() => { - return Table1.create().then(table1 => { - return Table2.create({ - Table1Id: table1.get('id') - }); - }).then(table2 => { - return Table3.bulkCreate([ - { - Table2Id: table2.get('id'), - value: 5 - }, - { - Table2Id: table2.get('id'), - value: 7 - } - ], { - validate: true - }); - }); - }).then(() => { - return Table1.findAll({ - raw: true, - attributes: [ - [Sequelize.fn('SUM', Sequelize.col('Table2.Tables3.value')), 'sum'] - ], - include: [ - { - model: Table2, - attributes: [], - include: [ - { - model: Table3, - as: 'Tables3', - attributes: [] - } - ] - } - ] - }).then(result => { - expect(result.length).to.equal(1); - expect(parseInt(result[0].sum, 10)).to.eq(12); - }); + await this.sequelize.sync({ force: true }); + const table1 = await Table1.create(); + + const table2 = await Table2.create({ + Table1Id: table1.get('id') + }); + + await Table3.bulkCreate([ + { + Table2Id: table2.get('id'), + value: 5 + }, + { + Table2Id: table2.get('id'), + value: 7 + } + ], { + validate: true + }); + + const result = await Table1.findAll({ + raw: true, + attributes: [ + [Sequelize.fn('SUM', Sequelize.col('Table2.Tables3.value')), 'sum'] + ], + include: [ + { + model: Table2, + attributes: [], + include: [ + { + model: Table3, + as: 'Tables3', + attributes: [] + } + ] + } + ] }); + + expect(result.length).to.equal(1); + expect(parseInt(result[0].sum, 10)).to.eq(12); }); - it('should support a belongsTo association reference with a where', function() { + it('should support a belongsTo association reference with a where', async function() { const Company = this.sequelize.define('Company', { name: DataTypes.STRING }), User = this.sequelize.define('User', {}), Employer = User.belongsTo(Company, { as: 'Employer', foreignKey: 'employerId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Company.create({ - name: 'CyberCorp' - }).then(company => { - return User.create({ - employerId: company.get('id') - }); - }); - }).then(() => { - return User.findOne({ - include: [ - { association: Employer, where: { name: 'CyberCorp' } } - ] - }).then(user => { - expect(user).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + const company = await Company.create({ + name: 'CyberCorp' }); + + await User.create({ + employerId: company.get('id') + }); + + const user = await User.findOne({ + include: [ + { association: Employer, where: { name: 'CyberCorp' } } + ] + }); + + expect(user).to.be.ok; }); - it('should support a empty hasOne include', function() { + it('should support a empty hasOne include', async function() { const Company = this.sequelize.define('Company', {}), Person = this.sequelize.define('Person', {}); Company.hasOne(Person, { as: 'CEO' }); - return this.sequelize.sync({ force: true }).then(() => { - return Company.create().then(() => { - return Company.findOne({ - include: [{ model: Person, as: 'CEO' }] - }).then(company => { - expect(company).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + await Company.create(); + + const company = await Company.findOne({ + include: [{ model: Person, as: 'CEO' }] }); + + expect(company).to.be.ok; }); - it('should support a hasOne association reference', function() { + it('should support a hasOne association reference', async function() { const Company = this.sequelize.define('Company', {}), Person = this.sequelize.define('Person', {}), CEO = Company.hasOne(Person, { as: 'CEO' }); - return this.sequelize.sync({ force: true }).then(() => { - return Company.create(); - }).then(() => { - return Company.findOne({ - include: [CEO] - }); - }).then(user => { - expect(user).to.be.ok; + await this.sequelize.sync({ force: true }); + await Company.create(); + + const user = await Company.findOne({ + include: [CEO] }); + + expect(user).to.be.ok; }); - it('should support including a belongsTo association rather than a model/as pair', function() { + it('should support including a belongsTo association rather than a model/as pair', async function() { const Company = this.sequelize.define('Company', {}), Person = this.sequelize.define('Person', {}); @@ -165,97 +161,84 @@ describe(Support.getTestDialectTeaser('Include'), () => { Employer: Person.belongsTo(Company, { as: 'employer' }) }; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Person.create(), - Company.create() - ).then(([person, company]) => { - return person.setEmployer(company); - }); - }).then(() => { - return Person.findOne({ - include: [Person.relation.Employer] - }).then(person => { - expect(person).to.be.ok; - expect(person.employer).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const [person0, company] = await Promise.all([Person.create(), Company.create()]); + await person0.setEmployer(company); + + const person = await Person.findOne({ + include: [Person.relation.Employer] }); + + expect(person).to.be.ok; + expect(person.employer).to.be.ok; }); - it('should support a hasMany association reference', function() { + it('should support a hasMany association reference', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', {}), Tasks = User.hasMany(Task); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return user.createTask(); - }).then(() => { - return User.findOne({ - include: [Tasks] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.tasks).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create(); + await user0.createTask(); + + const user = await User.findOne({ + include: [Tasks] }); + + expect(user).to.be.ok; + expect(user.tasks).to.be.ok; }); - it('should support a hasMany association reference with a where condition', function() { + it('should support a hasMany association reference with a where condition', async function() { const User = this.sequelize.define('user', {}), Task = this.sequelize.define('task', { title: DataTypes.STRING }), Tasks = User.hasMany(Task); Task.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return Promise.join( - user.createTask({ - title: 'trivial' - }), - user.createTask({ - title: 'pursuit' - }) - ); - }).then(() => { - return User.findOne({ - include: [ - { association: Tasks, where: { title: 'trivial' } } - ] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.tasks).to.be.ok; - expect(user.tasks.length).to.equal(1); - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create(); + + await Promise.all([user0.createTask({ + title: 'trivial' + }), user0.createTask({ + title: 'pursuit' + })]); + + const user = await User.findOne({ + include: [ + { association: Tasks, where: { title: 'trivial' } } + ] }); + + expect(user).to.be.ok; + expect(user.tasks).to.be.ok; + expect(user.tasks.length).to.equal(1); }); - it('should support a belongsToMany association reference', function() { + it('should support a belongsToMany association reference', async function() { const User = this.sequelize.define('user', {}), Group = this.sequelize.define('group', {}), Groups = User.belongsToMany(Group, { through: 'UserGroup' }); Group.belongsToMany(User, { through: 'UserGroup' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create().then(user => { - return user.createGroup(); - }); - }).then(() => { - return User.findOne({ - include: [Groups] - }).then(user => { - expect(user).to.be.ok; - expect(user.groups).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const user0 = await User.create(); + await user0.createGroup(); + + const user = await User.findOne({ + include: [Groups] }); + + expect(user).to.be.ok; + expect(user.groups).to.be.ok; }); - it('should support a simple nested belongsTo -> belongsTo include', function() { + it('should support a simple nested belongsTo -> belongsTo include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); @@ -263,35 +246,33 @@ describe(Support.getTestDialectTeaser('Include'), () => { Task.belongsTo(User); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - task: Task.create(), - user: User.create(), - group: Group.create() - }).then(props => { - return Promise.join( - props.task.setUser(props.user), - props.user.setGroup(props.group) - ).return(props); - }).then(props => { - return Task.findOne({ - where: { - id: props.task.id - }, - include: [ - { model: User, include: [ - { model: Group } - ] } - ] - }).then(task => { - expect(task.User).to.be.ok; - expect(task.User.Group).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const props0 = await promiseProps({ + task: Task.create(), + user: User.create(), + group: Group.create() + }); + + await Promise.all([props0.task.setUser(props0.user), props0.user.setGroup(props0.group)]); + const props = props0; + + const task = await Task.findOne({ + where: { + id: props.task.id + }, + include: [ + { model: User, include: [ + { model: Group } + ] } + ] }); + + expect(task.User).to.be.ok; + expect(task.User.Group).to.be.ok; }); - it('should support a simple sibling set of belongsTo include', function() { + it('should support a simple sibling set of belongsTo include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); @@ -299,30 +280,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { Task.belongsTo(User); Task.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ - User: {}, - Group: {} - }, { - include: [User, Group] - }); - }).then(task => { - return Task.findOne({ - where: { - id: task.id - }, - include: [ - { model: User }, - { model: Group } - ] - }); - }).then(task => { - expect(task.User).to.be.ok; - expect(task.Group).to.be.ok; + await this.sequelize.sync({ force: true }); + + const task0 = await Task.create({ + User: {}, + Group: {} + }, { + include: [User, Group] + }); + + const task = await Task.findOne({ + where: { + id: task0.id + }, + include: [ + { model: User }, + { model: Group } + ] }); + + expect(task.User).to.be.ok; + expect(task.Group).to.be.ok; }); - it('should support a simple nested hasOne -> hasOne include', function() { + it('should support a simple nested hasOne -> hasOne include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); @@ -331,31 +312,31 @@ describe(Support.getTestDialectTeaser('Include'), () => { Group.hasOne(User); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - Task: {}, - Group: {} - }, { - include: [Task, Group] - }); - }).then(user => { - return Group.findOne({ - where: { - id: user.Group.id - }, - include: [ - { model: User, include: [ - { model: Task } - ] } - ] - }); - }).then(group => { - expect(group.User).to.be.ok; - expect(group.User.Task).to.be.ok; + await this.sequelize.sync({ force: true }); + + const user = await User.create({ + Task: {}, + Group: {} + }, { + include: [Task, Group] + }); + + const group = await Group.findOne({ + where: { + id: user.Group.id + }, + include: [ + { model: User, include: [ + { model: Task } + ] } + ] }); + + expect(group.User).to.be.ok; + expect(group.User.Task).to.be.ok; }); - it('should support a simple nested hasMany -> belongsTo include', function() { + it('should support a simple nested hasMany -> belongsTo include', async function() { const Task = this.sequelize.define('Task', {}), User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project', {}); @@ -363,41 +344,40 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Task); Task.belongsTo(Project); - return this.sequelize.sync({ force: true }).then(() => { - return Project.bulkCreate([{ id: 1 }, { id: 2 }]); - }).then(() => { - return User.create({ - Tasks: [ - { ProjectId: 1 }, - { ProjectId: 2 }, - { ProjectId: 1 }, - { ProjectId: 2 } - ] - }, { - include: [Task] - }); - }).then(user => { - return User.findOne({ - where: { - id: user.id - }, - include: [ - { model: Task, include: [ - { model: Project } - ] } - ] - }); - }).then(user => { - expect(user.Tasks).to.be.ok; - expect(user.Tasks.length).to.equal(4); + await this.sequelize.sync({ force: true }); + await Project.bulkCreate([{ id: 1 }, { id: 2 }]); - user.Tasks.forEach(task => { - expect(task.Project).to.be.ok; - }); + const user0 = await User.create({ + Tasks: [ + { ProjectId: 1 }, + { ProjectId: 2 }, + { ProjectId: 1 }, + { ProjectId: 2 } + ] + }, { + include: [Task] + }); + + const user = await User.findOne({ + where: { + id: user0.id + }, + include: [ + { model: Task, include: [ + { model: Project } + ] } + ] + }); + + expect(user.Tasks).to.be.ok; + expect(user.Tasks.length).to.equal(4); + + user.Tasks.forEach(task => { + expect(task.Project).to.be.ok; }); }); - it('should support a simple nested belongsTo -> hasMany include', function() { + it('should support a simple nested belongsTo -> hasMany include', async function() { const Task = this.sequelize.define('Task', {}), Worker = this.sequelize.define('Worker', {}), Project = this.sequelize.define('Project', {}); @@ -406,32 +386,32 @@ describe(Support.getTestDialectTeaser('Include'), () => { Project.hasMany(Worker); Project.hasMany(Task); - return this.sequelize.sync({ force: true }).then(() => { - return Project.create({ - Workers: [{}], - Tasks: [{}, {}, {}, {}] - }, { - include: [Worker, Task] - }); - }).then(project => { - return Worker.findOne({ - where: { - id: project.Workers[0].id - }, - include: [ - { model: Project, include: [ - { model: Task } - ] } - ] - }); - }).then(worker => { - expect(worker.Project).to.be.ok; - expect(worker.Project.Tasks).to.be.ok; - expect(worker.Project.Tasks.length).to.equal(4); + await this.sequelize.sync({ force: true }); + + const project = await Project.create({ + Workers: [{}], + Tasks: [{}, {}, {}, {}] + }, { + include: [Worker, Task] }); + + const worker = await Worker.findOne({ + where: { + id: project.Workers[0].id + }, + include: [ + { model: Project, include: [ + { model: Task } + ] } + ] + }); + + expect(worker.Project).to.be.ok; + expect(worker.Project.Tasks).to.be.ok; + expect(worker.Project.Tasks.length).to.equal(4); }); - it('should support a simple nested hasMany to hasMany include', function() { + it('should support a simple nested hasMany to hasMany include', async function() { const User = this.sequelize.define('User', {}), Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -444,60 +424,60 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: 'product_tag' }); Tag.belongsToMany(Product, { through: 'product_tag' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ - id: 1, - Products: [ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' }, - { title: 'Bed' } - ] - }, { - include: [Product] - }).then(() => { - return Product.findAll({ order: [['id']] }); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll({ order: [['id']] }); - }) - ]); - }).then(([products, tags]) => { - return Promise.all([ - products[0].setTags([tags[0], tags[2]]), - products[1].setTags([tags[1]]), - products[2].setTags([tags[0], tags[1], tags[2]]) - ]); - }).then(() => { - return User.findOne({ - where: { - id: 1 - }, - include: [ - { model: Product, include: [ - { model: Tag } - ] } - ], - order: [ - User.rawAttributes.id, - [Product, 'id'] + await this.sequelize.sync({ force: true }); + + const [products, tags] = await Promise.all([ + User.create({ + id: 1, + Products: [ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' }, + { title: 'Bed' } ] - }); - }).then(user => { - expect(user.Products.length).to.equal(4); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[2].Tags.length).to.equal(3); - expect(user.Products[3].Tags.length).to.equal(0); + }, { + include: [Product] + }).then(() => { + return Product.findAll({ order: [['id']] }); + }), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll({ order: [['id']] }); + }) + ]); + + await Promise.all([ + products[0].setTags([tags[0], tags[2]]), + products[1].setTags([tags[1]]), + products[2].setTags([tags[0], tags[1], tags[2]]) + ]); + + const user = await User.findOne({ + where: { + id: 1 + }, + include: [ + { model: Product, include: [ + { model: Tag } + ] } + ], + order: [ + User.rawAttributes.id, + [Product, 'id'] + ] }); + + expect(user.Products.length).to.equal(4); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[2].Tags.length).to.equal(3); + expect(user.Products[3].Tags.length).to.equal(0); }); - it('should support an include with multiple different association types', function() { + it('should support an include with multiple different association types', async function() { const User = this.sequelize.define('User', {}), Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -542,78 +522,78 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Product.create({ - id: 1, - title: 'Chair', - Prices: [{ value: 5 }, { value: 10 }] - }, { include: [Price] }), - Product.create({ - id: 2, - title: 'Desk', - Prices: [{ value: 5 }, { value: 10 }, { value: 15 }, { value: 20 }] - }, { include: [Price] }), - User.create({ - id: 1, - Memberships: [ - { id: 1, Group: { name: 'Developers' }, Rank: { name: 'Admin', canInvite: 1, canRemove: 1 } }, - { id: 2, Group: { name: 'Designers' }, Rank: { name: 'Member', canInvite: 1, canRemove: 0 } } - ] - }, { - include: { model: GroupMember, as: 'Memberships', include: [Group, Rank] } - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]); - }).then(([product1, product2, user, tags]) => { - return Promise.all([ - user.setProducts([product1, product2]), - product1.setTags([tags[0], tags[2]]), - product2.setTags([tags[1]]), - product1.setCategory(tags[1]) - ]); - }).then(() => { - return User.findOne({ - where: { id: 1 }, - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - Rank - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - Price - ] } + await this.sequelize.sync({ force: true }); + + const [product1, product2, user0, tags] = await Promise.all([ + Product.create({ + id: 1, + title: 'Chair', + Prices: [{ value: 5 }, { value: 10 }] + }, { include: [Price] }), + Product.create({ + id: 2, + title: 'Desk', + Prices: [{ value: 5 }, { value: 10 }, { value: 15 }, { value: 20 }] + }, { include: [Price] }), + User.create({ + id: 1, + Memberships: [ + { id: 1, Group: { name: 'Developers' }, Rank: { name: 'Admin', canInvite: 1, canRemove: 1 } }, + { id: 2, Group: { name: 'Designers' }, Rank: { name: 'Member', canInvite: 1, canRemove: 0 } } ] - }); - }).then(user => { - user.Memberships.sort(sortById); - expect(user.Memberships.length).to.equal(2); - expect(user.Memberships[0].Group.name).to.equal('Developers'); - expect(user.Memberships[0].Rank.canRemove).to.equal(1); - expect(user.Memberships[1].Group.name).to.equal('Designers'); - expect(user.Memberships[1].Rank.canRemove).to.equal(0); + }, { + include: { model: GroupMember, as: 'Memberships', include: [Group, Rank] } + }), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll(); + }) + ]); - user.Products.sort(sortById); - expect(user.Products.length).to.equal(2); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[0].Category).to.be.ok; - expect(user.Products[1].Category).not.to.be.ok; + await Promise.all([ + user0.setProducts([product1, product2]), + product1.setTags([tags[0], tags[2]]), + product2.setTags([tags[1]]), + product1.setCategory(tags[1]) + ]); - expect(user.Products[0].Prices.length).to.equal(2); - expect(user.Products[1].Prices.length).to.equal(4); + const user = await User.findOne({ + where: { id: 1 }, + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + Rank + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + Price + ] } + ] }); + + user.Memberships.sort(sortById); + expect(user.Memberships.length).to.equal(2); + expect(user.Memberships[0].Group.name).to.equal('Developers'); + expect(user.Memberships[0].Rank.canRemove).to.equal(1); + expect(user.Memberships[1].Group.name).to.equal('Designers'); + expect(user.Memberships[1].Rank.canRemove).to.equal(0); + + user.Products.sort(sortById); + expect(user.Products.length).to.equal(2); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[0].Category).to.be.ok; + expect(user.Products[1].Category).not.to.be.ok; + + expect(user.Products[0].Prices.length).to.equal(2); + expect(user.Products[1].Prices.length).to.equal(4); }); - it('should support specifying attributes', function() { + it('should support specifying attributes', async function() { const Project = this.sequelize.define('Project', { title: Sequelize.STRING }); @@ -626,30 +606,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { Project.hasMany(Task); Task.belongsTo(Project); - return this.sequelize.sync({ force: true }).then(() => { - return Task.create({ - title: 'FooBar', - Project: { title: 'BarFoo' } - }, { - include: [Project] - }); - }).then(() => { - return Task.findAll({ - attributes: ['title'], - include: [ - { model: Project, attributes: ['title'] } - ] - }); - }).then(tasks => { - expect(tasks[0].title).to.equal('FooBar'); - expect(tasks[0].Project.title).to.equal('BarFoo'); + await this.sequelize.sync({ force: true }); + + await Task.create({ + title: 'FooBar', + Project: { title: 'BarFoo' } + }, { + include: [Project] + }); - expect(_.omit(tasks[0].get(), 'Project')).to.deep.equal({ title: 'FooBar' }); - expect(tasks[0].Project.get()).to.deep.equal({ title: 'BarFoo' }); + const tasks = await Task.findAll({ + attributes: ['title'], + include: [ + { model: Project, attributes: ['title'] } + ] }); + + expect(tasks[0].title).to.equal('FooBar'); + expect(tasks[0].Project.title).to.equal('BarFoo'); + + expect(_.omit(tasks[0].get(), 'Project')).to.deep.equal({ title: 'FooBar' }); + expect(tasks[0].Project.get()).to.deep.equal({ title: 'BarFoo' }); }); - it('should support Sequelize.literal and renaming of attributes in included model attributes', function() { + it('should support Sequelize.literal and renaming of attributes in included model attributes', async function() { const Post = this.sequelize.define('Post', {}); const PostComment = this.sequelize.define('PostComment', { someProperty: Sequelize.VIRTUAL, // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field @@ -658,75 +638,71 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.hasMany(PostComment); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({}); - }).then(post => { - return post.createPostComment({ - comment_title: 'WAT' - }); - }).then(() => { - let findAttributes; - if (dialect === 'mssql') { - findAttributes = [ - Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "PostComments.someProperty"'), - [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] - ]; - } else { - findAttributes = [ - Sequelize.literal('EXISTS(SELECT 1) AS "PostComments.someProperty"'), - [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] - ]; - } - findAttributes.push(['comment_title', 'commentTitle']); - - return Post.findAll({ - include: [ - { - model: PostComment, - attributes: findAttributes - } - ] - }); - }).then(posts => { - expect(posts[0].PostComments[0].get('someProperty')).to.be.ok; - expect(posts[0].PostComments[0].get('someProperty2')).to.be.ok; - expect(posts[0].PostComments[0].get('commentTitle')).to.equal('WAT'); + await this.sequelize.sync({ force: true }); + const post = await Post.create({}); + + await post.createPostComment({ + comment_title: 'WAT' }); + + let findAttributes; + if (dialect === 'mssql') { + findAttributes = [ + Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "PostComments.someProperty"'), + [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] + ]; + } else { + findAttributes = [ + Sequelize.literal('EXISTS(SELECT 1) AS "PostComments.someProperty"'), + [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] + ]; + } + findAttributes.push(['comment_title', 'commentTitle']); + + const posts = await Post.findAll({ + include: [ + { + model: PostComment, + attributes: findAttributes + } + ] + }); + + expect(posts[0].PostComments[0].get('someProperty')).to.be.ok; + expect(posts[0].PostComments[0].get('someProperty2')).to.be.ok; + expect(posts[0].PostComments[0].get('commentTitle')).to.equal('WAT'); }); - it('should support self associated hasMany (with through) include', function() { + it('should support self associated hasMany (with through) include', async function() { const Group = this.sequelize.define('Group', { name: DataTypes.STRING }); Group.belongsToMany(Group, { through: 'groups_outsourcing_companies', as: 'OutsourcingCompanies' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Group.bulkCreate([ - { name: 'SoccerMoms' }, - { name: 'Coca Cola' }, - { name: 'Dell' }, - { name: 'Pepsi' } - ]); - }).then(() => { - return Group.findAll(); - }).then(groups => { - ctx.groups = groups; - return groups[0].setOutsourcingCompanies(groups.slice(1)); - }).then(() => { - return Group.findOne({ - where: { - id: ctx.groups[0].id - }, - include: [{ model: Group, as: 'OutsourcingCompanies' }] - }); - }).then(group => { - expect(group.OutsourcingCompanies).to.have.length(3); + await this.sequelize.sync({ force: true }); + + await Group.bulkCreate([ + { name: 'SoccerMoms' }, + { name: 'Coca Cola' }, + { name: 'Dell' }, + { name: 'Pepsi' } + ]); + + const groups = await Group.findAll(); + await groups[0].setOutsourcingCompanies(groups.slice(1)); + + const group = await Group.findOne({ + where: { + id: groups[0].id + }, + include: [{ model: Group, as: 'OutsourcingCompanies' }] }); + + expect(group.OutsourcingCompanies).to.have.length(3); }); - it('should support including date fields, with the correct timeszone', function() { + it('should support including date fields, with the correct timeszone', async function() { const User = this.sequelize.define('user', { dateField: Sequelize.DATE }, { timestamps: false }), @@ -737,29 +713,27 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsToMany(Group, { through: 'group_user' }); Group.belongsToMany(User, { through: 'group_user' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ dateField: Date.UTC(2014, 1, 20) }), - Group.create({ dateField: Date.UTC(2014, 1, 20) }) - ]); - }).then(([user, group]) => { - ctx.user = user; - return user.addGroup(group); - }).then(() => { - return User.findOne({ - where: { - id: ctx.user.id - }, - include: [Group] - }); - }).then(user => { - expect(user.dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(user.groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + await this.sequelize.sync({ force: true }); + + const [user0, group] = await Promise.all([ + User.create({ dateField: Date.UTC(2014, 1, 20) }), + Group.create({ dateField: Date.UTC(2014, 1, 20) }) + ]); + + await user0.addGroup(group); + + const user = await User.findOne({ + where: { + id: user0.id + }, + include: [Group] }); + + expect(user.dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(user.groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); }); - it('should support include when retrieving associated objects', function() { + it('should support include when retrieving associated objects', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }), @@ -781,35 +755,30 @@ describe(Support.getTestDialectTeaser('Include'), () => { as: 'Members' }); - const ctx = {}; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ name: 'Owner' }), - User.create({ name: 'Member' }), - Group.create({ name: 'Group' }) - ]); - }).then(([owner, member, group]) => { - ctx.owner = owner; - ctx.member = member; - ctx.group = group; - return owner.addGroup(group); - }).then(() => { - return ctx.group.addMember(ctx.member); - }).then(() => { - return ctx.owner.getGroups({ - include: [{ - model: User, - as: 'Members' - }] - }); - }).then(groups => { - expect(groups.length).to.equal(1); - expect(groups[0].Members[0].name).to.equal('Member'); + await this.sequelize.sync({ force: true }); + + const [owner, member, group] = await Promise.all([ + User.create({ name: 'Owner' }), + User.create({ name: 'Member' }), + Group.create({ name: 'Group' }) + ]); + + await owner.addGroup(group); + await group.addMember(member); + + const groups = await owner.getGroups({ + include: [{ + model: User, + as: 'Members' + }] }); + + expect(groups.length).to.equal(1); + expect(groups[0].Members[0].name).to.equal('Member'); }); }); - const createUsersAndItems = function() { + const createUsersAndItems = async function() { const User = this.sequelize.define('User', {}), Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }); @@ -819,46 +788,46 @@ describe(Support.getTestDialectTeaser('Include'), () => { this.User = User; this.Item = Item; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }), - Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' } - ]).then(() => { - return Item.findAll(); - }) - ]); - }).then(([users, items]) => { - return Promise.all([ - users[0].setItem(items[0]), - users[1].setItem(items[1]), - users[2].setItem(items[2]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const [users, items] = await Promise.all([ + User.bulkCreate([{}, {}, {}]).then(() => { + return User.findAll(); + }), + Item.bulkCreate([ + { 'test': 'abc' }, + { 'test': 'def' }, + { 'test': 'ghi' } + ]).then(() => { + return Item.findAll(); + }) + ]); + + return Promise.all([ + users[0].setItem(items[0]), + users[1].setItem(items[1]), + users[2].setItem(items[2]) + ]); }; describe('where', () => { - beforeEach(function() { - return createUsersAndItems.bind(this)(); + beforeEach(async function() { + await createUsersAndItems.bind(this)(); }); - it('should support Sequelize.and()', function() { - return this.User.findAll({ + it('should support Sequelize.and()', async function() { + const result = await this.User.findAll({ include: [ { model: this.Item, where: Sequelize.and({ test: 'def' }) } ] - }).then(result => { - expect(result.length).to.eql(1); - expect(result[0].Item.test).to.eql('def'); }); + + expect(result.length).to.eql(1); + expect(result[0].Item.test).to.eql('def'); }); - it('should support Sequelize.or()', function() { - return expect(this.User.findAll({ + it('should support Sequelize.or()', async function() { + await expect(this.User.findAll({ include: [ { model: this.Item, where: Sequelize.or({ test: 'def' @@ -871,26 +840,26 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); describe('findAndCountAll', () => { - it('should include associations to findAndCountAll', function() { - return createUsersAndItems.bind(this)().then(() => { - return this.User.findAndCountAll({ - include: [ - { model: this.Item, where: { - test: 'def' - } } - ] - }); - }).then(result => { - expect(result.count).to.eql(1); + it('should include associations to findAndCountAll', async function() { + await createUsersAndItems.bind(this)(); - expect(result.rows.length).to.eql(1); - expect(result.rows[0].Item.test).to.eql('def'); + const result = await this.User.findAndCountAll({ + include: [ + { model: this.Item, where: { + test: 'def' + } } + ] }); + + expect(result.count).to.eql(1); + + expect(result.rows.length).to.eql(1); + expect(result.rows[0].Item.test).to.eql('def'); }); }); describe('association getter', () => { - it('should support getting an include on a N:M association getter', function() { + it('should support getting an include on a N:M association getter', async function() { const Question = this.sequelize.define('Question', {}), Answer = this.sequelize.define('Answer', {}), Questionnaire = this.sequelize.define('Questionnaire', {}); @@ -901,18 +870,109 @@ describe(Support.getTestDialectTeaser('Include'), () => { Questionnaire.hasMany(Question); Question.belongsTo(Questionnaire); - return this.sequelize.sync({ force: true }).then(() => { - return Questionnaire.create(); - }).then(questionnaire => { - return questionnaire.getQuestions({ - include: Answer + await this.sequelize.sync({ force: true }); + const questionnaire = await Questionnaire.create(); + + await questionnaire.getQuestions({ + include: Answer + }); + }); + }); + + describe('right join', () => { + it('should support getting an include with a right join', async function() { + const User = this.sequelize.define('user', { + name: DataTypes.STRING + }), + Group = this.sequelize.define('group', { + name: DataTypes.STRING + }); + + User.hasMany(Group); + Group.belongsTo(User); + + await this.sequelize.sync({ force: true }); + + await Promise.all([ + User.create({ name: 'User 1' }), + User.create({ name: 'User 2' }), + User.create({ name: 'User 3' }), + Group.create({ name: 'A Group' }) + ]); + + const groups = await Group.findAll({ + include: [{ + model: User, + right: true + }] + }); + + if (current.dialect.supports['RIGHT JOIN']) { + expect(groups.length).to.equal(3); + } else { + expect(groups.length).to.equal(1); + } + }); + + it('should support getting an include through with a right join', async function() { + const User = this.sequelize.define('user', { + name: DataTypes.STRING + }), + Group = this.sequelize.define('group', { + name: DataTypes.STRING + }), + UserGroup = this.sequelize.define('user_group', { + vip: DataTypes.INTEGER }); + + User.hasMany(Group); + Group.belongsTo(User); + User.belongsToMany(Group, { + through: UserGroup, + as: 'Clubs', + constraints: false }); + Group.belongsToMany(User, { + through: UserGroup, + as: 'Members', + constraints: false + }); + + await this.sequelize.sync({ force: true }); + + const [member1, member2, group1, group2] = await Promise.all([ + User.create({ name: 'Member 1' }), + User.create({ name: 'Member 2' }), + Group.create({ name: 'Group 1' }), + Group.create({ name: 'Group 2' }) + ]); + + await Promise.all([ + group1.addMember(member1), + group1.addMember(member2), + group2.addMember(member1) + ]); + + await group2.destroy(); + + const groups = await Group.findAll({ + include: [{ + model: User, + as: 'Members', + right: true + }] + }); + + if (current.dialect.supports['RIGHT JOIN']) { + expect(groups.length).to.equal(2); + } else { + expect(groups.length).to.equal(1); + } }); }); describe('nested includes', () => { - beforeEach(function() { + beforeEach(async function() { const Employee = this.sequelize.define('Employee', { 'name': DataTypes.STRING }); const Team = this.sequelize.define('Team', { 'name': DataTypes.STRING }); const Clearence = this.sequelize.define('Clearence', { 'level': DataTypes.INTEGER }); @@ -925,29 +985,29 @@ describe(Support.getTestDialectTeaser('Include'), () => { this.Team = Team; this.Clearence = Clearence; - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Team.create({ name: 'TeamA' }), - Team.create({ name: 'TeamB' }), - Employee.create({ name: 'John' }), - Employee.create({ name: 'Jane' }), - Employee.create({ name: 'Josh' }), - Employee.create({ name: 'Jill' }), - Clearence.create({ level: 3 }), - Clearence.create({ level: 5 }) - ]).then(instances => { - return Promise.all([ - instances[0].addMembers([instances[2], instances[3]]), - instances[1].addMembers([instances[4], instances[5]]), - instances[2].setClearence(instances[6]), - instances[3].setClearence(instances[7]) - ]); - }); - }); + await this.sequelize.sync({ force: true }); + + const instances = await Promise.all([ + Team.create({ name: 'TeamA' }), + Team.create({ name: 'TeamB' }), + Employee.create({ name: 'John' }), + Employee.create({ name: 'Jane' }), + Employee.create({ name: 'Josh' }), + Employee.create({ name: 'Jill' }), + Clearence.create({ level: 3 }), + Clearence.create({ level: 5 }) + ]); + + await Promise.all([ + instances[0].addMembers([instances[2], instances[3]]), + instances[1].addMembers([instances[4], instances[5]]), + instances[2].setClearence(instances[6]), + instances[3].setClearence(instances[7]) + ]); }); - it('should not ripple grandchild required to top level find when required of child is set to false', function() { - return this.Team.findAll({ + it('should not ripple grandchild required to top level find when required of child is set to false', async function() { + const teams = await this.Team.findAll({ include: [ { association: this.Team.Members, @@ -960,13 +1020,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { ] } ] - }).then(teams => { - expect(teams).to.have.length(2); }); + + expect(teams).to.have.length(2); }); - it('should support eager loading associations using the name of the relation (string)', function() { - return this.Team.findOne({ + it('should support eager loading associations using the name of the relation (string)', async function() { + const team = await this.Team.findOne({ where: { name: 'TeamA' }, @@ -976,13 +1036,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { required: true } ] - }).then(team => { - expect(team.members).to.have.length(2); }); + + expect(team.members).to.have.length(2); }); - it('should not ripple grandchild required to top level find when required of child is not given (implicitly false)', function() { - return this.Team.findAll({ + it('should not ripple grandchild required to top level find when required of child is not given (implicitly false)', async function() { + const teams = await this.Team.findAll({ include: [ { association: this.Team.Members, @@ -994,13 +1054,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { ] } ] - }).then(teams => { - expect(teams).to.have.length(2); }); + + expect(teams).to.have.length(2); }); - it('should ripple grandchild required to top level find when required of child is set to true as well', function() { - return this.Team.findAll({ + it('should ripple grandchild required to top level find when required of child is set to true as well', async function() { + const teams = await this.Team.findAll({ include: [ { association: this.Team.Members, @@ -1013,9 +1073,9 @@ describe(Support.getTestDialectTeaser('Include'), () => { ] } ] - }).then(teams => { - expect(teams).to.have.length(1); }); + + expect(teams).to.have.length(1); }); }); diff --git a/test/integration/include/findAll.test.js b/test/integration/include/findAll.test.js index 6399b115565b..0ce86e1eb222 100644 --- a/test/integration/include/findAll.test.js +++ b/test/integration/include/findAll.test.js @@ -3,11 +3,11 @@ const chai = require('chai'), Sequelize = require('../../../index'), Op = Sequelize.Op, - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - _ = require('lodash'); + _ = require('lodash'), + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -16,7 +16,7 @@ const sortById = function(a, b) { describe(Support.getTestDialectTeaser('Include'), () => { describe('findAll', () => { beforeEach(function() { - this.fixtureA = function() { + this.fixtureA = async function() { const User = this.sequelize.define('User', {}), Company = this.sequelize.define('Company', { name: DataTypes.STRING @@ -84,118 +84,94 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' }, - { name: 'Managers' } - ]).then(() => { - return Group.findAll(); - }), - companies: Company.bulkCreate([ - { name: 'Sequelize' }, - { name: 'Coca Cola' }, - { name: 'Bonanza' }, - { name: 'NYSE' }, - { name: 'Coshopr' } - ]).then(() => { - return Company.findAll(); - }), - ranks: Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, - { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } - ]).then(() => { - return Rank.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' }, - { name: 'D' }, - { name: 'E' } - ]).then(() => { - return Tag.findAll(); - }) - }).then(results => { - const groups = results.groups, - ranks = results.ranks, - tags = results.tags, - companies = results.companies; - - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.props({ - user: User.create(), - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Bed' }, - { title: 'Pen' }, - { title: 'Monitor' } - ]).then(() => { - return Product.findAll(); - }) - }).then(results => { - const user = results.user, - products = results.products, - groupMembers = [ - { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } - ]; - if (i < 3) { - groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); - } - - return Promise.join( - GroupMember.bulkCreate(groupMembers), - user.setProducts([ - products[i * 5 + 0], - products[i * 5 + 1], - products[i * 5 + 3] - ]), - Promise.join( - products[i * 5 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 5 + 1].setTags([ - tags[1] - ]), - products[i * 5 + 0].setCategory(tags[1]), - products[i * 5 + 2].setTags([ - tags[0] - ]), - products[i * 5 + 3].setTags([ - tags[0] - ]) - ), - Promise.join( - products[i * 5 + 0].setCompany(companies[4]), - products[i * 5 + 1].setCompany(companies[3]), - products[i * 5 + 2].setCompany(companies[2]), - products[i * 5 + 3].setCompany(companies[1]), - products[i * 5 + 4].setCompany(companies[0]) - ), - Price.bulkCreate([ - { ProductId: products[i * 5 + 0].id, value: 5 }, - { ProductId: products[i * 5 + 0].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 5 }, - { ProductId: products[i * 5 + 1].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 15 }, - { ProductId: products[i * 5 + 1].id, value: 20 }, - { ProductId: products[i * 5 + 2].id, value: 20 }, - { ProductId: products[i * 5 + 3].id, value: 20 } - ]) - ); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' }, + { name: 'Managers' } + ]); + const groups = await Group.findAll(); + await Company.bulkCreate([ + { name: 'Sequelize' }, + { name: 'Coca Cola' }, + { name: 'Bonanza' }, + { name: 'NYSE' }, + { name: 'Coshopr' } + ]); + const companies = await Company.findAll(); + await Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, + { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } + ]); + const ranks = await Rank.findAll(); + await Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' }, + { name: 'D' }, + { name: 'E' } + ]); + const tags = await Tag.findAll(); + for (const i of [0, 1, 2, 3, 4]) { + const user = await User.create(); + await Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Bed' }, + { title: 'Pen' }, + { title: 'Monitor' } + ]); + const products = await Product.findAll(); + const groupMembers = [ + { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } + ]; + if (i < 3) { + groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); + } + await Promise.all([ + GroupMember.bulkCreate(groupMembers), + user.setProducts([ + products[i * 5 + 0], + products[i * 5 + 1], + products[i * 5 + 3] + ]), + products[i * 5 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 5 + 1].setTags([ + tags[1] + ]), + products[i * 5 + 0].setCategory(tags[1]), + products[i * 5 + 2].setTags([ + tags[0] + ]), + products[i * 5 + 3].setTags([ + tags[0] + ]), + products[i * 5 + 0].setCompany(companies[4]), + products[i * 5 + 1].setCompany(companies[3]), + products[i * 5 + 2].setCompany(companies[2]), + products[i * 5 + 3].setCompany(companies[1]), + products[i * 5 + 4].setCompany(companies[0]), + Price.bulkCreate([ + { ProductId: products[i * 5 + 0].id, value: 5 }, + { ProductId: products[i * 5 + 0].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 5 }, + { ProductId: products[i * 5 + 1].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 15 }, + { ProductId: products[i * 5 + 1].id, value: 20 }, + { ProductId: products[i * 5 + 2].id, value: 20 }, + { ProductId: products[i * 5 + 3].id, value: 20 } + ]) + ]); + } }; }); - it('should work on a nested set of relations with a where condition in between relations', function() { + it('should work on a nested set of relations with a where condition in between relations', async function() { const User = this.sequelize.define('User', {}), SubscriptionForm = this.sequelize.define('SubscriptionForm', {}), Collection = this.sequelize.define('Collection', {}), @@ -218,42 +194,42 @@ describe(Support.getTestDialectTeaser('Include'), () => { Category.hasMany(SubCategory, { foreignKey: 'boundCategory' }); SubCategory.belongsTo(Category, { foreignKey: 'boundCategory' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.findOne({ - include: [ - { - model: SubscriptionForm, - include: [ - { - model: Collection, - where: { - id: 13 - } - }, - { - model: Category, - include: [ - { - model: SubCategory - }, - { - model: Capital, - include: [ - { - model: Category - } - ] - } - ] + await this.sequelize.sync({ force: true }); + + await User.findOne({ + include: [ + { + model: SubscriptionForm, + include: [ + { + model: Collection, + where: { + id: 13 } - ] - } - ] - }); + }, + { + model: Category, + include: [ + { + model: SubCategory + }, + { + model: Capital, + include: [ + { + model: Category + } + ] + } + ] + } + ] + } + ] }); }); - it('should accept nested `where` and `limit` at the same time', function() { + it('should accept nested `where` and `limit` at the same time', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -272,59 +248,51 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Set.bulkCreate([ - { title: 'office' } - ]), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ).then(() => { - return Promise.join( - Set.findAll(), - Product.findAll(), - Tag.findAll() - ); - }).then(([sets, products, tags]) => { - return Promise.join( - sets[0].addProducts([products[0], products[1]]), - products[0].addTag(tags[0], { priority: 1 }).then(() => { - return products[0].addTag(tags[1], { priority: 2 }); - }).then(() => { - return products[0].addTag(tags[2], { priority: 1 }); - }), - products[1].addTag(tags[1], { priority: 2 }).then(() => { - return products[2].addTag(tags[1], { priority: 3 }); - }).then(() => { - return products[2].addTag(tags[2], { priority: 0 }); - }) - ); + await this.sequelize.sync({ force: true }); + + await Promise.all([Set.bulkCreate([ + { title: 'office' } + ]), Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ])]); + + const [sets, products, tags] = await Promise.all([Set.findAll(), Product.findAll(), Tag.findAll()]); + + await Promise.all([ + sets[0].addProducts([products[0], products[1]]), + products[0].addTag(tags[0], { priority: 1 }).then(() => { + return products[0].addTag(tags[1], { priority: 2 }); }).then(() => { - return Set.findAll({ - include: [{ - model: Product, - include: [{ - model: Tag, - where: { - name: 'A' - } - }] - }], - limit: 1 - }); - }); + return products[0].addTag(tags[2], { priority: 1 }); + }), + products[1].addTag(tags[1], { priority: 2 }).then(() => { + return products[2].addTag(tags[1], { priority: 3 }); + }).then(() => { + return products[2].addTag(tags[2], { priority: 0 }); + }) + ]); + + await Set.findAll({ + include: [{ + model: Product, + include: [{ + model: Tag, + where: { + name: 'A' + } + }] + }], + limit: 1 }); }); - it('should support an include with multiple different association types', function() { + it('should support an include with multiple different association types', async function() { const User = this.sequelize.define('User', {}), Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -369,108 +337,95 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } - ]).then(() => { - return Group.findAll(); - }), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } - ]).then(() => { - return Rank.findAll(); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]).then(([groups, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.all([ - User.create(), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - return Promise.all([ - GroupMember.bulkCreate([ - { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - user.setProducts([ - products[i * 2 + 0], - products[i * 2 + 1] - ]), - products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 2 + 1].setTags([ - tags[1] - ]), - products[i * 2 + 0].setCategory(tags[1]), - Price.bulkCreate([ - { ProductId: products[i * 2 + 0].id, value: 5 }, - { ProductId: products[i * 2 + 0].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 5 }, - { ProductId: products[i * 2 + 1].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 15 }, - { ProductId: products[i * 2 + 1].id, value: 20 } - ]) - ]); - }); - }).then(() => { - return User.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - Rank - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - Price - ] } - ], - order: [ - ['id', 'ASC'] - ] - }).then(users => { - users.forEach(user => { - user.Memberships.sort(sortById); - - expect(user.Memberships.length).to.equal(2); - expect(user.Memberships[0].Group.name).to.equal('Developers'); - expect(user.Memberships[0].Rank.canRemove).to.equal(1); - expect(user.Memberships[1].Group.name).to.equal('Designers'); - expect(user.Memberships[1].Rank.canRemove).to.equal(0); - - user.Products.sort(sortById); - expect(user.Products.length).to.equal(2); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[0].Category).to.be.ok; - expect(user.Products[1].Category).not.to.be.ok; - - expect(user.Products[0].Prices.length).to.equal(2); - expect(user.Products[1].Prices.length).to.equal(4); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + User.create(), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()) + ]); + await Promise.all([ + GroupMember.bulkCreate([ + { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } + ]), + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] + ]), + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } + ]) + ]); + const users = await User.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + Rank + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + Price + ] } + ], + order: [ + ['id', 'ASC'] + ] }); - }); + for (const user of users) { + user.Memberships.sort(sortById); + + expect(user.Memberships.length).to.equal(2); + expect(user.Memberships[0].Group.name).to.equal('Developers'); + expect(user.Memberships[0].Rank.canRemove).to.equal(1); + expect(user.Memberships[1].Group.name).to.equal('Designers'); + expect(user.Memberships[1].Rank.canRemove).to.equal(0); + + user.Products.sort(sortById); + expect(user.Products.length).to.equal(2); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[0].Category).to.be.ok; + expect(user.Products[1].Category).not.to.be.ok; + + expect(user.Products[0].Prices.length).to.equal(2); + expect(user.Products[1].Prices.length).to.equal(4); + } + } }); - it('should support many levels of belongsTo', function() { + it('should support many levels of belongsTo', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), C = this.sequelize.define('c', {}), @@ -488,77 +443,74 @@ describe(Support.getTestDialectTeaser('Include'), () => { F.belongsTo(G); G.belongsTo(H); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - A.bulkCreate([ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ]).then(() => { - return A.findAll(); - }), - (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; - - singles.forEach(model => { - promise = promise.then(() => { - return model.create({}).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); - - promise = promise.then(() => { - return b; - }); - - return promise; - })([B, C, D, E, F, G, H]) - ).then(([as, b]) => { - return Promise.map(as, a => { - return a.setB(b); - }); - }).then(() => { - return A.findAll({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, include: [ - { model: H } - ] } - ] } + await this.sequelize.sync({ force: true }); + + const [as0, b] = await Promise.all([A.bulkCreate([ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ]).then(() => { + return A.findAll(); + }), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; + + singles.forEach(model => { + promise = (async () => { + await promise; + const instance = await model.create({}); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + return; + } + previousInstance = b = instance; + })(); + }); + + promise = promise.then(() => { + return b; + }); + + return promise; + })([B, C, D, E, F, G, H])]); + + await Promise.all(as0.map(a => { + return a.setB(b); + })); + + const as = await A.findAll({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(as => { - expect(as.length).to.be.ok; + ] } + ] } + ] + }); - as.forEach(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); - }); + expect(as.length).to.be.ok; + + as.forEach(a => { + expect(a.b.c.d.e.f.g.h).to.be.ok; }); }); - it('should support many levels of belongsTo (with a lower level having a where)', function() { + it('should support many levels of belongsTo (with a lower level having a where)', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), C = this.sequelize.define('c', {}), @@ -580,85 +532,82 @@ describe(Support.getTestDialectTeaser('Include'), () => { F.belongsTo(G); G.belongsTo(H); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - A.bulkCreate([ - {}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ]).then(() => { - return A.findAll(); - }), - (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; + await this.sequelize.sync({ force: true }); + + const [as0, b] = await Promise.all([A.bulkCreate([ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ]).then(() => { + return A.findAll(); + }), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; + + singles.forEach(model => { + const values = {}; + + if (model.name === 'g') { + values.name = 'yolo'; + } - singles.forEach(model => { - const values = {}; + promise = (async () => { + await promise; + const instance = await model.create(values); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + return; + } + previousInstance = b = instance; + })(); + }); - if (model.name === 'g') { - values.name = 'yolo'; - } + promise = promise.then(() => { + return b; + }); - promise = promise.then(() => { - return model.create(values).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); - - promise = promise.then(() => { - return b; - }); - - return promise; - })([B, C, D, E, F, G, H]) - ).then(([as, b]) => { - return Promise.map(as, a => { - return a.setB(b); - }); - }).then(() => { - return A.findAll({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, where: { - name: 'yolo' - }, include: [ - { model: H } - ] } - ] } + return promise; + })([B, C, D, E, F, G, H])]); + + await Promise.all(as0.map(a => { + return a.setB(b); + })); + + const as = await A.findAll({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, where: { + name: 'yolo' + }, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(as => { - expect(as.length).to.be.ok; + ] } + ] } + ] + }); - as.forEach(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); - }); + expect(as.length).to.be.ok; + + as.forEach(a => { + expect(a.b.c.d.e.f.g.h).to.be.ok; }); }); - it('should support ordering with only belongsTo includes', function() { + it('should support ordering with only belongsTo includes', async function() { const User = this.sequelize.define('User', {}), Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }), Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }); @@ -667,74 +616,74 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); User.belongsTo(Order); - return this.sequelize.sync().then(() => { - return Promise.props({ - users: User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }), - items: Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' }, - { 'test': 'jkl' } - ]).then(() => { - return Item.findAll({ order: ['id'] }); - }), - orders: Order.bulkCreate([ - { 'position': 2 }, - { 'position': 3 }, - { 'position': 1 } - ]).then(() => { - return Order.findAll({ order: ['id'] }); - }) - }).then(results => { - const user1 = results.users[0]; - const user2 = results.users[1]; - const user3 = results.users[2]; - - const item1 = results.items[0]; - const item2 = results.items[1]; - const item3 = results.items[2]; - const item4 = results.items[3]; - - const order1 = results.orders[0]; - const order2 = results.orders[1]; - const order3 = results.orders[2]; - - return Promise.join( - user1.setItemA(item1), - user1.setItemB(item2), - user1.setOrder(order3), - user2.setItemA(item3), - user2.setItemB(item4), - user2.setOrder(order2), - user3.setItemA(item1), - user3.setItemB(item4), - user3.setOrder(order1) - ); - }).then(() => { - return User.findAll({ - 'include': [ - { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, - { 'model': Item, 'as': 'itemB' }, - Order], - 'order': [ - [Order, 'position'] - ] - }).then(as => { - expect(as.length).to.eql(2); + await this.sequelize.sync(); - expect(as[0].itemA.test).to.eql('abc'); - expect(as[1].itemA.test).to.eql('abc'); + const results = await promiseProps({ + users: User.bulkCreate([{}, {}, {}]).then(() => { + return User.findAll(); + }), + items: Item.bulkCreate([ + { 'test': 'abc' }, + { 'test': 'def' }, + { 'test': 'ghi' }, + { 'test': 'jkl' } + ]).then(() => { + return Item.findAll({ order: ['id'] }); + }), + orders: Order.bulkCreate([ + { 'position': 2 }, + { 'position': 3 }, + { 'position': 1 } + ]).then(() => { + return Order.findAll({ order: ['id'] }); + }) + }); - expect(as[0].Order.position).to.eql(1); - expect(as[1].Order.position).to.eql(2); - }); - }); + const user1 = results.users[0]; + const user2 = results.users[1]; + const user3 = results.users[2]; + + const item1 = results.items[0]; + const item2 = results.items[1]; + const item3 = results.items[2]; + const item4 = results.items[3]; + + const order1 = results.orders[0]; + const order2 = results.orders[1]; + const order3 = results.orders[2]; + + await Promise.all([ + user1.setItemA(item1), + user1.setItemB(item2), + user1.setOrder(order3), + user2.setItemA(item3), + user2.setItemB(item4), + user2.setOrder(order2), + user3.setItemA(item1), + user3.setItemB(item4), + user3.setOrder(order1) + ]); + + const as = await User.findAll({ + 'include': [ + { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, + { 'model': Item, 'as': 'itemB' }, + Order], + 'order': [ + [Order, 'position'] + ] }); + + expect(as.length).to.eql(2); + + expect(as[0].itemA.test).to.eql('abc'); + expect(as[1].itemA.test).to.eql('abc'); + + expect(as[0].Order.position).to.eql(1); + expect(as[1].Order.position).to.eql(2); }); - it('should include attributes from through models', function() { + it('should include attributes from through models', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -748,84 +697,84 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]).then(() => { - return Product.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - }).then(results => { - return Promise.join( - results.products[0].addTag(results.tags[0], { through: { priority: 1 } }), - results.products[0].addTag(results.tags[1], { through: { priority: 2 } }), - results.products[1].addTag(results.tags[1], { through: { priority: 1 } }), - results.products[2].addTag(results.tags[0], { through: { priority: 3 } }), - results.products[2].addTag(results.tags[1], { through: { priority: 1 } }), - results.products[2].addTag(results.tags[2], { through: { priority: 2 } }) - ); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag } - ], - order: [ - ['id', 'ASC'], - [Tag, 'id', 'ASC'] - ] - }).then(products => { - expect(products[0].Tags[0].ProductTag.priority).to.equal(1); - expect(products[0].Tags[1].ProductTag.priority).to.equal(2); + await this.sequelize.sync({ force: true }); - expect(products[1].Tags[0].ProductTag.priority).to.equal(1); + const results = await promiseProps({ + products: Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]).then(() => { + return Product.findAll(); + }), + tags: Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll(); + }) + }); - expect(products[2].Tags[0].ProductTag.priority).to.equal(3); - expect(products[2].Tags[1].ProductTag.priority).to.equal(1); - expect(products[2].Tags[2].ProductTag.priority).to.equal(2); - }); - }); + await Promise.all([ + results.products[0].addTag(results.tags[0], { through: { priority: 1 } }), + results.products[0].addTag(results.tags[1], { through: { priority: 2 } }), + results.products[1].addTag(results.tags[1], { through: { priority: 1 } }), + results.products[2].addTag(results.tags[0], { through: { priority: 3 } }), + results.products[2].addTag(results.tags[1], { through: { priority: 1 } }), + results.products[2].addTag(results.tags[2], { through: { priority: 2 } }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag } + ], + order: [ + ['id', 'ASC'], + [Tag, 'id', 'ASC'] + ] }); + + expect(products[0].Tags[0].ProductTag.priority).to.equal(1); + expect(products[0].Tags[1].ProductTag.priority).to.equal(2); + + expect(products[1].Tags[0].ProductTag.priority).to.equal(1); + + expect(products[2].Tags[0].ProductTag.priority).to.equal(3); + expect(products[2].Tags[1].ProductTag.priority).to.equal(1); + expect(products[2].Tags[2].ProductTag.priority).to.equal(2); }); - it('should support a required belongsTo include', function() { + it('should support a required belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', {}); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([{}, {}]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return results.users[2].setGroup(results.groups[1]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([{}, {}]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}, {}]).then(() => { + return User.findAll(); + }) + }); + + await results.users[2].setGroup(results.groups[1]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -833,37 +782,37 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - expect(users[0].Group.name).to.equal('A'); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }) }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ] + }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; + expect(users[0].Group.name).to.equal('A'); }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -871,37 +820,37 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - users.forEach(user => { - expect(user.Group).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] + }); + + users.forEach(user => { + expect(user.Group).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany not required', function() { + it('should be possible to define a belongsTo include as required with child hasMany not required', async function() { const Address = this.sequelize.define('Address', { 'active': DataTypes.BOOLEAN }), Street = this.sequelize.define('Street', { 'active': DataTypes.BOOLEAN }), User = this.sequelize.define('User', { 'username': DataTypes.STRING }); @@ -914,33 +863,31 @@ describe(Support.getTestDialectTeaser('Include'), () => { Street.hasMany(Address, { foreignKey: 'streetId' }); // Sync - return this.sequelize.sync({ force: true }).then(() => { - return Street.create({ active: true }).then(street => { - return Address.create({ active: true, streetId: street.id }).then(address => { - return User.create({ username: 'John', addressId: address.id }).then(() => { - return User.findOne({ - where: { username: 'John' }, - include: [{ - model: Address, - required: true, - where: { - active: true - }, - include: [{ - model: Street - }] - }] - }).then(john => { - expect(john.Address).to.be.ok; - expect(john.Address.Street).to.be.ok; - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const street = await Street.create({ active: true }); + const address = await Address.create({ active: true, streetId: street.id }); + await User.create({ username: 'John', addressId: address.id }); + + const john = await User.findOne({ + where: { username: 'John' }, + include: [{ + model: Address, + required: true, + where: { + active: true + }, + include: [{ + model: Street + }] + }] }); + + expect(john.Address).to.be.ok; + expect(john.Address.Street).to.be.ok; }); - it('should be possible to define a belongsTo include as required with child hasMany with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -952,48 +899,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }), - categories: Category.bulkCreate([{}, {}]).then(() => { - return Category.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]), - Promise.map(results.groups, group => { - return group.setCategories(results.categories); - }) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }), + categories: Category.bulkCreate([{}, {}]).then(() => { + return Category.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]), + Promise.all(results.groups.map(group => { + return group.setCategories(results.categories); + })) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1005,48 +952,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group, { as: 'Team' }); Group.hasMany(Category, { as: 'Tags' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }), - categories: Category.bulkCreate([{}, {}]).then(() => { - return Category.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[0].setTeam(results.groups[1]), - results.users[1].setTeam(results.groups[0]), - Promise.map(results.groups, group => { - return group.setTags(results.categories); - }) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, as: 'Team', include: [ - { model: Category, as: 'Tags' } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Team).to.be.ok; - expect(user.Team.Tags).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }), + categories: Category.bulkCreate([{}, {}]).then(() => { + return Category.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setTeam(results.groups[1]), + results.users[1].setTeam(results.groups[0]), + Promise.all(results.groups.map(group => { + return group.setTags(results.categories); + })) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, as: 'Team', include: [ + { model: Category, as: 'Tags' } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Team).to.be.ok; + expect(user.Team.Tags).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1058,48 +1005,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }), - categories: Category.bulkCreate([{}, {}]).then(() => { - return Category.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[0].setGroup(results.groups[1]), - results.users[1].setGroup(results.groups[0]), - Promise.map(results.groups, group => { - return group.setCategories(results.categories); - }) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category, required: false } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }), + categories: Category.bulkCreate([{}, {}]).then(() => { + return Category.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[1]), + results.users[1].setGroup(results.groups[0]), + Promise.all(results.groups.map(group => { + return group.setCategories(results.categories); + })) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category, required: false } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to extend the on clause with a where option on a hasOne include', function() { + it('should be possible to extend the on clause with a where option on a hasOne include', async function() { const User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project', { title: DataTypes.STRING @@ -1107,37 +1054,37 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasOne(Project, { as: 'LeaderOf' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - projects: Project.bulkCreate([ - { title: 'Alpha' }, - { title: 'Beta' } - ]).then(() => { - return Project.findAll(); - }), - users: User.bulkCreate([{}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[1].setLeaderOf(results.projects[1]), - results.users[0].setLeaderOf(results.projects[0]) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].LeaderOf).to.be.ok; - expect(users[0].LeaderOf.title).to.equal('Beta'); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + projects: Project.bulkCreate([ + { title: 'Alpha' }, + { title: 'Beta' } + ]).then(() => { + return Project.findAll(); + }), + users: User.bulkCreate([{}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[1].setLeaderOf(results.projects[1]), + results.users[0].setLeaderOf(results.projects[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].LeaderOf).to.be.ok; + expect(users[0].LeaderOf.title).to.equal('Beta'); }); - it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function() { + it('should be possible to extend the on clause with a where option on a hasMany include with a through model', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -1151,45 +1098,45 @@ describe(Support.getTestDialectTeaser('Include'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]).then(() => { - return Product.findAll(); - }), - tags: Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - }).then(results => { - return Promise.join( - results.products[0].addTag(results.tags[0], { priority: 1 }), - results.products[0].addTag(results.tags[1], { priority: 2 }), - results.products[1].addTag(results.tags[1], { priority: 1 }), - results.products[2].addTag(results.tags[0], { priority: 3 }), - results.products[2].addTag(results.tags[1], { priority: 1 }), - results.products[2].addTag(results.tags[2], { priority: 2 }) - ); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag, where: { name: 'C' } } - ] - }).then(products => { - expect(products.length).to.equal(1); - expect(products[0].Tags.length).to.equal(1); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + products: Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]).then(() => { + return Product.findAll(); + }), + tags: Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => { + return Tag.findAll(); + }) }); + + await Promise.all([ + results.products[0].addTag(results.tags[0], { priority: 1 }), + results.products[0].addTag(results.tags[1], { priority: 2 }), + results.products[1].addTag(results.tags[1], { priority: 1 }), + results.products[2].addTag(results.tags[0], { priority: 3 }), + results.products[2].addTag(results.tags[1], { priority: 1 }), + results.products[2].addTag(results.tags[2], { priority: 2 }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag, where: { name: 'C' } } + ] + }); + + expect(products.length).to.equal(1); + expect(products[0].Tags.length).to.equal(1); }); - it('should be possible to extend the on clause with a where option on nested includes', function() { + it('should be possible to extend the on clause with a where option on nested includes', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING }), @@ -1236,101 +1183,87 @@ describe(Support.getTestDialectTeaser('Include'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } - ]).then(() => { - return Group.findAll(); - }), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } - ]).then(() => { - return Rank.findAll(); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]).then(([groups, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.props({ - user: User.create({ name: 'FooBarzz' }), - products: Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - }).then(results => { - return Promise.join( - GroupMember.bulkCreate([ - { UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - results.user.setProducts([ - results.products[i * 2 + 0], - results.products[i * 2 + 1] - ]), - Promise.join( - results.products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - results.products[i * 2 + 1].setTags([ - tags[1] - ]), - results.products[i * 2 + 0].setCategory(tags[1]) - ), - Price.bulkCreate([ - { ProductId: results.products[i * 2 + 0].id, value: 5 }, - { ProductId: results.products[i * 2 + 0].id, value: 10 }, - { ProductId: results.products[i * 2 + 1].id, value: 5 }, - { ProductId: results.products[i * 2 + 1].id, value: 10 }, - { ProductId: results.products[i * 2 + 1].id, value: 15 }, - { ProductId: results.products[i * 2 + 1].id, value: 20 } - ]) - ); - }); - }); - }).then(() => { - return User.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - { model: Rank, where: { name: 'Admin' } } - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - { model: Price, where: { - value: { - [Op.gt]: 15 - } - } } - ] } - ], - order: [ - ['id', 'ASC'] - ] - }).then(users => { - users.forEach(user => { - expect(user.Memberships.length).to.equal(1); - expect(user.Memberships[0].Rank.name).to.equal('Admin'); - expect(user.Products.length).to.equal(1); - expect(user.Products[0].Prices.length).to.equal(1); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const user = await User.create({ name: 'FooBarzz' }); + + await Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]); + + const products = await Product.findAll(); + await Promise.all([ + GroupMember.bulkCreate([ + { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } + ]), + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] + ]), + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } + ]) + ]); + } + const users = await User.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + { model: Rank, where: { name: 'Admin' } } + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + { model: Price, where: { + value: { + [Op.gt]: 15 + } + } } + ] } + ], + order: [ + ['id', 'ASC'] + ] }); + for (const user of users) { + expect(user.Memberships.length).to.equal(1); + expect(user.Memberships[0].Rank.name).to.equal('Admin'); + expect(user.Products.length).to.equal(1); + expect(user.Products[0].Prices.length).to.equal(1); + } }); - it('should be possible to use limit and a where with a belongsTo include', function() { + it('should be possible to use limit and a where with a belongsTo include', async function() { const User = this.sequelize.define('User', {}), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1338,160 +1271,159 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}, {}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[0].setGroup(results.groups[0]), - results.users[1].setGroup(results.groups[0]), - results.users[2].setGroup(results.groups[0]), - results.users[3].setGroup(results.groups[1]) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ], - limit: 2 - }).then(users => { - expect(users.length).to.equal(2); - - users.forEach(user => { - expect(user.Group.name).to.equal('A'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}, {}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[0].setGroup(results.groups[0]), + results.users[1].setGroup(results.groups[0]), + results.users[2].setGroup(results.groups[0]), + results.users[3].setGroup(results.groups[1]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ], + limit: 2 + }); + + expect(users.length).to.equal(2); + + users.forEach(user => { + expect(user.Group.name).to.equal('A'); }); }); - it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['id', 'title'], - include: [ - { model: this.models.Company, where: { name: 'NYSE' } }, - { model: this.models.Tag }, - { model: this.models.Price } - ], - limit: 3, - order: [ - [this.sequelize.col(`${this.models.Product.name}.id`), 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(3); + it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['id', 'title'], + include: [ + { model: this.models.Company, where: { name: 'NYSE' } }, + { model: this.models.Tag }, + { model: this.models.Price } + ], + limit: 3, + order: [ + [this.sequelize.col(`${this.models.Product.name}.id`), 'ASC'] + ] + }); - products.forEach(product => { - expect(product.Company.name).to.equal('NYSE'); - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - }); - }); + expect(products.length).to.equal(3); + + products.forEach(product => { + expect(product.Company.name).to.equal('NYSE'); + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; }); }); - it('should be possible to have the primary key in attributes', function() { + it('should be possible to have the primary key in attributes', async function() { const Parent = this.sequelize.define('Parent', {}); const Child1 = this.sequelize.define('Child1', {}); Parent.hasMany(Child1); Child1.belongsTo(Parent); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Parent.create(), - Child1.create() - ]); - }).then(([parent, child]) => { - return parent.addChild1(child).then(() => { - return parent; - }); - }).then(parent => { - return Child1.findOne({ - include: [ - { - model: Parent, - attributes: ['id'], // This causes a duplicated entry in the query - where: { - id: parent.id - } + await this.sequelize.sync({ force: true }); + + const [parent0, child] = await Promise.all([ + Parent.create(), + Child1.create() + ]); + + await parent0.addChild1(child); + const parent = parent0; + + await Child1.findOne({ + include: [ + { + model: Parent, + attributes: ['id'], // This causes a duplicated entry in the query + where: { + id: parent.id } - ] - }); + } + ] }); }); - it('should be possible to turn off the attributes for the through table', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.Tag, through: { attributes: [] }, required: true } - ] - }).then(products => { - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - product.Tags.forEach(tag => { - expect(tag.get().productTags).not.to.be.ok; - }); - }); + it('should be possible to turn off the attributes for the through table', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { model: this.models.Tag, through: { attributes: [] }, required: true } + ] + }); + + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + product.Tags.forEach(tag => { + expect(tag.get().productTags).not.to.be.ok; }); }); }); - it('should be possible to select on columns inside a through table', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { - model: this.models.Tag, - through: { - where: { - ProductId: 3 - } - }, - required: true - } - ] - }).then(products => { - expect(products).have.length(1); - }); + it('should be possible to select on columns inside a through table', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { + model: this.models.Tag, + through: { + where: { + ProductId: 3 + } + }, + required: true + } + ] }); + + expect(products).have.length(1); }); - it('should be possible to select on columns inside a through table and a limit', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { - model: this.models.Tag, - through: { - where: { - ProductId: 3 - } - }, - required: true - } - ], - limit: 5 - }).then(products => { - expect(products).have.length(1); - }); + it('should be possible to select on columns inside a through table and a limit', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { + model: this.models.Tag, + through: { + where: { + ProductId: 3 + } + }, + required: true + } + ], + limit: 5 }); + + expect(products).have.length(1); }); // Test case by @eshell - it('should be possible not to include the main id in the attributes', function() { + it('should be possible not to include the main id in the attributes', async function() { const Member = this.sequelize.define('Member', { id: { type: Sequelize.BIGINT, @@ -1525,101 +1457,99 @@ describe(Support.getTestDialectTeaser('Include'), () => { Album.belongsTo(Member); Member.hasMany(Album); - return this.sequelize.sync({ force: true }).then(() => { - const members = [], - albums = [], - memberCount = 20; + await this.sequelize.sync({ force: true }); + const members = [], + albums = [], + memberCount = 20; - for (let i = 1; i <= memberCount; i++) { - members.push({ - id: i, - email: `email${i}@lmu.com`, - password: `testing${i}` - }); - albums.push({ - title: `Album${i}`, - MemberId: i - }); - } - - return Member.bulkCreate(members).then(() => { - return Album.bulkCreate(albums).then(() => { - return Member.findAll({ - attributes: ['email'], - include: [ - { - model: Album - } - ] - }).then(members => { - expect(members.length).to.equal(20); - members.forEach(member => { - expect(member.get('id')).not.to.be.ok; - expect(member.Albums.length).to.equal(1); - }); - }); - }); + for (let i = 1; i <= memberCount; i++) { + members.push({ + id: i, + email: `email${i}@lmu.com`, + password: `testing${i}` + }); + albums.push({ + title: `Album${i}`, + MemberId: i }); + } + + await Member.bulkCreate(members); + await Album.bulkCreate(albums); + + const members0 = await Member.findAll({ + attributes: ['email'], + include: [ + { + model: Album + } + ] + }); + + expect(members0.length).to.equal(20); + members0.forEach(member => { + expect(member.get('id')).not.to.be.ok; + expect(member.Albums.length).to.equal(1); }); }); - it('should be possible to use limit and a where on a hasMany with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag }, - { model: this.models.Price, where: { - value: { [Op.gt]: 5 } - } } - ], - limit: 6, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(6); + it('should be possible to use limit and a where on a hasMany with additional includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag }, + { model: this.models.Price, where: { + value: { [Op.gt]: 5 } + } } + ], + limit: 6, + order: [ + ['id', 'ASC'] + ] + }); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + expect(products.length).to.equal(6); - product.Prices.forEach(price => { - expect(price.value).to.be.above(5); - }); - }); + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Prices.forEach(price => { + expect(price.value).to.be.above(5); }); }); }); - it('should be possible to use limit and a where on a hasMany with a through model with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, - { model: this.models.Price } - ], - limit: 10, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(10); + it('should be possible to use limit and a where on a hasMany with a through model with additional includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, + { model: this.models.Price } + ], + limit: 10, + order: [ + ['id', 'ASC'] + ] + }); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + expect(products.length).to.equal(10); - product.Tags.forEach(tag => { - expect(['A', 'B', 'C']).to.include(tag.name); - }); - }); + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Tags.forEach(tag => { + expect(['A', 'B', 'C']).to.include(tag.name); }); }); }); - it('should support including date fields, with the correct timeszone', function() { + it('should support including date fields, with the correct timeszone', async function() { const User = this.sequelize.define('user', { dateField: Sequelize.DATE }, { timestamps: false }), @@ -1630,54 +1560,48 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsToMany(Group, { through: 'group_user' }); Group.belongsToMany(User, { through: 'group_user' }); - return this.sequelize.sync().then(() => { - return User.create({ dateField: Date.UTC(2014, 1, 20) }).then(user => { - return Group.create({ dateField: Date.UTC(2014, 1, 20) }).then(group => { - return user.addGroup(group).then(() => { - return User.findAll({ - where: { - id: user.id - }, - include: [Group] - }).then(users => { - expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - }); - }); - }); - }); + await this.sequelize.sync(); + const user = await User.create({ dateField: Date.UTC(2014, 1, 20) }); + const group = await Group.create({ dateField: Date.UTC(2014, 1, 20) }); + await user.addGroup(group); + + const users = await User.findAll({ + where: { + id: user.id + }, + include: [Group] }); + + expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); }); - it('should still pull the main record(s) when an included model is not required and has where restrictions without matches', function() { + it('should still pull the main record(s) when an included model is not required and has where restrictions without matches', async function() { const A = this.sequelize.define('a', { name: DataTypes.STRING(40) }), B = this.sequelize.define('b', { name: DataTypes.STRING(40) }); A.belongsToMany(B, { through: 'a_b' }); B.belongsToMany(A, { through: 'a_b' }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return A.create({ - name: 'Foobar' - }); - }) - .then(() => { - return A.findAll({ - where: { name: 'Foobar' }, - include: [ - { model: B, where: { name: 'idontexist' }, required: false } - ] - }); - }) - .then(as => { - expect(as.length).to.equal(1); - expect(as[0].get('bs')).deep.equal([]); - }); + await this.sequelize + .sync({ force: true }); + + await A.create({ + name: 'Foobar' + }); + + const as = await A.findAll({ + where: { name: 'Foobar' }, + include: [ + { model: B, where: { name: 'idontexist' }, required: false } + ] + }); + + expect(as.length).to.equal(1); + expect(as[0].get('bs')).deep.equal([]); }); - it('should work with paranoid, a main record where, an include where, and a limit', function() { + it('should work with paranoid, a main record where, an include where, and a limit', async function() { const Post = this.sequelize.define('post', { date: DataTypes.DATE, 'public': DataTypes.BOOLEAN @@ -1691,38 +1615,38 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.hasMany(Category); Category.belongsTo(Post); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Post.create({ 'public': true }), - Post.create({ 'public': true }), - Post.create({ 'public': true }), - Post.create({ 'public': true }) - ).then(posts => { - return Promise.map(posts.slice(1, 3), post => { - return post.createCategory({ slug: 'food' }); - }); - }).then(() => { - return Post.findAll({ - limit: 2, + await this.sequelize.sync({ force: true }); + + const posts0 = await Promise.all([ + Post.create({ 'public': true }), + Post.create({ 'public': true }), + Post.create({ 'public': true }), + Post.create({ 'public': true }) + ]); + + await Promise.all(posts0.slice(1, 3).map(post => { + return post.createCategory({ slug: 'food' }); + })); + + const posts = await Post.findAll({ + limit: 2, + where: { + 'public': true + }, + include: [ + { + model: Category, where: { - 'public': true - }, - include: [ - { - model: Category, - where: { - slug: 'food' - } - } - ] - }).then(posts => { - expect(posts.length).to.equal(2); - }); - }); + slug: 'food' + } + } + ] }); + + expect(posts.length).to.equal(2); }); - it('should work on a nested set of required 1:1 relations', function() { + it('should work on a nested set of required 1:1 relations', async function() { const Person = this.sequelize.define('Person', { name: { type: Sequelize.STRING, @@ -1782,26 +1706,26 @@ describe(Support.getTestDialectTeaser('Include'), () => { onDelete: 'CASCADE' }); - return this.sequelize.sync({ force: true }).then(() => { - return Person.findAll({ - offset: 0, - limit: 20, - attributes: ['id', 'name'], + await this.sequelize.sync({ force: true }); + + await Person.findAll({ + offset: 0, + limit: 20, + attributes: ['id', 'name'], + include: [{ + model: UserPerson, + required: true, + attributes: ['rank'], include: [{ - model: UserPerson, + model: User, required: true, - attributes: ['rank'], - include: [{ - model: User, - required: true, - attributes: ['login'] - }] + attributes: ['login'] }] - }); + }] }); }); - it('should work with an empty include.where', function() { + it('should work with an empty include.where', async function() { const User = this.sequelize.define('User', {}), Company = this.sequelize.define('Company', {}), Group = this.sequelize.define('Group', {}); @@ -1810,17 +1734,17 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsToMany(Group, { through: 'UsersGroups' }); Group.belongsToMany(User, { through: 'UsersGroups' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: {} }, - { model: Company, where: {} } - ] - }); + await this.sequelize.sync({ force: true }); + + await User.findAll({ + include: [ + { model: Group, where: {} }, + { model: Company, where: {} } + ] }); }); - it('should be able to order on the main table and a required belongsTo relation with custom tablenames and limit ', function() { + it('should be able to order on the main table and a required belongsTo relation with custom tablenames and limit ', async function() { const User = this.sequelize.define('User', { lastName: DataTypes.STRING }, { tableName: 'dem_users' }); @@ -1831,44 +1755,44 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.belongsTo(Company); Company.hasMany(User); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ lastName: 'Albertsen' }), - User.create({ lastName: 'Zenith' }), - User.create({ lastName: 'Hansen' }), - Company.create({ rank: 1 }), - Company.create({ rank: 2 }) - ).then(([albertsen, zenith, hansen, company1, company2]) => { - return Promise.join( - albertsen.setCompany(company1), - zenith.setCompany(company2), - hansen.setCompany(company2) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Company, required: true } - ], - order: [ - [Company, 'rank', 'ASC'], - ['lastName', 'DESC'] - ], - limit: 5 - }).then(users => { - expect(users[0].lastName).to.equal('Albertsen'); - expect(users[0].Company.rank).to.equal(1); - - expect(users[1].lastName).to.equal('Zenith'); - expect(users[1].Company.rank).to.equal(2); - - expect(users[2].lastName).to.equal('Hansen'); - expect(users[2].Company.rank).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const [albertsen, zenith, hansen, company1, company2] = await Promise.all([ + User.create({ lastName: 'Albertsen' }), + User.create({ lastName: 'Zenith' }), + User.create({ lastName: 'Hansen' }), + Company.create({ rank: 1 }), + Company.create({ rank: 2 }) + ]); + + await Promise.all([ + albertsen.setCompany(company1), + zenith.setCompany(company2), + hansen.setCompany(company2) + ]); + + const users = await User.findAll({ + include: [ + { model: Company, required: true } + ], + order: [ + [Company, 'rank', 'ASC'], + ['lastName', 'DESC'] + ], + limit: 5 }); + + expect(users[0].lastName).to.equal('Albertsen'); + expect(users[0].Company.rank).to.equal(1); + + expect(users[1].lastName).to.equal('Zenith'); + expect(users[1].Company.rank).to.equal(2); + + expect(users[2].lastName).to.equal('Hansen'); + expect(users[2].Company.rank).to.equal(2); }); - it('should ignore include with attributes: [] (used for aggregates)', function() { + it('should ignore include with attributes: [] (used for aggregates)', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }), @@ -1878,40 +1802,84 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.Comments = Post.hasMany(Comment, { as: 'comments' }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({ - title: Math.random().toString(), - comments: [ - { content: Math.random().toString() }, - { content: Math.random().toString() }, - { content: Math.random().toString() } - ] - }, { - include: [Post.Comments] - }); - }).then(() => { - return Post.findAll({ - attributes: [ - [this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount'] - ], - include: [ - { association: Post.Comments, attributes: [] } - ], - group: [ - 'Post.id' - ] - }); - }).then(posts => { - expect(posts.length).to.equal(1); + await this.sequelize.sync({ force: true }); - const post = posts[0]; + await Post.create({ + title: Math.random().toString(), + comments: [ + { content: Math.random().toString() }, + { content: Math.random().toString() }, + { content: Math.random().toString() } + ] + }, { + include: [Post.Comments] + }); - expect(post.get('comments')).not.to.be.ok; - expect(parseInt(post.get('commentCount'), 10)).to.equal(3); + const posts = await Post.findAll({ + attributes: [ + [this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount'] + ], + include: [ + { association: Post.Comments, attributes: [] } + ], + group: [ + 'Post.id' + ] }); + + expect(posts.length).to.equal(1); + + const post = posts[0]; + + expect(post.get('comments')).not.to.be.ok; + expect(parseInt(post.get('commentCount'), 10)).to.equal(3); }); - it('should not add primary key when including and aggregating with raw: true', function() { + it('should ignore include with attributes: [] and through: { attributes: [] } (used for aggregates)', async function() { + const User = this.sequelize.define('User', { + name: DataTypes.STRING + }); + const Project = this.sequelize.define('Project', { + title: DataTypes.STRING + }); + + User.belongsToMany(Project, { as: 'projects', through: 'UserProject' }); + Project.belongsToMany(User, { as: 'users', through: 'UserProject' }); + + await this.sequelize.sync({ force: true }); + + await User.create({ + name: Math.random().toString(), + projects: [ + { title: Math.random().toString() }, + { title: Math.random().toString() }, + { title: Math.random().toString() } + ] + }, { + include: [User.associations.projects] + }); + + const users = await User.findAll({ + attributes: [ + [this.sequelize.fn('COUNT', this.sequelize.col('projects.id')), 'projectsCount'] + ], + include: { + association: User.associations.projects, + attributes: [], + through: { attributes: [] } + }, + group: ['User.id'] + }); + + expect(users.length).to.equal(1); + + const user = users[0]; + + expect(user.projects).not.to.be.ok; + expect(parseInt(user.get('projectsCount'), 10)).to.equal(3); + }); + + it('should not add primary key when including and aggregating with raw: true', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }), @@ -1921,39 +1889,38 @@ describe(Support.getTestDialectTeaser('Include'), () => { Post.Comments = Post.hasMany(Comment, { as: 'comments' }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({ - title: Math.random().toString(), - comments: [ - { content: Math.random().toString() }, - { content: Math.random().toString() }, - { content: Math.random().toString() } - ] - }, { - include: [Post.Comments] - }); - }).then(() => { - return Post.findAll({ - attributes: [], - include: [ - { - association: Post.Comments, - attributes: [[this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount']] - } - ], - raw: true - }); - }).then(posts => { - expect(posts.length).to.equal(1); + await this.sequelize.sync({ force: true }); - const post = posts[0]; - expect(post.id).not.to.be.ok; - expect(parseInt(post['comments.commentCount'], 10)).to.equal(3); + await Post.create({ + title: Math.random().toString(), + comments: [ + { content: Math.random().toString() }, + { content: Math.random().toString() }, + { content: Math.random().toString() } + ] + }, { + include: [Post.Comments] }); - }); - it('Should return posts with nested include with inner join with a m:n association', function() { + const posts = await Post.findAll({ + attributes: [], + include: [ + { + association: Post.Comments, + attributes: [[this.sequelize.fn('COUNT', this.sequelize.col('comments.id')), 'commentCount']] + } + ], + raw: true + }); + expect(posts.length).to.equal(1); + + const post = posts[0]; + expect(post.id).not.to.be.ok; + expect(parseInt(post['comments.commentCount'], 10)).to.equal(3); + }); + + it('should return posts with nested include with inner join with a m:n association', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -2010,45 +1977,46 @@ describe(Support.getTestDialectTeaser('Include'), () => { otherKey: 'entity_id' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'bob' })) - .then(() => TaggableSentient.create({ nametag: 'bob' })) - .then(() => Entity.create({ creator: 'bob' })) - .then(entity => Promise.all([ - Post.create({ post_id: entity.entity_id }), - entity.addTags('bob') - ])) - .then(() => Post.findAll({ + await this.sequelize.sync({ force: true }); + await User.create({ username: 'bob' }); + await TaggableSentient.create({ nametag: 'bob' }); + const entity = await Entity.create({ creator: 'bob' }); + + await Promise.all([ + Post.create({ post_id: entity.entity_id }), + entity.addTags('bob') + ]); + + const posts = await Post.findAll({ + include: [{ + model: Entity, + required: true, include: [{ - model: Entity, + model: User, + required: true + }, { + model: TaggableSentient, + as: 'tags', required: true, - include: [{ - model: User, - required: true - }, { - model: TaggableSentient, - as: 'tags', - required: true, - through: { - where: { - tag_name: ['bob'] - } + through: { + where: { + tag_name: ['bob'] } - }] - }], - limit: 5, - offset: 0 - })) - .then(posts => { - expect(posts.length).to.equal(1); - expect(posts[0].Entity.creator).to.equal('bob'); - expect(posts[0].Entity.tags.length).to.equal(1); - expect(posts[0].Entity.tags[0].EntityTag.tag_name).to.equal('bob'); - expect(posts[0].Entity.tags[0].EntityTag.entity_id).to.equal(posts[0].post_id); - }); + } + }] + }], + limit: 5, + offset: 0 + }); + + expect(posts.length).to.equal(1); + expect(posts[0].Entity.creator).to.equal('bob'); + expect(posts[0].Entity.tags.length).to.equal(1); + expect(posts[0].Entity.tags[0].EntityTag.tag_name).to.equal('bob'); + expect(posts[0].Entity.tags[0].EntityTag.entity_id).to.equal(posts[0].post_id); }); - it('should be able to generate a correct request with inner and outer join', function() { + it('should be able to generate a correct request with inner and outer join', async function() { const Customer = this.sequelize.define('customer', { name: DataTypes.STRING }); @@ -2075,45 +2043,91 @@ describe(Support.getTestDialectTeaser('Include'), () => { Shipment.belongsTo(Order); Order.hasOne(Shipment); - return this.sequelize.sync({ force: true }).then(() => { - return Shipment.findOne({ + await this.sequelize.sync({ force: true }); + + await Shipment.findOne({ + include: [{ + model: Order, + required: true, include: [{ - model: Order, - required: true, + model: Customer, include: [{ - model: Customer, - include: [{ - model: ShippingAddress, - where: { verified: true } - }] + model: ShippingAddress, + where: { verified: true } }] }] - }); + }] }); }); - it('should be able to generate a correct request for entity with 1:n and m:1 associations and limit', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.User }, - { model: this.models.Price } - ], - limit: 10 - }).then( products => { - expect(products).to.be.an('array'); - expect(products).to.be.lengthOf(10); - for (const product of products) { - expect(product.title).to.be.a('string'); - // checking that internally added fields used to handle 'BelongsTo' associations are not leaked to result - expect(product.UserId).to.be.equal(undefined); - // checking that included models are on their places - expect(product.User).to.satisfy( User => User === null || User instanceof this.models.User ); - expect(product.Prices).to.be.an('array'); - } - }); + it('should be able to generate a correct request for entity with 1:n and m:1 associations and limit', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { model: this.models.User }, + { model: this.models.Price } + ], + limit: 10 }); + + expect(products).to.be.an('array'); + expect(products).to.be.lengthOf(10); + for (const product of products) { + expect(product.title).to.be.a('string'); + // checking that internally added fields used to handle 'BelongsTo' associations are not leaked to result + expect(product.UserId).to.be.equal(undefined); + // checking that included models are on their places + expect(product.User).to.satisfy( User => User === null || User instanceof this.models.User ); + expect(product.Prices).to.be.an('array'); + } + }); + + it('should allow through model to be paranoid', async function() { + const User = this.sequelize.define('user', { name: DataTypes.STRING }, { timestamps: false }); + const Customer = this.sequelize.define('customer', { name: DataTypes.STRING }, { timestamps: false }); + const UserCustomer = this.sequelize.define( + 'user_customer', + {}, + { paranoid: true, createdAt: false, updatedAt: false } + ); + User.belongsToMany(Customer, { through: UserCustomer }); + + await this.sequelize.sync({ force: true }); + + const [user, customer1, customer2] = await Promise.all([ + User.create({ name: 'User 1' }), + Customer.create({ name: 'Customer 1' }), + Customer.create({ name: 'Customer 2' }) + ]); + await user.setCustomers([customer1]); + await user.setCustomers([customer2]); + + const users = await User.findAll({ include: Customer }); + + expect(users).to.be.an('array'); + expect(users).to.be.lengthOf(1); + const customers = users[0].customers; + + expect(customers).to.be.an('array'); + expect(customers).to.be.lengthOf(1); + + const user_customer = customers[0].user_customer; + + expect(user_customer.deletedAt).not.to.exist; + + const userCustomers = await UserCustomer.findAll({ + paranoid: false + }); + + expect(userCustomers).to.be.an('array'); + expect(userCustomers).to.be.lengthOf(2); + + const [nonDeletedUserCustomers, deletedUserCustomers] = _.partition(userCustomers, userCustomer => !userCustomer.deletedAt); + + expect(nonDeletedUserCustomers).to.be.lengthOf(1); + expect(deletedUserCustomers).to.be.lengthOf(1); }); }); }); diff --git a/test/integration/include/findAndCountAll.test.js b/test/integration/include/findAndCountAll.test.js index 67e92a131bd9..22597038719e 100644 --- a/test/integration/include/findAndCountAll.test.js +++ b/test/integration/include/findAndCountAll.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), sinon = require('sinon'), Support = require('../support'), Op = Support.Sequelize.Op, - DataTypes = require('../../../lib/data-types'), - Promise = require('bluebird'); + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Include'), () => { before(function() { @@ -18,7 +17,7 @@ describe(Support.getTestDialectTeaser('Include'), () => { }); describe('findAndCountAll', () => { - it('should be able to include two required models with a limit. Result rows should match limit.', function() { + it('should be able to include two required models with a limit. Result rows should match limit.', async function() { const Project = this.sequelize.define('Project', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING(40) }), Task = this.sequelize.define('Task', { name: DataTypes.STRING(40), fk: DataTypes.INTEGER }), Employee = this.sequelize.define('Employee', { name: DataTypes.STRING(40), fk: DataTypes.INTEGER }); @@ -30,51 +29,47 @@ describe(Support.getTestDialectTeaser('Include'), () => { Employee.belongsTo(Project, { foreignKey: 'fk', constraints: false }); // Sync them - return this.sequelize.sync({ force: true }).then(() => { - // Create an enviroment - return Promise.join( - Project.bulkCreate([ - { id: 1, name: 'No tasks' }, - { id: 2, name: 'No tasks no employees' }, - { id: 3, name: 'No employees' }, - { id: 4, name: 'In progress A' }, - { id: 5, name: 'In progress B' }, - { id: 6, name: 'In progress C' } - ]), - Task.bulkCreate([ - { name: 'Important task', fk: 3 }, - { name: 'Important task', fk: 4 }, - { name: 'Important task', fk: 5 }, - { name: 'Important task', fk: 6 } - ]), - Employee.bulkCreate([ - { name: 'Jane Doe', fk: 1 }, - { name: 'John Doe', fk: 4 }, - { name: 'Jane John Doe', fk: 5 }, - { name: 'John Jane Doe', fk: 6 } - ]) - ).then(() =>{ - //Find all projects with tasks and employees - const availableProjects = 3; - const limit = 2; - - return Project.findAndCountAll({ - include: [{ - model: Task, required: true - }, - { - model: Employee, required: true - }], - limit - }).then(result => { - expect(result.count).to.be.equal(availableProjects); - expect(result.rows.length).to.be.equal(limit, 'Complete set of available rows were not returned.'); - }); - }); + await this.sequelize.sync({ force: true }); + + // Create an enviroment + await Promise.all([Project.bulkCreate([ + { id: 1, name: 'No tasks' }, + { id: 2, name: 'No tasks no employees' }, + { id: 3, name: 'No employees' }, + { id: 4, name: 'In progress A' }, + { id: 5, name: 'In progress B' }, + { id: 6, name: 'In progress C' } + ]), Task.bulkCreate([ + { name: 'Important task', fk: 3 }, + { name: 'Important task', fk: 4 }, + { name: 'Important task', fk: 5 }, + { name: 'Important task', fk: 6 } + ]), Employee.bulkCreate([ + { name: 'Jane Doe', fk: 1 }, + { name: 'John Doe', fk: 4 }, + { name: 'Jane John Doe', fk: 5 }, + { name: 'John Jane Doe', fk: 6 } + ])]); + + //Find all projects with tasks and employees + const availableProjects = 3; + const limit = 2; + + const result = await Project.findAndCountAll({ + include: [{ + model: Task, required: true + }, + { + model: Employee, required: true + }], + limit }); + + expect(result.count).to.be.equal(availableProjects); + expect(result.rows.length).to.be.equal(limit, 'Complete set of available rows were not returned.'); }); - it('should be able to include a required model. Result rows should match count', function() { + it('should be able to include a required model. Result rows should match count', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING(40) }, { paranoid: true }), SomeConnection = this.sequelize.define('SomeConnection', { m: DataTypes.STRING(40), @@ -98,85 +93,80 @@ describe(Support.getTestDialectTeaser('Include'), () => { C.hasMany(SomeConnection, { foreignKey: 'fk', constraints: false }); // Sync them - return this.sequelize.sync({ force: true }).then(() => { - // Create an enviroment - - return Promise.join( - User.bulkCreate([ - { name: 'Youtube' }, - { name: 'Facebook' }, - { name: 'Google' }, - { name: 'Yahoo' }, - { name: '404' } - ]), - SomeConnection.bulkCreate([ // Lets count, m: A and u: 1 - { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted - { u: 2, m: 'A', fk: 1 }, - { u: 3, m: 'A', fk: 1 }, - { u: 4, m: 'A', fk: 1 }, - { u: 5, m: 'A', fk: 1 }, - { u: 1, m: 'B', fk: 1 }, - { u: 2, m: 'B', fk: 1 }, - { u: 3, m: 'B', fk: 1 }, - { u: 4, m: 'B', fk: 1 }, - { u: 5, m: 'B', fk: 1 }, - { u: 1, m: 'C', fk: 1 }, - { u: 2, m: 'C', fk: 1 }, - { u: 3, m: 'C', fk: 1 }, - { u: 4, m: 'C', fk: 1 }, - { u: 5, m: 'C', fk: 1 }, - { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted - { u: 4, m: 'A', fk: 2 }, - { u: 2, m: 'A', fk: 2 }, - { u: 1, m: 'A', fk: 3 }, // 3 - { u: 2, m: 'A', fk: 3 }, - { u: 3, m: 'A', fk: 3 }, - { u: 2, m: 'B', fk: 2 }, - { u: 1, m: 'A', fk: 4 }, // 4 - { u: 4, m: 'A', fk: 2 } - ]), - A.bulkCreate([ - { name: 'Just' }, - { name: 'for' }, - { name: 'testing' }, - { name: 'proposes' }, - { name: 'only' } - ]), - B.bulkCreate([ - { name: 'this should not' }, - { name: 'be loaded' } - ]), - C.bulkCreate([ - { name: 'because we only want A' } - ]) - ).then(() => { - // Delete some of conns to prove the concept - return SomeConnection.destroy({ where: { - m: 'A', - u: 1, - fk: [1, 2] - } }).then(() => { - this.clock.tick(1000); - // Last and most important queries ( we connected 4, but deleted 2, witch means we must get 2 only ) - return A.findAndCountAll({ - include: [{ - model: SomeConnection, required: true, - where: { - m: 'A', // Pseudo Polymorphy - u: 1 - } - }], - limit: 5 - }).then(result => { - expect(result.count).to.be.equal(2); - expect(result.rows.length).to.be.equal(2); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + // Create an enviroment + + await Promise.all([User.bulkCreate([ + { name: 'Youtube' }, + { name: 'Facebook' }, + { name: 'Google' }, + { name: 'Yahoo' }, + { name: '404' } + ]), SomeConnection.bulkCreate([ // Lets count, m: A and u: 1 + { u: 1, m: 'A', fk: 1 }, // 1 // Will be deleted + { u: 2, m: 'A', fk: 1 }, + { u: 3, m: 'A', fk: 1 }, + { u: 4, m: 'A', fk: 1 }, + { u: 5, m: 'A', fk: 1 }, + { u: 1, m: 'B', fk: 1 }, + { u: 2, m: 'B', fk: 1 }, + { u: 3, m: 'B', fk: 1 }, + { u: 4, m: 'B', fk: 1 }, + { u: 5, m: 'B', fk: 1 }, + { u: 1, m: 'C', fk: 1 }, + { u: 2, m: 'C', fk: 1 }, + { u: 3, m: 'C', fk: 1 }, + { u: 4, m: 'C', fk: 1 }, + { u: 5, m: 'C', fk: 1 }, + { u: 1, m: 'A', fk: 2 }, // 2 // Will be deleted + { u: 4, m: 'A', fk: 2 }, + { u: 2, m: 'A', fk: 2 }, + { u: 1, m: 'A', fk: 3 }, // 3 + { u: 2, m: 'A', fk: 3 }, + { u: 3, m: 'A', fk: 3 }, + { u: 2, m: 'B', fk: 2 }, + { u: 1, m: 'A', fk: 4 }, // 4 + { u: 4, m: 'A', fk: 2 } + ]), A.bulkCreate([ + { name: 'Just' }, + { name: 'for' }, + { name: 'testing' }, + { name: 'proposes' }, + { name: 'only' } + ]), B.bulkCreate([ + { name: 'this should not' }, + { name: 'be loaded' } + ]), C.bulkCreate([ + { name: 'because we only want A' } + ])]); + + // Delete some of conns to prove the concept + await SomeConnection.destroy({ where: { + m: 'A', + u: 1, + fk: [1, 2] + } }); + + this.clock.tick(1000); + + // Last and most important queries ( we connected 4, but deleted 2, witch means we must get 2 only ) + const result = await A.findAndCountAll({ + include: [{ + model: SomeConnection, required: true, + where: { + m: 'A', // Pseudo Polymorphy + u: 1 + } + }], + limit: 5 }); + + expect(result.count).to.be.equal(2); + expect(result.rows.length).to.be.equal(2); }); - it('should count on a where and not use an uneeded include', function() { + it('should count on a where and not use an uneeded include', async function() { const Project = this.sequelize.define('Project', { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, project_name: { type: DataTypes.STRING } @@ -191,28 +181,25 @@ describe(Support.getTestDialectTeaser('Include'), () => { let userId = null; - return User.sync({ force: true }).then(() => { - return Project.sync({ force: true }); - }).then(() => { - return Promise.all([User.create(), Project.create(), Project.create(), Project.create()]); - }).then(results => { - const user = results[0]; - userId = user.id; - return user.setProjects([results[1], results[2], results[3]]); - }).then(() => { - return User.findAndCountAll({ - where: { id: userId }, - include: [Project], - distinct: true - }); - }).then(result => { - expect(result.rows.length).to.equal(1); - expect(result.rows[0].Projects.length).to.equal(3); - expect(result.count).to.equal(1); + await User.sync({ force: true }); + await Project.sync({ force: true }); + const results = await Promise.all([User.create(), Project.create(), Project.create(), Project.create()]); + const user = results[0]; + userId = user.id; + await user.setProjects([results[1], results[2], results[3]]); + + const result = await User.findAndCountAll({ + where: { id: userId }, + include: [Project], + distinct: true }); + + expect(result.rows.length).to.equal(1); + expect(result.rows[0].Projects.length).to.equal(3); + expect(result.count).to.equal(1); }); - it('should return the correct count and rows when using a required belongsTo and a limit', function() { + it('should return the correct count and rows when using a required belongsTo and a limit', async function() { const s = this.sequelize, Foo = s.define('Foo', {}), Bar = s.define('Bar', {}); @@ -220,63 +207,60 @@ describe(Support.getTestDialectTeaser('Include'), () => { Foo.hasMany(Bar); Bar.belongsTo(Foo); - return s.sync({ force: true }).then(() => { - // Make five instances of Foo - return Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); - }).then(() => { - // Make four instances of Bar, related to the last four instances of Foo - return Bar.bulkCreate([{ 'FooId': 2 }, { 'FooId': 3 }, { 'FooId': 4 }, { 'FooId': 5 }]); - }).then(() => { - // Query for the first two instances of Foo which have related Bars - return Foo.findAndCountAll({ - include: [{ model: Bar, required: true }], - limit: 2 - }).tap(() => { - return Foo.findAll({ - include: [{ model: Bar, required: true }], - limit: 2 - }).then(items => { - expect(items.length).to.equal(2); - }); - }); - }).then(result => { - expect(result.count).to.equal(4); - - // The first two of those should be returned due to the limit (Foo - // instances 2 and 3) - expect(result.rows.length).to.equal(2); + await s.sync({ force: true }); + // Make five instances of Foo + await Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); + // Make four instances of Bar, related to the last four instances of Foo + await Bar.bulkCreate([{ 'FooId': 2 }, { 'FooId': 3 }, { 'FooId': 4 }, { 'FooId': 5 }]); + + // Query for the first two instances of Foo which have related Bars + const result0 = await Foo.findAndCountAll({ + include: [{ model: Bar, required: true }], + limit: 2 }); + + const items = await Foo.findAll({ + include: [{ model: Bar, required: true }], + limit: 2 + }); + + expect(items.length).to.equal(2); + + const result = result0; + expect(result.count).to.equal(4); + + // The first two of those should be returned due to the limit (Foo + // instances 2 and 3) + expect(result.rows.length).to.equal(2); }); - it('should return the correct count and rows when using a required belongsTo with a where condition and a limit', function() { + it('should return the correct count and rows when using a required belongsTo with a where condition and a limit', async function() { const Foo = this.sequelize.define('Foo', {}), Bar = this.sequelize.define('Bar', { m: DataTypes.STRING(40) }); Foo.hasMany(Bar); Bar.belongsTo(Foo); - return this.sequelize.sync({ force: true }).then(() => { - return Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); - }).then(() => { - // Make four instances of Bar, related to the first two instances of Foo - return Bar.bulkCreate([{ 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'no' }, { 'FooId': 2, m: 'yes' }]); - }).then(() => { - // Query for the first instance of Foo which have related Bars with m === 'yes' - return Foo.findAndCountAll({ - include: [{ model: Bar, where: { m: 'yes' } }], - limit: 1, - distinct: true - }); - }).then(result => { - // There should be 2 instances matching the query (Instances 1 and 2), see the findAll statement - expect(result.count).to.equal(2); - - // The first one of those should be returned due to the limit (Foo instance 1) - expect(result.rows.length).to.equal(1); + await this.sequelize.sync({ force: true }); + await Foo.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]); + // Make four instances of Bar, related to the first two instances of Foo + await Bar.bulkCreate([{ 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'yes' }, { 'FooId': 1, m: 'no' }, { 'FooId': 2, m: 'yes' }]); + + // Query for the first instance of Foo which have related Bars with m === 'yes' + const result = await Foo.findAndCountAll({ + include: [{ model: Bar, where: { m: 'yes' } }], + limit: 1, + distinct: true }); + + // There should be 2 instances matching the query (Instances 1 and 2), see the findAll statement + expect(result.count).to.equal(2); + + // The first one of those should be returned due to the limit (Foo instance 1) + expect(result.rows.length).to.equal(1); }); - it('should correctly filter, limit and sort when multiple includes and types of associations are present.', function() { + it('should correctly filter, limit and sort when multiple includes and types of associations are present.', async function() { const TaskTag = this.sequelize.define('TaskTag', { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING } @@ -304,52 +288,52 @@ describe(Support.getTestDialectTeaser('Include'), () => { Project.belongsTo(User); Task.belongsTo(Project); Task.belongsToMany(Tag, { through: TaskTag }); + // Sync them - return this.sequelize.sync({ force: true }).then(() => { - // Create an enviroment - return User.bulkCreate([ - { name: 'user-name-1' }, - { name: 'user-name-2' } - ]).then(() => { - return Project.bulkCreate([ - { m: 'A', UserId: 1 }, - { m: 'A', UserId: 2 } - ]); - }).then(() => { - return Task.bulkCreate([ - { ProjectId: 1, name: 'Just' }, - { ProjectId: 1, name: 'for' }, - { ProjectId: 2, name: 'testing' }, - { ProjectId: 2, name: 'proposes' } - ]); - }) - .then(() => { - // Find All Tasks with Project(m=a) and User(name=user-name-2) - return Task.findAndCountAll({ - limit: 1, - offset: 0, - order: [['id', 'DESC']], - include: [ - { - model: Project, - where: { [Op.and]: [{ m: 'A' }] }, - include: [{ - model: User, - where: { [Op.and]: [{ name: 'user-name-2' }] } - } - ] - }, - { model: Tag } - ] - }); - }); - }).then(result => { - expect(result.count).to.equal(2); - expect(result.rows.length).to.equal(1); + await this.sequelize.sync({ force: true }); + + // Create an enviroment + await User.bulkCreate([ + { name: 'user-name-1' }, + { name: 'user-name-2' } + ]); + + await Project.bulkCreate([ + { m: 'A', UserId: 1 }, + { m: 'A', UserId: 2 } + ]); + + await Task.bulkCreate([ + { ProjectId: 1, name: 'Just' }, + { ProjectId: 1, name: 'for' }, + { ProjectId: 2, name: 'testing' }, + { ProjectId: 2, name: 'proposes' } + ]); + + // Find All Tasks with Project(m=a) and User(name=user-name-2) + const result = await Task.findAndCountAll({ + limit: 1, + offset: 0, + order: [['id', 'DESC']], + include: [ + { + model: Project, + where: { [Op.and]: [{ m: 'A' }] }, + include: [{ + model: User, + where: { [Op.and]: [{ name: 'user-name-2' }] } + } + ] + }, + { model: Tag } + ] }); + + expect(result.count).to.equal(2); + expect(result.rows.length).to.equal(1); }); - it('should properly work with sequelize.function', function() { + it('should properly work with sequelize.function', async function() { const sequelize = this.sequelize; const User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true }, @@ -364,41 +348,40 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Project); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { first_name: 'user-fname-1', last_name: 'user-lname-1' }, - { first_name: 'user-fname-2', last_name: 'user-lname-2' }, - { first_name: 'user-xfname-1', last_name: 'user-xlname-1' } - ]); - }).then(() => { - return Project.bulkCreate([ - { name: 'naam-satya', UserId: 1 }, - { name: 'guru-satya', UserId: 2 }, - { name: 'app-satya', UserId: 2 } - ]); - }).then(() => { - return User.findAndCountAll({ - limit: 1, - offset: 1, - where: sequelize.or( - { first_name: { [Op.like]: '%user-fname%' } }, - { last_name: { [Op.like]: '%user-lname%' } } - ), - include: [ - { - model: Project, - required: true, - where: { name: { - [Op.in]: ['naam-satya', 'guru-satya'] - } } - } - ] - }); - }).then(result => { - expect(result.count).to.equal(2); - expect(result.rows.length).to.equal(1); + await this.sequelize.sync({ force: true }); + + await User.bulkCreate([ + { first_name: 'user-fname-1', last_name: 'user-lname-1' }, + { first_name: 'user-fname-2', last_name: 'user-lname-2' }, + { first_name: 'user-xfname-1', last_name: 'user-xlname-1' } + ]); + + await Project.bulkCreate([ + { name: 'naam-satya', UserId: 1 }, + { name: 'guru-satya', UserId: 2 }, + { name: 'app-satya', UserId: 2 } + ]); + + const result = await User.findAndCountAll({ + limit: 1, + offset: 1, + where: sequelize.or( + { first_name: { [Op.like]: '%user-fname%' } }, + { last_name: { [Op.like]: '%user-lname%' } } + ), + include: [ + { + model: Project, + required: true, + where: { name: { + [Op.in]: ['naam-satya', 'guru-satya'] + } } + } + ] }); + expect(result.count).to.equal(2); + expect(result.rows.length).to.equal(1); }); }); }); diff --git a/test/integration/include/findOne.test.js b/test/integration/include/findOne.test.js index 88793a37758c..7ea0dcb18cdd 100644 --- a/test/integration/include/findOne.test.js +++ b/test/integration/include/findOne.test.js @@ -4,16 +4,15 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, DataTypes = require('../../../lib/data-types'), _ = require('lodash'); describe(Support.getTestDialectTeaser('Include'), () => { describe('findOne', () => { - it('should include a non required model, with conditions and two includes N:M 1:M', function( ) { - const A = this.sequelize.define('A', { name: DataTypes.STRING(40) }, { paranoid: true }), - B = this.sequelize.define('B', { name: DataTypes.STRING(40) }, { paranoid: true }), - C = this.sequelize.define('C', { name: DataTypes.STRING(40) }, { paranoid: true }), + it('should include a non required model, with conditions and two includes N:M 1:M', async function() { + const A = this.sequelize.define('A', { name: DataTypes.STRING(40) }, { paranoid: true }), + B = this.sequelize.define('B', { name: DataTypes.STRING(40) }, { paranoid: true }), + C = this.sequelize.define('C', { name: DataTypes.STRING(40) }, { paranoid: true }), D = this.sequelize.define('D', { name: DataTypes.STRING(40) }, { paranoid: true }); // Associations @@ -30,19 +29,19 @@ describe(Support.getTestDialectTeaser('Include'), () => { D.hasMany(B); - return this.sequelize.sync({ force: true }).then(() => { - return A.findOne({ - include: [ - { model: B, required: false, include: [ - { model: C, required: false }, - { model: D } - ] } - ] - }); + await this.sequelize.sync({ force: true }); + + await A.findOne({ + include: [ + { model: B, required: false, include: [ + { model: C, required: false }, + { model: D } + ] } + ] }); }); - it('should work with a 1:M to M:1 relation with a where on the last include', function() { + it('should work with a 1:M to M:1 relation with a where on the last include', async function() { const Model = this.sequelize.define('Model', {}); const Model2 = this.sequelize.define('Model2', {}); const Model4 = this.sequelize.define('Model4', { something: { type: DataTypes.INTEGER } }); @@ -53,18 +52,18 @@ describe(Support.getTestDialectTeaser('Include'), () => { Model2.hasMany(Model4); Model4.belongsTo(Model2); - return this.sequelize.sync({ force: true }).then(() => { - return Model.findOne({ - include: [ - { model: Model2, include: [ - { model: Model4, where: { something: 2 } } - ] } - ] - }); + await this.sequelize.sync({ force: true }); + + await Model.findOne({ + include: [ + { model: Model2, include: [ + { model: Model4, where: { something: 2 } } + ] } + ] }); }); - it('should include a model with a where condition but no required', function() { + it('should include a model with a where condition but no required', async function() { const User = this.sequelize.define('User', {}, { paranoid: false }), Task = this.sequelize.define('Task', { deletedAt: { @@ -75,29 +74,29 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Task, { foreignKey: 'userId' }); Task.belongsTo(User, { foreignKey: 'userId' }); - return this.sequelize.sync({ + await this.sequelize.sync({ force: true - }).then(() => { - return User.create(); - }).then(user => { - return Task.bulkCreate([ - { userId: user.get('id'), deletedAt: new Date() }, - { userId: user.get('id'), deletedAt: new Date() }, - { userId: user.get('id'), deletedAt: new Date() } - ]); - }).then(() => { - return User.findOne({ - include: [ - { model: Task, where: { deletedAt: null }, required: false } - ] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.Tasks.length).to.equal(0); }); + + const user0 = await User.create(); + + await Task.bulkCreate([ + { userId: user0.get('id'), deletedAt: new Date() }, + { userId: user0.get('id'), deletedAt: new Date() }, + { userId: user0.get('id'), deletedAt: new Date() } + ]); + + const user = await User.findOne({ + include: [ + { model: Task, where: { deletedAt: null }, required: false } + ] + }); + + expect(user).to.be.ok; + expect(user.Tasks.length).to.equal(0); }); - it('should include a model with a where clause when the PK field name and attribute name are different', function() { + it('should include a model with a where clause when the PK field name and attribute name are different', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -113,28 +112,28 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Task, { foreignKey: 'userId' }); Task.belongsTo(User, { foreignKey: 'userId' }); - return this.sequelize.sync({ + await this.sequelize.sync({ force: true - }).then(() => { - return User.create(); - }).then(user => { - return Task.bulkCreate([ - { userId: user.get('id'), searchString: 'one' }, - { userId: user.get('id'), searchString: 'two' } - ]); - }).then(() => { - return User.findOne({ - include: [ - { model: Task, where: { searchString: 'one' } } - ] - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.Tasks.length).to.equal(1); }); + + const user0 = await User.create(); + + await Task.bulkCreate([ + { userId: user0.get('id'), searchString: 'one' }, + { userId: user0.get('id'), searchString: 'two' } + ]); + + const user = await User.findOne({ + include: [ + { model: Task, where: { searchString: 'one' } } + ] + }); + + expect(user).to.be.ok; + expect(user.Tasks.length).to.equal(1); }); - it('should include a model with a through.where and required true clause when the PK field name and attribute name are different', function() { + it('should include a model with a through.where and required true clause when the PK field name and attribute name are different', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), AB = this.sequelize.define('a_b', { @@ -148,32 +147,24 @@ describe(Support.getTestDialectTeaser('Include'), () => { A.belongsToMany(B, { through: AB }); B.belongsToMany(A, { through: AB }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return Promise.join( - A.create({}), - B.create({}) - ); - }) - .then(([a, b]) => { - return a.addB(b, { through: { name: 'Foobar' } }); - }) - .then(() => { - return A.findOne({ - include: [ - { model: B, through: { where: { name: 'Foobar' } }, required: true } - ] - }); - }) - .then(a => { - expect(a).to.not.equal(null); - expect(a.get('bs')).to.have.length(1); - }); + await this.sequelize + .sync({ force: true }); + + const [a0, b] = await Promise.all([A.create({}), B.create({})]); + await a0.addB(b, { through: { name: 'Foobar' } }); + + const a = await A.findOne({ + include: [ + { model: B, through: { where: { name: 'Foobar' } }, required: true } + ] + }); + + expect(a).to.not.equal(null); + expect(a.get('bs')).to.have.length(1); }); - it('should still pull the main record when an included model is not required and has where restrictions without matches', function() { + it('should still pull the main record when an included model is not required and has where restrictions without matches', async function() { const A = this.sequelize.define('a', { name: DataTypes.STRING(40) }), @@ -184,28 +175,25 @@ describe(Support.getTestDialectTeaser('Include'), () => { A.belongsToMany(B, { through: 'a_b' }); B.belongsToMany(A, { through: 'a_b' }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return A.create({ - name: 'Foobar' - }); - }) - .then(() => { - return A.findOne({ - where: { name: 'Foobar' }, - include: [ - { model: B, where: { name: 'idontexist' }, required: false } - ] - }); - }) - .then(a => { - expect(a).to.not.equal(null); - expect(a.get('bs')).to.deep.equal([]); - }); + await this.sequelize + .sync({ force: true }); + + await A.create({ + name: 'Foobar' + }); + + const a = await A.findOne({ + where: { name: 'Foobar' }, + include: [ + { model: B, where: { name: 'idontexist' }, required: false } + ] + }); + + expect(a).to.not.equal(null); + expect(a.get('bs')).to.deep.equal([]); }); - it('should support a nested include (with a where)', function() { + it('should support a nested include (with a where)', async function() { const A = this.sequelize.define('A', { name: DataTypes.STRING }); @@ -224,53 +212,47 @@ describe(Support.getTestDialectTeaser('Include'), () => { B.hasMany(C); C.belongsTo(B); - return this.sequelize - .sync({ force: true }) - .then(() => { - return A.findOne({ + await this.sequelize + .sync({ force: true }); + + const a = await A.findOne({ + include: [ + { + model: B, + where: { flag: true }, include: [ { - model: B, - where: { flag: true }, - include: [ - { - model: C - } - ] + model: C } ] - }); - }) - .then(a => { - expect(a).to.not.exist; - }); + } + ] + }); + + expect(a).to.not.exist; }); - it('should support a belongsTo with the targetKey option', function() { + it('should support a belongsTo with the targetKey option', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, unique: true } }), Task = this.sequelize.define('Task', { title: DataTypes.STRING }); User.removeAttribute('id'); Task.belongsTo(User, { foreignKey: 'user_name', targetKey: 'username' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'bob' }).then(newUser => { - return Task.create({ title: 'some task' }).then(newTask => { - return newTask.setUser(newUser).then(() => { - return Task.findOne({ - where: { title: 'some task' }, - include: [{ model: User }] - }) - .then(foundTask => { - expect(foundTask).to.be.ok; - expect(foundTask.User.username).to.equal('bob'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const newUser = await User.create({ username: 'bob' }); + const newTask = await Task.create({ title: 'some task' }); + await newTask.setUser(newUser); + + const foundTask = await Task.findOne({ + where: { title: 'some task' }, + include: [{ model: User }] }); + + expect(foundTask).to.be.ok; + expect(foundTask.User.username).to.equal('bob'); }); - it('should support many levels of belongsTo (with a lower level having a where)', function() { + it('should support many levels of belongsTo (with a lower level having a where)', async function() { const A = this.sequelize.define('a', {}), B = this.sequelize.define('b', {}), C = this.sequelize.define('c', {}), @@ -292,68 +274,65 @@ describe(Support.getTestDialectTeaser('Include'), () => { F.belongsTo(G); G.belongsTo(H); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - A.create({}), - (function(singles) { - let promise = Promise.resolve(), - previousInstance, - b; + await this.sequelize.sync({ force: true }); - singles.forEach(model => { - const values = {}; + const [a0, b] = await Promise.all([A.create({}), (function(singles) { + let promise = Promise.resolve(), + previousInstance, + b; - if (model.name === 'g') { - values.name = 'yolo'; - } + singles.forEach(model => { + const values = {}; - promise = promise.then(() => { - return model.create(values).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - }); - }); - }); - - promise = promise.then(() => { - return b; - }); - - return promise; - })([B, C, D, E, F, G, H]) - ).then(([a, b]) => { - return a.setB(b); - }).then(() => { - return A.findOne({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, where: { - name: 'yolo' - }, include: [ - { model: H } - ] } - ] } + if (model.name === 'g') { + values.name = 'yolo'; + } + + promise = (async () => { + await promise; + const instance = await model.create(values); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + return; + } + previousInstance = b = instance; + })(); + }); + + promise = promise.then(() => { + return b; + }); + + return promise; + })([B, C, D, E, F, G, H])]); + + await a0.setB(b); + + const a = await A.findOne({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, where: { + name: 'yolo' + }, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); + ] } + ] } + ] }); + + expect(a.b.c.d.e.f.g.h).to.be.ok; }); - it('should work with combinding a where and a scope', function() { + it('should work with combinding a where and a scope', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: DataTypes.STRING @@ -369,13 +348,13 @@ describe(Support.getTestDialectTeaser('Include'), () => { User.hasMany(Post, { foreignKey: 'owner_id', scope: { owner_type: 'user' }, as: 'UserPosts', constraints: false }); Post.belongsTo(User, { foreignKey: 'owner_id', as: 'Owner', constraints: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.findOne({ - where: { id: 2 }, - include: [ - { model: Post, as: 'UserPosts', where: { 'private': true } } - ] - }); + await this.sequelize.sync({ force: true }); + + await User.findOne({ + where: { id: 2 }, + include: [ + { model: Post, as: 'UserPosts', where: { 'private': true } } + ] }); }); }); diff --git a/test/integration/include/limit.test.js b/test/integration/include/limit.test.js index d8263ac2dfaf..9ff45e10009a 100644 --- a/test/integration/include/limit.test.js +++ b/test/integration/include/limit.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - Promise = Sequelize.Promise, Op = Sequelize.Op; describe(Support.getTestDialectTeaser('Include'), () => { @@ -126,599 +125,621 @@ describe(Support.getTestDialectTeaser('Include'), () => { /* * many-to-many */ - it('supports many-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')) - )) - .then(([projects, users]) => Promise.join( - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]) - )) - .then(() => this.Project.findAll({ + it('supports many-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + where: { + name: 'Alice' + } + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); + }); + + it('supports 2 levels of required many-to-many associations', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, hobbies] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, - where: { - name: 'Alice' - } - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + model: this.Hobby, + required: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 levels of required many-to-many associations', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([projects, users, hobbies]) => Promise.join( - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]) - )) - .then(() => this.Project.findAll({ + it('supports 2 levels of required many-to-many associations with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, hobbies] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, - required: true, - include: [{ - model: this.Hobby, - required: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + model: this.Hobby, + where: { + name: 'archery' + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 levels of required many-to-many associations with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([projects, users, hobbies]) => Promise.join( - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]) - )) - .then(() => this.Project.findAll({ + it('supports 2 levels of required many-to-many associations with through.where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, hobbies] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, + model: this.Hobby, required: true, - include: [{ - model: this.Hobby, + through: { where: { - name: 'archery' + HobbyName: 'archery' } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 levels of required many-to-many associations with through.where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([projects, users, hobbies]) => Promise.join( - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]) - )) - .then(() => this.Project.findAll({ + it('supports 3 levels of required many-to-many associations with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [tasks, projects, users, hobbies] = await Promise.all([ + this.Task.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + tasks[0].addProject(projects[0]), + tasks[1].addProject(projects[1]), + tasks[2].addProject(projects[2]), + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[2].addUser(users[0]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]) + ]); + + const result = await this.Task.findAll({ + include: [{ + model: this.Project, + required: true, include: [{ model: this.User, required: true, include: [{ model: this.Hobby, - required: true, - through: { - where: { - HobbyName: 'archery' - } + where: { + name: 'archery' } }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); - }); - - it('supports 3 levels of required many-to-many associations with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Task.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([tasks, projects, users, hobbies]) => Promise.join( - tasks[0].addProject(projects[0]), - tasks[1].addProject(projects[1]), - tasks[2].addProject(projects[2]), - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[2].addUser(users[0]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]) - )) - .then(() => this.Task.findAll({ - include: [{ - model: this.Project, - required: true, - include: [{ - model: this.User, - required: true, - include: [{ - model: this.Hobby, - where: { - name: 'archery' - } - }] - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports required many-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.User.bulkCreate(build('Alice', 'Bob')) - )) - .then(([projects, users]) => Promise.join( - projects[0].addUser(users[0]), // alpha - projects[2].addUser(users[0]) // charlie - )) - .then(() => this.Project.findAll({ - include: [{ - model: this.User, - required: true - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + it('supports required many-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.User.bulkCreate(build('Alice', 'Bob')) + ]); + + await Promise.all([// alpha + projects[0].addUser(users[0]), // charlie + projects[2].addUser(users[0])]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports 2 required many-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), - this.User.bulkCreate(build('Alice', 'Bob', 'David')), - this.Task.bulkCreate(build('a', 'c', 'd')) - )) - .then(([projects, users, tasks]) => Promise.join( - projects[0].addUser(users[0]), - projects[0].addTask(tasks[0]), - projects[1].addUser(users[1]), - projects[2].addTask(tasks[1]), - projects[3].addUser(users[2]), - projects[3].addTask(tasks[2]) - )) - .then(() => this.Project.findAll({ - include: [{ - model: this.User, - required: true - }, { - model: this.Task, - required: true - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('delta'); - }); + it('supports 2 required many-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, tasks] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), + this.User.bulkCreate(build('Alice', 'Bob', 'David')), + this.Task.bulkCreate(build('a', 'c', 'd')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[0].addTask(tasks[0]), + projects[1].addUser(users[1]), + projects[2].addTask(tasks[1]), + projects[3].addUser(users[2]), + projects[3].addTask(tasks[2]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true + }, { + model: this.Task, + required: true + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('delta'); }); /* * one-to-many */ - it('supports required one-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Comment.bulkCreate(build('comment0', 'comment1')) - )) - .then(([posts, comments]) => Promise.join( - posts[0].addComment(comments[0]), - posts[2].addComment(comments[1]) - )) - .then(() => this.Post.findAll({ - include: [{ - model: this.Comment, - required: true - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + it('supports required one-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, comments] = await Promise.all([ + this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Comment.bulkCreate(build('comment0', 'comment1')) + ]); + + await Promise.all([posts[0].addComment(comments[0]), posts[2].addComment(comments[1])]); + + const result = await this.Post.findAll({ + include: [{ + model: this.Comment, + required: true + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports required one-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - )) - .then(([posts, comments]) => Promise.join( - posts[0].addComment(comments[0]), - posts[1].addComment(comments[1]), - posts[2].addComment(comments[2]) - )) - .then(() => this.Post.findAll({ - include: [{ - model: this.Comment, - required: true, - where: { - [Op.or]: [{ - name: 'comment0' - }, { - name: 'comment2' - }] - } - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('charlie'); - }); + it('supports required one-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, comments] = await Promise.all([ + this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) + ]); + + await Promise.all([ + posts[0].addComment(comments[0]), + posts[1].addComment(comments[1]), + posts[2].addComment(comments[2]) + ]); + + const result = await this.Post.findAll({ + include: [{ + model: this.Comment, + required: true, + where: { + [Op.or]: [{ + name: 'comment0' + }, { + name: 'comment2' + }] + } + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('charlie'); }); - it('supports required one-to-many association with where clause (findOne)', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - )) - .then(([posts, comments]) => Promise.join( - posts[0].addComment(comments[0]), - posts[1].addComment(comments[1]), - posts[2].addComment(comments[2]) - )) - .then(() => this.Post.findOne({ - include: [{ - model: this.Comment, - required: true, - where: { - name: 'comment2' - } - }] - })) - .then(post => { - expect(post.name).to.equal('charlie'); - }); + it('supports required one-to-many association with where clause (findOne)', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, comments] = await Promise.all([ + this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')), + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) + ]); + + await Promise.all([ + posts[0].addComment(comments[0]), + posts[1].addComment(comments[1]), + posts[2].addComment(comments[2]) + ]); + + const post = await this.Post.findOne({ + include: [{ + model: this.Comment, + required: true, + where: { + name: 'comment2' + } + }] + }); + + expect(post.name).to.equal('charlie'); }); - it('supports 2 levels of required one-to-many associations', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Post.bulkCreate(build('post0', 'post1', 'post2')), - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) - )) - .then(([users, posts, comments]) => Promise.join( - users[0].addPost(posts[0]), - users[1].addPost(posts[1]), - users[3].addPost(posts[2]), - posts[0].addComment(comments[0]), - posts[2].addComment(comments[2]) - )) - .then(() => this.User.findAll({ + it('supports 2 levels of required one-to-many associations', async function() { + await this.sequelize.sync({ force: true }); + + const [users, posts, comments] = await Promise.all([ + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Post.bulkCreate(build('post0', 'post1', 'post2')), + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')) + ]); + + await Promise.all([ + users[0].addPost(posts[0]), + users[1].addPost(posts[1]), + users[3].addPost(posts[2]), + posts[0].addComment(comments[0]), + posts[2].addComment(comments[2]) + ]); + + const result = await this.User.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.Comment, - required: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('David'); - }); + model: this.Comment, + required: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('David'); }); /* * mixed many-to-many, one-to-many and many-to-one */ - it('supports required one-to-many association with nested required many-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Post.bulkCreate(build('alpha', 'charlie', 'delta')), - this.Tag.bulkCreate(build('atag', 'btag', 'dtag')) - )) - .then(([users, posts, tags]) => Promise.join( - users[0].addPost(posts[0]), - users[2].addPost(posts[1]), - users[3].addPost(posts[2]), - - posts[0].addTag([tags[0]]), - posts[2].addTag([tags[2]]) - )) - .then(() => this.User.findAll({ + it('supports required one-to-many association with nested required many-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [users, posts, tags] = await Promise.all([ + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Post.bulkCreate(build('alpha', 'charlie', 'delta')), + this.Tag.bulkCreate(build('atag', 'btag', 'dtag')) + ]); + + await Promise.all([ + users[0].addPost(posts[0]), + users[2].addPost(posts[1]), + users[3].addPost(posts[2]), + posts[0].addTag([tags[0]]), + posts[2].addTag([tags[2]]) + ]); + + const result = await this.User.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.Tag, - required: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('David'); - }); + model: this.Tag, + required: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('David'); }); - it('supports required many-to-many association with nested required one-to-many association', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), - this.User.bulkCreate(build('Alice', 'Bob', 'David')), - this.Post.bulkCreate(build('post0', 'post1', 'post2')) - )) - .then(([projects, users, posts]) => Promise.join( - projects[0].addUser(users[0]), - projects[1].addUser(users[1]), - projects[3].addUser(users[2]), - - users[0].addPost([posts[0]]), - users[2].addPost([posts[2]]) - )) - .then(() => this.Project.findAll({ + it('supports required many-to-many association with nested required one-to-many association', async function() { + await this.sequelize.sync({ force: true }); + + const [projects, users, posts] = await Promise.all([ + this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')), + this.User.bulkCreate(build('Alice', 'Bob', 'David')), + this.Post.bulkCreate(build('post0', 'post1', 'post2')) + ]); + + await Promise.all([ + projects[0].addUser(users[0]), + projects[1].addUser(users[1]), + projects[3].addUser(users[2]), + users[0].addPost([posts[0]]), + users[2].addPost([posts[2]]) + ]); + + const result = await this.Project.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, + model: this.Post, required: true, - include: [{ - model: this.Post, - required: true, - duplicating: true - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('delta'); - }); + duplicating: true + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('delta'); }); - it('supports required many-to-one association with nested many-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - - this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([posts, users, hobbies]) => Promise.join( - posts[0].setUser(users[0]), - posts[1].setUser(users[1]), - posts[3].setUser(users[3]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]), - users[3].addHobby(hobbies[0]) - )) - .then(() => this.Post.findAll({ + it('supports required many-to-one association with nested many-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, users, hobbies] = await Promise.all([ + this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + posts[0].setUser(users[0]), + posts[1].setUser(users[1]), + posts[3].setUser(users[3]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]), + users[3].addHobby(hobbies[0]) + ]); + + const result = await this.Post.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, - required: true, - include: [{ - model: this.Hobby, - where: { - name: 'archery' - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('post3'); - }); + model: this.Hobby, + where: { + name: 'archery' + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('post3'); }); - it('supports required many-to-one association with nested many-to-many association with through.where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - - this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), - this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), - this.Hobby.bulkCreate(build('archery', 'badminton')) - )) - .then(([posts, users, hobbies]) => Promise.join( - posts[0].setUser(users[0]), - posts[1].setUser(users[1]), - posts[3].setUser(users[3]), - users[0].addHobby(hobbies[0]), - users[1].addHobby(hobbies[1]), - users[3].addHobby(hobbies[0]) - )) - .then(() => this.Post.findAll({ + it('supports required many-to-one association with nested many-to-many association with through.where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [posts, users, hobbies] = await Promise.all([ + this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')), + this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')), + this.Hobby.bulkCreate(build('archery', 'badminton')) + ]); + + await Promise.all([ + posts[0].setUser(users[0]), + posts[1].setUser(users[1]), + posts[3].setUser(users[3]), + users[0].addHobby(hobbies[0]), + users[1].addHobby(hobbies[1]), + users[3].addHobby(hobbies[0]) + ]); + + const result = await this.Post.findAll({ + include: [{ + model: this.User, + required: true, include: [{ - model: this.User, + model: this.Hobby, required: true, - include: [{ - model: this.Hobby, - required: true, - through: { - where: { - HobbyName: 'archery' - } + through: { + where: { + HobbyName: 'archery' } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('post3'); - }); + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('post3'); }); - it('supports required many-to-one association with multiple nested associations with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2', 'comment3', 'comment4', 'comment5')), - this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3', 'post4')), - this.User.bulkCreate(build('Alice', 'Bob')), - this.Tag.bulkCreate(build('tag0', 'tag1')) - )) - .then(([comments, posts, users, tags]) => Promise.join( - comments[0].setPost(posts[0]), - comments[1].setPost(posts[1]), - comments[3].setPost(posts[2]), - comments[4].setPost(posts[3]), - comments[5].setPost(posts[4]), - - posts[0].addTag(tags[0]), - posts[3].addTag(tags[0]), - posts[4].addTag(tags[0]), - posts[1].addTag(tags[1]), - - posts[0].setUser(users[0]), - posts[2].setUser(users[0]), - posts[4].setUser(users[0]), - posts[1].setUser(users[1]) - )) - .then(() => this.Comment.findAll({ + it('supports required many-to-one association with multiple nested associations with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [comments, posts, users, tags] = await Promise.all([ + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2', 'comment3', 'comment4', 'comment5')), + this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3', 'post4')), + this.User.bulkCreate(build('Alice', 'Bob')), + this.Tag.bulkCreate(build('tag0', 'tag1')) + ]); + + await Promise.all([ + comments[0].setPost(posts[0]), + comments[1].setPost(posts[1]), + comments[3].setPost(posts[2]), + comments[4].setPost(posts[3]), + comments[5].setPost(posts[4]), + posts[0].addTag(tags[0]), + posts[3].addTag(tags[0]), + posts[4].addTag(tags[0]), + posts[1].addTag(tags[1]), + posts[0].setUser(users[0]), + posts[2].setUser(users[0]), + posts[4].setUser(users[0]), + posts[1].setUser(users[1]) + ]); + + const result = await this.Comment.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.User, - where: { - name: 'Alice' - } - }, { - model: this.Tag, - where: { - name: 'tag0' - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('comment5'); - }); + model: this.User, + where: { + name: 'Alice' + } + }, { + model: this.Tag, + where: { + name: 'tag0' + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('comment5'); }); - it('supports required many-to-one association with nested one-to-many association with where clause', function() { - return this.sequelize.sync({ force: true }) - .then(() => Promise.join( - - this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')), - this.Post.bulkCreate(build('post0', 'post1', 'post2')), - this.Footnote.bulkCreate(build('footnote0', 'footnote1', 'footnote2')) - )) - .then(([comments, posts, footnotes]) => Promise.join( - comments[0].setPost(posts[0]), - comments[1].setPost(posts[1]), - comments[2].setPost(posts[2]), - posts[0].addFootnote(footnotes[0]), - posts[1].addFootnote(footnotes[1]), - posts[2].addFootnote(footnotes[2]) - )) - .then(() => this.Comment.findAll({ + it('supports required many-to-one association with nested one-to-many association with where clause', async function() { + await this.sequelize.sync({ force: true }); + + const [comments, posts, footnotes] = await Promise.all([ + this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')), + this.Post.bulkCreate(build('post0', 'post1', 'post2')), + this.Footnote.bulkCreate(build('footnote0', 'footnote1', 'footnote2')) + ]); + + await Promise.all([ + comments[0].setPost(posts[0]), + comments[1].setPost(posts[1]), + comments[2].setPost(posts[2]), + posts[0].addFootnote(footnotes[0]), + posts[1].addFootnote(footnotes[1]), + posts[2].addFootnote(footnotes[2]) + ]); + + const result = await this.Comment.findAll({ + include: [{ + model: this.Post, + required: true, include: [{ - model: this.Post, - required: true, - include: [{ - model: this.Footnote, - where: { - [Op.or]: [{ - name: 'footnote0' - }, { - name: 'footnote2' - }] - } - }] - }], - order: ['name'], - limit: 1, - offset: 1 - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].name).to.equal('comment2'); - }); + model: this.Footnote, + where: { + [Op.or]: [{ + name: 'footnote0' + }, { + name: 'footnote2' + }] + } + }] + }], + order: ['name'], + limit: 1, + offset: 1 + }); + + expect(result.length).to.equal(1); + expect(result[0].name).to.equal('comment2'); }); }); }); diff --git a/test/integration/include/paranoid.test.js b/test/integration/include/paranoid.test.js index eebdb2d64c11..9f88e8baf1f6 100644 --- a/test/integration/include/paranoid.test.js +++ b/test/integration/include/paranoid.test.js @@ -4,12 +4,11 @@ const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), Support = require('../support'), - Sequelize = require('../../../index'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Paranoid'), () => { - beforeEach(function( ) { + beforeEach(async function() { const S = this.sequelize, DT = DataTypes, @@ -30,7 +29,7 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { D.belongsToMany(A, { through: 'a_d' }); - return S.sync({ force: true }); + await S.sync({ force: true }); }); before(function() { @@ -41,7 +40,7 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { this.clock.restore(); }); - it('paranoid with timestamps: false should be ignored / not crash', function() { + it('paranoid with timestamps: false should be ignored / not crash', async function() { const S = this.sequelize, Test = S.define('Test', { name: DataTypes.STRING @@ -50,12 +49,12 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { paranoid: true }); - return S.sync({ force: true }).then(() => { - return Test.findByPk(1); - }); + await S.sync({ force: true }); + + await Test.findByPk(1); }); - it('test if non required is marked as false', function( ) { + it('test if non required is marked as false', async function() { const A = this.A, B = this.B, options = { @@ -67,12 +66,11 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { ] }; - return A.findOne(options).then(() => { - expect(options.include[0].required).to.be.equal(false); - }); + await A.findOne(options); + expect(options.include[0].required).to.be.equal(false); }); - it('test if required is marked as true', function( ) { + it('test if required is marked as true', async function() { const A = this.A, B = this.B, options = { @@ -84,12 +82,11 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { ] }; - return A.findOne(options).then(() => { - expect(options.include[0].required).to.be.equal(true); - }); + await A.findOne(options); + expect(options.include[0].required).to.be.equal(true); }); - it('should not load paranoid, destroyed instances, with a non-paranoid parent', function() { + it('should not load paranoid, destroyed instances, with a non-paranoid parent', async function() { const X = this.sequelize.define('x', { name: DataTypes.STRING }, { @@ -105,27 +102,26 @@ describe(Support.getTestDialectTeaser('Paranoid'), () => { X.hasMany(Y); - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ - X.create(), - Y.create() - ]); - }).then(([x, y]) => { - this.x = x; - this.y = y; - - return x.addY(y); - }).then(() => { - return this.y.destroy(); - }).then(() => { - //prevent CURRENT_TIMESTAMP to be same - this.clock.tick(1000); - - return X.findAll({ - include: [Y] - }).get(0); - }).then(x => { - expect(x.ys).to.have.length(0); + await this.sequelize.sync({ force: true }); + + const [x0, y] = await Promise.all([ + X.create(), + Y.create() + ]); + + this.x = x0; + this.y = y; + + await x0.addY(y); + await this.y.destroy(); + //prevent CURRENT_TIMESTAMP to be same + this.clock.tick(1000); + + const obj = await X.findAll({ + include: [Y] }); + + const x = await obj[0]; + expect(x.ys).to.have.length(0); }); }); diff --git a/test/integration/include/schema.test.js b/test/integration/include/schema.test.js index 25b5495a0a76..bfffa78f80f0 100644 --- a/test/integration/include/schema.test.js +++ b/test/integration/include/schema.test.js @@ -6,9 +6,9 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - Promise = Sequelize.Promise, dialect = Support.getTestDialect(), - _ = require('lodash'); + _ = require('lodash'), + promiseProps = require('p-props'); const sortById = function(a, b) { return a.id < b.id ? -1 : 1; @@ -16,339 +16,306 @@ const sortById = function(a, b) { describe(Support.getTestDialectTeaser('Includes with schemas'), () => { describe('findAll', () => { - afterEach(function() { - return this.sequelize.dropSchema('account'); + afterEach(async function() { + await this.sequelize.dropSchema('account'); }); - beforeEach(function() { - this.fixtureA = function() { - return this.sequelize.dropSchema('account').then(() => { - return this.sequelize.createSchema('account').then(() => { - const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), - Company = this.sequelize.define('Company', { - name: DataTypes.STRING - }, { schema: 'account' }), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }, { schema: 'account' }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { schema: 'account' }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }, { schema: 'account' }), - Customer = this.sequelize.define('Customer', { - name: DataTypes.STRING - }, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - GroupMember = this.sequelize.define('GroupMember', { - - }, { schema: 'account' }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canPost: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }, { schema: 'account' }); + beforeEach(async function() { + this.fixtureA = async function() { + await this.sequelize.dropSchema('account'); + await this.sequelize.createSchema('account'); + const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), + Company = this.sequelize.define('Company', { + name: DataTypes.STRING + }, { schema: 'account' }), + Product = this.sequelize.define('Product', { + title: DataTypes.STRING + }, { schema: 'account' }), + Tag = this.sequelize.define('Tag', { + name: DataTypes.STRING + }, { schema: 'account' }), + Price = this.sequelize.define('Price', { + value: DataTypes.FLOAT + }, { schema: 'account' }), + Customer = this.sequelize.define('Customer', { + name: DataTypes.STRING + }, { schema: 'account' }), + Group = this.sequelize.define('Group', { + name: DataTypes.STRING + }, { schema: 'account' }), + GroupMember = this.sequelize.define('GroupMember', { + + }, { schema: 'account' }), + Rank = this.sequelize.define('Rank', { + name: DataTypes.STRING, + canInvite: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + canRemove: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + canPost: { + type: DataTypes.INTEGER, + defaultValue: 0 + } + }, { schema: 'account' }); + + this.models = { + AccUser, + Company, + Product, + Tag, + Price, + Customer, + Group, + GroupMember, + Rank + }; + + AccUser.hasMany(Product); + Product.belongsTo(AccUser); + + Product.belongsToMany(Tag, { through: 'product_tag' }); + Tag.belongsToMany(Product, { through: 'product_tag' }); + Product.belongsTo(Tag, { as: 'Category' }); + Product.belongsTo(Company); + + Product.hasMany(Price); + Price.belongsTo(Product); + + AccUser.hasMany(GroupMember, { as: 'Memberships' }); + GroupMember.belongsTo(AccUser); + GroupMember.belongsTo(Rank); + GroupMember.belongsTo(Group); + Group.hasMany(GroupMember, { as: 'Memberships' }); + + await this.sequelize.sync({ force: true }); + const [groups, companies, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' }, + { name: 'Managers' } + ]).then(() => Group.findAll()), + Company.bulkCreate([ + { name: 'Sequelize' }, + { name: 'Coca Cola' }, + { name: 'Bonanza' }, + { name: 'NYSE' }, + { name: 'Coshopr' } + ]).then(() => Company.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, + { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' }, + { name: 'D' }, + { name: 'E' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + AccUser.create(), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Bed' }, + { title: 'Pen' }, + { title: 'Monitor' } + ]).then(() => Product.findAll()) + ]); + const groupMembers = [ + { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } + ]; + if (i < 3) { + groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); + } - this.models = { - AccUser, - Company, - Product, - Tag, - Price, - Customer, - Group, - GroupMember, - Rank - }; - - AccUser.hasMany(Product); - Product.belongsTo(AccUser); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - Product.belongsTo(Company); - - Product.hasMany(Price); - Price.belongsTo(Product); - - AccUser.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(AccUser); - GroupMember.belongsTo(Rank); - GroupMember.belongsTo(Group); - Group.hasMany(GroupMember, { as: 'Memberships' }); - - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' }, - { name: 'Managers' } - ]), - Company.bulkCreate([ - { name: 'Sequelize' }, - { name: 'Coca Cola' }, - { name: 'Bonanza' }, - { name: 'NYSE' }, - { name: 'Coshopr' } - ]), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1 }, - { name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0, canPost: 0 } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' }, - { name: 'D' }, - { name: 'E' } - ]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - Company.findAll(), - Rank.findAll(), - Tag.findAll() - ]); - }).then(([groups, companies, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.all([ - AccUser.create(), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Bed' }, - { title: 'Pen' }, - { title: 'Monitor' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - const groupMembers = [ - { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[2].id } - ]; - if (i < 3) { - groupMembers.push({ AccUserId: user.id, GroupId: groups[2].id, RankId: ranks[1].id }); - } - - return Promise.join( - GroupMember.bulkCreate(groupMembers), - user.setProducts([ - products[i * 5 + 0], - products[i * 5 + 1], - products[i * 5 + 3] - ]), - Promise.join( - products[i * 5 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 5 + 1].setTags([ - tags[1] - ]), - products[i * 5 + 0].setCategory(tags[1]), - products[i * 5 + 2].setTags([ - tags[0] - ]), - products[i * 5 + 3].setTags([ - tags[0] - ]) - ), - Promise.join( - products[i * 5 + 0].setCompany(companies[4]), - products[i * 5 + 1].setCompany(companies[3]), - products[i * 5 + 2].setCompany(companies[2]), - products[i * 5 + 3].setCompany(companies[1]), - products[i * 5 + 4].setCompany(companies[0]) - ), - Price.bulkCreate([ - { ProductId: products[i * 5 + 0].id, value: 5 }, - { ProductId: products[i * 5 + 0].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 5 }, - { ProductId: products[i * 5 + 1].id, value: 10 }, - { ProductId: products[i * 5 + 1].id, value: 15 }, - { ProductId: products[i * 5 + 1].id, value: 20 }, - { ProductId: products[i * 5 + 2].id, value: 20 }, - { ProductId: products[i * 5 + 3].id, value: 20 } - ]) - ); - }); - }); - }); - }); - }); - }); + await Promise.all([ + GroupMember.bulkCreate(groupMembers), + user.setProducts([ + products[i * 5 + 0], + products[i * 5 + 1], + products[i * 5 + 3] + ]), + products[i * 5 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 5 + 1].setTags([ + tags[1] + ]), + products[i * 5 + 0].setCategory(tags[1]), + products[i * 5 + 2].setTags([ + tags[0] + ]), + products[i * 5 + 3].setTags([ + tags[0] + ]), + products[i * 5 + 0].setCompany(companies[4]), + products[i * 5 + 1].setCompany(companies[3]), + products[i * 5 + 2].setCompany(companies[2]), + products[i * 5 + 3].setCompany(companies[1]), + products[i * 5 + 4].setCompany(companies[0]), + Price.bulkCreate([ + { ProductId: products[i * 5 + 0].id, value: 5 }, + { ProductId: products[i * 5 + 0].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 5 }, + { ProductId: products[i * 5 + 1].id, value: 10 }, + { ProductId: products[i * 5 + 1].id, value: 15 }, + { ProductId: products[i * 5 + 1].id, value: 20 }, + { ProductId: products[i * 5 + 2].id, value: 20 }, + { ProductId: products[i * 5 + 3].id, value: 20 } + ]) + ]); + } }; - return this.sequelize.createSchema('account'); + await this.sequelize.createSchema('account'); }); - it('should support an include with multiple different association types', function() { - return this.sequelize.dropSchema('account').then(() => { - return this.sequelize.createSchema('account').then(() => { - const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), - Product = this.sequelize.define('Product', { - title: DataTypes.STRING - }, { schema: 'account' }), - Tag = this.sequelize.define('Tag', { - name: DataTypes.STRING - }, { schema: 'account' }), - Price = this.sequelize.define('Price', { - value: DataTypes.FLOAT - }, { schema: 'account' }), - Group = this.sequelize.define('Group', { - name: DataTypes.STRING - }, { schema: 'account' }), - GroupMember = this.sequelize.define('GroupMember', { - - }, { schema: 'account' }), - Rank = this.sequelize.define('Rank', { - name: DataTypes.STRING, - canInvite: { - type: DataTypes.INTEGER, - defaultValue: 0 - }, - canRemove: { - type: DataTypes.INTEGER, - defaultValue: 0 - } - }, { schema: 'account' }); - - AccUser.hasMany(Product); - Product.belongsTo(AccUser); - - Product.belongsToMany(Tag, { through: 'product_tag' }); - Tag.belongsToMany(Product, { through: 'product_tag' }); - Product.belongsTo(Tag, { as: 'Category' }); - - Product.hasMany(Price); - Price.belongsTo(Product); - - AccUser.hasMany(GroupMember, { as: 'Memberships' }); - GroupMember.belongsTo(AccUser); - GroupMember.belongsTo(Rank); - GroupMember.belongsTo(Group); - Group.hasMany(GroupMember, { as: 'Memberships' }); - - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } - ]).then(() => { - return Group.findAll(); - }), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } - ]).then(() => { - return Rank.findAll(); - }), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]).then(() => { - return Tag.findAll(); - }) - ]).then(([groups, ranks, tags]) => { - return Promise.each([0, 1, 2, 3, 4], i => { - return Promise.all([ - AccUser.create(), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - return Promise.all([ - GroupMember.bulkCreate([ - { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - user.setProducts([ - products[i * 2 + 0], - products[i * 2 + 1] - ]), - products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 2 + 1].setTags([ - tags[1] - ]), - products[i * 2 + 0].setCategory(tags[1]), - Price.bulkCreate([ - { ProductId: products[i * 2 + 0].id, value: 5 }, - { ProductId: products[i * 2 + 0].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 5 }, - { ProductId: products[i * 2 + 1].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 15 }, - { ProductId: products[i * 2 + 1].id, value: 20 } - ]) - ]); - }); - }); - }).then(() => { - return AccUser.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - Rank - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - Price - ] } - ], - order: [ - [AccUser.rawAttributes.id, 'ASC'] - ] - }).then(users => { - users.forEach(user => { - expect(user.Memberships).to.be.ok; - user.Memberships.sort(sortById); - - expect(user.Memberships.length).to.equal(2); - expect(user.Memberships[0].Group.name).to.equal('Developers'); - expect(user.Memberships[0].Rank.canRemove).to.equal(1); - expect(user.Memberships[1].Group.name).to.equal('Designers'); - expect(user.Memberships[1].Rank.canRemove).to.equal(0); - - user.Products.sort(sortById); - expect(user.Products.length).to.equal(2); - expect(user.Products[0].Tags.length).to.equal(2); - expect(user.Products[1].Tags.length).to.equal(1); - expect(user.Products[0].Category).to.be.ok; - expect(user.Products[1].Category).not.to.be.ok; - - expect(user.Products[0].Prices.length).to.equal(2); - expect(user.Products[1].Prices.length).to.equal(4); - }); - }); - }); - }); + it('should support an include with multiple different association types', async function() { + await this.sequelize.dropSchema('account'); + await this.sequelize.createSchema('account'); + const AccUser = this.sequelize.define('AccUser', {}, { schema: 'account' }), + Product = this.sequelize.define('Product', { + title: DataTypes.STRING + }, { schema: 'account' }), + Tag = this.sequelize.define('Tag', { + name: DataTypes.STRING + }, { schema: 'account' }), + Price = this.sequelize.define('Price', { + value: DataTypes.FLOAT + }, { schema: 'account' }), + Group = this.sequelize.define('Group', { + name: DataTypes.STRING + }, { schema: 'account' }), + GroupMember = this.sequelize.define('GroupMember', { + + }, { schema: 'account' }), + Rank = this.sequelize.define('Rank', { + name: DataTypes.STRING, + canInvite: { + type: DataTypes.INTEGER, + defaultValue: 0 + }, + canRemove: { + type: DataTypes.INTEGER, + defaultValue: 0 + } + }, { schema: 'account' }); + + AccUser.hasMany(Product); + Product.belongsTo(AccUser); + + Product.belongsToMany(Tag, { through: 'product_tag' }); + Tag.belongsToMany(Product, { through: 'product_tag' }); + Product.belongsTo(Tag, { as: 'Category' }); + + Product.hasMany(Price); + Price.belongsTo(Product); + + AccUser.hasMany(GroupMember, { as: 'Memberships' }); + GroupMember.belongsTo(AccUser); + GroupMember.belongsTo(Rank); + GroupMember.belongsTo(Group); + Group.hasMany(GroupMember, { as: 'Memberships' }); + + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + AccUser.create(), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()) + ]); + await Promise.all([ + GroupMember.bulkCreate([ + { AccUserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { AccUserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } + ]), + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] + ]), + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } + ]) + ]); + const users = await AccUser.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + Rank + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + Price + ] } + ], + order: [ + [AccUser.rawAttributes.id, 'ASC'] + ] }); - }); + for (const user of users) { + expect(user.Memberships).to.be.ok; + user.Memberships.sort(sortById); + + expect(user.Memberships.length).to.equal(2); + expect(user.Memberships[0].Group.name).to.equal('Developers'); + expect(user.Memberships[0].Rank.canRemove).to.equal(1); + expect(user.Memberships[1].Group.name).to.equal('Designers'); + expect(user.Memberships[1].Rank.canRemove).to.equal(0); + + user.Products.sort(sortById); + expect(user.Products.length).to.equal(2); + expect(user.Products[0].Tags.length).to.equal(2); + expect(user.Products[1].Tags.length).to.equal(1); + expect(user.Products[0].Category).to.be.ok; + expect(user.Products[1].Category).not.to.be.ok; + + expect(user.Products[0].Prices.length).to.equal(2); + expect(user.Products[1].Prices.length).to.equal(4); + } + } }); - it('should support many levels of belongsTo', function() { + it('should support many levels of belongsTo', async function() { const A = this.sequelize.define('a', {}, { schema: 'account' }), B = this.sequelize.define('b', {}, { schema: 'account' }), C = this.sequelize.define('c', {}, { schema: 'account' }), @@ -377,58 +344,46 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { H ]; - return this.sequelize.sync().then(() => { - return A.bulkCreate([ - {}, {}, {}, {}, {}, {}, {}, {} - ]).then(() => { - let previousInstance; - return Promise.each(singles, model => { - return model.create({}).then(instance => { - if (previousInstance) { - return previousInstance[`set${_.upperFirst(model.name)}`](instance).then(() => { - previousInstance = instance; - }); - } - previousInstance = b = instance; - return void 0; - }); - }); - }).then(() => { - return A.findAll(); - }).then(as => { - const promises = []; - as.forEach(a => { - promises.push(a.setB(b)); - }); - return Promise.all(promises); - }).then(() => { - return A.findAll({ - include: [ - { model: B, include: [ - { model: C, include: [ - { model: D, include: [ - { model: E, include: [ - { model: F, include: [ - { model: G, include: [ - { model: H } - ] } - ] } + await this.sequelize.sync(); + await A.bulkCreate([ + {}, {}, {}, {}, {}, {}, {}, {} + ]); + let previousInstance; + for (const model of singles) { + const instance = await model.create({}); + if (previousInstance) { + await previousInstance[`set${_.upperFirst(model.name)}`](instance); + previousInstance = instance; + continue; + } + previousInstance = b = instance; + } + let as = await A.findAll(); + await Promise.all(as.map(a => a.setB(b))); + as = await A.findAll({ + include: [ + { model: B, include: [ + { model: C, include: [ + { model: D, include: [ + { model: E, include: [ + { model: F, include: [ + { model: G, include: [ + { model: H } ] } ] } ] } ] } - ] - }).then(as => { - expect(as.length).to.be.ok; - as.forEach(a => { - expect(a.b.c.d.e.f.g.h).to.be.ok; - }); - }); - }); + ] } + ] } + ] + }); + expect(as.length).to.be.ok; + as.forEach(a => { + expect(a.b.c.d.e.f.g.h).to.be.ok; }); }); - it('should support ordering with only belongsTo includes', function() { + it('should support ordering with only belongsTo includes', async function() { const User = this.sequelize.define('SpecialUser', {}, { schema: 'account' }), Item = this.sequelize.define('Item', { 'test': DataTypes.STRING }, { schema: 'account' }), Order = this.sequelize.define('Order', { 'position': DataTypes.INTEGER }, { schema: 'account' }); @@ -437,59 +392,59 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Item, { 'as': 'itemB', foreignKey: 'itemB_id' }); User.belongsTo(Order); - return this.sequelize.sync().then(() => { - return Promise.all([ - User.bulkCreate([{}, {}, {}]), - Item.bulkCreate([ - { 'test': 'abc' }, - { 'test': 'def' }, - { 'test': 'ghi' }, - { 'test': 'jkl' } - ]), - Order.bulkCreate([ - { 'position': 2 }, - { 'position': 3 }, - { 'position': 1 } - ]) - ]).then(() => { - return Promise.all([ - User.findAll(), - Item.findAll({ order: ['id'] }), - Order.findAll({ order: ['id'] }) - ]); - }).then(([users, items, orders]) => { - return Promise.all([ - users[0].setItemA(items[0]), - users[0].setItemB(items[1]), - users[0].setOrder(orders[2]), - users[1].setItemA(items[2]), - users[1].setItemB(items[3]), - users[1].setOrder(orders[1]), - users[2].setItemA(items[0]), - users[2].setItemB(items[3]), - users[2].setOrder(orders[0]) - ]); - }).then(() => { - return User.findAll({ - 'include': [ - { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, - { 'model': Item, 'as': 'itemB' }, - Order], - 'order': [ - [Order, 'position'] - ] - }).then(as => { - expect(as.length).to.eql(2); - expect(as[0].itemA.test).to.eql('abc'); - expect(as[1].itemA.test).to.eql('abc'); - expect(as[0].Order.position).to.eql(1); - expect(as[1].Order.position).to.eql(2); - }); - }); + await this.sequelize.sync(); + + await Promise.all([ + User.bulkCreate([{}, {}, {}]), + Item.bulkCreate([ + { 'test': 'abc' }, + { 'test': 'def' }, + { 'test': 'ghi' }, + { 'test': 'jkl' } + ]), + Order.bulkCreate([ + { 'position': 2 }, + { 'position': 3 }, + { 'position': 1 } + ]) + ]); + + const [users, items, orders] = await Promise.all([ + User.findAll(), + Item.findAll({ order: ['id'] }), + Order.findAll({ order: ['id'] }) + ]); + + await Promise.all([ + users[0].setItemA(items[0]), + users[0].setItemB(items[1]), + users[0].setOrder(orders[2]), + users[1].setItemA(items[2]), + users[1].setItemB(items[3]), + users[1].setOrder(orders[1]), + users[2].setItemA(items[0]), + users[2].setItemB(items[3]), + users[2].setOrder(orders[0]) + ]); + + const as = await User.findAll({ + 'include': [ + { 'model': Item, 'as': 'itemA', where: { test: 'abc' } }, + { 'model': Item, 'as': 'itemB' }, + Order], + 'order': [ + [Order, 'position'] + ] }); + + expect(as.length).to.eql(2); + expect(as[0].itemA.test).to.eql('abc'); + expect(as[1].itemA.test).to.eql('abc'); + expect(as[0].Order.position).to.eql(1); + expect(as[1].Order.position).to.eql(2); }); - it('should include attributes from through models', function() { + it('should include attributes from through models', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }, { schema: 'account' }), @@ -503,84 +458,84 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ]).then(() => { - return Promise.all([ - Product.findAll(), - Tag.findAll() - ]); - }).then(([products, tags]) => { - return Promise.all([ - products[0].addTag(tags[0], { through: { priority: 1 } }), - products[0].addTag(tags[1], { through: { priority: 2 } }), - products[1].addTag(tags[1], { through: { priority: 1 } }), - products[2].addTag(tags[0], { through: { priority: 3 } }), - products[2].addTag(tags[1], { through: { priority: 1 } }), - products[2].addTag(tags[2], { through: { priority: 2 } }) - ]); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag } - ], - order: [ - ['id', 'ASC'], - [Tag, 'id', 'ASC'] - ] - }).then(products => { - expect(products[0].Tags[0].ProductTag.priority).to.equal(1); - expect(products[0].Tags[1].ProductTag.priority).to.equal(2); - expect(products[1].Tags[0].ProductTag.priority).to.equal(1); - expect(products[2].Tags[0].ProductTag.priority).to.equal(3); - expect(products[2].Tags[1].ProductTag.priority).to.equal(1); - expect(products[2].Tags[2].ProductTag.priority).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]) + ]); + + const [products0, tags] = await Promise.all([ + Product.findAll(), + Tag.findAll() + ]); + + await Promise.all([ + products0[0].addTag(tags[0], { through: { priority: 1 } }), + products0[0].addTag(tags[1], { through: { priority: 2 } }), + products0[1].addTag(tags[1], { through: { priority: 1 } }), + products0[2].addTag(tags[0], { through: { priority: 3 } }), + products0[2].addTag(tags[1], { through: { priority: 1 } }), + products0[2].addTag(tags[2], { through: { priority: 2 } }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag } + ], + order: [ + ['id', 'ASC'], + [Tag, 'id', 'ASC'] + ] }); + + expect(products[0].Tags[0].ProductTag.priority).to.equal(1); + expect(products[0].Tags[1].ProductTag.priority).to.equal(2); + expect(products[1].Tags[0].ProductTag.priority).to.equal(1); + expect(products[2].Tags[0].ProductTag.priority).to.equal(3); + expect(products[2].Tags[1].ProductTag.priority).to.equal(1); + expect(products[2].Tags[2].ProductTag.priority).to.equal(2); }); - it('should support a required belongsTo include', function() { + it('should support a required belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', {}, { schema: 'account' }); User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([{}, {}]), - User.bulkCreate([{}, {}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll() - ]); - }).then(([groups, users]) => { - return users[2].setGroup(groups[1]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([{}, {}]), + User.bulkCreate([{}, {}, {}]) + ]); + + const [groups, users0] = await Promise.all([ + Group.findAll(), + User.findAll() + ]); + + await users0[2].setGroup(groups[1]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -588,38 +543,38 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll() - ]); - }).then(([groups, users]) => { - return Promise.all([ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].Group).to.be.ok; - expect(users[0].Group.name).to.equal('A'); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]) + ]); + + const [groups, users0] = await Promise.all([ + Group.findAll(), + User.findAll() + ]); + + await Promise.all([ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].Group).to.be.ok; + expect(users[0].Group.name).to.equal('A'); }); - it('should be possible to extend the on clause with a where option on a belongsTo include', function() { + it('should be possible to extend the on clause with a where option on a belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -627,38 +582,38 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll() - ]); - }).then(([groups, users]) => { - return Promise.all([ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true } - ] - }).then(users => { - users.forEach(user => { - expect(user.Group).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]) + ]); + + const [groups, users0] = await Promise.all([ + Group.findAll(), + User.findAll() + ]); + + await Promise.all([ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, required: true } + ] + }); + + users.forEach(user => { + expect(user.Group).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -670,49 +625,49 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]), - Category.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll(), - Category.findAll() - ]); - }).then(([groups, users, categories]) => { - const promises = [ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]; - groups.forEach(group => { - promises.push(group.setCategories(categories)); - }); - return Promise.all(promises); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]), + Category.bulkCreate([{}, {}]) + ]); + + const [groups, users0, categories] = await Promise.all([ + Group.findAll(), + User.findAll(), + Category.findAll() + ]); + + const promises = [ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]; + groups.forEach(group => { + promises.push(group.setCategories(categories)); + }); + await Promise.all(promises); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', function() { + it('should be possible to define a belongsTo include as required with child hasMany with limit and aliases', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -724,49 +679,49 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group, { as: 'Team' }); Group.hasMany(Category, { as: 'Tags' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]), - Category.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll(), - Category.findAll() - ]); - }).then(([groups, users, categories]) => { - const promises = [ - users[0].setTeam(groups[1]), - users[1].setTeam(groups[0]) - ]; - groups.forEach(group => { - promises.push(group.setTags(categories)); - }); - return Promise.all(promises); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, as: 'Team', include: [ - { model: Category, as: 'Tags' } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Team).to.be.ok; - expect(user.Team.Tags).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]), + Category.bulkCreate([{}, {}]) + ]); + + const [groups, users0, categories] = await Promise.all([ + Group.findAll(), + User.findAll(), + Category.findAll() + ]); + + const promises = [ + users0[0].setTeam(groups[1]), + users0[1].setTeam(groups[0]) + ]; + groups.forEach(group => { + promises.push(group.setTags(categories)); + }); + await Promise.all(promises); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, as: 'Team', include: [ + { model: Category, as: 'Tags' } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Team).to.be.ok; + expect(user.Team.Tags).to.be.ok; }); }); - it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', function() { + it('should be possible to define a belongsTo include as required with child hasMany which is not required with limit', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -778,49 +733,49 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); Group.hasMany(Category); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]), - User.bulkCreate([{}, {}]), - Category.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - User.findAll(), - Category.findAll() - ]); - }).then(([groups, users, categories]) => { - const promises = [ - users[0].setGroup(groups[1]), - users[1].setGroup(groups[0]) - ]; - groups.forEach(group => { - promises.push(group.setCategories(categories)); - }); - return Promise.all(promises); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, required: true, include: [ - { model: Category, required: false } - ] } - ], - limit: 1 - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.Group).to.be.ok; - expect(user.Group.Categories).to.be.ok; - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]), + User.bulkCreate([{}, {}]), + Category.bulkCreate([{}, {}]) + ]); + + const [groups, users0, categories] = await Promise.all([ + Group.findAll(), + User.findAll(), + Category.findAll() + ]); + + const promises = [ + users0[0].setGroup(groups[1]), + users0[1].setGroup(groups[0]) + ]; + groups.forEach(group => { + promises.push(group.setCategories(categories)); + }); + await Promise.all(promises); + + const users = await User.findAll({ + include: [ + { model: Group, required: true, include: [ + { model: Category, required: false } + ] } + ], + limit: 1 + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.Group).to.be.ok; + expect(user.Group.Categories).to.be.ok; }); }); - it('should be possible to extend the on clause with a where option on a hasOne include', function() { + it('should be possible to extend the on clause with a where option on a hasOne include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Project = this.sequelize.define('Project', { title: DataTypes.STRING @@ -828,38 +783,38 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.hasOne(Project, { as: 'LeaderOf' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Project.bulkCreate([ - { title: 'Alpha' }, - { title: 'Beta' } - ]), - User.bulkCreate([{}, {}]) - ]).then(() => { - return Promise.all([ - Project.findAll(), - User.findAll() - ]); - }).then(([projects, users]) => { - return Promise.all([ - users[1].setLeaderOf(projects[1]), - users[0].setLeaderOf(projects[0]) - ]); - }).then(() => { - return User.findAll({ - include: [ - { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } - ] - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].LeaderOf).to.be.ok; - expect(users[0].LeaderOf.title).to.equal('Beta'); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Project.bulkCreate([ + { title: 'Alpha' }, + { title: 'Beta' } + ]), + User.bulkCreate([{}, {}]) + ]); + + const [projects, users0] = await Promise.all([ + Project.findAll(), + User.findAll() + ]); + + await Promise.all([ + users0[1].setLeaderOf(projects[1]), + users0[0].setLeaderOf(projects[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Project, as: 'LeaderOf', where: { title: 'Beta' } } + ] }); + + expect(users.length).to.equal(1); + expect(users[0].LeaderOf).to.be.ok; + expect(users[0].LeaderOf.title).to.equal('Beta'); }); - it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function() { + it('should be possible to extend the on clause with a where option on a hasMany include with a through model', async function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }, { schema: 'account' }), @@ -873,46 +828,46 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { Product.belongsToMany(Tag, { through: ProductTag }); Tag.belongsToMany(Product, { through: ProductTag }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Dress' } - ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } - ]) - ]).then(() => { - return Promise.all([ - Product.findAll(), - Tag.findAll() - ]); - }).then(([products, tags]) => { - return Promise.all([ - products[0].addTag(tags[0], { priority: 1 }), - products[0].addTag(tags[1], { priority: 2 }), - products[1].addTag(tags[1], { priority: 1 }), - products[2].addTag(tags[0], { priority: 3 }), - products[2].addTag(tags[1], { priority: 1 }), - products[2].addTag(tags[2], { priority: 2 }) - ]); - }).then(() => { - return Product.findAll({ - include: [ - { model: Tag, where: { name: 'C' } } - ] - }).then(products => { - expect(products.length).to.equal(1); - expect(products[0].Tags.length).to.equal(1); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Dress' } + ]), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]) + ]); + + const [products0, tags] = await Promise.all([ + Product.findAll(), + Tag.findAll() + ]); + + await Promise.all([ + products0[0].addTag(tags[0], { priority: 1 }), + products0[0].addTag(tags[1], { priority: 2 }), + products0[1].addTag(tags[1], { priority: 1 }), + products0[2].addTag(tags[0], { priority: 3 }), + products0[2].addTag(tags[1], { priority: 1 }), + products0[2].addTag(tags[2], { priority: 2 }) + ]); + + const products = await Product.findAll({ + include: [ + { model: Tag, where: { name: 'C' } } + ] }); + + expect(products.length).to.equal(1); + expect(products[0].Tags.length).to.equal(1); }); - it('should be possible to extend the on clause with a where option on nested includes', function() { + it('should be possible to extend the on clause with a where option on nested includes', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING }, { schema: 'account' }), @@ -959,99 +914,86 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { GroupMember.belongsTo(Group); Group.hasMany(GroupMember, { as: 'Memberships' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - Group.bulkCreate([ - { name: 'Developers' }, - { name: 'Designers' } + await this.sequelize.sync({ force: true }); + const [groups, ranks, tags] = await Promise.all([ + Group.bulkCreate([ + { name: 'Developers' }, + { name: 'Designers' } + ]).then(() => Group.findAll()), + Rank.bulkCreate([ + { name: 'Admin', canInvite: 1, canRemove: 1 }, + { name: 'Member', canInvite: 1, canRemove: 0 } + ]).then(() => Rank.findAll()), + Tag.bulkCreate([ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ]).then(() => Tag.findAll()) + ]); + for (const i of [0, 1, 2, 3, 4]) { + const [user, products] = await Promise.all([ + User.create({ name: 'FooBarzz' }), + Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' } + ]).then(() => Product.findAll()) + ]); + await Promise.all([ + GroupMember.bulkCreate([ + { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, + { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } ]), - Rank.bulkCreate([ - { name: 'Admin', canInvite: 1, canRemove: 1 }, - { name: 'Member', canInvite: 1, canRemove: 0 } + user.setProducts([ + products[i * 2 + 0], + products[i * 2 + 1] ]), - Tag.bulkCreate([ - { name: 'A' }, - { name: 'B' }, - { name: 'C' } + products[i * 2 + 0].setTags([ + tags[0], + tags[2] + ]), + products[i * 2 + 1].setTags([ + tags[1] + ]), + products[i * 2 + 0].setCategory(tags[1]), + Price.bulkCreate([ + { ProductId: products[i * 2 + 0].id, value: 5 }, + { ProductId: products[i * 2 + 0].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 5 }, + { ProductId: products[i * 2 + 1].id, value: 10 }, + { ProductId: products[i * 2 + 1].id, value: 15 }, + { ProductId: products[i * 2 + 1].id, value: 20 } ]) - ]).then(() => { - return Promise.all([ - Group.findAll(), - Rank.findAll(), - Tag.findAll() - ]); - }).then(([groups, ranks, tags]) => { - return Promise.resolve([0, 1, 2, 3, 4]).each(i => { - return Promise.all([ - User.create({ name: 'FooBarzz' }), - Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' } - ]).then(() => { - return Product.findAll(); - }) - ]).then(([user, products]) => { - return Promise.all([ - GroupMember.bulkCreate([ - { UserId: user.id, GroupId: groups[0].id, RankId: ranks[0].id }, - { UserId: user.id, GroupId: groups[1].id, RankId: ranks[1].id } - ]), - user.setProducts([ - products[i * 2 + 0], - products[i * 2 + 1] - ]), - products[i * 2 + 0].setTags([ - tags[0], - tags[2] - ]), - products[i * 2 + 1].setTags([ - tags[1] - ]), - products[i * 2 + 0].setCategory(tags[1]), - Price.bulkCreate([ - { ProductId: products[i * 2 + 0].id, value: 5 }, - { ProductId: products[i * 2 + 0].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 5 }, - { ProductId: products[i * 2 + 1].id, value: 10 }, - { ProductId: products[i * 2 + 1].id, value: 15 }, - { ProductId: products[i * 2 + 1].id, value: 20 } - ]) - ]); - }); - }); - }).then(() => { - return User.findAll({ - include: [ - { model: GroupMember, as: 'Memberships', include: [ - Group, - { model: Rank, where: { name: 'Admin' } } - ] }, - { model: Product, include: [ - Tag, - { model: Tag, as: 'Category' }, - { model: Price, where: { - value: { - [Op.gt]: 15 - } - } } - ] } - ], - order: [ - ['id', 'ASC'] - ] - }).then(users => { - users.forEach(user => { - expect(user.Memberships.length).to.equal(1); - expect(user.Memberships[0].Rank.name).to.equal('Admin'); - expect(user.Products.length).to.equal(1); - expect(user.Products[0].Prices.length).to.equal(1); - }); - }); + ]); + const users = await User.findAll({ + include: [ + { model: GroupMember, as: 'Memberships', include: [ + Group, + { model: Rank, where: { name: 'Admin' } } + ] }, + { model: Product, include: [ + Tag, + { model: Tag, as: 'Category' }, + { model: Price, where: { + value: { + [Op.gt]: 15 + } + } } + ] } + ], + order: [ + ['id', 'ASC'] + ] }); - }); + users.forEach(user => { + expect(user.Memberships.length).to.equal(1); + expect(user.Memberships[0].Rank.name).to.equal('Admin'); + expect(user.Products.length).to.equal(1); + expect(user.Products[0].Prices.length).to.equal(1); + }); + } }); - it('should be possible to use limit and a where with a belongsTo include', function() { + it('should be possible to use limit and a where with a belongsTo include', async function() { const User = this.sequelize.define('User', {}, { schema: 'account' }), Group = this.sequelize.define('Group', { name: DataTypes.STRING @@ -1059,123 +1001,123 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsTo(Group); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.props({ - groups: Group.bulkCreate([ - { name: 'A' }, - { name: 'B' } - ]).then(() => { - return Group.findAll(); - }), - users: User.bulkCreate([{}, {}, {}, {}]).then(() => { - return User.findAll(); - }) - }).then(results => { - return Promise.join( - results.users[1].setGroup(results.groups[0]), - results.users[2].setGroup(results.groups[0]), - results.users[3].setGroup(results.groups[1]), - results.users[0].setGroup(results.groups[0]) - ); - }).then(() => { - return User.findAll({ - include: [ - { model: Group, where: { name: 'A' } } - ], - limit: 2 - }).then(users => { - expect(users.length).to.equal(2); - - users.forEach(user => { - expect(user.Group.name).to.equal('A'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const results = await promiseProps({ + groups: Group.bulkCreate([ + { name: 'A' }, + { name: 'B' } + ]).then(() => { + return Group.findAll(); + }), + users: User.bulkCreate([{}, {}, {}, {}]).then(() => { + return User.findAll(); + }) + }); + + await Promise.all([ + results.users[1].setGroup(results.groups[0]), + results.users[2].setGroup(results.groups[0]), + results.users[3].setGroup(results.groups[1]), + results.users[0].setGroup(results.groups[0]) + ]); + + const users = await User.findAll({ + include: [ + { model: Group, where: { name: 'A' } } + ], + limit: 2 + }); + + expect(users.length).to.equal(2); + + users.forEach(user => { + expect(user.Group.name).to.equal('A'); }); }); - it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - attributes: ['title'], - include: [ - { model: this.models.Company, where: { name: 'NYSE' } }, - { model: this.models.Tag }, - { model: this.models.Price } - ], - limit: 3, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(3); - - products.forEach(product => { - expect(product.Company.name).to.equal('NYSE'); - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; - }); - }); + it('should be possible use limit, attributes and a where on a belongsTo with additional hasMany includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + attributes: ['title'], + include: [ + { model: this.models.Company, where: { name: 'NYSE' } }, + { model: this.models.Tag }, + { model: this.models.Price } + ], + limit: 3, + order: [ + ['id', 'ASC'] + ] + }); + + expect(products.length).to.equal(3); + + products.forEach(product => { + expect(product.Company.name).to.equal('NYSE'); + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; }); }); - it('should be possible to use limit and a where on a hasMany with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag }, - { model: this.models.Price, where: { - value: { [Op.gt]: 5 } - } } - ], - limit: 6, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(6); + it('should be possible to use limit and a where on a hasMany with additional includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag }, + { model: this.models.Price, where: { + value: { [Op.gt]: 5 } + } } + ], + limit: 6, + order: [ + ['id', 'ASC'] + ] + }); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + expect(products.length).to.equal(6); - product.Prices.forEach(price => { - expect(price.value).to.be.above(5); - }); - }); + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Prices.forEach(price => { + expect(price.value).to.be.above(5); }); }); }); - it('should be possible to use limit and a where on a hasMany with a through model with additional includes', function() { - return this.fixtureA().then(() => { - return this.models.Product.findAll({ - include: [ - { model: this.models.Company }, - { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, - { model: this.models.Price } - ], - limit: 10, - order: [ - ['id', 'ASC'] - ] - }).then(products => { - expect(products.length).to.equal(10); + it('should be possible to use limit and a where on a hasMany with a through model with additional includes', async function() { + await this.fixtureA(); + + const products = await this.models.Product.findAll({ + include: [ + { model: this.models.Company }, + { model: this.models.Tag, where: { name: ['A', 'B', 'C'] } }, + { model: this.models.Price } + ], + limit: 10, + order: [ + ['id', 'ASC'] + ] + }); - products.forEach(product => { - expect(product.Tags.length).to.be.ok; - expect(product.Prices.length).to.be.ok; + expect(products.length).to.equal(10); - product.Tags.forEach(tag => { - expect(['A', 'B', 'C']).to.include(tag.name); - }); - }); + products.forEach(product => { + expect(product.Tags.length).to.be.ok; + expect(product.Prices.length).to.be.ok; + + product.Tags.forEach(tag => { + expect(['A', 'B', 'C']).to.include(tag.name); }); }); }); - it('should support including date fields, with the correct timezone', function() { + it('should support including date fields, with the correct timezone', async function() { const User = this.sequelize.define('user', { dateField: Sequelize.DATE }, { timestamps: false, schema: 'account' }), @@ -1186,34 +1128,31 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { User.belongsToMany(Group, { through: 'group_user' }); Group.belongsToMany(User, { through: 'group_user' }); - return this.sequelize.sync().then(() => { - return User.create({ dateField: Date.UTC(2014, 1, 20) }).then(user => { - return Group.create({ dateField: Date.UTC(2014, 1, 20) }).then(group => { - return user.addGroup(group).then(() => { - return User.findAll({ - where: { - id: user.id - }, - include: [Group] - }).then(users => { - if (dialect === 'sqlite') { - expect(new Date(users[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(new Date(users[0].groups[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); - } else { - expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); - } - }); - }); - }); - }); + await this.sequelize.sync(); + const user = await User.create({ dateField: Date.UTC(2014, 1, 20) }); + const group = await Group.create({ dateField: Date.UTC(2014, 1, 20) }); + await user.addGroup(group); + + const users = await User.findAll({ + where: { + id: user.id + }, + include: [Group] }); + + if (dialect === 'sqlite') { + expect(new Date(users[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(new Date(users[0].groups[0].dateField).getTime()).to.equal(Date.UTC(2014, 1, 20)); + } else { + expect(users[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + expect(users[0].groups[0].dateField.getTime()).to.equal(Date.UTC(2014, 1, 20)); + } }); }); describe('findOne', () => { - it('should work with schemas', function() { + it('should work with schemas', async function() { const UserModel = this.sequelize.define('User', { Id: { type: DataTypes.INTEGER, @@ -1265,21 +1204,21 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => { foreignKey: 'UserId' }); - return this.sequelize.dropSchema('hero').then(() => { - return this.sequelize.createSchema('hero'); - }).then(() => { - return this.sequelize.sync({ force: true }).then(() => { - return UserModel.findOne({ - where: { - Id: 1 - }, - include: [{ - model: ResumeModel, - as: 'Resume' - }] - }); - }); - }).then(() => this.sequelize.dropSchema('hero')); + await this.sequelize.dropSchema('hero'); + await this.sequelize.createSchema('hero'); + await this.sequelize.sync({ force: true }); + + await UserModel.findOne({ + where: { + Id: 1 + }, + include: [{ + model: ResumeModel, + as: 'Resume' + }] + }); + + await this.sequelize.dropSchema('hero'); }); }); }); diff --git a/test/integration/include/separate.test.js b/test/integration/include/separate.test.js old mode 100755 new mode 100644 index de33d846812f..aea667455c47 --- a/test/integration/include/separate.test.js +++ b/test/integration/include/separate.test.js @@ -4,67 +4,62 @@ const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), Support = require('../support'), - Sequelize = require('../../../index'), DataTypes = require('../../../lib/data-types'), current = Support.sequelize, - dialect = Support.getTestDialect(), - Promise = Sequelize.Promise; + dialect = Support.getTestDialect(); if (current.dialect.supports.groupedLimit) { describe(Support.getTestDialectTeaser('Include'), () => { describe('separate', () => { - it('should run a hasMany association in a separate query', function() { + it('should run a hasMany association in a separate query', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', {}), sqlSpy = sinon.spy(); User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - {} - ] - }, { - include: [User.Tasks] - }) - ).then(() => { - return User.findAll({ - include: [ - { association: User.Tasks, separate: true } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(3); - expect(users[1].get('tasks')).to.be.ok; - expect(users[1].get('tasks').length).to.equal(1); - - expect(users[0].get('tasks')[0].createdAt).to.be.ok; - expect(users[0].get('tasks')[0].updatedAt).to.be.ok; - - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {} + ] + }, { + include: [User.Tasks] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Tasks, separate: true } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(3); + expect(users[1].get('tasks')).to.be.ok; + expect(users[1].get('tasks').length).to.equal(1); + + expect(users[0].get('tasks')[0].createdAt).to.be.ok; + expect(users[0].get('tasks')[0].updatedAt).to.be.ok; + + expect(sqlSpy).to.have.been.calledTwice; }); - it('should work even if the id was not included', function() { + it('should work even if the id was not included', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING }), @@ -73,36 +68,36 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }).then(() => { - return User.findAll({ - attributes: ['name'], - include: [ - { association: User.Tasks, separate: true } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(3); - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }); + + const users = await User.findAll({ + attributes: ['name'], + include: [ + { association: User.Tasks, separate: true } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(3); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should work even if include does not specify foreign key attribute with custom sourceKey', function() { + it('should work even if include does not specify foreign key attribute with custom sourceKey', async function() { const User = this.sequelize.define('User', { name: DataTypes.STRING, userExtraId: { @@ -121,47 +116,44 @@ if (current.dialect.supports.groupedLimit) { sourceKey: 'userExtraId' }); - return this.sequelize - .sync({ force: true }) - .then(() => { - return User.create({ - id: 1, - userExtraId: 222, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }); - }) - .then(() => { - return User.findAll({ - attributes: ['name'], - include: [ - { - attributes: [ - 'title' - ], - association: User.Tasks, - separate: true - } - ], - order: [ - ['id', 'ASC'] + await this.sequelize + .sync({ force: true }); + + await User.create({ + id: 1, + userExtraId: 222, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }); + + const users = await User.findAll({ + attributes: ['name'], + include: [ + { + attributes: [ + 'title' ], - logging: sqlSpy - }); - }) - .then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(3); - expect(sqlSpy).to.have.been.calledTwice; - }); + association: User.Tasks, + separate: true + } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy + }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(3); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should not break a nested include with null values', function() { + it('should not break a nested include with null values', async function() { const User = this.sequelize.define('User', {}), Team = this.sequelize.define('Team', {}), Company = this.sequelize.define('Company', {}); @@ -169,18 +161,17 @@ if (current.dialect.supports.groupedLimit) { User.Team = User.belongsTo(Team); Team.Company = Team.belongsTo(Company); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}); - }).then(() => { - return User.findAll({ - include: [ - { association: User.Team, include: [Team.Company] } - ] - }); + await this.sequelize.sync({ force: true }); + await User.create({}); + + await User.findAll({ + include: [ + { association: User.Team, include: [Team.Company] } + ] }); }); - it('should run a hasMany association with limit in a separate query', function() { + it('should run a hasMany association with limit in a separate query', async function() { const User = this.sequelize.define('User', {}), Task = this.sequelize.define('Task', { userId: { @@ -192,50 +183,47 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks', foreignKey: 'userId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - {}, - {}, - {}, - {} - ] - }, { - include: [User.Tasks] - }) - ).then(() => { - return User.findAll({ - include: [ - { association: User.Tasks, limit: 2 } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('tasks')).to.be.ok; - expect(users[0].get('tasks').length).to.equal(2); - expect(users[1].get('tasks')).to.be.ok; - expect(users[1].get('tasks').length).to.equal(2); - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + {}, + {}, + {}, + {} + ] + }, { + include: [User.Tasks] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Tasks, limit: 2 } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('tasks')).to.be.ok; + expect(users[0].get('tasks').length).to.equal(2); + expect(users[1].get('tasks')).to.be.ok; + expect(users[1].get('tasks').length).to.equal(2); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should run a nested (from a non-separate include) hasMany association in a separate query', function() { + it('should run a nested (from a non-separate include) hasMany association in a separate query', async function() { const User = this.sequelize.define('User', {}), Company = this.sequelize.define('Company'), Task = this.sequelize.define('Task', {}), @@ -244,57 +232,54 @@ if (current.dialect.supports.groupedLimit) { User.Company = User.belongsTo(Company, { as: 'company' }); Company.Tasks = Company.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - company: { - tasks: [ - {}, - {}, - {} - ] - } - }, { - include: [ - { association: User.Company, include: [Company.Tasks] } - ] - }), - User.create({ - id: 2, - company: { - tasks: [ - {} - ] - } - }, { - include: [ - { association: User.Company, include: [Company.Tasks] } - ] - }) - ).then(() => { - return User.findAll({ - include: [ - { association: User.Company, include: [ - { association: Company.Tasks, separate: true } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - expect(users[0].get('company').get('tasks')).to.be.ok; - expect(users[0].get('company').get('tasks').length).to.equal(3); - expect(users[1].get('company').get('tasks')).to.be.ok; - expect(users[1].get('company').get('tasks').length).to.equal(1); - expect(sqlSpy).to.have.been.calledTwice; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + company: { + tasks: [ + {}, + {}, + {} + ] + } + }, { + include: [ + { association: User.Company, include: [Company.Tasks] } + ] + }), User.create({ + id: 2, + company: { + tasks: [ + {} + ] + } + }, { + include: [ + { association: User.Company, include: [Company.Tasks] } + ] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Company, include: [ + { association: Company.Tasks, separate: true } + ] } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(users[0].get('company').get('tasks')).to.be.ok; + expect(users[0].get('company').get('tasks').length).to.equal(3); + expect(users[1].get('company').get('tasks')).to.be.ok; + expect(users[1].get('company').get('tasks').length).to.equal(1); + expect(sqlSpy).to.have.been.calledTwice; }); - it('should work having a separate include between a parent and child include', function() { + it('should work having a separate include between a parent and child include', async function() { const User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project'), Company = this.sequelize.define('Company'), @@ -305,51 +290,49 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); Task.Project = Task.belongsTo(Project, { as: 'project' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Company.create({ - id: 1, - users: [ - { - tasks: [ - { project: {} }, - { project: {} }, - { project: {} } - ] - } - ] - }, { - include: [ - { association: Company.Users, include: [ - { association: User.Tasks, include: [ - Task.Project - ] } - ] } - ] - }) - ).then(() => { - return Company.findAll({ - include: [ - { association: Company.Users, include: [ - { association: User.Tasks, separate: true, include: [ - Task.Project - ] } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(companies => { - expect(sqlSpy).to.have.been.calledTwice; + await this.sequelize.sync({ force: true }); - expect(companies[0].users[0].tasks[0].project).to.be.ok; - }); + await Promise.all([Company.create({ + id: 1, + users: [ + { + tasks: [ + { project: {} }, + { project: {} }, + { project: {} } + ] + } + ] + }, { + include: [ + { association: Company.Users, include: [ + { association: User.Tasks, include: [ + Task.Project + ] } + ] } + ] + })]); + + const companies = await Company.findAll({ + include: [ + { association: Company.Users, include: [ + { association: User.Tasks, separate: true, include: [ + Task.Project + ] } + ] } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + expect(sqlSpy).to.have.been.calledTwice; + + expect(companies[0].users[0].tasks[0].project).to.be.ok; }); - it('should run two nested hasMany association in a separate queries', function() { + it('should run two nested hasMany association in a separate queries', async function() { const User = this.sequelize.define('User', {}), Project = this.sequelize.define('Project', {}), Task = this.sequelize.define('Task', {}), @@ -358,82 +341,79 @@ if (current.dialect.supports.groupedLimit) { User.Projects = User.hasMany(Project, { as: 'projects' }); Project.Tasks = Project.hasMany(Task, { as: 'tasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + projects: [ + { id: 1, - projects: [ - { - id: 1, - tasks: [ - {}, - {}, - {} - ] - }, - { - id: 2, - tasks: [ - {} - ] - } - ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } + tasks: [ + {}, + {}, + {} ] - }), - User.create({ + }, + { id: 2, - projects: [ - { - id: 3, - tasks: [ - {}, - {} - ] - } + tasks: [ + {} ] - }, { - include: [ - { association: User.Projects, include: [Project.Tasks] } + } + ] + }, { + include: [ + { association: User.Projects, include: [Project.Tasks] } + ] + }), User.create({ + id: 2, + projects: [ + { + id: 3, + tasks: [ + {}, + {} ] - }) - ).then(() => { - return User.findAll({ - include: [ - { association: User.Projects, separate: true, include: [ - { association: Project.Tasks, separate: true } - ] } - ], - order: [ - ['id', 'ASC'] - ], - logging: sqlSpy - }); - }).then(users => { - const u1projects = users[0].get('projects'); - - expect(u1projects).to.be.ok; - expect(u1projects[0].get('tasks')).to.be.ok; - expect(u1projects[1].get('tasks')).to.be.ok; - expect(u1projects.length).to.equal(2); - - // WTB ES2015 syntax ... - expect(u1projects.find(p => p.id === 1).get('tasks').length).to.equal(3); - expect(u1projects.find(p => p.id === 2).get('tasks').length).to.equal(1); - - expect(users[1].get('projects')).to.be.ok; - expect(users[1].get('projects')[0].get('tasks')).to.be.ok; - expect(users[1].get('projects').length).to.equal(1); - expect(users[1].get('projects')[0].get('tasks').length).to.equal(2); - - expect(sqlSpy).to.have.been.calledThrice; - }); + } + ] + }, { + include: [ + { association: User.Projects, include: [Project.Tasks] } + ] + })]); + + const users = await User.findAll({ + include: [ + { association: User.Projects, separate: true, include: [ + { association: Project.Tasks, separate: true } + ] } + ], + order: [ + ['id', 'ASC'] + ], + logging: sqlSpy }); + + const u1projects = users[0].get('projects'); + + expect(u1projects).to.be.ok; + expect(u1projects[0].get('tasks')).to.be.ok; + expect(u1projects[1].get('tasks')).to.be.ok; + expect(u1projects.length).to.equal(2); + + // WTB ES2015 syntax ... + expect(u1projects.find(p => p.id === 1).get('tasks').length).to.equal(3); + expect(u1projects.find(p => p.id === 2).get('tasks').length).to.equal(1); + + expect(users[1].get('projects')).to.be.ok; + expect(users[1].get('projects')[0].get('tasks')).to.be.ok; + expect(users[1].get('projects').length).to.equal(1); + expect(users[1].get('projects')[0].get('tasks').length).to.equal(2); + + expect(sqlSpy).to.have.been.calledThrice; }); - it('should work with two schema models in a hasMany association', function() { + it('should work with two schema models in a hasMany association', async function() { const User = this.sequelize.define('User', {}, { schema: 'archive' }), Task = this.sequelize.define('Task', { id: { type: DataTypes.INTEGER, primaryKey: true }, @@ -442,57 +422,93 @@ if (current.dialect.supports.groupedLimit) { User.Tasks = User.hasMany(Task, { as: 'tasks' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('archive').then(() => { - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ - id: 1, - tasks: [ - { id: 1, title: 'b' }, - { id: 2, title: 'd' }, - { id: 3, title: 'c' }, - { id: 4, title: 'a' } - ] - }, { - include: [User.Tasks] - }), - User.create({ - id: 2, - tasks: [ - { id: 5, title: 'a' }, - { id: 6, title: 'c' }, - { id: 7, title: 'b' } - ] - }, { - include: [User.Tasks] - }) - ); - }).then(() => { - return User.findAll({ - include: [{ model: Task, limit: 2, as: 'tasks', order: [['id', 'ASC']] }], - order: [ - ['id', 'ASC'] - ] - }).then(result => { - expect(result[0].tasks.length).to.equal(2); - expect(result[0].tasks[0].title).to.equal('b'); - expect(result[0].tasks[1].title).to.equal('d'); - - expect(result[1].tasks.length).to.equal(2); - expect(result[1].tasks[0].title).to.equal('a'); - expect(result[1].tasks[1].title).to.equal('c'); - return this.sequelize.dropSchema('archive').then(() => { - return this.sequelize.showAllSchemas().then(schemas => { - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - expect(schemas).to.not.have.property('archive'); - } - }); - }); - }); - }); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('archive'); + await this.sequelize.sync({ force: true }); + + await Promise.all([User.create({ + id: 1, + tasks: [ + { id: 1, title: 'b' }, + { id: 2, title: 'd' }, + { id: 3, title: 'c' }, + { id: 4, title: 'a' } + ] + }, { + include: [User.Tasks] + }), User.create({ + id: 2, + tasks: [ + { id: 5, title: 'a' }, + { id: 6, title: 'c' }, + { id: 7, title: 'b' } + ] + }, { + include: [User.Tasks] + })]); + + const result = await User.findAll({ + include: [{ model: Task, limit: 2, as: 'tasks', order: [['id', 'ASC']] }], + order: [ + ['id', 'ASC'] + ] + }); + + expect(result[0].tasks.length).to.equal(2); + expect(result[0].tasks[0].title).to.equal('b'); + expect(result[0].tasks[1].title).to.equal('d'); + + expect(result[1].tasks.length).to.equal(2); + expect(result[1].tasks[0].title).to.equal('a'); + expect(result[1].tasks[1].title).to.equal('c'); + await this.sequelize.dropSchema('archive'); + const schemas = await this.sequelize.showAllSchemas(); + if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { + expect(schemas).to.not.have.property('archive'); + } + }); + + it('should work with required non-separate parent and required child', async function() { + const User = this.sequelize.define('User', {}); + const Task = this.sequelize.define('Task', {}); + const Company = this.sequelize.define('Company', {}); + + Task.User = Task.belongsTo(User); + User.Tasks = User.hasMany(Task); + User.Company = User.belongsTo(Company); + + await this.sequelize.sync({ force: true }); + + const task = await Task.create({ id: 1 }); + const user = await task.createUser({ id: 2 }); + await user.createCompany({ id: 3 }); + + const results = await Task.findAll({ + include: [{ + association: Task.User, + required: true, + include: [{ + association: User.Tasks, + attributes: ['UserId'], + separate: true, + include: [{ + association: Task.User, + attributes: ['id'], + required: true, + include: [{ + association: User.Company + }] + }] + }] + }] }); + + expect(results.length).to.equal(1); + expect(results[0].id).to.equal(1); + expect(results[0].User.id).to.equal(2); + expect(results[0].User.Tasks.length).to.equal(1); + expect(results[0].User.Tasks[0].User.id).to.equal(2); + expect(results[0].User.Tasks[0].User.Company.id).to.equal(3); }); }); }); diff --git a/test/integration/instance.test.js b/test/integration/instance.test.js index 854e117587be..d7d16c7dba4b 100644 --- a/test/integration/instance.test.js +++ b/test/integration/instance.test.js @@ -21,7 +21,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -53,65 +53,62 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('Escaping', () => { - it('is done properly for special characters', function() { + it('is done properly for special characters', async function() { // Ideally we should test more: "\0\n\r\b\t\\\'\"\x1a" // But this causes sqlite to fail and exits the entire test suite immediately const bio = `${dialect}'"\n`; // Need to add the dialect here so in case of failure I know what DB it failed for - return this.User.create({ username: bio }).then(u1 => { - return this.User.findByPk(u1.id).then(u2 => { - expect(u2.username).to.equal(bio); - }); - }); + const u1 = await this.User.create({ username: bio }); + const u2 = await this.User.findByPk(u1.id); + expect(u2.username).to.equal(bio); }); }); describe('isNewRecord', () => { it('returns true for non-saved objects', function() { - const user = new this.User({ username: 'user' }); + const user = this.User.build({ username: 'user' }); expect(user.id).to.be.null; expect(user.isNewRecord).to.be.ok; }); - it('returns false for saved objects', function() { - return new this.User({ username: 'user' }).save().then(user => { - expect(user.isNewRecord).to.not.be.ok; - }); + it('returns false for saved objects', async function() { + const user = await this.User.build({ username: 'user' }).save(); + expect(user.isNewRecord).to.not.be.ok; }); - it('returns false for created objects', function() { - return this.User.create({ username: 'user' }).then(user => { - expect(user.isNewRecord).to.not.be.ok; - }); + it('returns false for created objects', async function() { + const user = await this.User.create({ username: 'user' }); + expect(user.isNewRecord).to.not.be.ok; }); - it('returns false for objects found by find method', function() { - return this.User.create({ username: 'user' }).then(() => { - return this.User.create({ username: 'user' }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.isNewRecord).to.not.be.ok; - }); - }); - }); + it('returns false for upserted objects', async function() { + // adding id here so MSSQL doesn't fail. It needs a primary key to upsert + const [user] = await this.User.upsert({ id: 2, username: 'user' }); + expect(user.isNewRecord).to.not.be.ok; + }); + + it('returns false for objects found by find method', async function() { + await this.User.create({ username: 'user' }); + const user = await this.User.create({ username: 'user' }); + const user0 = await this.User.findByPk(user.id); + expect(user0.isNewRecord).to.not.be.ok; }); - it('returns false for objects found by findAll method', function() { + it('returns false for objects found by findAll method', async function() { const users = []; for (let i = 0; i < 10; i++) { users[i] = { username: 'user' }; } - return this.User.bulkCreate(users).then(() => { - return this.User.findAll().then(users => { - users.forEach(u => { - expect(u.isNewRecord).to.not.be.ok; - }); - }); + await this.User.bulkCreate(users); + const users0 = await this.User.findAll(); + users0.forEach(u => { + expect(u.isNewRecord).to.not.be.ok; }); }); }); @@ -119,19 +116,19 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('default values', () => { describe('uuid', () => { it('should store a string in uuidv1 and uuidv4', function() { - const user = new this.User({ username: 'a user' }); + const user = this.User.build({ username: 'a user' }); expect(user.uuidv1).to.be.a('string'); expect(user.uuidv4).to.be.a('string'); }); it('should store a string of length 36 in uuidv1 and uuidv4', function() { - const user = new this.User({ username: 'a user' }); + const user = this.User.build({ username: 'a user' }); expect(user.uuidv1).to.have.length(36); expect(user.uuidv4).to.have.length(36); }); it('should store a valid uuid in uuidv1 and uuidv4 that conforms to the UUID v1 and v4 specifications', function() { - const user = new this.User({ username: 'a user' }); + const user = this.User.build({ username: 'a user' }); expect(isUUID(user.uuidv1)).to.be.true; expect(isUUID(user.uuidv4, 4)).to.be.true; }); @@ -150,7 +147,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - const person = new Person({}); + const person = Person.build({}); expect(person.id1).to.be.ok; expect(person.id1).to.have.length(36); @@ -160,274 +157,239 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('current date', () => { it('should store a date in touchedAt', function() { - const user = new this.User({ username: 'a user' }); + const user = this.User.build({ username: 'a user' }); expect(user.touchedAt).to.be.instanceof(Date); }); it('should store the current date in touchedAt', function() { const clock = sinon.useFakeTimers(); clock.tick(5000); - const user = new this.User({ username: 'a user' }); + const user = this.User.build({ username: 'a user' }); clock.restore(); expect(+user.touchedAt).to.be.equal(5000); }); }); describe('allowNull date', () => { - it('should be just "null" and not Date with Invalid Date', function() { - return new this.User({ username: 'a user' }).save().then(() => { - return this.User.findOne({ where: { username: 'a user' } }).then(user => { - expect(user.dateAllowNullTrue).to.be.null; - }); - }); + it('should be just "null" and not Date with Invalid Date', async function() { + await this.User.build({ username: 'a user' }).save(); + const user = await this.User.findOne({ where: { username: 'a user' } }); + expect(user.dateAllowNullTrue).to.be.null; }); - it('should be the same valid date when saving the date', function() { + it('should be the same valid date when saving the date', async function() { const date = new Date(); - return new this.User({ username: 'a user', dateAllowNullTrue: date }).save().then(() => { - return this.User.findOne({ where: { username: 'a user' } }).then(user => { - expect(user.dateAllowNullTrue.toString()).to.equal(date.toString()); - }); - }); + await this.User.build({ username: 'a user', dateAllowNullTrue: date }).save(); + const user = await this.User.findOne({ where: { username: 'a user' } }); + expect(user.dateAllowNullTrue.toString()).to.equal(date.toString()); }); }); describe('super user boolean', () => { - it('should default to false', function() { - return new this.User({ + it('should default to false', async function() { + await this.User.build({ username: 'a user' }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.false; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.false; }); - it('should override default when given truthy boolean', function() { - return new this.User({ + it('should override default when given truthy boolean', async function() { + await this.User.build({ username: 'a user', isSuperUser: true }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.true; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.true; }); - it('should override default when given truthy boolean-string ("true")', function() { - return new this.User({ + it('should override default when given truthy boolean-string ("true")', async function() { + await this.User.build({ username: 'a user', isSuperUser: 'true' }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.true; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.true; }); - it('should override default when given truthy boolean-int (1)', function() { - return new this.User({ + it('should override default when given truthy boolean-int (1)', async function() { + await this.User.build({ username: 'a user', isSuperUser: 1 }) - .save() - .then(() => { - return this.User.findOne({ - where: { - username: 'a user' - } - }) - .then(user => { - expect(user.isSuperUser).to.be.true; - }); - }); + .save(); + + const user = await this.User.findOne({ + where: { + username: 'a user' + } + }); + + expect(user.isSuperUser).to.be.true; }); - it('should throw error when given value of incorrect type', function() { + it('should throw error when given value of incorrect type', async function() { let callCount = 0; - return new this.User({ - username: 'a user', - isSuperUser: 'INCORRECT_VALUE_TYPE' - }) - .save() - .then(() => { - callCount += 1; + try { + await this.User.build({ + username: 'a user', + isSuperUser: 'INCORRECT_VALUE_TYPE' }) - .catch(err => { - expect(callCount).to.equal(0); - expect(err).to.exist; - expect(err.message).to.exist; - }); + .save(); + + callCount += 1; + } catch (err) { + expect(callCount).to.equal(0); + expect(err).to.exist; + expect(err.message).to.exist; + } }); }); }); describe('complete', () => { - it('gets triggered if an error occurs', function() { - return this.User.findOne({ where: ['asdasdasd'] }).catch(err => { + it('gets triggered if an error occurs', async function() { + try { + await this.User.findOne({ where: ['asdasdasd'] }); + } catch (err) { expect(err).to.exist; expect(err.message).to.exist; - }); + } }); - it('gets triggered if everything was ok', function() { - return this.User.count().then(result => { - expect(result).to.exist; - }); + it('gets triggered if everything was ok', async function() { + const result = await this.User.count(); + expect(result).to.exist; }); }); describe('findAll', () => { - beforeEach(function() { + beforeEach(async function() { this.ParanoidUser = this.sequelize.define('ParanoidUser', { username: { type: DataTypes.STRING } }, { paranoid: true }); this.ParanoidUser.hasOne(this.ParanoidUser); - return this.ParanoidUser.sync({ force: true }); + await this.ParanoidUser.sync({ force: true }); }); - it('sql should have paranoid condition', function() { - return this.ParanoidUser.create({ username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll(); - }) - .then(users => { - expect(users).to.have.length(1); - return users[0].destroy(); - }) - .then(() => { - return this.ParanoidUser.findAll(); - }) - .then(users => { - expect(users).to.have.length(0); - }); + it('sql should have paranoid condition', async function() { + await this.ParanoidUser.create({ username: 'cuss' }); + const users0 = await this.ParanoidUser.findAll(); + expect(users0).to.have.length(1); + await users0[0].destroy(); + const users = await this.ParanoidUser.findAll(); + expect(users).to.have.length(0); }); - it('sequelize.and as where should include paranoid condition', function() { - return this.ParanoidUser.create({ username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.and({ - username: 'cuss' - }) - }); - }) - .then(users => { - expect(users).to.have.length(1); - return users[0].destroy(); + it('sequelize.and as where should include paranoid condition', async function() { + await this.ParanoidUser.create({ username: 'cuss' }); + + const users0 = await this.ParanoidUser.findAll({ + where: this.sequelize.and({ + username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.and({ - username: 'cuss' - }) - }); + }); + + expect(users0).to.have.length(1); + await users0[0].destroy(); + + const users = await this.ParanoidUser.findAll({ + where: this.sequelize.and({ + username: 'cuss' }) - .then(users => { - expect(users).to.have.length(0); - }); + }); + + expect(users).to.have.length(0); }); - it('sequelize.or as where should include paranoid condition', function() { - return this.ParanoidUser.create({ username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.or({ - username: 'cuss' - }) - }); - }) - .then(users => { - expect(users).to.have.length(1); - return users[0].destroy(); + it('sequelize.or as where should include paranoid condition', async function() { + await this.ParanoidUser.create({ username: 'cuss' }); + + const users0 = await this.ParanoidUser.findAll({ + where: this.sequelize.or({ + username: 'cuss' }) - .then(() => { - return this.ParanoidUser.findAll({ - where: this.sequelize.or({ - username: 'cuss' - }) - }); + }); + + expect(users0).to.have.length(1); + await users0[0].destroy(); + + const users = await this.ParanoidUser.findAll({ + where: this.sequelize.or({ + username: 'cuss' }) - .then(users => { - expect(users).to.have.length(0); - }); - }); + }); - it('escapes a single single quotes properly in where clauses', function() { - return this.User - .create({ username: "user'name" }) - .then(() => { - return this.User.findAll({ - where: { username: "user'name" } - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal("user'name"); - }); - }); + expect(users).to.have.length(0); }); - it('escapes two single quotes properly in where clauses', function() { - return this.User - .create({ username: "user''name" }) - .then(() => { - return this.User.findAll({ - where: { username: "user''name" } - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal("user''name"); - }); - }); - }); + it('escapes a single single quotes properly in where clauses', async function() { + await this.User + .create({ username: "user'name" }); - it('returns the timestamps if no attributes have been specified', function() { - return this.User.create({ username: 'fnord' }).then(() => { - return this.User.findAll().then(users => { - expect(users[0].createdAt).to.exist; - }); + const users = await this.User.findAll({ + where: { username: "user'name" } }); + + expect(users.length).to.equal(1); + expect(users[0].username).to.equal("user'name"); }); - it('does not return the timestamps if the username attribute has been specified', function() { - return this.User.create({ username: 'fnord' }).then(() => { - return this.User.findAll({ attributes: ['username'] }).then(users => { - expect(users[0].createdAt).not.to.exist; - expect(users[0].username).to.exist; - }); + it('escapes two single quotes properly in where clauses', async function() { + await this.User + .create({ username: "user''name" }); + + const users = await this.User.findAll({ + where: { username: "user''name" } }); + + expect(users.length).to.equal(1); + expect(users[0].username).to.equal("user''name"); }); - it('creates the deletedAt property, when defining paranoid as true', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - expect(users[0].deletedAt).to.be.null; - }); - }); + it('returns the timestamps if no attributes have been specified', async function() { + await this.User.create({ username: 'fnord' }); + const users = await this.User.findAll(); + expect(users[0].createdAt).to.exist; + }); + + it('does not return the timestamps if the username attribute has been specified', async function() { + await this.User.create({ username: 'fnord' }); + const users = await this.User.findAll({ attributes: ['username'] }); + expect(users[0].createdAt).not.to.exist; + expect(users[0].username).to.exist; }); - it('destroys a record with a primary key of something other than id', function() { + it('creates the deletedAt property, when defining paranoid as true', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + expect(users[0].deletedAt).to.be.null; + }); + + it('destroys a record with a primary key of something other than id', async function() { const UserDestroy = this.sequelize.define('UserDestroy', { newId: { type: DataTypes.STRING, @@ -436,77 +398,58 @@ describe(Support.getTestDialectTeaser('Instance'), () => { email: DataTypes.STRING }); - return UserDestroy.sync().then(() => { - return UserDestroy.create({ newId: '123ABC', email: 'hello' }).then(() => { - return UserDestroy.findOne({ where: { email: 'hello' } }).then(user => { - return user.destroy(); - }); - }); - }); + await UserDestroy.sync(); + await UserDestroy.create({ newId: '123ABC', email: 'hello' }); + const user = await UserDestroy.findOne({ where: { email: 'hello' } }); + + await user.destroy(); }); - it('sets deletedAt property to a specific date when deleting an instance', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return users[0].destroy().then(() => { - expect(users[0].deletedAt.getMonth).to.exist; + it('sets deletedAt property to a specific date when deleting an instance', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + await users[0].destroy(); + expect(users[0].deletedAt.getMonth).to.exist; - return users[0].reload({ paranoid: false }).then(user => { - expect(user.deletedAt.getMonth).to.exist; - }); - }); - }); - }); + const user = await users[0].reload({ paranoid: false }); + expect(user.deletedAt.getMonth).to.exist; }); - it('keeps the deletedAt-attribute with value null, when running update', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return users[0].update({ username: 'newFnord' }).then(user => { - expect(user.deletedAt).not.to.exist; - }); - }); - }); + it('keeps the deletedAt-attribute with value null, when running update', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + const user = await users[0].update({ username: 'newFnord' }); + expect(user.deletedAt).not.to.exist; }); - it('keeps the deletedAt-attribute with value null, when updating associations', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return this.ParanoidUser.create({ username: 'linkedFnord' }).then(linkedUser => { - return users[0].setParanoidUser(linkedUser).then(user => { - expect(user.deletedAt).not.to.exist; - }); - }); - }); - }); + it('keeps the deletedAt-attribute with value null, when updating associations', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + const linkedUser = await this.ParanoidUser.create({ username: 'linkedFnord' }); + const user = await users[0].setParanoidUser(linkedUser); + expect(user.deletedAt).not.to.exist; }); - it('can reuse query option objects', function() { - return this.User.create({ username: 'fnord' }).then(() => { - const query = { where: { username: 'fnord' } }; - return this.User.findAll(query).then(users => { - expect(users[0].username).to.equal('fnord'); - return this.User.findAll(query).then(users => { - expect(users[0].username).to.equal('fnord'); - }); - }); - }); + it('can reuse query option objects', async function() { + await this.User.create({ username: 'fnord' }); + const query = { where: { username: 'fnord' } }; + const users = await this.User.findAll(query); + expect(users[0].username).to.equal('fnord'); + const users0 = await this.User.findAll(query); + expect(users0[0].username).to.equal('fnord'); }); }); describe('findOne', () => { - it('can reuse query option objects', function() { - return this.User.create({ username: 'fnord' }).then(() => { - const query = { where: { username: 'fnord' } }; - return this.User.findOne(query).then(user => { - expect(user.username).to.equal('fnord'); - return this.User.findOne(query).then(user => { - expect(user.username).to.equal('fnord'); - }); - }); - }); - }); - it('returns null for null, undefined, and unset boolean values', function() { + it('can reuse query option objects', async function() { + await this.User.create({ username: 'fnord' }); + const query = { where: { username: 'fnord' } }; + const user = await this.User.findOne(query); + expect(user.username).to.equal('fnord'); + const user0 = await this.User.findOne(query); + expect(user0.username).to.equal('fnord'); + }); + it('returns null for null, undefined, and unset boolean values', async function() { const Setting = this.sequelize.define('SettingHelper', { setting_key: DataTypes.STRING, bool_value: { type: DataTypes.BOOLEAN, allowNull: true }, @@ -514,28 +457,23 @@ describe(Support.getTestDialectTeaser('Instance'), () => { bool_value3: { type: DataTypes.BOOLEAN, allowNull: true } }, { timestamps: false, logging: false }); - return Setting.sync({ force: true }).then(() => { - return Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined }).then(() => { - return Setting.findOne({ where: { setting_key: 'test' } }).then(setting => { - expect(setting.bool_value).to.equal(null); - expect(setting.bool_value2).to.equal(null); - expect(setting.bool_value3).to.equal(null); - }); - }); - }); + await Setting.sync({ force: true }); + await Setting.create({ setting_key: 'test', bool_value: null, bool_value2: undefined }); + const setting = await Setting.findOne({ where: { setting_key: 'test' } }); + expect(setting.bool_value).to.equal(null); + expect(setting.bool_value2).to.equal(null); + expect(setting.bool_value3).to.equal(null); }); }); describe('equals', () => { - it('can compare records with Date field', function() { - return this.User.create({ username: 'fnord' }).then(user1 => { - return this.User.findOne({ where: { username: 'fnord' } }).then(user2 => { - expect(user1.equals(user2)).to.be.true; - }); - }); + it('can compare records with Date field', async function() { + const user1 = await this.User.create({ username: 'fnord' }); + const user2 = await this.User.findOne({ where: { username: 'fnord' } }); + expect(user1.equals(user2)).to.be.true; }); - it('does not compare the existence of associations', function() { + it('does not compare the existence of associations', async function() { this.UserAssociationEqual = this.sequelize.define('UserAssociationEquals', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -549,80 +487,65 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.UserAssociationEqual.hasMany(this.ProjectAssociationEqual, { as: 'Projects', foreignKey: 'userId' }); this.ProjectAssociationEqual.belongsTo(this.UserAssociationEqual, { as: 'Users', foreignKey: 'userId' }); - return this.UserAssociationEqual.sync({ force: true }).then(() => { - return this.ProjectAssociationEqual.sync({ force: true }).then(() => { - return this.UserAssociationEqual.create({ username: 'jimhalpert' }).then(user1 => { - return this.ProjectAssociationEqual.create({ title: 'A Cool Project' }).then(project1 => { - return user1.setProjects([project1]).then(() => { - return this.UserAssociationEqual.findOne({ where: { username: 'jimhalpert' }, include: [{ model: this.ProjectAssociationEqual, as: 'Projects' }] }).then(user2 => { - return this.UserAssociationEqual.create({ username: 'pambeesly' }).then(user3 => { - expect(user1.get('Projects')).to.not.exist; - expect(user2.get('Projects')).to.exist; - expect(user1.equals(user2)).to.be.true; - expect(user2.equals(user1)).to.be.true; - expect(user1.equals(user3)).to.not.be.true; - expect(user3.equals(user1)).to.not.be.true; - }); - }); - }); - }); - }); - }); - }); + await this.UserAssociationEqual.sync({ force: true }); + await this.ProjectAssociationEqual.sync({ force: true }); + const user1 = await this.UserAssociationEqual.create({ username: 'jimhalpert' }); + const project1 = await this.ProjectAssociationEqual.create({ title: 'A Cool Project' }); + await user1.setProjects([project1]); + const user2 = await this.UserAssociationEqual.findOne({ where: { username: 'jimhalpert' }, include: [{ model: this.ProjectAssociationEqual, as: 'Projects' }] }); + const user3 = await this.UserAssociationEqual.create({ username: 'pambeesly' }); + expect(user1.get('Projects')).to.not.exist; + expect(user2.get('Projects')).to.exist; + expect(user1.equals(user2)).to.be.true; + expect(user2.equals(user1)).to.be.true; + expect(user1.equals(user3)).to.not.be.true; + expect(user3.equals(user1)).to.not.be.true; }); }); describe('values', () => { - it('returns all values', function() { + it('returns all values', async function() { const User = this.sequelize.define('UserHelper', { username: DataTypes.STRING }, { timestamps: false, logging: false }); - return User.sync().then(() => { - const user = new User({ username: 'foo' }); - expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); - }); + await User.sync(); + const user = User.build({ username: 'foo' }); + expect(user.get({ plain: true })).to.deep.equal({ username: 'foo', id: null }); }); }); describe('isSoftDeleted', () => { - beforeEach(function() { + beforeEach(async function() { this.ParanoidUser = this.sequelize.define('ParanoidUser', { username: { type: DataTypes.STRING } }, { paranoid: true }); - return this.ParanoidUser.sync({ force: true }); + await this.ParanoidUser.sync({ force: true }); }); - it('should return false when model is just created', function() { - return this.ParanoidUser.create({ username: 'foo' }).then(user => { - expect(user.isSoftDeleted()).to.be.false; - }); + it('should return false when model is just created', async function() { + const user = await this.ParanoidUser.create({ username: 'foo' }); + expect(user.isSoftDeleted()).to.be.false; }); - it('returns false if user is not soft deleted', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - expect(users[0].isSoftDeleted()).to.be.false; - }); - }); + it('returns false if user is not soft deleted', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + expect(users[0].isSoftDeleted()).to.be.false; }); - it('returns true if user is soft deleted', function() { - return this.ParanoidUser.create({ username: 'fnord' }).then(() => { - return this.ParanoidUser.findAll().then(users => { - return users[0].destroy().then(() => { - expect(users[0].isSoftDeleted()).to.be.true; + it('returns true if user is soft deleted', async function() { + await this.ParanoidUser.create({ username: 'fnord' }); + const users = await this.ParanoidUser.findAll(); + await users[0].destroy(); + expect(users[0].isSoftDeleted()).to.be.true; - return users[0].reload({ paranoid: false }).then(user => { - expect(user.isSoftDeleted()).to.be.true; - }); - }); - }); - }); + const user = await users[0].reload({ paranoid: false }); + expect(user.isSoftDeleted()).to.be.true; }); - it('works with custom `deletedAt` field name', function() { + it('works with custom `deletedAt` field name', async function() { this.ParanoidUserWithCustomDeletedAt = this.sequelize.define('ParanoidUserWithCustomDeletedAt', { username: { type: DataTypes.STRING } }, { @@ -632,32 +555,26 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.ParanoidUserWithCustomDeletedAt.hasOne(this.ParanoidUser); - return this.ParanoidUserWithCustomDeletedAt.sync({ force: true }).then(() => { - return this.ParanoidUserWithCustomDeletedAt.create({ username: 'fnord' }).then(() => { - return this.ParanoidUserWithCustomDeletedAt.findAll().then(users => { - expect(users[0].isSoftDeleted()).to.be.false; + await this.ParanoidUserWithCustomDeletedAt.sync({ force: true }); + await this.ParanoidUserWithCustomDeletedAt.create({ username: 'fnord' }); + const users = await this.ParanoidUserWithCustomDeletedAt.findAll(); + expect(users[0].isSoftDeleted()).to.be.false; - return users[0].destroy().then(() => { - expect(users[0].isSoftDeleted()).to.be.true; + await users[0].destroy(); + expect(users[0].isSoftDeleted()).to.be.true; - return users[0].reload({ paranoid: false }).then(user => { - expect(user.isSoftDeleted()).to.be.true; - }); - }); - }); - }); - }); + const user = await users[0].reload({ paranoid: false }); + expect(user.isSoftDeleted()).to.be.true; }); }); describe('restore', () => { - it('returns an error if the model is not paranoid', function() { - return this.User.create({ username: 'Peter', secretValue: '42' }).then(user => { - expect(() => {user.restore();}).to.throw(Error, 'Model is not paranoid'); - }); + it('returns an error if the model is not paranoid', async function() { + const user = await this.User.create({ username: 'Peter', secretValue: '42' }); + await expect(user.restore()).to.be.rejectedWith(Error, 'Model is not paranoid'); }); - it('restores a previously deleted model', function() { + it('restores a previously deleted model', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -670,124 +587,106 @@ describe(Support.getTestDialectTeaser('Instance'), () => { { username: 'Paul', secretValue: '43' }, { username: 'Bob', secretValue: '44' }]; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate(data); - }).then(() => { - return ParanoidUser.findOne({ where: { secretValue: '42' } }); - }).then(user => { - return user.destroy().then(() => { - return user.restore(); - }); - }).then(() => { - return ParanoidUser.findOne({ where: { secretValue: '42' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.username).to.equal('Peter'); - }); + await ParanoidUser.sync({ force: true }); + await ParanoidUser.bulkCreate(data); + const user0 = await ParanoidUser.findOne({ where: { secretValue: '42' } }); + await user0.destroy(); + await user0.restore(); + const user = await ParanoidUser.findOne({ where: { secretValue: '42' } }); + expect(user).to.be.ok; + expect(user.username).to.equal('Peter'); }); - it('supports custom deletedAt field', function() { + it('supports custom deletedAt field', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, destroyTime: DataTypes.DATE }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - return user.restore(); - }).then(user => { - expect(user.destroyTime).to.not.be.ok; - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.not.be.ok; - expect(user.deletedAt).to.not.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + expect(user1.destroyTime).to.be.ok; + expect(user1.deletedAt).to.not.be.ok; + const user0 = await user1.restore(); + expect(user0.destroyTime).to.not.be.ok; + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.destroyTime).to.not.be.ok; + expect(user.deletedAt).to.not.be.ok; }); - it('supports custom deletedAt field name', function() { + it('supports custom deletedAt field name', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, deletedAt: { type: DataTypes.DATE, field: 'deleted_at' } }, { paranoid: true }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.deletedAt).to.be.ok; - expect(user.dataValues.deleted_at).to.not.be.ok; - return user.restore(); - }).then(user => { - expect(user.dataValues.deletedAt).to.not.be.ok; - expect(user.dataValues.deleted_at).to.not.be.ok; - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - expect(user.deleted_at).to.not.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + expect(user1.dataValues.deletedAt).to.be.ok; + expect(user1.dataValues.deleted_at).to.not.be.ok; + const user0 = await user1.restore(); + expect(user0.dataValues.deletedAt).to.not.be.ok; + expect(user0.dataValues.deleted_at).to.not.be.ok; + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.deletedAt).to.not.be.ok; + expect(user.deleted_at).to.not.be.ok; }); - it('supports custom deletedAt field and database column', function() { + it('supports custom deletedAt field and database column', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, destroyTime: { type: DataTypes.DATE, field: 'destroy_time' } }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.destroyTime).to.be.ok; - expect(user.dataValues.deletedAt).to.not.be.ok; - expect(user.dataValues.destroy_time).to.not.be.ok; - return user.restore(); - }).then(user => { - expect(user.dataValues.destroyTime).to.not.be.ok; - expect(user.dataValues.destroy_time).to.not.be.ok; - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.not.be.ok; - expect(user.destroy_time).to.not.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + expect(user1.dataValues.destroyTime).to.be.ok; + expect(user1.dataValues.deletedAt).to.not.be.ok; + expect(user1.dataValues.destroy_time).to.not.be.ok; + const user0 = await user1.restore(); + expect(user0.dataValues.destroyTime).to.not.be.ok; + expect(user0.dataValues.destroy_time).to.not.be.ok; + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.destroyTime).to.not.be.ok; + expect(user.destroy_time).to.not.be.ok; }); - it('supports custom default value', function() { + it('supports custom default value', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING, deletedAt: { type: DataTypes.DATE, defaultValue: new Date(0) } }, { paranoid: true }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - return user.restore(); - }).then(user => { - expect(user.dataValues.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); - return ParanoidUser.findOne({ where: { username: 'username' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' }); + + const user1 = await user2.destroy(); + const user0 = await user1.restore(); + expect(user0.dataValues.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); + const user = await ParanoidUser.findOne({ where: { username: 'username' } }); + expect(user).to.be.ok; + expect(user.deletedAt.toISOString()).to.equal(new Date(0).toISOString()); }); }); }); diff --git a/test/integration/instance.validations.test.js b/test/integration/instance.validations.test.js index 9a6b451cadf7..69cf060739eb 100644 --- a/test/integration/instance.validations.test.js +++ b/test/integration/instance.validations.test.js @@ -3,12 +3,11 @@ const chai = require('chai'), expect = chai.expect, Sequelize = require('../../index'), - Support = require('./support'), - config = require('../config/config'); + Support = require('./support'); describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('#update', () => { - it('should allow us to update specific columns without tripping the validations', function() { + it('should allow us to update specific columns without tripping the validations', async function() { const User = this.sequelize.define('model', { username: Sequelize.STRING, email: { @@ -22,20 +21,17 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'bob', email: 'hello@world.com' }).then(user => { - return User - .update({ username: 'toni' }, { where: { id: user.id } }) - .then(() => { - return User.findByPk(1).then(user => { - expect(user.username).to.equal('toni'); - }); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ username: 'bob', email: 'hello@world.com' }); + + await User + .update({ username: 'toni' }, { where: { id: user.id } }); + + const user0 = await User.findByPk(1); + expect(user0.username).to.equal('toni'); }); - it('should be able to emit an error upon updating when a validation has failed from an instance', function() { + it('should be able to emit an error upon updating when a validation has failed from an instance', async function() { const Model = this.sequelize.define('model', { name: { type: Sequelize.STRING, @@ -46,17 +42,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Model.sync({ force: true }).then(() => { - return Model.create({ name: 'World' }).then(model => { - return model.update({ name: '' }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); - }); - }); - }); + await Model.sync({ force: true }); + const model = await Model.create({ name: 'World' }); + + try { + await model.update({ name: '' }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); + } }); - it('should be able to emit an error upon updating when a validation has failed from the factory', function() { + it('should be able to emit an error upon updating when a validation has failed from the factory', async function() { const Model = this.sequelize.define('model', { name: { type: Sequelize.STRING, @@ -67,17 +64,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Model.sync({ force: true }).then(() => { - return Model.create({ name: 'World' }).then(() => { - return Model.update({ name: '' }, { where: { id: 1 } }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); - }); - }); - }); + await Model.sync({ force: true }); + await Model.create({ name: 'World' }); + + try { + await Model.update({ name: '' }, { where: { id: 1 } }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('name')[0].message).to.equal('Validation notEmpty on name failed'); + } }); - it('should enforce a unique constraint', function() { + it('should enforce a unique constraint', async function() { const Model = this.sequelize.define('model', { uniqueName: { type: Sequelize.STRING, unique: 'uniqueName' } }); @@ -85,24 +83,19 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { { uniqueName: 'unique name one' }, { uniqueName: 'unique name two' } ]; - return Model.sync({ force: true }) - .then(() => { - return Model.create(records[0]); - }).then(instance => { - expect(instance).to.be.ok; - return Model.create(records[1]); - }).then(instance => { - expect(instance).to.be.ok; - return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName'); - expect(err.errors[0].message).to.include('must be unique'); - }); - }); - - it('should allow a custom unique constraint error message', function() { + await Model.sync({ force: true }); + const instance0 = await Model.create(records[0]); + expect(instance0).to.be.ok; + const instance = await Model.create(records[1]); + expect(instance).to.be.ok; + const err = await expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; + expect(err).to.be.an.instanceOf(Error); + expect(err.errors).to.have.length(1); + expect(err.errors[0].path).to.include('uniqueName'); + expect(err.errors[0].message).to.include('must be unique'); + }); + + it('should allow a custom unique constraint error message', async function() { const Model = this.sequelize.define('model', { uniqueName: { type: Sequelize.STRING, @@ -113,24 +106,19 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { { uniqueName: 'unique name one' }, { uniqueName: 'unique name two' } ]; - return Model.sync({ force: true }) - .then(() => { - return Model.create(records[0]); - }).then(instance => { - expect(instance).to.be.ok; - return Model.create(records[1]); - }).then(instance => { - expect(instance).to.be.ok; - return expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName'); - expect(err.errors[0].message).to.equal('custom unique error message'); - }); - }); - - it('should handle multiple unique messages correctly', function() { + await Model.sync({ force: true }); + const instance0 = await Model.create(records[0]); + expect(instance0).to.be.ok; + const instance = await Model.create(records[1]); + expect(instance).to.be.ok; + const err = await expect(Model.update(records[0], { where: { id: instance.id } })).to.be.rejected; + expect(err).to.be.an.instanceOf(Error); + expect(err.errors).to.have.length(1); + expect(err.errors[0].path).to.include('uniqueName'); + expect(err.errors[0].message).to.equal('custom unique error message'); + }); + + it('should handle multiple unique messages correctly', async function() { const Model = this.sequelize.define('model', { uniqueName1: { type: Sequelize.STRING, @@ -146,31 +134,26 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { { uniqueName1: 'unique name one', uniqueName2: 'this is ok' }, { uniqueName1: 'this is ok', uniqueName2: 'unique name one' } ]; - return Model.sync({ force: true }) - .then(() => { - return Model.create(records[0]); - }).then(instance => { - expect(instance).to.be.ok; - return expect(Model.create(records[1])).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName1'); - expect(err.errors[0].message).to.equal('custom unique error message 1'); - - return expect(Model.create(records[2])).to.be.rejected; - }).then(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.errors).to.have.length(1); - expect(err.errors[0].path).to.include('uniqueName2'); - expect(err.errors[0].message).to.equal('custom unique error message 2'); - }); + await Model.sync({ force: true }); + const instance = await Model.create(records[0]); + expect(instance).to.be.ok; + const err0 = await expect(Model.create(records[1])).to.be.rejected; + expect(err0).to.be.an.instanceOf(Error); + expect(err0.errors).to.have.length(1); + expect(err0.errors[0].path).to.include('uniqueName1'); + expect(err0.errors[0].message).to.equal('custom unique error message 1'); + + const err = await expect(Model.create(records[2])).to.be.rejected; + expect(err).to.be.an.instanceOf(Error); + expect(err.errors).to.have.length(1); + expect(err.errors[0].path).to.include('uniqueName2'); + expect(err.errors[0].message).to.equal('custom unique error message 2'); }); }); describe('#create', () => { describe('generic', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: { type: Sequelize.STRING, @@ -189,34 +172,31 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { Project.hasOne(Task); Task.belongsTo(Project); - return this.sequelize.sync({ force: true }).then(() => { - this.Project = Project; - this.Task = Task; - }); + await this.sequelize.sync({ force: true }); + this.Project = Project; + this.Task = Task; }); - it('correctly throws an error using create method ', function() { - return this.Project.create({ name: 'nope' }).catch(err => { + it('correctly throws an error using create method ', async function() { + try { + await this.Project.create({ name: 'nope' }); + } catch (err) { expect(err).to.have.ownProperty('name'); - }); + } }); - it('correctly validates using create method ', function() { - return this.Project.create({}).then(project => { - return this.Task.create({ something: 1 }).then(task => { - return project.setTask(task).then(task => { - expect(task.ProjectId).to.not.be.null; - return task.setProject(project).then(project => { - expect(project.ProjectId).to.not.be.null; - }); - }); - }); - }); + it('correctly validates using create method ', async function() { + const project = await this.Project.create({}); + const task = await this.Task.create({ something: 1 }); + const task0 = await project.setTask(task); + expect(task0.ProjectId).to.not.be.null; + const project0 = await task0.setProject(project); + expect(project0.ProjectId).to.not.be.null; }); }); describe('explicitly validating primary/auto incremented columns', () => { - it('should emit an error when we try to enter in a string for the id key without validation arguments', function() { + it('should emit an error when we try to enter in a string for the id key without validation arguments', async function() { const User = this.sequelize.define('UserId', { id: { type: Sequelize.INTEGER, @@ -228,15 +208,17 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ id: 'helloworld' }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('id')[0].message).to.equal('Validation isInt on id failed'); - }); - }); + await User.sync({ force: true }); + + try { + await User.create({ id: 'helloworld' }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('id')[0].message).to.equal('Validation isInt on id failed'); + } }); - it('should emit an error when we try to enter in a string for an auto increment key (not named id)', function() { + it('should emit an error when we try to enter in a string for an auto increment key (not named id)', async function() { const User = this.sequelize.define('UserId', { username: { type: Sequelize.INTEGER, @@ -248,16 +230,18 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'helloworldhelloworld' }).catch(err => { - expect(err).to.be.an.instanceOf(Error); - expect(err.get('username')[0].message).to.equal('Username must be an integer!'); - }); - }); + await User.sync({ force: true }); + + try { + await User.create({ username: 'helloworldhelloworld' }); + } catch (err) { + expect(err).to.be.an.instanceOf(Error); + expect(err.get('username')[0].message).to.equal('Username must be an integer!'); + } }); describe('primaryKey with the name as id with arguments for it\'s validatio', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('UserId', { id: { type: Sequelize.INTEGER, @@ -269,36 +253,40 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should emit an error when we try to enter in a string for the id key with validation arguments', function() { - return this.User.create({ id: 'helloworld' }).catch(err => { + it('should emit an error when we try to enter in a string for the id key with validation arguments', async function() { + try { + await this.User.create({ id: 'helloworld' }); + } catch (err) { expect(err).to.be.an.instanceOf(Error); expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - }); + } }); - it('should emit an error when we try to enter in a string for an auto increment key through new Model().validate()', function() { - const user = new this.User({ id: 'helloworld' }); + it('should emit an error when we try to enter in a string for an auto increment key through .build().validate()', async function() { + const user = this.User.build({ id: 'helloworld' }); - return expect(user.validate()).to.be.rejected.then(err => { - expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - }); + const err = await expect(user.validate()).to.be.rejected; + expect(err.get('id')[0].message).to.equal('ID must be an integer!'); }); - it('should emit an error when we try to .save()', function() { - const user = new this.User({ id: 'helloworld' }); - return user.save().catch(err => { + it('should emit an error when we try to .save()', async function() { + const user = this.User.build({ id: 'helloworld' }); + + try { + await user.save(); + } catch (err) { expect(err).to.be.an.instanceOf(Error); expect(err.get('id')[0].message).to.equal('ID must be an integer!'); - }); + } }); }); }); describe('pass all paths when validating', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: { type: Sequelize.STRING, @@ -325,25 +313,25 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { Project.hasOne(Task); Task.belongsTo(Project); - return Project.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - this.Project = Project; - this.Task = Task; - }); - }); + await Project.sync({ force: true }); + await Task.sync({ force: true }); + this.Project = Project; + this.Task = Task; }); - it('produce 3 errors', function() { - return this.Project.create({}).catch(err => { + it('produce 3 errors', async function() { + try { + await this.Project.create({}); + } catch (err) { expect(err).to.be.an.instanceOf(Error); delete err.stack; // longStackTraces expect(err.errors).to.have.length(3); - }); + } }); }); describe('not null schema validation', () => { - beforeEach(function() { + beforeEach(async function() { const Project = this.sequelize.define('Project', { name: { type: Sequelize.STRING, @@ -354,13 +342,12 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - this.Project = Project; - }); + await this.sequelize.sync({ force: true }); + this.Project = Project; }); - it('correctly throws an error using create method ', function() { - return this.Project.create({}) + it('correctly throws an error using create method ', async function() { + await this.Project.create({}) .then(() => { throw new Error('Validation must be failed'); }, () => { @@ -368,19 +355,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('correctly throws an error using create method with default generated messages', function() { - return this.Project.create({}).catch(err => { + it('correctly throws an error using create method with default generated messages', async function() { + try { + await this.Project.create({}); + } catch (err) { expect(err).to.have.property('name', 'SequelizeValidationError'); expect(err.message).equal('notNull Violation: Project.name cannot be null'); expect(err.errors).to.be.an('array').and.have.length(1); expect(err.errors[0]).to.have.property('message', 'Project.name cannot be null'); - }); + } }); }); }); - it('correctly validates using custom validation methods', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('correctly validates using custom validation methods', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: { type: Sequelize.STRING, validate: { @@ -395,46 +384,42 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingUser = new User({ name: '3' }); + const failingUser = User.build({ name: '3' }); - return expect(failingUser.validate()).to.be.rejected.then(error => { - expect(error).to.be.an.instanceOf(Error); - expect(error.get('name')[0].message).to.equal("name should equal '2'"); + const error = await expect(failingUser.validate()).to.be.rejected; + expect(error).to.be.an.instanceOf(Error); + expect(error.get('name')[0].message).to.equal("name should equal '2'"); - const successfulUser = new User({ name: '2' }); - return expect(successfulUser.validate()).not.to.be.rejected; - }); + const successfulUser = User.build({ name: '2' }); + + await expect(successfulUser.validate()).not.to.be.rejected; }); - it('supports promises with custom validation methods', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('supports promises with custom validation methods', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: { type: Sequelize.STRING, validate: { - customFn(val) { - return User.findAll() - .then(() => { - if (val === 'error') { - throw new Error('Invalid username'); - } - }); + async customFn(val) { + await User.findAll(); + if (val === 'error') { + throw new Error('Invalid username'); + } } } } }); - return User.sync().then(() => { - return expect(new User({ name: 'error' }).validate()).to.be.rejected.then(error => { - expect(error).to.be.instanceof(Sequelize.ValidationError); - expect(error.get('name')[0].message).to.equal('Invalid username'); + await User.sync(); + const error = await expect(User.build({ name: 'error' }).validate()).to.be.rejected; + expect(error).to.be.instanceof(Sequelize.ValidationError); + expect(error.get('name')[0].message).to.equal('Invalid username'); - return expect(new User({ name: 'no error' }).validate()).not.to.be.rejected; - }); - }); + await expect(User.build({ name: 'no error' }).validate()).not.to.be.rejected; }); - it('skips other validations if allowNull is true and the value is null', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('skips other validations if allowNull is true and the value is null', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { age: { type: Sequelize.INTEGER, allowNull: true, @@ -444,16 +429,16 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(new User({ age: -1 }) + const error = await expect(User + .build({ age: -1 }) .validate()) - .to.be.rejected - .then(error => { - expect(error.get('age')[0].message).to.equal('must be positive'); - }); + .to.be.rejected; + + expect(error.get('age')[0].message).to.equal('must be positive'); }); - it('validates a model with custom model-wide validation methods', function() { - const Foo = this.sequelize.define(`Foo${config.rand()}`, { + it('validates a model with custom model-wide validation methods', async function() { + const Foo = this.sequelize.define(`Foo${Support.rand()}`, { field1: { type: Sequelize.INTEGER, allowNull: true @@ -472,20 +457,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(new Foo({ field1: null, field2: null }) + const error = await expect(Foo + .build({ field1: null, field2: null }) .validate()) - .to.be.rejected - .then(error => { - expect(error.get('xnor')[0].message).to.equal('xnor failed'); + .to.be.rejected; - return expect(new Foo({ field1: 33, field2: null }) - .validate()) - .not.to.be.rejected; - }); + expect(error.get('xnor')[0].message).to.equal('xnor failed'); + + await expect(Foo + .build({ field1: 33, field2: null }) + .validate()) + .not.to.be.rejected; }); - it('validates model with a validator whose arg is an Array successfully twice in a row', function() { - const Foo = this.sequelize.define(`Foo${config.rand()}`, { + it('validates model with a validator whose arg is an Array successfully twice in a row', async function() { + const Foo = this.sequelize.define(`Foo${Support.rand()}`, { bar: { type: Sequelize.STRING, validate: { @@ -493,16 +479,15 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } } }), - foo = new Foo({ bar: 'a' }); - return expect(foo.validate()).not.to.be.rejected.then(() => { - return expect(foo.validate()).not.to.be.rejected; - }); + foo = Foo.build({ bar: 'a' }); + await expect(foo.validate()).not.to.be.rejected; + await expect(foo.validate()).not.to.be.rejected; }); - it('validates enums', function() { + it('validates enums', async function() { const values = ['value1', 'value2']; - const Bar = this.sequelize.define(`Bar${config.rand()}`, { + const Bar = this.sequelize.define(`Bar${Support.rand()}`, { field: { type: Sequelize.ENUM, values, @@ -512,18 +497,17 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingBar = new Bar({ field: 'value3' }); + const failingBar = Bar.build({ field: 'value3' }); - return expect(failingBar.validate()).to.be.rejected.then(errors => { - expect(errors.get('field')).to.have.length(1); - expect(errors.get('field')[0].message).to.equal('Validation isIn on field failed'); - }); + const errors = await expect(failingBar.validate()).to.be.rejected; + expect(errors.get('field')).to.have.length(1); + expect(errors.get('field')[0].message).to.equal('Validation isIn on field failed'); }); - it('skips validations for the given fields', function() { + it('skips validations for the given fields', async function() { const values = ['value1', 'value2']; - const Bar = this.sequelize.define(`Bar${config.rand()}`, { + const Bar = this.sequelize.define(`Bar${Support.rand()}`, { field: { type: Sequelize.ENUM, values, @@ -533,15 +517,15 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingBar = new Bar({ field: 'value3' }); + const failingBar = Bar.build({ field: 'value3' }); - return expect(failingBar.validate({ skip: ['field'] })).not.to.be.rejected; + await expect(failingBar.validate({ skip: ['field'] })).not.to.be.rejected; }); - it('skips validations for fields with value that is SequelizeMethod', function() { + it('skips validations for fields with value that is SequelizeMethod', async function() { const values = ['value1', 'value2']; - const Bar = this.sequelize.define(`Bar${config.rand()}`, { + const Bar = this.sequelize.define(`Bar${Support.rand()}`, { field: { type: Sequelize.ENUM, values, @@ -551,12 +535,12 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const failingBar = new Bar({ field: this.sequelize.literal('5 + 1') }); + const failingBar = Bar.build({ field: this.sequelize.literal('5 + 1') }); - return expect(failingBar.validate()).not.to.be.rejected; + await expect(failingBar.validate()).not.to.be.rejected; }); - it('raises an error if saving a different value into an immutable field', function() { + it('raises an error if saving a different value into an immutable field', async function() { const User = this.sequelize.define('User', { name: { type: Sequelize.STRING, @@ -566,18 +550,15 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ name: 'RedCat' }).then(user => { - expect(user.getDataValue('name')).to.equal('RedCat'); - user.setDataValue('name', 'YellowCat'); - return expect(user.save()).to.be.rejected.then(errors => { - expect(errors.get('name')[0].message).to.eql('Validation isImmutable on name failed'); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ name: 'RedCat' }); + expect(user.getDataValue('name')).to.equal('RedCat'); + user.setDataValue('name', 'YellowCat'); + const errors = await expect(user.save()).to.be.rejected; + expect(errors.get('name')[0].message).to.eql('Validation isImmutable on name failed'); }); - it('allows setting an immutable field if the record is unsaved', function() { + it('allows setting an immutable field if the record is unsaved', async function() { const User = this.sequelize.define('User', { name: { type: Sequelize.STRING, @@ -587,98 +568,98 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const user = new User({ name: 'RedCat' }); + const user = User.build({ name: 'RedCat' }); expect(user.getDataValue('name')).to.equal('RedCat'); user.setDataValue('name', 'YellowCat'); - return expect(user.validate()).not.to.be.rejected; + await expect(user.validate()).not.to.be.rejected; }); - it('raises an error for array on a STRING', function() { + it('raises an error for array on a STRING', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING } }); - return expect(new User({ + await expect(User.build({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for array on a STRING(20)', function() { + it('raises an error for array on a STRING(20)', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING(20) } }); - return expect(new User({ + await expect(User.build({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for array on a TEXT', function() { + it('raises an error for array on a TEXT', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.TEXT } }); - return expect(new User({ + await expect(User.build({ email: ['iama', 'dummy.com'] }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for {} on a STRING', function() { + it('raises an error for {} on a STRING', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING } }); - return expect(new User({ + await expect(User.build({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for {} on a STRING(20)', function() { + it('raises an error for {} on a STRING(20)', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING(20) } }); - return expect(new User({ + await expect(User.build({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('raises an error for {} on a TEXT', function() { + it('raises an error for {} on a TEXT', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.TEXT } }); - return expect(new User({ + await expect(User.build({ email: { lol: true } }).validate()).to.be.rejectedWith(Sequelize.ValidationError); }); - it('does not raise an error for null on a STRING (where null is allowed)', function() { + it('does not raise an error for null on a STRING (where null is allowed)', async function() { const User = this.sequelize.define('User', { 'email': { type: Sequelize.STRING } }); - return expect(new User({ + await expect(User.build({ email: null }).validate()).not.to.be.rejected; }); - it('validates VIRTUAL fields', function() { + it('validates VIRTUAL fields', async function() { const User = this.sequelize.define('user', { password_hash: Sequelize.STRING, salt: Sequelize.STRING, @@ -698,21 +679,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return Sequelize.Promise.all([ - expect(new User({ + await Promise.all([ + expect(User.build({ password: 'short', salt: '42' }).validate()).to.be.rejected.then(errors => { expect(errors.get('password')[0].message).to.equal('Please choose a longer password'); }), - expect(new User({ + expect(User.build({ password: 'loooooooong', salt: '42' }).validate()).not.to.be.rejected ]); }); - it('allows me to add custom validation functions to validator.js', function() { + it('allows me to add custom validation functions to validator.js', async function() { this.sequelize.Validator.extend('isExactly7Characters', val => { return val.length === 7; }); @@ -726,14 +707,14 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - return expect(new User({ + await expect(User.build({ name: 'abcdefg' - }).validate()).not.to.be.rejected.then(() => { - return expect(new User({ - name: 'a' - }).validate()).to.be.rejected; - }).then(errors => { - expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters on name failed'); - }); + }).validate()).not.to.be.rejected; + + const errors = await expect(User.build({ + name: 'a' + }).validate()).to.be.rejected; + + expect(errors.get('name')[0].message).to.equal('Validation isExactly7Characters on name failed'); }); }); diff --git a/test/integration/instance/decrement.test.js b/test/integration/instance/decrement.test.js index 525b6dfb490c..a38397066c35 100644 --- a/test/integration/instance/decrement.test.js +++ b/test/integration/instance/decrement.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), expect = chai.expect, - Sequelize = require('../../../index'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), sinon = require('sinon'), @@ -21,7 +20,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -53,170 +52,137 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('decrement', () => { - beforeEach(function() { - return this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); + beforeEach(async function() { + await this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); - - return User.sync({ force: true }).then(() => { - return User.create({ number: 3 }).then(user => { - return sequelize.transaction().then(t => { - return user.decrement('number', { by: 2, transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].number).to.equal(3); - expect(users2[0].number).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); + + await User.sync({ force: true }); + const user = await User.create({ number: 3 }); + const t = await sequelize.transaction(); + await user.decrement('number', { by: 2, transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].number).to.equal(3); + expect(users2[0].number).to.equal(1); + await t.rollback(); }); } if (current.dialect.supports.returnValues.returning) { - it('supports returning', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement('aNumber', { by: 2 }).then(() => { - expect(user1.aNumber).to.be.equal(-2); - return user1.decrement('bNumber', { by: 2, returning: false }).then(user3 => { - expect(user3.bNumber).to.be.equal(0); - }); - }); - }); + it('supports returning', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement('aNumber', { by: 2 }); + expect(user1.aNumber).to.be.equal(-2); + const user3 = await user1.decrement('bNumber', { by: 2, returning: false }); + expect(user3.bNumber).to.be.equal(0); }); } - it('with array', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(-2); - }); - }); - }); + it('with array', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement(['aNumber'], { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(-2); }); - it('with single field', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement('aNumber', { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(-2); - }); - }); - }); + it('with single field', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement('aNumber', { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(-2); }); - it('with single field and no value', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement('aNumber').then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.be.equal(-1); - }); - }); - }); + it('with single field and no value', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement('aNumber'); + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.be.equal(-1); }); - it('should still work right with other concurrent updates', function() { - return this.User.findByPk(1).then(user1 => { - // Select the user again (simulating a concurrent query) - return this.User.findByPk(1).then(user2 => { - return user2.update({ - aNumber: user2.aNumber + 1 - }).then(() => { - return user1.decrement(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user5 => { - expect(user5.aNumber).to.be.equal(-1); - }); - }); - }); - }); + it('should still work right with other concurrent updates', async function() { + const user1 = await this.User.findByPk(1); + // Select the user again (simulating a concurrent query) + const user2 = await this.User.findByPk(1); + + await user2.update({ + aNumber: user2.aNumber + 1 }); + + await user1.decrement(['aNumber'], { by: 2 }); + const user5 = await this.User.findByPk(1); + expect(user5.aNumber).to.be.equal(-1); }); - it('should still work right with other concurrent increments', function() { - return this.User.findByPk(1).then(user1 => { - return Sequelize.Promise.all([ - user1.decrement(['aNumber'], { by: 2 }), - user1.decrement(['aNumber'], { by: 2 }), - user1.decrement(['aNumber'], { by: 2 }) - ]).then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.equal(-6); - }); - }); - }); + it('should still work right with other concurrent increments', async function() { + const user1 = await this.User.findByPk(1); + + await Promise.all([ + user1.decrement(['aNumber'], { by: 2 }), + user1.decrement(['aNumber'], { by: 2 }), + user1.decrement(['aNumber'], { by: 2 }) + ]); + + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.equal(-6); }); - it('with key value pair', function() { - return this.User.findByPk(1).then(user1 => { - return user1.decrement({ 'aNumber': 1, 'bNumber': 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(-1); - expect(user3.bNumber).to.be.equal(-2); - }); - }); - }); + it('with key value pair', async function() { + const user1 = await this.User.findByPk(1); + await user1.decrement({ 'aNumber': 1, 'bNumber': 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(-1); + expect(user3.bNumber).to.be.equal(-2); }); - it('with negative value', function() { - return this.User.findByPk(1).then(user1 => { - return Sequelize.Promise.all([ - user1.decrement('aNumber', { by: -2 }), - user1.decrement(['aNumber', 'bNumber'], { by: -2 }), - user1.decrement({ 'aNumber': -1, 'bNumber': -2 }) - ]).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(+5); - expect(user3.bNumber).to.be.equal(+4); - }); - }); - }); + it('with negative value', async function() { + const user1 = await this.User.findByPk(1); + + await Promise.all([ + user1.decrement('aNumber', { by: -2 }), + user1.decrement(['aNumber', 'bNumber'], { by: -2 }), + user1.decrement({ 'aNumber': -1, 'bNumber': -2 }) + ]); + + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(+5); + expect(user3.bNumber).to.be.equal(+4); }); - it('with timestamps set to true', function() { + it('with timestamps set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return user.decrement('aNumber', { by: 1 }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await user.decrement('aNumber', { by: 1 }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); }); - it('with timestamps set to true and options.silent set to true', function() { + it('with timestamps set to true and options.silent set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return user.decrement('aNumber', { by: 1, silent: true }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await user.decrement('aNumber', { by: 1, silent: true }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); }); }); }); diff --git a/test/integration/instance/destroy.test.js b/test/integration/instance/destroy.test.js index c674e4106e07..c912fb24aeae 100644 --- a/test/integration/instance/destroy.test.js +++ b/test/integration/instance/destroy.test.js @@ -11,271 +11,241 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('destroy', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return sequelize.transaction().then(t => { - return user.destroy({ transaction: t }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(1); - expect(count2).to.equal(0); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + + await User.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await user.destroy({ transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(1); + expect(count2).to.equal(0); + await t.rollback(); }); } - it('does not set the deletedAt date in subsequent destroys if dao is paranoid', function() { + it('does not set the deletedAt date in subsequent destroys if dao is paranoid', async function() { const UserDestroy = this.sequelize.define('UserDestroy', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT }, { paranoid: true }); - return UserDestroy.sync({ force: true }).then(() => { - return UserDestroy.create({ name: 'hallo', bio: 'welt' }).then(user => { - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - const deletedAt = user.deletedAt; - - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAt).to.eql(deletedAt); - }); - }); - }); - }); - }); - }); + await UserDestroy.sync({ force: true }); + const user = await UserDestroy.create({ name: 'hallo', bio: 'welt' }); + await user.destroy(); + await user.reload({ paranoid: false }); + const deletedAt = user.deletedAt; + + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAt).to.eql(deletedAt); }); - it('does not update deletedAt with custom default in subsequent destroys', function() { + it('does not update deletedAt with custom default in subsequent destroys', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, deletedAt: { type: Support.Sequelize.DATE, defaultValue: new Date(0) } }, { paranoid: true }); - let deletedAt; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - deletedAt = user.deletedAt; - expect(deletedAt).to.be.ok; - expect(deletedAt.getTime()).to.be.ok; - - return user.destroy(); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.be.ok; - expect(user.deletedAt.toISOString()).to.equal(deletedAt.toISOString()); + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' }); + + const user0 = await user1.destroy(); + const deletedAt = user0.deletedAt; + expect(deletedAt).to.be.ok; + expect(deletedAt.getTime()).to.be.ok; + + const user = await user0.destroy(); + expect(user).to.be.ok; + expect(user.deletedAt).to.be.ok; + expect(user.deletedAt.toISOString()).to.equal(deletedAt.toISOString()); }); - it('deletes a record from the database if dao is not paranoid', function() { + it('deletes a record from the database if dao is not paranoid', async function() { const UserDestroy = this.sequelize.define('UserDestroy', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT }); - return UserDestroy.sync({ force: true }).then(() => { - return UserDestroy.create({ name: 'hallo', bio: 'welt' }).then(u => { - return UserDestroy.findAll().then(users => { - expect(users.length).to.equal(1); - return u.destroy().then(() => { - return UserDestroy.findAll().then(users => { - expect(users.length).to.equal(0); - }); - }); - }); - }); - }); + await UserDestroy.sync({ force: true }); + const u = await UserDestroy.create({ name: 'hallo', bio: 'welt' }); + const users = await UserDestroy.findAll(); + expect(users.length).to.equal(1); + await u.destroy(); + const users0 = await UserDestroy.findAll(); + expect(users0.length).to.equal(0); }); - it('allows updating soft deleted instance', function() { + it('allows updating soft deleted instance', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING }, { paranoid: true }); - let deletedAt; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.deletedAt).to.be.ok; - deletedAt = user.deletedAt; - user.username = 'foo'; - return user.save(); - }).then(user => { - expect(user.username).to.equal('foo'); - expect(user.deletedAt).to.equal(deletedAt, 'should not update deletedAt'); - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.be.ok; + await ParanoidUser.sync({ force: true }); + + const user2 = await ParanoidUser.create({ + username: 'username' + }); + + const user1 = await user2.destroy(); + expect(user1.deletedAt).to.be.ok; + const deletedAt = user1.deletedAt; + user1.username = 'foo'; + const user0 = await user1.save(); + expect(user0.username).to.equal('foo'); + expect(user0.deletedAt).to.equal(deletedAt, 'should not update deletedAt'); + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { + username: 'foo' + } }); + + expect(user).to.be.ok; + expect(user.deletedAt).to.be.ok; }); - it('supports custom deletedAt field', function() { + it('supports custom deletedAt field', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, destroyTime: Support.Sequelize.DATE }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' + }); + + const user0 = await user1.destroy(); + expect(user0.destroyTime).to.be.ok; + expect(user0.deletedAt).to.not.be.ok; + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'username' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.be.ok; - expect(user.deletedAt).to.not.be.ok; + } }); + + expect(user).to.be.ok; + expect(user.destroyTime).to.be.ok; + expect(user.deletedAt).to.not.be.ok; }); - it('supports custom deletedAt database column', function() { + it('supports custom deletedAt database column', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, deletedAt: { type: Support.Sequelize.DATE, field: 'deleted_at' } }, { paranoid: true }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' + }); + + const user0 = await user1.destroy(); + expect(user0.dataValues.deletedAt).to.be.ok; + expect(user0.dataValues.deleted_at).to.not.be.ok; + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.deletedAt).to.be.ok; - expect(user.dataValues.deleted_at).to.not.be.ok; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'username' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.deletedAt).to.be.ok; - expect(user.deleted_at).to.not.be.ok; + } }); + + expect(user).to.be.ok; + expect(user.deletedAt).to.be.ok; + expect(user.deleted_at).to.not.be.ok; }); - it('supports custom deletedAt field and database column', function() { + it('supports custom deletedAt field and database column', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING, destroyTime: { type: Support.Sequelize.DATE, field: 'destroy_time' } }, { paranoid: true, deletedAt: 'destroyTime' }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ + await ParanoidUser.sync({ force: true }); + + const user1 = await ParanoidUser.create({ + username: 'username' + }); + + const user0 = await user1.destroy(); + expect(user0.dataValues.destroyTime).to.be.ok; + expect(user0.dataValues.destroy_time).to.not.be.ok; + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { username: 'username' - }); - }).then(user => { - return user.destroy(); - }).then(user => { - expect(user.dataValues.destroyTime).to.be.ok; - expect(user.dataValues.destroy_time).to.not.be.ok; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'username' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(user.destroyTime).to.be.ok; - expect(user.destroy_time).to.not.be.ok; + } }); + + expect(user).to.be.ok; + expect(user.destroyTime).to.be.ok; + expect(user.destroy_time).to.not.be.ok; }); - it('persists other model changes when soft deleting', function() { + it('persists other model changes when soft deleting', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { username: Support.Sequelize.STRING }, { paranoid: true }); - let deletedAt; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - user.username = 'foo'; - return user.destroy(); - }).then(user => { - expect(user.username).to.equal('foo'); - expect(user.deletedAt).to.be.ok; - deletedAt = user.deletedAt; - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).tap(user => { - expect(user).to.be.ok; - expect(moment.utc(user.deletedAt).startOf('second').toISOString()) - .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); - expect(user.username).to.equal('foo'); - }).then(user => { - // update model and delete again - user.username = 'bar'; - return user.destroy(); - }).then(user => { - expect(moment.utc(user.deletedAt).startOf('second').toISOString()) - .to.equal(moment.utc(deletedAt).startOf('second').toISOString(), - 'should not updated deletedAt when destroying multiple times'); - - return ParanoidUser.findOne({ - paranoid: false, - where: { - username: 'bar' - } - }); - }).then(user => { - expect(user).to.be.ok; - expect(moment.utc(user.deletedAt).startOf('second').toISOString()) - .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); - expect(user.username).to.equal('bar'); + await ParanoidUser.sync({ force: true }); + + const user4 = await ParanoidUser.create({ + username: 'username' + }); + + user4.username = 'foo'; + const user3 = await user4.destroy(); + expect(user3.username).to.equal('foo'); + expect(user3.deletedAt).to.be.ok; + const deletedAt = user3.deletedAt; + + const user2 = await ParanoidUser.findOne({ + paranoid: false, + where: { + username: 'foo' + } + }); + + expect(user2).to.be.ok; + expect(moment.utc(user2.deletedAt).startOf('second').toISOString()) + .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); + expect(user2.username).to.equal('foo'); + const user1 = user2; + // update model and delete again + user1.username = 'bar'; + const user0 = await user1.destroy(); + expect(moment.utc(user0.deletedAt).startOf('second').toISOString()) + .to.equal(moment.utc(deletedAt).startOf('second').toISOString(), + 'should not updated deletedAt when destroying multiple times'); + + const user = await ParanoidUser.findOne({ + paranoid: false, + where: { + username: 'bar' + } }); + + expect(user).to.be.ok; + expect(moment.utc(user.deletedAt).startOf('second').toISOString()) + .to.equal(moment.utc(deletedAt).startOf('second').toISOString()); + expect(user.username).to.equal('bar'); }); - it('allows sql logging of delete statements', function() { + it('allows sql logging of delete statements', async function() { const UserDelete = this.sequelize.define('UserDelete', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT @@ -283,22 +253,18 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const logging = sinon.spy(); - return UserDelete.sync({ force: true }).then(() => { - return UserDelete.create({ name: 'hallo', bio: 'welt' }).then(u => { - return UserDelete.findAll().then(users => { - expect(users.length).to.equal(1); - return u.destroy({ logging }); - }); - }); - }).then(() => { - expect(logging.callCount).to.equal(1, 'should call logging'); - const sql = logging.firstCall.args[0]; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('DELETE'); - }); + await UserDelete.sync({ force: true }); + const u = await UserDelete.create({ name: 'hallo', bio: 'welt' }); + const users = await UserDelete.findAll(); + expect(users.length).to.equal(1); + await u.destroy({ logging }); + expect(logging.callCount).to.equal(1, 'should call logging'); + const sql = logging.firstCall.args[0]; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('DELETE'); }); - it('allows sql logging of update statements', function() { + it('allows sql logging of update statements', async function() { const UserDelete = this.sequelize.define('UserDelete', { name: Support.Sequelize.STRING, bio: Support.Sequelize.TEXT @@ -306,22 +272,18 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const logging = sinon.spy(); - return UserDelete.sync({ force: true }).then(() => { - return UserDelete.create({ name: 'hallo', bio: 'welt' }).then(u => { - return UserDelete.findAll().then(users => { - expect(users.length).to.equal(1); - return u.destroy({ logging }); - }); - }); - }).then(() => { - expect(logging.callCount).to.equal(1, 'should call logging'); - const sql = logging.firstCall.args[0]; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('UPDATE'); - }); + await UserDelete.sync({ force: true }); + const u = await UserDelete.create({ name: 'hallo', bio: 'welt' }); + const users = await UserDelete.findAll(); + expect(users.length).to.equal(1); + await u.destroy({ logging }); + expect(logging.callCount).to.equal(1, 'should call logging'); + const sql = logging.firstCall.args[0]; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('UPDATE'); }); - it('should not call save hooks when soft deleting', function() { + it('should not call save hooks when soft deleting', async function() { const beforeSave = sinon.spy(); const afterSave = sinon.spy(); @@ -335,29 +297,28 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.create({ - username: 'username' - }); - }).then(user => { - // clear out calls from .create - beforeSave.resetHistory(); - afterSave.resetHistory(); - - return user.destroy(); - }).tap(() => { - expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave'); - expect(afterSave.callCount).to.equal(0, 'should not call afterSave'); - }).then(user => { - // now try with `hooks: true` - return user.destroy({ hooks: true }); - }).tap(() => { - expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave even if `hooks: true`'); - expect(afterSave.callCount).to.equal(0, 'should not call afterSave even if `hooks: true`'); + await ParanoidUser.sync({ force: true }); + + const user0 = await ParanoidUser.create({ + username: 'username' }); + + // clear out calls from .create + beforeSave.resetHistory(); + afterSave.resetHistory(); + + const result0 = await user0.destroy(); + expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave'); + expect(afterSave.callCount).to.equal(0, 'should not call afterSave'); + const user = result0; + const result = await user.destroy({ hooks: true }); + expect(beforeSave.callCount).to.equal(0, 'should not call beforeSave even if `hooks: true`'); + expect(afterSave.callCount).to.equal(0, 'should not call afterSave even if `hooks: true`'); + + await result; }); - it('delete a record of multiple primary keys table', function() { + it('delete a record of multiple primary keys table', async function() { const MultiPrimary = this.sequelize.define('MultiPrimary', { bilibili: { type: Support.Sequelize.CHAR(2), @@ -370,33 +331,29 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return MultiPrimary.sync({ force: true }).then(() => { - return MultiPrimary.create({ bilibili: 'bl', guruguru: 'gu' }).then(() => { - return MultiPrimary.create({ bilibili: 'bl', guruguru: 'ru' }).then(m2 => { - return MultiPrimary.findAll().then(ms => { - expect(ms.length).to.equal(2); - return m2.destroy({ - logging(sql) { - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('DELETE'); - expect(sql).to.include('ru'); - expect(sql).to.include('bl'); - } - }).then(() => { - return MultiPrimary.findAll().then(ms => { - expect(ms.length).to.equal(1); - expect(ms[0].bilibili).to.equal('bl'); - expect(ms[0].guruguru).to.equal('gu'); - }); - }); - }); - }); - }); + await MultiPrimary.sync({ force: true }); + await MultiPrimary.create({ bilibili: 'bl', guruguru: 'gu' }); + const m2 = await MultiPrimary.create({ bilibili: 'bl', guruguru: 'ru' }); + const ms = await MultiPrimary.findAll(); + expect(ms.length).to.equal(2); + + await m2.destroy({ + logging(sql) { + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('DELETE'); + expect(sql).to.include('ru'); + expect(sql).to.include('bl'); + } }); + + const ms0 = await MultiPrimary.findAll(); + expect(ms0.length).to.equal(1); + expect(ms0[0].bilibili).to.equal('bl'); + expect(ms0[0].guruguru).to.equal('gu'); }); if (dialect.match(/^postgres/)) { - it('converts Infinity in where clause to a timestamp', function() { + it('converts Infinity in where clause to a timestamp', async function() { const Date = this.sequelize.define('Date', { date: { @@ -410,14 +367,12 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { paranoid: true }); - return this.sequelize.sync({ force: true }) - .then(() => { - return new Date({ date: Infinity }) - .save() - .then(date => { - return date.destroy(); - }); - }); + await this.sequelize.sync({ force: true }); + + const date = await Date.build({ date: Infinity }) + .save(); + + await date.destroy(); }); } }); diff --git a/test/integration/instance/increment.test.js b/test/integration/instance/increment.test.js index 87826728293f..a50aba8fe9f6 100644 --- a/test/integration/instance/increment.test.js +++ b/test/integration/instance/increment.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), expect = chai.expect, - Sequelize = require('../../../index'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), sinon = require('sinon'), @@ -21,7 +20,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -53,169 +52,132 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('increment', () => { - beforeEach(function() { - return this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); + beforeEach(async function() { + await this.User.create({ id: 1, aNumber: 0, bNumber: 0 }); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); - - return User.sync({ force: true }).then(() => { - return User.create({ number: 1 }).then(user => { - return sequelize.transaction().then(t => { - return user.increment('number', { by: 2, transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].number).to.equal(1); - expect(users2[0].number).to.equal(3); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { number: Support.Sequelize.INTEGER }); + + await User.sync({ force: true }); + const user = await User.create({ number: 1 }); + const t = await sequelize.transaction(); + await user.increment('number', { by: 2, transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].number).to.equal(1); + expect(users2[0].number).to.equal(3); + await t.rollback(); }); } if (current.dialect.supports.returnValues.returning) { - it('supports returning', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment('aNumber', { by: 2 }).then(() => { - expect(user1.aNumber).to.be.equal(2); - return user1.increment('bNumber', { by: 2, returning: false }).then(user3 => { - expect(user3.bNumber).to.be.equal(0); - }); - }); - }); + it('supports returning', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment('aNumber', { by: 2 }); + expect(user1.aNumber).to.be.equal(2); + const user3 = await user1.increment('bNumber', { by: 2, returning: false }); + expect(user3.bNumber).to.be.equal(0); }); } - it('supports where conditions', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment(['aNumber'], { by: 2, where: { bNumber: 1 } }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(0); - }); - }); - }); + it('supports where conditions', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment(['aNumber'], { by: 2, where: { bNumber: 1 } }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(0); }); - it('with array', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(2); - }); - }); - }); + it('with array', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment(['aNumber'], { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(2); }); - it('with single field', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment('aNumber', { by: 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(2); - }); - }); - }); + it('with single field', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment('aNumber', { by: 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(2); }); - it('with single field and no value', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment('aNumber').then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.be.equal(1); - }); - }); - }); + it('with single field and no value', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment('aNumber'); + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.be.equal(1); }); - it('should still work right with other concurrent updates', function() { - return this.User.findByPk(1).then(user1 => { - // Select the user again (simulating a concurrent query) - return this.User.findByPk(1).then(user2 => { - return user2.update({ - aNumber: user2.aNumber + 1 - }).then(() => { - return user1.increment(['aNumber'], { by: 2 }).then(() => { - return this.User.findByPk(1).then(user5 => { - expect(user5.aNumber).to.be.equal(3); - }); - }); - }); - }); + it('should still work right with other concurrent updates', async function() { + const user1 = await this.User.findByPk(1); + // Select the user again (simulating a concurrent query) + const user2 = await this.User.findByPk(1); + + await user2.update({ + aNumber: user2.aNumber + 1 }); + + await user1.increment(['aNumber'], { by: 2 }); + const user5 = await this.User.findByPk(1); + expect(user5.aNumber).to.be.equal(3); }); - it('should still work right with other concurrent increments', function() { - return this.User.findByPk(1).then(user1 => { - return Sequelize.Promise.all([ - user1.increment(['aNumber'], { by: 2 }), - user1.increment(['aNumber'], { by: 2 }), - user1.increment(['aNumber'], { by: 2 }) - ]).then(() => { - return this.User.findByPk(1).then(user2 => { - expect(user2.aNumber).to.equal(6); - }); - }); - }); + it('should still work right with other concurrent increments', async function() { + const user1 = await this.User.findByPk(1); + + await Promise.all([ + user1.increment(['aNumber'], { by: 2 }), + user1.increment(['aNumber'], { by: 2 }), + user1.increment(['aNumber'], { by: 2 }) + ]); + + const user2 = await this.User.findByPk(1); + expect(user2.aNumber).to.equal(6); }); - it('with key value pair', function() { - return this.User.findByPk(1).then(user1 => { - return user1.increment({ 'aNumber': 1, 'bNumber': 2 }).then(() => { - return this.User.findByPk(1).then(user3 => { - expect(user3.aNumber).to.be.equal(1); - expect(user3.bNumber).to.be.equal(2); - }); - }); - }); + it('with key value pair', async function() { + const user1 = await this.User.findByPk(1); + await user1.increment({ 'aNumber': 1, 'bNumber': 2 }); + const user3 = await this.User.findByPk(1); + expect(user3.aNumber).to.be.equal(1); + expect(user3.bNumber).to.be.equal(2); }); - it('with timestamps set to true', function() { + it('with timestamps set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; + await User.sync({ force: true }); + const user1 = await User.create({ aNumber: 1 }); + const oldDate = user1.get('updatedAt'); - return User.sync({ force: true }) - .then(() => User.create({ aNumber: 1 })) - .then(user => { - oldDate = user.get('updatedAt'); + this.clock.tick(1000); + const user0 = await user1.increment('aNumber', { by: 1 }); + const user = await user0.reload(); - this.clock.tick(1000); - return user.increment('aNumber', { by: 1 }); - }) - .then(user => user.reload()) - .then(user => { - return expect(user).to.have.property('updatedAt').afterTime(oldDate); - }); + await expect(user).to.have.property('updatedAt').afterTime(oldDate); }); - it('with timestamps set to true and options.silent set to true', function() { + it('with timestamps set to true and options.silent set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return user.increment('aNumber', { by: 1, silent: true }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await user.increment('aNumber', { by: 1, silent: true }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); }); }); }); diff --git a/test/integration/instance/reload.test.js b/test/integration/instance/reload.test.js index 26bf1144cbbc..8b9566d82140 100644 --- a/test/integration/instance/reload.test.js +++ b/test/integration/instance/reload.test.js @@ -21,7 +21,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -53,240 +53,225 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('reload', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return sequelize.transaction().then(t => { - return User.update({ username: 'bar' }, { where: { username: 'foo' }, transaction: t }).then(() => { - return user.reload().then(user => { - expect(user.username).to.equal('foo'); - return user.reload({ transaction: t }).then(user => { - expect(user.username).to.equal('bar'); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + + await User.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await User.update({ username: 'bar' }, { where: { username: 'foo' }, transaction: t }); + const user1 = await user.reload(); + expect(user1.username).to.equal('foo'); + const user0 = await user1.reload({ transaction: t }); + expect(user0.username).to.equal('bar'); + await t.rollback(); }); } - it('should return a reference to the same DAO instead of creating a new one', function() { - return this.User.create({ username: 'John Doe' }).then(originalUser => { - return originalUser.update({ username: 'Doe John' }).then(() => { - return originalUser.reload().then(updatedUser => { - expect(originalUser === updatedUser).to.be.true; - }); - }); - }); + it('should return a reference to the same DAO instead of creating a new one', async function() { + const originalUser = await this.User.create({ username: 'John Doe' }); + await originalUser.update({ username: 'Doe John' }); + const updatedUser = await originalUser.reload(); + expect(originalUser === updatedUser).to.be.true; }); - it('should update the values on all references to the DAO', function() { - return this.User.create({ username: 'John Doe' }).then(originalUser => { - return this.User.findByPk(originalUser.id).then(updater => { - return updater.update({ username: 'Doe John' }).then(() => { - // We used a different reference when calling update, so originalUser is now out of sync - expect(originalUser.username).to.equal('John Doe'); - return originalUser.reload().then(updatedUser => { - expect(originalUser.username).to.equal('Doe John'); - expect(updatedUser.username).to.equal('Doe John'); - }); - }); - }); - }); + it('should use default internal where', async function() { + const user = await this.User.create({ username: 'Balak Bukhara' }); + const anotherUser = await this.User.create({ username: 'John Smith' }); + + const primaryKey = user.get('id'); + + await user.reload(); + expect(user.get('id')).to.equal(primaryKey); + + // options.where should be ignored + await user.reload({ where: { id: anotherUser.get('id') } }); + expect(user.get('id')).to.equal(primaryKey).and.not.equal(anotherUser.get('id')); + }); + + it('should update the values on all references to the DAO', async function() { + const originalUser = await this.User.create({ username: 'John Doe' }); + const updater = await this.User.findByPk(originalUser.id); + await updater.update({ username: 'Doe John' }); + // We used a different reference when calling update, so originalUser is now out of sync + expect(originalUser.username).to.equal('John Doe'); + const updatedUser = await originalUser.reload(); + expect(originalUser.username).to.equal('Doe John'); + expect(updatedUser.username).to.equal('Doe John'); }); - it('should support updating a subset of attributes', function() { - return this.User.create({ + it('should support updating a subset of attributes', async function() { + const user1 = await this.User.create({ aNumber: 1, bNumber: 1 - }).tap(user => { - return this.User.update({ - bNumber: 2 - }, { - where: { - id: user.get('id') - } - }); - }).then(user => { - return user.reload({ - attributes: ['bNumber'] - }); - }).then(user => { - expect(user.get('aNumber')).to.equal(1); - expect(user.get('bNumber')).to.equal(2); }); - }); - it('should update read only attributes as well (updatedAt)', function() { - return this.User.create({ username: 'John Doe' }).then(originalUser => { - this.originallyUpdatedAt = originalUser.updatedAt; - this.originalUser = originalUser; - - // Wait for a second, so updatedAt will actually be different - this.clock.tick(1000); - return this.User.findByPk(originalUser.id); - }).then(updater => { - return updater.update({ username: 'Doe John' }); - }).then(updatedUser => { - this.updatedUser = updatedUser; - return this.originalUser.reload(); - }).then(() => { - expect(this.originalUser.updatedAt).to.be.above(this.originallyUpdatedAt); - expect(this.updatedUser.updatedAt).to.be.above(this.originallyUpdatedAt); + await this.User.update({ + bNumber: 2 + }, { + where: { + id: user1.get('id') + } + }); + + const user0 = user1; + + const user = await user0.reload({ + attributes: ['bNumber'] }); + + expect(user.get('aNumber')).to.equal(1); + expect(user.get('bNumber')).to.equal(2); + }); + + it('should update read only attributes as well (updatedAt)', async function() { + const originalUser = await this.User.create({ username: 'John Doe' }); + this.originallyUpdatedAt = originalUser.updatedAt; + this.originalUser = originalUser; + + // Wait for a second, so updatedAt will actually be different + this.clock.tick(1000); + const updater = await this.User.findByPk(originalUser.id); + const updatedUser = await updater.update({ username: 'Doe John' }); + this.updatedUser = updatedUser; + await this.originalUser.reload(); + expect(this.originalUser.updatedAt).to.be.above(this.originallyUpdatedAt); + expect(this.updatedUser.updatedAt).to.be.above(this.originallyUpdatedAt); }); - it('should update the associations as well', function() { + it('should update the associations as well', async function() { const Book = this.sequelize.define('Book', { title: DataTypes.STRING }), Page = this.sequelize.define('Page', { content: DataTypes.TEXT }); Book.hasMany(Page); Page.belongsTo(Book); - return Book.sync({ force: true }).then(() => { - return Page.sync({ force: true }).then(() => { - return Book.create({ title: 'A very old book' }).then(book => { - return Page.create({ content: 'om nom nom' }).then(page => { - return book.setPages([page]).then(() => { - return Book.findOne({ - where: { id: book.id }, - include: [Page] - }).then(leBook => { - return page.update({ content: 'something totally different' }).then(page => { - expect(leBook.Pages.length).to.equal(1); - expect(leBook.Pages[0].content).to.equal('om nom nom'); - expect(page.content).to.equal('something totally different'); - return leBook.reload().then(leBook => { - expect(leBook.Pages.length).to.equal(1); - expect(leBook.Pages[0].content).to.equal('something totally different'); - expect(page.content).to.equal('something totally different'); - }); - }); - }); - }); - }); - }); - }); + await Book.sync({ force: true }); + await Page.sync({ force: true }); + const book = await Book.create({ title: 'A very old book' }); + const page = await Page.create({ content: 'om nom nom' }); + await book.setPages([page]); + + const leBook = await Book.findOne({ + where: { id: book.id }, + include: [Page] }); + + const page0 = await page.update({ content: 'something totally different' }); + expect(leBook.Pages.length).to.equal(1); + expect(leBook.Pages[0].content).to.equal('om nom nom'); + expect(page0.content).to.equal('something totally different'); + const leBook0 = await leBook.reload(); + expect(leBook0.Pages.length).to.equal(1); + expect(leBook0.Pages[0].content).to.equal('something totally different'); + expect(page0.content).to.equal('something totally different'); }); - it('should update internal options of the instance', function() { + it('should update internal options of the instance', async function() { const Book = this.sequelize.define('Book', { title: DataTypes.STRING }), Page = this.sequelize.define('Page', { content: DataTypes.TEXT }); Book.hasMany(Page); Page.belongsTo(Book); - return Book.sync({ force: true }).then(() => { - return Page.sync({ force: true }).then(() => { - return Book.create({ title: 'A very old book' }).then(book => { - return Page.create().then(page => { - return book.setPages([page]).then(() => { - return Book.findOne({ - where: { id: book.id } - }).then(leBook => { - const oldOptions = leBook._options; - return leBook.reload({ - include: [Page] - }).then(leBook => { - expect(oldOptions).not.to.equal(leBook._options); - expect(leBook._options.include.length).to.equal(1); - expect(leBook.Pages.length).to.equal(1); - expect(leBook.get({ plain: true }).Pages.length).to.equal(1); - }); - }); - }); - }); - }); - }); + await Book.sync({ force: true }); + await Page.sync({ force: true }); + const book = await Book.create({ title: 'A very old book' }); + const page = await Page.create(); + await book.setPages([page]); + + const leBook = await Book.findOne({ + where: { id: book.id } }); - }); - it('should return an error when reload fails', function() { - return this.User.create({ username: 'John Doe' }).then(user => { - return user.destroy().then(() => { - return expect(user.reload()).to.be.rejectedWith( - Sequelize.InstanceError, - 'Instance could not be reloaded because it does not exist anymore (find call returned null)' - ); - }); + const oldOptions = leBook._options; + + const leBook0 = await leBook.reload({ + include: [Page] }); + + expect(oldOptions).not.to.equal(leBook0._options); + expect(leBook0._options.include.length).to.equal(1); + expect(leBook0.Pages.length).to.equal(1); + expect(leBook0.get({ plain: true }).Pages.length).to.equal(1); + }); + + it('should return an error when reload fails', async function() { + const user = await this.User.create({ username: 'John Doe' }); + await user.destroy(); + + await expect(user.reload()).to.be.rejectedWith( + Sequelize.InstanceError, + 'Instance could not be reloaded because it does not exist anymore (find call returned null)' + ); }); - it('should set an association to null after deletion, 1-1', function() { + it('should set an association to null after deletion, 1-1', async function() { const Shoe = this.sequelize.define('Shoe', { brand: DataTypes.STRING }), Player = this.sequelize.define('Player', { name: DataTypes.STRING }); Player.hasOne(Shoe); Shoe.belongsTo(Player); - return this.sequelize.sync({ force: true }).then(() => { - return Shoe.create({ - brand: 'the brand', - Player: { - name: 'the player' - } - }, { include: [Player] }); - }).then(shoe => { - return Player.findOne({ - where: { id: shoe.Player.id }, - include: [Shoe] - }).then(lePlayer => { - expect(lePlayer.Shoe).not.to.be.null; - return lePlayer.Shoe.destroy().return(lePlayer); - }).then(lePlayer => { - return lePlayer.reload(); - }).then(lePlayer => { - expect(lePlayer.Shoe).to.be.null; - }); + await this.sequelize.sync({ force: true }); + + const shoe = await Shoe.create({ + brand: 'the brand', + Player: { + name: 'the player' + } + }, { include: [Player] }); + + const lePlayer1 = await Player.findOne({ + where: { id: shoe.Player.id }, + include: [Shoe] }); + + expect(lePlayer1.Shoe).not.to.be.null; + await lePlayer1.Shoe.destroy(); + const lePlayer0 = lePlayer1; + const lePlayer = await lePlayer0.reload(); + expect(lePlayer.Shoe).to.be.null; }); - it('should set an association to empty after all deletion, 1-N', function() { + it('should set an association to empty after all deletion, 1-N', async function() { const Team = this.sequelize.define('Team', { name: DataTypes.STRING }), Player = this.sequelize.define('Player', { name: DataTypes.STRING }); Team.hasMany(Player); Player.belongsTo(Team); - return this.sequelize.sync({ force: true }).then(() => { - return Team.create({ - name: 'the team', - Players: [{ - name: 'the player1' - }, { - name: 'the player2' - }] - }, { include: [Player] }); - }).then(team => { - return Team.findOne({ - where: { id: team.id }, - include: [Player] - }).then(leTeam => { - expect(leTeam.Players).not.to.be.empty; - return leTeam.Players[1].destroy().then(() => { - return leTeam.Players[0].destroy(); - }).return(leTeam); - }).then(leTeam => { - return leTeam.reload(); - }).then(leTeam => { - expect(leTeam.Players).to.be.empty; - }); + await this.sequelize.sync({ force: true }); + + const team = await Team.create({ + name: 'the team', + Players: [{ + name: 'the player1' + }, { + name: 'the player2' + }] + }, { include: [Player] }); + + const leTeam1 = await Team.findOne({ + where: { id: team.id }, + include: [Player] }); + + expect(leTeam1.Players).not.to.be.empty; + await leTeam1.Players[1].destroy(); + await leTeam1.Players[0].destroy(); + const leTeam0 = leTeam1; + const leTeam = await leTeam0.reload(); + expect(leTeam.Players).to.be.empty; }); - it('should update the associations after one element deleted', function() { + it('should update the associations after one element deleted', async function() { const Team = this.sequelize.define('Team', { name: DataTypes.STRING }), Player = this.sequelize.define('Player', { name: DataTypes.STRING }); @@ -294,28 +279,58 @@ describe(Support.getTestDialectTeaser('Instance'), () => { Player.belongsTo(Team); - return this.sequelize.sync({ force: true }).then(() => { - return Team.create({ - name: 'the team', - Players: [{ - name: 'the player1' - }, { - name: 'the player2' - }] - }, { include: [Player] }); - }).then(team => { - return Team.findOne({ - where: { id: team.id }, - include: [Player] - }).then(leTeam => { - expect(leTeam.Players).to.have.length(2); - return leTeam.Players[0].destroy().return(leTeam); - }).then(leTeam => { - return leTeam.reload(); - }).then(leTeam => { - expect(leTeam.Players).to.have.length(1); - }); + await this.sequelize.sync({ force: true }); + + const team = await Team.create({ + name: 'the team', + Players: [{ + name: 'the player1' + }, { + name: 'the player2' + }] + }, { include: [Player] }); + + const leTeam1 = await Team.findOne({ + where: { id: team.id }, + include: [Player] }); + + expect(leTeam1.Players).to.have.length(2); + await leTeam1.Players[0].destroy(); + const leTeam0 = leTeam1; + const leTeam = await leTeam0.reload(); + expect(leTeam.Players).to.have.length(1); + }); + + it('should inject default scope when reloading', async function() { + const Bar = this.sequelize.define('Bar', { + name: DataTypes.TEXT + }); + + const Foo = this.sequelize.define('Foo', { + name: DataTypes.TEXT + }, { + defaultScope: { + include: [{ model: Bar }] + } + }); + + Bar.belongsTo(Foo); + Foo.hasMany(Bar); + + await this.sequelize.sync(); + + const foo = await Foo.create({ name: 'foo' }); + await foo.createBar({ name: 'bar' }); + const fooFromFind = await Foo.findByPk(foo.id); + + expect(fooFromFind.Bars).to.be.ok; + expect(fooFromFind.Bars[0].name).to.equal('bar'); + + await foo.reload(); + + expect(foo.Bars).to.be.ok; + expect(foo.Bars[0].name).to.equal('bar'); }); }); }); diff --git a/test/integration/instance/save.test.js b/test/integration/instance/save.test.js index 48fe9488fc11..0ab42dda82aa 100644 --- a/test/integration/instance/save.test.js +++ b/test/integration/instance/save.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), Support = require('../support'), DataTypes = require('../../../lib/data-types'), - config = require('../../config/config'), sinon = require('sinon'), current = Support.sequelize; @@ -22,7 +21,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -54,83 +53,74 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('save', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return new User({ username: 'foo' }).save({ transaction: t }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(0); - expect(count2).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.build({ username: 'foo' }).save({ transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(0); + expect(count2).to.equal(1); + await t.rollback(); }); } - it('only updates fields in passed array', function() { + it('only updates fields in passed array', async function() { const date = new Date(1990, 1, 1); - return this.User.create({ + const user = await this.User.create({ username: 'foo', touchedAt: new Date() - }).then(user => { - user.username = 'fizz'; - user.touchedAt = date; - - return user.save({ fields: ['username'] }).then(() => { - // re-select user - return this.User.findByPk(user.id).then(user2 => { - // name should have changed - expect(user2.username).to.equal('fizz'); - // bio should be unchanged - expect(user2.birthDate).not.to.equal(date); - }); - }); }); + + user.username = 'fizz'; + user.touchedAt = date; + + await user.save({ fields: ['username'] }); + // re-select user + const user2 = await this.User.findByPk(user.id); + // name should have changed + expect(user2.username).to.equal('fizz'); + // bio should be unchanged + expect(user2.birthDate).not.to.equal(date); }); - it('should work on a model with an attribute named length', function() { + it('should work on a model with an attribute named length', async function() { const Box = this.sequelize.define('box', { length: DataTypes.INTEGER, width: DataTypes.INTEGER, height: DataTypes.INTEGER }); - return Box.sync({ force: true }).then(() => { - return Box.create({ - length: 1, - width: 2, - height: 3 - }).then(box => { - return box.update({ - length: 4, - width: 5, - height: 6 - }); - }).then(() => { - return Box.findOne({}).then(box => { - expect(box.get('length')).to.equal(4); - expect(box.get('width')).to.equal(5); - expect(box.get('height')).to.equal(6); - }); - }); + await Box.sync({ force: true }); + + const box0 = await Box.create({ + length: 1, + width: 2, + height: 3 + }); + + await box0.update({ + length: 4, + width: 5, + height: 6 }); + + const box = await Box.findOne({}); + expect(box.get('length')).to.equal(4); + expect(box.get('width')).to.equal(5); + expect(box.get('height')).to.equal(6); }); - it('only validates fields in passed array', function() { - return new this.User({ + it('only validates fields in passed array', async function() { + await this.User.build({ validateTest: 'cake', // invalid, but not saved validateCustom: '1' }).save({ @@ -139,71 +129,69 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('hooks', () => { - it('should update attributes added in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should update attributes added in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.set({ - name: 'B', - bio: 'B' - }).save(); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('B'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' }); + + await user0.set({ + name: 'B', + bio: 'B' + }).save(); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('B'); }); - it('should update attributes changed in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should update attributes changed in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'C'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.set({ - name: 'B', - bio: 'B', - email: 'B' - }).save(); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('C'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' }); + + await user0.set({ + name: 'B', + bio: 'B', + email: 'B' + }).save(); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('C'); }); - it('should validate attributes added in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should validate attributes added in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: { @@ -214,29 +202,28 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.set({ - name: 'B' - }).save()).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.set({ + name: 'B' + }).save()).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); - it('should validate attributes changed in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should validate attributes changed in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: { @@ -247,51 +234,47 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.set({ - name: 'B', - email: 'still.valid.email@gmail.com' - }).save()).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.set({ + name: 'B', + email: 'still.valid.email@gmail.com' + }).save()).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); }); - it('stores an entry in the database', function() { + it('stores an entry in the database', async function() { const username = 'user', User = this.User, - user = new this.User({ + user = this.User.build({ username, touchedAt: new Date(1984, 8, 23) }); - return User.findAll().then(users => { - expect(users).to.have.length(0); - return user.save().then(() => { - return User.findAll().then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal(username); - expect(users[0].touchedAt).to.be.instanceof(Date); - expect(users[0].touchedAt).to.equalDate(new Date(1984, 8, 23)); - }); - }); - }); + const users = await User.findAll(); + expect(users).to.have.length(0); + await user.save(); + const users0 = await User.findAll(); + expect(users0).to.have.length(1); + expect(users0[0].username).to.equal(username); + expect(users0[0].touchedAt).to.be.instanceof(Date); + expect(users0[0].touchedAt).to.equalDate(new Date(1984, 8, 23)); }); - it('handles an entry with primaryKey of zero', function() { + it('handles an entry with primaryKey of zero', async function() { const username = 'user', newUsername = 'newUser', User2 = this.sequelize.define('User2', @@ -304,101 +287,85 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: { type: DataTypes.STRING } }); - return User2.sync().then(() => { - return User2.create({ id: 0, username }).then(user => { - expect(user).to.be.ok; - expect(user.id).to.equal(0); - expect(user.username).to.equal(username); - return User2.findByPk(0).then(user => { - expect(user).to.be.ok; - expect(user.id).to.equal(0); - expect(user.username).to.equal(username); - return user.update({ username: newUsername }).then(user => { - expect(user).to.be.ok; - expect(user.id).to.equal(0); - expect(user.username).to.equal(newUsername); - }); - }); - }); - }); + await User2.sync(); + const user = await User2.create({ id: 0, username }); + expect(user).to.be.ok; + expect(user.id).to.equal(0); + expect(user.username).to.equal(username); + const user1 = await User2.findByPk(0); + expect(user1).to.be.ok; + expect(user1.id).to.equal(0); + expect(user1.username).to.equal(username); + const user0 = await user1.update({ username: newUsername }); + expect(user0).to.be.ok; + expect(user0.id).to.equal(0); + expect(user0.username).to.equal(newUsername); }); - it('updates the timestamps', function() { + it('updates the timestamps', async function() { const now = new Date(); now.setMilliseconds(0); - const user = new this.User({ username: 'user' }); + const user = this.User.build({ username: 'user' }); this.clock.tick(1000); - return user.save().then(savedUser => { - expect(savedUser).have.property('updatedAt').afterTime(now); + const savedUser = await user.save(); + expect(savedUser).have.property('updatedAt').afterTime(now); - this.clock.tick(1000); - return savedUser.save(); - }).then(updatedUser => { - expect(updatedUser).have.property('updatedAt').afterTime(now); - }); + this.clock.tick(1000); + const updatedUser = await savedUser.save(); + expect(updatedUser).have.property('updatedAt').afterTime(now); }); - it('does not update timestamps when passing silent=true', function() { - return this.User.create({ username: 'user' }).then(user => { - const updatedAt = user.updatedAt; + it('does not update timestamps when passing silent=true', async function() { + const user = await this.User.create({ username: 'user' }); + const updatedAt = user.updatedAt; - this.clock.tick(1000); - return expect(user.update({ - username: 'userman' - }, { - silent: true - })).to.eventually.have.property('updatedAt').equalTime(updatedAt); - }); + this.clock.tick(1000); + + await expect(user.update({ + username: 'userman' + }, { + silent: true + })).to.eventually.have.property('updatedAt').equalTime(updatedAt); }); - it('does not update timestamps when passing silent=true in a bulk update', function() { + it('does not update timestamps when passing silent=true in a bulk update', async function() { const data = [ { username: 'Paul' }, { username: 'Peter' } ]; - let updatedAtPeter, - updatedAtPaul; - - return this.User.bulkCreate(data).then(() => { - return this.User.findAll(); - }).then(users => { - updatedAtPaul = users[0].updatedAt; - updatedAtPeter = users[1].updatedAt; - }) - .then(() => { - this.clock.tick(150); - return this.User.update( - { aNumber: 1 }, - { where: {}, silent: true } - ); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users[0].updatedAt).to.equalTime(updatedAtPeter); - expect(users[1].updatedAt).to.equalTime(updatedAtPaul); - }); + + await this.User.bulkCreate(data); + const users0 = await this.User.findAll(); + const updatedAtPaul = users0[0].updatedAt; + const updatedAtPeter = users0[1].updatedAt; + this.clock.tick(150); + + await this.User.update( + { aNumber: 1 }, + { where: {}, silent: true } + ); + + const users = await this.User.findAll(); + expect(users[0].updatedAt).to.equalTime(updatedAtPeter); + expect(users[1].updatedAt).to.equalTime(updatedAtPaul); }); describe('when nothing changed', () => { - it('does not update timestamps', function() { - return this.User.create({ username: 'John' }).then(() => { - return this.User.findOne({ where: { username: 'John' } }).then(user => { - const updatedAt = user.updatedAt; - this.clock.tick(2000); - return user.save().then(newlySavedUser => { - expect(newlySavedUser.updatedAt).to.equalTime(updatedAt); - return this.User.findOne({ where: { username: 'John' } }).then(newlySavedUser => { - expect(newlySavedUser.updatedAt).to.equalTime(updatedAt); - }); - }); - }); - }); + it('does not update timestamps', async function() { + await this.User.create({ username: 'John' }); + const user = await this.User.findOne({ where: { username: 'John' } }); + const updatedAt = user.updatedAt; + this.clock.tick(2000); + const newlySavedUser = await user.save(); + expect(newlySavedUser.updatedAt).to.equalTime(updatedAt); + const newlySavedUser0 = await this.User.findOne({ where: { username: 'John' } }); + expect(newlySavedUser0.updatedAt).to.equalTime(updatedAt); }); - it('should not throw ER_EMPTY_QUERY if changed only virtual fields', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should not throw ER_EMPTY_QUERY if changed only virtual fields', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: { type: DataTypes.VIRTUAL, @@ -407,44 +374,48 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { timestamps: false }); - return User.sync({ force: true }).then(() => - User.create({ name: 'John', bio: 'swag 1' }).then(user => user.update({ bio: 'swag 2' }).should.be.fulfilled) - ); + await User.sync({ force: true }); + const user = await User.create({ name: 'John', bio: 'swag 1' }); + await user.update({ bio: 'swag 2' }).should.be.fulfilled; }); }); - it('updates with function and column value', function() { - return this.User.create({ + it('updates with function and column value', async function() { + const user = await this.User.create({ aNumber: 42 - }).then(user => { - user.bNumber = this.sequelize.col('aNumber'); - user.username = this.sequelize.fn('upper', 'sequelize'); - return user.save().then(() => { - return this.User.findByPk(user.id).then(user2 => { - expect(user2.username).to.equal('SEQUELIZE'); - expect(user2.bNumber).to.equal(42); - }); - }); }); + + user.bNumber = this.sequelize.col('aNumber'); + user.username = this.sequelize.fn('upper', 'sequelize'); + await user.save(); + const user2 = await this.User.findByPk(user.id); + expect(user2.username).to.equal('SEQUELIZE'); + expect(user2.bNumber).to.equal(42); + }); + + it('updates with function that contains escaped dollar symbol', async function() { + const user = await this.User.create({}); + user.username = this.sequelize.fn('upper', '$sequelize'); + await user.save(); + const userAfterUpdate = await this.User.findByPk(user.id); + expect(userAfterUpdate.username).to.equal('$SEQUELIZE'); }); describe('without timestamps option', () => { - it("doesn't update the updatedAt column", function() { + it("doesn't update the updatedAt column", async function() { const User2 = this.sequelize.define('User2', { username: DataTypes.STRING, updatedAt: DataTypes.DATE }, { timestamps: false }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - // sqlite and mysql return undefined, whereas postgres returns null - expect([undefined, null]).to.include(johnDoe.updatedAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + // sqlite and mysql return undefined, whereas postgres returns null + expect([undefined, null]).to.include(johnDoe.updatedAt); }); }); describe('with custom timestamp options', () => { - it('updates the createdAt column if updatedAt is disabled', function() { + it('updates the createdAt column if updatedAt is disabled', async function() { const now = new Date(); this.clock.tick(1000); @@ -452,15 +423,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: DataTypes.STRING }, { updatedAt: false }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - expect(johnDoe.updatedAt).to.be.undefined; - expect(now).to.be.beforeTime(johnDoe.createdAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + expect(johnDoe.updatedAt).to.be.undefined; + expect(now).to.be.beforeTime(johnDoe.createdAt); }); - it('updates the updatedAt column if createdAt is disabled', function() { + it('updates the updatedAt column if createdAt is disabled', async function() { const now = new Date(); this.clock.tick(1000); @@ -468,15 +437,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { username: DataTypes.STRING }, { createdAt: false }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - expect(johnDoe.createdAt).to.be.undefined; - expect(now).to.be.beforeTime(johnDoe.updatedAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + expect(johnDoe.createdAt).to.be.undefined; + expect(now).to.be.beforeTime(johnDoe.updatedAt); }); - it('works with `allowNull: false` on createdAt and updatedAt columns', function() { + it('works with `allowNull: false` on createdAt and updatedAt columns', async function() { const User2 = this.sequelize.define('User2', { username: DataTypes.STRING, createdAt: { @@ -489,86 +456,88 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }, { timestamps: true }); - return User2.sync().then(() => { - return User2.create({ username: 'john doe' }).then(johnDoe => { - expect(johnDoe.createdAt).to.be.an.instanceof(Date); - expect( ! isNaN(johnDoe.createdAt.valueOf()) ).to.be.ok; - expect(johnDoe.createdAt).to.equalTime(johnDoe.updatedAt); - }); - }); + await User2.sync(); + const johnDoe = await User2.create({ username: 'john doe' }); + expect(johnDoe.createdAt).to.be.an.instanceof(Date); + expect( ! isNaN(johnDoe.createdAt.valueOf()) ).to.be.ok; + expect(johnDoe.createdAt).to.equalTime(johnDoe.updatedAt); }); }); - it('should fail a validation upon creating', function() { - return this.User.create({ aNumber: 0, validateTest: 'hello' }).catch(err => { + it('should fail a validation upon creating', async function() { + try { + await this.User.create({ aNumber: 0, validateTest: 'hello' }); + } catch (err) { expect(err).to.exist; expect(err).to.be.instanceof(Object); expect(err.get('validateTest')).to.be.instanceof(Array); expect(err.get('validateTest')[0]).to.exist; expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); - }); + } }); - it('should fail a validation upon creating with hooks false', function() { - return this.User.create({ aNumber: 0, validateTest: 'hello' }, { hooks: false }).catch(err => { + it('should fail a validation upon creating with hooks false', async function() { + try { + await this.User.create({ aNumber: 0, validateTest: 'hello' }, { hooks: false }); + } catch (err) { expect(err).to.exist; expect(err).to.be.instanceof(Object); expect(err.get('validateTest')).to.be.instanceof(Array); expect(err.get('validateTest')[0]).to.exist; expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); - }); + } }); - it('should fail a validation upon building', function() { - return new this.User({ aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' }).save() - .catch(err => { - expect(err).to.exist; - expect(err).to.be.instanceof(Object); - expect(err.get('validateCustom')).to.exist; - expect(err.get('validateCustom')).to.be.instanceof(Array); - expect(err.get('validateCustom')[0]).to.exist; - expect(err.get('validateCustom')[0].message).to.equal('Length failed.'); - }); + it('should fail a validation upon building', async function() { + try { + await this.User.build({ aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' }).save(); + } catch (err) { + expect(err).to.exist; + expect(err).to.be.instanceof(Object); + expect(err.get('validateCustom')).to.exist; + expect(err.get('validateCustom')).to.be.instanceof(Array); + expect(err.get('validateCustom')[0]).to.exist; + expect(err.get('validateCustom')[0].message).to.equal('Length failed.'); + } }); - it('should fail a validation when updating', function() { - return this.User.create({ aNumber: 0 }).then(user => { - return user.update({ validateTest: 'hello' }).catch(err => { - expect(err).to.exist; - expect(err).to.be.instanceof(Object); - expect(err.get('validateTest')).to.exist; - expect(err.get('validateTest')).to.be.instanceof(Array); - expect(err.get('validateTest')[0]).to.exist; - expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); - }); - }); + it('should fail a validation when updating', async function() { + const user = await this.User.create({ aNumber: 0 }); + + try { + await user.update({ validateTest: 'hello' }); + } catch (err) { + expect(err).to.exist; + expect(err).to.be.instanceof(Object); + expect(err.get('validateTest')).to.exist; + expect(err.get('validateTest')).to.be.instanceof(Array); + expect(err.get('validateTest')[0]).to.exist; + expect(err.get('validateTest')[0].message).to.equal('Validation isInt on validateTest failed'); + } }); - it('takes zero into account', function() { - return new this.User({ aNumber: 0 }).save({ + it('takes zero into account', async function() { + const user = await this.User.build({ aNumber: 0 }).save({ fields: ['aNumber'] - }).then(user => { - expect(user.aNumber).to.equal(0); }); + + expect(user.aNumber).to.equal(0); }); - it('saves a record with no primary key', function() { + it('saves a record with no primary key', async function() { const HistoryLog = this.sequelize.define('HistoryLog', { someText: { type: DataTypes.STRING }, aNumber: { type: DataTypes.INTEGER }, aRandomId: { type: DataTypes.INTEGER } }); - return HistoryLog.sync().then(() => { - return HistoryLog.create({ someText: 'Some random text', aNumber: 3, aRandomId: 5 }).then(log => { - return log.update({ aNumber: 5 }).then(newLog => { - expect(newLog.aNumber).to.equal(5); - }); - }); - }); + await HistoryLog.sync(); + const log = await HistoryLog.create({ someText: 'Some random text', aNumber: 3, aRandomId: 5 }); + const newLog = await log.update({ aNumber: 5 }); + expect(newLog.aNumber).to.equal(5); }); describe('eagerly loaded objects', () => { - beforeEach(function() { + beforeEach(async function() { this.UserEager = this.sequelize.define('UserEagerLoadingSaves', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -582,113 +551,88 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.UserEager.hasMany(this.ProjectEager, { as: 'Projects', foreignKey: 'PoobahId' }); this.ProjectEager.belongsTo(this.UserEager, { as: 'Poobah', foreignKey: 'PoobahId' }); - return this.UserEager.sync({ force: true }).then(() => { - return this.ProjectEager.sync({ force: true }); - }); + await this.UserEager.sync({ force: true }); + + await this.ProjectEager.sync({ force: true }); }); - it('saves one object that has a collection of eagerly loaded objects', function() { - return this.UserEager.create({ username: 'joe', age: 1 }).then(user => { - return this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }).then(project1 => { - return this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }).then(project2 => { - return user.setProjects([project1, project2]).then(() => { - return this.UserEager.findOne({ where: { age: 1 }, include: [{ model: this.ProjectEager, as: 'Projects' }] }).then(user => { - expect(user.username).to.equal('joe'); - expect(user.age).to.equal(1); - expect(user.Projects).to.exist; - expect(user.Projects.length).to.equal(2); - - user.age = user.age + 1; // happy birthday joe - return user.save().then(user => { - expect(user.username).to.equal('joe'); - expect(user.age).to.equal(2); - expect(user.Projects).to.exist; - expect(user.Projects.length).to.equal(2); - }); - }); - }); - }); - }); - }); + it('saves one object that has a collection of eagerly loaded objects', async function() { + const user = await this.UserEager.create({ username: 'joe', age: 1 }); + const project1 = await this.ProjectEager.create({ title: 'project-joe1', overdue_days: 0 }); + const project2 = await this.ProjectEager.create({ title: 'project-joe2', overdue_days: 0 }); + await user.setProjects([project1, project2]); + const user1 = await this.UserEager.findOne({ where: { age: 1 }, include: [{ model: this.ProjectEager, as: 'Projects' }] }); + expect(user1.username).to.equal('joe'); + expect(user1.age).to.equal(1); + expect(user1.Projects).to.exist; + expect(user1.Projects.length).to.equal(2); + + user1.age = user1.age + 1; // happy birthday joe + const user0 = await user1.save(); + expect(user0.username).to.equal('joe'); + expect(user0.age).to.equal(2); + expect(user0.Projects).to.exist; + expect(user0.Projects.length).to.equal(2); }); - it('saves many objects that each a have collection of eagerly loaded objects', function() { - return this.UserEager.create({ username: 'bart', age: 20 }).then(bart => { - return this.UserEager.create({ username: 'lisa', age: 20 }).then(lisa => { - return this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }).then(detention1 => { - return this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }).then(detention2 => { - return this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }).then(exam1 => { - return this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }).then(exam2 => { - return bart.setProjects([detention1, detention2]).then(() => { - return lisa.setProjects([exam1, exam2]).then(() => { - return this.UserEager.findAll({ where: { age: 20 }, order: [['username', 'ASC']], include: [{ model: this.ProjectEager, as: 'Projects' }] }).then(simpsons => { - expect(simpsons.length).to.equal(2); - - const _bart = simpsons[0]; - const _lisa = simpsons[1]; - - expect(_bart.Projects).to.exist; - expect(_lisa.Projects).to.exist; - expect(_bart.Projects.length).to.equal(2); - expect(_lisa.Projects.length).to.equal(2); - - _bart.age = _bart.age + 1; // happy birthday bart - off to Moe's - - return _bart.save().then(savedbart => { - expect(savedbart.username).to.equal('bart'); - expect(savedbart.age).to.equal(21); - - _lisa.username = 'lsimpson'; - - return _lisa.save().then(savedlisa => { - expect(savedlisa.username).to.equal('lsimpson'); - expect(savedlisa.age).to.equal(20); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + it('saves many objects that each a have collection of eagerly loaded objects', async function() { + const bart = await this.UserEager.create({ username: 'bart', age: 20 }); + const lisa = await this.UserEager.create({ username: 'lisa', age: 20 }); + const detention1 = await this.ProjectEager.create({ title: 'detention1', overdue_days: 0 }); + const detention2 = await this.ProjectEager.create({ title: 'detention2', overdue_days: 0 }); + const exam1 = await this.ProjectEager.create({ title: 'exam1', overdue_days: 0 }); + const exam2 = await this.ProjectEager.create({ title: 'exam2', overdue_days: 0 }); + await bart.setProjects([detention1, detention2]); + await lisa.setProjects([exam1, exam2]); + const simpsons = await this.UserEager.findAll({ where: { age: 20 }, order: [['username', 'ASC']], include: [{ model: this.ProjectEager, as: 'Projects' }] }); + expect(simpsons.length).to.equal(2); + + const _bart = simpsons[0]; + const _lisa = simpsons[1]; + + expect(_bart.Projects).to.exist; + expect(_lisa.Projects).to.exist; + expect(_bart.Projects.length).to.equal(2); + expect(_lisa.Projects.length).to.equal(2); + + _bart.age = _bart.age + 1; // happy birthday bart - off to Moe's + + const savedbart = await _bart.save(); + expect(savedbart.username).to.equal('bart'); + expect(savedbart.age).to.equal(21); + + _lisa.username = 'lsimpson'; + + const savedlisa = await _lisa.save(); + expect(savedlisa.username).to.equal('lsimpson'); + expect(savedlisa.age).to.equal(20); }); - it('saves many objects that each has one eagerly loaded object (to which they belong)', function() { - return this.UserEager.create({ username: 'poobah', age: 18 }).then(user => { - return this.ProjectEager.create({ title: 'homework', overdue_days: 10 }).then(homework => { - return this.ProjectEager.create({ title: 'party', overdue_days: 2 }).then(party => { - return user.setProjects([homework, party]).then(() => { - return this.ProjectEager.findAll({ include: [{ model: this.UserEager, as: 'Poobah' }] }).then(projects => { - expect(projects.length).to.equal(2); - expect(projects[0].Poobah).to.exist; - expect(projects[1].Poobah).to.exist; - expect(projects[0].Poobah.username).to.equal('poobah'); - expect(projects[1].Poobah.username).to.equal('poobah'); - - projects[0].title = 'partymore'; - projects[1].title = 'partymore'; - projects[0].overdue_days = 0; - projects[1].overdue_days = 0; - - return projects[0].save().then(() => { - return projects[1].save().then(() => { - return this.ProjectEager.findAll({ where: { title: 'partymore', overdue_days: 0 }, include: [{ model: this.UserEager, as: 'Poobah' }] }).then(savedprojects => { - expect(savedprojects.length).to.equal(2); - expect(savedprojects[0].Poobah).to.exist; - expect(savedprojects[1].Poobah).to.exist; - expect(savedprojects[0].Poobah.username).to.equal('poobah'); - expect(savedprojects[1].Poobah.username).to.equal('poobah'); - }); - }); - }); - }); - }); - }); - }); - }); + it('saves many objects that each has one eagerly loaded object (to which they belong)', async function() { + const user = await this.UserEager.create({ username: 'poobah', age: 18 }); + const homework = await this.ProjectEager.create({ title: 'homework', overdue_days: 10 }); + const party = await this.ProjectEager.create({ title: 'party', overdue_days: 2 }); + await user.setProjects([homework, party]); + const projects = await this.ProjectEager.findAll({ include: [{ model: this.UserEager, as: 'Poobah' }] }); + expect(projects.length).to.equal(2); + expect(projects[0].Poobah).to.exist; + expect(projects[1].Poobah).to.exist; + expect(projects[0].Poobah.username).to.equal('poobah'); + expect(projects[1].Poobah.username).to.equal('poobah'); + + projects[0].title = 'partymore'; + projects[1].title = 'partymore'; + projects[0].overdue_days = 0; + projects[1].overdue_days = 0; + + await projects[0].save(); + await projects[1].save(); + const savedprojects = await this.ProjectEager.findAll({ where: { title: 'partymore', overdue_days: 0 }, include: [{ model: this.UserEager, as: 'Poobah' }] }); + expect(savedprojects.length).to.equal(2); + expect(savedprojects[0].Poobah).to.exist; + expect(savedprojects[1].Poobah).to.exist; + expect(savedprojects[0].Poobah.username).to.equal('poobah'); + expect(savedprojects[1].Poobah.username).to.equal('poobah'); }); }); }); diff --git a/test/integration/instance/to-json.test.js b/test/integration/instance/to-json.test.js index 59de8a33595a..cd904747facd 100644 --- a/test/integration/instance/to-json.test.js +++ b/test/integration/instance/to-json.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('toJSON', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, age: DataTypes.INTEGER, @@ -26,50 +26,46 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.User.hasMany(this.Project, { as: 'Projects', foreignKey: 'lovelyUserId' }); this.Project.belongsTo(this.User, { as: 'LovelyUser', foreignKey: 'lovelyUserId' }); - return this.User.sync({ force: true }).then(() => { - return this.Project.sync({ force: true }); - }); + await this.User.sync({ force: true }); + + await this.Project.sync({ force: true }); }); - it("doesn't return instance that isn't defined", function() { - return this.Project.create({ lovelyUserId: null }) - .then(project => { - return this.Project.findOne({ - where: { - id: project.id - }, - include: [ - { model: this.User, as: 'LovelyUser' } - ] - }); - }) - .then(project => { - const json = project.toJSON(); - expect(json.LovelyUser).to.be.equal(null); - }); + it("doesn't return instance that isn't defined", async function() { + const project0 = await this.Project.create({ lovelyUserId: null }); + + const project = await this.Project.findOne({ + where: { + id: project0.id + }, + include: [ + { model: this.User, as: 'LovelyUser' } + ] + }); + + const json = project.toJSON(); + expect(json.LovelyUser).to.be.equal(null); }); - it("doesn't return instances that aren't defined", function() { - return this.User.create({ username: 'cuss' }) - .then(user => { - return this.User.findOne({ - where: { - id: user.id - }, - include: [ - { model: this.Project, as: 'Projects' } - ] - }); - }) - .then(user => { - expect(user.Projects).to.be.instanceof(Array); - expect(user.Projects).to.be.length(0); - }); + it("doesn't return instances that aren't defined", async function() { + const user0 = await this.User.create({ username: 'cuss' }); + + const user = await this.User.findOne({ + where: { + id: user0.id + }, + include: [ + { model: this.Project, as: 'Projects' } + ] + }); + + expect(user.Projects).to.be.instanceof(Array); + expect(user.Projects).to.be.length(0); }); - describe('constructor', () => { + describe('build', () => { it('returns an object containing all values', function() { - const user = new this.User({ + const user = this.User.build({ username: 'Adam', age: 22, level: -1, @@ -88,7 +84,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('returns a response that can be stringified', function() { - const user = new this.User({ + const user = this.User.build({ username: 'test.user', age: 99, isAdmin: true, @@ -98,131 +94,129 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('returns a response that can be stringified and then parsed', function() { - const user = new this.User({ username: 'test.user', age: 99, isAdmin: true }); + const user = this.User.build({ username: 'test.user', age: 99, isAdmin: true }); expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ username: 'test.user', age: 99, isAdmin: true, isUser: false, id: null }); }); }); describe('create', () => { - it('returns an object containing all values', function() { - return this.User.create({ + it('returns an object containing all values', async function() { + const user = await this.User.create({ username: 'Adam', age: 22, level: -1, isUser: false, isAdmin: true - }).then(user => { - expect(user.toJSON()).to.deep.equal({ - id: user.get('id'), - username: 'Adam', - age: 22, - isUser: false, - isAdmin: true, - level: -1 - }); + }); + + expect(user.toJSON()).to.deep.equal({ + id: user.get('id'), + username: 'Adam', + age: 22, + isUser: false, + isAdmin: true, + level: -1 }); }); - it('returns a response that can be stringified', function() { - return this.User.create({ + it('returns a response that can be stringified', async function() { + const user = await this.User.create({ username: 'test.user', age: 99, isAdmin: true, isUser: false, level: null - }).then(user => { - expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"isAdmin":true,"isUser":false,"level":null}`); }); + + expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"isAdmin":true,"isUser":false,"level":null}`); }); - it('returns a response that can be stringified and then parsed', function() { - return this.User.create({ + it('returns a response that can be stringified and then parsed', async function() { + const user = await this.User.create({ username: 'test.user', age: 99, isAdmin: true, level: null - }).then(user => { - expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ - age: 99, - id: user.get('id'), - isAdmin: true, - isUser: false, - level: null, - username: 'test.user' - }); + }); + + expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ + age: 99, + id: user.get('id'), + isAdmin: true, + isUser: false, + level: null, + username: 'test.user' }); }); }); describe('find', () => { - it('returns an object containing all values', function() { - return this.User.create({ + it('returns an object containing all values', async function() { + const user0 = await this.User.create({ + username: 'Adam', + age: 22, + level: -1, + isUser: false, + isAdmin: true + }); + + const user = await this.User.findByPk(user0.get('id')); + expect(user.toJSON()).to.deep.equal({ + id: user.get('id'), username: 'Adam', age: 22, level: -1, isUser: false, isAdmin: true - }).then(user => this.User.findByPk(user.get('id'))).then(user => { - expect(user.toJSON()).to.deep.equal({ - id: user.get('id'), - username: 'Adam', - age: 22, - level: -1, - isUser: false, - isAdmin: true - }); }); }); - it('returns a response that can be stringified', function() { - return this.User.create({ + it('returns a response that can be stringified', async function() { + const user0 = await this.User.create({ username: 'test.user', age: 99, isAdmin: true, isUser: false - }).then(user => this.User.findByPk(user.get('id'))).then(user => { - expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"level":null,"isUser":false,"isAdmin":true}`); }); + + const user = await this.User.findByPk(user0.get('id')); + expect(JSON.stringify(user)).to.deep.equal(`{"id":${user.get('id')},"username":"test.user","age":99,"level":null,"isUser":false,"isAdmin":true}`); }); - it('returns a response that can be stringified and then parsed', function() { - return this.User.create({ + it('returns a response that can be stringified and then parsed', async function() { + const user0 = await this.User.create({ username: 'test.user', age: 99, isAdmin: true - }).then(user => this.User.findByPk(user.get('id'))).then(user => { - expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ - id: user.get('id'), - username: 'test.user', - age: 99, - isAdmin: true, - isUser: false, - level: null - }); + }); + + const user = await this.User.findByPk(user0.get('id')); + expect(JSON.parse(JSON.stringify(user))).to.deep.equal({ + id: user.get('id'), + username: 'test.user', + age: 99, + isAdmin: true, + isUser: false, + level: null }); }); }); - it('includes the eagerly loaded associations', function() { - return this.User.create({ username: 'fnord', age: 1, isAdmin: true }).then(user => { - return this.Project.create({ title: 'fnord' }).then(project => { - return user.setProjects([project]).then(() => { - return this.User.findAll({ include: [{ model: this.Project, as: 'Projects' }] }).then(users => { - const _user = users[0]; + it('includes the eagerly loaded associations', async function() { + const user = await this.User.create({ username: 'fnord', age: 1, isAdmin: true }); + const project = await this.Project.create({ title: 'fnord' }); + await user.setProjects([project]); + const users = await this.User.findAll({ include: [{ model: this.Project, as: 'Projects' }] }); + const _user = users[0]; - expect(_user.Projects).to.exist; - expect(JSON.parse(JSON.stringify(_user)).Projects).to.exist; + expect(_user.Projects).to.exist; + expect(JSON.parse(JSON.stringify(_user)).Projects).to.exist; - return this.Project.findAll({ include: [{ model: this.User, as: 'LovelyUser' }] }).then(projects => { - const _project = projects[0]; + const projects = await this.Project.findAll({ include: [{ model: this.User, as: 'LovelyUser' }] }); + const _project = projects[0]; - expect(_project.LovelyUser).to.exist; - expect(JSON.parse(JSON.stringify(_project)).LovelyUser).to.exist; - }); - }); - }); - }); - }); + expect(_project.LovelyUser).to.exist; + expect(JSON.parse(JSON.stringify(_project)).LovelyUser).to.exist; }); }); }); diff --git a/test/integration/instance/update.test.js b/test/integration/instance/update.test.js index 847d3645f956..c3f17aefeac8 100644 --- a/test/integration/instance/update.test.js +++ b/test/integration/instance/update.test.js @@ -6,7 +6,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), - config = require('../../config/config'), current = Support.sequelize; describe(Support.getTestDialectTeaser('Instance'), () => { @@ -18,7 +17,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); describe('update', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: { type: DataTypes.STRING }, uuidv1: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV1 }, @@ -58,60 +57,51 @@ describe(Support.getTestDialectTeaser('Instance'), () => { allowNull: true } }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(user => { - return sequelize.transaction().then(t => { - return user.update({ username: 'bar' }, { transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].username).to.equal('foo'); - expect(users2[0].username).to.equal('bar'); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + + await User.sync({ force: true }); + const user = await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await user.update({ username: 'bar' }, { transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].username).to.equal('foo'); + expect(users2[0].username).to.equal('bar'); + await t.rollback(); }); } - it('should update fields that are not specified on create', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should update fields that are not specified on create', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }, { - fields: ['name', 'email'] - }).then(user => { - return user.update({ bio: 'swag' }); - }).then(user => { - return user.reload(); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - expect(user.get('bio')).to.equal('swag'); - }); + await User.sync({ force: true }); + + const user1 = await User.create({ + name: 'snafu', + email: 'email' + }, { + fields: ['name', 'email'] }); + + const user0 = await user1.update({ bio: 'swag' }); + const user = await user0.reload(); + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); + expect(user.get('bio')).to.equal('swag'); }); - it('should succeed in updating when values are unchanged (without timestamps)', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should succeed in updating when values are unchanged (without timestamps)', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING @@ -119,28 +109,27 @@ describe(Support.getTestDialectTeaser('Instance'), () => { timestamps: false }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }, { - fields: ['name', 'email'] - }).then(user => { - return user.update({ - name: 'snafu', - email: 'email' - }); - }).then(user => { - return user.reload(); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - }); + await User.sync({ force: true }); + + const user1 = await User.create({ + name: 'snafu', + email: 'email' + }, { + fields: ['name', 'email'] }); + + const user0 = await user1.update({ + name: 'snafu', + email: 'email' + }); + + const user = await user0.reload(); + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); }); - it('should update timestamps with milliseconds', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should update timestamps with milliseconds', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING, @@ -152,118 +141,110 @@ describe(Support.getTestDialectTeaser('Instance'), () => { this.clock.tick(2100); //move the clock forward 2100 ms. - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }).then(user => { - return user.reload(); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - const testDate = new Date(); - testDate.setTime(2100); - expect(user.get('createdAt')).to.equalTime(testDate); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'snafu', + email: 'email' }); + + const user = await user0.reload(); + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); + const testDate = new Date(); + testDate.setTime(2100); + expect(user.get('createdAt')).to.equalTime(testDate); }); - it('should only save passed attributes', function() { - const user = new this.User(); - return user.save().then(() => { - user.set('validateTest', 5); - expect(user.changed('validateTest')).to.be.ok; - return user.update({ - validateCustom: '1' - }); - }).then(() => { - expect(user.changed('validateTest')).to.be.ok; - expect(user.validateTest).to.be.equal(5); - }).then(() => { - return user.reload(); - }).then(() => { - expect(user.validateTest).to.not.be.equal(5); + it('should only save passed attributes', async function() { + const user = this.User.build(); + await user.save(); + user.set('validateTest', 5); + expect(user.changed('validateTest')).to.be.ok; + + await user.update({ + validateCustom: '1' }); + + expect(user.changed('validateTest')).to.be.ok; + expect(user.validateTest).to.be.equal(5); + await user.reload(); + expect(user.validateTest).to.not.be.equal(5); }); - it('should save attributes affected by setters', function() { - const user = new this.User(); - return user.update({ validateSideEffect: 5 }).then(() => { - expect(user.validateSideEffect).to.be.equal(5); - }).then(() => { - return user.reload(); - }).then(() => { - expect(user.validateSideAffected).to.be.equal(10); - expect(user.validateSideEffect).not.to.be.ok; - }); + it('should save attributes affected by setters', async function() { + const user = this.User.build(); + await user.update({ validateSideEffect: 5 }); + expect(user.validateSideEffect).to.be.equal(5); + await user.reload(); + expect(user.validateSideAffected).to.be.equal(10); + expect(user.validateSideEffect).not.to.be.ok; }); describe('hooks', () => { - it('should update attributes added in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should update attributes added in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.update({ - name: 'B', - bio: 'B' - }); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('B'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' + }); + + await user0.update({ + name: 'B', + bio: 'B' }); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('B'); }); - it('should update attributes changed in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should update attributes changed in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'C'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'A' - }).then(user => { - return user.update({ - name: 'B', - bio: 'B', - email: 'B' - }); - }).then(() => { - return User.findOne({}); - }).then(user => { - expect(user.get('name')).to.equal('B'); - expect(user.get('bio')).to.equal('B'); - expect(user.get('email')).to.equal('C'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'A' + }); + + await user0.update({ + name: 'B', + bio: 'B', + email: 'B' }); + + const user = await User.findOne({}); + expect(user.get('name')).to.equal('B'); + expect(user.get('bio')).to.equal('B'); + expect(user.get('email')).to.equal('C'); }); - it('should validate attributes added in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should validate attributes added in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: { @@ -274,29 +255,28 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.update({ - name: 'B' - })).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.update({ + name: 'B' + })).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); - it('should validate attributes changed in hooks when default fields are used', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should validate attributes changed in hooks when default fields are used', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: { @@ -307,157 +287,148 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - User.hooks.add('beforeUpdate', instance => { + User.beforeUpdate(instance => { instance.set('email', 'B'); }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'A', - bio: 'A', - email: 'valid.email@gmail.com' - }).then(user => { - return expect(user.update({ - name: 'B', - email: 'still.valid.email@gmail.com' - })).to.be.rejectedWith(Sequelize.ValidationError); - }).then(() => { - return User.findOne({}).then(user => { - expect(user.get('email')).to.equal('valid.email@gmail.com'); - }); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'A', + bio: 'A', + email: 'valid.email@gmail.com' }); + + await expect(user0.update({ + name: 'B', + email: 'still.valid.email@gmail.com' + })).to.be.rejectedWith(Sequelize.ValidationError); + + const user = await User.findOne({}); + expect(user.get('email')).to.equal('valid.email@gmail.com'); }); }); - it('should not set attributes that are not specified by fields', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('should not set attributes that are not specified by fields', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, email: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - email: 'email' - }).then(user => { - return user.update({ - bio: 'heyo', - email: 'heho' - }, { - fields: ['bio'] - }); - }).then(user => { - expect(user.get('name')).to.equal('snafu'); - expect(user.get('email')).to.equal('email'); - expect(user.get('bio')).to.equal('heyo'); - }); + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'snafu', + email: 'email' }); - }); - it('updates attributes in the database', function() { - return this.User.create({ username: 'user' }).then(user => { - expect(user.username).to.equal('user'); - return user.update({ username: 'person' }).then(user => { - expect(user.username).to.equal('person'); - }); + const user = await user0.update({ + bio: 'heyo', + email: 'heho' + }, { + fields: ['bio'] }); + + expect(user.get('name')).to.equal('snafu'); + expect(user.get('email')).to.equal('email'); + expect(user.get('bio')).to.equal('heyo'); }); - it('ignores unknown attributes', function() { - return this.User.create({ username: 'user' }).then(user => { - return user.update({ username: 'person', foo: 'bar' }).then(user => { - expect(user.username).to.equal('person'); - expect(user.foo).not.to.exist; - }); - }); + it('updates attributes in the database', async function() { + const user = await this.User.create({ username: 'user' }); + expect(user.username).to.equal('user'); + const user0 = await user.update({ username: 'person' }); + expect(user0.username).to.equal('person'); }); - it('ignores undefined attributes', function() { - return this.User.sync({ force: true }).then(() => { - return this.User.create({ username: 'user' }).then(user => { - return user.update({ username: undefined }).then(user => { - expect(user.username).to.equal('user'); - }); - }); - }); + it('ignores unknown attributes', async function() { + const user = await this.User.create({ username: 'user' }); + const user0 = await user.update({ username: 'person', foo: 'bar' }); + expect(user0.username).to.equal('person'); + expect(user0.foo).not.to.exist; + }); + + it('ignores undefined attributes', async function() { + await this.User.sync({ force: true }); + const user = await this.User.create({ username: 'user' }); + const user0 = await user.update({ username: undefined }); + expect(user0.username).to.equal('user'); }); - it('doesn\'t update primary keys or timestamps', function() { - const User = this.sequelize.define(`User${ config.rand()}`, { + it('doesn\'t update primary keys or timestamps', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { name: DataTypes.STRING, bio: DataTypes.TEXT, identifier: { type: DataTypes.STRING, primaryKey: true } }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'snafu', - identifier: 'identifier' - }); - }).then(user => { - const oldCreatedAt = user.createdAt, - oldUpdatedAt = user.updatedAt, - oldIdentifier = user.identifier; - - this.clock.tick(1000); - return user.update({ - name: 'foobar', - createdAt: new Date(2000, 1, 1), - identifier: 'another identifier' - }).then(user => { - expect(new Date(user.createdAt)).to.equalDate(new Date(oldCreatedAt)); - expect(new Date(user.updatedAt)).to.not.equalTime(new Date(oldUpdatedAt)); - expect(user.identifier).to.equal(oldIdentifier); - }); + await User.sync({ force: true }); + + const user = await User.create({ + name: 'snafu', + identifier: 'identifier' + }); + + const oldCreatedAt = user.createdAt, + oldUpdatedAt = user.updatedAt, + oldIdentifier = user.identifier; + + this.clock.tick(1000); + + const user0 = await user.update({ + name: 'foobar', + createdAt: new Date(2000, 1, 1), + identifier: 'another identifier' }); + + expect(new Date(user0.createdAt)).to.equalDate(new Date(oldCreatedAt)); + expect(new Date(user0.updatedAt)).to.not.equalTime(new Date(oldUpdatedAt)); + expect(user0.identifier).to.equal(oldIdentifier); }); - it('stores and restores null values', function() { + it('stores and restores null values', async function() { const Download = this.sequelize.define('download', { startedAt: DataTypes.DATE, canceledAt: DataTypes.DATE, finishedAt: DataTypes.DATE }); - return Download.sync().then(() => { - return Download.create({ - startedAt: new Date() - }).then(download => { - expect(download.startedAt instanceof Date).to.be.true; - expect(download.canceledAt).to.not.be.ok; - expect(download.finishedAt).to.not.be.ok; - - return download.update({ - canceledAt: new Date() - }).then(download => { - expect(download.startedAt instanceof Date).to.be.true; - expect(download.canceledAt instanceof Date).to.be.true; - expect(download.finishedAt).to.not.be.ok; - - return Download.findAll({ - where: { finishedAt: null } - }).then(downloads => { - downloads.forEach(download => { - expect(download.startedAt instanceof Date).to.be.true; - expect(download.canceledAt instanceof Date).to.be.true; - expect(download.finishedAt).to.not.be.ok; - }); - }); - }); - }); + await Download.sync(); + + const download = await Download.create({ + startedAt: new Date() + }); + + expect(download.startedAt instanceof Date).to.be.true; + expect(download.canceledAt).to.not.be.ok; + expect(download.finishedAt).to.not.be.ok; + + const download0 = await download.update({ + canceledAt: new Date() + }); + + expect(download0.startedAt instanceof Date).to.be.true; + expect(download0.canceledAt instanceof Date).to.be.true; + expect(download0.finishedAt).to.not.be.ok; + + const downloads = await Download.findAll({ + where: { finishedAt: null } + }); + + downloads.forEach(download => { + expect(download.startedAt instanceof Date).to.be.true; + expect(download.canceledAt instanceof Date).to.be.true; + expect(download.finishedAt).to.not.be.ok; }); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.create({}).then(user => { - return user.update({ username: 'yolo' }, { logging: spy }).then(() => { - expect(spy.called).to.be.ok; - }); - }); + const user = await this.User.create({}); + await user.update({ username: 'yolo' }, { logging: spy }); + expect(spy.called).to.be.ok; }); }); }); diff --git a/test/integration/instance/values.test.js b/test/integration/instance/values.test.js index fd4b379fe75a..6995eff1cebd 100644 --- a/test/integration/instance/values.test.js +++ b/test/integration/instance/values.test.js @@ -15,7 +15,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { name: { type: DataTypes.STRING } }); - const user = new User({ id: 1, name: 'Mick' }); + const user = User.build({ id: 1, name: 'Mick' }); expect(user.get('id')).to.equal(1); expect(user.get('name')).to.equal('Mick'); @@ -32,7 +32,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { identifier: { type: DataTypes.STRING, primaryKey: true } }); - const user = new User({ identifier: 'identifier' }); + const user = User.build({ identifier: 'identifier' }); expect(user.get('identifier')).to.equal('identifier'); user.set('identifier', 'another identifier'); @@ -44,7 +44,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { identifier: { type: DataTypes.STRING, primaryKey: true } }); - const user = new User({}, { + const user = User.build({}, { isNewRecord: false }); @@ -64,7 +64,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { underscored: true }); - const user = new User({}, { + const user = User.build({}, { isNewRecord: false }); @@ -89,7 +89,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const user = new User(); + const user = User.build(); user.set({ name: 'antonio banderaz', @@ -105,7 +105,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { expect(user.dataValues.email).not.to.be.ok; }); - it('allows use of sequelize.fn and sequelize.col in date and bool fields', function() { + it('allows use of sequelize.fn and sequelize.col in date and bool fields', async function() { const User = this.sequelize.define('User', { d: DataTypes.DATE, b: DataTypes.BOOLEAN, @@ -115,30 +115,26 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }, { timestamps: false }); - return User.sync({ force: true }).then(() => { - return User.create({}).then(user => { - // Create the user first to set the proper default values. PG does not support column references in insert, - // so we must create a record with the right value for always_false, then reference it in an update - let now = dialect === 'sqlite' ? this.sequelize.fn('', this.sequelize.fn('datetime', 'now')) : this.sequelize.fn('NOW'); - if (dialect === 'mssql') { - now = this.sequelize.fn('', this.sequelize.fn('getdate')); - } - user.set({ - d: now, - b: this.sequelize.col('always_false') - }); - - expect(user.get('d')).to.be.instanceof(Sequelize.Utils.Fn); - expect(user.get('b')).to.be.instanceof(Sequelize.Utils.Col); - - return user.save().then(() => { - return user.reload().then(() => { - expect(user.d).to.equalDate(new Date()); - expect(user.b).to.equal(false); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}); + // Create the user first to set the proper default values. PG does not support column references in insert, + // so we must create a record with the right value for always_false, then reference it in an update + let now = dialect === 'sqlite' ? this.sequelize.fn('', this.sequelize.fn('datetime', 'now')) : this.sequelize.fn('NOW'); + if (dialect === 'mssql') { + now = this.sequelize.fn('', this.sequelize.fn('getdate')); + } + user.set({ + d: now, + b: this.sequelize.col('always_false') }); + + expect(user.get('d')).to.be.instanceof(Sequelize.Utils.Fn); + expect(user.get('b')).to.be.instanceof(Sequelize.Utils.Col); + + await user.save(); + await user.reload(); + expect(user.d).to.equalDate(new Date()); + expect(user.b).to.equal(false); }); describe('includes', () => { @@ -157,7 +153,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.hasMany(Tag); Product.belongsTo(User); - const product = new Product({}, { + const product = Product.build({}, { include: [ User, Tag @@ -200,7 +196,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.hasMany(Tag); Product.belongsTo(User); - const product = new Product({}, { + const product = Product.build({}, { include: [ User, Tag @@ -241,7 +237,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const product = new Product({ + const product = Product.build({ price: 10 }); expect(product.get('price')).to.equal(1000); @@ -260,7 +256,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const product = new Product({ + const product = Product.build({ priceInCents: 1000 }); expect(product.get('price')).to.equal(10); @@ -282,13 +278,13 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - const product = new Product({ + const product = Product.build({ price: 10 }); expect(product.toJSON()).to.deep.equal({ withTaxes: 1250, price: 1000, id: null }); }); - it('should work with save', function() { + it('should work with save', async function() { const Contact = this.sequelize.define('Contact', { first: { type: Sequelize.STRING }, last: { type: Sequelize.STRING }, @@ -304,18 +300,16 @@ describe(Support.getTestDialectTeaser('DAO'), () => { } }); - return this.sequelize.sync().then(() => { - const contact = new Contact({ - first: 'My', - last: 'Name', - tags: ['yes', 'no'] - }); - expect(contact.get('tags')).to.deep.equal(['yes', 'no']); - - return contact.save().then(me => { - expect(me.get('tags')).to.deep.equal(['yes', 'no']); - }); + await this.sequelize.sync(); + const contact = Contact.build({ + first: 'My', + last: 'Name', + tags: ['yes', 'no'] }); + expect(contact.get('tags')).to.deep.equal(['yes', 'no']); + + const me = await contact.save(); + expect(me.get('tags')).to.deep.equal(['yes', 'no']); }); describe('plain', () => { @@ -330,7 +324,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.belongsTo(User); - const product = new Product({}, { + const product = Product.build({}, { include: [ User ] @@ -357,7 +351,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { title: Sequelize.STRING }); - const product = new Product({ + const product = Product.build({ id: 1, title: 'Chair' }, { raw: true }); @@ -401,7 +395,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { Product.belongsTo(User); - const product = new Product({}, { + const product = Product.build({}, { include: [ User ] @@ -432,22 +426,18 @@ describe(Support.getTestDialectTeaser('DAO'), () => { }); describe('changed', () => { - it('should return false if object was built from database', function() { + it('should return false if object was built from database', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING } }); - return User.sync().then(() => { - return User.create({ name: 'Jan Meier' }).then(user => { - expect(user.changed('name')).to.be.false; - expect(user.changed()).not.to.be.ok; - }); - }).then(() => { - return User.bulkCreate([{ name: 'Jan Meier' }]).then(([user]) => { - expect(user.changed('name')).to.be.false; - expect(user.changed()).not.to.be.ok; - }); - }); + await User.sync(); + const user0 = await User.create({ name: 'Jan Meier' }); + expect(user0.changed('name')).to.be.false; + expect(user0.changed()).not.to.be.ok; + const [user] = await User.bulkCreate([{ name: 'Jan Meier' }]); + expect(user.changed('name')).to.be.false; + expect(user.changed()).not.to.be.ok; }); it('should return true if previous value is different', function() { @@ -455,7 +445,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { name: { type: DataTypes.STRING } }); - const user = new User({ + const user = User.build({ name: 'Jan Meier' }); user.set('name', 'Mick Hansen'); @@ -463,51 +453,49 @@ describe(Support.getTestDialectTeaser('DAO'), () => { expect(user.changed()).to.be.ok; }); - it('should return false immediately after saving', function() { + it('should return false immediately after saving', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING } }); - return User.sync().then(() => { - const user = new User({ - name: 'Jan Meier' - }); - user.set('name', 'Mick Hansen'); - expect(user.changed('name')).to.be.true; - expect(user.changed()).to.be.ok; - - return user.save().then(() => { - expect(user.changed('name')).to.be.false; - expect(user.changed()).not.to.be.ok; - }); + await User.sync(); + const user = User.build({ + name: 'Jan Meier' }); + user.set('name', 'Mick Hansen'); + expect(user.changed('name')).to.be.true; + expect(user.changed()).to.be.ok; + + await user.save(); + expect(user.changed('name')).to.be.false; + expect(user.changed()).not.to.be.ok; }); - it('should be available to a afterUpdate hook', function() { + it('should be available to a afterUpdate hook', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING } }); let changed; - User.hooks.add('afterUpdate', instance => { + User.afterUpdate(instance => { changed = instance.changed(); return; }); - return User.sync({ force: true }).then(() => { - return User.create({ - name: 'Ford Prefect' - }); - }).then(user => { - return user.update({ - name: 'Arthur Dent' - }); - }).then(user => { - expect(changed).to.be.ok; - expect(changed.length).to.be.ok; - expect(changed).to.include('name'); - expect(user.changed()).not.to.be.ok; + await User.sync({ force: true }); + + const user0 = await User.create({ + name: 'Ford Prefect' }); + + const user = await user0.update({ + name: 'Arthur Dent' + }); + + expect(changed).to.be.ok; + expect(changed.length).to.be.ok; + expect(changed).to.include('name'); + expect(user.changed()).not.to.be.ok; }); }); @@ -518,7 +506,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { title: { type: DataTypes.STRING } }); - const user = new User({ + const user = User.build({ name: 'Jan Meier', title: 'Mr' }); @@ -534,7 +522,7 @@ describe(Support.getTestDialectTeaser('DAO'), () => { name: { type: DataTypes.STRING } }); - const user = new User({ + const user = User.build({ name: 'Jan Meier' }); user.set('name', 'Mick Hansen'); diff --git a/test/integration/json.test.js b/test/integration/json.test.js index 9deb81e81ab8..37a511e5d4db 100644 --- a/test/integration/json.test.js +++ b/test/integration/json.test.js @@ -11,7 +11,7 @@ const chai = require('chai'), describe('model', () => { if (current.dialect.supports.JSON) { describe('json', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, emergency_contact: DataTypes.JSON, @@ -20,21 +20,19 @@ describe('model', () => { this.Order = this.sequelize.define('Order'); this.Order.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should tell me that a column is json', function() { - return this.sequelize.queryInterface.describeTable('Users') - .then(table => { - // expected for mariadb 10.4 : https://jira.mariadb.org/browse/MDEV-15558 - if (dialect !== 'mariadb') { - expect(table.emergency_contact.type).to.equal('JSON'); - } - }); + it('should tell me that a column is json', async function() { + const table = await this.sequelize.queryInterface.describeTable('Users'); + // expected for mariadb 10.4 : https://jira.mariadb.org/browse/MDEV-15558 + if (dialect !== 'mariadb') { + expect(table.emergency_contact.type).to.equal('JSON'); + } }); - it('should use a placeholder for json with insert', function() { - return this.User.create({ + it('should use a placeholder for json with insert', async function() { + await this.User.create({ username: 'bob', emergency_contact: { name: 'joe', phones: [1337, 42] } }, { @@ -49,246 +47,226 @@ describe('model', () => { }); }); - it('should insert json using a custom field name', function() { + it('should insert json using a custom field name', async function() { this.UserFields = this.sequelize.define('UserFields', { emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' } }); - return this.UserFields.sync({ force: true }).then(() => { - return this.UserFields.create({ - emergencyContact: { name: 'joe', phones: [1337, 42] } - }).then(user => { - expect(user.emergencyContact.name).to.equal('joe'); - }); + await this.UserFields.sync({ force: true }); + + const user = await this.UserFields.create({ + emergencyContact: { name: 'joe', phones: [1337, 42] } }); + + expect(user.emergencyContact.name).to.equal('joe'); }); - it('should update json using a custom field name', function() { + it('should update json using a custom field name', async function() { this.UserFields = this.sequelize.define('UserFields', { emergencyContact: { type: DataTypes.JSON, field: 'emergy_contact' } }); - return this.UserFields.sync({ force: true }).then(() => { - return this.UserFields.create({ - emergencyContact: { name: 'joe', phones: [1337, 42] } - }).then(user => { - user.emergencyContact = { name: 'larry' }; - return user.save(); - }).then(user => { - expect(user.emergencyContact.name).to.equal('larry'); - }); + await this.UserFields.sync({ force: true }); + + const user0 = await this.UserFields.create({ + emergencyContact: { name: 'joe', phones: [1337, 42] } }); + + user0.emergencyContact = { name: 'larry' }; + const user = await user0.save(); + expect(user.emergencyContact.name).to.equal('larry'); }); - it('should be able retrieve json value as object', function() { + it('should be able retrieve json value as object', async function() { const emergencyContact = { name: 'kate', phone: 1337 }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ where: { username: 'swen' }, attributes: ['emergency_contact'] }); - }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user0.emergency_contact).to.eql(emergencyContact); + const user = await this.User.findOne({ where: { username: 'swen' }, attributes: ['emergency_contact'] }); + expect(user.emergency_contact).to.eql(emergencyContact); }); - it('should be able to retrieve element of array by index', function() { + it('should be able to retrieve element of array by index', async function() { const emergencyContact = { name: 'kate', phones: [1337, 42] }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.phones[1]'), 'firstEmergencyNumber']] - }); - }) - .then(user => { - expect(parseInt(user.getDataValue('firstEmergencyNumber'), 10)).to.equal(42); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user0.emergency_contact).to.eql(emergencyContact); + + const user = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.phones[1]'), 'firstEmergencyNumber']] + }); + + expect(parseInt(user.getDataValue('firstEmergencyNumber'), 10)).to.equal(42); }); - it('should be able to retrieve root level value of an object by key', function() { + it('should be able to retrieve root level value of an object by key', async function() { const emergencyContact = { kate: 1337 }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.kate'), 'katesNumber']] - }); - }) - .then(user => { - expect(parseInt(user.getDataValue('katesNumber'), 10)).to.equal(1337); - }); + const user0 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user0.emergency_contact).to.eql(emergencyContact); + + const user = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.kate'), 'katesNumber']] + }); + + expect(parseInt(user.getDataValue('katesNumber'), 10)).to.equal(1337); }); - it('should be able to retrieve nested value of an object by path', function() { + it('should be able to retrieve nested value of an object by path', async function() { const emergencyContact = { kate: { email: 'kate@kate.com', phones: [1337, 42] } }; - return this.User.create({ username: 'swen', emergency_contact: emergencyContact }) - .then(user => { - expect(user.emergency_contact).to.eql(emergencyContact); - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.kate.email'), 'katesEmail']] - }); - }).then(user => { - expect(user.getDataValue('katesEmail')).to.equal('kate@kate.com'); - }).then(() => { - return this.User.findOne({ - where: { username: 'swen' }, - attributes: [[Sequelize.json('emergency_contact.kate.phones[1]'), 'katesFirstPhone']] - }); - }).then(user => { - expect(parseInt(user.getDataValue('katesFirstPhone'), 10)).to.equal(42); - }); + const user1 = await this.User.create({ username: 'swen', emergency_contact: emergencyContact }); + expect(user1.emergency_contact).to.eql(emergencyContact); + + const user0 = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.kate.email'), 'katesEmail']] + }); + + expect(user0.getDataValue('katesEmail')).to.equal('kate@kate.com'); + + const user = await this.User.findOne({ + where: { username: 'swen' }, + attributes: [[Sequelize.json('emergency_contact.kate.phones[1]'), 'katesFirstPhone']] + }); + + expect(parseInt(user.getDataValue('katesFirstPhone'), 10)).to.equal(42); }); - it('should be able to retrieve a row based on the values of the json document', function() { - return Sequelize.Promise.all([ + it('should be able to retrieve a row based on the values of the json document', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json('emergency_contact.name', 'kate'), - attributes: ['username', 'emergency_contact'] - }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('kate'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json('emergency_contact.name', 'kate'), + attributes: ['username', 'emergency_contact'] }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using the nested query language', function() { - return Sequelize.Promise.all([ + it('should be able to query using the nested query language', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - where: Sequelize.json({ emergency_contact: { name: 'kate' } }) - }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('kate'); + ]); + + const user = await this.User.findOne({ + where: Sequelize.json({ emergency_contact: { name: 'kate' } }) }); + + expect(user.emergency_contact.name).to.equal('kate'); }); - it('should be able to query using dot notation', function() { - return Sequelize.Promise.all([ + it('should be able to query using dot notation', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: { name: 'kate' } }), this.User.create({ username: 'anna', emergency_contact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.name', 'joe') }); - }).then(user => { - expect(user.emergency_contact.name).to.equal('joe'); - }); + ]); + + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact.name', 'joe') }); + expect(user.emergency_contact.name).to.equal('joe'); }); - it('should be able to query using dot notation with uppercase name', function() { - return Sequelize.Promise.all([ + it('should be able to query using dot notation with uppercase name', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergencyContact: { name: 'kate' } }), this.User.create({ username: 'anna', emergencyContact: { name: 'joe' } }) - ]).then(() => { - return this.User.findOne({ - attributes: [[Sequelize.json('emergencyContact.name'), 'contactName']], - where: Sequelize.json('emergencyContact.name', 'joe') - }); - }).then(user => { - expect(user.get('contactName')).to.equal('joe'); + ]); + + const user = await this.User.findOne({ + attributes: [[Sequelize.json('emergencyContact.name'), 'contactName']], + where: Sequelize.json('emergencyContact.name', 'joe') }); + + expect(user.get('contactName')).to.equal('joe'); }); - it('should be able to query array using property accessor', function() { - return Sequelize.Promise.all([ + it('should be able to query array using property accessor', async function() { + await Promise.all([ this.User.create({ username: 'swen', emergency_contact: ['kate', 'joe'] }), this.User.create({ username: 'anna', emergency_contact: [{ name: 'joe' }] }) - ]).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.0', 'kate') }); - }).then(user => { - expect(user.username).to.equal('swen'); - }).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact[0].name', 'joe') }); - }).then(user => { - expect(user.username).to.equal('anna'); - }); + ]); + + const user0 = await this.User.findOne({ where: Sequelize.json('emergency_contact.0', 'kate') }); + expect(user0.username).to.equal('swen'); + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact[0].name', 'joe') }); + expect(user.username).to.equal('anna'); }); - it('should be able to store values that require JSON escaping', function() { + it('should be able to store values that require JSON escaping', async function() { const text = 'Multi-line \'$string\' needing "escaping" for $$ and $1 type values'; - return this.User.create({ + const user0 = await this.User.create({ username: 'swen', emergency_contact: { value: text } - }).then(user => { - expect(user.isNewRecord).to.equal(false); - }).then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); - }).then(user => { - expect(user.username).to.equal('swen'); }); + + expect(user0.isNewRecord).to.equal(false); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); - it('should be able to findOrCreate with values that require JSON escaping', function() { + it('should be able to findOrCreate with values that require JSON escaping', async function() { const text = 'Multi-line \'$string\' needing "escaping" for $$ and $1 type values'; - return this.User.findOrCreate({ + const user0 = await this.User.findOrCreate({ where: { username: 'swen' }, defaults: { emergency_contact: { value: text } } - }).then(user => { - expect(!user.isNewRecord).to.equal(true); - }).then(() => { - return this.User.findOne({ where: { username: 'swen' } }); - }).then(() => { - return this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); - }).then(user => { - expect(user.username).to.equal('swen'); }); + + expect(!user0.isNewRecord).to.equal(true); + await this.User.findOne({ where: { username: 'swen' } }); + const user = await this.User.findOne({ where: Sequelize.json('emergency_contact.value', text) }); + expect(user.username).to.equal('swen'); }); // JSONB Supports this, but not JSON in postgres/mysql if (current.dialect.name === 'sqlite') { - it('should be able to find with just string', function() { - return this.User.create({ + it('should be able to find with just string', async function() { + await this.User.create({ username: 'swen123', emergency_contact: 'Unknown' - }).then(() => { - return this.User.findOne({ where: { - emergency_contact: 'Unknown' - } }); - }).then(user => { - expect(user.username).to.equal('swen123'); }); + + const user = await this.User.findOne({ where: { + emergency_contact: 'Unknown' + } }); + + expect(user.username).to.equal('swen123'); }); } - it('should be able retrieve json value with nested include', function() { - return this.User.create({ + it('should be able retrieve json value with nested include', async function() { + const user = await this.User.create({ emergency_contact: { name: 'kate' } - }).then(user => { - return this.Order.create({ UserId: user.id }); - }).then(() => { - return this.Order.findAll({ - attributes: ['id'], - include: [{ - model: this.User, - attributes: [ - [this.sequelize.json('emergency_contact.name'), 'katesName'] - ] - }] - }); - }).then(orders => { - expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); + + await this.Order.create({ UserId: user.id }); + + const orders = await this.Order.findAll({ + attributes: ['id'], + include: [{ + model: this.User, + attributes: [ + [this.sequelize.json('emergency_contact.name'), 'katesName'] + ] + }] + }); + + expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); }); } if (current.dialect.supports.JSONB) { describe('jsonb', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, emergency_contact: DataTypes.JSONB @@ -296,29 +274,29 @@ describe('model', () => { this.Order = this.sequelize.define('Order'); this.Order.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should be able retrieve json value with nested include', function() { - return this.User.create({ + it('should be able retrieve json value with nested include', async function() { + const user = await this.User.create({ emergency_contact: { name: 'kate' } - }).then(user => { - return this.Order.create({ UserId: user.id }); - }).then(() => { - return this.Order.findAll({ - attributes: ['id'], - include: [{ - model: this.User, - attributes: [ - [this.sequelize.json('emergency_contact.name'), 'katesName'] - ] - }] - }); - }).then(orders => { - expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); + + await this.Order.create({ UserId: user.id }); + + const orders = await this.Order.findAll({ + attributes: ['id'], + include: [{ + model: this.User, + attributes: [ + [this.sequelize.json('emergency_contact.name'), 'katesName'] + ] + }] + }); + + expect(orders[0].User.getDataValue('katesName')).to.equal('kate'); }); }); } diff --git a/test/integration/model.test.js b/test/integration/model.test.js old mode 100755 new mode 100644 index ae16e9d0b4ee..d6020747b468 --- a/test/integration/model.test.js +++ b/test/integration/model.test.js @@ -6,15 +6,18 @@ const chai = require('chai'), Support = require('./support'), DataTypes = require('../../lib/data-types'), dialect = Support.getTestDialect(), + errors = require('../../lib/errors'), sinon = require('sinon'), _ = require('lodash'), moment = require('moment'), - Promise = require('bluebird'), current = Support.sequelize, Op = Sequelize.Op, - semver = require('semver'); - + semver = require('semver'), + pMap = require('p-map'); + describe(Support.getTestDialectTeaser('Model'), () => { + let isMySQL8; + before(function() { this.clock = sinon.useFakeTimers(); }); @@ -23,7 +26,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { + isMySQL8 = dialect === 'mysql' && semver.satisfies(current.options.databaseVersion, '>=8.0.0'); + this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -33,7 +38,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { aBool: DataTypes.BOOLEAN }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('constructor', () => { @@ -57,7 +62,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(factorySize).to.equal(factorySize2); }); - it('allows us to predefine the ID column with our own specs', function() { + it('allows us to predefine the ID column with our own specs', async function() { const User = this.sequelize.define('UserCol', { id: { type: Sequelize.STRING, @@ -66,9 +71,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return expect(User.create({ id: 'My own ID!' })).to.eventually.have.property('id', 'My own ID!'); - }); + await User.sync({ force: true }); + expect(await User.create({ id: 'My own ID!' })).to.have.property('id', 'My own ID!'); }); it('throws an error if 2 autoIncrements are passed', function() { @@ -104,7 +108,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }).to.throw(Error, 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field'); }); - it('should allow me to set a default value for createdAt and updatedAt', function() { + it('should allow me to set a default value for createdAt and updatedAt', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER, createdAt: { @@ -117,26 +121,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, { timestamps: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create({ aNumber: 5 }).then(user => { - return UserTable.bulkCreate([ - { aNumber: 10 }, - { aNumber: 12 } - ]).then(() => { - return UserTable.findAll({ where: { aNumber: { [Op.gte]: 10 } } }).then(users => { - expect(moment(user.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); - expect(moment(user.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); - users.forEach(u => { - expect(moment(u.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); - expect(moment(u.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); - }); - }); - }); - }); - }); + await UserTable.sync({ force: true }); + const user = await UserTable.create({ aNumber: 5 }); + await UserTable.bulkCreate([{ aNumber: 10 }, { aNumber: 12 }]); + const users = await UserTable.findAll({ where: { aNumber: { [Op.gte]: 10 } } }); + expect(moment(user.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); + expect(moment(user.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); + for (const u of users) { + expect(moment(u.createdAt).format('YYYY-MM-DD')).to.equal('2012-01-01'); + expect(moment(u.updatedAt).format('YYYY-MM-DD')).to.equal('2012-01-02'); + } }); - it('should allow me to set a function as default value', function() { + it('should allow me to set a function as default value', async function() { const defaultFunction = sinon.stub().returns(5); const UserTable = this.sequelize.define('UserCol', { aNumber: { @@ -145,18 +142,55 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }, { timestamps: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create().then(user => { - return UserTable.create().then(user2 => { - expect(user.aNumber).to.equal(5); - expect(user2.aNumber).to.equal(5); - expect(defaultFunction.callCount).to.equal(2); - }); - }); - }); + await UserTable.sync({ force: true }); + const user = await UserTable.create(); + const user2 = await UserTable.create(); + expect(user.aNumber).to.equal(5); + expect(user2.aNumber).to.equal(5); + expect(defaultFunction.callCount).to.equal(2); + }); + + it('should throw `TypeError` when value for updatedAt, createdAt, or deletedAt is neither string nor boolean', async function() { + const modelName = 'UserCol'; + const attributes = { aNumber: Sequelize.INTEGER }; + + expect(() => { + this.sequelize.define(modelName, attributes, { timestamps: true, updatedAt: {} }); + }).to.throw(Error, 'Value for "updatedAt" option must be a string or a boolean, got object'); + expect(() => { + this.sequelize.define(modelName, attributes, { timestamps: true, createdAt: 100 }); + }).to.throw(Error, 'Value for "createdAt" option must be a string or a boolean, got number'); + expect(() => { + this.sequelize.define(modelName, attributes, { timestamps: true, deletedAt: () => {} }); + }).to.throw(Error, 'Value for "deletedAt" option must be a string or a boolean, got function'); + }); + + it('should allow me to use `true` as a value for updatedAt, createdAt, and deletedAt fields', async function() { + const UserTable = this.sequelize.define( + 'UserCol', + { + aNumber: Sequelize.INTEGER + }, + { + timestamps: true, + updatedAt: true, + createdAt: true, + deletedAt: true, + paranoid: true + } + ); + + await UserTable.sync({ force: true }); + const user = await UserTable.create({ aNumber: 4 }); + expect(user['true']).to.not.exist; + expect(user.updatedAt).to.exist; + expect(user.createdAt).to.exist; + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAt).to.exist; }); - it('should allow me to override updatedAt, createdAt, and deletedAt fields', function() { + it('should allow me to override updatedAt, createdAt, and deletedAt fields', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER }, { @@ -167,20 +201,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create({ aNumber: 4 }).then(user => { - expect(user.updatedOn).to.exist; - expect(user.dateCreated).to.exist; - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAtThisTime).to.exist; - }); - }); - }); - }); + await UserTable.sync({ force: true }); + const user = await UserTable.create({ aNumber: 4 }); + expect(user.updatedOn).to.exist; + expect(user.dateCreated).to.exist; + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAtThisTime).to.exist; }); - it('should allow me to disable some of the timestamp fields', function() { + it('should allow me to disable some of the timestamp fields', async function() { const UpdatingUser = this.sequelize.define('UpdatingUser', { name: DataTypes.STRING }, { @@ -191,27 +221,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return UpdatingUser.sync({ force: true }).then(() => { - return UpdatingUser.create({ - name: 'heyo' - }).then(user => { - expect(user.createdAt).not.to.exist; - expect(user.false).not.to.exist; // because, you know we might accidentally add a field named 'false' - - user.name = 'heho'; - return user.save().then(user => { - expect(user.updatedAt).not.to.exist; - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAtThisTime).to.exist; - }); - }); - }); - }); - }); + await UpdatingUser.sync({ force: true }); + let user = await UpdatingUser.create({ name: 'heyo' }); + expect(user.createdAt).not.to.exist; + expect(user.false).not.to.exist; // because, you know we might accidentally add a field named 'false' + user.name = 'heho'; + user = await user.save(); + expect(user.updatedAt).not.to.exist; + await user.destroy(); + await user.reload({ paranoid: false }); + expect(user.deletedAtThisTime).to.exist; }); - it('returns proper defaultValues after save when setter is set', function() { + it('returns proper defaultValues after save when setter is set', async function() { const titleSetter = sinon.spy(), Task = this.sequelize.define('TaskBuild', { title: { @@ -225,16 +247,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Task.sync({ force: true }).then(() => { - return new Task().save().then(record => { - expect(record.title).to.be.a('string'); - expect(record.title).to.equal(''); - expect(titleSetter.notCalled).to.be.ok; // The setter method should not be invoked for default values - }); - }); + await Task.sync({ force: true }); + const record = await Task.build().save(); + expect(record.title).to.be.a('string'); + expect(record.title).to.equal(''); + expect(titleSetter.notCalled).to.be.ok; // The setter method should not be invoked for default values }); - it('should work with both paranoid and underscored being true', function() { + it('should work with both paranoid and underscored being true', async function() { const UserTable = this.sequelize.define('UserCol', { aNumber: Sequelize.INTEGER }, { @@ -242,16 +262,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { underscored: true }); - return UserTable.sync({ force: true }).then(() => { - return UserTable.create({ aNumber: 30 }).then(() => { - return UserTable.count().then(c => { - expect(c).to.equal(1); - }); - }); - }); + await UserTable.sync({ force: true }); + await UserTable.create({ aNumber: 30 }); + expect(await UserTable.count()).to.equal(1); }); - it('allows multiple column unique keys to be defined', function() { + it('allows multiple column unique keys to be defined', async function() { const User = this.sequelize.define('UserWithUniqueUsername', { username: { type: Sequelize.STRING, unique: 'user_and_email' }, email: { type: Sequelize.STRING, unique: 'user_and_email' }, @@ -259,7 +275,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { bCol: { type: Sequelize.STRING, unique: 'a_and_b' } }); - return User.sync({ force: true, logging: _.after(2, _.once(sql => { + await User.sync({ force: true, logging: _.after(2, _.once(sql => { if (dialect === 'mssql') { expect(sql).to.match(/CONSTRAINT\s*([`"[]?user_and_email[`"\]]?)?\s*UNIQUE\s*\([`"[]?username[`"\]]?, [`"[]?email[`"\]]?\)/); expect(sql).to.match(/CONSTRAINT\s*([`"[]?a_and_b[`"\]]?)?\s*UNIQUE\s*\([`"[]?aCol[`"\]]?, [`"[]?bCol[`"\]]?\)/); @@ -270,61 +286,63 @@ describe(Support.getTestDialectTeaser('Model'), () => { })) }); }); - it('allows unique on column with field aliases', function() { + it('allows unique on column with field aliases', async function() { const User = this.sequelize.define('UserWithUniqueFieldAlias', { userName: { type: Sequelize.STRING, unique: 'user_name_unique', field: 'user_name' } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.queryInterface.showIndex(User.tableName).then(indexes => { - let idxUnique; - if (dialect === 'sqlite') { - expect(indexes).to.have.length(1); - idxUnique = indexes[0]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: undefined }]); - } else if (dialect === 'mysql') { - expect(indexes).to.have.length(2); - idxUnique = indexes[1]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: 'ASC' }]); - expect(idxUnique.type).to.equal('BTREE'); - } else if (dialect === 'postgres') { - expect(indexes).to.have.length(2); - idxUnique = indexes[1]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, order: undefined, length: undefined }]); - } else if (dialect === 'mssql') { - expect(indexes).to.have.length(2); - idxUnique = indexes[1]; - expect(idxUnique.primary).to.equal(false); - expect(idxUnique.unique).to.equal(true); - expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, length: undefined, order: 'ASC' }]); - } - }); - }); + await User.sync({ force: true }); + const indexes = await this.sequelize.queryInterface.showIndex(User.tableName); + let idxUnique; + if (dialect === 'sqlite') { + expect(indexes).to.have.length(1); + idxUnique = indexes[0]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: undefined }]); + } else if (dialect === 'mysql') { + expect(indexes).to.have.length(2); + idxUnique = indexes[1]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', length: undefined, order: 'ASC' }]); + expect(idxUnique.type).to.equal('BTREE'); + } else if (dialect === 'postgres') { + expect(indexes).to.have.length(2); + idxUnique = indexes[1]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, order: undefined, length: undefined }]); + } else if (dialect === 'mssql') { + expect(indexes).to.have.length(2); + idxUnique = indexes[1]; + expect(idxUnique.primary).to.equal(false); + expect(idxUnique.unique).to.equal(true); + expect(idxUnique.fields).to.deep.equal([{ attribute: 'user_name', collate: undefined, length: undefined, order: 'ASC' }]); + } }); - it('allows us to customize the error message for unique constraint', function() { + it('allows us to customize the error message for unique constraint', async function() { const User = this.sequelize.define('UserWithUniqueUsername', { username: { type: Sequelize.STRING, unique: { name: 'user_and_email', msg: 'User and email must be unique' } }, email: { type: Sequelize.STRING, unique: 'user_and_email' } }); - return User.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ + await User.sync({ force: true }); + + try { + await Promise.all([ User.create({ username: 'tobi', email: 'tobi@tobi.me' }), - User.create({ username: 'tobi', email: 'tobi@tobi.me' })]); - }).catch(Sequelize.UniqueConstraintError, err => { + User.create({ username: 'tobi', email: 'tobi@tobi.me' }) + ]); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err.message).to.equal('User and email must be unique'); - }); + } }); // If you use migrations to create unique indexes that have explicit names and/or contain fields // that have underscore in their name. Then sequelize must use the index name to map the custom message to the error thrown from db. - it('allows us to map the customized error message with unique constraint name', function() { + it('allows us to map the customized error message with unique constraint name', async function() { // Fake migration style index creation with explicit index definition let User = this.sequelize.define('UserWithUniqueUsername', { user_id: { type: Sequelize.INTEGER }, @@ -340,26 +358,112 @@ describe(Support.getTestDialectTeaser('Model'), () => { }] }); - return User.sync({ force: true }).then(() => { - // Redefine the model to use the index in database and override error message - User = this.sequelize.define('UserWithUniqueUsername', { - user_id: { type: Sequelize.INTEGER, unique: { name: 'user_and_email_index', msg: 'User and email must be unique' } }, - email: { type: Sequelize.STRING, unique: 'user_and_email_index' } - }); - return Sequelize.Promise.all([ + await User.sync({ force: true }); + + // Redefine the model to use the index in database and override error message + User = this.sequelize.define('UserWithUniqueUsername', { + user_id: { type: Sequelize.INTEGER, unique: { name: 'user_and_email_index', msg: 'User and email must be unique' } }, + email: { type: Sequelize.STRING, unique: 'user_and_email_index' } + }); + + try { + await Promise.all([ User.create({ user_id: 1, email: 'tobi@tobi.me' }), - User.create({ user_id: 1, email: 'tobi@tobi.me' })]); - }).catch(Sequelize.UniqueConstraintError, err => { + User.create({ user_id: 1, email: 'tobi@tobi.me' }) + ]); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err.message).to.equal('User and email must be unique'); + } + }); + + describe('descending indices (MySQL 8 specific)', ()=>{ + it('complains about missing support for descending indexes', async function() { + if (!isMySQL8) { + return; + } + + const indices = [{ + name: 'a_b_uniq', + unique: true, + method: 'BTREE', + fields: [ + 'fieldB', + { + attribute: 'fieldA', + collate: 'en_US', + order: 'DESC', + length: 5 + } + ] + }]; + + this.sequelize.define('model', { + fieldA: Sequelize.STRING, + fieldB: Sequelize.INTEGER, + fieldC: Sequelize.STRING, + fieldD: Sequelize.STRING + }, { + indexes: indices, + engine: 'MyISAM' + }); + + try { + await this.sequelize.sync(); + expect.fail(); + } catch (e) { + expect(e.message).to.equal('The storage engine for the table doesn\'t support descending indexes'); + } + }); + + it('works fine with InnoDB', async function() { + if (!isMySQL8) { + return; + } + + const indices = [{ + name: 'a_b_uniq', + unique: true, + method: 'BTREE', + fields: [ + 'fieldB', + { + attribute: 'fieldA', + collate: 'en_US', + order: 'DESC', + length: 5 + } + ] + }]; + + this.sequelize.define('model', { + fieldA: Sequelize.STRING, + fieldB: Sequelize.INTEGER, + fieldC: Sequelize.STRING, + fieldD: Sequelize.STRING + }, { + indexes: indices, + engine: 'InnoDB' + }); + + await this.sequelize.sync(); }); }); - it('should allow the user to specify indexes in options', function() { + it('should allow the user to specify indexes in options', async function() { const indices = [{ name: 'a_b_uniq', unique: true, method: 'BTREE', - fields: ['fieldB', { attribute: 'fieldA', collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', order: 'DESC', length: 5 }] + fields: [ + 'fieldB', + { + attribute: 'fieldA', + collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', + order: isMySQL8 ? 'ASC' : 'DESC', + length: 5 + } + ] }]; if (dialect !== 'mssql') { @@ -385,90 +489,85 @@ describe(Support.getTestDialectTeaser('Model'), () => { engine: 'MyISAM' }); - return this.sequelize.sync().then(() => { - return this.sequelize.sync(); // The second call should not try to create the indices again - }).then(() => { - return this.sequelize.queryInterface.showIndex(Model.tableName); - }).then(args => { - let primary, idx1, idx2, idx3; + await this.sequelize.sync(); + await this.sequelize.sync(); // The second call should not try to create the indices again + const args = await this.sequelize.queryInterface.showIndex(Model.tableName); + let primary, idx1, idx2, idx3; - if (dialect === 'sqlite') { - // PRAGMA index_info does not return the primary index - idx1 = args[0]; - idx2 = args[1]; - - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: undefined }, - { attribute: 'fieldA', length: undefined, order: undefined } - ]); - - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined } - ]); - } else if (dialect === 'mssql') { - idx1 = args[0]; + if (dialect === 'sqlite') { + // PRAGMA index_info does not return the primary index + idx1 = args[0]; + idx2 = args[1]; - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: 'ASC', collate: undefined }, - { attribute: 'fieldA', length: undefined, order: 'DESC', collate: undefined } - ]); - } else if (dialect === 'postgres') { - // Postgres returns indexes in alphabetical order - primary = args[2]; - idx1 = args[0]; - idx2 = args[1]; - idx3 = args[2]; - - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: undefined, collate: undefined }, - { attribute: 'fieldA', length: undefined, order: 'DESC', collate: 'en_US' } - ]); - - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined, collate: undefined } - ]); - - expect(idx3.fields).to.deep.equal([ - { attribute: 'fieldD', length: undefined, order: undefined, collate: undefined } - ]); - } else { - // And finally mysql returns the primary first, and then the rest in the order they were defined - primary = args[0]; - idx1 = args[1]; - idx2 = args[2]; + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: undefined }, + { attribute: 'fieldA', length: undefined, order: undefined } + ]); + + expect(idx2.fields).to.deep.equal([ + { attribute: 'fieldC', length: undefined, order: undefined } + ]); + } else if (dialect === 'mssql') { + idx1 = args[0]; + + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: 'ASC', collate: undefined }, + { attribute: 'fieldA', length: undefined, order: 'DESC', collate: undefined } + ]); + } else if (dialect === 'postgres') { + // Postgres returns indexes in alphabetical order + primary = args[2]; + idx1 = args[0]; + idx2 = args[1]; + idx3 = args[2]; + + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: undefined, collate: undefined }, + { attribute: 'fieldA', length: undefined, order: 'DESC', collate: 'en_US' } + ]); - expect(primary.primary).to.be.ok; + expect(idx2.fields).to.deep.equal([ + { attribute: 'fieldC', length: undefined, order: undefined, collate: undefined } + ]); - expect(idx1.type).to.equal('BTREE'); - expect(idx2.type).to.equal('FULLTEXT'); + expect(idx3.fields).to.deep.equal([ + { attribute: 'fieldD', length: undefined, order: undefined, collate: undefined } + ]); + } else { + // And finally mysql returns the primary first, and then the rest in the order they were defined + primary = args[0]; + idx1 = args[1]; + idx2 = args[2]; - expect(idx1.fields).to.deep.equal([ - { attribute: 'fieldB', length: undefined, order: 'ASC' }, - { attribute: 'fieldA', length: 5, order: 'ASC' } - ]); + expect(primary.primary).to.be.ok; - expect(idx2.fields).to.deep.equal([ - { attribute: 'fieldC', length: undefined, order: undefined } - ]); - } + expect(idx1.type).to.equal('BTREE'); + expect(idx2.type).to.equal('FULLTEXT'); - expect(idx1.name).to.equal('a_b_uniq'); - expect(idx1.unique).to.be.ok; + expect(idx1.fields).to.deep.equal([ + { attribute: 'fieldB', length: undefined, order: 'ASC' }, + { attribute: 'fieldA', length: 5, order: 'ASC' } + ]); - if (dialect !== 'mssql') { - expect(idx2.name).to.equal('models_field_c'); - expect(idx2.unique).not.to.be.ok; - } - }); + expect(idx2.fields).to.deep.equal([ + { attribute: 'fieldC', length: undefined, order: undefined } + ]); + } + + expect(idx1.name).to.equal('a_b_uniq'); + expect(idx1.unique).to.be.ok; + + if (dialect !== 'mssql') { + expect(idx2.name).to.equal('models_field_c'); + expect(idx2.unique).not.to.be.ok; + } }); }); - describe('constructor', () => { - it("doesn't create database entries", function() { - new this.User({ username: 'John Wayne' }); - return this.User.findAll().then(users => { - expect(users).to.have.length(0); - }); + describe('build', () => { + it("doesn't create database entries", async function() { + this.User.build({ username: 'John Wayne' }); + expect(await this.User.findAll()).to.have.length(0); }); it('fills the objects with default values', function() { @@ -480,11 +579,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { flag: { type: Sequelize.BOOLEAN, defaultValue: false } }); - expect(new Task().title).to.equal('a task!'); - expect(new Task().foo).to.equal(2); - expect(new Task().bar).to.not.be.ok; - expect(new Task().foobar).to.equal('asd'); - expect(new Task().flag).to.be.false; + expect(Task.build().title).to.equal('a task!'); + expect(Task.build().foo).to.equal(2); + expect(Task.build().bar).to.not.be.ok; + expect(Task.build().foobar).to.equal('asd'); + expect(Task.build().flag).to.be.false; }); it('fills the objects with default values', function() { @@ -495,11 +594,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { foobar: { type: Sequelize.TEXT, defaultValue: 'asd' }, flag: { type: Sequelize.BOOLEAN, defaultValue: false } }, { timestamps: false }); - expect(new Task().title).to.equal('a task!'); - expect(new Task().foo).to.equal(2); - expect(new Task().bar).to.not.be.ok; - expect(new Task().foobar).to.equal('asd'); - expect(new Task().flag).to.be.false; + expect(Task.build().title).to.equal('a task!'); + expect(Task.build().foo).to.equal(2); + expect(Task.build().bar).to.not.be.ok; + expect(Task.build().foobar).to.equal('asd'); + expect(Task.build().flag).to.be.false; }); it('attaches getter and setter methods from attribute definition', function() { @@ -515,9 +614,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - expect(new Product({ price: 42 }).price).to.equal('answer = 84'); + expect(Product.build({ price: 42 }).price).to.equal('answer = 84'); - const p = new Product({ price: 1 }); + const p = Product.build({ price: 1 }); expect(p.price).to.equal('answer = 43'); p.price = 0; @@ -544,8 +643,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - expect(new Product({ price: 20 }).priceInCents).to.equal(20 * 100); - expect(new Product({ priceInCents: 30 * 100 }).price).to.equal(`$${30}`); + expect(Product.build({ price: 20 }).priceInCents).to.equal(20 * 100); + expect(Product.build({ priceInCents: 30 * 100 }).price).to.equal(`$${30}`); }); it('attaches getter and setter methods from options only if not defined in attribute', function() { @@ -567,7 +666,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - const p = new Product({ price1: 1, price2: 2 }); + const p = Product.build({ price1: 1, price2: 2 }); expect(p.price1).to.equal(10); expect(p.price2).to.equal(20); @@ -589,7 +688,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.hasMany(Tag); Product.belongsTo(User); - const product = new Product({ + const product = Product.build({ id: 1, title: 'Chair', Tags: [ @@ -631,7 +730,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.belongsToMany(User, { as: 'followers', through: 'product_followers' }); User.belongsToMany(Product, { as: 'following', through: 'product_followers' }); - const product = new Product({ + const product = Product.build({ id: 1, title: 'Chair', categories: [ @@ -671,149 +770,115 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('findOne', () => { if (current.dialect.supports.transactions) { - it('supports the transaction option in the first parameter', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findOne({ where: { username: 'foo' }, transaction: t }).then(user => { - expect(user).to.not.be.null; - return t.rollback(); - }); - }); - }); - }); + it('supports the transaction option in the first parameter', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { + username: Sequelize.STRING, + foo: Sequelize.STRING }); + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const user = await User.findOne({ where: { username: 'foo' }, transaction: t }); + expect(user).to.not.be.null; + await t.rollback(); }); } - it('should not fail if model is paranoid and where is an empty array', function() { + it('should not fail if model is paranoid and where is an empty array', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }) - .then(() => { - return User.create({ username: 'A fancy name' }); - }) - .then(() => { - return User.findOne({ where: [] }); - }) - .then(u => { - expect(u.username).to.equal('A fancy name'); - }); + await User.sync({ force: true }); + await User.create({ username: 'A fancy name' }); + expect((await User.findOne({ where: [] })).username).to.equal('A fancy name'); }); - // https://github.com/sequelize/sequelize/issues/8406 - it('should work if model is paranoid and only operator in where clause is a Symbol', function() { - const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true } ); - - return User.sync({ force: true }) - .then(() => { - return User.create({ username: 'foo' }); - }) - .then(() => { - return User.findOne({ - where: { - [Op.or]: [ - { username: 'bar' }, - { username: 'baz' } - ] - } - }); - }) - .then(user => { - expect(user).to.not.be.ok; - }); + it('should work if model is paranoid and only operator in where clause is a Symbol (#8406)', async function() { + const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); + + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + expect(await User.findOne({ + where: { + [Op.or]: [ + { username: 'bar' }, + { username: 'baz' } + ] + } + })).to.not.be.ok; }); }); describe('findOrBuild', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findOrBuild({ - where: { username: 'foo' } - }).then(([user1]) => { - return User.findOrBuild({ - where: { username: 'foo' }, - transaction: t - }).then(([user2]) => { - return User.findOrBuild({ - where: { username: 'foo' }, - defaults: { foo: 'asd' }, - transaction: t - }).then(([user3]) => { - expect(user1.isNewRecord).to.be.true; - expect(user2.isNewRecord).to.be.false; - expect(user3.isNewRecord).to.be.false; - return t.commit(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING, foo: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const [user1] = await User.findOrBuild({ + where: { username: 'foo' } + }); + const [user2] = await User.findOrBuild({ + where: { username: 'foo' }, + transaction: t + }); + const [user3] = await User.findOrBuild({ + where: { username: 'foo' }, + defaults: { foo: 'asd' }, + transaction: t + }); + expect(user1.isNewRecord).to.be.true; + expect(user2.isNewRecord).to.be.false; + expect(user3.isNewRecord).to.be.false; + await t.commit(); }); } describe('returns an instance if it already exists', () => { - it('with a single find field', function() { - return this.User.create({ username: 'Username' }).then(user => { - return this.User.findOrBuild({ - where: { username: user.username } - }).then(([_user, initialized]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(initialized).to.be.false; - }); + it('with a single find field', async function() { + const user = await this.User.create({ username: 'Username' }); + const [_user, initialized] = await this.User.findOrBuild({ + where: { username: user.username } }); + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(initialized).to.be.false; }); - it('with multiple find fields', function() { - return this.User.create({ username: 'Username', data: 'data' }).then(user => { - return this.User.findOrBuild({ where: { + it('with multiple find fields', async function() { + const user = await this.User.create({ username: 'Username', data: 'data' }); + const [_user, initialized] = await this.User.findOrBuild({ + where: { username: user.username, data: user.data - } }).then(([_user, initialized]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(_user.data).to.equal('data'); - expect(initialized).to.be.false; - }); + } }); + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(_user.data).to.equal('data'); + expect(initialized).to.be.false; }); - it('builds a new instance with default value.', function() { - const data = { - username: 'Username' - }, - default_values = { - data: 'ThisIsData' - }; - - return this.User.findOrBuild({ - where: data, - defaults: default_values - }).then(([user, initialized]) => { - expect(user.id).to.be.null; - expect(user.username).to.equal('Username'); - expect(user.data).to.equal('ThisIsData'); - expect(initialized).to.be.true; - expect(user.isNewRecord).to.be.true; + it('builds a new instance with default value.', async function() { + const [user, initialized] = await this.User.findOrBuild({ + where: { username: 'Username' }, + defaults: { data: 'ThisIsData' } }); + expect(user.id).to.be.null; + expect(user.username).to.equal('Username'); + expect(user.data).to.equal('ThisIsData'); + expect(initialized).to.be.true; + expect(user.isNewRecord).to.be.true; }); }); }); describe('save', () => { - it('should mapping the correct fields when saving instance. see #10589', function() { + it('should map the correct fields when saving instance (#10589)', async function() { const User = this.sequelize.define('User', { id3: { field: 'id', @@ -832,35 +897,30 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - // Setup - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id3: 94, id: 87, id2: 943 }); - }) - // Test - .then(() => User.findByPk(94)) - .then(user => user.set('id2', 8877)) - .then(user => user.save({ id2: 8877 })) - // Validate - .then(() => User.findByPk(94)) - .then(user => expect(user.id2).to.equal(8877)); + await this.sequelize.sync({ force: true }); + await User.create({ id3: 94, id: 87, id2: 943 }); + const user = await User.findByPk(94); + await user.set('id2', 8877); + await user.save({ id2: 8877 }); + expect((await User.findByPk(94)).id2).to.equal(8877); }); }); describe('update', () => { - it('throws an error if no where clause is given', function() { + it('throws an error if no where clause is given', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return User.update(); - }).then(() => { + await this.sequelize.sync({ force: true }); + try { + await User.update(); throw new Error('Update should throw an error if no where clause is given.'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('Missing where attribute in the options parameter'); - }); + } }); - it('should mapping the correct fields when updating instance. see #10589', function() { + it('should map the correct fields when updating instance (#10589)', async function() { const User = this.sequelize.define('User', { id3: { field: 'id', @@ -879,45 +939,35 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - // Setup - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ id3: 94, id: 87, id2: 943 }); - }) - // Test - .then(() => User.findByPk(94)) - .then(user => { - return user.update({ id2: 8877 }); - }) - // Validate - .then(() => User.findByPk(94)) - .then(user => expect(user.id2).to.equal(8877)); + await this.sequelize.sync({ force: true }); + await User.create({ id3: 94, id: 87, id2: 943 }); + const user = await User.findByPk(94); + await user.update({ id2: 8877 }); + expect((await User.findByPk(94)).id2).to.equal(8877); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(() => { - return sequelize.transaction().then(t => { - return User.update({ username: 'bar' }, { where: { username: 'foo' }, transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1[0].username).to.equal('foo'); - expect(users2[0].username).to.equal('bar'); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + + const t = await sequelize.transaction(); + await User.update({ username: 'bar' }, { + where: { username: 'foo' }, + transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1[0].username).to.equal('foo'); + expect(users2[0].username).to.equal('bar'); + await t.rollback(); }); } - it('updates the attributes that we select only without updating createdAt', function() { + it('updates the attributes that we select only without updating createdAt', async function() { const User = this.sequelize.define('User1', { username: Sequelize.STRING, secretValue: Sequelize.STRING @@ -926,25 +976,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ username: 'Peter', secretValue: '42' }).then(user => { - return user.update({ secretValue: '43' }, { - fields: ['secretValue'], logging(sql) { - test = true; - if (dialect === 'mssql') { - expect(sql).to.not.contain('createdAt'); - } else { - expect(sql).to.match(/UPDATE\s+[`"]+User1s[`"]+\s+SET\s+[`"]+secretValue[`"]=(\$1|\?),[`"]+updatedAt[`"]+=(\$2|\?)\s+WHERE [`"]+id[`"]+\s=\s(\$3|\?)/); - } - } - }); - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + const user = await User.create({ username: 'Peter', secretValue: '42' }); + await user.update({ secretValue: '43' }, { + fields: ['secretValue'], + logging(sql) { + test = true; + if (dialect === 'mssql') { + expect(sql).to.not.contain('createdAt'); + } else { + expect(sql).to.match(/UPDATE\s+[`"]+User1s[`"]+\s+SET\s+[`"]+secretValue[`"]=(\$1|\?),[`"]+updatedAt[`"]+=(\$2|\?)\s+WHERE [`"]+id[`"]+\s=\s(\$3|\?)/); + } + }, + returning: ['*'] }); + expect(test).to.be.true; }); - it('allows sql logging of updated statements', function() { + it('allows sql logging of updated statements', async function() { const User = this.sequelize.define('User', { name: Sequelize.STRING, bio: Sequelize.TEXT @@ -952,125 +1001,114 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ name: 'meg', bio: 'none' }).then(u => { - expect(u).to.exist; - return u.update({ name: 'brian' }, { - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('UPDATE'); - } - }); - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + const u = await User.create({ name: 'meg', bio: 'none' }); + expect(u).to.exist; + await u.update({ name: 'brian' }, { + logging(sql) { + test = true; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('UPDATE'); + } }); + expect(test).to.be.true; }); - it('updates only values that match filter', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('updates only values that match filter', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(3); + { username: 'Bob', secretValue: '43' } + ]; - users.forEach(user => { - if (user.secretValue === '42') { - expect(user.username).to.equal('Bill'); - } else { - expect(user.username).to.equal('Bob'); - } - }); + await this.User.bulkCreate(data); + await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users).to.have.lengthOf(3); - }); - }); - }); + for (const user of users) { + if (user.secretValue === '42') { + expect(user.username).to.equal('Bill'); + } else { + expect(user.username).to.equal('Bob'); + } + } }); - it('throws an error if where has a key with undefined value', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('throws an error if where has a key with undefined value', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42', username: undefined } }).then(() => { - throw new Error('Update should throw an error if where has a key with undefined value'); - }, err => { - expect(err).to.be.an.instanceof(Error); - expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + try { + await this.User.update({ username: 'Bill' }, { + where: { + secretValue: '42', + username: undefined + } }); - }); + throw new Error('Update should throw an error if where has a key with undefined value'); + } catch (err) { + expect(err).to.be.an.instanceof(Error); + expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); + } }); - it('updates only values that match the allowed fields', function() { + it('updates only values that match the allowed fields', async function() { const data = [{ username: 'Peter', secretValue: '42' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill', secretValue: '43' }, { where: { secretValue: '42' }, fields: ['username'] }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(1); - - const user = users[0]; - expect(user.username).to.equal('Bill'); - expect(user.secretValue).to.equal('42'); - }); - }); - }); + await this.User.bulkCreate(data); + await this.User.update({ username: 'Bill', secretValue: '43' }, { where: { secretValue: '42' }, fields: ['username'] }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users).to.have.lengthOf(1); + expect(users[0].username).to.equal('Bill'); + expect(users[0].secretValue).to.equal('42'); }); - it('updates with casting', function() { - return this.User.create({ - username: 'John' - }).then(() => { - return this.User.update({ username: this.sequelize.cast('1', dialect === 'mssql' ? 'nvarchar' : 'char') }, { where: { username: 'John' } }).then(() => { - return this.User.findAll().then(users => { - expect(users[0].username).to.equal('1'); - }); - }); + it('updates with casting', async function() { + await this.User.create({ username: 'John' }); + await this.User.update({ + username: this.sequelize.cast('1', dialect === 'mssql' ? 'nvarchar' : 'char') + }, { + where: { username: 'John' } }); + expect((await this.User.findOne()).username).to.equal('1'); }); - it('updates with function and column value', function() { - return this.User.create({ - username: 'John' - }).then(() => { - return this.User.update({ username: this.sequelize.fn('upper', this.sequelize.col('username')) }, { where: { username: 'John' } }).then(() => { - return this.User.findAll().then(users => { - expect(users[0].username).to.equal('JOHN'); - }); - }); + it('updates with function and column value', async function() { + await this.User.create({ username: 'John' }); + await this.User.update({ + username: this.sequelize.fn('upper', this.sequelize.col('username')) + }, { + where: { username: 'John' } }); + expect((await this.User.findOne()).username).to.equal('JOHN'); }); - it('does not update virtual attributes', function() { + it('does not update virtual attributes', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING, virtual: Sequelize.VIRTUAL }); - return User.create({ - username: 'jan' - }).then(() => { - return User.update({ - username: 'kurt', - virtual: 'test' - }, { - where: { - username: 'jan' - } - }); - }).then(() => { - return User.findAll(); - }).then(([user]) => { - expect(user.username).to.equal('kurt'); + await User.create({ username: 'jan' }); + await User.update({ + username: 'kurt', + virtual: 'test' + }, { + where: { + username: 'jan' + } }); + const user = await User.findOne(); + expect(user.username).to.equal('kurt'); + expect(user.virtual).to.not.equal('test'); }); - it('doesn\'t update attributes that are altered by virtual setters when option is enabled', function() { + it('doesn\'t update attributes that are altered by virtual setters when option is enabled', async function() { const User = this.sequelize.define('UserWithVirtualSetters', { username: Sequelize.STRING, illness_name: Sequelize.STRING, @@ -1084,29 +1122,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'Jan', - illness_name: 'Headache', - illness_pain: 5 - }); - }).then(() => { - return User.update({ - illness: { pain: 10, name: 'Backache' } - }, { - where: { - username: 'Jan' - }, - sideEffects: false - }); - }).then(() => { - return User.findAll(); - }).then(([user]) => { - expect(user.illness_pain).to.be.equal(5); + await User.sync({ force: true }); + await User.create({ + username: 'Jan', + illness_name: 'Headache', + illness_pain: 5 }); + await User.update({ + illness: { pain: 10, name: 'Backache' } + }, { + where: { + username: 'Jan' + }, + sideEffects: false + }); + expect((await User.findOne()).illness_pain).to.be.equal(5); }); - it('updates attributes that are altered by virtual setters', function() { + it('updates attributes that are altered by virtual setters', async function() { const User = this.sequelize.define('UserWithVirtualSetters', { username: Sequelize.STRING, illness_name: Sequelize.STRING, @@ -1120,334 +1153,286 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'Jan', - illness_name: 'Headache', - illness_pain: 5 - }); - }).then(() => { - return User.update({ - illness: { pain: 10, name: 'Backache' } - }, { - where: { - username: 'Jan' - } - }); - }).then(() => { - return User.findAll(); - }).then(([user]) => { - expect(user.illness_pain).to.be.equal(10); + await User.sync({ force: true }); + await User.create({ + username: 'Jan', + illness_name: 'Headache', + illness_pain: 5 }); + await User.update({ + illness: { pain: 10, name: 'Backache' } + }, { + where: { + username: 'Jan' + } + }); + expect((await User.findOne()).illness_pain).to.be.equal(10); }); - it('should properly set data when individualHooks are true', function() { - this.User.hooks.add('beforeUpdate', instance => { + it('should properly set data when individualHooks are true', async function() { + this.User.beforeUpdate(instance => { instance.set('intVal', 1); }); - return this.User.create({ username: 'Peter' }).then(user => { - return this.User.update({ data: 'test' }, { where: { id: user.id }, individualHooks: true }).then(() => { - return this.User.findByPk(user.id).then(userUpdated => { - expect(userUpdated.intVal).to.be.equal(1); - }); - }); + const user = await this.User.create({ username: 'Peter' }); + await this.User.update({ data: 'test' }, { + where: { id: user.id }, + individualHooks: true }); + expect((await this.User.findByPk(user.id)).intVal).to.be.equal(1); }); - it('sets updatedAt to the current timestamp', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('sets updatedAt to the current timestamp', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; + { username: 'Bob', secretValue: '43' } + ]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }); - }).then(users => { - this.updatedAt = users[0].updatedAt; + await this.User.bulkCreate(data); + let users = await this.User.findAll({ order: ['id'] }); + this.updatedAt = users[0].updatedAt; - expect(this.updatedAt).to.be.ok; - expect(this.updatedAt).to.equalTime(users[2].updatedAt); // All users should have the same updatedAt + expect(this.updatedAt).to.be.ok; + expect(this.updatedAt).to.equalTime(users[2].updatedAt); // All users should have the same updatedAt - // Pass the time so we can actually see a change - this.clock.tick(1000); - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); - }).then(() => { - return this.User.findAll({ order: ['id'] }); - }).then(users => { - expect(users[0].username).to.equal('Bill'); - expect(users[1].username).to.equal('Bill'); - expect(users[2].username).to.equal('Bob'); + // Pass the time so we can actually see a change + this.clock.tick(1000); + await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); - expect(users[0].updatedAt).to.be.afterTime(this.updatedAt); - expect(users[2].updatedAt).to.equalTime(this.updatedAt); - }); + users = await this.User.findAll({ order: ['id'] }); + expect(users[0].username).to.equal('Bill'); + expect(users[1].username).to.equal('Bill'); + expect(users[2].username).to.equal('Bob'); + + expect(users[0].updatedAt).to.be.afterTime(this.updatedAt); + expect(users[2].updatedAt).to.equalTime(this.updatedAt); }); - it('returns the number of affected rows', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('returns the number of affected rows', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }).then(([affectedRows]) => { - expect(affectedRows).to.equal(2); - }).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '44' } }).then(([affectedRows]) => { - expect(affectedRows).to.equal(0); - }); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + let [affectedRows] = await this.User.update({ username: 'Bill' }, { where: { secretValue: '42' } }); + expect(affectedRows).to.equal(2); + [affectedRows] = await this.User.update({ username: 'Bill' }, { where: { secretValue: '44' } }); + expect(affectedRows).to.equal(0); }); - it('does not update soft deleted records when model is paranoid', function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING }, { paranoid: true }); + it('does not update soft deleted records when model is paranoid', async function() { + const ParanoidUser = this.sequelize.define('ParanoidUser', { + username: DataTypes.STRING + }, { paranoid: true }); - return this.sequelize.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate([ - { username: 'user1' }, - { username: 'user2' } - ]); - }).then(() => { - return ParanoidUser.destroy({ - where: { - username: 'user1' - } - }); - }).then(() => { - return ParanoidUser.update({ username: 'foo' }, { - where: {} - }); - }).then(() => { - return ParanoidUser.findAll({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(users => { - expect(users).to.have.lengthOf(1, 'should not update soft-deleted record'); + await this.sequelize.sync({ force: true }); + await ParanoidUser.bulkCreate([ + { username: 'user1' }, + { username: 'user2' } + ]); + await ParanoidUser.destroy({ + where: { username: 'user1' } + }); + await ParanoidUser.update({ username: 'foo' }, { where: {} }); + const users = await ParanoidUser.findAll({ + paranoid: false, + where: { + username: 'foo' + } }); + expect(users).to.have.lengthOf(1, 'should not update soft-deleted record'); }); - it('updates soft deleted records when paranoid is overridden', function() { - const ParanoidUser = this.sequelize.define('ParanoidUser', { username: DataTypes.STRING }, { paranoid: true }); + it('updates soft deleted records when paranoid is overridden', async function() { + const ParanoidUser = this.sequelize.define('ParanoidUser', { + username: DataTypes.STRING + }, { paranoid: true }); - return this.sequelize.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate([ - { username: 'user1' }, - { username: 'user2' } - ]); - }).then(() => { - return ParanoidUser.destroy({ - where: { - username: 'user1' - } - }); - }).then(() => { - return ParanoidUser.update({ username: 'foo' }, { - where: {}, - paranoid: false - }); - }).then(() => { - return ParanoidUser.findAll({ - paranoid: false, - where: { - username: 'foo' - } - }); - }).then(users => { - expect(users).to.have.lengthOf(2); + await this.sequelize.sync({ force: true }); + await ParanoidUser.bulkCreate([ + { username: 'user1' }, + { username: 'user2' } + ]); + await ParanoidUser.destroy({ where: { username: 'user1' } }); + await ParanoidUser.update({ username: 'foo' }, { + where: {}, + paranoid: false + }); + const users = await ParanoidUser.findAll({ + paranoid: false, + where: { + username: 'foo' + } }); + expect(users).to.have.lengthOf(2); }); - it('calls update hook for soft deleted objects', function() { + it('calls update hook for soft deleted objects', async function() { const hookSpy = sinon.spy(); const User = this.sequelize.define('User', { username: DataTypes.STRING }, { paranoid: true, hooks: { beforeUpdate: hookSpy } } ); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'user1' } - ]); - }).then(() => { - return User.destroy({ - where: { - username: 'user1' - } - }); - }).then(() => { - return User.update( - { username: 'updUser1' }, - { paranoid: false, where: { username: 'user1' }, individualHooks: true }); - }).then(() => { - return User.findOne({ where: { username: 'updUser1' }, paranoid: false }); - }).then( user => { - expect(user).to.not.be.null; - expect(user.username).to.eq('updUser1'); - expect(hookSpy).to.have.been.called; + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }]); + await User.destroy({ + where: { + username: 'user1' + } + }); + await User.update({ username: 'updUser1' }, { + paranoid: false, + where: { username: 'user1' }, + individualHooks: true }); + const user = await User.findOne({ where: { username: 'updUser1' }, paranoid: false }); + expect(user).to.not.be.null; + expect(user.username).to.eq('updUser1'); + expect(hookSpy).to.have.been.called; }); if (dialect === 'postgres') { - it('returns the affected rows if `options.returning` is true', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('returns the affected rows if `options.returning` is true', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '42' }, returning: true }).then(([count, rows]) => { - expect(count).to.equal(2); - expect(rows).to.have.length(2); - }).then(() => { - return this.User.update({ username: 'Bill' }, { where: { secretValue: '44' }, returning: true }).then(([count, rows]) => { - expect(count).to.equal(0); - expect(rows).to.have.length(0); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + let [count, rows] = await this.User.update({ username: 'Bill' }, { + where: { secretValue: '42' }, + returning: true + }); + expect(count).to.equal(2); + expect(rows).to.have.length(2); + [count, rows] = await this.User.update({ username: 'Bill' }, { + where: { secretValue: '44' }, + returning: true }); + expect(count).to.equal(0); + expect(rows).to.have.length(0); }); } if (dialect === 'mysql') { - it('supports limit clause', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('supports limit clause', async function() { + const data = [ { username: 'Peter', secretValue: '42' }, - { username: 'Peter', secretValue: '42' }]; + { username: 'Peter', secretValue: '42' }, + { username: 'Peter', secretValue: '42' } + ]; - return this.User.bulkCreate(data).then(() => { - return this.User.update({ secretValue: '43' }, { where: { username: 'Peter' }, limit: 1 }).then(([affectedRows]) => { - expect(affectedRows).to.equal(1); - }); + await this.User.bulkCreate(data); + const [affectedRows] = await this.User.update({ secretValue: '43' }, { + where: { username: 'Peter' }, + limit: 1 }); + expect(affectedRows).to.equal(1); }); } }); describe('destroy', () => { - it('convenient method `truncate` should clear the table', function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - data = [ - { username: 'user1' }, - { username: 'user2' } - ]; - - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate(data); - }).then(() => { - return User.truncate(); - }).then(() => { - return expect(User.findAll()).to.eventually.have.length(0); - }); + it('`truncate` method should clear the table', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + await User.truncate(); + expect(await User.findAll()).to.have.lengthOf(0); }); - it('truncate should clear the table', function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - data = [ - { username: 'user1' }, - { username: 'user2' } - ]; - - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate(data); - }).then(() => { - return User.destroy({ truncate: true }); - }).then(() => { - return expect(User.findAll()).to.eventually.have.length(0); - }); + it('`truncate` option should clear the table', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + await User.destroy({ truncate: true }); + expect(await User.findAll()).to.have.lengthOf(0); }); - it('throws an error if no where clause is given', function() { + it('`truncate` option returns a number', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + const affectedRows = await User.destroy({ truncate: true }); + expect(await User.findAll()).to.have.lengthOf(0); + expect(affectedRows).to.be.a('number'); + }); - return this.sequelize.sync({ force: true }).then(() => { - return User.destroy(); - }).then(() => { + it('throws an error if no where clause is given', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + try { + await User.destroy(); throw new Error('Destroy should throw an error if no where clause is given.'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('Missing where or truncate attribute in the options parameter of model.destroy.'); - }); + } }); - it('deletes all instances when given an empty where object', function() { - const User = this.sequelize.define('User', { username: DataTypes.STRING }), - data = [ - { username: 'user1' }, - { username: 'user2' } - ]; - - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate(data); - }).then(() => { - return User.destroy({ where: {} }); - }).then(affectedRows => { - expect(affectedRows).to.equal(2); - return User.findAll(); - }).then(users => { - expect(users).to.have.length(0); - }); + it('deletes all instances when given an empty where object', async function() { + const User = this.sequelize.define('User', { username: DataTypes.STRING }); + await this.sequelize.sync({ force: true }); + await User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + const affectedRows = await User.destroy({ where: {} }); + expect(affectedRows).to.equal(2); + expect(await User.findAll()).to.have.lengthOf(0); }); - it('throws an error if where has a key with undefined value', function() { + it('throws an error if where has a key with undefined value', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return User.destroy({ where: { username: undefined } }); - }).then(() => { + await this.sequelize.sync({ force: true }); + try { + await User.destroy({ where: { username: undefined } }); throw new Error('Destroy should throw an error if where has a key with undefined value'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); - }); + } }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(() => { - return sequelize.transaction().then(t => { - return User.destroy({ - where: {}, - transaction: t - }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(1); - expect(count2).to.equal(0); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + const t = await sequelize.transaction(); + await User.destroy({ + where: {}, + transaction: t + }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(1); + expect(count2).to.equal(0); + await t.rollback(); + }); } - it('deletes values that match filter', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + it('deletes values that match filter', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.destroy({ where: { secretValue: '42' } }) - .then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Bob'); - }); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; + + await this.User.bulkCreate(data); + await this.User.destroy({ where: { secretValue: '42' } }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Bob'); }); - it('works without a primary key', function() { + it('works without a primary key', async function() { const Log = this.sequelize.define('Log', { client_id: DataTypes.INTEGER, content: DataTypes.TEXT, @@ -1455,26 +1440,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); Log.removeAttribute('id'); - return Log.sync({ force: true }).then(() => { - return Log.create({ - client_id: 13, - content: 'Error!', - timestamp: new Date() - }); - }).then(() => { - return Log.destroy({ - where: { - client_id: 13 - } - }); - }).then(() => { - return Log.findAll().then(logs => { - expect(logs.length).to.equal(0); - }); + await Log.sync({ force: true }); + await Log.create({ + client_id: 13, + content: 'Error!', + timestamp: new Date() + }); + await Log.destroy({ + where: { + client_id: 13 + } }); + expect(await Log.findAll()).to.have.lengthOf(0); }); - it('supports .field', function() { + it('supports .field', async function() { const UserProject = this.sequelize.define('UserProject', { userId: { type: DataTypes.INTEGER, @@ -1482,152 +1462,113 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return UserProject.sync({ force: true }).then(() => { - return UserProject.create({ - userId: 10 - }); - }).then(() => { - return UserProject.destroy({ - where: { - userId: 10 - } - }); - }).then(() => { - return UserProject.findAll(); - }).then(userProjects => { - expect(userProjects.length).to.equal(0); - }); + await UserProject.sync({ force: true }); + await UserProject.create({ userId: 10 }); + await UserProject.destroy({ where: { userId: 10 } }); + expect(await UserProject.findAll()).to.have.lengthOf(0); }); - it('sets deletedAt to the current timestamp if paranoid is true', function() { - const qi = this.sequelize.queryInterface.QueryGenerator.quoteIdentifier.bind(this.sequelize.queryInterface.QueryGenerator), - ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Sequelize.STRING, - secretValue: Sequelize.STRING, - data: Sequelize.STRING, - intVal: { type: Sequelize.INTEGER, defaultValue: 1 } - }, { - paranoid: true - }), - data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; + it('sets deletedAt to the current timestamp if paranoid is true', async function() { + const ParanoidUser = this.sequelize.define('ParanoidUser', { + username: Sequelize.STRING, + secretValue: Sequelize.STRING, + data: Sequelize.STRING, + intVal: { type: Sequelize.INTEGER, defaultValue: 1 } + }, { paranoid: true }); + const data = [ + { username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '42' }, + { username: 'Bob', secretValue: '43' } + ]; - const ctx = {}; - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate(data); - }).then(() => { - // since we save in UTC, let's format to UTC time - ctx.date = moment().utc().format('YYYY-MM-DD h:mm'); - return ParanoidUser.destroy({ where: { secretValue: '42' } }); - }).then(() => { - return ParanoidUser.findAll({ order: ['id'] }); - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Bob'); + await ParanoidUser.sync({ force: true }); + await ParanoidUser.bulkCreate(data); - return this.sequelize.query(`SELECT * FROM ${qi('ParanoidUsers')} WHERE ${qi('deletedAt')} IS NOT NULL ORDER BY ${qi('id')}`); - }).then(([users]) => { - expect(users[0].username).to.equal('Peter'); - expect(users[1].username).to.equal('Paul'); + // since we save in UTC, let's format to UTC time + const date = moment().utc().format('YYYY-MM-DD h:mm'); + await ParanoidUser.destroy({ where: { secretValue: '42' } }); - expect(moment(new Date(users[0].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(ctx.date); - expect(moment(new Date(users[1].deletedAt)).utc().format('YYYY-MM-DD h:mm')).to.equal(ctx.date); - }); + let users = await ParanoidUser.findAll({ order: ['id'] }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Bob'); + + const queryGenerator = this.sequelize.queryInterface.queryGenerator; + const qi = queryGenerator.quoteIdentifier.bind(queryGenerator); + const query = `SELECT * FROM ${qi('ParanoidUsers')} WHERE ${qi('deletedAt')} IS NOT NULL ORDER BY ${qi('id')}`; + [users] = await this.sequelize.query(query); + + expect(users[0].username).to.equal('Peter'); + expect(users[1].username).to.equal('Paul'); + + const formatDate = val => moment(new Date(val)).utc().format('YYYY-MM-DD h:mm'); + + expect(formatDate(users[0].deletedAt)).to.equal(date); + expect(formatDate(users[1].deletedAt)).to.equal(date); }); - it('does not set deletedAt for previously destroyed instances if paranoid is true', function() { + it('does not set deletedAt for previously destroyed instances if paranoid is true', async function() { const User = this.sequelize.define('UserCol', { secretValue: Sequelize.STRING, username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'Toni', secretValue: '42' }, - { username: 'Tobi', secretValue: '42' }, - { username: 'Max', secretValue: '42' } - ]).then(() => { - return User.findByPk(1).then(user => { - return user.destroy().then(() => { - return user.reload({ paranoid: false }).then(() => { - const deletedAt = user.deletedAt; - - return User.destroy({ where: { secretValue: '42' } }).then(() => { - return user.reload({ paranoid: false }).then(() => { - expect(user.deletedAt).to.eql(deletedAt); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Toni', secretValue: '42' }, + { username: 'Tobi', secretValue: '42' }, + { username: 'Max', secretValue: '42' } + ]); + const user = await User.findByPk(1); + await user.destroy(); + await user.reload({ paranoid: false }); + const deletedAt = user.deletedAt; + await User.destroy({ where: { secretValue: '42' } }); + await user.reload({ paranoid: false }); + expect(user.deletedAt).to.eql(deletedAt); }); describe("can't find records marked as deleted with paranoid being true", () => { - it('with the DAOFactory', function() { + it('with the DAOFactory', async function() { const User = this.sequelize.define('UserCol', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'Toni' }, - { username: 'Tobi' }, - { username: 'Max' } - ]).then(() => { - return User.findByPk(1).then(user => { - return user.destroy().then(() => { - return User.findByPk(1).then(user => { - expect(user).to.be.null; - return User.count().then(cnt => { - expect(cnt).to.equal(2); - return User.findAll().then(users => { - expect(users).to.have.length(2); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Toni' }, + { username: 'Tobi' }, + { username: 'Max' } + ]); + const user = await User.findByPk(1); + await user.destroy(); + expect(await User.findByPk(1)).to.be.null; + expect(await User.count()).to.equal(2); + expect(await User.findAll()).to.have.length(2); }); }); describe('can find paranoid records if paranoid is marked as false in query', () => { - it('with the DAOFactory', function() { + it('with the DAOFactory', async function() { const User = this.sequelize.define('UserCol', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }) - .then(() => { - return User.bulkCreate([ - { username: 'Toni' }, - { username: 'Tobi' }, - { username: 'Max' } - ]); - }) - .then(() => { return User.findByPk(1); }) - .then(user => { return user.destroy(); }) - .then(() => { return User.findOne({ where: 1, paranoid: false }); }) - .then(user => { - expect(user).to.exist; - return User.findByPk(1); - }) - .then(user => { - expect(user).to.be.null; - return Promise.all([User.count(), User.count({ paranoid: false })]); - }) - .then(([cnt, cntWithDeleted]) => { - expect(cnt).to.equal(2); - expect(cntWithDeleted).to.equal(3); - }); + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Toni' }, + { username: 'Tobi' }, + { username: 'Max' } + ]); + const user = await User.findByPk(1); + await user.destroy(); + expect(await User.findOne({ where: 1, paranoid: false })).to.exist; + expect(await User.findByPk(1)).to.be.null; + expect(await User.count()).to.equal(2); + expect(await User.count({ paranoid: false })).to.equal(3); }); }); - it('should include deleted associated records if include has paranoid marked as false', function() { + it('should include deleted associated records if include has paranoid marked as false', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }, { paranoid: true }); @@ -1639,190 +1580,150 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasMany(Pet); Pet.belongsTo(User); - let user; - return User.sync({ force: true }) - .then(() => { return Pet.sync({ force: true }); }) - .then(() => { return User.create({ username: 'Joe' }); }) - .then(_user => { - user = _user; - return Pet.bulkCreate([ - { name: 'Fido', UserId: user.id }, - { name: 'Fifi', UserId: user.id } - ]); - }) - .then(() => { return Pet.findByPk(1); }) - .then(pet => { return pet.destroy(); }) - .then(() => { - return Promise.all([ - User.findOne({ where: { id: user.id }, include: Pet }), - User.findOne({ - where: { id: user.id }, - include: [{ model: Pet, paranoid: false }] - }) - ]); - }) - .then(([user, userWithDeletedPets]) => { - expect(user).to.exist; - expect(user.Pets).to.have.length(1); - expect(userWithDeletedPets).to.exist; - expect(userWithDeletedPets.Pets).to.have.length(2); - }); - }); - - it('should delete a paranoid record if I set force to true', function() { + await User.sync({ force: true }); + await Pet.sync({ force: true }); + const userId = (await User.create({ username: 'Joe' })).id; + await Pet.bulkCreate([ + { name: 'Fido', UserId: userId }, + { name: 'Fifi', UserId: userId } + ]); + const pet = await Pet.findByPk(1); + await pet.destroy(); + const user = await User.findOne({ + where: { id: userId }, + include: Pet + }); + const userWithDeletedPets = await User.findOne({ + where: { id: userId }, + include: { model: Pet, paranoid: false } + }); + expect(user).to.exist; + expect(user.Pets).to.have.length(1); + expect(userWithDeletedPets).to.exist; + expect(userWithDeletedPets.Pets).to.have.length(2); + }); + + it('should delete a paranoid record if I set force to true', async function() { const User = this.sequelize.define('paranoiduser', { username: Sequelize.STRING }, { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'Bob' }, - { username: 'Tobi' }, - { username: 'Max' }, - { username: 'Tony' } - ]); - }).then(() => { - return User.findOne({ where: { username: 'Bob' } }); - }).then(user => { - return user.destroy({ force: true }); - }).then(() => { - return expect(User.findOne({ where: { username: 'Bob' } })).to.eventually.be.null; - }).then(() => { - return User.findOne({ where: { username: 'Tobi' } }); - }).then(tobi => { - return tobi.destroy(); - }).then(() => { - return this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', { plain: true }); - }).then(result => { - expect(result.username).to.equal('Tobi'); - return User.destroy({ where: { username: 'Tony' } }); - }).then(() => { - return this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', { plain: true }); - }).then(result => { - expect(result.username).to.equal('Tony'); - return User.destroy({ where: { username: ['Tony', 'Max'] }, force: true }); - }).then(() => { - return this.sequelize.query('SELECT * FROM paranoidusers', { raw: true }); - }).then(([users]) => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('Tobi'); - }); - }); - - it('returns the number of affected rows', function() { - const data = [{ username: 'Peter', secretValue: '42' }, + await User.sync({ force: true }); + await User.bulkCreate([ + { username: 'Bob' }, + { username: 'Tobi' }, + { username: 'Max' }, + { username: 'Tony' } + ]); + const user = await User.findOne({ where: { username: 'Bob' } }); + await user.destroy({ force: true }); + expect(await User.findOne({ where: { username: 'Bob' } })).to.be.null; + const tobi = await User.findOne({ where: { username: 'Tobi' } }); + await tobi.destroy(); + let result = await this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tobi\'', { plain: true }); + expect(result.username).to.equal('Tobi'); + await User.destroy({ where: { username: 'Tony' } }); + result = await this.sequelize.query('SELECT * FROM paranoidusers WHERE username=\'Tony\'', { plain: true }); + expect(result.username).to.equal('Tony'); + await User.destroy({ where: { username: ['Tony', 'Max'] }, force: true }); + const [users] = await this.sequelize.query('SELECT * FROM paranoidusers', { raw: true }); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('Tobi'); + }); + + it('returns the number of affected rows', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }]; - - return this.User.bulkCreate(data).then(() => { - return this.User.destroy({ where: { secretValue: '42' } }).then(affectedRows => { - expect(affectedRows).to.equal(2); - }); - }).then(() => { - return this.User.destroy({ where: { secretValue: '44' } }).then(affectedRows => { - expect(affectedRows).to.equal(0); - }); - }); - }); + { username: 'Bob', secretValue: '43' } + ]; - it('supports table schema/prefix', function() { - const data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '42' }, - { username: 'Bob', secretValue: '43' }], - prefixUser = this.User.schema('prefix'); - - const run = function() { - return prefixUser.sync({ force: true }).then(() => { - return prefixUser.bulkCreate(data).then(() => { - return prefixUser.destroy({ where: { secretValue: '42' } }).then(() => { - return prefixUser.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Bob'); - }); - }); - }); - }); - }; - return Support.dropTestSchemas(this.sequelize) - .then(() => this.sequelize.queryInterface.createSchema('prefix')) - .then(() => run.call(this)) - .then(() => this.sequelize.queryInterface.dropSchema('prefix')); + await this.User.bulkCreate(data); + let affectedRows = await this.User.destroy({ where: { secretValue: '42' } }); + expect(affectedRows).to.equal(2); + affectedRows = await this.User.destroy({ where: { secretValue: '44' } }); + expect(affectedRows).to.equal(0); }); - it('should work if model is paranoid and only operator in where clause is a Symbol', function() { + it('supports table schema/prefix', async function() { + const data = [ + { username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '42' }, + { username: 'Bob', secretValue: '43' } + ]; + const prefixUser = this.User.schema('prefix'); + + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.queryInterface.createSchema('prefix'); + await prefixUser.sync({ force: true }); + await prefixUser.bulkCreate(data); + await prefixUser.destroy({ where: { secretValue: '42' } }); + const users = await prefixUser.findAll({ order: ['id'] }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Bob'); + await this.sequelize.queryInterface.dropSchema('prefix'); + }); + + it('should work if model is paranoid and only operator in where clause is a Symbol', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING - }, { - paranoid: true - }); + }, { paranoid: true }); - return User.sync({ force: true }) - .then(() => User.create({ username: 'foo' })) - .then(() => User.create({ username: 'bar' })) - .then(() => { - return User.destroy({ - where: { - [Op.or]: [ - { username: 'bar' }, - { username: 'baz' } - ] - } - }); - }) - .then(() => User.findAll()) - .then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('foo'); - }); + await User.sync({ force: true }); + await User.bulkCreate([{ username: 'foo' }, { username: 'bar' }]); + await User.destroy({ + where: { + [Op.or]: [ + { username: 'bar' }, + { username: 'baz' } + ] + } + }); + const users = await User.findAll(); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('foo'); }); }); describe('restore', () => { - it('returns an error if the model is not paranoid', function() { - return this.User.create({ username: 'Peter', secretValue: '42' }) - .then(() => { - expect(() => {this.User.restore({ where: { secretValue: '42' } });}).to.throw(Error, 'Model is not paranoid'); - }); + it('rejects with an error if the model is not paranoid', async function() { + await expect(this.User.restore({ where: { secretValue: '42' } })).to.be.rejectedWith(Error, 'Model is not paranoid'); }); - it('restores a previously deleted model', function() { + it('restores a previously deleted model', async function() { const ParanoidUser = this.sequelize.define('ParanoidUser', { - username: Sequelize.STRING, - secretValue: Sequelize.STRING, - data: Sequelize.STRING, - intVal: { type: Sequelize.INTEGER, defaultValue: 1 } - }, { - paranoid: true - }), - data = [{ username: 'Peter', secretValue: '42' }, - { username: 'Paul', secretValue: '43' }, - { username: 'Bob', secretValue: '44' }]; - - return ParanoidUser.sync({ force: true }).then(() => { - return ParanoidUser.bulkCreate(data); - }).then(() => { - return ParanoidUser.destroy({ where: { secretValue: '42' } }); - }).then(() => { - return ParanoidUser.restore({ where: { secretValue: '42' } }); - }).then(() => { - return ParanoidUser.findOne({ where: { secretValue: '42' } }); - }).then(user => { - expect(user).to.be.ok; - expect(user.username).to.equal('Peter'); + username: Sequelize.STRING, + secretValue: Sequelize.STRING, + data: Sequelize.STRING, + intVal: { type: Sequelize.INTEGER, defaultValue: 1 } + }, { + paranoid: true }); + const data = [ + { username: 'Peter', secretValue: '42' }, + { username: 'Paul', secretValue: '43' }, + { username: 'Bob', secretValue: '44' } + ]; + + await ParanoidUser.sync({ force: true }); + await ParanoidUser.bulkCreate(data); + await ParanoidUser.destroy({ where: { secretValue: '42' } }); + await ParanoidUser.restore({ where: { secretValue: '42' } }); + const user = await ParanoidUser.findOne({ where: { secretValue: '42' } }); + expect(user).to.be.ok; + expect(user.username).to.equal('Peter'); }); }); describe('equals', () => { - it('correctly determines equality of objects', function() { - return this.User.create({ username: 'hallo', data: 'welt' }).then(u => { - expect(u.equals(u)).to.be.ok; - }); + it('correctly determines equality of objects', async function() { + const user = await this.User.create({ username: 'hallo', data: 'welt' }); + expect(user.equals(user)).to.be.ok; }); // sqlite can't handle multiple primary keys if (dialect !== 'sqlite') { - it('correctly determines equality with multiple primary keys', function() { + it('correctly determines equality with multiple primary keys', async function() { const userKeys = this.sequelize.define('userkeys', { foo: { type: Sequelize.STRING, primaryKey: true }, bar: { type: Sequelize.STRING, primaryKey: true }, @@ -1830,19 +1731,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { bio: Sequelize.TEXT }); - return userKeys.sync({ force: true }).then(() => { - return userKeys.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }).then(u => { - expect(u.equals(u)).to.be.ok; - }); - }); + await userKeys.sync({ force: true }); + const user = await userKeys.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }); + expect(user.equals(user)).to.be.ok; }); } }); - describe('equalsOneOf', () => { - // sqlite can't handle multiple primary keys - if (dialect !== 'sqlite') { - beforeEach(function() { + // sqlite can't handle multiple primary keys + if (dialect !== 'sqlite') { + describe('equalsOneOf', () => { + beforeEach(async function() { this.userKey = this.sequelize.define('userKeys', { foo: { type: Sequelize.STRING, primaryKey: true }, bar: { type: Sequelize.STRING, primaryKey: true }, @@ -1850,81 +1749,71 @@ describe(Support.getTestDialectTeaser('Model'), () => { bio: Sequelize.TEXT }); - return this.userKey.sync({ force: true }); + await this.userKey.sync({ force: true }); }); - it('determines equality if one is matching', function() { - return this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }).then(u => { - expect(u.equalsOneOf([u, { a: 1 }])).to.be.ok; - }); + it('determines equality if one is matching', async function() { + const u = await this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }); + expect(u.equalsOneOf([u, { a: 1 }])).to.be.ok; }); - it("doesn't determine equality if none is matching", function() { - return this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }).then(u => { - expect(u.equalsOneOf([{ b: 2 }, { a: 1 }])).to.not.be.ok; - }); + it("doesn't determine equality if none is matching", async function() { + const u = await this.userKey.create({ foo: '1', bar: '2', name: 'hallo', bio: 'welt' }); + expect(u.equalsOneOf([{ b: 2 }, { a: 1 }])).to.not.be.ok; }); - } - }); + }); + } describe('count', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.count().then(count1 => { - return User.count({ transaction: t }).then(count2 => { - expect(count1).to.equal(0); - expect(count2).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const count1 = await User.count(); + const count2 = await User.count({ transaction: t }); + expect(count1).to.equal(0); + expect(count2).to.equal(1); + await t.rollback(); }); } - it('counts all created objects', function() { - return this.User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]).then(() => { - return this.User.count().then(count => { - expect(count).to.equal(2); - }); - }); + it('counts all created objects', async function() { + await this.User.bulkCreate([{ username: 'user1' }, { username: 'user2' }]); + expect(await this.User.count()).to.equal(2); }); - it('returns multiple rows when using group', function() { - return this.User.bulkCreate([ + it('returns multiple rows when using group', async function() { + await this.User.bulkCreate([ { username: 'user1', data: 'A' }, { username: 'user2', data: 'A' }, { username: 'user3', data: 'B' } - ]).then(() => { - return this.User.count({ - attributes: ['data'], - group: ['data'] - }).then(count => { - expect(count.length).to.equal(2); - }); + ]); + const count = await this.User.count({ + attributes: ['data'], + group: ['data'] }); + expect(count).to.have.lengthOf(2); + + // The order of count varies across dialects; Hence find element by identified first. + expect(count.find(i => i.data === 'A')).to.deep.equal({ data: 'A', count: 2 }); + expect(count.find(i => i.data === 'B')).to.deep.equal({ data: 'B', count: 1 }); }); - describe('aggregate', () => { - if (dialect === 'mssql') { - return; - } - it('allows grouping by aliased attribute', function() { - return this.User.aggregate('id', 'count', { - attributes: [['id', 'id2']], - group: ['id2'], - logging: true + if (dialect !== 'mssql') { + describe('aggregate', () => { + it('allows grouping by aliased attribute', async function() { + await this.User.aggregate('id', 'count', { + attributes: [['id', 'id2']], + group: ['id2'], + logging: true + }); }); }); - }); + } describe('options sent to aggregate', () => { let options, aggregateSpy; @@ -1938,267 +1827,156 @@ describe(Support.getTestDialectTeaser('Model'), () => { afterEach(() => { expect(aggregateSpy).to.have.been.calledWith( sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('where', { username: 'user1' }))); + sinon.match.object.and(sinon.match.has('where', { username: 'user1' })) + ); aggregateSpy.restore(); }); - it('modifies option "limit" by setting it to null', function() { + it('modifies option "limit" by setting it to null', async function() { options.limit = 5; - return this.User.count(options).then(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('limit', null))); - }); + await this.User.count(options); + expect(aggregateSpy).to.have.been.calledWith( + sinon.match.any, sinon.match.any, + sinon.match.object.and(sinon.match.has('limit', null)) + ); }); - it('modifies option "offset" by setting it to null', function() { + it('modifies option "offset" by setting it to null', async function() { options.offset = 10; - return this.User.count(options).then(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('offset', null))); - }); + await this.User.count(options); + expect(aggregateSpy).to.have.been.calledWith( + sinon.match.any, sinon.match.any, + sinon.match.object.and(sinon.match.has('offset', null)) + ); }); - it('modifies option "order" by setting it to null', function() { + it('modifies option "order" by setting it to null', async function() { options.order = 'username'; - return this.User.count(options).then(() => { - expect(aggregateSpy).to.have.been.calledWith( - sinon.match.any, sinon.match.any, - sinon.match.object.and(sinon.match.has('order', null))); - }); + await this.User.count(options); + expect(aggregateSpy).to.have.been.calledWith( + sinon.match.any, sinon.match.any, + sinon.match.object.and(sinon.match.has('order', null)) + ); }); }); - it('allows sql logging', function() { + it('allows sql logging', async function() { let test = false; - return this.User.count({ + await this.User.count({ logging(sql) { test = true; expect(sql).to.exist; expect(sql.toUpperCase()).to.include('SELECT'); } - }).then(() => { - expect(test).to.be.true; }); + expect(test).to.be.true; }); - it('filters object', function() { - return this.User.create({ username: 'user1' }).then(() => { - return this.User.create({ username: 'foo' }).then(() => { - return this.User.count({ where: { username: { [Op.like]: '%us%' } } }).then(count => { - expect(count).to.equal(1); - }); - }); - }); + it('filters object', async function() { + await this.User.create({ username: 'user1' }); + await this.User.create({ username: 'foo' }); + const count = await this.User.count({ where: { username: { [Op.like]: '%us%' } } }); + expect(count).to.equal(1); }); - it('supports distinct option', function() { + it('supports distinct option', async function() { const Post = this.sequelize.define('Post', {}); const PostComment = this.sequelize.define('PostComment', {}); Post.hasMany(PostComment); - return Post.sync({ force: true }) - .then(() => PostComment.sync({ force: true })) - .then(() => Post.create({})) - .then(post => PostComment.bulkCreate([{ PostId: post.id }, { PostId: post.id }])) - .then(() => Promise.join( - Post.count({ distinct: false, include: [{ model: PostComment, required: false }] }), - Post.count({ distinct: true, include: [{ model: PostComment, required: false }] }), - (count1, count2) => { - expect(count1).to.equal(2); - expect(count2).to.equal(1); - }) - ); + await Post.sync({ force: true }); + await PostComment.sync({ force: true }); + const post = await Post.create({}); + await PostComment.bulkCreate([{ PostId: post.id }, { PostId: post.id }]); + const count1 = await Post.count({ distinct: false, include: { model: PostComment, required: false } }); + const count2 = await Post.count({ distinct: true, include: { model: PostComment, required: false } }); + expect(count1).to.equal(2); + expect(count2).to.equal(1); }); }); - describe('min', () => { - beforeEach(function() { - this.UserWithAge = this.sequelize.define('UserWithAge', { - age: Sequelize.INTEGER - }); - - this.UserWithDec = this.sequelize.define('UserWithDec', { - value: Sequelize.DECIMAL(10, 3) - }); - - return this.UserWithAge.sync({ force: true }).then(() => { - return this.UserWithDec.sync({ force: true }); - }); - }); - - if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { age: Sequelize.INTEGER }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }).then(() => { - return User.min('age').then(min1 => { - return User.min('age', { transaction: t }).then(min2 => { - expect(min1).to.be.not.ok; - expect(min2).to.equal(2); - return t.rollback(); - }); - }); - }); - }); - }); + for (const methodName of ['min', 'max']) { + describe(methodName, () => { + beforeEach(async function() { + this.UserWithAge = this.sequelize.define('UserWithAge', { + age: Sequelize.INTEGER, + order: Sequelize.INTEGER }); - }); - } - it('should return the min value', function() { - return this.UserWithAge.bulkCreate([{ age: 3 }, { age: 2 }]).then(() => { - return this.UserWithAge.min('age').then(min => { - expect(min).to.equal(2); + this.UserWithDec = this.sequelize.define('UserWithDec', { + value: Sequelize.DECIMAL(10, 3) }); - }); - }); - it('allows sql logging', function() { - let test = false; - return this.UserWithAge.min('age', { - logging(sql) { - test = true; - expect(sql).to.exist; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }).then(() => { - expect(test).to.be.true; - }); - }); - - it('should allow decimals in min', function() { - return this.UserWithDec.bulkCreate([{ value: 5.5 }, { value: 3.5 }]).then(() => { - return this.UserWithDec.min('value').then(min => { - expect(min).to.equal(3.5); - }); + await this.UserWithAge.sync({ force: true }); + await this.UserWithDec.sync({ force: true }); }); - }); - it('should allow strings in min', function() { - return this.User.bulkCreate([{ username: 'bbb' }, { username: 'yyy' }]).then(() => { - return this.User.min('username').then(min => { - expect(min).to.equal('bbb'); - }); - }); - }); - - it('should allow dates in min', function() { - return this.User.bulkCreate([{ theDate: new Date(2000, 1, 1) }, { theDate: new Date(1990, 1, 1) }]).then(() => { - return this.User.min('theDate').then(min => { - expect(min).to.be.a('Date'); - expect(new Date(1990, 1, 1)).to.equalDate(min); - }); - }); - }); - }); - - describe('max', () => { - beforeEach(function() { - this.UserWithAge = this.sequelize.define('UserWithAge', { - age: Sequelize.INTEGER, - order: Sequelize.INTEGER - }); - - this.UserWithDec = this.sequelize.define('UserWithDec', { - value: Sequelize.DECIMAL(10, 3) - }); - - return this.UserWithAge.sync({ force: true }).then(() => { - return this.UserWithDec.sync({ force: true }); - }); - }); - - if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { + if (current.dialect.supports.transactions) { + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); const User = sequelize.define('User', { age: Sequelize.INTEGER }); - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }).then(() => { - return User.max('age').then(min1 => { - return User.max('age', { transaction: t }).then(min2 => { - expect(min1).to.be.not.ok; - expect(min2).to.equal(5); - return t.rollback(); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.bulkCreate([{ age: 2 }, { age: 5 }, { age: 3 }], { transaction: t }); + const val1 = await User[methodName]('age'); + const val2 = await User[methodName]('age', { transaction: t }); + expect(val1).to.be.not.ok; + expect(val2).to.equal(methodName === 'min' ? 2 : 5); + await t.rollback(); }); - }); - } + } - it('should return the max value for a field named the same as an SQL reserved keyword', function() { - return this.UserWithAge.bulkCreate([{ age: 2, order: 3 }, { age: 3, order: 5 }]).then(() => { - return this.UserWithAge.max('order').then(max => { - expect(max).to.equal(5); - }); + it('returns the correct value', async function() { + await this.UserWithAge.bulkCreate([{ age: 3 }, { age: 2 }]); + expect(await this.UserWithAge[methodName]('age')).to.equal(methodName === 'min' ? 2 : 3); }); - }); - it('should return the max value', function() { - return this.UserWithAge.bulkCreate([{ age: 2 }, { age: 3 }]).then(() => { - return this.UserWithAge.max('age').then(max => { - expect(max).to.equal(3); + it('allows sql logging', async function() { + let test = false; + await this.UserWithAge[methodName]('age', { + logging(sql) { + test = true; + expect(sql).to.exist; + expect(sql.toUpperCase()).to.include('SELECT'); + } }); + expect(test).to.be.true; }); - }); - it('should allow decimals in max', function() { - return this.UserWithDec.bulkCreate([{ value: 3.5 }, { value: 5.5 }]).then(() => { - return this.UserWithDec.max('value').then(max => { - expect(max).to.equal(5.5); - }); + it('should allow decimals', async function() { + await this.UserWithDec.bulkCreate([{ value: 5.5 }, { value: 3.5 }]); + expect(await this.UserWithDec[methodName]('value')).to.equal(methodName === 'min' ? 3.5 : 5.5); }); - }); - it('should allow dates in max', function() { - return this.User.bulkCreate([ - { theDate: new Date(2013, 11, 31) }, - { theDate: new Date(2000, 1, 1) } - ]).then(() => { - return this.User.max('theDate').then(max => { - expect(max).to.be.a('Date'); - expect(max).to.equalDate(new Date(2013, 11, 31)); - }); + it('should allow strings', async function() { + await this.User.bulkCreate([{ username: 'bbb' }, { username: 'yyy' }]); + expect(await this.User[methodName]('username')).to.equal(methodName === 'min' ? 'bbb' : 'yyy'); }); - }); - it('should allow strings in max', function() { - return this.User.bulkCreate([{ username: 'aaa' }, { username: 'zzz' }]).then(() => { - return this.User.max('username').then(max => { - expect(max).to.equal('zzz'); - }); + it('should allow dates', async function() { + const date1 = new Date(2000, 1, 1); + const date2 = new Date(1990, 1, 1); + await this.User.bulkCreate([{ theDate: date1 }, { theDate: date2 }]); + expect(await this.User[methodName]('theDate')).to.equalDate(methodName === 'min' ? date2 : date1); }); - }); - it('allows sql logging', function() { - let logged = false; - return this.UserWithAge.max('age', { - logging(sql) { - expect(sql).to.exist; - logged = true; - expect(sql.toUpperCase()).to.include('SELECT'); - } - }).then(() => { - expect(logged).to.true; + it('should work with fields named as an SQL reserved keyword', async function() { + await this.UserWithAge.bulkCreate([ + { age: 2, order: 3 }, + { age: 3, order: 5 } + ]); + expect(await this.UserWithAge[methodName]('order')).to.equal(methodName === 'min' ? 3 : 5); }); }); - }); + } describe('sum', () => { - beforeEach(function() { + beforeEach(async function() { this.UserWithAge = this.sequelize.define('UserWithAge', { age: Sequelize.INTEGER, order: Sequelize.INTEGER, @@ -2221,74 +1999,61 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Promise.join( + await Promise.all([ this.UserWithAge.sync({ force: true }), this.UserWithDec.sync({ force: true }), this.UserWithFields.sync({ force: true }) - ); + ]); }); - it('should return the sum of the values for a field named the same as an SQL reserved keyword', function() { - return this.UserWithAge.bulkCreate([{ age: 2, order: 3 }, { age: 3, order: 5 }]).then(() => { - return this.UserWithAge.sum('order').then(sum => { - expect(sum).to.equal(8); - }); - }); + it('should work in the simplest case', async function() { + await this.UserWithAge.bulkCreate([{ age: 2 }, { age: 3 }]); + expect(await this.UserWithAge.sum('age')).to.equal(5); }); - it('should return the sum of a field in various records', function() { - return this.UserWithAge.bulkCreate([{ age: 2 }, { age: 3 }]).then(() => { - return this.UserWithAge.sum('age').then(sum => { - expect(sum).to.equal(5); - }); - }); + it('should work with fields named as an SQL reserved keyword', async function() { + await this.UserWithAge.bulkCreate([{ age: 2, order: 3 }, { age: 3, order: 5 }]); + expect(await this.UserWithAge.sum('order')).to.equal(8); }); - it('should allow decimals in sum', function() { - return this.UserWithDec.bulkCreate([{ value: 3.5 }, { value: 5.25 }]).then(() => { - return this.UserWithDec.sum('value').then(sum => { - expect(sum).to.equal(8.75); - }); - }); + it('should allow decimals in sum', async function() { + await this.UserWithDec.bulkCreate([{ value: 3.5 }, { value: 5.25 }]); + expect(await this.UserWithDec.sum('value')).to.equal(8.75); }); - it('should accept a where clause', function() { - const options = { where: { 'gender': 'male' } }; - - return this.UserWithAge.bulkCreate([{ age: 2, gender: 'male' }, { age: 3, gender: 'female' }]).then(() => { - return this.UserWithAge.sum('age', options).then(sum => { - expect(sum).to.equal(2); - }); - }); + it('should accept a where clause', async function() { + const options = { where: { gender: 'male' } }; + await this.UserWithAge.bulkCreate([ + { age: 2, gender: 'male' }, + { age: 3, gender: 'female' } + ]); + expect(await this.UserWithAge.sum('age', options)).to.equal(2); }); - it('should accept a where clause with custom fields', function() { - return this.UserWithFields.bulkCreate([ + it('should accept a where clause with custom fields', async function() { + const options = { where: { gender: 'male' } }; + await this.UserWithFields.bulkCreate([ { age: 2, gender: 'male' }, { age: 3, gender: 'female' } - ]).then(() => { - return expect(this.UserWithFields.sum('age', { - where: { 'gender': 'male' } - })).to.eventually.equal(2); - }); + ]); + expect(await this.UserWithFields.sum('age', options)).to.equal(2); }); - it('allows sql logging', function() { - let logged = false; - return this.UserWithAge.sum('age', { + it('allows sql logging', async function() { + let test = false; + await this.UserWithAge.sum('age', { logging(sql) { + test = true; expect(sql).to.exist; - logged = true; expect(sql.toUpperCase()).to.include('SELECT'); } - }).then(() => { - expect(logged).to.true; }); + expect(test).to.true; }); }); describe('schematic support', () => { - beforeEach(function() { + beforeEach(async function() { this.UserPublic = this.sequelize.define('UserPublic', { age: Sequelize.INTEGER }); @@ -2297,117 +2062,118 @@ describe(Support.getTestDialectTeaser('Model'), () => { age: Sequelize.INTEGER }); - return Support.dropTestSchemas(this.sequelize) - .then(() => this.sequelize.createSchema('schema_test')) - .then(() => this.sequelize.createSchema('special')) - .then(() => this.UserSpecial.schema('special').sync({ force: true })) - .then(UserSpecialSync => { - this.UserSpecialSync = UserSpecialSync; - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('schema_test'); + await this.sequelize.createSchema('special'); + this.UserSpecialSync = await this.UserSpecial.schema('special').sync({ force: true }); }); - afterEach(function() { - return this.sequelize.dropSchema('schema_test') - .finally(() => this.sequelize.dropSchema('special')) - .finally(() => this.sequelize.dropSchema('prefix')); + afterEach(async function() { + try { + await this.sequelize.dropSchema('schema_test'); + } finally { + await this.sequelize.dropSchema('special'); + await this.sequelize.dropSchema('prefix'); + } }); - it('should be able to drop with schemas', function() { - return this.UserSpecial.drop(); + it('should be able to drop with schemas', async function() { + await this.UserSpecial.drop(); }); - it('should be able to list schemas', function() { - return this.sequelize.showAllSchemas().then(schemas => { - expect(schemas).to.be.instanceof(Array); - - // sqlite & MySQL doesn't actually create schemas unless Model.sync() is called - // Postgres supports schemas natively - switch (dialect) { - case 'mssql': - case 'postgres': - expect(schemas).to.have.length(2); - break; - case 'mariadb': - expect(schemas).to.have.length(3); - break; - default : - expect(schemas).to.have.length(1); - break; - } - }); + it('should be able to list schemas', async function() { + const schemas = await this.sequelize.showAllSchemas(); + expect(schemas).to.be.instanceof(Array); + const expectedLengths = { + mssql: 2, + postgres: 2, + mariadb: 3, + mysql: 1, + sqlite: 1 + }; + expect(schemas).to.have.length(expectedLengths[dialect]); }); - if (dialect === 'mysql' || dialect === 'sqlite') { - it('should take schemaDelimiter into account if applicable', function() { + if (['mysql', 'sqlite'].includes(dialect)) { + it('should take schemaDelimiter into account if applicable', async function() { let test = 0; - const UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', { age: Sequelize.INTEGER }, { schema: 'hello', schemaDelimiter: '_' }); - const UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', { age: Sequelize.INTEGER }); - return UserSpecialUnderscore.sync({ force: true }).then(User => { - return UserSpecialDblUnderscore.schema('hello', '__').sync({ force: true }).then(DblUser => { - return DblUser.create({ age: 3 }, { - logging(sql) { - expect(sql).to.exist; - test++; - expect(sql).to.include('INSERT INTO `hello__UserSpecialDblUnderscores`'); - } - }).then(() => { - return User.create({ age: 3 }, { - logging(sql) { - expect(sql).to.exist; - test++; - expect(sql).to.include('INSERT INTO `hello_UserSpecialUnderscores`'); - } - }); - }); - }).then(() => { - expect(test).to.equal(2); - }); + const UserSpecialUnderscore = this.sequelize.define('UserSpecialUnderscore', { + age: Sequelize.INTEGER + }, { schema: 'hello', schemaDelimiter: '_' }); + const UserSpecialDblUnderscore = this.sequelize.define('UserSpecialDblUnderscore', { + age: Sequelize.INTEGER + }); + const User = await UserSpecialUnderscore.sync({ force: true }); + const DblUser = await UserSpecialDblUnderscore.schema('hello', '__').sync({ force: true }); + await DblUser.create({ age: 3 }, { + logging(sql) { + test++; + expect(sql).to.exist; + expect(sql).to.include('INSERT INTO `hello__UserSpecialDblUnderscores`'); + } }); + await User.create({ age: 3 }, { + logging(sql) { + test++; + expect(sql).to.exist; + expect(sql).to.include('INSERT INTO `hello_UserSpecialUnderscores`'); + } + }); + expect(test).to.equal(2); }); } - it('should describeTable using the default schema settings', function() { + it('should describeTable using the default schema settings', async function() { const UserPublic = this.sequelize.define('Public', { username: Sequelize.STRING }); - let count = 0; - return UserPublic.sync({ force: true }).then(() => { - return UserPublic.schema('special').sync({ force: true }).then(() => { - return this.sequelize.queryInterface.describeTable('Publics', { - logging(sql) { - if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql' || dialect === 'mariadb') { - expect(sql).to.not.contain('special'); - count++; - } - } - }).then(table => { - if (dialect === 'postgres') { - expect(table.id.defaultValue).to.not.contain('special'); - count++; - } - return this.sequelize.queryInterface.describeTable('Publics', { - schema: 'special', - logging(sql) { - if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql' || dialect === 'mariadb') { - expect(sql).to.contain('special'); - count++; - } - } - }).then(table => { - if (dialect === 'postgres') { - expect(table.id.defaultValue).to.contain('special'); - count++; - } - }); - }).then(() => { - expect(count).to.equal(2); - }); - }); + let test = 0; + + await UserPublic.sync({ force: true }); + await UserPublic.schema('special').sync({ force: true }); + + let table = await this.sequelize.queryInterface.describeTable('Publics', { + logging(sql) { + if (dialect === 'sqlite' && sql.includes('TABLE_INFO')) { + test++; + expect(sql).to.not.contain('special'); + } + else if (['mysql', 'mssql', 'mariadb'].includes(dialect)) { + test++; + expect(sql).to.not.contain('special'); + } + } }); + + if (dialect === 'postgres') { + test++; + expect(table.id.defaultValue).to.not.contain('special'); + } + + table = await this.sequelize.queryInterface.describeTable('Publics', { + schema: 'special', + logging(sql) { + if (dialect === 'sqlite' && sql.includes('TABLE_INFO')) { + test++; + expect(sql).to.contain('special'); + } + else if (['mysql', 'mssql', 'mariadb'].includes(dialect)) { + test++; + expect(sql).to.contain('special'); + } + } + }); + + if (dialect === 'postgres') { + test++; + expect(table.id.defaultValue).to.contain('special'); + } + + expect(test).to.equal(2); }); - it('should be able to reference a table with a schema set', function() { + it('should be able to reference a table with a schema set', async function() { const UserPub = this.sequelize.define('UserPub', { username: Sequelize.STRING }, { schema: 'prefix' }); @@ -2416,111 +2182,108 @@ describe(Support.getTestDialectTeaser('Model'), () => { name: Sequelize.STRING }, { schema: 'prefix' }); - UserPub.hasMany(ItemPub, { - foreignKeyConstraint: true - }); - - const run = function() { - return UserPub.sync({ force: true }).then(() => { - return ItemPub.sync({ force: true, logging: _.after(2, _.once(sql => { - if (dialect === 'postgres') { - expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/); - } else if (dialect === 'mssql') { - expect(sql).to.match(/REFERENCES\s+\[prefix\]\.\[UserPubs\] \(\[id\]\)/); - } else if (dialect === 'mariadb') { - expect(sql).to.match(/REFERENCES\s+`prefix`\.`UserPubs` \(`id`\)/); - } else { - expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/); - } + UserPub.hasMany(ItemPub, { foreignKeyConstraint: true }); - })) }); - }); - }; - - if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') { - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.queryInterface.createSchema('prefix').then(() => { - return run.call(this); - }); - }); + if (['postgres', 'mssql', 'mariadb'].includes(dialect)) { + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.queryInterface.createSchema('prefix'); } - return run.call(this); + + let test = false; + + await UserPub.sync({ force: true }); + await ItemPub.sync({ + force: true, + logging: _.after(2, _.once(sql => { + test = true; + if (dialect === 'postgres') { + expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/); + } else if (dialect === 'mssql') { + expect(sql).to.match(/REFERENCES\s+\[prefix\]\.\[UserPubs\] \(\[id\]\)/); + } else if (dialect === 'mariadb') { + expect(sql).to.match(/REFERENCES\s+`prefix`\.`UserPubs` \(`id`\)/); + } else { + expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/); + } + })) + }); + + expect(test).to.be.true; }); - it('should be able to create and update records under any valid schematic', function() { + it('should be able to create and update records under any valid schematic', async function() { let logged = 0; - return this.UserPublic.sync({ force: true }).then(UserPublicSync => { - return UserPublicSync.create({ age: 3 }, { - logging: UserPublic => { - logged++; - if (dialect === 'postgres') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('"special"."UserSpecials"'); - expect(UserPublic).to.include('INSERT INTO "UserPublics"'); - } else if (dialect === 'sqlite') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); - expect(UserPublic).to.include('INSERT INTO `UserPublics`'); - } else if (dialect === 'mssql') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('[special].[UserSpecials]'); - expect(UserPublic).to.include('INSERT INTO [UserPublics]'); - } else if (dialect === 'mariadb') { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special`.`UserSpecials`'); - expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1); - } else { - expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); - expect(UserPublic).to.include('INSERT INTO `UserPublics`'); - } + const UserPublicSync = await this.UserPublic.sync({ force: true }); + + await UserPublicSync.create({ age: 3 }, { + logging: UserPublic => { + logged++; + if (dialect === 'postgres') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('"special"."UserSpecials"'); + expect(UserPublic).to.include('INSERT INTO "UserPublics"'); + } else if (dialect === 'sqlite') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); + expect(UserPublic).to.include('INSERT INTO `UserPublics`'); + } else if (dialect === 'mssql') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('[special].[UserSpecials]'); + expect(UserPublic).to.include('INSERT INTO [UserPublics]'); + } else if (dialect === 'mariadb') { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special`.`UserSpecials`'); + expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1); + } else { + expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`'); + expect(UserPublic).to.include('INSERT INTO `UserPublics`'); } - }).then(() => { - return this.UserSpecialSync.schema('special').create({ age: 3 }, { - logging(UserSpecial) { - logged++; - if (dialect === 'postgres') { - expect(UserSpecial).to.include('INSERT INTO "special"."UserSpecials"'); - } else if (dialect === 'sqlite') { - expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); - } else if (dialect === 'mssql') { - expect(UserSpecial).to.include('INSERT INTO [special].[UserSpecials]'); - } else if (dialect === 'mariadb') { - expect(UserSpecial).to.include('INSERT INTO `special`.`UserSpecials`'); - } else { - expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); - } - } - }).then(UserSpecial => { - return UserSpecial.update({ age: 5 }, { - logging(user) { - logged++; - if (dialect === 'postgres') { - expect(user).to.include('UPDATE "special"."UserSpecials"'); - } else if (dialect === 'mssql') { - expect(user).to.include('UPDATE [special].[UserSpecials]'); - } else if (dialect === 'mariadb') { - expect(user).to.include('UPDATE `special`.`UserSpecials`'); - } else { - expect(user).to.include('UPDATE `special.UserSpecials`'); - } - } - }); - }); - }).then(() => { - expect(logged).to.equal(3); - }); + } + }); + + const UserSpecial = await this.UserSpecialSync.schema('special').create({ age: 3 }, { + logging(UserSpecial) { + logged++; + if (dialect === 'postgres') { + expect(UserSpecial).to.include('INSERT INTO "special"."UserSpecials"'); + } else if (dialect === 'sqlite') { + expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); + } else if (dialect === 'mssql') { + expect(UserSpecial).to.include('INSERT INTO [special].[UserSpecials]'); + } else if (dialect === 'mariadb') { + expect(UserSpecial).to.include('INSERT INTO `special`.`UserSpecials`'); + } else { + expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`'); + } + } + }); + + await UserSpecial.update({ age: 5 }, { + logging(user) { + logged++; + if (dialect === 'postgres') { + expect(user).to.include('UPDATE "special"."UserSpecials"'); + } else if (dialect === 'mssql') { + expect(user).to.include('UPDATE [special].[UserSpecials]'); + } else if (dialect === 'mariadb') { + expect(user).to.include('UPDATE `special`.`UserSpecials`'); + } else { + expect(user).to.include('UPDATE `special.UserSpecials`'); + } + } }); + + expect(logged).to.equal(3); }); }); describe('references', () => { - beforeEach(function() { + beforeEach(async function() { this.Author = this.sequelize.define('author', { firstName: Sequelize.STRING }); - return this.sequelize.getQueryInterface().dropTable('posts', { force: true }).then(() => { - return this.sequelize.getQueryInterface().dropTable('authors', { force: true }); - }).then(() => { - return this.Author.sync(); - }); + await this.sequelize.getQueryInterface().dropTable('posts', { force: true }); + await this.sequelize.getQueryInterface().dropTable('authors', { force: true }); + + await this.Author.sync(); }); - it('uses an existing dao factory and references the author table', function() { + it('uses an existing dao factory and references the author table', async function() { const authorIdColumn = { type: Sequelize.INTEGER, references: { model: this.Author, key: 'id' } }; const Post = this.sequelize.define('post', { @@ -2532,7 +2295,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.belongsTo(this.Author); // The posts table gets dropped in the before filter. - return Post.sync({ logging: _.once(sql => { + await Post.sync({ logging: _.once(sql => { if (dialect === 'postgres') { expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/); } else if (dialect === 'mysql' || dialect === 'mariadb') { @@ -2547,7 +2310,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }) }); }); - it('uses a table name as a string and references the author table', function() { + it('uses a table name as a string and references the author table', async function() { const authorIdColumn = { type: Sequelize.INTEGER, references: { model: 'authors', key: 'id' } }; const Post = this.sequelize.define('post', { title: Sequelize.STRING, authorId: authorIdColumn }); @@ -2556,7 +2319,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.belongsTo(this.Author); // The posts table gets dropped in the before filter. - return Post.sync({ logging: _.once(sql => { + await Post.sync({ logging: _.once(sql => { if (dialect === 'postgres') { expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/); } else if (dialect === 'mysql' || dialect === 'mariadb') { @@ -2571,7 +2334,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }) }); }); - it('emits an error event as the referenced table name is invalid', function() { + it('emits an error event as the referenced table name is invalid', async function() { const authorIdColumn = { type: Sequelize.INTEGER, references: { model: '4uth0r5', key: 'id' } }; const Post = this.sequelize.define('post', { title: Sequelize.STRING, authorId: authorIdColumn }); @@ -2579,8 +2342,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Author.hasMany(Post); Post.belongsTo(this.Author); - // The posts table gets dropped in the before filter. - return Post.sync().then(() => { + try { + // The posts table gets dropped in the before filter. + await Post.sync(); if (dialect === 'sqlite') { // sorry ... but sqlite is too stupid to understand whats going on ... expect(1).to.equal(1); @@ -2588,12 +2352,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { // the parser should not end up here ... expect(2).to.equal(1); } - - return; - }).catch(err => { + } catch (err) { if (dialect === 'mysql') { - // MySQL 5.7 or above doesn't support POINT EMPTY - if (semver.gte(current.options.databaseVersion, '5.6.0')) { + if (isMySQL8) { + expect(err.message).to.match(/Failed to open the referenced table '4uth0r5'/); + } else if (semver.gte(current.options.databaseVersion, '5.6.0')) { expect(err.message).to.match(/Cannot add foreign key constraint/); } else { expect(err.message).to.match(/Can't create table/); @@ -2610,10 +2373,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { } else { throw new Error('Undefined dialect!'); } - }); + } }); - it('works with comments', function() { + it('works with comments', async function() { // Test for a case where the comment was being moved to the end of the table when there was also a reference on the column, see #1521 const Member = this.sequelize.define('Member', {}); const idColumn = { @@ -2627,47 +2390,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.define('Profile', { id: idColumn }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); }); describe('blob', () => { - beforeEach(function() { + beforeEach(async function() { this.BlobUser = this.sequelize.define('blobUser', { data: Sequelize.BLOB }); - return this.BlobUser.sync({ force: true }); + await this.BlobUser.sync({ force: true }); }); describe('buffers', () => { - it('should be able to take a buffer as parameter to a BLOB field', function() { - return this.BlobUser.create({ + it('should be able to take a buffer as parameter to a BLOB field', async function() { + const user = await this.BlobUser.create({ data: Buffer.from('Sequelize') - }).then(user => { - expect(user).to.be.ok; }); + + expect(user).to.be.ok; }); - it('should return a buffer when fetching a blob', function() { - return this.BlobUser.create({ + it('should return a buffer when fetching a blob', async function() { + const user = await this.BlobUser.create({ data: Buffer.from('Sequelize') - }).then(user => { - return this.BlobUser.findByPk(user.id).then(user => { - expect(user.data).to.be.an.instanceOf(Buffer); - expect(user.data.toString()).to.have.string('Sequelize'); - }); }); + + const user0 = await this.BlobUser.findByPk(user.id); + expect(user0.data).to.be.an.instanceOf(Buffer); + expect(user0.data.toString()).to.have.string('Sequelize'); }); - it('should work when the database returns null', function() { - return this.BlobUser.create({ + it('should work when the database returns null', async function() { + const user = await this.BlobUser.create({ // create a null column - }).then(user => { - return this.BlobUser.findByPk(user.id).then(user => { - expect(user.data).to.be.null; - }); }); + + const user0 = await this.BlobUser.findByPk(user.id); + expect(user0.data).to.be.null; }); }); @@ -2678,23 +2439,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { // data is passed in, in string form? Very unclear, and very different. describe('strings', () => { - it('should be able to take a string as parameter to a BLOB field', function() { - return this.BlobUser.create({ + it('should be able to take a string as parameter to a BLOB field', async function() { + const user = await this.BlobUser.create({ data: 'Sequelize' - }).then(user => { - expect(user).to.be.ok; }); + + expect(user).to.be.ok; }); - it('should return a buffer when fetching a BLOB, even when the BLOB was inserted as a string', function() { - return this.BlobUser.create({ + it('should return a buffer when fetching a BLOB, even when the BLOB was inserted as a string', async function() { + const user = await this.BlobUser.create({ data: 'Sequelize' - }).then(user => { - return this.BlobUser.findByPk(user.id).then(user => { - expect(user.data).to.be.an.instanceOf(Buffer); - expect(user.data.toString()).to.have.string('Sequelize'); - }); }); + + const user0 = await this.BlobUser.findByPk(user.id); + expect(user0.data).to.be.an.instanceOf(Buffer); + expect(user0.data.toString()).to.have.string('Sequelize'); }); }); } @@ -2703,96 +2463,92 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('paranoid is true and where is an array', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING }, { paranoid: true }); this.Project = this.sequelize.define('Project', { title: DataTypes.STRING }, { paranoid: true }); this.Project.belongsToMany(this.User, { through: 'project_user' }); this.User.belongsToMany(this.Project, { through: 'project_user' }); - return this.sequelize.sync({ force: true }).then(() => { - return this.User.bulkCreate([{ - username: 'leia' - }, { - username: 'luke' - }, { - username: 'vader' - }]).then(() => { - return this.Project.bulkCreate([{ - title: 'republic' - }, { - title: 'empire' - }]).then(() => { - return this.User.findAll().then(users => { - return this.Project.findAll().then(projects => { - const leia = users[0], - luke = users[1], - vader = users[2], - republic = projects[0], - empire = projects[1]; - return leia.setProjects([republic]).then(() => { - return luke.setProjects([republic]).then(() => { - return vader.setProjects([empire]).then(() => { - return leia.destroy(); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await this.User.bulkCreate([{ + username: 'leia' + }, { + username: 'luke' + }, { + username: 'vader' + }]); + + await this.Project.bulkCreate([{ + title: 'republic' + }, { + title: 'empire' + }]); + + const users = await this.User.findAll(); + const projects = await this.Project.findAll(); + const leia = users[0], + luke = users[1], + vader = users[2], + republic = projects[0], + empire = projects[1]; + await leia.setProjects([republic]); + await luke.setProjects([republic]); + await vader.setProjects([empire]); + + await leia.destroy(); }); - it('should not fail when array contains Sequelize.or / and', function() { - return this.User.findAll({ + it('should not fail when array contains Sequelize.or / and', async function() { + const res = await this.User.findAll({ where: [ this.sequelize.or({ username: 'vader' }, { username: 'luke' }), this.sequelize.and({ id: [1, 2, 3] }) ] - }) - .then(res => { - expect(res).to.have.length(2); - }); + }); + + expect(res).to.have.length(2); }); - it('should fail when array contains strings', function() { - return expect(this.User.findAll({ + it('should fail when array contains strings', async function() { + await expect(this.User.findAll({ where: ['this is a mistake', ['dont do it!']] })).to.eventually.be.rejectedWith(Error, 'Support for literal replacements in the `where` object has been removed.'); }); - it('should not fail with an include', function() { - return this.User.findAll({ - where: this.sequelize.literal(`${this.sequelize.queryInterface.QueryGenerator.quoteIdentifiers('Projects.title')} = ${this.sequelize.queryInterface.QueryGenerator.escape('republic')}`), + it('should not fail with an include', async function() { + const users = await this.User.findAll({ + where: this.sequelize.literal(`${this.sequelize.queryInterface.queryGenerator.quoteIdentifiers('Projects.title')} = ${this.sequelize.queryInterface.queryGenerator.escape('republic')}`), include: [ { model: this.Project } ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('luke'); }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('luke'); }); - it('should not overwrite a specified deletedAt by setting paranoid: false', function() { + it('should not overwrite a specified deletedAt by setting paranoid: false', async function() { let tableName = ''; if (this.User.name) { - tableName = `${this.sequelize.queryInterface.QueryGenerator.quoteIdentifier(this.User.name)}.`; + tableName = `${this.sequelize.queryInterface.queryGenerator.quoteIdentifier(this.User.name)}.`; } - return this.User.findAll({ + + const users = await this.User.findAll({ paranoid: false, - where: this.sequelize.literal(`${tableName + this.sequelize.queryInterface.QueryGenerator.quoteIdentifier('deletedAt')} IS NOT NULL `), + where: this.sequelize.literal(`${tableName + this.sequelize.queryInterface.queryGenerator.quoteIdentifier('deletedAt')} IS NOT NULL `), include: [ { model: this.Project } ] - }).then(users => { - expect(users.length).to.be.equal(1); - expect(users[0].username).to.be.equal('leia'); }); + + expect(users.length).to.be.equal(1); + expect(users[0].username).to.be.equal('leia'); }); - it('should not overwrite a specified deletedAt (complex query) by setting paranoid: false', function() { - return this.User.findAll({ + it('should not overwrite a specified deletedAt (complex query) by setting paranoid: false', async function() { + const res = await this.User.findAll({ paranoid: false, where: [ this.sequelize.or({ username: 'leia' }, { username: 'luke' }), @@ -2801,98 +2557,94 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.or({ deletedAt: null }, { deletedAt: { [Op.gt]: new Date(0) } }) ) ] - }) - .then(res => { - expect(res).to.have.length(2); - }); + }); + + expect(res).to.have.length(2); }); }); if (dialect !== 'sqlite' && current.dialect.supports.transactions) { - it('supports multiple async transactions', function() { + it('supports multiple async transactions', async function() { this.timeout(90000); - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - const testAsync = function() { - return sequelize.transaction().then(t => { - return User.create({ - username: 'foo' - }, { - transaction: t - }).then(() => { - return User.findAll({ - where: { - username: 'foo' - } - }).then(users => { - expect(users).to.have.length(0); - }); - }).then(() => { - return User.findAll({ - where: { - username: 'foo' - }, - transaction: t - }).then(users => { - expect(users).to.have.length(1); - }); - }).then(() => { - return t; - }); - }).then(t => { - return t.rollback(); - }); - }; - return User.sync({ force: true }).then(() => { - const tasks = []; - for (let i = 0; i < 1000; i++) { - tasks.push(testAsync); + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + const testAsync = async function() { + const t0 = await sequelize.transaction(); + + await User.create({ + username: 'foo' + }, { + transaction: t0 + }); + + const users0 = await User.findAll({ + where: { + username: 'foo' } - return Sequelize.Promise.resolve(tasks).map(entry => { - return entry(); - }, { - // Needs to be one less than ??? else the non transaction query won't ever get a connection - concurrency: (sequelize.config.pool && sequelize.config.pool.max || 5) - 1 - }); }); + + expect(users0).to.have.length(0); + + const users = await User.findAll({ + where: { + username: 'foo' + }, + transaction: t0 + }); + + expect(users).to.have.length(1); + const t = t0; + return t.rollback(); + }; + await User.sync({ force: true }); + const tasks = []; + for (let i = 0; i < 1000; i++) { + tasks.push(testAsync); + } + + await pMap(tasks, entry => { + return entry(); + }, { + // Needs to be one less than ??? else the non transaction query won't ever get a connection + concurrency: (sequelize.config.pool && sequelize.config.pool.max || 5) - 1 }); }); } describe('Unique', () => { - it('should set unique when unique is true', function() { + it('should set unique when unique is true', async function() { const uniqueTrue = this.sequelize.define('uniqueTrue', { str: { type: Sequelize.STRING, unique: true } }); - return uniqueTrue.sync({ force: true, logging: _.after(2, _.once(s => { + await uniqueTrue.sync({ force: true, logging: _.after(2, _.once(s => { expect(s).to.match(/UNIQUE/); })) }); }); - it('should not set unique when unique is false', function() { + it('should not set unique when unique is false', async function() { const uniqueFalse = this.sequelize.define('uniqueFalse', { str: { type: Sequelize.STRING, unique: false } }); - return uniqueFalse.sync({ force: true, logging: _.after(2, _.once(s => { + await uniqueFalse.sync({ force: true, logging: _.after(2, _.once(s => { expect(s).not.to.match(/UNIQUE/); })) }); }); - it('should not set unique when unique is unset', function() { + it('should not set unique when unique is unset', async function() { const uniqueUnset = this.sequelize.define('uniqueUnset', { str: { type: Sequelize.STRING } }); - return uniqueUnset.sync({ force: true, logging: _.after(2, _.once(s => { + await uniqueUnset.sync({ force: true, logging: _.after(2, _.once(s => { expect(s).not.to.match(/UNIQUE/); })) }); }); }); - it('should be possible to use a key named UUID as foreign key', function() { + it('should be possible to use a key named UUID as foreign key', async function() { this.sequelize.define('project', { UserId: { type: Sequelize.STRING, @@ -2916,11 +2668,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('bulkCreate', () => { - it('errors - should return array of errors if validate and individualHooks are true', function() { + it('errors - should return array of errors if validate and individualHooks are true', async function() { const data = [{ username: null }, { username: null }, { username: null }]; @@ -2936,15 +2688,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - expect(user.bulkCreate(data, { - validate: true, - individualHooks: true - })).to.be.rejectedWith(Promise.AggregateError); - }); + await this.sequelize.sync({ force: true }); + expect(user.bulkCreate(data, { + validate: true, + individualHooks: true + })).to.be.rejectedWith(errors.AggregateError); }); - it('should not use setter when renaming fields in dataValues', function() { + it('should not use setter when renaming fields in dataValues', async function() { const user = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -2964,13 +2715,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); const data = [{ username: 'jon' }]; - return this.sequelize.sync({ force: true }).then(() => { - return user.bulkCreate(data).then(() => { - return user.findAll().then(users1 => { - expect(users1[0].username).to.equal('jon'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await user.bulkCreate(data); + const users1 = await user.findAll(); + expect(users1[0].username).to.equal('jon'); }); }); }); diff --git a/test/integration/model/attributes.test.js b/test/integration/model/attributes.test.js index 38164addc8a6..99dd66e07ebc 100644 --- a/test/integration/model/attributes.test.js +++ b/test/integration/model/attributes.test.js @@ -2,14 +2,13 @@ const chai = require('chai'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'); describe(Support.getTestDialectTeaser('Model'), () => { describe('attributes', () => { describe('set', () => { - it('should only be called once when used on a join model called with an association getter', function() { + it('should only be called once when used on a join model called with an association getter', async function() { let callCount = 0; this.Student = this.sequelize.define('student', { @@ -45,33 +44,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Student.belongsToMany(this.Course, { through: this.Score, foreignKey: 'StudentId' }); this.Course.belongsToMany(this.Student, { through: this.Score, foreignKey: 'CourseId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.Student.create({ no: 1, name: 'ryan' }), - this.Course.create({ no: 100, name: 'history' }) - ).then(([student, course]) => { - return student.addCourse(course, { through: { score: 98, test_value: 1000 } }); - }).then(() => { - expect(callCount).to.equal(1); - return this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }).then(score => { - expect(score.test_value).to.equal(1001); - }); - }) - .then(() => { - return Promise.join( - new this.Student({ no: 1 }).getCourses({ where: { no: 100 } }), - this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }) - ); - }) - .then(([courses, score]) => { - expect(score.test_value).to.equal(1001); - expect(courses[0].score.toJSON().test_value).to.equal(1001); - expect(callCount).to.equal(1); - }); - }); + await this.sequelize.sync({ force: true }); + + const [student, course] = await Promise.all([ + this.Student.create({ no: 1, name: 'ryan' }), + this.Course.create({ no: 100, name: 'history' }) + ]); + + await student.addCourse(course, { through: { score: 98, test_value: 1000 } }); + expect(callCount).to.equal(1); + const score0 = await this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }); + expect(score0.test_value).to.equal(1001); + + const [courses, score] = await Promise.all([ + this.Student.build({ no: 1 }).getCourses({ where: { no: 100 } }), + this.Score.findOne({ where: { StudentId: 1, CourseId: 100 } }) + ]); + + expect(score.test_value).to.equal(1001); + expect(courses[0].score.toJSON().test_value).to.equal(1001); + expect(callCount).to.equal(1); }); - it('allows for an attribute to be called "toString"', function() { + it('allows for an attribute to be called "toString"', async function() { const Person = this.sequelize.define('person', { name: Sequelize.STRING, nick: Sequelize.STRING @@ -79,24 +74,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { timestamps: false }); - return this.sequelize.sync({ force: true }) - .then(() => Person.create({ name: 'Jozef', nick: 'Joe' })) - .then(() => Person.findOne({ - attributes: [ - 'nick', - ['name', 'toString'] - ], - where: { - name: 'Jozef' - } - })) - .then(person => { - expect(person.dataValues['toString']).to.equal('Jozef'); - expect(person.get('toString')).to.equal('Jozef'); - }); + await this.sequelize.sync({ force: true }); + await Person.create({ name: 'Jozef', nick: 'Joe' }); + + const person = await Person.findOne({ + attributes: [ + 'nick', + ['name', 'toString'] + ], + where: { + name: 'Jozef' + } + }); + + expect(person.dataValues['toString']).to.equal('Jozef'); + expect(person.get('toString')).to.equal('Jozef'); }); - it('allows for an attribute to be called "toString" with associations', function() { + it('allows for an attribute to be called "toString" with associations', async function() { const Person = this.sequelize.define('person', { name: Sequelize.STRING, nick: Sequelize.STRING @@ -108,41 +103,39 @@ describe(Support.getTestDialectTeaser('Model'), () => { Person.hasMany(Computer); - return this.sequelize.sync({ force: true }) - .then(() => Person.create({ name: 'Jozef', nick: 'Joe' })) - .then(person => person.createComputer({ hostname: 'laptop' })) - .then(() => Person.findAll({ - attributes: [ - 'nick', - ['name', 'toString'] - ], - include: { - model: Computer - }, - where: { - name: 'Jozef' - } - })) - .then(result => { - expect(result.length).to.equal(1); - expect(result[0].dataValues['toString']).to.equal('Jozef'); - expect(result[0].get('toString')).to.equal('Jozef'); - expect(result[0].get('computers')[0].hostname).to.equal('laptop'); - }); + await this.sequelize.sync({ force: true }); + const person = await Person.create({ name: 'Jozef', nick: 'Joe' }); + await person.createComputer({ hostname: 'laptop' }); + + const result = await Person.findAll({ + attributes: [ + 'nick', + ['name', 'toString'] + ], + include: { + model: Computer + }, + where: { + name: 'Jozef' + } + }); + + expect(result.length).to.equal(1); + expect(result[0].dataValues['toString']).to.equal('Jozef'); + expect(result[0].get('toString')).to.equal('Jozef'); + expect(result[0].get('computers')[0].hostname).to.equal('laptop'); }); }); describe('quote', () => { - it('allows for an attribute with dots', function() { + it('allows for an attribute with dots', async function() { const User = this.sequelize.define('user', { 'foo.bar.baz': Sequelize.TEXT }); - return this.sequelize.sync({ force: true }) - .then(() => User.findAll()) - .then(result => { - expect(result.length).to.equal(0); - }); + await this.sequelize.sync({ force: true }); + const result = await User.findAll(); + expect(result.length).to.equal(0); }); }); }); diff --git a/test/integration/model/attributes/field.test.js b/test/integration/model/attributes/field.test.js index 8c4d5de372e7..e03aebfe4ee2 100644 --- a/test/integration/model/attributes/field.test.js +++ b/test/integration/model/attributes/field.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../../support'), DataTypes = require('../../../../lib/data-types'), @@ -21,7 +20,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('attributes', () => { describe('field', () => { - beforeEach(function() { + beforeEach(async function() { const queryInterface = this.sequelize.getQueryInterface(); this.User = this.sequelize.define('user', { @@ -101,7 +100,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { through: 'userComments' }); - return Promise.all([ + await Promise.all([ queryInterface.createTable('users', { userId: { type: DataTypes.INTEGER, @@ -172,7 +171,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('primaryKey', () => { describe('in combination with allowNull', () => { - beforeEach(function() { + beforeEach(async function() { this.ModelUnderTest = this.sequelize.define('ModelUnderTest', { identifier: { primaryKey: true, @@ -181,151 +180,137 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.ModelUnderTest.sync({ force: true }); + await this.ModelUnderTest.sync({ force: true }); }); - it('sets the column to not allow null', function() { - return this + it('sets the column to not allow null', async function() { + const fields = await this .ModelUnderTest - .describe() - .then(fields => { - expect(fields.identifier).to.include({ allowNull: false }); - }); + .describe(); + + expect(fields.identifier).to.include({ allowNull: false }); }); }); - it('should support instance.destroy()', function() { - return this.User.create().then(user => { - return user.destroy(); - }); + it('should support instance.destroy()', async function() { + const user = await this.User.create(); + await user.destroy(); }); - it('should support Model.destroy()', function() { - return this.User.create().then(user => { - return this.User.destroy({ - where: { - id: user.get('id') - } - }); + it('should support Model.destroy()', async function() { + const user = await this.User.create(); + + await this.User.destroy({ + where: { + id: user.get('id') + } }); }); }); describe('field and attribute name is the same', () => { - beforeEach(function() { - return this.Comment.bulkCreate([ + beforeEach(async function() { + await this.Comment.bulkCreate([ { notes: 'Number one' }, { notes: 'Number two' } ]); }); - it('bulkCreate should work', function() { - return this.Comment.findAll().then(comments => { - expect(comments[0].notes).to.equal('Number one'); - expect(comments[1].notes).to.equal('Number two'); - }); + it('bulkCreate should work', async function() { + const comments = await this.Comment.findAll(); + expect(comments[0].notes).to.equal('Number one'); + expect(comments[1].notes).to.equal('Number two'); }); - it('find with where should work', function() { - return this.Comment.findAll({ where: { notes: 'Number one' } }).then(comments => { - expect(comments).to.have.length(1); - expect(comments[0].notes).to.equal('Number one'); - }); + it('find with where should work', async function() { + const comments = await this.Comment.findAll({ where: { notes: 'Number one' } }); + expect(comments).to.have.length(1); + expect(comments[0].notes).to.equal('Number one'); }); - it('reload should work', function() { - return this.Comment.findByPk(1).then(comment => { - return comment.reload(); - }); + it('reload should work', async function() { + const comment = await this.Comment.findByPk(1); + await comment.reload(); }); - it('save should work', function() { - return this.Comment.create({ notes: 'my note' }).then(comment => { - comment.notes = 'new note'; - return comment.save(); - }).then(comment => { - return comment.reload(); - }).then(comment => { - expect(comment.notes).to.equal('new note'); - }); + it('save should work', async function() { + const comment1 = await this.Comment.create({ notes: 'my note' }); + comment1.notes = 'new note'; + const comment0 = await comment1.save(); + const comment = await comment0.reload(); + expect(comment.notes).to.equal('new note'); }); }); - it('increment should work', function() { - return this.Comment.destroy({ truncate: true }) - .then(() => this.Comment.create({ note: 'oh boy, here I go again', likes: 23 })) - .then(comment => comment.increment('likes')) - .then(comment => comment.reload()) - .then(comment => { - expect(comment.likes).to.be.equal(24); - }); + it('increment should work', async function() { + await this.Comment.destroy({ truncate: true }); + const comment1 = await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 }); + const comment0 = await comment1.increment('likes'); + const comment = await comment0.reload(); + expect(comment.likes).to.be.equal(24); }); - it('decrement should work', function() { - return this.Comment.destroy({ truncate: true }) - .then(() => this.Comment.create({ note: 'oh boy, here I go again', likes: 23 })) - .then(comment => comment.decrement('likes')) - .then(comment => comment.reload()) - .then(comment => { - expect(comment.likes).to.be.equal(22); - }); + it('decrement should work', async function() { + await this.Comment.destroy({ truncate: true }); + const comment1 = await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 }); + const comment0 = await comment1.decrement('likes'); + const comment = await comment0.reload(); + expect(comment.likes).to.be.equal(22); }); - it('sum should work', function() { - return this.Comment.destroy({ truncate: true }) - .then(() => this.Comment.create({ note: 'oh boy, here I go again', likes: 23 })) - .then(() => this.Comment.sum('likes')) - .then(likes => { - expect(likes).to.be.equal(23); - }); + it('sum should work', async function() { + await this.Comment.destroy({ truncate: true }); + await this.Comment.create({ note: 'oh boy, here I go again', likes: 23 }); + const likes = await this.Comment.sum('likes'); + expect(likes).to.be.equal(23); }); - it('should create, fetch and update with alternative field names from a simple model', function() { - return this.User.create({ + it('should create, fetch and update with alternative field names from a simple model', async function() { + await this.User.create({ name: 'Foobar' - }).then(() => { - return this.User.findOne({ - limit: 1 - }); - }).then(user => { - expect(user.get('name')).to.equal('Foobar'); - return user.update({ - name: 'Barfoo' - }); - }).then(() => { - return this.User.findOne({ - limit: 1 - }); - }).then(user => { - expect(user.get('name')).to.equal('Barfoo'); }); + + const user0 = await this.User.findOne({ + limit: 1 + }); + + expect(user0.get('name')).to.equal('Foobar'); + + await user0.update({ + name: 'Barfoo' + }); + + const user = await this.User.findOne({ + limit: 1 + }); + + expect(user.get('name')).to.equal('Barfoo'); }); - it('should bulk update', function() { + it('should bulk update', async function() { const Entity = this.sequelize.define('Entity', { strField: { type: Sequelize.STRING, field: 'str_field' } }); - return this.sequelize.sync({ force: true }).then(() => { - return Entity.create({ strField: 'foo' }); - }).then(() => { - return Entity.update( - { strField: 'bar' }, - { where: { strField: 'foo' } } - ); - }).then(() => { - return Entity.findOne({ - where: { - strField: 'bar' - } - }).then(entity => { - expect(entity).to.be.ok; - expect(entity.get('strField')).to.equal('bar'); - }); + await this.sequelize.sync({ force: true }); + await Entity.create({ strField: 'foo' }); + + await Entity.update( + { strField: 'bar' }, + { where: { strField: 'foo' } } + ); + + const entity = await Entity.findOne({ + where: { + strField: 'bar' + } }); + + expect(entity).to.be.ok; + expect(entity.get('strField')).to.equal('bar'); }); - it('should not contain the field properties after create', function() { + it('should not contain the field properties after create', async function() { const Model = this.sequelize.define('test', { id: { type: Sequelize.INTEGER, @@ -347,218 +332,203 @@ describe(Support.getTestDialectTeaser('Model'), () => { freezeTableName: true }); - return Model.sync({ force: true }).then(() => { - return Model.create({ title: 'test' }).then(data => { - expect(data.get('test_title')).to.be.an('undefined'); - expect(data.get('test_id')).to.be.an('undefined'); - }); - }); + await Model.sync({ force: true }); + const data = await Model.create({ title: 'test' }); + expect(data.get('test_title')).to.be.an('undefined'); + expect(data.get('test_id')).to.be.an('undefined'); }); - it('should make the aliased auto incremented primary key available after create', function() { - return this.User.create({ + it('should make the aliased auto incremented primary key available after create', async function() { + const user = await this.User.create({ name: 'Barfoo' - }).then(user => { - expect(user.get('id')).to.be.ok; }); + + expect(user.get('id')).to.be.ok; }); - it('should work with where on includes for find', function() { - return this.User.create({ + it('should work with where on includes for find', async function() { + const user = await this.User.create({ name: 'Barfoo' - }).then(user => { - return user.createTask({ - title: 'DatDo' - }); - }).then(task => { - return task.createComment({ - text: 'Comment' - }); - }).then(() => { - return this.Task.findOne({ - include: [ - { model: this.Comment }, - { model: this.User } - ], - where: { title: 'DatDo' } - }); - }).then(task => { - expect(task.get('title')).to.equal('DatDo'); - expect(task.get('comments')[0].get('text')).to.equal('Comment'); - expect(task.get('user')).to.be.ok; }); + + const task0 = await user.createTask({ + title: 'DatDo' + }); + + await task0.createComment({ + text: 'Comment' + }); + + const task = await this.Task.findOne({ + include: [ + { model: this.Comment }, + { model: this.User } + ], + where: { title: 'DatDo' } + }); + + expect(task.get('title')).to.equal('DatDo'); + expect(task.get('comments')[0].get('text')).to.equal('Comment'); + expect(task.get('user')).to.be.ok; }); - it('should work with where on includes for findAll', function() { - return this.User.create({ + it('should work with where on includes for findAll', async function() { + const user = await this.User.create({ name: 'Foobar' - }).then(user => { - return user.createTask({ - title: 'DoDat' - }); - }).then(task => { - return task.createComment({ - text: 'Comment' - }); - }).then(() => { - return this.User.findAll({ - include: [ - { model: this.Task, where: { title: 'DoDat' }, include: [ - { model: this.Comment } - ] } - ] - }); - }).then(users => { - users.forEach(user => { - expect(user.get('name')).to.be.ok; - expect(user.get('tasks')[0].get('title')).to.equal('DoDat'); - expect(user.get('tasks')[0].get('comments')).to.be.ok; - }); }); - }); - it('should work with increment', function() { - return this.User.create().then(user => { - return user.increment('taskCount'); + const task = await user.createTask({ + title: 'DoDat' + }); + + await task.createComment({ + text: 'Comment' + }); + + const users = await this.User.findAll({ + include: [ + { model: this.Task, where: { title: 'DoDat' }, include: [ + { model: this.Comment } + ] } + ] + }); + + users.forEach(user => { + expect(user.get('name')).to.be.ok; + expect(user.get('tasks')[0].get('title')).to.equal('DoDat'); + expect(user.get('tasks')[0].get('comments')).to.be.ok; }); }); - it('should work with a simple where', function() { - return this.User.create({ + it('should work with increment', async function() { + const user = await this.User.create(); + await user.increment('taskCount'); + }); + + it('should work with a simple where', async function() { + await this.User.create({ name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: 'Foobar' - } - }); - }).then(user => { - expect(user).to.be.ok; }); + + const user = await this.User.findOne({ + where: { + name: 'Foobar' + } + }); + + expect(user).to.be.ok; }); - it('should work with a where or', function() { - return this.User.create({ + it('should work with a where or', async function() { + await this.User.create({ name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: this.sequelize.or({ - name: 'Foobar' - }, { - name: 'Lollerskates' - }) - }); - }).then(user => { - expect(user).to.be.ok; }); + + const user = await this.User.findOne({ + where: this.sequelize.or({ + name: 'Foobar' + }, { + name: 'Lollerskates' + }) + }); + + expect(user).to.be.ok; }); - it('should work with bulkCreate and findAll', function() { - return this.User.bulkCreate([{ + it('should work with bulkCreate and findAll', async function() { + await this.User.bulkCreate([{ name: 'Abc' }, { name: 'Bcd' }, { name: 'Cde' - }]).then(() => { - return this.User.findAll(); - }).then(users => { - users.forEach(user => { - expect(['Abc', 'Bcd', 'Cde'].includes(user.get('name'))).to.be.true; - }); + }]); + + const users = await this.User.findAll(); + users.forEach(user => { + expect(['Abc', 'Bcd', 'Cde'].includes(user.get('name'))).to.be.true; }); }); - it('should support renaming of sequelize method fields', function() { + it('should support renaming of sequelize method fields', async function() { const Test = this.sequelize.define('test', { someProperty: Sequelize.VIRTUAL // Since we specify the AS part as a part of the literal string, not with sequelize syntax, we have to tell sequelize about the field }); - return this.sequelize.sync({ force: true }).then(() => { - return Test.create({}); - }).then(() => { - let findAttributes; - if (dialect === 'mssql') { - findAttributes = [ - Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someProperty"'), - [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] - ]; - } else { - findAttributes = [ - Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'), - [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] - ]; - } + await this.sequelize.sync({ force: true }); + await Test.create({}); + let findAttributes; + if (dialect === 'mssql') { + findAttributes = [ + Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someProperty"'), + [Sequelize.literal('CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT)'), 'someProperty2'] + ]; + } else { + findAttributes = [ + Sequelize.literal('EXISTS(SELECT 1) AS "someProperty"'), + [Sequelize.literal('EXISTS(SELECT 1)'), 'someProperty2'] + ]; + } - return Test.findAll({ - attributes: findAttributes - }); - - }).then(tests => { - expect(tests[0].get('someProperty')).to.be.ok; - expect(tests[0].get('someProperty2')).to.be.ok; + const tests = await Test.findAll({ + attributes: findAttributes }); + + expect(tests[0].get('someProperty')).to.be.ok; + expect(tests[0].get('someProperty2')).to.be.ok; }); - it('should sync foreign keys with custom field names', function() { - return this.sequelize.sync({ force: true }) - .then(() => { - const attrs = this.Task.tableAttributes; - expect(attrs.user_id.references.model).to.equal('users'); - expect(attrs.user_id.references.key).to.equal('userId'); - }); + it('should sync foreign keys with custom field names', async function() { + await this.sequelize.sync({ force: true }); + const attrs = this.Task.tableAttributes; + expect(attrs.user_id.references.model).to.equal('users'); + expect(attrs.user_id.references.key).to.equal('userId'); }); - it('should find the value of an attribute with a custom field name', function() { - return this.User.create({ name: 'test user' }) - .then(() => { - return this.User.findOne({ where: { name: 'test user' } }); - }) - .then(user => { - expect(user.name).to.equal('test user'); - }); + it('should find the value of an attribute with a custom field name', async function() { + await this.User.create({ name: 'test user' }); + const user = await this.User.findOne({ where: { name: 'test user' } }); + expect(user.name).to.equal('test user'); }); - it('field names that are the same as property names should create, update, and read correctly', function() { - return this.Comment.create({ + it('field names that are the same as property names should create, update, and read correctly', async function() { + await this.Comment.create({ notes: 'Foobar' - }).then(() => { - return this.Comment.findOne({ - limit: 1 - }); - }).then(comment => { - expect(comment.get('notes')).to.equal('Foobar'); - return comment.update({ - notes: 'Barfoo' - }); - }).then(() => { - return this.Comment.findOne({ - limit: 1 - }); - }).then(comment => { - expect(comment.get('notes')).to.equal('Barfoo'); }); + + const comment0 = await this.Comment.findOne({ + limit: 1 + }); + + expect(comment0.get('notes')).to.equal('Foobar'); + + await comment0.update({ + notes: 'Barfoo' + }); + + const comment = await this.Comment.findOne({ + limit: 1 + }); + + expect(comment.get('notes')).to.equal('Barfoo'); }); - it('should work with a belongsTo association getter', function() { + it('should work with a belongsTo association getter', async function() { const userId = Math.floor(Math.random() * 100000); - return Promise.join( - this.User.create({ - id: userId - }), - this.Task.create({ - user_id: userId - }) - ).then(([user, task]) => { - return Promise.all([user, task.getUser()]); - }).then(([userA, userB]) => { - expect(userA.get('id')).to.equal(userB.get('id')); - expect(userA.get('id')).to.equal(userId); - expect(userB.get('id')).to.equal(userId); - }); + + const [user, task] = await Promise.all([this.User.create({ + id: userId + }), this.Task.create({ + user_id: userId + })]); + + const [userA, userB] = await Promise.all([user, task.getUser()]); + expect(userA.get('id')).to.equal(userB.get('id')); + expect(userA.get('id')).to.equal(userId); + expect(userB.get('id')).to.equal(userId); }); - it('should work with paranoid instance.destroy()', function() { + it('should work with paranoid instance.destroy()', async function() { const User = this.sequelize.define('User', { deletedAt: { type: DataTypes.DATE, @@ -569,23 +539,15 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return User.sync({ force: true }) - .then(() => { - return User.create(); - }) - .then(user => { - return user.destroy(); - }) - .then(() => { - this.clock.tick(1000); - return User.findAll(); - }) - .then(users => { - expect(users.length).to.equal(0); - }); + await User.sync({ force: true }); + const user = await User.create(); + await user.destroy(); + this.clock.tick(1000); + const users = await User.findAll(); + expect(users.length).to.equal(0); }); - it('should work with paranoid Model.destroy()', function() { + it('should work with paranoid Model.destroy()', async function() { const User = this.sequelize.define('User', { deletedAt: { type: DataTypes.DATE, @@ -596,31 +558,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return User.sync({ force: true }).then(() => { - return User.create().then(user => { - return User.destroy({ where: { id: user.get('id') } }); - }).then(() => { - return User.findAll().then(users => { - expect(users.length).to.equal(0); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create(); + await User.destroy({ where: { id: user.get('id') } }); + const users = await User.findAll(); + expect(users.length).to.equal(0); }); - it('should work with `belongsToMany` association `count`', function() { - return this.User.create({ + it('should work with `belongsToMany` association `count`', async function() { + const user = await this.User.create({ name: 'John' - }) - .then(user => user.countComments()) - .then(commentCount => expect(commentCount).to.equal(0)); + }); + + const commentCount = await user.countComments(); + await expect(commentCount).to.equal(0); }); - it('should work with `hasMany` association `count`', function() { - return this.User.create({ + it('should work with `hasMany` association `count`', async function() { + const user = await this.User.create({ name: 'John' - }) - .then(user => user.countTasks()) - .then(taskCount => expect(taskCount).to.equal(0)); + }); + + const taskCount = await user.countTasks(); + await expect(taskCount).to.equal(0); }); }); }); diff --git a/test/integration/model/attributes/types.test.js b/test/integration/model/attributes/types.test.js index da2da915b90b..8da3d1235ddb 100644 --- a/test/integration/model/attributes/types.test.js +++ b/test/integration/model/attributes/types.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../../support'), dialect = Support.getTestDialect(); @@ -11,7 +10,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('attributes', () => { describe('types', () => { describe('VIRTUAL', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { storage: Sequelize.STRING, field1: { @@ -49,11 +48,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(sql).to.not.include('field2'); }; - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); it('should not be ignored in dataValues get', function() { - const user = new this.User({ + const user = this.User.build({ field1: 'field1_value', field2: 'field2_value' }); @@ -61,14 +60,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(user.get()).to.deep.equal({ storage: 'field1_value', field1: 'field1_value', virtualWithDefault: 'cake', field2: 42, id: null }); }); - it('should be ignored in table creation', function() { - return this.sequelize.getQueryInterface().describeTable(this.User.tableName).then(fields => { - expect(Object.keys(fields).length).to.equal(2); - }); + it('should be ignored in table creation', async function() { + const fields = await this.sequelize.getQueryInterface().describeTable(this.User.tableName); + expect(Object.keys(fields).length).to.equal(2); }); - it('should be ignored in find, findAll and includes', function() { - return Promise.all([ + it('should be ignored in find, findAll and includes', async function() { + await Promise.all([ this.User.findOne({ logging: this.sqlAssert }), @@ -90,7 +88,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { ]); }); - it('should allow me to store selected values', function() { + it('should allow me to store selected values', async function() { const Post = this.sequelize.define('Post', { text: Sequelize.TEXT, someBoolean: { @@ -98,98 +96,94 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.bulkCreate([{ text: 'text1' }, { text: 'text2' }]); - }).then(() => { - let boolQuery = 'EXISTS(SELECT 1) AS "someBoolean"'; - if (dialect === 'mssql') { - boolQuery = 'CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someBoolean"'; - } + await this.sequelize.sync({ force: true }); + await Post.bulkCreate([{ text: 'text1' }, { text: 'text2' }]); + let boolQuery = 'EXISTS(SELECT 1) AS "someBoolean"'; + if (dialect === 'mssql') { + boolQuery = 'CAST(CASE WHEN EXISTS(SELECT 1) THEN 1 ELSE 0 END AS BIT) AS "someBoolean"'; + } - return Post.findOne({ attributes: ['id', 'text', Sequelize.literal(boolQuery)] }); - }).then(post => { - expect(post.get('someBoolean')).to.be.ok; - expect(post.get().someBoolean).to.be.ok; - }); + const post = await Post.findOne({ attributes: ['id', 'text', Sequelize.literal(boolQuery)] }); + expect(post.get('someBoolean')).to.be.ok; + expect(post.get().someBoolean).to.be.ok; }); - it('should be ignored in create and update', function() { - return this.User.create({ + it('should be ignored in create and update', async function() { + const user0 = await this.User.create({ field1: 'something' - }).then(user => { - // We already verified that the virtual is not added to the table definition, - // so if this succeeds, were good - - expect(user.virtualWithDefault).to.equal('cake'); - expect(user.storage).to.equal('something'); - return user.update({ - field1: 'something else' - }, { - fields: ['storage'] - }); - }).then(user => { - expect(user.virtualWithDefault).to.equal('cake'); - expect(user.storage).to.equal('something else'); }); + + // We already verified that the virtual is not added to the table definition, + // so if this succeeds, were good + + expect(user0.virtualWithDefault).to.equal('cake'); + expect(user0.storage).to.equal('something'); + + const user = await user0.update({ + field1: 'something else' + }, { + fields: ['storage'] + }); + + expect(user.virtualWithDefault).to.equal('cake'); + expect(user.storage).to.equal('something else'); }); - it('should be ignored in bulkCreate and and bulkUpdate', function() { - return this.User.bulkCreate([{ + it('should be ignored in bulkCreate and and bulkUpdate', async function() { + await this.User.bulkCreate([{ field1: 'something' }], { logging: this.sqlAssert - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users[0].storage).to.equal('something'); }); + + const users = await this.User.findAll(); + expect(users[0].storage).to.equal('something'); }); - it('should be able to exclude with attributes', function() { - return this.User.bulkCreate([{ + it('should be able to exclude with attributes', async function() { + await this.User.bulkCreate([{ field1: 'something' }], { logging: this.sqlAssert - }).then(() => { - return this.User.findAll({ - logging: this.sqlAssert - }); - }).then(users => { - const user = users[0].get(); + }); - expect(user.storage).to.equal('something'); - expect(user).to.include.all.keys(['field1', 'field2']); + const users0 = await this.User.findAll({ + logging: this.sqlAssert + }); - return this.User.findAll({ - attributes: { - exclude: ['field1'] - }, - logging: this.sqlAssert - }); - }).then(users => { - const user = users[0].get(); + const user0 = users0[0].get(); - expect(user.storage).to.equal('something'); - expect(user).not.to.include.all.keys(['field1']); - expect(user).to.include.all.keys(['field2']); + expect(user0.storage).to.equal('something'); + expect(user0).to.include.all.keys(['field1', 'field2']); + + const users = await this.User.findAll({ + attributes: { + exclude: ['field1'] + }, + logging: this.sqlAssert }); + + const user = users[0].get(); + + expect(user.storage).to.equal('something'); + expect(user).not.to.include.all.keys(['field1']); + expect(user).to.include.all.keys(['field2']); }); - it('should be able to include model with virtual attributes', function() { - return this.User.create({}).then(user => { - return user.createTask(); - }).then(() => { - return this.Task.findAll({ - include: [{ - attributes: ['field2', 'id'], - model: this.User - }] - }); - }).then(tasks => { - const user = tasks[0].user.get(); - - expect(user.field2).to.equal(42); + it('should be able to include model with virtual attributes', async function() { + const user0 = await this.User.create({}); + await user0.createTask(); + + const tasks = await this.Task.findAll({ + include: [{ + attributes: ['field2', 'id'], + model: this.User + }] }); + + const user = tasks[0].user.get(); + + expect(user.field2).to.equal(42); }); }); }); diff --git a/test/integration/model/bulk-create.test.js b/test/integration/model/bulk-create.test.js index 10ee9845472c..e0af4fea371b 100644 --- a/test/integration/model/bulk-create.test.js +++ b/test/integration/model/bulk-create.test.js @@ -2,8 +2,8 @@ const chai = require('chai'), Sequelize = require('../../../index'), + AggregateError = require('../../../lib/errors/aggregate-error'), Op = Sequelize.Op, - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), @@ -11,71 +11,69 @@ const chai = require('chai'), current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: { - type: DataTypes.STRING, - field: 'secret_value' - }, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN, - uniqueName: { type: DataTypes.STRING, unique: true } - }); - this.Account = this.sequelize.define('Account', { - accountName: DataTypes.STRING - }); - this.Student = this.sequelize.define('Student', { - no: { type: DataTypes.INTEGER, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false } - }); - this.Car = this.sequelize.define('Car', { - plateNumber: { - type: DataTypes.STRING, - primaryKey: true, - field: 'plate_number' - }, - color: { - type: DataTypes.TEXT - } - }); - - return this.sequelize.sync({ force: true }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.User = this.sequelize.define('User', { + username: DataTypes.STRING, + secretValue: { + type: DataTypes.STRING, + field: 'secret_value' + }, + data: DataTypes.STRING, + intVal: DataTypes.INTEGER, + theDate: DataTypes.DATE, + aBool: DataTypes.BOOLEAN, + uniqueName: { type: DataTypes.STRING, unique: true } + }); + this.Account = this.sequelize.define('Account', { + accountName: DataTypes.STRING + }); + this.Student = this.sequelize.define('Student', { + no: { type: DataTypes.INTEGER, primaryKey: true }, + name: { type: DataTypes.STRING, allowNull: false } + }); + this.Car = this.sequelize.define('Car', { + plateNumber: { + type: DataTypes.STRING, + primaryKey: true, + field: 'plate_number' + }, + color: { + type: DataTypes.TEXT + } }); + + await this.sequelize.sync({ force: true }); }); describe('bulkCreate', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { + it('supports transactions', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - let transaction, count1; - return User.sync({ force: true }) - .then(() => this.sequelize.transaction()) - .then(t => { - transaction = t; - return User.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction }); - }) - .then(() => User.count()) - .then(count => { - count1 = count; - return User.count({ transaction }); - }) - .then(count2 => { - expect(count1).to.equal(0); - expect(count2).to.equal(2); - return transaction.rollback(); - }); + await User.sync({ force: true }); + const transaction = await this.sequelize.transaction(); + await User.bulkCreate([{ username: 'foo' }, { username: 'bar' }], { transaction }); + const count1 = await User.count(); + const count2 = await User.count({ transaction }); + expect(count1).to.equal(0); + expect(count2).to.equal(2); + await transaction.rollback(); }); } + + it('should not alter options', async function() { + const User = this.sequelize.define('User'); + await User.sync({ force: true }); + const options = { anOption: 1 }; + await User.bulkCreate([{ }], options); + expect(options).to.eql({ anOption: 1 }); + }); - it('should be able to set createdAt and updatedAt if using silent: true', function() { + it('should be able to set createdAt and updatedAt if using silent: true', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }, { @@ -89,39 +87,39 @@ describe(Support.getTestDialectTeaser('Model'), () => { updatedAt }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate(values, { - silent: true - }).then(() => { - return User.findAll({ - where: { - updatedAt: { - [Op.ne]: null - } - } - }).then(users => { - users.forEach(user => { - expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); - expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); - }); - }); - }); + await User.sync({ force: true }); + + await User.bulkCreate(values, { + silent: true + }); + + const users = await User.findAll({ + where: { + updatedAt: { + [Op.ne]: null + } + } + }); + + users.forEach(user => { + expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); + expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); }); }); - it('should not fail on validate: true and individualHooks: true', function() { + it('should not fail on validate: true and individualHooks: true', async function() { const User = this.sequelize.define('user', { name: Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { name: 'James' } - ], { validate: true, individualHooks: true }); - }); + await User.sync({ force: true }); + + await User.bulkCreate([ + { name: 'James' } + ], { validate: true, individualHooks: true }); }); - it('should not map instance dataValues to fields with individualHooks: true', function() { + it('should not map instance dataValues to fields with individualHooks: true', async function() { const User = this.sequelize.define('user', { name: Sequelize.STRING, type: { @@ -140,169 +138,153 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { name: 'James', type: 'A' }, - { name: 'Alan', type: 'Z' } - ], { individualHooks: true }); - }); + await User.sync({ force: true }); + + await User.bulkCreate([ + { name: 'James', type: 'A' }, + { name: 'Alan', type: 'Z' } + ], { individualHooks: true }); }); - it('should not insert NULL for unused fields', function() { + it('should not insert NULL for unused fields', async function() { const Beer = this.sequelize.define('Beer', { style: Sequelize.STRING, size: Sequelize.INTEGER }); - return Beer.sync({ force: true }).then(() => { - return Beer.bulkCreate([{ - style: 'ipa' - }], { - logging(sql) { - if (dialect === 'postgres') { - expect(sql).to.include('INSERT INTO "Beers" ("id","style","createdAt","updatedAt") VALUES (DEFAULT'); - } else if (dialect === 'mssql') { - expect(sql).to.include('INSERT INTO [Beers] ([style],[createdAt],[updatedAt]) '); - } else { // mysql, sqlite - expect(sql).to.include('INSERT INTO `Beers` (`id`,`style`,`createdAt`,`updatedAt`) VALUES (NULL'); - } + await Beer.sync({ force: true }); + + await Beer.bulkCreate([{ + style: 'ipa' + }], { + logging(sql) { + if (dialect === 'postgres') { + expect(sql).to.include('INSERT INTO "Beers" ("id","style","createdAt","updatedAt") VALUES (DEFAULT'); + } else if (dialect === 'mssql') { + expect(sql).to.include('INSERT INTO [Beers] ([style],[createdAt],[updatedAt]) '); + } else { // mysql, sqlite + expect(sql).to.include('INSERT INTO `Beers` (`id`,`style`,`createdAt`,`updatedAt`) VALUES (NULL'); } - }); + } }); }); - it('properly handles disparate field lists', function() { + it('properly handles disparate field lists', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', uniqueName: '2' }, { username: 'Steve', uniqueName: '3' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ where: { username: 'Paul' } }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].username).to.equal('Paul'); - expect(users[0].secretValue).to.be.null; - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ where: { username: 'Paul' } }); + expect(users.length).to.equal(1); + expect(users[0].username).to.equal('Paul'); + expect(users[0].secretValue).to.be.null; }); - it('inserts multiple values respecting the white list', function() { + it('inserts multiple values respecting the white list', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - return this.User.bulkCreate(data, { fields: ['username', 'uniqueName'] }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].secretValue).to.be.null; - expect(users[1].username).to.equal('Paul'); - expect(users[1].secretValue).to.be.null; - }); - }); + await this.User.bulkCreate(data, { fields: ['username', 'uniqueName'] }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].secretValue).to.be.null; + expect(users[1].username).to.equal('Paul'); + expect(users[1].secretValue).to.be.null; }); - it('should store all values if no whitelist is specified', function() { + it('should store all values if no whitelist is specified', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].secretValue).to.equal('42'); - expect(users[1].username).to.equal('Paul'); - expect(users[1].secretValue).to.equal('23'); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].secretValue).to.equal('42'); + expect(users[1].username).to.equal('Paul'); + expect(users[1].secretValue).to.equal('23'); }); - it('should set isNewRecord = false', function() { + it('should set isNewRecord = false', async function() { const data = [{ username: 'Peter', secretValue: '42', uniqueName: '1' }, { username: 'Paul', secretValue: '23', uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - users.forEach(user => { - expect(user.isNewRecord).to.equal(false); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + users.forEach(user => { + expect(user.isNewRecord).to.equal(false); }); }); - it('saves data with single quote', function() { + it('saves data with single quote', async function() { const quote = "Single'Quote", data = [{ username: 'Peter', data: quote, uniqueName: '1' }, { username: 'Paul', data: quote, uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].data).to.equal(quote); - expect(users[1].username).to.equal('Paul'); - expect(users[1].data).to.equal(quote); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].data).to.equal(quote); + expect(users[1].username).to.equal('Paul'); + expect(users[1].data).to.equal(quote); }); - it('saves data with double quote', function() { + it('saves data with double quote', async function() { const quote = 'Double"Quote', data = [{ username: 'Peter', data: quote, uniqueName: '1' }, { username: 'Paul', data: quote, uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].data).to.equal(quote); - expect(users[1].username).to.equal('Paul'); - expect(users[1].data).to.equal(quote); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].data).to.equal(quote); + expect(users[1].username).to.equal('Paul'); + expect(users[1].data).to.equal(quote); }); - it('saves stringified JSON data', function() { + it('saves stringified JSON data', async function() { const json = JSON.stringify({ key: 'value' }), data = [{ username: 'Peter', data: json, uniqueName: '1' }, { username: 'Paul', data: json, uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(users[0].data).to.equal(json); - expect(users[1].username).to.equal('Paul'); - expect(users[1].data).to.equal(json); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(users[0].data).to.equal(json); + expect(users[1].username).to.equal('Paul'); + expect(users[1].data).to.equal(json); }); - it('properly handles a model with a length column', function() { + it('properly handles a model with a length column', async function() { const UserWithLength = this.sequelize.define('UserWithLength', { length: Sequelize.INTEGER }); - return UserWithLength.sync({ force: true }).then(() => { - return UserWithLength.bulkCreate([{ length: 42 }, { length: 11 }]); - }); + await UserWithLength.sync({ force: true }); + + await UserWithLength.bulkCreate([{ length: 42 }, { length: 11 }]); }); - it('stores the current date in createdAt', function() { + it('stores the current date in createdAt', async function() { const data = [{ username: 'Peter', uniqueName: '1' }, { username: 'Paul', uniqueName: '2' }]; - return this.User.bulkCreate(data).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].username).to.equal('Peter'); - expect(parseInt(+users[0].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); - expect(users[1].username).to.equal('Paul'); - expect(parseInt(+users[1].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); - }); - }); + await this.User.bulkCreate(data); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(2); + expect(users[0].username).to.equal('Peter'); + expect(parseInt(+users[0].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); + expect(users[1].username).to.equal('Paul'); + expect(parseInt(+users[1].createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); }); - it('emits an error when validate is set to true', function() { + it('emits an error when validate is set to true', async function() { const Tasks = this.sequelize.define('Task', { name: { type: Sequelize.STRING, @@ -316,33 +298,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Tasks.sync({ force: true }).then(() => { - return Tasks.bulkCreate([ + await Tasks.sync({ force: true }); + + try { + await Tasks.bulkCreate([ { name: 'foo', code: '123' }, { code: '1234' }, { name: 'bar', code: '1' } - ], { validate: true }).catch(errors => { - const expectedValidationError = 'Validation len on code failed'; - const expectedNotNullError = 'notNull Violation: Task.name cannot be null'; - - expect(errors).to.be.instanceof(Promise.AggregateError); - expect(errors.toString()).to.include(expectedValidationError) - .and.to.include(expectedNotNullError); - expect(errors).to.have.length(2); - - const e0name0 = errors[0].errors.get('name')[0]; - - expect(errors[0].record.code).to.equal('1234'); - expect(e0name0.type || e0name0.origin).to.equal('notNull Violation'); - - expect(errors[1].record.name).to.equal('bar'); - expect(errors[1].record.code).to.equal('1'); - expect(errors[1].errors.get('code')[0].message).to.equal(expectedValidationError); - }); - }); + ], { validate: true }); + } catch (error) { + const expectedValidationError = 'Validation len on code failed'; + const expectedNotNullError = 'notNull Violation: Task.name cannot be null'; + + expect(error).to.be.instanceof(AggregateError); + expect(error.toString()).to.include(expectedValidationError) + .and.to.include(expectedNotNullError); + const { errors } = error; + expect(errors).to.have.length(2); + + const e0name0 = errors[0].errors.get('name')[0]; + + expect(errors[0].record.code).to.equal('1234'); + expect(e0name0.type || e0name0.origin).to.equal('notNull Violation'); + + expect(errors[1].record.name).to.equal('bar'); + expect(errors[1].record.code).to.equal('1'); + expect(errors[1].errors.get('code')[0].message).to.equal(expectedValidationError); + } }); - it("doesn't emit an error when validate is set to true but our selectedValues are fine", function() { + it("doesn't emit an error when validate is set to true but our selectedValues are fine", async function() { const Tasks = this.sequelize.define('Task', { name: { type: Sequelize.STRING, @@ -358,49 +343,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Tasks.sync({ force: true }).then(() => { - return Tasks.bulkCreate([ - { name: 'foo', code: '123' }, - { code: '1234' } - ], { fields: ['code'], validate: true }); - }); + await Tasks.sync({ force: true }); + + await Tasks.bulkCreate([ + { name: 'foo', code: '123' }, + { code: '1234' } + ], { fields: ['code'], validate: true }); }); - it('should allow blank arrays (return immediately)', function() { + it('should allow blank arrays (return immediately)', async function() { const Worker = this.sequelize.define('Worker', {}); - return Worker.sync().then(() => { - return Worker.bulkCreate([]).then(workers => { - expect(workers).to.be.ok; - expect(workers.length).to.equal(0); - }); - }); + await Worker.sync(); + const workers = await Worker.bulkCreate([]); + expect(workers).to.be.ok; + expect(workers.length).to.equal(0); }); - it('should allow blank creates (with timestamps: false)', function() { + it('should allow blank creates (with timestamps: false)', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.bulkCreate([{}, {}]).then(workers => { - expect(workers).to.be.ok; - }); - }); + await Worker.sync(); + const workers = await Worker.bulkCreate([{}, {}]); + expect(workers).to.be.ok; }); - it('should allow autoincremented attributes to be set', function() { + it('should allow autoincremented attributes to be set', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.bulkCreate([ - { id: 5 }, - { id: 10 } - ]).then(() => { - return Worker.findAll({ order: [['id', 'ASC']] }).then(workers => { - expect(workers[0].id).to.equal(5); - expect(workers[1].id).to.equal(10); - }); - }); - }); + await Worker.sync(); + + await Worker.bulkCreate([ + { id: 5 }, + { id: 10 } + ]); + + const workers = await Worker.findAll({ order: [['id', 'ASC']] }); + expect(workers[0].id).to.equal(5); + expect(workers[1].id).to.equal(10); }); - it('should support schemas', function() { + it('should support schemas', async function() { const Dummy = this.sequelize.define('Dummy', { foo: DataTypes.STRING, bar: DataTypes.STRING @@ -409,159 +389,345 @@ describe(Support.getTestDialectTeaser('Model'), () => { tableName: 'Dummy' }); - return Support.dropTestSchemas(this.sequelize).then(() => { - return this.sequelize.createSchema('space1'); - }).then(() => { - return Dummy.sync({ force: true }); - }).then(() => { - return Dummy.bulkCreate([ - { foo: 'a', bar: 'b' }, - { foo: 'c', bar: 'd' } - ]); - }); + await Support.dropTestSchemas(this.sequelize); + await this.sequelize.createSchema('space1'); + await Dummy.sync({ force: true }); + + await Dummy.bulkCreate([ + { foo: 'a', bar: 'b' }, + { foo: 'c', bar: 'd' } + ]); }); if (current.dialect.supports.inserts.ignoreDuplicates || current.dialect.supports.inserts.onConflictDoNothing) { - it('should support the ignoreDuplicates option', function() { + it('should support the ignoreDuplicates option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).then(() => { - data.push({ uniqueName: 'Michael', secretValue: '26' }); - - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(3); - expect(users[0].uniqueName).to.equal('Peter'); - expect(users[0].secretValue).to.equal('42'); - expect(users[1].uniqueName).to.equal('Paul'); - expect(users[1].secretValue).to.equal('23'); - expect(users[2].uniqueName).to.equal('Michael'); - expect(users[2].secretValue).to.equal('26'); - }); - }); - }); + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }); + data.push({ uniqueName: 'Michael', secretValue: '26' }); + + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(3); + expect(users[0].uniqueName).to.equal('Peter'); + expect(users[0].secretValue).to.equal('42'); + expect(users[1].uniqueName).to.equal('Paul'); + expect(users[1].secretValue).to.equal('23'); + expect(users[2].uniqueName).to.equal('Michael'); + expect(users[2].secretValue).to.equal('26'); }); } else { - it('should throw an error when the ignoreDuplicates option is passed', function() { + it('should throw an error when the ignoreDuplicates option is passed', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).then(() => { - data.push({ uniqueName: 'Michael', secretValue: '26' }); + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }); + data.push({ uniqueName: 'Michael', secretValue: '26' }); - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).catch(err => { - expect(err.message).to.equal(`${dialect} does not support the ignoreDuplicates option.`); - }); - }); + try { + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }); + } catch (err) { + expect(err.message).to.equal(`${dialect} does not support the ignoreDuplicates option.`); + } }); } if (current.dialect.supports.inserts.updateOnDuplicate) { describe('updateOnDuplicate', () => { - it('should support the updateOnDuplicate option', function() { + it('should support the updateOnDuplicate option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }).then(() => { - const new_data = [ - { uniqueName: 'Peter', secretValue: '43' }, - { uniqueName: 'Paul', secretValue: '24' }, - { uniqueName: 'Michael', secretValue: '26' } - ]; - return this.User.bulkCreate(new_data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }).then(() => { - return this.User.findAll({ order: ['id'] }).then(users => { - expect(users.length).to.equal(3); - expect(users[0].uniqueName).to.equal('Peter'); - expect(users[0].secretValue).to.equal('43'); - expect(users[1].uniqueName).to.equal('Paul'); - expect(users[1].secretValue).to.equal('24'); - expect(users[2].uniqueName).to.equal('Michael'); - expect(users[2].secretValue).to.equal('26'); - }); - }); - }); + await this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }); + const new_data = [ + { uniqueName: 'Peter', secretValue: '43' }, + { uniqueName: 'Paul', secretValue: '24' }, + { uniqueName: 'Michael', secretValue: '26' } + ]; + await this.User.bulkCreate(new_data, { fields: ['uniqueName', 'secretValue'], updateOnDuplicate: ['secretValue'] }); + const users = await this.User.findAll({ order: ['id'] }); + expect(users.length).to.equal(3); + expect(users[0].uniqueName).to.equal('Peter'); + expect(users[0].secretValue).to.equal('43'); + expect(users[1].uniqueName).to.equal('Paul'); + expect(users[1].secretValue).to.equal('24'); + expect(users[2].uniqueName).to.equal('Michael'); + expect(users[2].secretValue).to.equal('26'); }); describe('should support the updateOnDuplicate option with primary keys', () => { - it('when the primary key column names and model field names are the same', function() { + it('when the primary key column names and model field names are the same', async function() { const data = [ { no: 1, name: 'Peter' }, { no: 2, name: 'Paul' } ]; - return this.Student.bulkCreate(data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }).then(() => { - const new_data = [ - { no: 1, name: 'Peterson' }, - { no: 2, name: 'Paulson' }, - { no: 3, name: 'Michael' } - ]; - return this.Student.bulkCreate(new_data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }).then(() => { - return this.Student.findAll({ order: ['no'] }).then(students => { - expect(students.length).to.equal(3); - expect(students[0].name).to.equal('Peterson'); - expect(students[0].no).to.equal(1); - expect(students[1].name).to.equal('Paulson'); - expect(students[1].no).to.equal(2); - expect(students[2].name).to.equal('Michael'); - expect(students[2].no).to.equal(3); - }); - }); - }); + await this.Student.bulkCreate(data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }); + const new_data = [ + { no: 1, name: 'Peterson' }, + { no: 2, name: 'Paulson' }, + { no: 3, name: 'Michael' } + ]; + await this.Student.bulkCreate(new_data, { fields: ['no', 'name'], updateOnDuplicate: ['name'] }); + const students = await this.Student.findAll({ order: ['no'] }); + expect(students.length).to.equal(3); + expect(students[0].name).to.equal('Peterson'); + expect(students[0].no).to.equal(1); + expect(students[1].name).to.equal('Paulson'); + expect(students[1].no).to.equal(2); + expect(students[2].name).to.equal('Michael'); + expect(students[2].no).to.equal(3); }); - it('when the primary key column names and model field names are different', function() { + it('when the primary key column names and model field names are different', async function() { const data = [ { plateNumber: 'abc', color: 'Grey' }, { plateNumber: 'def', color: 'White' } ]; - return this.Car.bulkCreate(data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }).then(() => { - const new_data = [ - { plateNumber: 'abc', color: 'Red' }, - { plateNumber: 'def', color: 'Green' }, - { plateNumber: 'ghi', color: 'Blue' } - ]; - return this.Car.bulkCreate(new_data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }).then(() => { - return this.Car.findAll({ order: ['plateNumber'] }).then(cars => { - expect(cars.length).to.equal(3); - expect(cars[0].plateNumber).to.equal('abc'); - expect(cars[0].color).to.equal('Red'); - expect(cars[1].plateNumber).to.equal('def'); - expect(cars[1].color).to.equal('Green'); - expect(cars[2].plateNumber).to.equal('ghi'); - expect(cars[2].color).to.equal('Blue'); - }); - }); + await this.Car.bulkCreate(data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }); + const new_data = [ + { plateNumber: 'abc', color: 'Red' }, + { plateNumber: 'def', color: 'Green' }, + { plateNumber: 'ghi', color: 'Blue' } + ]; + await this.Car.bulkCreate(new_data, { fields: ['plateNumber', 'color'], updateOnDuplicate: ['color'] }); + const cars = await this.Car.findAll({ order: ['plateNumber'] }); + expect(cars.length).to.equal(3); + expect(cars[0].plateNumber).to.equal('abc'); + expect(cars[0].color).to.equal('Red'); + expect(cars[1].plateNumber).to.equal('def'); + expect(cars[1].color).to.equal('Green'); + expect(cars[2].plateNumber).to.equal('ghi'); + expect(cars[2].color).to.equal('Blue'); + }); + + it('when the primary key column names and model field names are different and have unique constraints', async function() { + const Person = this.sequelize.define('Person', { + emailAddress: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + unique: true, + field: 'email_address' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, {}); + + await Person.sync({ force: true }); + const inserts = [ + { emailAddress: 'a@example.com', name: 'Alice' } + ]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].emailAddress).to.equal('a@example.com'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { emailAddress: 'a@example.com', name: 'CHANGED NAME' }, + { emailAddress: 'b@example.com', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['emailAddress', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].emailAddress).to.equal('a@example.com'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].emailAddress).to.equal('b@example.com'); + expect(people[1].name).to.equal('Bob'); + }); + + it('when the composite primary key column names and model field names are different', async function() { + const Person = this.sequelize.define('Person', { + systemId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + field: 'system_id' + }, + system: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + field: 'system' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, {}); + + await Person.sync({ force: true }); + const inserts = [ + { systemId: 1, system: 'system1', name: 'Alice' } + ]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].systemId).to.equal(1); + expect(people0[0].system).to.equal('system1'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { systemId: 1, system: 'system2', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); + }); + + it('when the primary key column names and model field names are different and have composite unique constraints', async function() { + const Person = this.sequelize.define('Person', { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + field: 'id' + }, + systemId: { + type: DataTypes.INTEGER, + allowNull: false, + unique: 'system_id_system_unique', + field: 'system_id' + }, + system: { + type: DataTypes.STRING, + allowNull: false, + unique: 'system_id_system_unique', + field: 'system' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, {}); + + await Person.sync({ force: true }); + const inserts = [ + { id: 1, systemId: 1, system: 'system1', name: 'Alice' } + ]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].systemId).to.equal(1); + expect(people0[0].system).to.equal('system1'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { id: 1, systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { id: 2, systemId: 1, system: 'system2', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { updateOnDuplicate: ['systemId', 'system', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); + }); + + it('[#12516] when the primary key column names and model field names are different and have composite unique index constraints', async function() { + const Person = this.sequelize.define( + 'Person', + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + autoIncrement: true, + primaryKey: true, + field: 'id' + }, + systemId: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'system_id' + }, + system: { + type: DataTypes.STRING, + allowNull: false, + field: 'system' + }, + name: { + type: DataTypes.STRING, + allowNull: false, + field: 'name' + } + }, + { + indexes: [ + { + unique: true, + fields: ['system_id', 'system'] + } + ] + } + ); + + await Person.sync({ force: true }); + const inserts = [{ systemId: 1, system: 'system1', name: 'Alice' }]; + const people0 = await Person.bulkCreate(inserts); + expect(people0.length).to.equal(1); + expect(people0[0].systemId).to.equal(1); + expect(people0[0].system).to.equal('system1'); + expect(people0[0].name).to.equal('Alice'); + + const updates = [ + { systemId: 1, system: 'system1', name: 'CHANGED NAME' }, + { systemId: 1, system: 'system2', name: 'Bob' } + ]; + + const people = await Person.bulkCreate(updates, { + updateOnDuplicate: ['systemId', 'system', 'name'] }); + expect(people.length).to.equal(2); + expect(people[0].systemId).to.equal(1); + expect(people[0].system).to.equal('system1'); + expect(people[0].name).to.equal('CHANGED NAME'); + expect(people[1].systemId).to.equal(1); + expect(people[1].system).to.equal('system2'); + expect(people[1].name).to.equal('Bob'); }); }); - it('should reject for non array updateOnDuplicate option', function() { + + it('should reject for non array updateOnDuplicate option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return expect( + await expect( this.User.bulkCreate(data, { updateOnDuplicate: true }) ).to.be.rejectedWith('updateOnDuplicate option only supports non-empty array.'); }); - it('should reject for empty array updateOnDuplicate option', function() { + it('should reject for empty array updateOnDuplicate option', async function() { const data = [ { uniqueName: 'Peter', secretValue: '42' }, { uniqueName: 'Paul', secretValue: '23' } ]; - return expect( + await expect( this.User.bulkCreate(data, { updateOnDuplicate: [] }) ).to.be.rejectedWith('updateOnDuplicate option only supports non-empty array.'); }); @@ -570,33 +736,31 @@ describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.returnValues) { describe('return values', () => { - it('should make the auto incremented values available on the returned instances', function() { + it('should make the auto incremented values available on the returned instances', async function() { const User = this.sequelize.define('user', {}); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - })) - .then(users => - User.findAll({ order: ['id'] }) - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - users.forEach((user, i) => { - expect(user.get('id')).to.be.ok; - expect(user.get('id')).to.equal(actualUsers[i].get('id')) - .and.to.equal(i + 1); - }); - }); + await User + .sync({ force: true }); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: true + }); + + const actualUsers0 = await User.findAll({ order: ['id'] }); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach((user, i) => { + expect(user.get('id')).to.be.ok; + expect(user.get('id')).to.equal(actualUsers[i].get('id')) + .and.to.equal(i + 1); + }); }); - it('should make the auto incremented values available on the returned instances with custom fields', function() { + it('should make the auto incremented values available on the returned instances with custom fields', async function() { const User = this.sequelize.define('user', { maId: { type: DataTypes.INTEGER, @@ -606,49 +770,92 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - {}, - {}, - {} - ], { - returning: true - })) - .then(users => - User.findAll({ order: ['maId'] }) - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - users.forEach((user, i) => { - expect(user.get('maId')).to.be.ok; - expect(user.get('maId')).to.equal(actualUsers[i].get('maId')) - .and.to.equal(i + 1); - }); - }); + await User + .sync({ force: true }); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: true + }); + + const actualUsers0 = await User.findAll({ order: ['maId'] }); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach((user, i) => { + expect(user.get('maId')).to.be.ok; + expect(user.get('maId')).to.equal(actualUsers[i].get('maId')) + .and.to.equal(i + 1); + }); + }); + + it('should only return fields that are not defined in the model (with returning: true)', async function() { + const User = this.sequelize.define('user'); + + await User + .sync({ force: true }); + + await this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: true + }); + + const actualUsers0 = await User.findAll(); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach(user => { + expect(user.get()).not.to.have.property('not_on_model'); + }); + }); + + it('should return fields that are not defined in the model (with returning: ["*"])', async function() { + const User = this.sequelize.define('user'); + + await User + .sync({ force: true }); + + await this.sequelize.queryInterface.addColumn('users', 'not_on_model', Sequelize.STRING); + + const users0 = await User.bulkCreate([ + {}, + {}, + {} + ], { + returning: ['*'] + }); + + const actualUsers0 = await User.findAll(); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + users.forEach(user => { + expect(user.get()).to.have.property('not_on_model'); + }); }); }); } describe('enums', () => { - it('correctly restores enum values', function() { + it('correctly restores enum values', async function() { const Item = this.sequelize.define('Item', { state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] }, name: Sequelize.STRING }); - return Item.sync({ force: true }).then(() => { - return Item.bulkCreate([{ state: 'in_cart', name: 'A' }, { state: 'available', name: 'B' }]).then(() => { - return Item.findOne({ where: { state: 'available' } }).then(item => { - expect(item.name).to.equal('B'); - }); - }); - }); + await Item.sync({ force: true }); + await Item.bulkCreate([{ state: 'in_cart', name: 'A' }, { state: 'available', name: 'B' }]); + const item = await Item.findOne({ where: { state: 'available' } }); + expect(item.name).to.equal('B'); }); }); - it('should properly map field names to attribute names', function() { + it('should properly map field names to attribute names', async function() { const Maya = this.sequelize.define('Maya', { name: Sequelize.STRING, secret: { @@ -668,89 +875,84 @@ describe(Support.getTestDialectTeaser('Model'), () => { const M1 = { id: 1, name: 'Prathma Maya', secret: 'You are on list #1' }; const M2 = { id: 2, name: 'Dwitiya Maya', secret: 'You are on list #2' }; - return Maya.sync({ force: true }).then(() => Maya.create(M1)) - .then(m => { - expect(m.createdAt).to.be.ok; - expect(m.id).to.be.eql(M1.id); - expect(m.name).to.be.eql(M1.name); - expect(m.secret).to.be.eql(M1.secret); - - return Maya.bulkCreate([M2]); - }).then(([m]) => { - - // only attributes are returned, no fields are mixed - expect(m.createdAt).to.be.ok; - expect(m.created_at).to.not.exist; - expect(m.secret_given).to.not.exist; - expect(m.get('secret_given')).to.be.undefined; - expect(m.get('created_at')).to.be.undefined; - - // values look fine - expect(m.id).to.be.eql(M2.id); - expect(m.name).to.be.eql(M2.name); - expect(m.secret).to.be.eql(M2.secret); - }); + await Maya.sync({ force: true }); + const m0 = await Maya.create(M1); + expect(m0.createdAt).to.be.ok; + expect(m0.id).to.be.eql(M1.id); + expect(m0.name).to.be.eql(M1.name); + expect(m0.secret).to.be.eql(M1.secret); + + const [m] = await Maya.bulkCreate([M2]); + + // only attributes are returned, no fields are mixed + expect(m.createdAt).to.be.ok; + expect(m.created_at).to.not.exist; + expect(m.secret_given).to.not.exist; + expect(m.get('secret_given')).to.be.undefined; + expect(m.get('created_at')).to.be.undefined; + + // values look fine + expect(m.id).to.be.eql(M2.id); + expect(m.name).to.be.eql(M2.name); + expect(m.secret).to.be.eql(M2.secret); }); describe('handles auto increment values', () => { - it('should return auto increment primary key values', function() { + it('should return auto increment primary key values', async function() { const Maya = this.sequelize.define('Maya', {}); const M1 = {}; const M2 = {}; - return Maya.sync({ force: true }) - .then(() => Maya.bulkCreate([M1, M2], { returning: true })) - .then(ms => { - expect(ms[0].id).to.be.eql(1); - expect(ms[1].id).to.be.eql(2); - }); + await Maya.sync({ force: true }); + const ms = await Maya.bulkCreate([M1, M2], { returning: true }); + expect(ms[0].id).to.be.eql(1); + expect(ms[1].id).to.be.eql(2); }); - it('should return supplied values on primary keys', function() { + it('should return supplied values on primary keys', async function() { const User = this.sequelize.define('user', {}); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - { id: 1 }, - { id: 2 }, - { id: 3 } - ], { returning: true })) - .then(users => - User.findAll({ order: [['id', 'ASC']] }) - .then(actualUsers => [users, actualUsers]) - ) - .then(([users, actualUsers]) => { - expect(users.length).to.eql(actualUsers.length); - - expect(users[0].get('id')).to.equal(1).and.to.equal(actualUsers[0].get('id')); - expect(users[1].get('id')).to.equal(2).and.to.equal(actualUsers[1].get('id')); - expect(users[2].get('id')).to.equal(3).and.to.equal(actualUsers[2].get('id')); - }); + await User + .sync({ force: true }); + + const users0 = await User.bulkCreate([ + { id: 1 }, + { id: 2 }, + { id: 3 } + ], { returning: true }); + + const actualUsers0 = await User.findAll({ order: [['id', 'ASC']] }); + const [users, actualUsers] = [users0, actualUsers0]; + expect(users.length).to.eql(actualUsers.length); + + expect(users[0].get('id')).to.equal(1).and.to.equal(actualUsers[0].get('id')); + expect(users[1].get('id')).to.equal(2).and.to.equal(actualUsers[1].get('id')); + expect(users[2].get('id')).to.equal(3).and.to.equal(actualUsers[2].get('id')); }); - it('should return supplied values on primary keys when some instances already exists', function() { + it('should return supplied values on primary keys when some instances already exists', async function() { const User = this.sequelize.define('user', {}); - return User - .sync({ force: true }) - .then(() => User.bulkCreate([ - { id: 1 }, - { id: 3 } - ])) - .then(() => User.bulkCreate([ - { id: 2 }, - { id: 4 }, - { id: 5 } - ], { returning: true })) - .then(users => { - expect(users.length).to.eql(3); - - expect(users[0].get('id')).to.equal(2); - expect(users[1].get('id')).to.equal(4); - expect(users[2].get('id')).to.equal(5); - }); + await User + .sync({ force: true }); + + await User.bulkCreate([ + { id: 1 }, + { id: 3 } + ]); + + const users = await User.bulkCreate([ + { id: 2 }, + { id: 4 }, + { id: 5 } + ], { returning: true }); + + expect(users.length).to.eql(3); + + expect(users[0].get('id')).to.equal(2); + expect(users[1].get('id')).to.equal(4); + expect(users[2].get('id')).to.equal(5); }); }); @@ -768,35 +970,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should validate', function() { - return this.User - .sync({ force: true }) - .then(() => this.User.bulkCreate([ + it('should validate', async function() { + try { + await this.User + .sync({ force: true }); + + await this.User.bulkCreate([ { password: 'password' } - ], { validate: true })) - .then(() => { - expect.fail(); - }, error => { - expect(error.length).to.equal(1); - expect(error[0].message).to.match(/.*always invalid.*/); - }); + ], { validate: true }); + + expect.fail(); + } catch (error) { + expect(error.errors.length).to.equal(1); + expect(error.errors[0].message).to.match(/.*always invalid.*/); + } }); - it('should not validate', function() { - return this.User - .sync({ force: true }) - .then(() => this.User.bulkCreate([ - { password: 'password' } - ], { validate: false })) - .then(users => { - expect(users.length).to.equal(1); - }) - .then(() => this.User.bulkCreate([ - { password: 'password' } - ])) - .then(users => { - expect(users.length).to.equal(1); - }); + it('should not validate', async function() { + await this.User + .sync({ force: true }); + + const users0 = await this.User.bulkCreate([ + { password: 'password' } + ], { validate: false }); + + expect(users0.length).to.equal(1); + + const users = await this.User.bulkCreate([ + { password: 'password' } + ]); + + expect(users.length).to.equal(1); }); }); }); diff --git a/test/integration/model/bulk-create/include.test.js b/test/integration/model/bulk-create/include.test.js index 581f60f4d6c7..34d24fe12fdd 100644 --- a/test/integration/model/bulk-create/include.test.js +++ b/test/integration/model/bulk-create/include.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('bulkCreate', () => { describe('include', () => { - it('should bulkCreate data for BelongsTo relations', function() { + it('should bulkCreate data for BelongsTo relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -36,54 +36,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - title: 'Chair', - User: { - first_name: 'Mick', - last_name: 'Broadstone' - } - }, { - title: 'Table', - User: { - first_name: 'John', - last_name: 'Johnson' - } - }], { - include: [{ - model: User, - myOption: 'option' - }] - }).then(savedProducts => { - expect(savedProducts[0].isIncludeCreatedOnAfterCreate).to.be.true; - expect(savedProducts[0].User.createOptions.myOption).to.be.equal('option'); - - expect(savedProducts[1].isIncludeCreatedOnAfterCreate).to.be.true; - expect(savedProducts[1].User.createOptions.myOption).to.be.equal('option'); - - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [User] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [User] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].User).to.be.ok; - expect(persistedProducts[0].User.first_name).to.be.equal('Mick'); - expect(persistedProducts[0].User.last_name).to.be.equal('Broadstone'); - - expect(persistedProducts[1].User).to.be.ok; - expect(persistedProducts[1].User.first_name).to.be.equal('John'); - expect(persistedProducts[1].User.last_name).to.be.equal('Johnson'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + title: 'Chair', + User: { + first_name: 'Mick', + last_name: 'Broadstone' + } + }, { + title: 'Table', + User: { + first_name: 'John', + last_name: 'Johnson' + } + }], { + include: [{ + model: User, + myOption: 'option' + }] + }); + + expect(savedProducts[0].isIncludeCreatedOnAfterCreate).to.be.true; + expect(savedProducts[0].User.createOptions.myOption).to.be.equal('option'); + + expect(savedProducts[1].isIncludeCreatedOnAfterCreate).to.be.true; + expect(savedProducts[1].User.createOptions.myOption).to.be.equal('option'); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, + include: [User] + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [User] + }) + ]); + + expect(persistedProducts[0].User).to.be.ok; + expect(persistedProducts[0].User.first_name).to.be.equal('Mick'); + expect(persistedProducts[0].User.last_name).to.be.equal('Broadstone'); + + expect(persistedProducts[1].User).to.be.ok; + expect(persistedProducts[1].User.first_name).to.be.equal('John'); + expect(persistedProducts[1].User.last_name).to.be.equal('Johnson'); }); - it('should bulkCreate data for BelongsTo relations with no nullable FK', function() { + it('should bulkCreate data for BelongsTo relations with no nullable FK', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -97,36 +97,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - title: 'Chair', - User: { - first_name: 'Mick' - } - }, { - title: 'Table', - User: { - first_name: 'John' - } - }], { - include: [{ - model: User - }] - }).then(savedProducts => { - expect(savedProducts[0]).to.exist; - expect(savedProducts[0].title).to.be.equal('Chair'); - expect(savedProducts[0].User).to.exist; - expect(savedProducts[0].User.first_name).to.be.equal('Mick'); - - expect(savedProducts[1]).to.exist; - expect(savedProducts[1].title).to.be.equal('Table'); - expect(savedProducts[1].User).to.exist; - expect(savedProducts[1].User.first_name).to.be.equal('John'); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + title: 'Chair', + User: { + first_name: 'Mick' + } + }, { + title: 'Table', + User: { + first_name: 'John' + } + }], { + include: [{ + model: User + }] }); + + expect(savedProducts[0]).to.exist; + expect(savedProducts[0].title).to.be.equal('Chair'); + expect(savedProducts[0].User).to.exist; + expect(savedProducts[0].User.first_name).to.be.equal('Mick'); + + expect(savedProducts[1]).to.exist; + expect(savedProducts[1].title).to.be.equal('Table'); + expect(savedProducts[1].User).to.exist; + expect(savedProducts[1].User.first_name).to.be.equal('John'); }); - it('should bulkCreate data for BelongsTo relations with alias', function() { + it('should bulkCreate data for BelongsTo relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -137,45 +137,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Creator = Product.belongsTo(User, { as: 'creator' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - title: 'Chair', - creator: { - first_name: 'Matt', - last_name: 'Hansen' - } - }, { - title: 'Table', - creator: { - first_name: 'John', - last_name: 'Johnson' - } - }], { - include: [Creator] - }).then(savedProducts => { - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [Creator] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [Creator] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].creator).to.be.ok; - expect(persistedProducts[0].creator.first_name).to.be.equal('Matt'); - expect(persistedProducts[0].creator.last_name).to.be.equal('Hansen'); - - expect(persistedProducts[1].creator).to.be.ok; - expect(persistedProducts[1].creator.first_name).to.be.equal('John'); - expect(persistedProducts[1].creator.last_name).to.be.equal('Johnson'); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + title: 'Chair', + creator: { + first_name: 'Matt', + last_name: 'Hansen' + } + }, { + title: 'Table', + creator: { + first_name: 'John', + last_name: 'Johnson' + } + }], { + include: [Creator] }); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, + include: [Creator] + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [Creator] + }) + ]); + + expect(persistedProducts[0].creator).to.be.ok; + expect(persistedProducts[0].creator.first_name).to.be.equal('Matt'); + expect(persistedProducts[0].creator.last_name).to.be.equal('Hansen'); + + expect(persistedProducts[1].creator).to.be.ok; + expect(persistedProducts[1].creator.first_name).to.be.equal('John'); + expect(persistedProducts[1].creator.last_name).to.be.equal('Johnson'); }); - it('should bulkCreate data for HasMany relations', function() { + it('should bulkCreate data for HasMany relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -202,55 +202,56 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.hasMany(Tag); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - id: 1, - title: 'Chair', - Tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - id: 2, - title: 'Table', - Tags: [ - { id: 3, name: 'Gamma' }, - { id: 4, name: 'Delta' } - ] - }], { - include: [{ - model: Tag, - myOption: 'option' - }] - }).then(savedProducts => { - expect(savedProducts[0].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedProducts[0].Tags[0].createOptions.myOption).to.be.equal('option'); - expect(savedProducts[0].Tags[1].createOptions.myOption).to.be.equal('option'); - - expect(savedProducts[1].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedProducts[1].Tags[0].createOptions.myOption).to.be.equal('option'); - expect(savedProducts[1].Tags[1].createOptions.myOption).to.be.equal('option'); - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [Tag] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [Tag] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].Tags).to.be.ok; - expect(persistedProducts[0].Tags.length).to.equal(2); - - expect(persistedProducts[1].Tags).to.be.ok; - expect(persistedProducts[1].Tags.length).to.equal(2); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + id: 1, + title: 'Chair', + Tags: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + id: 2, + title: 'Table', + Tags: [ + { id: 3, name: 'Gamma' }, + { id: 4, name: 'Delta' } + ] + }], { + include: [{ + model: Tag, + myOption: 'option' + }] + }); + + expect(savedProducts[0].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedProducts[0].Tags[0].createOptions.myOption).to.be.equal('option'); + expect(savedProducts[0].Tags[1].createOptions.myOption).to.be.equal('option'); + + expect(savedProducts[1].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedProducts[1].Tags[0].createOptions.myOption).to.be.equal('option'); + expect(savedProducts[1].Tags[1].createOptions.myOption).to.be.equal('option'); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, + include: [Tag] + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [Tag] + }) + ]); + + expect(persistedProducts[0].Tags).to.be.ok; + expect(persistedProducts[0].Tags.length).to.equal(2); + + expect(persistedProducts[1].Tags).to.be.ok; + expect(persistedProducts[1].Tags.length).to.equal(2); }); - it('should bulkCreate data for HasMany relations with alias', function() { + it('should bulkCreate data for HasMany relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -260,45 +261,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Categories = Product.hasMany(Tag, { as: 'categories' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.bulkCreate([{ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - id: 2, - title: 'Table', - categories: [ - { id: 3, name: 'Gamma' }, - { id: 4, name: 'Delta' } - ] - }], { + await this.sequelize.sync({ force: true }); + + const savedProducts = await Product.bulkCreate([{ + id: 1, + title: 'Chair', + categories: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + id: 2, + title: 'Table', + categories: [ + { id: 3, name: 'Gamma' }, + { id: 4, name: 'Delta' } + ] + }], { + include: [Categories] + }); + + const persistedProducts = await Promise.all([ + Product.findOne({ + where: { id: savedProducts[0].id }, include: [Categories] - }).then(savedProducts => { - return Promise.all([ - Product.findOne({ - where: { id: savedProducts[0].id }, - include: [Categories] - }), - Product.findOne({ - where: { id: savedProducts[1].id }, - include: [Categories] - }) - ]).then(persistedProducts => { - expect(persistedProducts[0].categories).to.be.ok; - expect(persistedProducts[0].categories.length).to.equal(2); - - expect(persistedProducts[1].categories).to.be.ok; - expect(persistedProducts[1].categories.length).to.equal(2); - }); - }); - }); + }), + Product.findOne({ + where: { id: savedProducts[1].id }, + include: [Categories] + }) + ]); + + expect(persistedProducts[0].categories).to.be.ok; + expect(persistedProducts[0].categories.length).to.equal(2); + + expect(persistedProducts[1].categories).to.be.ok; + expect(persistedProducts[1].categories.length).to.equal(2); }); - it('should bulkCreate data for HasOne relations', function() { + it('should bulkCreate data for HasOne relations', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -309,38 +310,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'Muzzy', - Task: { - title: 'Eat Clocks' - } - }, { - username: 'Walker', - Task: { - title: 'Walk' - } - }], { - include: [Task] - }).then(savedUsers => { - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Task] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Task] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].Task).to.be.ok; - expect(persistedUsers[1].Task).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'Muzzy', + Task: { + title: 'Eat Clocks' + } + }, { + username: 'Walker', + Task: { + title: 'Walk' + } + }], { + include: [Task] }); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, + include: [Task] + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Task] + }) + ]); + + expect(persistedUsers[0].Task).to.be.ok; + expect(persistedUsers[1].Task).to.be.ok; }); - it('should bulkCreate data for HasOne relations with alias', function() { + it('should bulkCreate data for HasOne relations with alias', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -352,38 +353,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Job = User.hasOne(Task, { as: 'job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'Muzzy', - job: { - title: 'Eat Clocks' - } - }, { - username: 'Walker', - job: { - title: 'Walk' - } - }], { - include: [Job] - }).then(savedUsers => { - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Job] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Job] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].job).to.be.ok; - expect(persistedUsers[1].job).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'Muzzy', + job: { + title: 'Eat Clocks' + } + }, { + username: 'Walker', + job: { + title: 'Walk' + } + }], { + include: [Job] }); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, + include: [Job] + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Job] + }) + ]); + + expect(persistedUsers[0].job).to.be.ok; + expect(persistedUsers[1].job).to.be.ok; }); - it('should bulkCreate data for BelongsToMany relations', function() { + it('should bulkCreate data for BelongsToMany relations', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }, { @@ -415,53 +416,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.belongsToMany(Task, { through: 'user_task' }); Task.belongsToMany(User, { through: 'user_task' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'John', - Tasks: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - username: 'Jack', - Tasks: [ - { title: 'Prepare sandwich', active: true }, - { title: 'Each sandwich', active: false } - ] - }], { - include: [{ - model: Task, - myOption: 'option' - }] - }).then(savedUsers => { - expect(savedUsers[0].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedUsers[0].Tasks[0].createOptions.myOption).to.be.equal('option'); - expect(savedUsers[0].Tasks[1].createOptions.myOption).to.be.equal('option'); - - expect(savedUsers[1].areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedUsers[1].Tasks[0].createOptions.myOption).to.be.equal('option'); - expect(savedUsers[1].Tasks[1].createOptions.myOption).to.be.equal('option'); - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Task] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Task] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].Tasks).to.be.ok; - expect(persistedUsers[0].Tasks.length).to.equal(2); - - expect(persistedUsers[1].Tasks).to.be.ok; - expect(persistedUsers[1].Tasks.length).to.equal(2); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'John', + Tasks: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + username: 'Jack', + Tasks: [ + { title: 'Prepare sandwich', active: true }, + { title: 'Each sandwich', active: false } + ] + }], { + include: [{ + model: Task, + myOption: 'option' + }] + }); + + expect(savedUsers[0].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedUsers[0].Tasks[0].createOptions.myOption).to.be.equal('option'); + expect(savedUsers[0].Tasks[1].createOptions.myOption).to.be.equal('option'); + + expect(savedUsers[1].areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedUsers[1].Tasks[0].createOptions.myOption).to.be.equal('option'); + expect(savedUsers[1].Tasks[1].createOptions.myOption).to.be.equal('option'); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, + include: [Task] + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Task] + }) + ]); + + expect(persistedUsers[0].Tasks).to.be.ok; + expect(persistedUsers[0].Tasks.length).to.equal(2); + + expect(persistedUsers[1].Tasks).to.be.ok; + expect(persistedUsers[1].Tasks.length).to.equal(2); }); - it('should bulkCreate data for polymorphic BelongsToMany relations', function() { + it('should bulkCreate data for polymorphic BelongsToMany relations', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }, { @@ -520,66 +522,65 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.bulkCreate([{ - title: 'Polymorphic Associations', - tags: [ - { - name: 'polymorphic' - }, - { - name: 'associations' - } - ] - }, { - title: 'Second Polymorphic Associations', - tags: [ - { - name: 'second polymorphic' - }, - { - name: 'second associations' - } - ] - }], { - include: [{ - model: Tag, - as: 'tags', - through: { - model: ItemTag - } - }] - } - ); - }).then(savedPosts => { - // The saved post should include the two tags - expect(savedPosts[0].tags.length).to.equal(2); - expect(savedPosts[1].tags.length).to.equal(2); - // The saved post should be able to retrieve the two tags - // using the convenience accessor methods - return Promise.all([ - savedPosts[0].getTags(), - savedPosts[1].getTags() - ]); - }).then(savedTagGroups => { - // All nested tags should be returned - expect(savedTagGroups[0].length).to.equal(2); - expect(savedTagGroups[1].length).to.equal(2); - }).then(() => { - return ItemTag.findAll(); - }).then(itemTags => { - // Four "through" models should be created - expect(itemTags.length).to.equal(4); - // And their polymorphic field should be correctly set to 'post' - expect(itemTags[0].taggable).to.equal('post'); - expect(itemTags[1].taggable).to.equal('post'); - - expect(itemTags[2].taggable).to.equal('post'); - expect(itemTags[3].taggable).to.equal('post'); - }); + await this.sequelize.sync({ force: true }); + + const savedPosts = await Post.bulkCreate([{ + title: 'Polymorphic Associations', + tags: [ + { + name: 'polymorphic' + }, + { + name: 'associations' + } + ] + }, { + title: 'Second Polymorphic Associations', + tags: [ + { + name: 'second polymorphic' + }, + { + name: 'second associations' + } + ] + }], { + include: [{ + model: Tag, + as: 'tags', + through: { + model: ItemTag + } + }] + } + ); + + // The saved post should include the two tags + expect(savedPosts[0].tags.length).to.equal(2); + expect(savedPosts[1].tags.length).to.equal(2); + + // The saved post should be able to retrieve the two tags + // using the convenience accessor methods + const savedTagGroups = await Promise.all([ + savedPosts[0].getTags(), + savedPosts[1].getTags() + ]); + + // All nested tags should be returned + expect(savedTagGroups[0].length).to.equal(2); + expect(savedTagGroups[1].length).to.equal(2); + const itemTags = await ItemTag.findAll(); + // Four "through" models should be created + expect(itemTags.length).to.equal(4); + // And their polymorphic field should be correctly set to 'post' + expect(itemTags[0].taggable).to.equal('post'); + expect(itemTags[1].taggable).to.equal('post'); + + expect(itemTags[2].taggable).to.equal('post'); + expect(itemTags[3].taggable).to.equal('post'); }); - it('should bulkCreate data for BelongsToMany relations with alias', function() { + it('should bulkCreate data for BelongsToMany relations with alias', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); @@ -592,40 +593,40 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Jobs = User.belongsToMany(Task, { through: 'user_job', as: 'jobs' }); Task.belongsToMany(User, { through: 'user_job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([{ - username: 'John', - jobs: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - username: 'Jack', - jobs: [ - { title: 'Prepare sandwich', active: true }, - { title: 'Eat sandwich', active: false } - ] - }], { + await this.sequelize.sync({ force: true }); + + const savedUsers = await User.bulkCreate([{ + username: 'John', + jobs: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + username: 'Jack', + jobs: [ + { title: 'Prepare sandwich', active: true }, + { title: 'Eat sandwich', active: false } + ] + }], { + include: [Jobs] + }); + + const persistedUsers = await Promise.all([ + User.findOne({ + where: { id: savedUsers[0].id }, include: [Jobs] - }).then(savedUsers => { - return Promise.all([ - User.findOne({ - where: { id: savedUsers[0].id }, - include: [Jobs] - }), - User.findOne({ - where: { id: savedUsers[1].id }, - include: [Jobs] - }) - ]).then(persistedUsers => { - expect(persistedUsers[0].jobs).to.be.ok; - expect(persistedUsers[0].jobs.length).to.equal(2); - - expect(persistedUsers[1].jobs).to.be.ok; - expect(persistedUsers[1].jobs.length).to.equal(2); - }); - }); - }); + }), + User.findOne({ + where: { id: savedUsers[1].id }, + include: [Jobs] + }) + ]); + + expect(persistedUsers[0].jobs).to.be.ok; + expect(persistedUsers[0].jobs.length).to.equal(2); + + expect(persistedUsers[1].jobs).to.be.ok; + expect(persistedUsers[1].jobs.length).to.equal(2); }); }); }); diff --git a/test/integration/model/count.test.js b/test/integration/model/count.test.js index 2cae757b4948..f10257e956e2 100644 --- a/test/integration/model/count.test.js +++ b/test/integration/model/count.test.js @@ -7,7 +7,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('count', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -19,97 +19,118 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.User.hasMany(this.Project); this.Project.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should count rows', function() { - return this.User.bulkCreate([ + it('should count rows', async function() { + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' } - ]).then(() => { - return expect(this.User.count()).to.eventually.equal(2); - }); + ]); + + await expect(this.User.count()).to.eventually.equal(2); }); - it('should support include', function() { - return this.User.bulkCreate([ + it('should support include', async function() { + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' } - ]).then(() => this.User.findOne()) - .then(user => user.createProject({ name: 'project1' })) - .then(() => { - return expect(this.User.count({ - include: [{ - model: this.Project, - where: { name: 'project1' } - }] - })).to.eventually.equal(1); - }); + ]); + + const user = await this.User.findOne(); + await user.createProject({ name: 'project1' }); + + await expect(this.User.count({ + include: [{ + model: this.Project, + where: { name: 'project1' } + }] + })).to.eventually.equal(1); }); - it('should count groups correctly and return attributes', function() { - return this.User.bulkCreate([ + it('should count groups correctly and return attributes', async function() { + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' }, { username: 'valak', createdAt: new Date().setFullYear(2015) } - ]).then(() => this.User.count({ + ]); + + const users = await this.User.count({ attributes: ['createdAt'], group: ['createdAt'] - })).then(users => { - expect(users.length).to.be.eql(2); - expect(users[0].createdAt).to.exist; - expect(users[1].createdAt).to.exist; }); + + expect(users.length).to.be.eql(2); + expect(users[0].createdAt).to.exist; + expect(users[1].createdAt).to.exist; }); - it('should not return NaN', function() { - return this.User.bulkCreate([ + it('should not return NaN', async function() { + await this.User.bulkCreate([ { username: 'valak', age: 10 }, { username: 'conjuring', age: 20 }, { username: 'scary', age: 10 } - ]).then(() => this.User.count({ + ]); + + const result = await this.User.count({ where: { age: 10 }, group: ['age'], order: ['age'] - })).then(result => { - // TODO: `parseInt` should not be needed, see #10533 - expect(parseInt(result[0].count, 10)).to.be.eql(2); - return this.User.count({ - where: { username: 'fire' } - }); - }).then(count => { - expect(count).to.be.eql(0); - return this.User.count({ - where: { username: 'fire' }, - group: 'age' - }); - }).then(count => { - expect(count).to.be.eql([]); }); + + // TODO: `parseInt` should not be needed, see #10533 + expect(parseInt(result[0].count, 10)).to.be.eql(2); + + const count0 = await this.User.count({ + where: { username: 'fire' } + }); + + expect(count0).to.be.eql(0); + + const count = await this.User.count({ + where: { username: 'fire' }, + group: 'age' + }); + + expect(count).to.be.eql([]); }); - it('should be able to specify column for COUNT()', function() { - return this.User.bulkCreate([ + it('should be able to specify column for COUNT()', async function() { + await this.User.bulkCreate([ { username: 'ember', age: 10 }, { username: 'angular', age: 20 }, { username: 'mithril', age: 10 } - ]).then(() => this.User.count({ col: 'username' })) - .then(count => { - expect(count).to.be.eql(3); - return this.User.count({ - col: 'age', - distinct: true - }); - }) - .then(count => { - expect(count).to.be.eql(2); - }); + ]); + + const count0 = await this.User.count({ col: 'username' }); + expect(count0).to.be.eql(3); + + const count = await this.User.count({ + col: 'age', + distinct: true + }); + + expect(count).to.be.eql(2); }); - it('should be able to use where clause on included models', function() { + it('should be able to specify NO column for COUNT() with DISTINCT', async function() { + await this.User.bulkCreate([ + { username: 'ember', age: 10 }, + { username: 'angular', age: 20 }, + { username: 'mithril', age: 10 } + ]); + + const count = await this.User.count({ + distinct: true + }); + + expect(count).to.be.eql(3); + }); + + it('should be able to use where clause on included models', async function() { const countOptions = { col: 'username', include: [this.Project], @@ -117,63 +138,64 @@ describe(Support.getTestDialectTeaser('Model'), () => { '$Projects.name$': 'project1' } }; - return this.User.bulkCreate([ + + await this.User.bulkCreate([ { username: 'foo' }, { username: 'bar' } - ]).then(() => this.User.findOne()) - .then(user => user.createProject({ name: 'project1' })) - .then(() => { - return this.User.count(countOptions).then(count => { - expect(count).to.be.eql(1); - countOptions.where['$Projects.name$'] = 'project2'; - return this.User.count(countOptions); - }); - }) - .then(count => { - expect(count).to.be.eql(0); - }); + ]); + + const user = await this.User.findOne(); + await user.createProject({ name: 'project1' }); + const count0 = await this.User.count(countOptions); + expect(count0).to.be.eql(1); + countOptions.where['$Projects.name$'] = 'project2'; + const count = await this.User.count(countOptions); + expect(count).to.be.eql(0); }); - it('should be able to specify column for COUNT() with includes', function() { - return this.User.bulkCreate([ + it('should be able to specify column for COUNT() with includes', async function() { + await this.User.bulkCreate([ { username: 'ember', age: 10 }, { username: 'angular', age: 20 }, { username: 'mithril', age: 10 } - ]).then(() => this.User.count({ + ]); + + const count0 = await this.User.count({ col: 'username', distinct: true, include: [this.Project] - })).then(count => { - expect(count).to.be.eql(3); - return this.User.count({ - col: 'age', - distinct: true, - include: [this.Project] - }); - }).then(count => { - expect(count).to.be.eql(2); }); + + expect(count0).to.be.eql(3); + + const count = await this.User.count({ + col: 'age', + distinct: true, + include: [this.Project] + }); + + expect(count).to.be.eql(2); }); - it('should work correctly with include and whichever raw option', function() { + it('should work correctly with include and whichever raw option', async function() { const Post = this.sequelize.define('Post', {}); this.User.hasMany(Post); - return Post.sync({ force: true }) - .then(() => Promise.all([this.User.create({}), Post.create({})])) - .then(([user, post]) => user.addPost(post)) - .then(() => Promise.all([ - this.User.count(), - this.User.count({ raw: undefined }), - this.User.count({ raw: false }), - this.User.count({ raw: true }), - this.User.count({ include: Post }), - this.User.count({ include: Post, raw: undefined }), - this.User.count({ include: Post, raw: false }), - this.User.count({ include: Post, raw: true }) - ])) - .then(counts => { - expect(counts).to.deep.equal([1, 1, 1, 1, 1, 1, 1, 1]); - }); + await Post.sync({ force: true }); + const [user, post] = await Promise.all([this.User.create({}), Post.create({})]); + await user.addPost(post); + + const counts = await Promise.all([ + this.User.count(), + this.User.count({ raw: undefined }), + this.User.count({ raw: false }), + this.User.count({ raw: true }), + this.User.count({ include: Post }), + this.User.count({ include: Post, raw: undefined }), + this.User.count({ include: Post, raw: false }), + this.User.count({ include: Post, raw: true }) + ]); + + expect(counts).to.deep.equal([1, 1, 1, 1, 1, 1, 1, 1]); }); }); diff --git a/test/integration/model/create.test.js b/test/integration/model/create.test.js index ac3caa58289a..a5a417f69e35 100644 --- a/test/integration/model/create.test.js +++ b/test/integration/model/create.test.js @@ -3,79 +3,73 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), dialect = Support.getTestDialect(), Op = Sequelize.Op, _ = require('lodash'), + delay = require('delay'), assert = require('assert'), - current = Support.sequelize; + current = Support.sequelize, + pTimeout = require('p-timeout'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.User = this.sequelize.define('User', { - username: DataTypes.STRING, - secretValue: DataTypes.STRING, - data: DataTypes.STRING, - intVal: DataTypes.INTEGER, - theDate: DataTypes.DATE, - aBool: DataTypes.BOOLEAN, - uniqueName: { type: DataTypes.STRING, unique: true } - }); - this.Account = this.sequelize.define('Account', { - accountName: DataTypes.STRING - }); - this.Student = this.sequelize.define('Student', { - no: { type: DataTypes.INTEGER, primaryKey: true }, - name: { type: DataTypes.STRING, allowNull: false } - }); - - return this.sequelize.sync({ force: true }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.User = this.sequelize.define('User', { + username: DataTypes.STRING, + secretValue: DataTypes.STRING, + data: DataTypes.STRING, + intVal: DataTypes.INTEGER, + theDate: DataTypes.DATE, + aBool: DataTypes.BOOLEAN, + uniqueName: { type: DataTypes.STRING, unique: true } }); + this.Account = this.sequelize.define('Account', { + accountName: DataTypes.STRING + }); + this.Student = this.sequelize.define('Student', { + no: { type: DataTypes.INTEGER, primaryKey: true }, + name: { type: DataTypes.STRING, allowNull: false } + }); + + await this.sequelize.sync({ force: true }); }); describe('findOrCreate', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return this.sequelize.transaction().then(t => { - return this.User.findOrCreate({ - where: { - username: 'Username' - }, - defaults: { - data: 'some data' - }, - transaction: t - }).then(() => { - return this.User.count().then(count => { - expect(count).to.equal(0); - return t.commit().then(() => { - return this.User.count().then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); + it('supports transactions', async function() { + const t = await this.sequelize.transaction(); + + await this.User.findOrCreate({ + where: { + username: 'Username' + }, + defaults: { + data: 'some data' + }, + transaction: t }); + + const count = await this.User.count(); + expect(count).to.equal(0); + await t.commit(); + const count0 = await this.User.count(); + expect(count0).to.equal(1); }); - it('supports more than one models per transaction', function() { - return this.sequelize.transaction().then(t => { - return this.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }, transaction: t }).then(() => { - return this.Account.findOrCreate({ where: { accountName: 'accountName' }, transaction: t }).then(() => { - return t.commit(); - }); - }); - }); + it('supports more than one models per transaction', async function() { + const t = await this.sequelize.transaction(); + await this.User.findOrCreate({ where: { username: 'Username' }, defaults: { data: 'some data' }, transaction: t }); + await this.Account.findOrCreate({ where: { accountName: 'accountName' }, transaction: t }); + await t.commit(); }); } - it('should error correctly when defaults contain a unique key', function() { + it('should error correctly when defaults contain a unique key', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -87,23 +81,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'gottlieb' - }); - }).then(() => { - return expect(User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb' - } - })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); + await User.sync({ force: true }); + + await User.create({ + username: 'gottlieb' }); + + await expect(User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb' + } + })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); }); - it('should error correctly when defaults contain a unique key and a non-existent field', function() { + it('should error correctly when defaults contain a unique key and a non-existent field', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -115,25 +109,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'gottlieb' - }); - }).then(() => { - return expect(User.findOrCreate({ - where: { - objectId: 'asdasdasd' - }, - defaults: { - username: 'gottlieb', - foo: 'bar', // field that's not a defined attribute - bar: 121 - } - })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); + await User.sync({ force: true }); + + await User.create({ + username: 'gottlieb' }); + + await expect(User.findOrCreate({ + where: { + objectId: 'asdasdasd' + }, + defaults: { + username: 'gottlieb', + foo: 'bar', // field that's not a defined attribute + bar: 121 + } + })).to.eventually.be.rejectedWith(Sequelize.UniqueConstraintError); }); - it('should error correctly when defaults contain a unique key and the where clause is complex', function() { + it('should error correctly when defaults contain a unique key and the where clause is complex', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -145,9 +139,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }) - .then(() => User.create({ username: 'gottlieb' })) - .then(() => User.findOrCreate({ + await User.sync({ force: true }); + await User.create({ username: 'gottlieb' }); + + try { + await User.findOrCreate({ where: { [Op.or]: [{ objectId: 'asdasdasd1' @@ -158,13 +154,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { defaults: { username: 'gottlieb' } - }).catch(error => { - expect(error).to.be.instanceof(Sequelize.UniqueConstraintError); - expect(error.errors[0].path).to.be.a('string', 'username'); - })); + }); + } catch (error) { + expect(error).to.be.instanceof(Sequelize.UniqueConstraintError); + expect(error.errors[0].path).to.be.a('string', 'username'); + } }); - it('should work with empty uuid primary key in where', function() { + it('should work with empty uuid primary key in where', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.UUID, @@ -177,18 +174,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.findOrCreate({ - where: {}, - defaults: { - name: Math.random().toString() - } - }); + await User.sync({ force: true }); + + await User.findOrCreate({ + where: {}, + defaults: { + name: Math.random().toString() + } }); }); if (!['sqlite', 'mssql'].includes(current.dialect.name)) { - it('should not deadlock with no existing entries and no outer transaction', function() { + it('should not deadlock with no existing entries and no outer transaction', async function() { const User = this.sequelize.define('User', { email: { type: DataTypes.STRING, @@ -200,19 +197,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return Promise.map(_.range(50), i => { - return User.findOrCreate({ - where: { - email: `unique.email.${i}@sequelizejs.com`, - companyId: Math.floor(Math.random() * 5) - } - }); + await User.sync({ force: true }); + + await Promise.all(_.range(50).map(i => { + return User.findOrCreate({ + where: { + email: `unique.email.${i}@sequelizejs.com`, + companyId: Math.floor(Math.random() * 5) + } }); - }); + })); }); - it('should not deadlock with existing entries and no outer transaction', function() { + it('should not deadlock with existing entries and no outer transaction', async function() { const User = this.sequelize.define('User', { email: { type: DataTypes.STRING, @@ -224,28 +221,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return Promise.map(_.range(50), i => { - return User.findOrCreate({ - where: { - email: `unique.email.${i}@sequelizejs.com`, - companyId: 2 - } - }); - }).then(() => { - return Promise.map(_.range(50), i => { - return User.findOrCreate({ - where: { - email: `unique.email.${i}@sequelizejs.com`, - companyId: 2 - } - }); - }); + await User.sync({ force: true }); + + await Promise.all(_.range(50).map(i => { + return User.findOrCreate({ + where: { + email: `unique.email.${i}@sequelizejs.com`, + companyId: 2 + } }); - }); + })); + + await Promise.all(_.range(50).map(i => { + return User.findOrCreate({ + where: { + email: `unique.email.${i}@sequelizejs.com`, + companyId: 2 + } + }); + })); }); - it('should not deadlock with concurrency duplicate entries and no outer transaction', function() { + it('should not deadlock with concurrency duplicate entries and no outer transaction', async function() { const User = this.sequelize.define('User', { email: { type: DataTypes.STRING, @@ -257,20 +254,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return Promise.map(_.range(50), () => { - return User.findOrCreate({ - where: { - email: 'unique.email.1@sequelizejs.com', - companyId: 2 - } - }); + await User.sync({ force: true }); + + await Promise.all(_.range(50).map(() => { + return User.findOrCreate({ + where: { + email: 'unique.email.1@sequelizejs.com', + companyId: 2 + } }); - }); + })); }); } - it('should support special characters in defaults', function() { + it('should support special characters in defaults', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.INTEGER, @@ -281,19 +278,19 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.findOrCreate({ - where: { - objectId: 1 - }, - defaults: { - description: '$$ and !! and :: and ? and ^ and * and \'' - } - }); + await User.sync({ force: true }); + + await User.findOrCreate({ + where: { + objectId: 1 + }, + defaults: { + description: '$$ and !! and :: and ? and ^ and * and \'' + } }); }); - it('should support bools in defaults', function() { + it('should support bools in defaults', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.INTEGER, @@ -302,72 +299,70 @@ describe(Support.getTestDialectTeaser('Model'), () => { bool: DataTypes.BOOLEAN }); - return User.sync({ force: true }).then(() => { - return User.findOrCreate({ - where: { - objectId: 1 - }, - defaults: { - bool: false - } - }); + await User.sync({ force: true }); + + await User.findOrCreate({ + where: { + objectId: 1 + }, + defaults: { + bool: false + } }); }); - it('returns instance if already existent. Single find field.', function() { + it('returns instance if already existent. Single find field.', async function() { const data = { username: 'Username' }; - return this.User.create(data).then(user => { - return this.User.findOrCreate({ where: { - username: user.username - } }).then(([_user, created]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(created).to.be.false; - }); - }); + const user = await this.User.create(data); + + const [_user, created] = await this.User.findOrCreate({ where: { + username: user.username + } }); + + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(created).to.be.false; }); - it('Returns instance if already existent. Multiple find fields.', function() { + it('Returns instance if already existent. Multiple find fields.', async function() { const data = { username: 'Username', data: 'ThisIsData' }; - return this.User.create(data).then(user => { - return this.User.findOrCreate({ where: data }).then(([_user, created]) => { - expect(_user.id).to.equal(user.id); - expect(_user.username).to.equal('Username'); - expect(_user.data).to.equal('ThisIsData'); - expect(created).to.be.false; - }); - }); + const user = await this.User.create(data); + const [_user, created] = await this.User.findOrCreate({ where: data }); + expect(_user.id).to.equal(user.id); + expect(_user.username).to.equal('Username'); + expect(_user.data).to.equal('ThisIsData'); + expect(created).to.be.false; }); - it('does not include exception catcher in response', function() { + it('does not include exception catcher in response', async function() { const data = { username: 'Username', data: 'ThisIsData' }; - return this.User.findOrCreate({ + const [user0] = await this.User.findOrCreate({ where: data, defaults: {} - }).then(([user]) => { - expect(user.dataValues.sequelize_caught_exception).to.be.undefined; - }).then(() => { - return this.User.findOrCreate({ - where: data, - defaults: {} - }).then(([user]) => { - expect(user.dataValues.sequelize_caught_exception).to.be.undefined; - }); }); + + expect(user0.dataValues.sequelize_caught_exception).to.be.undefined; + + const [user] = await this.User.findOrCreate({ + where: data, + defaults: {} + }); + + expect(user.dataValues.sequelize_caught_exception).to.be.undefined; }); - it('creates new instance with default value.', function() { + it('creates new instance with default value.', async function() { const data = { username: 'Username' }, @@ -375,77 +370,87 @@ describe(Support.getTestDialectTeaser('Model'), () => { data: 'ThisIsData' }; - return this.User.findOrCreate({ where: data, defaults: default_values }).then(([user, created]) => { - expect(user.username).to.equal('Username'); - expect(user.data).to.equal('ThisIsData'); - expect(created).to.be.true; - }); + const [user, created] = await this.User.findOrCreate({ where: data, defaults: default_values }); + expect(user.username).to.equal('Username'); + expect(user.data).to.equal('ThisIsData'); + expect(created).to.be.true; }); - it('supports .or() (only using default values)', function() { - return this.User.findOrCreate({ + it('supports .or() (only using default values)', async function() { + const [user, created] = await this.User.findOrCreate({ where: Sequelize.or({ username: 'Fooobzz' }, { secretValue: 'Yolo' }), defaults: { username: 'Fooobzz', secretValue: 'Yolo' } - }).then(([user, created]) => { - expect(user.username).to.equal('Fooobzz'); - expect(user.secretValue).to.equal('Yolo'); - expect(created).to.be.true; }); + + expect(user.username).to.equal('Fooobzz'); + expect(user.secretValue).to.equal('Yolo'); + expect(created).to.be.true; + }); + + it('should ignore option returning', async function() { + const [user, created] = await this.User.findOrCreate({ + where: { username: 'Username' }, + defaults: { data: 'ThisIsData' }, + returning: false + }); + + expect(user.username).to.equal('Username'); + expect(user.data).to.equal('ThisIsData'); + expect(created).to.be.true; }); if (current.dialect.supports.transactions) { - it('should release transaction when meeting errors', function() { - const test = times => { + it('should release transaction when meeting errors', async function() { + const test = async times => { if (times > 10) { return true; } - return this.Student.findOrCreate({ - where: { - no: 1 - } - }) - .timeout(1000) - .catch(Promise.TimeoutError, e => { - throw new Error(e); - }) - .catch(Sequelize.ValidationError, () => { - return test(times + 1); - }); + + try { + return await pTimeout(this.Student.findOrCreate({ + where: { + no: 1 + } + }), 1000); + } catch (e) { + if (e instanceof Sequelize.ValidationError) return test(times + 1); + if (e instanceof pTimeout.TimeoutError) throw new Error(e); + throw e; + } }; - return test(0); + await test(0); }); } describe('several concurrent calls', () => { if (current.dialect.supports.transactions) { - it('works with a transaction', function() { - return this.sequelize.transaction().then(transaction => { - return Promise.join( - this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), - this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), - (first, second) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1]; - - // Depending on execution order and MAGIC either the first OR the second call should return true - expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR - - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - - expect(firstInstance.id).to.equal(secondInstance.id); - - return transaction.commit(); - } - ); - }); + it('works with a transaction', async function() { + const transaction = await this.sequelize.transaction(); + + const [first, second] = await Promise.all([ + this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }), + this.User.findOrCreate({ where: { uniqueName: 'winner' }, transaction }) + ]); + + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1]; + + // Depending on execution order and MAGIC either the first OR the second call should return true + expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR + + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; + + expect(firstInstance.id).to.equal(secondInstance.id); + + await transaction.commit(); }); } - (dialect !== 'sqlite' && dialect !== 'mssql' ? it : it.skip)('should not fail silently with concurrency higher than pool, a unique constraint and a create hook resulting in mismatched values', function() { + (dialect !== 'sqlite' && dialect !== 'mssql' ? it : it.skip)('should not fail silently with concurrency higher than pool, a unique constraint and a create hook resulting in mismatched values', async function() { const User = this.sequelize.define('user', { username: { type: DataTypes.STRING, @@ -454,7 +459,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - User.hooks.add('beforeCreate', instance => { + User.beforeCreate(instance => { instance.set('username', instance.get('username').trim()); }); @@ -470,21 +475,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { 'mick ' ]; - return User.sync({ force: true }).then(() => { - return Promise.all( - names.map(username => { - return User.findOrCreate({ where: { username } }).catch(err => { - spy(); - expect(err.message).to.equal('user#findOrCreate: value used for username was not equal for both the find and the create calls, \'mick \' vs \'mick\''); - }); - }) - ); - }).then(() => { - expect(spy).to.have.been.called; - }); + await User.sync({ force: true }); + + await Promise.all( + names.map(async username => { + try { + return await User.findOrCreate({ where: { username } }); + } catch (err) { + spy(); + expect(err.message).to.equal('user#findOrCreate: value used for username was not equal for both the find and the create calls, \'mick \' vs \'mick\''); + } + }) + ); + + expect(spy).to.have.been.called; }); - (dialect !== 'sqlite' ? it : it.skip)('should error correctly when defaults contain a unique key without a transaction', function() { + (dialect !== 'sqlite' ? it : it.skip)('should error correctly when defaults contain a unique key without a transaction', async function() { const User = this.sequelize.define('user', { objectId: { type: DataTypes.STRING, @@ -496,97 +503,119 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({ - username: 'gottlieb' - }); - }).then(() => { - return Promise.join( - User.findOrCreate({ + await User.sync({ force: true }); + + await User.create({ + username: 'gottlieb' + }); + + return Promise.all([(async () => { + try { + await User.findOrCreate({ where: { objectId: 'asdasdasd' }, defaults: { username: 'gottlieb' } - }).then(() => { - throw new Error('I should have ben rejected'); - }).catch(err => { - expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; - expect(err.fields).to.be.ok; - }), - User.findOrCreate({ + }); + + throw new Error('I should have ben rejected'); + } catch (err) { + expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; + expect(err.fields).to.be.ok; + } + })(), (async () => { + try { + await User.findOrCreate({ where: { objectId: 'asdasdasd' }, defaults: { username: 'gottlieb' } - }).then(() => { - throw new Error('I should have ben rejected'); - }).catch(err => { - expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; - expect(err.fields).to.be.ok; - }) - ); - }); + }); + + throw new Error('I should have ben rejected'); + } catch (err) { + expect(err instanceof Sequelize.UniqueConstraintError).to.be.ok; + expect(err.fields).to.be.ok; + } + })()]); }); // Creating two concurrent transactions and selecting / inserting from the same table throws sqlite off - (dialect !== 'sqlite' ? it : it.skip)('works without a transaction', function() { - return Promise.join( - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), + (dialect !== 'sqlite' ? it : it.skip)('works without a transaction', async function() { + const [first, second] = await Promise.all([ this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - (first, second) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1]; + this.User.findOrCreate({ where: { uniqueName: 'winner' } }) + ]); - // Depending on execution order and MAGIC either the first OR the second call should return true - expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR + const firstInstance = first[0], + firstCreated = first[1], + secondInstance = second[0], + secondCreated = second[1]; - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; + // Depending on execution order and MAGIC either the first OR the second call should return true + expect(firstCreated ? !secondCreated : secondCreated).to.be.ok; // XOR - expect(firstInstance.id).to.equal(secondInstance.id); - } - ); + expect(firstInstance).to.be.ok; + expect(secondInstance).to.be.ok; + + expect(firstInstance.id).to.equal(secondInstance.id); }); }); }); describe('findCreateFind', () => { - (dialect !== 'sqlite' ? it : it.skip)('should work with multiple concurrent calls', function() { - return Promise.join( - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - this.User.findOrCreate({ where: { uniqueName: 'winner' } }), - (first, second, third) => { - const firstInstance = first[0], - firstCreated = first[1], - secondInstance = second[0], - secondCreated = second[1], - thirdInstance = third[0], - thirdCreated = third[1]; + if (dialect !== 'sqlite') { + it('should work with multiple concurrent calls', async function() { + const [ + [instance1, created1], + [instance2, created2], + [instance3, created3] + ] = await Promise.all([ + this.User.findCreateFind({ where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ where: { uniqueName: 'winner' } }) + ]); + + // All instances are the same + expect(instance1.id).to.equal(1); + expect(instance2.id).to.equal(1); + expect(instance3.id).to.equal(1); + // Only one of the createdN values is true + expect(!!(created1 ^ created2 ^ created3)).to.be.true; + }); - expect([firstCreated, secondCreated, thirdCreated].filter(value => { - return value; - }).length).to.equal(1); + if (current.dialect.supports.transactions) { + it('should work with multiple concurrent calls within a transaction', async function() { + const t = await this.sequelize.transaction(); + const [ + [instance1, created1], + [instance2, created2], + [instance3, created3] + ] = await Promise.all([ + this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }), + this.User.findCreateFind({ transaction: t, where: { uniqueName: 'winner' } }) + ]); - expect(firstInstance).to.be.ok; - expect(secondInstance).to.be.ok; - expect(thirdInstance).to.be.ok; + await t.commit(); - expect(firstInstance.id).to.equal(secondInstance.id); - expect(secondInstance.id).to.equal(thirdInstance.id); - } - ); - }); + // All instances are the same + expect(instance1.id).to.equal(1); + expect(instance2.id).to.equal(1); + expect(instance3.id).to.equal(1); + // Only one of the createdN values is true + expect(!!(created1 ^ created2 ^ created3)).to.be.true; + }); + } + } }); describe('create', () => { - it('works with multiple non-integer primary keys with a default value', function() { + it('works with multiple non-integer primary keys with a default value', async function() { const User = this.sequelize.define('User', { 'id1': { primaryKey: true, @@ -604,16 +633,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user).to.be.ok; - expect(user.id1).to.be.ok; - expect(user.id2).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + expect(user).to.be.ok; + expect(user.id1).to.be.ok; + expect(user.id2).to.be.ok; }); - it('should return an error for a unique constraint error', function() { + it('should return an error for a unique constraint error', async function() { const User = this.sequelize.define('User', { 'email': { type: DataTypes.STRING, @@ -625,43 +652,39 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ email: 'hello@sequelize.com' }).then(() => { - return User.create({ email: 'hello@sequelize.com' }).then(() => { - assert(false); - }).catch(err => { - expect(err).to.be.ok; - expect(err).to.be.an.instanceof(Error); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ email: 'hello@sequelize.com' }); + + try { + await User.create({ email: 'hello@sequelize.com' }); + assert(false); + } catch (err) { + expect(err).to.be.ok; + expect(err).to.be.an.instanceof(Error); + } }); - it('works without any primary key', function() { + it('works without any primary key', async function() { const Log = this.sequelize.define('log', { level: DataTypes.STRING }); Log.removeAttribute('id'); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - Log.create({ level: 'info' }), - Log.bulkCreate([ - { level: 'error' }, - { level: 'debug' } - ]) - ); - }).then(() => { - return Log.findAll(); - }).then(logs => { - logs.forEach(log => { - expect(log.get('id')).not.to.be.ok; - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([Log.create({ level: 'info' }), Log.bulkCreate([ + { level: 'error' }, + { level: 'debug' } + ])]); + + const logs = await Log.findAll(); + logs.forEach(log => { + expect(log.get('id')).not.to.be.ok; }); }); - it('should be able to set createdAt and updatedAt if using silent: true', function() { + it('should be able to set createdAt and updatedAt if using silent: true', async function() { const User = this.sequelize.define('user', { name: DataTypes.STRING }, { @@ -671,31 +694,31 @@ describe(Support.getTestDialectTeaser('Model'), () => { const createdAt = new Date(2012, 10, 10, 10, 10, 10); const updatedAt = new Date(2011, 11, 11, 11, 11, 11); - return User.sync({ force: true }).then(() => { - return User.create({ - createdAt, - updatedAt - }, { - silent: true - }).then(user => { - expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); - expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); - - return User.findOne({ - where: { - updatedAt: { - [Op.ne]: null - } - } - }).then(user => { - expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); - expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); - }); - }); + await User.sync({ force: true }); + + const user = await User.create({ + createdAt, + updatedAt + }, { + silent: true }); + + expect(createdAt.getTime()).to.equal(user.get('createdAt').getTime()); + expect(updatedAt.getTime()).to.equal(user.get('updatedAt').getTime()); + + const user0 = await User.findOne({ + where: { + updatedAt: { + [Op.ne]: null + } + } + }); + + expect(createdAt.getTime()).to.equal(user0.get('createdAt').getTime()); + expect(updatedAt.getTime()).to.equal(user0.get('updatedAt').getTime()); }); - it('works with custom timestamps with a default value', function() { + it('works with custom timestamps with a default value', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING, date_of_birth: DataTypes.DATE, @@ -720,18 +743,32 @@ describe(Support.getTestDialectTeaser('Model'), () => { force: false }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user).to.be.ok; - expect(user.created_time).to.be.ok; - expect(user.updated_time).to.be.ok; - expect(user.created_time.getMilliseconds()).not.to.equal(0); - expect(user.updated_time.getMilliseconds()).not.to.equal(0); - }); - }); + await this.sequelize.sync({ force: true }); + + const user1 = await User.create({}); + await delay(10); + const user2 = await User.create({}); + + for (const user of [user1, user2]) { + expect(user).to.be.ok; + expect(user.created_time).to.be.ok; + expect(user.updated_time).to.be.ok; + } + + // Timestamps should have milliseconds. However, there is a small chance that + // it really is 0 for one of them, by coincidence. So we check twice with two + // users created almost at the same time. + expect([ + user1.created_time.getMilliseconds(), + user2.created_time.getMilliseconds() + ]).not.to.deep.equal([0, 0]); + expect([ + user1.updated_time.getMilliseconds(), + user2.updated_time.getMilliseconds() + ]).not.to.deep.equal([0, 0]); }); - it('works with custom timestamps and underscored', function() { + it('works with custom timestamps and underscored', async function() { const User = this.sequelize.define('User', { }, { @@ -740,49 +777,40 @@ describe(Support.getTestDialectTeaser('Model'), () => { underscored: true }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user).to.be.ok; - expect(user.createdAt).to.be.ok; - expect(user.updatedAt).to.be.ok; + await this.sequelize.sync({ force: true }); + const user = await User.create({}); + expect(user).to.be.ok; + expect(user.createdAt).to.be.ok; + expect(user.updatedAt).to.be.ok; - expect(user.created_at).not.to.be.ok; - expect(user.updated_at).not.to.be.ok; - }); - }); + expect(user.created_at).not.to.be.ok; + expect(user.updated_at).not.to.be.ok; }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return this.sequelize.transaction().then(t => { - return this.User.create({ username: 'user' }, { transaction: t }).then(() => { - return this.User.count().then(count => { - expect(count).to.equal(0); - return t.commit().then(() => { - return this.User.count().then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const t = await this.sequelize.transaction(); + await this.User.create({ username: 'user' }, { transaction: t }); + const count = await this.User.count(); + expect(count).to.equal(0); + await t.commit(); + const count0 = await this.User.count(); + expect(count0).to.equal(1); }); } if (current.dialect.supports.returnValues) { describe('return values', () => { - it('should make the autoincremented values available on the returned instances', function() { + it('should make the autoincremented values available on the returned instances', async function() { const User = this.sequelize.define('user', {}); - return User.sync({ force: true }).then(() => { - return User.create({}, { returning: true }).then(user => { - expect(user.get('id')).to.be.ok; - expect(user.get('id')).to.equal(1); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}, { returning: true }); + expect(user.get('id')).to.be.ok; + expect(user.get('id')).to.equal(1); }); - it('should make the autoincremented values available on the returned instances with custom fields', function() { + it('should make the autoincremented values available on the returned instances with custom fields', async function() { const User = this.sequelize.define('user', { maId: { type: DataTypes.INTEGER, @@ -792,36 +820,33 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.create({}, { returning: true }).then(user => { - expect(user.get('maId')).to.be.ok; - expect(user.get('maId')).to.equal(1); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}, { returning: true }); + expect(user.get('maId')).to.be.ok; + expect(user.get('maId')).to.equal(1); }); }); } - it('is possible to use casting when creating an instance', function() { + it('is possible to use casting when creating an instance', async function() { const type = dialect === 'mysql' || dialect === 'mariadb' ? 'signed' : 'integer'; let match = false; - return this.User.create({ + const user = await this.User.create({ intVal: this.sequelize.cast('1', type) }, { logging(sql) { expect(sql).to.match(new RegExp(`CAST\\(N?'1' AS ${type.toUpperCase()}\\)`)); match = true; } - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.intVal).to.equal(1); - expect(match).to.equal(true); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.intVal).to.equal(1); + expect(match).to.equal(true); }); - it('is possible to use casting multiple times mixed in with other utilities', function() { + it('is possible to use casting multiple times mixed in with other utilities', async function() { let type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer'), match = false; @@ -829,7 +854,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed'); } - return this.User.create({ + const user = await this.User.create({ intVal: type }, { logging(sql) { @@ -840,65 +865,67 @@ describe(Support.getTestDialectTeaser('Model'), () => { } match = true; } - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.intVal).to.equal(-1); - expect(match).to.equal(true); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.intVal).to.equal(-1); + expect(match).to.equal(true); }); - it('is possible to just use .literal() to bypass escaping', function() { - return this.User.create({ + it('is possible to just use .literal() to bypass escaping', async function() { + const user = await this.User.create({ intVal: this.sequelize.literal(`CAST(1-2 AS ${dialect === 'mysql' ? 'SIGNED' : 'INTEGER'})`) - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.intVal).to.equal(-1); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.intVal).to.equal(-1); }); - it('is possible to use funtions when creating an instance', function() { - return this.User.create({ + it('is possible to use funtions when creating an instance', async function() { + const user = await this.User.create({ secretValue: this.sequelize.fn('upper', 'sequelize') - }).then(user => { - return this.User.findByPk(user.id).then(user => { - expect(user.secretValue).to.equal('SEQUELIZE'); - }); }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.secretValue).to.equal('SEQUELIZE'); + }); + + it('should escape $ in sequelize functions arguments', async function() { + const user = await this.User.create({ + secretValue: this.sequelize.fn('upper', '$sequelize') + }); + + const user0 = await this.User.findByPk(user.id); + expect(user0.secretValue).to.equal('$SEQUELIZE'); }); - it('should work with a non-id named uuid primary key columns', function() { + it('should work with a non-id named uuid primary key columns', async function() { const Monkey = this.sequelize.define('Monkey', { monkeyId: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, allowNull: false } }); - return this.sequelize.sync({ force: true }).then(() => { - return Monkey.create(); - }).then(monkey => { - expect(monkey.get('monkeyId')).to.be.ok; - }); + await this.sequelize.sync({ force: true }); + const monkey = await Monkey.create(); + expect(monkey.get('monkeyId')).to.be.ok; }); - it('is possible to use functions as default values', function() { + it('is possible to use functions as default values', async function() { let userWithDefaults; if (dialect.startsWith('postgres')) { - return this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"').then(() => { - userWithDefaults = this.sequelize.define('userWithDefaults', { - uuid: { - type: 'UUID', - defaultValue: this.sequelize.fn('uuid_generate_v4') - } - }); - - return userWithDefaults.sync({ force: true }).then(() => { - return userWithDefaults.create({}).then(user => { - // uuid validation regex taken from http://stackoverflow.com/a/13653180/800016 - expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - }); - }); + await this.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); + userWithDefaults = this.sequelize.define('userWithDefaults', { + uuid: { + type: 'UUID', + defaultValue: this.sequelize.fn('uuid_generate_v4') + } }); + + await userWithDefaults.sync({ force: true }); + const user = await userWithDefaults.create({}); + // uuid validation regex taken from http://stackoverflow.com/a/13653180/800016 + expect(user.uuid).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + return; } if (dialect === 'sqlite') { // The definition here is a bit hacky. sqlite expects () around the expression for default values, so we call a function without a name @@ -910,116 +937,141 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return userWithDefaults.sync({ force: true }).then(() => { - return userWithDefaults.create({}).then(user => { - return userWithDefaults.findByPk(user.id).then(user => { - const now = new Date(); - const pad = number => number.toString().padStart(2, '0'); + await userWithDefaults.sync({ force: true }); + const user = await userWithDefaults.create({}); + const user0 = await userWithDefaults.findByPk(user.id); + const now = new Date(); + const pad = number => number.toString().padStart(2, '0'); - expect(user.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); - }); - }); - }); + expect(user0.year).to.equal(`${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())}`); + + return; } // functions as default values are not supported in mysql, see http://stackoverflow.com/a/270338/800016 - return void 0; }); if (dialect === 'postgres') { - it('does not cast arrays for postgresql insert', function() { + it('does not cast arrays for postgresql insert', async function() { const User = this.sequelize.define('UserWithArray', { myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) }, mystr: { type: Sequelize.ARRAY(Sequelize.STRING) } }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ myvals: [], mystr: [] }, { - logging(sql) { - test = true; - expect(sql).to.contain('INSERT INTO "UserWithArrays" ("id","myvals","mystr","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4)'); - } - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + + await User.create({ myvals: [], mystr: [] }, { + logging(sql) { + test = true; + expect(sql).to.contain('INSERT INTO "UserWithArrays" ("id","myvals","mystr","createdAt","updatedAt") VALUES (DEFAULT,$1,$2,$3,$4)'); + } }); + + expect(test).to.be.true; }); - it('does not cast arrays for postgres update', function() { + it('does not cast arrays for postgres update', async function() { const User = this.sequelize.define('UserWithArray', { myvals: { type: Sequelize.ARRAY(Sequelize.INTEGER) }, mystr: { type: Sequelize.ARRAY(Sequelize.STRING) } }); let test = false; - return User.sync({ force: true }).then(() => { - return User.create({ myvals: [1, 2, 3, 4], mystr: ['One', 'Two', 'Three', 'Four'] }).then(user => { - user.myvals = []; - user.mystr = []; - return user.save({ - logging(sql) { - test = true; - expect(sql).to.contain('UPDATE "UserWithArrays" SET "myvals"=$1,"mystr"=$2,"updatedAt"=$3 WHERE "id" = $4'); - } - }); - }); - }).then(() => { - expect(test).to.be.true; + await User.sync({ force: true }); + const user = await User.create({ myvals: [1, 2, 3, 4], mystr: ['One', 'Two', 'Three', 'Four'] }); + user.myvals = []; + user.mystr = []; + + await user.save({ + logging(sql) { + test = true; + expect(sql).to.contain('UPDATE "UserWithArrays" SET "myvals"=$1,"mystr"=$2,"updatedAt"=$3 WHERE "id" = $4'); + } }); + + expect(test).to.be.true; }); } - it("doesn't allow duplicated records with unique:true", function() { + it("doesn't allow duplicated records with unique:true", async function() { const User = this.sequelize.define('UserWithUniqueUsername', { username: { type: Sequelize.STRING, unique: true } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }).then(() => { - return User.create({ username: 'foo' }).catch(Sequelize.UniqueConstraintError, err => { - expect(err).to.be.ok; - }); - }); - }); + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + + try { + await User.create({ username: 'foo' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + expect(err).to.be.ok; + } }); if (dialect === 'postgres' || dialect === 'sqlite') { - it("doesn't allow case-insensitive duplicated records using CITEXT", function() { + it("doesn't allow case-insensitive duplicated records using CITEXT", async function() { const User = this.sequelize.define('UserWithUniqueCITEXT', { username: { type: Sequelize.CITEXT, unique: true } }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'foo' }); - }).then(() => { - return User.create({ username: 'fOO' }); - }).catch(Sequelize.UniqueConstraintError, err => { + try { + await User.sync({ force: true }); + await User.create({ username: 'foo' }); + await User.create({ username: 'fOO' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; + } + }); + } + + if (dialect === 'postgres') { + it('allows the creation of a TSVECTOR field', async function() { + const User = this.sequelize.define('UserWithTSVECTOR', { + name: Sequelize.TSVECTOR }); + + await User.sync({ force: true }); + await User.create({ name: 'John Doe' }); + }); + + it('TSVECTOR only allow string', async function() { + const User = this.sequelize.define('UserWithTSVECTOR', { + username: { type: Sequelize.TSVECTOR } + }); + + try { + await User.sync({ force: true }); + await User.create({ username: 42 }); + } catch (err) { + if (!(err instanceof Sequelize.ValidationError)) throw err; + expect(err).to.be.ok; + } }); } if (current.dialect.supports.index.functionBased) { - it("doesn't allow duplicated records with unique function based indexes", function() { + it("doesn't allow duplicated records with unique function based indexes", async function() { const User = this.sequelize.define('UserWithUniqueUsernameFunctionIndex', { username: Sequelize.STRING, email: { type: Sequelize.STRING, unique: true } }); - return User.sync({ force: true }).then(() => { + try { + await User.sync({ force: true }); const tableName = User.getTableName(); - return this.sequelize.query(`CREATE UNIQUE INDEX lower_case_username ON "${tableName}" ((lower(username)))`); - }).then(() => { - return User.create({ username: 'foo' }); - }).then(() => { - return User.create({ username: 'foo' }); - }).catch(Sequelize.UniqueConstraintError, err => { + await this.sequelize.query(`CREATE UNIQUE INDEX lower_case_username ON "${tableName}" ((lower(username)))`); + await User.create({ username: 'foo' }); + await User.create({ username: 'foo' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; expect(err).to.be.ok; - }); + } }); } - it('raises an error if created object breaks definition constraints', function() { + it('raises an error if created object breaks definition constraints', async function() { const UserNull = this.sequelize.define('UserWithNonNullSmth', { username: { type: Sequelize.STRING, unique: true }, smth: { type: Sequelize.STRING, allowNull: false } @@ -1027,18 +1079,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.options.omitNull = false; - return UserNull.sync({ force: true }).then(() => { - return UserNull.create({ username: 'foo2', smth: null }).catch(err => { - expect(err).to.exist; + await UserNull.sync({ force: true }); - const smth1 = err.get('smth')[0] || {}; + try { + await UserNull.create({ username: 'foo2', smth: null }); + } catch (err) { + expect(err).to.exist; - expect(smth1.path).to.equal('smth'); - expect(smth1.type || smth1.origin).to.match(/notNull Violation/); - }); - }); + const smth1 = err.get('smth')[0] || {}; + + expect(smth1.path).to.equal('smth'); + expect(smth1.type || smth1.origin).to.match(/notNull Violation/); + } }); - it('raises an error if created object breaks definition constraints', function() { + it('raises an error if created object breaks definition constraints', async function() { const UserNull = this.sequelize.define('UserWithNonNullSmth', { username: { type: Sequelize.STRING, unique: true }, smth: { type: Sequelize.STRING, allowNull: false } @@ -1046,34 +1100,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.sequelize.options.omitNull = false; - return UserNull.sync({ force: true }).then(() => { - return UserNull.create({ username: 'foo', smth: 'foo' }).then(() => { - return UserNull.create({ username: 'foo', smth: 'bar' }).catch(Sequelize.UniqueConstraintError, err => { - expect(err).to.be.ok; - }); - }); - }); + await UserNull.sync({ force: true }); + await UserNull.create({ username: 'foo', smth: 'foo' }); + + try { + await UserNull.create({ username: 'foo', smth: 'bar' }); + } catch (err) { + if (!(err instanceof Sequelize.UniqueConstraintError)) throw err; + expect(err).to.be.ok; + } }); - it('raises an error if saving an empty string into a column allowing null or URL', function() { + it('raises an error if saving an empty string into a column allowing null or URL', async function() { const StringIsNullOrUrl = this.sequelize.define('StringIsNullOrUrl', { str: { type: Sequelize.STRING, allowNull: true, validate: { isURL: true } } }); this.sequelize.options.omitNull = false; - return StringIsNullOrUrl.sync({ force: true }).then(() => { - return StringIsNullOrUrl.create({ str: null }).then(str1 => { - expect(str1.str).to.be.null; - return StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' }).then(str2 => { - expect(str2.str).to.equal('http://sequelizejs.org'); - return StringIsNullOrUrl.create({ str: '' }).catch(err => { - expect(err).to.exist; - expect(err.get('str')[0].message).to.match(/Validation isURL on str failed/); - }); - }); - }); - }); + await StringIsNullOrUrl.sync({ force: true }); + const str1 = await StringIsNullOrUrl.create({ str: null }); + expect(str1.str).to.be.null; + const str2 = await StringIsNullOrUrl.create({ str: 'http://sequelizejs.org' }); + expect(str2.str).to.equal('http://sequelizejs.org'); + + try { + await StringIsNullOrUrl.create({ str: '' }); + } catch (err) { + expect(err).to.exist; + expect(err.get('str')[0].message).to.match(/Validation isURL on str failed/); + } }); it('raises an error if you mess up the datatype', function() { @@ -1090,34 +1146,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { }).to.throw(Error, 'Unrecognized datatype for attribute "UserBadDataType.activity_date"'); }); - it('sets a 64 bit int in bigint', function() { + it('sets a 64 bit int in bigint', async function() { const User = this.sequelize.define('UserWithBigIntFields', { big: Sequelize.BIGINT }); - return User.sync({ force: true }).then(() => { - return User.create({ big: '9223372036854775807' }).then(user => { - expect(user.big).to.be.equal('9223372036854775807'); - }); - }); + await User.sync({ force: true }); + const user = await User.create({ big: '9223372036854775807' }); + expect(user.big).to.be.equal('9223372036854775807'); }); - it('sets auto increment fields', function() { + it('sets auto increment fields', async function() { const User = this.sequelize.define('UserWithAutoIncrementField', { userid: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true, allowNull: false } }); - return User.sync({ force: true }).then(() => { - return User.create({}).then(user => { - expect(user.userid).to.equal(1); - return User.create({}).then(user => { - expect(user.userid).to.equal(2); - }); - }); - }); + await User.sync({ force: true }); + const user = await User.create({}); + expect(user.userid).to.equal(1); + const user0 = await User.create({}); + expect(user0.userid).to.equal(2); }); - it('allows the usage of options as attribute', function() { + it('allows the usage of options as attribute', async function() { const User = this.sequelize.define('UserWithNameAndOptions', { name: Sequelize.STRING, options: Sequelize.TEXT @@ -1125,60 +1176,55 @@ describe(Support.getTestDialectTeaser('Model'), () => { const options = JSON.stringify({ foo: 'bar', bar: 'foo' }); - return User.sync({ force: true }).then(() => { - return User - .create({ name: 'John Doe', options }) - .then(user => { - expect(user.options).to.equal(options); - }); - }); + await User.sync({ force: true }); + + const user = await User + .create({ name: 'John Doe', options }); + + expect(user.options).to.equal(options); }); - it('allows sql logging', function() { + it('allows sql logging', async function() { const User = this.sequelize.define('UserWithUniqueNameAndNonNullSmth', { name: { type: Sequelize.STRING, unique: true }, smth: { type: Sequelize.STRING, allowNull: false } }); let test = false; - return User.sync({ force: true }).then(() => { - return User - .create({ name: 'Fluffy Bunny', smth: 'else' }, { - logging(sql) { - expect(sql).to.exist; - test = true; - expect(sql.toUpperCase()).to.contain('INSERT'); - } - }); - }).then(() => { - expect(test).to.be.true; - }); + await User.sync({ force: true }); + + await User + .create({ name: 'Fluffy Bunny', smth: 'else' }, { + logging(sql) { + expect(sql).to.exist; + test = true; + expect(sql.toUpperCase()).to.contain('INSERT'); + } + }); + + expect(test).to.be.true; }); - it('should only store the values passed in the whitelist', function() { + it('should only store the values passed in the whitelist', async function() { const data = { username: 'Peter', secretValue: '42' }; - return this.User.create(data, { fields: ['username'] }).then(user => { - return this.User.findByPk(user.id).then(_user => { - expect(_user.username).to.equal(data.username); - expect(_user.secretValue).not.to.equal(data.secretValue); - expect(_user.secretValue).to.equal(null); - }); - }); + const user = await this.User.create(data, { fields: ['username'] }); + const _user = await this.User.findByPk(user.id); + expect(_user.username).to.equal(data.username); + expect(_user.secretValue).not.to.equal(data.secretValue); + expect(_user.secretValue).to.equal(null); }); - it('should store all values if no whitelist is specified', function() { + it('should store all values if no whitelist is specified', async function() { const data = { username: 'Peter', secretValue: '42' }; - return this.User.create(data).then(user => { - return this.User.findByPk(user.id).then(_user => { - expect(_user.username).to.equal(data.username); - expect(_user.secretValue).to.equal(data.secretValue); - }); - }); + const user = await this.User.create(data); + const _user = await this.User.findByPk(user.id); + expect(_user.username).to.equal(data.username); + expect(_user.secretValue).to.equal(data.secretValue); }); - it('can omit autoincremental columns', function() { + it('can omit autoincremental columns', async function() { const data = { title: 'Iliad' }, dataTypes = [Sequelize.INTEGER, Sequelize.BIGINT], sync = [], @@ -1196,85 +1242,73 @@ describe(Support.getTestDialectTeaser('Model'), () => { sync.push(b.sync({ force: true })); }); - return Promise.all(sync).then(() => { - books.forEach((b, index) => { - promises.push(b.create(data).then(book => { - expect(book.title).to.equal(data.title); - expect(book.author).to.equal(data.author); - expect(books[index].rawAttributes.id.type instanceof dataTypes[index]).to.be.ok; - })); - }); - return Promise.all(promises); + await Promise.all(sync); + books.forEach((b, index) => { + promises.push((async () => { + const book = await b.create(data); + expect(book.title).to.equal(data.title); + expect(book.author).to.equal(data.author); + expect(books[index].rawAttributes.id.type instanceof dataTypes[index]).to.be.ok; + })()); }); + + await Promise.all(promises); }); - it('saves data with single quote', function() { + it('saves data with single quote', async function() { const quote = "single'quote"; - return this.User.create({ data: quote }).then(user => { - expect(user.data).to.equal(quote); - return this.User.findOne({ where: { id: user.id } }).then(user => { - expect(user.data).to.equal(quote); - }); - }); + const user = await this.User.create({ data: quote }); + expect(user.data).to.equal(quote); + const user0 = await this.User.findOne({ where: { id: user.id } }); + expect(user0.data).to.equal(quote); }); - it('saves data with double quote', function() { + it('saves data with double quote', async function() { const quote = 'double"quote'; - return this.User.create({ data: quote }).then(user => { - expect(user.data).to.equal(quote); - return this.User.findOne({ where: { id: user.id } }).then(user => { - expect(user.data).to.equal(quote); - }); - }); + const user = await this.User.create({ data: quote }); + expect(user.data).to.equal(quote); + const user0 = await this.User.findOne({ where: { id: user.id } }); + expect(user0.data).to.equal(quote); }); - it('saves stringified JSON data', function() { + it('saves stringified JSON data', async function() { const json = JSON.stringify({ key: 'value' }); - return this.User.create({ data: json }).then(user => { - expect(user.data).to.equal(json); - return this.User.findOne({ where: { id: user.id } }).then(user => { - expect(user.data).to.equal(json); - }); - }); + const user = await this.User.create({ data: json }); + expect(user.data).to.equal(json); + const user0 = await this.User.findOne({ where: { id: user.id } }); + expect(user0.data).to.equal(json); }); - it('stores the current date in createdAt', function() { - return this.User.create({ username: 'foo' }).then(user => { - expect(parseInt(+user.createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); - }); + it('stores the current date in createdAt', async function() { + const user = await this.User.create({ username: 'foo' }); + expect(parseInt(+user.createdAt / 5000, 10)).to.be.closeTo(parseInt(+new Date() / 5000, 10), 1.5); }); - it('allows setting custom IDs', function() { - return this.User.create({ id: 42 }).then(user => { - expect(user.id).to.equal(42); - return this.User.findByPk(42).then(user => { - expect(user).to.exist; - }); - }); + it('allows setting custom IDs', async function() { + const user = await this.User.create({ id: 42 }); + expect(user.id).to.equal(42); + const user0 = await this.User.findByPk(42); + expect(user0).to.exist; }); - it('should allow blank creates (with timestamps: false)', function() { + it('should allow blank creates (with timestamps: false)', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.create({}, { fields: [] }).then(worker => { - expect(worker).to.be.ok; - }); - }); + await Worker.sync(); + const worker = await Worker.create({}, { fields: [] }); + expect(worker).to.be.ok; }); - it('should allow truly blank creates', function() { + it('should allow truly blank creates', async function() { const Worker = this.sequelize.define('Worker', {}, { timestamps: false }); - return Worker.sync().then(() => { - return Worker.create({}, { fields: [] }).then(worker => { - expect(worker).to.be.ok; - }); - }); + await Worker.sync(); + const worker = await Worker.create({}, { fields: [] }); + expect(worker).to.be.ok; }); - it('should only set passed fields', function() { + it('should only set passed fields', async function() { const User = this.sequelize.define('User', { 'email': { type: DataTypes.STRING @@ -1284,61 +1318,55 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - name: 'Yolo Bear', - email: 'yolo@bear.com' - }, { - fields: ['name'] - }).then(user => { - expect(user.name).to.be.ok; - expect(user.email).not.to.be.ok; - return User.findByPk(user.id).then(user => { - expect(user.name).to.be.ok; - expect(user.email).not.to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const user = await User.create({ + name: 'Yolo Bear', + email: 'yolo@bear.com' + }, { + fields: ['name'] }); + + expect(user.name).to.be.ok; + expect(user.email).not.to.be.ok; + const user0 = await User.findByPk(user.id); + expect(user0.name).to.be.ok; + expect(user0.email).not.to.be.ok; }); - it('Works even when SQL query has a values of transaction keywords such as BEGIN TRANSACTION', function() { + it('Works even when SQL query has a values of transaction keywords such as BEGIN TRANSACTION', async function() { const Task = this.sequelize.define('task', { title: DataTypes.STRING }); - return Task.sync({ force: true }) - .then(() => { - return Promise.all([ - Task.create({ title: 'BEGIN TRANSACTION' }), - Task.create({ title: 'COMMIT TRANSACTION' }), - Task.create({ title: 'ROLLBACK TRANSACTION' }), - Task.create({ title: 'SAVE TRANSACTION' }) - ]); - }) - .then(newTasks => { - expect(newTasks).to.have.lengthOf(4); - expect(newTasks[0].title).to.equal('BEGIN TRANSACTION'); - expect(newTasks[1].title).to.equal('COMMIT TRANSACTION'); - expect(newTasks[2].title).to.equal('ROLLBACK TRANSACTION'); - expect(newTasks[3].title).to.equal('SAVE TRANSACTION'); - }); + await Task.sync({ force: true }); + + const newTasks = await Promise.all([ + Task.create({ title: 'BEGIN TRANSACTION' }), + Task.create({ title: 'COMMIT TRANSACTION' }), + Task.create({ title: 'ROLLBACK TRANSACTION' }), + Task.create({ title: 'SAVE TRANSACTION' }) + ]); + + expect(newTasks).to.have.lengthOf(4); + expect(newTasks[0].title).to.equal('BEGIN TRANSACTION'); + expect(newTasks[1].title).to.equal('COMMIT TRANSACTION'); + expect(newTasks[2].title).to.equal('ROLLBACK TRANSACTION'); + expect(newTasks[3].title).to.equal('SAVE TRANSACTION'); }); describe('enums', () => { - it('correctly restores enum values', function() { + it('correctly restores enum values', async function() { const Item = this.sequelize.define('Item', { state: { type: Sequelize.ENUM, values: ['available', 'in_cart', 'shipped'] } }); - return Item.sync({ force: true }).then(() => { - return Item.create({ state: 'available' }).then(_item => { - return Item.findOne({ where: { state: 'available' } }).then(item => { - expect(item.id).to.equal(_item.id); - }); - }); - }); + await Item.sync({ force: true }); + const _item = await Item.create({ state: 'available' }); + const item = await Item.findOne({ where: { state: 'available' } }); + expect(item.id).to.equal(_item.id); }); - it('allows null values', function() { + it('allows null values', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM, @@ -1347,63 +1375,61 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: null }).then(_enum => { - expect(_enum.state).to.be.null; - }); - }); + await Enum.sync({ force: true }); + const _enum = await Enum.create({ state: null }); + expect(_enum.state).to.be.null; }); describe('when defined via { field: Sequelize.ENUM }', () => { - it('allows values passed as parameters', function() { + it('allows values passed as parameters', async function() { const Enum = this.sequelize.define('Enum', { state: Sequelize.ENUM('happy', 'sad') }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); - it('allows values passed as an array', function() { + it('allows values passed as an array', async function() { const Enum = this.sequelize.define('Enum', { state: Sequelize.ENUM(['happy', 'sad']) }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); }); describe('when defined via { field: { type: Sequelize.ENUM } }', () => { - it('allows values passed as parameters', function() { + it('allows values passed as parameters', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM('happy', 'sad') } }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); - it('allows values passed as an array', function() { + it('allows values passed as an array', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM(['happy', 'sad']) } }); - return Enum.sync({ force: true }).then(() => { - return Enum.create({ state: 'happy' }); - }); + await Enum.sync({ force: true }); + + await Enum.create({ state: 'happy' }); }); }); describe('can safely sync multiple times', () => { - it('through the factory', function() { + it('through the factory', async function() { const Enum = this.sequelize.define('Enum', { state: { type: Sequelize.ENUM, @@ -1412,14 +1438,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Enum.sync({ force: true }).then(() => { - return Enum.sync().then(() => { - return Enum.sync({ force: true }); - }); - }); + await Enum.sync({ force: true }); + await Enum.sync(); + + await Enum.sync({ force: true }); }); - it('through sequelize', function() { + it('through sequelize', async function() { this.sequelize.define('Enum', { state: { type: Sequelize.ENUM, @@ -1428,34 +1453,49 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return this.sequelize.sync().then(() => { - return this.sequelize.sync({ force: true }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.sequelize.sync(); + + await this.sequelize.sync({ force: true }); }); }); }); }); - it('should return autoIncrement primary key (create)', function() { + it('should return autoIncrement primary key (create)', async function() { const Maya = this.sequelize.define('Maya', {}); const M1 = {}; - return Maya.sync({ force: true }).then(() => Maya.create(M1, { returning: true })) - .then(m => { - expect(m.id).to.be.eql(1); - }); + await Maya.sync({ force: true }); + const m = await Maya.create(M1, { returning: true }); + expect(m.id).to.be.eql(1); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.create({}, { + await this.User.create({}, { logging: spy - }).then(() => { - expect(spy.called).to.be.ok; }); + + expect(spy.called).to.be.ok; }); + + if (current.dialect.supports.returnValues) { + it('should return default value set by the database (create)', async function() { + + const User = this.sequelize.define('User', { + name: DataTypes.STRING, + code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } + }); + + await User.sync({ force: true }); + + const user = await User.create({ name: 'FooBar' }); + + expect(user.name).to.be.equal('FooBar'); + expect(user.code).to.be.equal(2020); + }); + } }); diff --git a/test/integration/model/create/include.test.js b/test/integration/model/create/include.test.js index 78984a323071..01b651523d6d 100644 --- a/test/integration/model/create/include.test.js +++ b/test/integration/model/create/include.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('create', () => { describe('include', () => { - it('should create data for BelongsTo relations', function() { + it('should create data for BelongsTo relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -32,35 +32,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.belongsTo(User); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - title: 'Chair', - User: { - first_name: 'Mick', - last_name: 'Broadstone' - } - }, { - include: [{ - model: User, - myOption: 'option' - }] - }).then(savedProduct => { - expect(savedProduct.isIncludeCreatedOnAfterCreate).to.be.true; - expect(savedProduct.User.createOptions.myOption).to.be.equal('option'); - expect(savedProduct.User.createOptions.parentRecord).to.be.equal(savedProduct); - return Product.findOne({ - where: { id: savedProduct.id }, - include: [User] - }).then(persistedProduct => { - expect(persistedProduct.User).to.be.ok; - expect(persistedProduct.User.first_name).to.be.equal('Mick'); - expect(persistedProduct.User.last_name).to.be.equal('Broadstone'); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + title: 'Chair', + User: { + first_name: 'Mick', + last_name: 'Broadstone' + } + }, { + include: [{ + model: User, + myOption: 'option' + }] }); + + expect(savedProduct.isIncludeCreatedOnAfterCreate).to.be.true; + expect(savedProduct.User.createOptions.myOption).to.be.equal('option'); + expect(savedProduct.User.createOptions.parentRecord).to.be.equal(savedProduct); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [User] + }); + + expect(persistedProduct.User).to.be.ok; + expect(persistedProduct.User.first_name).to.be.equal('Mick'); + expect(persistedProduct.User.last_name).to.be.equal('Broadstone'); }); - it('should create data for BelongsTo relations with no nullable FK', function() { + it('should create data for BelongsTo relations with no nullable FK', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -74,26 +75,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - title: 'Chair', - User: { - first_name: 'Mick' - } - }, { - include: [{ - model: User - }] - }).then(savedProduct => { - expect(savedProduct).to.exist; - expect(savedProduct.title).to.be.equal('Chair'); - expect(savedProduct.User).to.exist; - expect(savedProduct.User.first_name).to.be.equal('Mick'); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + title: 'Chair', + User: { + first_name: 'Mick' + } + }, { + include: [{ + model: User + }] }); + + expect(savedProduct).to.exist; + expect(savedProduct.title).to.be.equal('Chair'); + expect(savedProduct.User).to.exist; + expect(savedProduct.User.first_name).to.be.equal('Mick'); }); - it('should create data for BelongsTo relations with alias', function() { + it('should create data for BelongsTo relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -104,29 +105,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Creator = Product.belongsTo(User, { as: 'creator' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - title: 'Chair', - creator: { - first_name: 'Matt', - last_name: 'Hansen' - } - }, { - include: [Creator] - }).then(savedProduct => { - return Product.findOne({ - where: { id: savedProduct.id }, - include: [Creator] - }).then(persistedProduct => { - expect(persistedProduct.creator).to.be.ok; - expect(persistedProduct.creator.first_name).to.be.equal('Matt'); - expect(persistedProduct.creator.last_name).to.be.equal('Hansen'); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + title: 'Chair', + creator: { + first_name: 'Matt', + last_name: 'Hansen' + } + }, { + include: [Creator] + }); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [Creator] }); + + expect(persistedProduct.creator).to.be.ok; + expect(persistedProduct.creator.first_name).to.be.equal('Matt'); + expect(persistedProduct.creator.last_name).to.be.equal('Hansen'); }); - it('should create data for HasMany relations', function() { + it('should create data for HasMany relations', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }, { @@ -151,37 +152,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { Product.hasMany(Tag); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - id: 1, - title: 'Chair', - Tags: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - include: [{ - model: Tag, - myOption: 'option' - }] - }).then(savedProduct => { - expect(savedProduct.areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedProduct.Tags[0].createOptions.myOption).to.be.equal('option'); - expect(savedProduct.Tags[0].createOptions.parentRecord).to.be.equal(savedProduct); - expect(savedProduct.Tags[1].createOptions.myOption).to.be.equal('option'); - expect(savedProduct.Tags[1].createOptions.parentRecord).to.be.equal(savedProduct); - return Product.findOne({ - where: { id: savedProduct.id }, - include: [Tag] - }).then(persistedProduct => { - expect(persistedProduct.Tags).to.be.ok; - expect(persistedProduct.Tags.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + id: 1, + title: 'Chair', + Tags: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + include: [{ + model: Tag, + myOption: 'option' + }] }); + + expect(savedProduct.areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedProduct.Tags[0].createOptions.myOption).to.be.equal('option'); + expect(savedProduct.Tags[0].createOptions.parentRecord).to.be.equal(savedProduct); + expect(savedProduct.Tags[1].createOptions.myOption).to.be.equal('option'); + expect(savedProduct.Tags[1].createOptions.parentRecord).to.be.equal(savedProduct); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [Tag] + }); + + expect(persistedProduct.Tags).to.be.ok; + expect(persistedProduct.Tags.length).to.equal(2); }); - it('should create data for HasMany relations with alias', function() { + it('should create data for HasMany relations with alias', async function() { const Product = this.sequelize.define('Product', { title: Sequelize.STRING }); @@ -191,29 +193,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Categories = Product.hasMany(Tag, { as: 'categories' }); - return this.sequelize.sync({ force: true }).then(() => { - return Product.create({ - id: 1, - title: 'Chair', - categories: [ - { id: 1, name: 'Alpha' }, - { id: 2, name: 'Beta' } - ] - }, { - include: [Categories] - }).then(savedProduct => { - return Product.findOne({ - where: { id: savedProduct.id }, - include: [Categories] - }).then(persistedProduct => { - expect(persistedProduct.categories).to.be.ok; - expect(persistedProduct.categories.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedProduct = await Product.create({ + id: 1, + title: 'Chair', + categories: [ + { id: 1, name: 'Alpha' }, + { id: 2, name: 'Beta' } + ] + }, { + include: [Categories] }); + + const persistedProduct = await Product.findOne({ + where: { id: savedProduct.id }, + include: [Categories] + }); + + expect(persistedProduct.categories).to.be.ok; + expect(persistedProduct.categories.length).to.equal(2); }); - it('should create data for HasOne relations', function() { + it('should create data for HasOne relations', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -224,26 +226,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasOne(Task); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'Muzzy', - Task: { - title: 'Eat Clocks' - } - }, { - include: [Task] - }).then(savedUser => { - return User.findOne({ - where: { id: savedUser.id }, - include: [Task] - }).then(persistedUser => { - expect(persistedUser.Task).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'Muzzy', + Task: { + title: 'Eat Clocks' + } + }, { + include: [Task] }); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Task] + }); + + expect(persistedUser.Task).to.be.ok; }); - it('should create data for HasOne relations with alias', function() { + it('should create data for HasOne relations with alias', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING }); @@ -255,26 +257,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Job = User.hasOne(Task, { as: 'job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'Muzzy', - job: { - title: 'Eat Clocks' - } - }, { - include: [Job] - }).then(savedUser => { - return User.findOne({ - where: { id: savedUser.id }, - include: [Job] - }).then(persistedUser => { - expect(persistedUser.job).to.be.ok; - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'Muzzy', + job: { + title: 'Eat Clocks' + } + }, { + include: [Job] + }); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Job] }); + + expect(persistedUser.job).to.be.ok; }); - it('should create data for BelongsToMany relations', function() { + it('should create data for BelongsToMany relations', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }, { @@ -302,36 +304,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.belongsToMany(Task, { through: 'user_task' }); Task.belongsToMany(User, { through: 'user_task' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'John', - Tasks: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - include: [{ - model: Task, - myOption: 'option' - }] - }).then(savedUser => { - expect(savedUser.areIncludesCreatedOnAfterCreate).to.be.true; - expect(savedUser.Tasks[0].createOptions.myOption).to.be.equal('option'); - expect(savedUser.Tasks[0].createOptions.parentRecord).to.be.equal(savedUser); - expect(savedUser.Tasks[1].createOptions.myOption).to.be.equal('option'); - expect(savedUser.Tasks[1].createOptions.parentRecord).to.be.equal(savedUser); - return User.findOne({ - where: { id: savedUser.id }, - include: [Task] - }).then(persistedUser => { - expect(persistedUser.Tasks).to.be.ok; - expect(persistedUser.Tasks.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'John', + Tasks: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + include: [{ + model: Task, + myOption: 'option' + }] }); + + expect(savedUser.areIncludesCreatedOnAfterCreate).to.be.true; + expect(savedUser.Tasks[0].createOptions.myOption).to.be.equal('option'); + expect(savedUser.Tasks[0].createOptions.parentRecord).to.be.equal(savedUser); + expect(savedUser.Tasks[1].createOptions.myOption).to.be.equal('option'); + expect(savedUser.Tasks[1].createOptions.parentRecord).to.be.equal(savedUser); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Task] + }); + + expect(persistedUser.Tasks).to.be.ok; + expect(persistedUser.Tasks.length).to.equal(2); }); - it('should create data for polymorphic BelongsToMany relations', function() { + it('should create data for polymorphic BelongsToMany relations', async function() { const Post = this.sequelize.define('Post', { title: DataTypes.STRING }, { @@ -390,48 +393,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return Post.create({ - title: 'Polymorphic Associations', - tags: [ - { - name: 'polymorphic' - }, - { - name: 'associations' - } - ] - }, { - include: [{ - model: Tag, - as: 'tags', - through: { - model: ItemTag - } - }] - } - ); - }).then(savedPost => { - // The saved post should include the two tags - expect(savedPost.tags.length).to.equal(2); - // The saved post should be able to retrieve the two tags - // using the convenience accessor methods - return savedPost.getTags(); - }).then(savedTags => { - // All nested tags should be returned - expect(savedTags.length).to.equal(2); - }).then(() => { - return ItemTag.findAll(); - }).then(itemTags => { - // Two "through" models should be created - expect(itemTags.length).to.equal(2); - // And their polymorphic field should be correctly set to 'post' - expect(itemTags[0].taggable).to.equal('post'); - expect(itemTags[1].taggable).to.equal('post'); - }); + await this.sequelize.sync({ force: true }); + + const savedPost = await Post.create({ + title: 'Polymorphic Associations', + tags: [ + { + name: 'polymorphic' + }, + { + name: 'associations' + } + ] + }, { + include: [{ + model: Tag, + as: 'tags', + through: { + model: ItemTag + } + }] + } + ); + + // The saved post should include the two tags + expect(savedPost.tags.length).to.equal(2); + // The saved post should be able to retrieve the two tags + // using the convenience accessor methods + const savedTags = await savedPost.getTags(); + // All nested tags should be returned + expect(savedTags.length).to.equal(2); + const itemTags = await ItemTag.findAll(); + // Two "through" models should be created + expect(itemTags.length).to.equal(2); + // And their polymorphic field should be correctly set to 'post' + expect(itemTags[0].taggable).to.equal('post'); + expect(itemTags[1].taggable).to.equal('post'); }); - it('should create data for BelongsToMany relations with alias', function() { + it('should create data for BelongsToMany relations with alias', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); @@ -444,25 +444,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Jobs = User.belongsToMany(Task, { through: 'user_job', as: 'jobs' }); Task.belongsToMany(User, { through: 'user_job' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ - username: 'John', - jobs: [ - { title: 'Get rich', active: true }, - { title: 'Die trying', active: false } - ] - }, { - include: [Jobs] - }).then(savedUser => { - return User.findOne({ - where: { id: savedUser.id }, - include: [Jobs] - }).then(persistedUser => { - expect(persistedUser.jobs).to.be.ok; - expect(persistedUser.jobs.length).to.equal(2); - }); - }); + await this.sequelize.sync({ force: true }); + + const savedUser = await User.create({ + username: 'John', + jobs: [ + { title: 'Get rich', active: true }, + { title: 'Die trying', active: false } + ] + }, { + include: [Jobs] }); + + const persistedUser = await User.findOne({ + where: { id: savedUser.id }, + include: [Jobs] + }); + + expect(persistedUser.jobs).to.be.ok; + expect(persistedUser.jobs.length).to.equal(2); }); }); }); diff --git a/test/integration/model/findAll.test.js b/test/integration/model/findAll.test.js index 4140cb794098..380a92e3f23d 100644 --- a/test/integration/model/findAll.test.js +++ b/test/integration/model/findAll.test.js @@ -8,13 +8,13 @@ const chai = require('chai'), Op = Sequelize.Op, DataTypes = require('../../../lib/data-types'), dialect = Support.getTestDialect(), - config = require('../../config/config'), _ = require('lodash'), moment = require('moment'), - current = Support.sequelize; + current = Support.sequelize, + promiseProps = require('p-props'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -25,188 +25,211 @@ describe(Support.getTestDialectTeaser('Model'), () => { binary: DataTypes.STRING(16, true) }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('findAll', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findAll({ where: { username: 'foo' } }).then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - return User.findAll({ where: { username: 'foo' }, transaction: t }).then(users3 => { - expect(users1.length).to.equal(0); - expect(users2.length).to.equal(1); - expect(users3.length).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const users1 = await User.findAll({ where: { username: 'foo' } }); + const users2 = await User.findAll({ transaction: t }); + const users3 = await User.findAll({ where: { username: 'foo' }, transaction: t }); + expect(users1.length).to.equal(0); + expect(users2.length).to.equal(1); + expect(users3.length).to.equal(1); + await t.rollback(); }); } - it('should not crash on an empty where array', function() { - return this.User.findAll({ + it('should not crash on an empty where array', async function() { + await this.User.findAll({ where: [] }); }); + it('should throw on an attempt to fetch no attributes', async function() { + await expect(this.User.findAll({ attributes: [] })).to.be.rejectedWith( + Sequelize.QueryError, + /^Attempted a SELECT query.+without selecting any columns$/ + ); + }); + + it('should not throw if overall attributes are nonempty', async function() { + const Post = this.sequelize.define('Post', { foo: DataTypes.STRING }); + const Comment = this.sequelize.define('Comment', { bar: DataTypes.STRING }); + Post.hasMany(Comment, { as: 'comments' }); + await Post.sync({ force: true }); + await Comment.sync({ force: true }); + + // Should not throw in this case, even + // though `attributes: []` is set for the main model + await Post.findAll({ + raw: true, + attributes: [], + include: [ + { + model: Comment, + as: 'comments', + attributes: [ + [Sequelize.fn('COUNT', Sequelize.col('comments.id')), 'commentCount'] + ] + } + ] + }); + }); + describe('special where conditions/smartWhere object', () => { - beforeEach(function() { + beforeEach(async function() { this.buf = Buffer.alloc(16); this.buf.fill('\x01'); - return this.User.bulkCreate([ + + await this.User.bulkCreate([ { username: 'boo', intVal: 5, theDate: '2013-01-01 12:00' }, { username: 'boo2', intVal: 10, theDate: '2013-01-10 12:00', binary: this.buf } ]); }); - it('should be able to find rows where attribute is in a list of values', function() { - return this.User.findAll({ + it('should be able to find rows where attribute is in a list of values', async function() { + const users = await this.User.findAll({ where: { username: ['boo', 'boo2'] } - }).then(users => { - expect(users).to.have.length(2); }); + + expect(users).to.have.length(2); }); - it('should not break when trying to find rows using an array of primary keys', function() { - return this.User.findAll({ + it('should not break when trying to find rows using an array of primary keys', async function() { + await this.User.findAll({ where: { id: [1, 2, 3] } }); }); - it('should not break when using smart syntax on binary fields', function() { - return this.User.findAll({ + it('should not break when using smart syntax on binary fields', async function() { + const users = await this.User.findAll({ where: { binary: [this.buf, this.buf] } - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].binary.toString()).to.equal(this.buf.toString()); - expect(users[0].username).to.equal('boo2'); }); + + expect(users).to.have.length(1); + expect(users[0].binary.toString()).to.equal(this.buf.toString()); + expect(users[0].username).to.equal('boo2'); }); - it('should be able to find a row using like', function() { - return this.User.findAll({ + it('should be able to find a row using like', async function() { + const users = await this.User.findAll({ where: { username: { [Op.like]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row using not like', function() { - return this.User.findAll({ + it('should be able to find a row using not like', async function() { + const users = await this.User.findAll({ where: { username: { [Op.notLike]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); if (dialect === 'postgres') { - it('should be able to find a row using ilike', function() { - return this.User.findAll({ + it('should be able to find a row using ilike', async function() { + const users = await this.User.findAll({ where: { username: { [Op.iLike]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row using not ilike', function() { - return this.User.findAll({ + it('should be able to find a row using not ilike', async function() { + const users = await this.User.findAll({ where: { username: { [Op.notILike]: '%2' } } - }).then(users => { - expect(users).to.be.an.instanceof(Array); - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users).to.be.an.instanceof(Array); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); } - it('should be able to find a row between a certain date using the between shortcut', function() { - return this.User.findAll({ + it('should be able to find a row between a certain date using the between shortcut', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2013-01-02', '2013-01-11'] } } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row not between a certain integer using the not between shortcut', function() { - return this.User.findAll({ + it('should be able to find a row not between a certain integer using the not between shortcut', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.notBetween]: [8, 10] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to handle false/true values just fine...', function() { + it('should be able to handle false/true values just fine...', async function() { const User = this.User; - return User.bulkCreate([ + await User.bulkCreate([ { username: 'boo5', aBool: false }, { username: 'boo6', aBool: true } - ]).then(() => { - return User.findAll({ where: { aBool: false } }).then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('boo5'); - return User.findAll({ where: { aBool: true } }).then(_users => { - expect(_users).to.have.length(1); - expect(_users[0].username).to.equal('boo6'); - }); - }); - }); + ]); + + const users = await User.findAll({ where: { aBool: false } }); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('boo5'); + const _users = await User.findAll({ where: { aBool: true } }); + expect(_users).to.have.length(1); + expect(_users[0].username).to.equal('boo6'); }); - it('should be able to handle false/true values through associations as well...', function() { + it('should be able to handle false/true values through associations as well...', async function() { const User = this.User, Passports = this.sequelize.define('Passports', { isActive: Sequelize.BOOLEAN @@ -215,43 +238,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasMany(Passports); Passports.belongsTo(User); - return User.sync({ force: true }).then(() => { - return Passports.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]).then(() => { - return Passports.bulkCreate([ - { isActive: true }, - { isActive: false } - ]).then(() => { - return User.findByPk(1).then(user => { - return Passports.findByPk(1).then(passport => { - return user.setPassports([passport]).then(() => { - return User.findByPk(2).then(_user => { - return Passports.findByPk(2).then(_passport => { - return _user.setPassports([_passport]).then(() => { - return _user.getPassports({ where: { isActive: false } }).then(theFalsePassport => { - return user.getPassports({ where: { isActive: true } }).then(theTruePassport => { - expect(theFalsePassport).to.have.length(1); - expect(theFalsePassport[0].isActive).to.be.false; - expect(theTruePassport).to.have.length(1); - expect(theTruePassport[0].isActive).to.be.true; - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + await Passports.sync({ force: true }); + + await User.bulkCreate([ + { username: 'boo5', aBool: false }, + { username: 'boo6', aBool: true } + ]); + + await Passports.bulkCreate([ + { isActive: true }, + { isActive: false } + ]); - it('should be able to handle binary values through associations as well...', function() { + const user = await User.findByPk(1); + const passport = await Passports.findByPk(1); + await user.setPassports([passport]); + const _user = await User.findByPk(2); + const _passport = await Passports.findByPk(2); + await _user.setPassports([_passport]); + const theFalsePassport = await _user.getPassports({ where: { isActive: false } }); + const theTruePassport = await user.getPassports({ where: { isActive: true } }); + expect(theFalsePassport).to.have.length(1); + expect(theFalsePassport[0].isActive).to.be.false; + expect(theTruePassport).to.have.length(1); + expect(theTruePassport[0].isActive).to.be.true; + }); + + it('should be able to handle binary values through associations as well...', async function() { const User = this.User; const Binary = this.sequelize.define('Binary', { id: { @@ -266,440 +280,431 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.belongsTo(Binary, { foreignKey: 'binary' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'boo5', aBool: false }, - { username: 'boo6', aBool: true } - ]).then(() => { - return Binary.bulkCreate([ - { id: buf1 }, - { id: buf2 } - ]).then(() => { - return User.findByPk(1).then(user => { - return Binary.findByPk(buf1).then(binary => { - return user.setBinary(binary).then(() => { - return User.findByPk(2).then(_user => { - return Binary.findByPk(buf2).then(_binary => { - return _user.setBinary(_binary).then(() => { - return _user.getBinary().then(_binaryRetrieved => { - return user.getBinary().then(binaryRetrieved => { - expect(binaryRetrieved.id).to.have.length(16); - expect(_binaryRetrieved.id).to.have.length(16); - expect(binaryRetrieved.id.toString()).to.be.equal(buf1.toString()); - expect(_binaryRetrieved.id.toString()).to.be.equal(buf2.toString()); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); - it('should be able to find a row between a certain date', function() { - return this.User.findAll({ + await User.bulkCreate([ + { username: 'boo5', aBool: false }, + { username: 'boo6', aBool: true } + ]); + + await Binary.bulkCreate([ + { id: buf1 }, + { id: buf2 } + ]); + + const user = await User.findByPk(1); + const binary = await Binary.findByPk(buf1); + await user.setBinary(binary); + const _user = await User.findByPk(2); + const _binary = await Binary.findByPk(buf2); + await _user.setBinary(_binary); + const _binaryRetrieved = await _user.getBinary(); + const binaryRetrieved = await user.getBinary(); + expect(binaryRetrieved.id).to.have.length(16); + expect(_binaryRetrieved.id).to.have.length(16); + expect(binaryRetrieved.id.toString()).to.be.equal(buf1.toString()); + expect(_binaryRetrieved.id.toString()).to.be.equal(buf2.toString()); + }); + + it('should be able to find a row between a certain date', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2013-01-02', '2013-01-11'] } } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row between a certain date and an additional where clause', function() { - return this.User.findAll({ + it('should be able to find a row between a certain date and an additional where clause', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2013-01-02', '2013-01-11'] }, intVal: 10 } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row not between a certain integer', function() { - return this.User.findAll({ + it('should be able to find a row not between a certain integer', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.notBetween]: [8, 10] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using not between and between logic', function() { - return this.User.findAll({ + it('should be able to find a row using not between and between logic', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: ['2012-12-10', '2013-01-02'], [Op.notBetween]: ['2013-01-04', '2013-01-20'] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using not between and between logic with dates', function() { - return this.User.findAll({ + it('should be able to find a row using not between and between logic with dates', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.between]: [new Date('2012-12-10'), new Date('2013-01-02')], [Op.notBetween]: [new Date('2013-01-04'), new Date('2013-01-20')] } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using greater than or equal to logic with dates', function() { - return this.User.findAll({ + it('should be able to find a row using greater than or equal to logic with dates', async function() { + const users = await this.User.findAll({ where: { theDate: { [Op.gte]: new Date('2013-01-09') } } - }).then(users => { - expect(users[0].username).to.equal('boo2'); - expect(users[0].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo2'); + expect(users[0].intVal).to.equal(10); }); - it('should be able to find a row using greater than or equal to', function() { - return this.User.findOne({ + it('should be able to find a row using greater than or equal to', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.gte]: 6 } } - }).then(user => { - expect(user.username).to.equal('boo2'); - expect(user.intVal).to.equal(10); }); + + expect(user.username).to.equal('boo2'); + expect(user.intVal).to.equal(10); }); - it('should be able to find a row using greater than', function() { - return this.User.findOne({ + it('should be able to find a row using greater than', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.gt]: 5 } } - }).then(user => { - expect(user.username).to.equal('boo2'); - expect(user.intVal).to.equal(10); }); + + expect(user.username).to.equal('boo2'); + expect(user.intVal).to.equal(10); }); - it('should be able to find a row using lesser than or equal to', function() { - return this.User.findOne({ + it('should be able to find a row using lesser than or equal to', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.lte]: 5 } } - }).then(user => { - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); }); + + expect(user.username).to.equal('boo'); + expect(user.intVal).to.equal(5); }); - it('should be able to find a row using lesser than', function() { - return this.User.findOne({ + it('should be able to find a row using lesser than', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.lt]: 6 } } - }).then(user => { - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); }); + + expect(user.username).to.equal('boo'); + expect(user.intVal).to.equal(5); }); - it('should have no problem finding a row using lesser and greater than', function() { - return this.User.findAll({ + it('should have no problem finding a row using lesser and greater than', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.lt]: 6, [Op.gt]: 4 } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); }); - it('should be able to find a row using not equal to logic', function() { - return this.User.findOne({ + it('should be able to find a row using not equal to logic', async function() { + const user = await this.User.findOne({ where: { intVal: { [Op.ne]: 10 } } - }).then(user => { - expect(user.username).to.equal('boo'); - expect(user.intVal).to.equal(5); }); + + expect(user.username).to.equal('boo'); + expect(user.intVal).to.equal(5); }); - it('should be able to find multiple users with any of the special where logic properties', function() { - return this.User.findAll({ + it('should be able to find multiple users with any of the special where logic properties', async function() { + const users = await this.User.findAll({ where: { intVal: { [Op.lte]: 10 } } - }).then(users => { - expect(users[0].username).to.equal('boo'); - expect(users[0].intVal).to.equal(5); - expect(users[1].username).to.equal('boo2'); - expect(users[1].intVal).to.equal(10); }); + + expect(users[0].username).to.equal('boo'); + expect(users[0].intVal).to.equal(5); + expect(users[1].username).to.equal('boo2'); + expect(users[1].intVal).to.equal(10); }); if (dialect === 'postgres' || dialect === 'sqlite') { - it('should be able to find multiple users with case-insensitive on CITEXT type', function() { + it('should be able to find multiple users with case-insensitive on CITEXT type', async function() { const User = this.sequelize.define('UsersWithCaseInsensitiveName', { username: Sequelize.CITEXT }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { username: 'lowercase' }, - { username: 'UPPERCASE' }, - { username: 'MIXEDcase' } - ]); - }).then(() => { - return User.findAll({ - where: { username: ['LOWERCASE', 'uppercase', 'mixedCase'] }, - order: [['id', 'ASC']] - }); - }).then(users => { - expect(users[0].username).to.equal('lowercase'); - expect(users[1].username).to.equal('UPPERCASE'); - expect(users[2].username).to.equal('MIXEDcase'); + await User.sync({ force: true }); + + await User.bulkCreate([ + { username: 'lowercase' }, + { username: 'UPPERCASE' }, + { username: 'MIXEDcase' } + ]); + + const users = await User.findAll({ + where: { username: ['LOWERCASE', 'uppercase', 'mixedCase'] }, + order: [['id', 'ASC']] }); + + expect(users[0].username).to.equal('lowercase'); + expect(users[1].username).to.equal('UPPERCASE'); + expect(users[2].username).to.equal('MIXEDcase'); }); } }); describe('eager loading', () => { - it('should not ignore where condition with empty includes, #8771', function() { - return this.User.bulkCreate([ + it('should not ignore where condition with empty includes, #8771', async function() { + await this.User.bulkCreate([ { username: 'D.E.N.N.I.S', intVal: 6 }, { username: 'F.R.A.N.K', intVal: 5 }, { username: 'W.I.L.D C.A.R.D', intVal: 8 } - ]).then(() => this.User.findAll({ + ]); + + const users = await this.User.findAll({ where: { intVal: 8 }, include: [] - })).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.be.equal('W.I.L.D C.A.R.D'); }); + + expect(users).to.have.length(1); + expect(users[0].get('username')).to.be.equal('W.I.L.D C.A.R.D'); }); describe('belongsTo', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('TaskBelongsTo', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Task.belongsTo(this.Worker); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.task.setWorker(this.worker); - }); - }); - }); - }); + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + + await this.task.setWorker(this.worker); }); - it('throws an error about unexpected input if include contains a non-object', function() { - return this.Worker.findAll({ include: [1] }).catch(err => { + it('throws an error about unexpected input if include contains a non-object', async function() { + try { + await this.Worker.findAll({ include: [1] }); + } catch (err) { expect(err.message).to.equal('Include unexpected. Element has to be either a Model, an Association or an object.'); - }); + } }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Worker.findAll({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Worker.findAll({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('TaskBelongsTo is not associated to Worker!'); - }); + } }); - it('returns the associated worker via task.worker', function() { - return this.Task.findAll({ + it('returns the associated worker via task.worker', async function() { + const tasks = await this.Task.findAll({ where: { title: 'homework' }, include: [this.Worker] - }).then(tasks => { - expect(tasks).to.exist; - expect(tasks[0].Worker).to.exist; - expect(tasks[0].Worker.name).to.equal('worker'); }); + + expect(tasks).to.exist; + expect(tasks[0].Worker).to.exist; + expect(tasks[0].Worker.name).to.equal('worker'); }); - it('returns the associated worker via task.worker, using limit and sort', function() { - return this.Task.findAll({ + it('returns the associated worker via task.worker, using limit and sort', async function() { + const tasks = await this.Task.findAll({ where: { title: 'homework' }, include: [this.Worker], limit: 1, order: [['title', 'DESC']] - }).then(tasks => { - expect(tasks).to.exist; - expect(tasks[0].Worker).to.exist; - expect(tasks[0].Worker.name).to.equal('worker'); }); + + expect(tasks).to.exist; + expect(tasks[0].Worker).to.exist; + expect(tasks[0].Worker.name).to.equal('worker'); }); }); describe('hasOne', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('TaskHasOne', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Worker.hasOne(this.Task); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setTaskHasOne(this.task); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findAll({ include: [this.Worker] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setTaskHasOne(this.task); + }); + + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findAll({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('Worker is not associated to TaskHasOne!'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [this.Task] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].TaskHasOne).to.exist; - expect(workers[0].TaskHasOne.title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].TaskHasOne).to.exist; + expect(workers[0].TaskHasOne.title).to.equal('homework'); }); }); describe('hasOne with alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Worker.hasOne(this.Task, { as: 'ToDo' }); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setToDo(this.task); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findAll({ include: [this.Task] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setToDo(this.task); + }); + + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findAll({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You must use the \'as\' keyword to specify the alias within your include statement.'); - }); + } }); - it('throws an error if alias is not associated', function() { - return this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error if alias is not associated', async function() { + try { + await this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDo).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].ToDo).to.exist; - expect(workers[0].ToDo.title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].ToDo).to.exist; + expect(workers[0].ToDo.title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(workers => { - expect(workers[0].ToDo.title).to.equal('homework'); }); + + expect(workers[0].ToDo.title).to.equal('homework'); }); }); describe('hasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('worker', { name: Sequelize.STRING }); this.Worker.hasMany(this.Task); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setTasks([this.task]); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findAll({ include: [this.Worker] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setTasks([this.task]); + }); + + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findAll({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('worker is not associated to task!'); - }); + } }); - it('returns the associated tasks via worker.tasks', function() { - return this.Worker.findAll({ + it('returns the associated tasks via worker.tasks', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [this.Task] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].tasks).to.exist; - expect(workers[0].tasks[0].title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].tasks).to.exist; + expect(workers[0].tasks[0].title).to.equal('homework'); }); // https://github.com/sequelize/sequelize/issues/8739 - it('supports sorting on renamed sub-query attribute', function() { + it('supports sorting on renamed sub-query attribute', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -709,30 +714,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Project = this.sequelize.define('project', { title: Sequelize.STRING }); User.hasMany(Project); - return User.sync({ force: true }) - .then(() => Project.sync({ force: true })) - .then(() => { - return User.bulkCreate([ - { name: 'a' }, - { name: 'b' }, - { name: 'c' } - ]); - }) - .then(() => { - return User.findAll({ - order: ['name'], - limit: 2, // to force use of a sub-query - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('a'); - expect(users[1].name).to.equal('b'); - }); + await User.sync({ force: true }); + await Project.sync({ force: true }); + + await User.bulkCreate([ + { name: 'a' }, + { name: 'b' }, + { name: 'c' } + ]); + + const users = await User.findAll({ + order: ['name'], + limit: 2, // to force use of a sub-query + include: [Project] + }); + + expect(users).to.have.lengthOf(2); + expect(users[0].name).to.equal('a'); + expect(users[1].name).to.equal('b'); }); - it('supports sorting DESC on renamed sub-query attribute', function() { + it('supports sorting DESC on renamed sub-query attribute', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -742,30 +744,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Project = this.sequelize.define('project', { title: Sequelize.STRING }); User.hasMany(Project); - return User.sync({ force: true }) - .then(() => Project.sync({ force: true })) - .then(() => { - return User.bulkCreate([ - { name: 'a' }, - { name: 'b' }, - { name: 'c' } - ]); - }) - .then(() => { - return User.findAll({ - order: [['name', 'DESC']], - limit: 2, - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('c'); - expect(users[1].name).to.equal('b'); - }); + await User.sync({ force: true }); + await Project.sync({ force: true }); + + await User.bulkCreate([ + { name: 'a' }, + { name: 'b' }, + { name: 'c' } + ]); + + const users = await User.findAll({ + order: [['name', 'DESC']], + limit: 2, + include: [Project] + }); + + expect(users).to.have.lengthOf(2); + expect(users[0].name).to.equal('c'); + expect(users[1].name).to.equal('b'); }); - it('supports sorting on multiple renamed sub-query attributes', function() { + it('supports sorting on multiple renamed sub-query attributes', async function() { const User = this.sequelize.define('user', { name: { type: Sequelize.STRING, @@ -779,133 +778,124 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Project = this.sequelize.define('project', { title: Sequelize.STRING }); User.hasMany(Project); - return User.sync({ force: true }) - .then(() => Project.sync({ force: true })) - .then(() => { - return User.bulkCreate([ - { name: 'a', age: 1 }, - { name: 'a', age: 2 }, - { name: 'b', age: 3 } - ]); - }) - .then(() => { - return User.findAll({ - order: [['name', 'ASC'], ['age', 'DESC']], - limit: 2, - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('a'); - expect(users[0].age).to.equal(2); - expect(users[1].name).to.equal('a'); - expect(users[1].age).to.equal(1); - }) - .then(() => { - return User.findAll({ - order: [['name', 'DESC'], 'age'], - limit: 2, - include: [Project] - }); - }) - .then(users => { - expect(users).to.have.lengthOf(2); - expect(users[0].name).to.equal('b'); - expect(users[1].name).to.equal('a'); - expect(users[1].age).to.equal(1); - }); + await User.sync({ force: true }); + await Project.sync({ force: true }); + + await User.bulkCreate([ + { name: 'a', age: 1 }, + { name: 'a', age: 2 }, + { name: 'b', age: 3 } + ]); + + const users0 = await User.findAll({ + order: [['name', 'ASC'], ['age', 'DESC']], + limit: 2, + include: [Project] + }); + + expect(users0).to.have.lengthOf(2); + expect(users0[0].name).to.equal('a'); + expect(users0[0].age).to.equal(2); + expect(users0[1].name).to.equal('a'); + expect(users0[1].age).to.equal(1); + + const users = await User.findAll({ + order: [['name', 'DESC'], 'age'], + limit: 2, + include: [Project] + }); + + expect(users).to.have.lengthOf(2); + expect(users[0].name).to.equal('b'); + expect(users[1].name).to.equal('a'); + expect(users[1].age).to.equal(1); }); }); describe('hasMany with alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); this.Worker.hasMany(this.Task, { as: 'ToDos' }); - return this.Worker.sync({ force: true }).then(() => { - return this.Task.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return this.worker.setToDos([this.task]); - }); - }); - }); - }); - }); - - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findAll({ include: [this.Task] }).catch(err => { + await this.Worker.sync({ force: true }); + await this.Task.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + await this.worker.setToDos([this.task]); + }); + + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findAll({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You must use the \'as\' keyword to specify the alias within your include statement.'); - }); + } }); - it('throws an error if alias is not associated', function() { - return this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error if alias is not associated', async function() { + try { + await this.Worker.findAll({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. ' + 'You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDos).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(workers => { - expect(workers).to.exist; - expect(workers[0].ToDos).to.exist; - expect(workers[0].ToDos[0].title).to.equal('homework'); }); + + expect(workers).to.exist; + expect(workers[0].ToDos).to.exist; + expect(workers[0].ToDos[0].title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findAll({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const workers = await this.Worker.findAll({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(workers => { - expect(workers[0].ToDos[0].title).to.equal('homework'); }); + + expect(workers[0].ToDos[0].title).to.equal('homework'); }); }); describe('queryOptions', () => { - beforeEach(function() { - return this.User.create({ username: 'barfooz' }).then(user => { - this.user = user; - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'barfooz' }); + this.user = user; }); - it('should return a DAO when queryOptions are not set', function() { - return this.User.findAll({ where: { username: 'barfooz' } }).then(users => { - users.forEach(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when queryOptions are not set', async function() { + const users = await this.User.findAll({ where: { username: 'barfooz' } }); + users.forEach(user => { + expect(user).to.be.instanceOf(this.User); }); }); - it('should return a DAO when raw is false', function() { - return this.User.findAll({ where: { username: 'barfooz' }, raw: false }).then(users => { - users.forEach(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when raw is false', async function() { + const users = await this.User.findAll({ where: { username: 'barfooz' }, raw: false }); + users.forEach(user => { + expect(user).to.be.instanceOf(this.User); }); }); - it('should return raw data when raw is true', function() { - return this.User.findAll({ where: { username: 'barfooz' }, raw: true }).then(users => { - users.forEach(user => { - expect(user).to.not.be.instanceOf(this.User); - expect(users[0]).to.be.instanceOf(Object); - }); + it('should return raw data when raw is true', async function() { + const users = await this.User.findAll({ where: { username: 'barfooz' }, raw: true }); + users.forEach(user => { + expect(user).to.not.be.instanceOf(this.User); + expect(users[0]).to.be.instanceOf(Object); }); }); }); describe('include all', () => { - beforeEach(function() { + beforeEach(async function() { this.Continent = this.sequelize.define('continent', { name: Sequelize.STRING }); this.Country = this.sequelize.define('country', { name: Sequelize.STRING }); this.Industry = this.sequelize.define('industry', { name: Sequelize.STRING }); @@ -920,88 +910,84 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Country.hasMany(this.Person, { as: 'residents', foreignKey: 'CountryResidentId' }); this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.props({ - europe: this.Continent.create({ name: 'Europe' }), - england: this.Country.create({ name: 'England' }), - coal: this.Industry.create({ name: 'Coal' }), - bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }) - }).then(r => { - _.forEach(r, (item, itemName) => { - this[itemName] = item; - }); - return Sequelize.Promise.all([ - this.england.setContinent(this.europe), - this.england.addIndustry(this.coal), - this.bob.setCountry(this.england), - this.bob.setCountryResident(this.england) - ]); - }); + await this.sequelize.sync({ force: true }); + + const r = await promiseProps({ + europe: this.Continent.create({ name: 'Europe' }), + england: this.Country.create({ name: 'England' }), + coal: this.Industry.create({ name: 'Coal' }), + bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }) }); - }); - it('includes all associations', function() { - return this.Country.findAll({ include: [{ all: true }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].industries).to.exist; - expect(countries[0].people).to.exist; - expect(countries[0].residents).to.exist; + _.forEach(r, (item, itemName) => { + this[itemName] = item; }); + + await Promise.all([ + this.england.setContinent(this.europe), + this.england.addIndustry(this.coal), + this.bob.setCountry(this.england), + this.bob.setCountryResident(this.england) + ]); }); - it('includes specific type of association', function() { - return this.Country.findAll({ include: [{ all: 'BelongsTo' }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].industries).not.to.exist; - expect(countries[0].people).not.to.exist; - expect(countries[0].residents).not.to.exist; - }); + it('includes all associations', async function() { + const countries = await this.Country.findAll({ include: [{ all: true }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].continent).to.exist; + expect(countries[0].industries).to.exist; + expect(countries[0].people).to.exist; + expect(countries[0].residents).to.exist; }); - it('utilises specified attributes', function() { - return this.Country.findAll({ include: [{ all: 'HasMany', attributes: ['name'] }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].people).to.exist; - expect(countries[0].people[0]).to.exist; - expect(countries[0].people[0].name).not.to.be.undefined; - expect(countries[0].people[0].lastName).to.be.undefined; - expect(countries[0].residents).to.exist; - expect(countries[0].residents[0]).to.exist; - expect(countries[0].residents[0].name).not.to.be.undefined; - expect(countries[0].residents[0].lastName).to.be.undefined; - }); + it('includes specific type of association', async function() { + const countries = await this.Country.findAll({ include: [{ all: 'BelongsTo' }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].continent).to.exist; + expect(countries[0].industries).not.to.exist; + expect(countries[0].people).not.to.exist; + expect(countries[0].residents).not.to.exist; }); - it('is over-ruled by specified include', function() { - return this.Country.findAll({ include: [{ all: true }, { model: this.Continent, attributes: ['id'] }] }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].continent).to.exist; - expect(countries[0].continent.name).to.be.undefined; - }); + it('utilises specified attributes', async function() { + const countries = await this.Country.findAll({ include: [{ all: 'HasMany', attributes: ['name'] }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].people).to.exist; + expect(countries[0].people[0]).to.exist; + expect(countries[0].people[0].name).not.to.be.undefined; + expect(countries[0].people[0].lastName).to.be.undefined; + expect(countries[0].residents).to.exist; + expect(countries[0].residents[0]).to.exist; + expect(countries[0].residents[0].name).not.to.be.undefined; + expect(countries[0].residents[0].lastName).to.be.undefined; }); - it('includes all nested associations', function() { - return this.Continent.findAll({ include: [{ all: true, nested: true }] }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].industries).to.exist; - expect(continents[0].countries[0].people).to.exist; - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].continent).not.to.exist; - }); + it('is over-ruled by specified include', async function() { + const countries = await this.Country.findAll({ include: [{ all: true }, { model: this.Continent, attributes: ['id'] }] }); + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].continent).to.exist; + expect(countries[0].continent.name).to.be.undefined; + }); + + it('includes all nested associations', async function() { + const continents = await this.Continent.findAll({ include: [{ all: true, nested: true }] }); + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].industries).to.exist; + expect(continents[0].countries[0].people).to.exist; + expect(continents[0].countries[0].residents).to.exist; + expect(continents[0].countries[0].continent).not.to.exist; }); }); describe('properly handles attributes:[] cases', () => { - beforeEach(function() { + beforeEach(async function() { this.Animal = this.sequelize.define('Animal', { name: Sequelize.STRING, age: Sequelize.INTEGER @@ -1016,44 +1002,46 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Kingdom.belongsToMany(this.Animal, { through: this.AnimalKingdom }); - return this.sequelize.sync({ force: true }) - .then(() => Sequelize.Promise.all([ - this.Animal.create({ name: 'Dog', age: 20 }), - this.Animal.create({ name: 'Cat', age: 30 }), - this.Animal.create({ name: 'Peacock', age: 25 }), - this.Animal.create({ name: 'Fish', age: 100 }) - ])) - .then(([a1, a2, a3, a4]) => Sequelize.Promise.all([ - this.Kingdom.create({ name: 'Earth' }), - this.Kingdom.create({ name: 'Water' }), - this.Kingdom.create({ name: 'Wind' }) - ]).then(([k1, k2, k3]) => - Sequelize.Promise.all([ - k1.addAnimals([a1, a2]), - k2.addAnimals([a4]), - k3.addAnimals([a3]) - ]) - )); - }); - - it('N:M with ignoring include.attributes only', function() { - return this.Kingdom.findAll({ + await this.sequelize.sync({ force: true }); + + const [a1, a2, a3, a4] = await Promise.all([ + this.Animal.create({ name: 'Dog', age: 20 }), + this.Animal.create({ name: 'Cat', age: 30 }), + this.Animal.create({ name: 'Peacock', age: 25 }), + this.Animal.create({ name: 'Fish', age: 100 }) + ]); + + const [k1, k2, k3] = await Promise.all([ + this.Kingdom.create({ name: 'Earth' }), + this.Kingdom.create({ name: 'Water' }), + this.Kingdom.create({ name: 'Wind' }) + ]); + + await Promise.all([ + k1.addAnimals([a1, a2]), + k2.addAnimals([a4]), + k3.addAnimals([a3]) + ]); + }); + + it('N:M with ignoring include.attributes only', async function() { + const kingdoms = await this.Kingdom.findAll({ include: [{ model: this.Animal, where: { age: { [Op.gte]: 29 } }, attributes: [] }] - }).then(kingdoms => { - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - // include.attributes:[] , model doesn't exists - expect(kingdom.Animals).to.not.exist; - }); + }); + + expect(kingdoms.length).to.be.eql(2); + kingdoms.forEach(kingdom => { + // include.attributes:[] , model doesn't exists + expect(kingdom.Animals).to.not.exist; }); }); - it('N:M with ignoring through.attributes only', function() { - return this.Kingdom.findAll({ + it('N:M with ignoring through.attributes only', async function() { + const kingdoms = await this.Kingdom.findAll({ include: [{ model: this.Animal, where: { age: { [Op.gte]: 29 } }, @@ -1061,17 +1049,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { attributes: [] } }] - }).then(kingdoms => { - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - expect(kingdom.Animals).to.exist; // include model exists - expect(kingdom.Animals[0].AnimalKingdom).to.not.exist; // through doesn't exists - }); + }); + + expect(kingdoms.length).to.be.eql(2); + kingdoms.forEach(kingdom => { + expect(kingdom.Animals).to.exist; // include model exists + expect(kingdom.Animals[0].AnimalKingdom).to.not.exist; // through doesn't exists }); }); - it('N:M with ignoring include.attributes but having through.attributes', function() { - return this.Kingdom.findAll({ + it('N:M with ignoring include.attributes but having through.attributes', async function() { + const kingdoms = await this.Kingdom.findAll({ include: [{ model: this.Animal, where: { age: { [Op.gte]: 29 } }, @@ -1080,12 +1068,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { attributes: ['mutation'] } }] - }).then(kingdoms => { - expect(kingdoms.length).to.be.eql(2); - kingdoms.forEach(kingdom => { - // include.attributes: [], model doesn't exists - expect(kingdom.Animals).to.not.exist; - }); + }); + + expect(kingdoms.length).to.be.eql(2); + kingdoms.forEach(kingdom => { + // include.attributes: [], model doesn't exists + expect(kingdom.Animals).to.not.exist; }); }); }); @@ -1093,7 +1081,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('order by eager loaded tables', () => { describe('HasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Continent = this.sequelize.define('continent', { name: Sequelize.STRING }); this.Country = this.sequelize.define('country', { name: Sequelize.STRING }); this.Person = this.sequelize.define('person', { name: Sequelize.STRING, lastName: Sequelize.STRING }); @@ -1105,72 +1093,72 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Country.hasMany(this.Person, { as: 'residents', foreignKey: 'CountryResidentId' }); this.Person.belongsTo(this.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' }); - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.props({ - europe: this.Continent.create({ name: 'Europe' }), - asia: this.Continent.create({ name: 'Asia' }), - england: this.Country.create({ name: 'England' }), - france: this.Country.create({ name: 'France' }), - korea: this.Country.create({ name: 'Korea' }), - bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }), - fred: this.Person.create({ name: 'Fred', lastName: 'Able' }), - pierre: this.Person.create({ name: 'Pierre', lastName: 'Paris' }), - kim: this.Person.create({ name: 'Kim', lastName: 'Z' }) - }).then(r => { - _.forEach(r, (item, itemName) => { - this[itemName] = item; - }); - - return Sequelize.Promise.all([ - this.england.setContinent(this.europe), - this.france.setContinent(this.europe), - this.korea.setContinent(this.asia), - - this.bob.setCountry(this.england), - this.fred.setCountry(this.england), - this.pierre.setCountry(this.france), - this.kim.setCountry(this.korea), - - this.bob.setCountryResident(this.england), - this.fred.setCountryResident(this.france), - this.pierre.setCountryResident(this.korea), - this.kim.setCountryResident(this.england) - ]); - }); + await this.sequelize.sync({ force: true }); + + const r = await promiseProps({ + europe: this.Continent.create({ name: 'Europe' }), + asia: this.Continent.create({ name: 'Asia' }), + england: this.Country.create({ name: 'England' }), + france: this.Country.create({ name: 'France' }), + korea: this.Country.create({ name: 'Korea' }), + bob: this.Person.create({ name: 'Bob', lastName: 'Becket' }), + fred: this.Person.create({ name: 'Fred', lastName: 'Able' }), + pierre: this.Person.create({ name: 'Pierre', lastName: 'Paris' }), + kim: this.Person.create({ name: 'Kim', lastName: 'Z' }) }); + + _.forEach(r, (item, itemName) => { + this[itemName] = item; + }); + + await Promise.all([ + this.england.setContinent(this.europe), + this.france.setContinent(this.europe), + this.korea.setContinent(this.asia), + + this.bob.setCountry(this.england), + this.fred.setCountry(this.england), + this.pierre.setCountry(this.france), + this.kim.setCountry(this.korea), + + this.bob.setCountryResident(this.england), + this.fred.setCountryResident(this.france), + this.pierre.setCountryResident(this.korea), + this.kim.setCountryResident(this.england) + ]); }); - it('sorts simply', function() { - return Sequelize.Promise.map([['ASC', 'Asia'], ['DESC', 'Europe']], params => { - return this.Continent.findAll({ + it('sorts simply', async function() { + await Promise.all([['ASC', 'Asia'], ['DESC', 'Europe']].map(async params => { + const continents = await this.Continent.findAll({ order: [['name', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); }); - }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + })); }); - it('sorts by 1st degree association', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']], params => { - return this.Continent.findAll({ + it('sorts by 1st degree association', async function() { + await Promise.all([['ASC', 'Europe', 'England'], ['DESC', 'Asia', 'Korea']].map(async params => { + const continents = await this.Continent.findAll({ include: [this.Country], order: [[this.Country, 'name', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); }); - }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + })); }); - it('sorts simply and by 1st degree association with limit where 1st degree associated instances returned for second one and not the first', function() { - return Sequelize.Promise.map([['ASC', 'Asia', 'Europe', 'England']], params => { - return this.Continent.findAll({ + it('sorts simply and by 1st degree association with limit where 1st degree associated instances returned for second one and not the first', async function() { + await Promise.all([['ASC', 'Asia', 'Europe', 'England']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, required: false, @@ -1180,83 +1168,83 @@ describe(Support.getTestDialectTeaser('Model'), () => { }], limit: 2, order: [['name', params[0]], [this.Country, 'name', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries.length).to.equal(0); - expect(continents[1]).to.exist; - expect(continents[1].name).to.equal(params[2]); - expect(continents[1].countries).to.exist; - expect(continents[1].countries.length).to.equal(1); - expect(continents[1].countries[0]).to.exist; - expect(continents[1].countries[0].name).to.equal(params[3]); }); - }); - }); - it('sorts by 2nd degree association', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']], params => { - return this.Continent.findAll({ + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries.length).to.equal(0); + expect(continents[1]).to.exist; + expect(continents[1].name).to.equal(params[2]); + expect(continents[1].countries).to.exist; + expect(continents[1].countries.length).to.equal(1); + expect(continents[1].countries[0]).to.exist; + expect(continents[1].countries[0].name).to.equal(params[3]); + })); + }); + + it('sorts by 2nd degree association', async function() { + await Promise.all([['ASC', 'Europe', 'England', 'Fred'], ['DESC', 'Asia', 'Korea', 'Kim']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person] }], order: [[this.Country, this.Person, 'lastName', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); - expect(continents[0].countries[0].people).to.exist; - expect(continents[0].countries[0].people[0]).to.exist; - expect(continents[0].countries[0].people[0].name).to.equal(params[3]); }); - }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + expect(continents[0].countries[0].people).to.exist; + expect(continents[0].countries[0].people[0]).to.exist; + expect(continents[0].countries[0].people[0].name).to.equal(params[3]); + })); }); - it('sorts by 2nd degree association with alias', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']], params => { - return this.Continent.findAll({ + it('sorts by 2nd degree association with alias', async function() { + await Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[this.Country, { model: this.Person, as: 'residents' }, 'lastName', params[0]]] - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].residents[0]).to.exist; - expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); }); - }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + expect(continents[0].countries[0].residents).to.exist; + expect(continents[0].countries[0].residents[0]).to.exist; + expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); + })); }); - it('sorts by 2nd degree association with alias while using limit', function() { - return Sequelize.Promise.map([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']], params => { - return this.Continent.findAll({ + it('sorts by 2nd degree association with alias while using limit', async function() { + await Promise.all([['ASC', 'Europe', 'France', 'Fred'], ['DESC', 'Europe', 'England', 'Kim']].map(async params => { + const continents = await this.Continent.findAll({ include: [{ model: this.Country, include: [this.Person, { model: this.Person, as: 'residents' }] }], order: [[{ model: this.Country }, { model: this.Person, as: 'residents' }, 'lastName', params[0]]], limit: 3 - }).then(continents => { - expect(continents).to.exist; - expect(continents[0]).to.exist; - expect(continents[0].name).to.equal(params[1]); - expect(continents[0].countries).to.exist; - expect(continents[0].countries[0]).to.exist; - expect(continents[0].countries[0].name).to.equal(params[2]); - expect(continents[0].countries[0].residents).to.exist; - expect(continents[0].countries[0].residents[0]).to.exist; - expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); }); - }); + + expect(continents).to.exist; + expect(continents[0]).to.exist; + expect(continents[0].name).to.equal(params[1]); + expect(continents[0].countries).to.exist; + expect(continents[0].countries[0]).to.exist; + expect(continents[0].countries[0].name).to.equal(params[2]); + expect(continents[0].countries[0].residents).to.exist; + expect(continents[0].countries[0].residents[0]).to.exist; + expect(continents[0].countries[0].residents[0].name).to.equal(params[3]); + })); }); }); describe('ManyToMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Country = this.sequelize.define('country', { name: Sequelize.STRING }); this.Industry = this.sequelize.define('industry', { name: Sequelize.STRING }); this.IndustryCountry = this.sequelize.define('IndustryCountry', { numYears: Sequelize.INTEGER }); @@ -1264,166 +1252,153 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Country.belongsToMany(this.Industry, { through: this.IndustryCountry }); this.Industry.belongsToMany(this.Country, { through: this.IndustryCountry }); - return this.sequelize.sync({ force: true }).then(() => { - return Sequelize.Promise.props({ - england: this.Country.create({ name: 'England' }), - france: this.Country.create({ name: 'France' }), - korea: this.Country.create({ name: 'Korea' }), - energy: this.Industry.create({ name: 'Energy' }), - media: this.Industry.create({ name: 'Media' }), - tech: this.Industry.create({ name: 'Tech' }) - }).then(r => { - _.forEach(r, (item, itemName) => { - this[itemName] = item; - }); - - return Sequelize.Promise.all([ - this.england.addIndustry(this.energy, { through: { numYears: 20 } }), - this.england.addIndustry(this.media, { through: { numYears: 40 } }), - this.france.addIndustry(this.media, { through: { numYears: 80 } }), - this.korea.addIndustry(this.tech, { through: { numYears: 30 } }) - ]); - }); + await this.sequelize.sync({ force: true }); + + const r = await promiseProps({ + england: this.Country.create({ name: 'England' }), + france: this.Country.create({ name: 'France' }), + korea: this.Country.create({ name: 'Korea' }), + energy: this.Industry.create({ name: 'Energy' }), + media: this.Industry.create({ name: 'Media' }), + tech: this.Industry.create({ name: 'Tech' }) + }); + + _.forEach(r, (item, itemName) => { + this[itemName] = item; }); + + await Promise.all([ + this.england.addIndustry(this.energy, { through: { numYears: 20 } }), + this.england.addIndustry(this.media, { through: { numYears: 40 } }), + this.france.addIndustry(this.media, { through: { numYears: 80 } }), + this.korea.addIndustry(this.tech, { through: { numYears: 30 } }) + ]); }); - it('sorts by 1st degree association', function() { - return Sequelize.Promise.map([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']], params => { - return this.Country.findAll({ + it('sorts by 1st degree association', async function() { + await Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(async params => { + const countries = await this.Country.findAll({ include: [this.Industry], order: [[this.Industry, 'name', params[0]]] - }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].name).to.equal(params[1]); - expect(countries[0].industries).to.exist; - expect(countries[0].industries[0]).to.exist; - expect(countries[0].industries[0].name).to.equal(params[2]); }); - }); + + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].name).to.equal(params[1]); + expect(countries[0].industries).to.exist; + expect(countries[0].industries[0]).to.exist; + expect(countries[0].industries[0].name).to.equal(params[2]); + })); }); - it('sorts by 1st degree association while using limit', function() { - return Sequelize.Promise.map([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']], params => { - return this.Country.findAll({ + it('sorts by 1st degree association while using limit', async function() { + await Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'Korea', 'Tech']].map(async params => { + const countries = await this.Country.findAll({ include: [this.Industry], order: [ [this.Industry, 'name', params[0]] ], limit: 3 - }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].name).to.equal(params[1]); - expect(countries[0].industries).to.exist; - expect(countries[0].industries[0]).to.exist; - expect(countries[0].industries[0].name).to.equal(params[2]); }); - }); + + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].name).to.equal(params[1]); + expect(countries[0].industries).to.exist; + expect(countries[0].industries[0]).to.exist; + expect(countries[0].industries[0].name).to.equal(params[2]); + })); }); - it('sorts by through table attribute', function() { - return Sequelize.Promise.map([['ASC', 'England', 'Energy'], ['DESC', 'France', 'Media']], params => { - return this.Country.findAll({ + it('sorts by through table attribute', async function() { + await Promise.all([['ASC', 'England', 'Energy'], ['DESC', 'France', 'Media']].map(async params => { + const countries = await this.Country.findAll({ include: [this.Industry], order: [[this.Industry, this.IndustryCountry, 'numYears', params[0]]] - }).then(countries => { - expect(countries).to.exist; - expect(countries[0]).to.exist; - expect(countries[0].name).to.equal(params[1]); - expect(countries[0].industries).to.exist; - expect(countries[0].industries[0]).to.exist; - expect(countries[0].industries[0].name).to.equal(params[2]); }); - }); + + expect(countries).to.exist; + expect(countries[0]).to.exist; + expect(countries[0].name).to.equal(params[1]); + expect(countries[0].industries).to.exist; + expect(countries[0].industries[0]).to.exist; + expect(countries[0].industries[0].name).to.equal(params[2]); + })); }); }); }); describe('normal findAll', () => { - beforeEach(function() { - return this.User.create({ username: 'user', data: 'foobar', theDate: moment().toDate() }).then(user => { - return this.User.create({ username: 'user2', data: 'bar', theDate: moment().toDate() }).then(user2 => { - this.users = [user].concat(user2); - }); - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'user', data: 'foobar', theDate: moment().toDate() }); + const user2 = await this.User.create({ username: 'user2', data: 'bar', theDate: moment().toDate() }); + this.users = [user].concat(user2); }); - it('finds all entries', function() { - return this.User.findAll().then(users => { - expect(users.length).to.equal(2); - }); + it('finds all entries', async function() { + const users = await this.User.findAll(); + expect(users.length).to.equal(2); }); - it('can also handle object notation', function() { - return this.User.findAll({ where: { id: this.users[1].id } }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].id).to.equal(this.users[1].id); - }); + it('can also handle object notation', async function() { + const users = await this.User.findAll({ where: { id: this.users[1].id } }); + expect(users.length).to.equal(1); + expect(users[0].id).to.equal(this.users[1].id); }); - it('sorts the results via id in ascending order', function() { - return this.User.findAll().then(users => { - expect(users.length).to.equal(2); - expect(users[0].id).to.be.below(users[1].id); - }); + it('sorts the results via id in ascending order', async function() { + const users = await this.User.findAll(); + expect(users.length).to.equal(2); + expect(users[0].id).to.be.below(users[1].id); }); - it('sorts the results via id in descending order', function() { - return this.User.findAll({ order: [['id', 'DESC']] }).then(users => { - expect(users[0].id).to.be.above(users[1].id); - }); + it('sorts the results via id in descending order', async function() { + const users = await this.User.findAll({ order: [['id', 'DESC']] }); + expect(users[0].id).to.be.above(users[1].id); }); - it('sorts the results via a date column', function() { - return this.User.create({ username: 'user3', data: 'bar', theDate: moment().add(2, 'hours').toDate() }).then(() => { - return this.User.findAll({ order: [['theDate', 'DESC']] }).then(users => { - expect(users[0].id).to.be.above(users[2].id); - }); - }); + it('sorts the results via a date column', async function() { + await this.User.create({ username: 'user3', data: 'bar', theDate: moment().add(2, 'hours').toDate() }); + const users = await this.User.findAll({ order: [['theDate', 'DESC']] }); + expect(users[0].id).to.be.above(users[2].id); }); - it('handles offset and limit', function() { - return this.User.bulkCreate([{ username: 'bobby' }, { username: 'tables' }]).then(() => { - return this.User.findAll({ limit: 2, offset: 2 }).then(users => { - expect(users.length).to.equal(2); - expect(users[0].id).to.equal(3); - }); - }); + it('handles offset and limit', async function() { + await this.User.bulkCreate([{ username: 'bobby' }, { username: 'tables' }]); + const users = await this.User.findAll({ limit: 2, offset: 2 }); + expect(users.length).to.equal(2); + expect(users[0].id).to.equal(3); }); - it('should allow us to find IDs using capital letters', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should allow us to find IDs using capital letters', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, Login: { type: Sequelize.STRING } }); - return User.sync({ force: true }).then(() => { - return User.create({ Login: 'foo' }).then(() => { - return User.findAll({ where: { ID: 1 } }).then(user => { - expect(user).to.be.instanceof(Array); - expect(user).to.have.length(1); - }); - }); - }); + await User.sync({ force: true }); + await User.create({ Login: 'foo' }); + const user = await User.findAll({ where: { ID: 1 } }); + expect(user).to.be.instanceof(Array); + expect(user).to.have.length(1); }); - it('should be possible to order by sequelize.col()', function() { + it('should be possible to order by sequelize.col()', async function() { const Company = this.sequelize.define('Company', { name: Sequelize.STRING }); - return Company.sync().then(() => { - return Company.findAll({ - order: [this.sequelize.col('name')] - }); + await Company.sync(); + + await Company.findAll({ + order: [this.sequelize.col('name')] }); }); - it('should pull in dependent fields for a VIRTUAL', function() { + it('should pull in dependent fields for a VIRTUAL', async function() { const User = this.sequelize.define('User', { active: { - type: new Sequelize.VIRTUAL(Sequelize.BOOLEAN, ['createdAt']), + type: Sequelize.VIRTUAL(Sequelize.BOOLEAN, ['createdAt']), get() { return this.get('createdAt') > Date.now() - 7 * 24 * 60 * 60 * 1000; } @@ -1432,105 +1407,143 @@ describe(Support.getTestDialectTeaser('Model'), () => { timestamps: true }); - return User.create().then(() => { - return User.findAll({ - attributes: ['active'] - }).then(users => { - users.forEach(user => { - expect(user.get('createdAt')).to.be.ok; - expect(user.get('active')).to.equal(true); - }); - }); + await User.create(); + + const users = await User.findAll({ + attributes: ['active'] + }); + + users.forEach(user => { + expect(user.get('createdAt')).to.be.ok; + expect(user.get('active')).to.equal(true); }); }); - it('should throw for undefined where parameters', function() { - return this.User.findAll({ where: { username: undefined } }).then(() => { + it('should pull in dependent fields for a VIRTUAL in include', async function() { + const User = this.sequelize.define('User', { + name: Sequelize.STRING + }); + + const Image = this.sequelize.define('Image', { + path: { + type: Sequelize.STRING, + allowNull: false + }, + url: { + type: Sequelize.VIRTUAL(Sequelize.STRING, ['path']), + get() { + return `https://my-cool-domain.com/${this.get('path')}`; + } + } + }); + + User.hasOne(Image); + Image.belongsTo(User); + + await this.sequelize.sync({ force: true }); + + await User.create({ + name: 'some user', + Image: { + path: 'folder1/folder2/logo.png' + } + }, { + include: { + model: Image + } + }); + + const users = await User.findAll({ + attributes: ['name'], + include: [{ + model: Image, + attributes: ['url'] + }] + }); + + users.forEach(user => { + expect(user.get('name')).to.equal('some user'); + expect(user.Image.get('url')).to.equal('https://my-cool-domain.com/folder1/folder2/logo.png'); + expect(user.Image.get('path')).to.equal('folder1/folder2/logo.png'); + }); + }); + + it('should throw for undefined where parameters', async function() { + try { + await this.User.findAll({ where: { username: undefined } }); throw new Error('findAll should throw an error if where has a key with undefined value'); - }, err => { + } catch (err) { expect(err).to.be.an.instanceof(Error); expect(err.message).to.equal('WHERE parameter "username" has invalid "undefined" value'); - }); + } }); }); }); describe('findAndCountAll', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'user', data: 'foobar' }, { username: 'user2', data: 'bar' }, { username: 'bobby', data: 'foo' } - ]).then(() => { - return this.User.findAll().then(users => { - this.users = users; - }); - }); + ]); + + const users = await this.User.findAll(); + this.users = users; }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findAndCountAll().then(info1 => { - return User.findAndCountAll({ transaction: t }).then(info2 => { - expect(info1.count).to.equal(0); - expect(info2.count).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const info1 = await User.findAndCountAll(); + const info2 = await User.findAndCountAll({ transaction: t }); + expect(info1.count).to.equal(0); + expect(info2.count).to.equal(1); + await t.rollback(); }); } - it('handles where clause {only}', function() { - return this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } } }).then(info => { - expect(info.count).to.equal(2); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - }); + it('handles where clause {only}', async function() { + const info = await this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } } }); + expect(info.count).to.equal(2); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); }); - it('handles where clause with ordering {only}', function() { - return this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, order: [['id', 'ASC']] }).then(info => { - expect(info.count).to.equal(2); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - }); + it('handles where clause with ordering {only}', async function() { + const info = await this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, order: [['id', 'ASC']] }); + expect(info.count).to.equal(2); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); }); - it('handles offset', function() { - return this.User.findAndCountAll({ offset: 1 }).then(info => { - expect(info.count).to.equal(3); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - }); + it('handles offset', async function() { + const info = await this.User.findAndCountAll({ offset: 1 }); + expect(info.count).to.equal(3); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); }); - it('handles limit', function() { - return this.User.findAndCountAll({ limit: 1 }).then(info => { - expect(info.count).to.equal(3); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(1); - }); + it('handles limit', async function() { + const info = await this.User.findAndCountAll({ limit: 1 }); + expect(info.count).to.equal(3); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(1); }); - it('handles offset and limit', function() { - return this.User.findAndCountAll({ offset: 1, limit: 1 }).then(info => { - expect(info.count).to.equal(3); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(1); - }); + it('handles offset and limit', async function() { + const info = await this.User.findAndCountAll({ offset: 1, limit: 1 }); + expect(info.count).to.equal(3); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(1); }); - it('handles offset with includes', function() { + it('handles offset with includes', async function() { const Election = this.sequelize.define('Election', { name: Sequelize.STRING }); @@ -1544,147 +1557,128 @@ describe(Support.getTestDialectTeaser('Model'), () => { Citizen.hasMany(Election); Citizen.belongsToMany(Election, { as: 'Votes', through: 'ElectionsVotes' }); - return this.sequelize.sync().then(() => { - // Add some data - return Citizen.create({ name: 'Alice' }).then(alice => { - return Citizen.create({ name: 'Bob' }).then(bob => { - return Election.create({ name: 'Some election' }).then(() => { - return Election.create({ name: 'Some other election' }).then(election => { - return election.setCitizen(alice).then(() => { - return election.setVoters([alice, bob]).then(() => { - const criteria = { - offset: 5, - limit: 1, - where: { - name: 'Some election' - }, - include: [ - Citizen, // Election creator - { model: Citizen, as: 'Voters' } // Election voters - ] - }; - return Election.findAndCountAll(criteria).then(elections => { - expect(elections.count).to.equal(1); - expect(elections.rows.length).to.equal(0); - }); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync(); + // Add some data + const alice = await Citizen.create({ name: 'Alice' }); + const bob = await Citizen.create({ name: 'Bob' }); + await Election.create({ name: 'Some election' }); + const election = await Election.create({ name: 'Some other election' }); + await election.setCitizen(alice); + await election.setVoters([alice, bob]); + const criteria = { + offset: 5, + limit: 1, + where: { + name: 'Some election' + }, + include: [ + Citizen, // Election creator + { model: Citizen, as: 'Voters' } // Election voters + ] + }; + const elections = await Election.findAndCountAll(criteria); + expect(elections.count).to.equal(1); + expect(elections.rows.length).to.equal(0); }); - it('handles attributes', function() { - return this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, attributes: ['data'] }).then(info => { - expect(info.count).to.equal(2); - expect(Array.isArray(info.rows)).to.be.ok; - expect(info.rows.length).to.equal(2); - expect(info.rows[0].dataValues).to.not.have.property('username'); - expect(info.rows[1].dataValues).to.not.have.property('username'); - }); + it('handles attributes', async function() { + const info = await this.User.findAndCountAll({ where: { id: { [Op.ne]: this.users[0].id } }, attributes: ['data'] }); + expect(info.count).to.equal(2); + expect(Array.isArray(info.rows)).to.be.ok; + expect(info.rows.length).to.equal(2); + expect(info.rows[0].dataValues).to.not.have.property('username'); + expect(info.rows[1].dataValues).to.not.have.property('username'); }); }); describe('all', () => { - beforeEach(function() { - return this.User.bulkCreate([ + beforeEach(async function() { + await this.User.bulkCreate([ { username: 'user', data: 'foobar' }, { username: 'user2', data: 'bar' } ]); }); if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findAll().then(users1 => { - return User.findAll({ transaction: t }).then(users2 => { - expect(users1.length).to.equal(0); - expect(users2.length).to.equal(1); - return t.rollback(); - }); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + const users1 = await User.findAll(); + const users2 = await User.findAll({ transaction: t }); + expect(users1.length).to.equal(0); + expect(users2.length).to.equal(1); + await t.rollback(); }); } - it('should return all users', function() { - return this.User.findAll().then(users => { - expect(users.length).to.equal(2); - }); + it('should return all users', async function() { + const users = await this.User.findAll(); + expect(users.length).to.equal(2); }); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.findAll({ + await this.User.findAll({ where: {}, logging: spy - }).then(() => { - expect(spy.called).to.be.ok; }); + + expect(spy.called).to.be.ok; }); describe('rejectOnEmpty mode', () => { - it('works from model options', () => { + it('works from model options', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: true }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findAll({ - where: { - username: 'some-username-that-is-not-used-anywhere' - } - })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); - }); + await Model.sync({ force: true }); + + await expect(Model.findAll({ + where: { + username: 'some-username-that-is-not-used-anywhere' + } + })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('throws custom error with initialized', () => { + it('throws custom error with initialized', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: new Sequelize.ConnectionError('Some Error') //using custom error instance }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findAll({ - where: { - username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' - } - })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); - }); + await Model.sync({ force: true }); + + await expect(Model.findAll({ + where: { + username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' + } + })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); }); - it('throws custom error with instance', () => { + it('throws custom error with instance', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: Sequelize.ConnectionError //using custom error instance }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findAll({ - where: { - username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' - } - })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); - }); + await Model.sync({ force: true }); + + await expect(Model.findAll({ + where: { + username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' + } + })).to.eventually.be.rejectedWith(Sequelize.ConnectionError); }); }); }); diff --git a/test/integration/model/findAll/group.test.js b/test/integration/model/findAll/group.test.js index 4780ca362649..fac604072b5a 100644 --- a/test/integration/model/findAll/group.test.js +++ b/test/integration/model/findAll/group.test.js @@ -10,8 +10,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('findAll', () => { describe('group', () => { - it('should correctly group with attributes, #3009', () => { - + it('should correctly group with attributes, #3009', async () => { const Post = current.define('Post', { id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false } @@ -24,38 +23,38 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.hasMany(Comment); - return current.sync({ force: true }).then(() => { - // Create an enviroment - return Post.bulkCreate([ - { name: 'post-1' }, - { name: 'post-2' } - ]); - }).then(() => { - return Comment.bulkCreate([ - { text: 'Market', PostId: 1 }, - { text: 'Text', PostId: 2 }, - { text: 'Abc', PostId: 2 }, - { text: 'Semaphor', PostId: 1 }, - { text: 'Text', PostId: 1 } - ]); - }).then(() => { - return Post.findAll({ - attributes: [[Sequelize.fn('COUNT', Sequelize.col('Comments.id')), 'comment_count']], - include: [ - { model: Comment, attributes: [] } - ], - group: ['Post.id'], - order: [ - ['id'] - ] - }); - }).then(posts => { - expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); - expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); + await current.sync({ force: true }); + + // Create an enviroment + await Post.bulkCreate([ + { name: 'post-1' }, + { name: 'post-2' } + ]); + + await Comment.bulkCreate([ + { text: 'Market', PostId: 1 }, + { text: 'Text', PostId: 2 }, + { text: 'Abc', PostId: 2 }, + { text: 'Semaphor', PostId: 1 }, + { text: 'Text', PostId: 1 } + ]); + + const posts = await Post.findAll({ + attributes: [[Sequelize.fn('COUNT', Sequelize.col('Comments.id')), 'comment_count']], + include: [ + { model: Comment, attributes: [] } + ], + group: ['Post.id'], + order: [ + ['id'] + ] }); + + expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); + expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); }); - it('should not add primary key when grouping using a belongsTo association', () => { + it('should not add primary key when grouping using a belongsTo association', async () => { const Post = current.define('Post', { id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false } @@ -69,36 +68,36 @@ describe(Support.getTestDialectTeaser('Model'), () => { Post.hasMany(Comment); Comment.belongsTo(Post); - return current.sync({ force: true }).then(() => { - return Post.bulkCreate([ - { name: 'post-1' }, - { name: 'post-2' } - ]); - }).then(() => { - return Comment.bulkCreate([ - { text: 'Market', PostId: 1 }, - { text: 'Text', PostId: 2 }, - { text: 'Abc', PostId: 2 }, - { text: 'Semaphor', PostId: 1 }, - { text: 'Text', PostId: 1 } - ]); - }).then(() => { - return Comment.findAll({ - attributes: ['PostId', [Sequelize.fn('COUNT', Sequelize.col('Comment.id')), 'comment_count']], - include: [ - { model: Post, attributes: [] } - ], - group: ['PostId'], - order: [ - ['PostId'] - ] - }); - }).then(posts => { - expect(posts[0].get().hasOwnProperty('id')).to.equal(false); - expect(posts[1].get().hasOwnProperty('id')).to.equal(false); - expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); - expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); + await current.sync({ force: true }); + + await Post.bulkCreate([ + { name: 'post-1' }, + { name: 'post-2' } + ]); + + await Comment.bulkCreate([ + { text: 'Market', PostId: 1 }, + { text: 'Text', PostId: 2 }, + { text: 'Abc', PostId: 2 }, + { text: 'Semaphor', PostId: 1 }, + { text: 'Text', PostId: 1 } + ]); + + const posts = await Comment.findAll({ + attributes: ['PostId', [Sequelize.fn('COUNT', Sequelize.col('Comment.id')), 'comment_count']], + include: [ + { model: Post, attributes: [] } + ], + group: ['PostId'], + order: [ + ['PostId'] + ] }); + + expect(posts[0].get().hasOwnProperty('id')).to.equal(false); + expect(posts[1].get().hasOwnProperty('id')).to.equal(false); + expect(parseInt(posts[0].get('comment_count'), 10)).to.be.equal(3); + expect(parseInt(posts[1].get('comment_count'), 10)).to.be.equal(2); }); }); }); diff --git a/test/integration/model/findAll/groupedLimit.test.js b/test/integration/model/findAll/groupedLimit.test.js index aea0c89dc4b2..0385c7464c23 100644 --- a/test/integration/model/findAll/groupedLimit.test.js +++ b/test/integration/model/findAll/groupedLimit.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../../support'), Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, DataTypes = require('../../../../lib/data-types'), current = Support.sequelize, _ = require('lodash'); @@ -26,7 +25,7 @@ if (current.dialect.supports['UNION ALL']) { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { age: Sequelize.INTEGER }); @@ -50,47 +49,47 @@ if (current.dialect.supports['UNION ALL']) { this.User.Tasks = this.User.hasMany(this.Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.User.bulkCreate([{ age: -5 }, { age: 45 }, { age: 7 }, { age: -9 }, { age: 8 }, { age: 15 }, { age: -9 }]), - this.Project.bulkCreate([{}, {}]), - this.Task.bulkCreate([{}, {}]) - ); - }) - .then(() => Promise.all([this.User.findAll(), this.Project.findAll(), this.Task.findAll()])) - .then(([users, projects, tasks]) => { - this.projects = projects; - return Promise.join( - projects[0].setMembers(users.slice(0, 4)), - projects[1].setMembers(users.slice(2)), - projects[0].setParanoidMembers(users.slice(0, 4)), - projects[1].setParanoidMembers(users.slice(2)), - users[2].setTasks(tasks) - ); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + this.User.bulkCreate([{ age: -5 }, { age: 45 }, { age: 7 }, { age: -9 }, { age: 8 }, { age: 15 }, { age: -9 }]), + this.Project.bulkCreate([{}, {}]), + this.Task.bulkCreate([{}, {}]) + ]); + + const [users, projects, tasks] = await Promise.all([this.User.findAll(), this.Project.findAll(), this.Task.findAll()]); + this.projects = projects; + + await Promise.all([ + projects[0].setMembers(users.slice(0, 4)), + projects[1].setMembers(users.slice(2)), + projects[0].setParanoidMembers(users.slice(0, 4)), + projects[1].setParanoidMembers(users.slice(2)), + users[2].setTasks(tasks) + ]); }); describe('on: belongsToMany', () => { - it('maps attributes from a grouped limit to models', function() { - return this.User.findAll({ + it('maps attributes from a grouped limit to models', async function() { + const users = await this.User.findAll({ groupedLimit: { limit: 3, on: this.User.Projects, values: this.projects.map(item => item.get('id')) } - }).then(users => { - expect(users).to.have.length(5); - users.filter(u => u.get('id') !== 3).forEach(u => { - expect(u.get('projects')).to.have.length(1); - }); - users.filter(u => u.get('id') === 3).forEach(u => { - expect(u.get('projects')).to.have.length(2); - }); + }); + + expect(users).to.have.length(5); + users.filter(u => u.get('id') !== 3).forEach(u => { + expect(u.get('projects')).to.have.length(1); + }); + users.filter(u => u.get('id') === 3).forEach(u => { + expect(u.get('projects')).to.have.length(2); }); }); - it('maps attributes from a grouped limit to models with include', function() { - return this.User.findAll({ + it('maps attributes from a grouped limit to models with include', async function() { + const users = await this.User.findAll({ groupedLimit: { limit: 3, on: this.User.Projects, @@ -98,26 +97,26 @@ if (current.dialect.supports['UNION ALL']) { }, order: ['id'], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 2, 3 - project2 - 3, 4, 5 - */ - expect(users).to.have.length(5); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 2, 3, 4, 5]); - - expect(users[2].get('tasks')).to.have.length(2); - users.filter(u => u.get('id') !== 3).forEach(u => { - expect(u.get('projects')).to.have.length(1); - }); - users.filter(u => u.get('id') === 3).forEach(u => { - expect(u.get('projects')).to.have.length(2); - }); + }); + + /* + project1 - 1, 2, 3 + project2 - 3, 4, 5 + */ + expect(users).to.have.length(5); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 2, 3, 4, 5]); + + expect(users[2].get('tasks')).to.have.length(2); + users.filter(u => u.get('id') !== 3).forEach(u => { + expect(u.get('projects')).to.have.length(1); + }); + users.filter(u => u.get('id') === 3).forEach(u => { + expect(u.get('projects')).to.have.length(2); }); }); - it('works with computed order', function() { - return this.User.findAll({ + it('works with computed order', async function() { + const users = await this.User.findAll({ attributes: ['id'], groupedLimit: { limit: 3, @@ -128,18 +127,18 @@ if (current.dialect.supports['UNION ALL']) { Sequelize.fn('ABS', Sequelize.col('age')) ], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 3, 4 - project2 - 3, 5, 4 - */ - expect(users).to.have.length(4); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 4]); }); + + /* + project1 - 1, 3, 4 + project2 - 3, 5, 4 + */ + expect(users).to.have.length(4); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 4]); }); - it('works with multiple orders', function() { - return this.User.findAll({ + it('works with multiple orders', async function() { + const users = await this.User.findAll({ attributes: ['id'], groupedLimit: { limit: 3, @@ -151,18 +150,18 @@ if (current.dialect.supports['UNION ALL']) { ['id', 'DESC'] ], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 3, 4 - project2 - 3, 5, 7 - */ - expect(users).to.have.length(5); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); }); + + /* + project1 - 1, 3, 4 + project2 - 3, 5, 7 + */ + expect(users).to.have.length(5); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); }); - it('works with paranoid junction models', function() { - return this.User.findAll({ + it('works with paranoid junction models', async function() { + const users0 = await this.User.findAll({ attributes: ['id'], groupedLimit: { limit: 3, @@ -174,68 +173,68 @@ if (current.dialect.supports['UNION ALL']) { ['id', 'DESC'] ], include: [this.User.Tasks] - }).then(users => { - /* - project1 - 1, 3, 4 - project2 - 3, 5, 7 - */ - expect(users).to.have.length(5); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); - - return Sequelize.Promise.join( - this.projects[0].setParanoidMembers(users.slice(0, 2)), - this.projects[1].setParanoidMembers(users.slice(4)) - ); - }).then(() => { - return this.User.findAll({ - attributes: ['id'], - groupedLimit: { - limit: 3, - on: this.User.ParanoidProjects, - values: this.projects.map(item => item.get('id')) - }, - order: [ - Sequelize.fn('ABS', Sequelize.col('age')), - ['id', 'DESC'] - ], - include: [this.User.Tasks] - }); - }).then(users => { - /* - project1 - 1, 3 - project2 - 4 - */ - expect(users).to.have.length(3); - expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 4]); }); + + /* + project1 - 1, 3, 4 + project2 - 3, 5, 7 + */ + expect(users0).to.have.length(5); + expect(users0.map(u => u.get('id'))).to.deep.equal([1, 3, 5, 7, 4]); + + await Promise.all([ + this.projects[0].setParanoidMembers(users0.slice(0, 2)), + this.projects[1].setParanoidMembers(users0.slice(4)) + ]); + + const users = await this.User.findAll({ + attributes: ['id'], + groupedLimit: { + limit: 3, + on: this.User.ParanoidProjects, + values: this.projects.map(item => item.get('id')) + }, + order: [ + Sequelize.fn('ABS', Sequelize.col('age')), + ['id', 'DESC'] + ], + include: [this.User.Tasks] + }); + + /* + project1 - 1, 3 + project2 - 4 + */ + expect(users).to.have.length(3); + expect(users.map(u => u.get('id'))).to.deep.equal([1, 3, 4]); }); }); describe('on: hasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user'); this.Task = this.sequelize.define('task'); this.User.Tasks = this.User.hasMany(this.Task); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - this.User.bulkCreate([{}, {}, {}]), - this.Task.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]) - ); - }) - .then(() => Promise.all([this.User.findAll(), this.Task.findAll()])) - .then(([users, tasks]) => { - this.users = users; - return Promise.join( - users[0].setTasks(tasks[0]), - users[1].setTasks(tasks.slice(1, 4)), - users[2].setTasks(tasks.slice(4)) - ); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + this.User.bulkCreate([{}, {}, {}]), + this.Task.bulkCreate([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }]) + ]); + + const [users, tasks] = await Promise.all([this.User.findAll(), this.Task.findAll()]); + this.users = users; + + await Promise.all([ + users[0].setTasks(tasks[0]), + users[1].setTasks(tasks.slice(1, 4)), + users[2].setTasks(tasks.slice(4)) + ]); }); - it('Applies limit and order correctly', function() { - return this.Task.findAll({ + it('Applies limit and order correctly', async function() { + const tasks = await this.Task.findAll({ order: [ ['id', 'DESC'] ], @@ -244,15 +243,15 @@ if (current.dialect.supports['UNION ALL']) { on: this.User.Tasks, values: this.users.map(item => item.get('id')) } - }).then(tasks => { - const byUser = _.groupBy(tasks, _.property('userId')); - expect(Object.keys(byUser)).to.have.length(3); - - expect(byUser[1]).to.have.length(1); - expect(byUser[2]).to.have.length(3); - expect(_.invokeMap(byUser[2], 'get', 'id')).to.deep.equal([4, 3, 2]); - expect(byUser[3]).to.have.length(2); }); + + const byUser = _.groupBy(tasks, _.property('userId')); + expect(Object.keys(byUser)).to.have.length(3); + + expect(byUser[1]).to.have.length(1); + expect(byUser[2]).to.have.length(3); + expect(_.invokeMap(byUser[2], 'get', 'id')).to.deep.equal([4, 3, 2]); + expect(byUser[3]).to.have.length(2); }); }); }); diff --git a/test/integration/model/findAll/order.test.js b/test/integration/model/findAll/order.test.js index 7eb4f1f54478..35bfd50523c0 100644 --- a/test/integration/model/findAll/order.test.js +++ b/test/integration/model/findAll/order.test.js @@ -10,58 +10,58 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('findAll', () => { describe('order', () => { describe('Sequelize.literal()', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { email: DataTypes.STRING }); - return this.User.sync({ force: true }).then(() => { - return this.User.create({ - email: 'test@sequelizejs.com' - }); + await this.User.sync({ force: true }); + + await this.User.create({ + email: 'test@sequelizejs.com' }); }); if (current.dialect.name !== 'mssql') { - it('should work with order: literal()', function() { - return this.User.findAll({ + it('should work with order: literal()', async function() { + const users = await this.User.findAll({ order: this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`) - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.get('email')).to.be.ok; }); }); - it('should work with order: [literal()]', function() { - return this.User.findAll({ + it('should work with order: [literal()]', async function() { + const users = await this.User.findAll({ order: [this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`)] - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.get('email')).to.be.ok; }); }); - it('should work with order: [[literal()]]', function() { - return this.User.findAll({ + it('should work with order: [[literal()]]', async function() { + const users = await this.User.findAll({ order: [ [this.sequelize.literal(`email = ${this.sequelize.escape('test@sequelizejs.com')}`)] ] - }).then(users => { - expect(users.length).to.equal(1); - users.forEach(user => { - expect(user.get('email')).to.be.ok; - }); + }); + + expect(users.length).to.equal(1); + users.forEach(user => { + expect(user.get('email')).to.be.ok; }); }); } }); describe('injections', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { name: DataTypes.STRING }); @@ -69,12 +69,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); this.User.belongsTo(this.Group); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); if (current.dialect.supports['ORDER NULLS']) { - it('should not throw with on NULLS LAST/NULLS FIRST', function() { - return this.User.findAll({ + it('should not throw with on NULLS LAST/NULLS FIRST', async function() { + await this.User.findAll({ include: [this.Group], order: [ ['id', 'ASC NULLS LAST'], @@ -84,16 +84,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); } - it('should not throw on a literal', function() { - return this.User.findAll({ + it('should not throw on a literal', async function() { + await this.User.findAll({ order: [ ['id', this.sequelize.literal('ASC, name DESC')] ] }); }); - it('should not throw with include when last order argument is a field', function() { - return this.User.findAll({ + it('should not throw with include when last order argument is a field', async function() { + await this.User.findAll({ include: [this.Group], order: [ [this.Group, 'id'] diff --git a/test/integration/model/findAll/separate.test.js b/test/integration/model/findAll/separate.test.js index 15f5a2fe60fa..d81a848fcf89 100644 --- a/test/integration/model/findAll/separate.test.js +++ b/test/integration/model/findAll/separate.test.js @@ -4,13 +4,12 @@ const chai = require('chai'); const expect = chai.expect; const Support = require('../../support'); const DataTypes = require('../../../../lib/data-types'); -const Sequelize = require('../../../../lib/sequelize'); const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { describe('findAll', () => { describe('separate with limit', () => { - it('should not throw syntax error (union)', () => { + it('should not throw syntax error (union)', async () => { // #9813 testcase const Project = current.define('Project', { name: DataTypes.STRING }); const LevelTwo = current.define('LevelTwo', { name: DataTypes.STRING }); @@ -23,47 +22,51 @@ describe(Support.getTestDialectTeaser('Model'), () => { LevelTwo.hasMany(LevelThree, { as: 'type_twos' }); LevelThree.belongsTo(LevelTwo); - return current.sync({ force: true }).then(() => { - return Sequelize.Promise.all([ - Project.create({ name: 'testProject' }), - LevelTwo.create({ name: 'testL21' }), - LevelTwo.create({ name: 'testL22' }) - ]); - }).then(([project, level21, level22]) => { - return Sequelize.Promise.all([ - project.addLevelTwo(level21), - project.addLevelTwo(level22) - ]); - }).then(() => { - // one include case - return Project.findAll({ - where: { name: 'testProject' }, - include: [ - { - model: LevelTwo, - include: [ - { - model: LevelThree, - as: 'type_ones', - where: { type: 0 }, - separate: true, - limit: 1, - order: [['createdAt', 'DESC']] - } - ] - } - ] - }); - }).then(projects => { - expect(projects).to.have.length(1); - expect(projects[0].LevelTwos).to.have.length(2); - expect(projects[0].LevelTwos[0].type_ones).to.have.length(0); - expect(projects[0].LevelTwos[1].type_ones).to.have.length(0); - }, () => { - expect.fail(); - }).then(() => { + try { + try { + await current.sync({ force: true }); + + const [project, level21, level22] = await Promise.all([ + Project.create({ name: 'testProject' }), + LevelTwo.create({ name: 'testL21' }), + LevelTwo.create({ name: 'testL22' }) + ]); + + await Promise.all([ + project.addLevelTwo(level21), + project.addLevelTwo(level22) + ]); + + // one include case + const projects0 = await Project.findAll({ + where: { name: 'testProject' }, + include: [ + { + model: LevelTwo, + include: [ + { + model: LevelThree, + as: 'type_ones', + where: { type: 0 }, + separate: true, + limit: 1, + order: [['createdAt', 'DESC']] + } + ] + } + ] + }); + + expect(projects0).to.have.length(1); + expect(projects0[0].LevelTwos).to.have.length(2); + expect(projects0[0].LevelTwos[0].type_ones).to.have.length(0); + expect(projects0[0].LevelTwos[1].type_ones).to.have.length(0); + } catch (err) { + expect.fail(); + } + // two includes case - return Project.findAll({ + const projects = await Project.findAll({ where: { name: 'testProject' }, include: [ { @@ -89,14 +92,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { } ] }); - }).then(projects => { + expect(projects).to.have.length(1); expect(projects[0].LevelTwos).to.have.length(2); expect(projects[0].LevelTwos[0].type_ones).to.have.length(0); expect(projects[0].LevelTwos[1].type_ones).to.have.length(0); - }, () => { + } catch (err) { expect.fail(); - }); + } }); }); }); diff --git a/test/integration/model/findOne.test.js b/test/integration/model/findOne.test.js index c5d8bbdd2bd6..6f1dcc07b327 100644 --- a/test/integration/model/findOne.test.js +++ b/test/integration/model/findOne.test.js @@ -3,16 +3,14 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), dialect = Support.getTestDialect(), DataTypes = require('../../../lib/data-types'), - config = require('../../config/config'), current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, secretValue: DataTypes.STRING, @@ -22,60 +20,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { aBool: DataTypes.BOOLEAN }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); describe('findOne', () => { if (current.dialect.supports.transactions) { - it('supports transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Sequelize.STRING }); - - return User.sync({ force: true }).then(() => { - return sequelize.transaction().then(t => { - return User.create({ username: 'foo' }, { transaction: t }).then(() => { - return User.findOne({ - where: { username: 'foo' } - }).then(user1 => { - return User.findOne({ - where: { username: 'foo' }, - transaction: t - }).then(user2 => { - expect(user1).to.be.null; - expect(user2).to.not.be.null; - return t.rollback(); - }); - }); - }); - }); - }); + it('supports transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Sequelize.STRING }); + + await User.sync({ force: true }); + const t = await sequelize.transaction(); + await User.create({ username: 'foo' }, { transaction: t }); + + const user1 = await User.findOne({ + where: { username: 'foo' } + }); + + const user2 = await User.findOne({ + where: { username: 'foo' }, + transaction: t }); + + expect(user1).to.be.null; + expect(user2).to.not.be.null; + await t.rollback(); }); } describe('general / basic function', () => { - beforeEach(function() { - return this.User.create({ username: 'barfooz' }).then(user => { - this.UserPrimary = this.sequelize.define('UserPrimary', { - specialkey: { - type: DataTypes.STRING, - primaryKey: true - } - }); - - return this.UserPrimary.sync({ force: true }).then(() => { - return this.UserPrimary.create({ specialkey: 'a string' }).then(() => { - this.user = user; - }); - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'barfooz' }); + this.UserPrimary = this.sequelize.define('UserPrimary', { + specialkey: { + type: DataTypes.STRING, + primaryKey: true + } }); + + await this.UserPrimary.sync({ force: true }); + await this.UserPrimary.create({ specialkey: 'a string' }); + this.user = user; }); if (dialect === 'mysql') { // Bit fields interpreted as boolean need conversion from buffer / bool. // Sqlite returns the inserted value as is, and postgres really should the built in bool type instead - it('allows bit fields as booleans', function() { + it('allows bit fields as booleans', async function() { let bitUser = this.sequelize.define('bituser', { bool: 'BIT(1)' }, { @@ -83,69 +75,65 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); // First use a custom data type def to create the bit field - return bitUser.sync({ force: true }).then(() => { - // Then change the definition to BOOLEAN - bitUser = this.sequelize.define('bituser', { - bool: DataTypes.BOOLEAN - }, { - timestamps: false - }); - - return bitUser.bulkCreate([ - { bool: 0 }, - { bool: 1 } - ]); - }).then(() => { - return bitUser.findAll(); - }).then(bitUsers => { - expect(bitUsers[0].bool).not.to.be.ok; - expect(bitUsers[1].bool).to.be.ok; + await bitUser.sync({ force: true }); + // Then change the definition to BOOLEAN + bitUser = this.sequelize.define('bituser', { + bool: DataTypes.BOOLEAN + }, { + timestamps: false }); + + await bitUser.bulkCreate([ + { bool: 0 }, + { bool: 1 } + ]); + + const bitUsers = await bitUser.findAll(); + expect(bitUsers[0].bool).not.to.be.ok; + expect(bitUsers[1].bool).to.be.ok; }); } - it('treats questionmarks in an array', function() { + it('treats questionmarks in an array', async function() { let test = false; - return this.UserPrimary.findOne({ + + await this.UserPrimary.findOne({ where: { 'specialkey': 'awesome' }, logging(sql) { test = true; expect(sql).to.match(/WHERE ["|`|[]UserPrimary["|`|\]]\.["|`|[]specialkey["|`|\]] = N?'awesome'/); } - }).then(() => { - expect(test).to.be.true; }); + + expect(test).to.be.true; }); - it('doesn\'t throw an error when entering in a non integer value for a specified primary field', function() { - return this.UserPrimary.findByPk('a string').then(user => { - expect(user.specialkey).to.equal('a string'); - }); + it('doesn\'t throw an error when entering in a non integer value for a specified primary field', async function() { + const user = await this.UserPrimary.findByPk('a string'); + expect(user.specialkey).to.equal('a string'); }); - it('returns a single dao', function() { - return this.User.findByPk(this.user.id).then(user => { - expect(Array.isArray(user)).to.not.be.ok; - expect(user.id).to.equal(this.user.id); - expect(user.id).to.equal(1); - }); + it('returns a single dao', async function() { + const user = await this.User.findByPk(this.user.id); + expect(Array.isArray(user)).to.not.be.ok; + expect(user.id).to.equal(this.user.id); + expect(user.id).to.equal(1); }); - it('returns a single dao given a string id', function() { - return this.User.findByPk(this.user.id.toString()).then(user => { - expect(Array.isArray(user)).to.not.be.ok; - expect(user.id).to.equal(this.user.id); - expect(user.id).to.equal(1); - }); + it('returns a single dao given a string id', async function() { + const user = await this.User.findByPk(this.user.id.toString()); + expect(Array.isArray(user)).to.not.be.ok; + expect(user.id).to.equal(this.user.id); + expect(user.id).to.equal(1); }); - it('should make aliased attributes available', function() { - return this.User.findOne({ + it('should make aliased attributes available', async function() { + const user = await this.User.findOne({ where: { id: 1 }, attributes: ['id', ['username', 'name']] - }).then(user => { - expect(user.dataValues.name).to.equal('barfooz'); }); + + expect(user.dataValues.name).to.equal('barfooz'); }); it('should fail with meaningful error message on invalid attributes definition', function() { @@ -155,146 +143,149 @@ describe(Support.getTestDialectTeaser('Model'), () => { })).to.be.rejectedWith('["username"] is not a valid attribute definition. Please use the following format: [\'attribute definition\', \'alias\']'); }); - it('should not try to convert boolean values if they are not selected', function() { + it('should not try to convert boolean values if they are not selected', async function() { const UserWithBoolean = this.sequelize.define('UserBoolean', { active: Sequelize.BOOLEAN }); - return UserWithBoolean.sync({ force: true }).then(() => { - return UserWithBoolean.create({ active: true }).then(user => { - return UserWithBoolean.findOne({ where: { id: user.id }, attributes: ['id'] }).then(user => { - expect(user.active).not.to.exist; - }); - }); - }); + await UserWithBoolean.sync({ force: true }); + const user = await UserWithBoolean.create({ active: true }); + const user0 = await UserWithBoolean.findOne({ where: { id: user.id }, attributes: ['id'] }); + expect(user0.active).not.to.exist; }); - it('finds a specific user via where option', function() { - return this.User.findOne({ where: { username: 'barfooz' } }).then(user => { - expect(user.username).to.equal('barfooz'); - }); + it('finds a specific user via where option', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' } }); + expect(user.username).to.equal('barfooz'); }); - it('doesn\'t find a user if conditions are not matching', function() { - return this.User.findOne({ where: { username: 'foo' } }).then(user => { - expect(user).to.be.null; - }); + it('doesn\'t find a user if conditions are not matching', async function() { + const user = await this.User.findOne({ where: { username: 'foo' } }); + expect(user).to.be.null; }); - it('allows sql logging', function() { + it('allows sql logging', async function() { let test = false; - return this.User.findOne({ + + await this.User.findOne({ where: { username: 'foo' }, logging(sql) { test = true; expect(sql).to.exist; expect(sql.toUpperCase()).to.include('SELECT'); } - }).then(() => { - expect(test).to.be.true; }); + + expect(test).to.be.true; }); - it('ignores passed limit option', function() { - return this.User.findOne({ limit: 10 }).then(user => { - // it returns an object instead of an array - expect(Array.isArray(user)).to.not.be.ok; - expect(user.dataValues.hasOwnProperty('username')).to.be.ok; - }); + it('ignores passed limit option', async function() { + const user = await this.User.findOne({ limit: 10 }); + // it returns an object instead of an array + expect(Array.isArray(user)).to.not.be.ok; + expect(user.dataValues.hasOwnProperty('username')).to.be.ok; }); - it('finds entries via primary keys', function() { + it('finds entries via primary keys', async function() { const UserPrimary = this.sequelize.define('UserWithPrimaryKey', { identifier: { type: Sequelize.STRING, primaryKey: true }, name: Sequelize.STRING }); - return UserPrimary.sync({ force: true }).then(() => { - return UserPrimary.create({ - identifier: 'an identifier', - name: 'John' - }).then(u => { - expect(u.id).not.to.exist; - return UserPrimary.findByPk('an identifier').then(u2 => { - expect(u2.identifier).to.equal('an identifier'); - expect(u2.name).to.equal('John'); - }); - }); + await UserPrimary.sync({ force: true }); + + const u = await UserPrimary.create({ + identifier: 'an identifier', + name: 'John' }); + + expect(u.id).not.to.exist; + const u2 = await UserPrimary.findByPk('an identifier'); + expect(u2.identifier).to.equal('an identifier'); + expect(u2.name).to.equal('John'); }); - it('finds entries via a string primary key called id', function() { + it('finds entries via a string primary key called id', async function() { const UserPrimary = this.sequelize.define('UserWithPrimaryKey', { id: { type: Sequelize.STRING, primaryKey: true }, name: Sequelize.STRING }); - return UserPrimary.sync({ force: true }).then(() => { - return UserPrimary.create({ - id: 'a string based id', - name: 'Johnno' - }).then(() => { - return UserPrimary.findByPk('a string based id').then(u2 => { - expect(u2.id).to.equal('a string based id'); - expect(u2.name).to.equal('Johnno'); - }); - }); + await UserPrimary.sync({ force: true }); + + await UserPrimary.create({ + id: 'a string based id', + name: 'Johnno' }); + + const u2 = await UserPrimary.findByPk('a string based id'); + expect(u2.id).to.equal('a string based id'); + expect(u2.name).to.equal('Johnno'); }); - it('always honors ZERO as primary key', function() { + it('always honors ZERO as primary key', async function() { const permutations = [ 0, '0' ]; let count = 0; - return this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]).then(() => { - return Sequelize.Promise.map(permutations, perm => { - return this.User.findByPk(perm, { - logging(s) { - expect(s).to.include(0); - count++; - } - }).then(user => { - expect(user).to.be.null; - }); + await this.User.bulkCreate([{ username: 'jack' }, { username: 'jack' }]); + + await Promise.all(permutations.map(async perm => { + const user = await this.User.findByPk(perm, { + logging(s) { + expect(s).to.include(0); + count++; + } }); - }).then(() => { - expect(count).to.be.equal(permutations.length); - }); + + expect(user).to.be.null; + })); + + expect(count).to.be.equal(permutations.length); }); - it('should allow us to find IDs using capital letters', function() { - const User = this.sequelize.define(`User${config.rand()}`, { + it('should allow us to find IDs using capital letters', async function() { + const User = this.sequelize.define(`User${Support.rand()}`, { ID: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, Login: { type: Sequelize.STRING } }); - return User.sync({ force: true }).then(() => { - return User.create({ Login: 'foo' }).then(() => { - return User.findByPk(1).then(user => { - expect(user).to.exist; - expect(user.ID).to.equal(1); - }); - }); - }); + await User.sync({ force: true }); + await User.create({ Login: 'foo' }); + const user = await User.findByPk(1); + expect(user).to.exist; + expect(user.ID).to.equal(1); }); if (dialect === 'postgres' || dialect === 'sqlite') { - it('should allow case-insensitive find on CITEXT type', function() { + it('should allow case-insensitive find on CITEXT type', async function() { const User = this.sequelize.define('UserWithCaseInsensitiveName', { username: Sequelize.CITEXT }); - return User.sync({ force: true }).then(() => { - return User.create({ username: 'longUserNAME' }); - }).then(() => { - return User.findOne({ where: { username: 'LONGusername' } }); - }).then(user => { - expect(user).to.exist; - expect(user.username).to.equal('longUserNAME'); + await User.sync({ force: true }); + await User.create({ username: 'longUserNAME' }); + const user = await User.findOne({ where: { username: 'LONGusername' } }); + expect(user).to.exist; + expect(user.username).to.equal('longUserNAME'); + }); + } + + if (dialect === 'postgres') { + it('should allow case-sensitive find on TSVECTOR type', async function() { + const User = this.sequelize.define('UserWithCaseInsensitiveName', { + username: Sequelize.TSVECTOR + }); + + await User.sync({ force: true }); + await User.create({ username: 'longUserNAME' }); + const user = await User.findOne({ + where: { username: 'longUserNAME' } }); + expect(user).to.exist; + expect(user.username).to.equal("'longUserNAME'"); }); } }); @@ -304,87 +295,83 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Task = this.sequelize.define('Task', { title: Sequelize.STRING }); this.Worker = this.sequelize.define('Worker', { name: Sequelize.STRING }); - this.init = function(callback) { - return this.sequelize.sync({ force: true }).then(() => { - return this.Worker.create({ name: 'worker' }).then(worker => { - return this.Task.create({ title: 'homework' }).then(task => { - this.worker = worker; - this.task = task; - return callback(); - }); - }); - }); + this.init = async function(callback) { + await this.sequelize.sync({ force: true }); + const worker = await this.Worker.create({ name: 'worker' }); + const task = await this.Task.create({ title: 'homework' }); + this.worker = worker; + this.task = task; + return callback(); }; }); describe('belongsTo', () => { describe('generic', () => { - it('throws an error about unexpected input if include contains a non-object', function() { - return this.Worker.findOne({ include: [1] }).catch(err => { + it('throws an error about unexpected input if include contains a non-object', async function() { + try { + await this.Worker.findOne({ include: [1] }); + } catch (err) { expect(err.message).to.equal('Include unexpected. Element has to be either a Model, an Association or an object.'); - }); + } }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Worker.findOne({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Worker.findOne({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is not associated to Worker!'); - }); + } }); - it('returns the associated worker via task.worker', function() { + it('returns the associated worker via task.worker', async function() { this.Task.belongsTo(this.Worker); - return this.init(() => { - return this.task.setWorker(this.worker).then(() => { - return this.Task.findOne({ - where: { title: 'homework' }, - include: [this.Worker] - }).then(task => { - expect(task).to.exist; - expect(task.Worker).to.exist; - expect(task.Worker.name).to.equal('worker'); - }); + + await this.init(async () => { + await this.task.setWorker(this.worker); + + const task = await this.Task.findOne({ + where: { title: 'homework' }, + include: [this.Worker] }); + + expect(task).to.exist; + expect(task.Worker).to.exist; + expect(task.Worker.name).to.equal('worker'); }); }); }); - it('returns the private and public ip', function() { + it('returns the private and public ip', async function() { const ctx = Object.create(this); ctx.Domain = ctx.sequelize.define('Domain', { ip: Sequelize.STRING }); ctx.Environment = ctx.sequelize.define('Environment', { name: Sequelize.STRING }); ctx.Environment.belongsTo(ctx.Domain, { as: 'PrivateDomain', foreignKey: 'privateDomainId' }); ctx.Environment.belongsTo(ctx.Domain, { as: 'PublicDomain', foreignKey: 'publicDomainId' }); - return ctx.Domain.sync({ force: true }).then(() => { - return ctx.Environment.sync({ force: true }).then(() => { - return ctx.Domain.create({ ip: '192.168.0.1' }).then(privateIp => { - return ctx.Domain.create({ ip: '91.65.189.19' }).then(publicIp => { - return ctx.Environment.create({ name: 'environment' }).then(env => { - return env.setPrivateDomain(privateIp).then(() => { - return env.setPublicDomain(publicIp).then(() => { - return ctx.Environment.findOne({ - where: { name: 'environment' }, - include: [ - { model: ctx.Domain, as: 'PrivateDomain' }, - { model: ctx.Domain, as: 'PublicDomain' } - ] - }).then(environment => { - expect(environment).to.exist; - expect(environment.PrivateDomain).to.exist; - expect(environment.PrivateDomain.ip).to.equal('192.168.0.1'); - expect(environment.PublicDomain).to.exist; - expect(environment.PublicDomain.ip).to.equal('91.65.189.19'); - }); - }); - }); - }); - }); - }); - }); + await ctx.Domain.sync({ force: true }); + await ctx.Environment.sync({ force: true }); + const privateIp = await ctx.Domain.create({ ip: '192.168.0.1' }); + const publicIp = await ctx.Domain.create({ ip: '91.65.189.19' }); + const env = await ctx.Environment.create({ name: 'environment' }); + await env.setPrivateDomain(privateIp); + await env.setPublicDomain(publicIp); + + const environment = await ctx.Environment.findOne({ + where: { name: 'environment' }, + include: [ + { model: ctx.Domain, as: 'PrivateDomain' }, + { model: ctx.Domain, as: 'PublicDomain' } + ] }); + + expect(environment).to.exist; + expect(environment.PrivateDomain).to.exist; + expect(environment.PrivateDomain.ip).to.equal('192.168.0.1'); + expect(environment.PublicDomain).to.exist; + expect(environment.PublicDomain.ip).to.equal('91.65.189.19'); }); - it('eager loads with non-id primary keys', function() { + it('eager loads with non-id primary keys', async function() { this.User = this.sequelize.define('UserPKeagerbelong', { username: { type: Sequelize.STRING, @@ -399,25 +386,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); this.User.belongsTo(this.Group); - return this.sequelize.sync({ force: true }).then(() => { - return this.Group.create({ name: 'people' }).then(() => { - return this.User.create({ username: 'someone', GroupPKeagerbelongName: 'people' }).then(() => { - return this.User.findOne({ - where: { - username: 'someone' - }, - include: [this.Group] - }).then(someUser => { - expect(someUser).to.exist; - expect(someUser.username).to.equal('someone'); - expect(someUser.GroupPKeagerbelong.name).to.equal('people'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.Group.create({ name: 'people' }); + await this.User.create({ username: 'someone', GroupPKeagerbelongName: 'people' }); + + const someUser = await this.User.findOne({ + where: { + username: 'someone' + }, + include: [this.Group] }); + + expect(someUser).to.exist; + expect(someUser.username).to.equal('someone'); + expect(someUser.GroupPKeagerbelong.name).to.equal('people'); }); - it('getting parent data in many to one relationship', function() { + it('getting parent data in many to one relationship', async function() { const User = this.sequelize.define('User', { id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true }, username: { type: Sequelize.STRING } @@ -432,36 +417,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { User.hasMany(Message); Message.belongsTo(User, { foreignKey: 'user_id' }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'test_testerson' }).then(user => { - return Message.create({ user_id: user.id, message: 'hi there!' }).then(() => { - return Message.create({ user_id: user.id, message: 'a second message' }).then(() => { - return Message.findAll({ - where: { user_id: user.id }, - attributes: [ - 'user_id', - 'message' - ], - include: [{ model: User, attributes: ['username'] }] - }).then(messages => { - expect(messages.length).to.equal(2); - - expect(messages[0].message).to.equal('hi there!'); - expect(messages[0].User.username).to.equal('test_testerson'); - - expect(messages[1].message).to.equal('a second message'); - expect(messages[1].User.username).to.equal('test_testerson'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'test_testerson' }); + await Message.create({ user_id: user.id, message: 'hi there!' }); + await Message.create({ user_id: user.id, message: 'a second message' }); + + const messages = await Message.findAll({ + where: { user_id: user.id }, + attributes: [ + 'user_id', + 'message' + ], + include: [{ model: User, attributes: ['username'] }] }); + + expect(messages.length).to.equal(2); + + expect(messages[0].message).to.equal('hi there!'); + expect(messages[0].User.username).to.equal('test_testerson'); + + expect(messages[1].message).to.equal('a second message'); + expect(messages[1].User.username).to.equal('test_testerson'); }); - it('allows mulitple assocations of the same model with different alias', function() { + it('allows mulitple assocations of the same model with different alias', async function() { this.Worker.belongsTo(this.Task, { as: 'ToDo' }); this.Worker.belongsTo(this.Task, { as: 'DoTo' }); - return this.init(() => { + + await this.init(() => { return this.Worker.findOne({ include: [ { model: this.Task, as: 'ToDo' }, @@ -473,31 +456,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('hasOne', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasOne(this.Task); - return this.init(() => { + + await this.init(() => { return this.worker.setTask(this.task); }); }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findOne({ include: [this.Worker] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findOne({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('Worker is not associated to Task!'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [this.Task] - }).then(worker => { - expect(worker).to.exist; - expect(worker.Task).to.exist; - expect(worker.Task.title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.Task).to.exist; + expect(worker.Task.title).to.equal('homework'); }); - it('eager loads with non-id primary keys', function() { + it('eager loads with non-id primary keys', async function() { this.User = this.sequelize.define('UserPKeagerone', { username: { type: Sequelize.STRING, @@ -512,69 +498,73 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); this.Group.hasOne(this.User); - return this.sequelize.sync({ force: true }).then(() => { - return this.Group.create({ name: 'people' }).then(() => { - return this.User.create({ username: 'someone', GroupPKeageroneName: 'people' }).then(() => { - return this.Group.findOne({ - where: { - name: 'people' - }, - include: [this.User] - }).then(someGroup => { - expect(someGroup).to.exist; - expect(someGroup.name).to.equal('people'); - expect(someGroup.UserPKeagerone.username).to.equal('someone'); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await this.Group.create({ name: 'people' }); + await this.User.create({ username: 'someone', GroupPKeageroneName: 'people' }); + + const someGroup = await this.Group.findOne({ + where: { + name: 'people' + }, + include: [this.User] }); + + expect(someGroup).to.exist; + expect(someGroup.name).to.equal('people'); + expect(someGroup.UserPKeagerone.username).to.equal('someone'); }); }); describe('hasOne with alias', () => { - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findOne({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findOne({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is not associated to Worker!'); - }); + } }); describe('alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasOne(this.Task, { as: 'ToDo' }); - return this.init(() => { + + await this.init(() => { return this.worker.setToDo(this.task); }); }); - it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', function() { - return this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', async function() { + try { + await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDo).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(worker => { - expect(worker).to.exist; - expect(worker.ToDo).to.exist; - expect(worker.ToDo.title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.ToDo).to.exist; + expect(worker.ToDo.title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDo' }] - }).then(worker => { - expect(worker.ToDo.title).to.equal('homework'); }); + + expect(worker.ToDo.title).to.equal('homework'); }); - it('allows mulitple assocations of the same model with different alias', function() { + it('allows mulitple assocations of the same model with different alias', async function() { this.Worker.hasOne(this.Task, { as: 'DoTo' }); - return this.init(() => { + + await this.init(() => { return this.Worker.findOne({ include: [ { model: this.Task, as: 'ToDo' }, @@ -587,31 +577,34 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('hasMany', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasMany(this.Task); - return this.init(() => { + + await this.init(() => { return this.worker.setTasks([this.task]); }); }); - it('throws an error if included DaoFactory is not associated', function() { - return this.Task.findOne({ include: [this.Worker] }).catch(err => { + it('throws an error if included DaoFactory is not associated', async function() { + try { + await this.Task.findOne({ include: [this.Worker] }); + } catch (err) { expect(err.message).to.equal('Worker is not associated to Task!'); - }); + } }); - it('returns the associated tasks via worker.tasks', function() { - return this.Worker.findOne({ + it('returns the associated tasks via worker.tasks', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [this.Task] - }).then(worker => { - expect(worker).to.exist; - expect(worker.Tasks).to.exist; - expect(worker.Tasks[0].title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.Tasks).to.exist; + expect(worker.Tasks[0].title).to.equal('homework'); }); - it('including two has many relations should not result in duplicate values', function() { + it('including two has many relations should not result in duplicate values', async function() { this.Contact = this.sequelize.define('Contact', { name: DataTypes.STRING }); this.Photo = this.sequelize.define('Photo', { img: DataTypes.TEXT }); this.PhoneNumber = this.sequelize.define('PhoneNumber', { phone: DataTypes.TEXT }); @@ -619,33 +612,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Contact.hasMany(this.Photo, { as: 'Photos' }); this.Contact.hasMany(this.PhoneNumber); - return this.sequelize.sync({ force: true }).then(() => { - return this.Contact.create({ name: 'Boris' }).then(someContact => { - return this.Photo.create({ img: 'img.jpg' }).then(somePhoto => { - return this.PhoneNumber.create({ phone: '000000' }).then(somePhone1 => { - return this.PhoneNumber.create({ phone: '111111' }).then(somePhone2 => { - return someContact.setPhotos([somePhoto]).then(() => { - return someContact.setPhoneNumbers([somePhone1, somePhone2]).then(() => { - return this.Contact.findOne({ - where: { - name: 'Boris' - }, - include: [this.PhoneNumber, { model: this.Photo, as: 'Photos' }] - }).then(fetchedContact => { - expect(fetchedContact).to.exist; - expect(fetchedContact.Photos.length).to.equal(1); - expect(fetchedContact.PhoneNumbers.length).to.equal(2); - }); - }); - }); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const someContact = await this.Contact.create({ name: 'Boris' }); + const somePhoto = await this.Photo.create({ img: 'img.jpg' }); + const somePhone1 = await this.PhoneNumber.create({ phone: '000000' }); + const somePhone2 = await this.PhoneNumber.create({ phone: '111111' }); + await someContact.setPhotos([somePhoto]); + await someContact.setPhoneNumbers([somePhone1, somePhone2]); + + const fetchedContact = await this.Contact.findOne({ + where: { + name: 'Boris' + }, + include: [this.PhoneNumber, { model: this.Photo, as: 'Photos' }] }); + + expect(fetchedContact).to.exist; + expect(fetchedContact.Photos.length).to.equal(1); + expect(fetchedContact.PhoneNumbers.length).to.equal(2); }); - it('eager loads with non-id primary keys', function() { + it('eager loads with non-id primary keys', async function() { this.User = this.sequelize.define('UserPKeagerone', { username: { type: Sequelize.STRING, @@ -661,71 +648,74 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Group.belongsToMany(this.User, { through: 'group_user' }); this.User.belongsToMany(this.Group, { through: 'group_user' }); - return this.sequelize.sync({ force: true }).then(() => { - return this.User.create({ username: 'someone' }).then(someUser => { - return this.Group.create({ name: 'people' }).then(someGroup => { - return someUser.setGroupPKeagerones([someGroup]).then(() => { - return this.User.findOne({ - where: { - username: 'someone' - }, - include: [this.Group] - }).then(someUser => { - expect(someUser).to.exist; - expect(someUser.username).to.equal('someone'); - expect(someUser.GroupPKeagerones[0].name).to.equal('people'); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const someUser = await this.User.create({ username: 'someone' }); + const someGroup = await this.Group.create({ name: 'people' }); + await someUser.setGroupPKeagerones([someGroup]); + + const someUser0 = await this.User.findOne({ + where: { + username: 'someone' + }, + include: [this.Group] }); + + expect(someUser0).to.exist; + expect(someUser0.username).to.equal('someone'); + expect(someUser0.GroupPKeagerones[0].name).to.equal('people'); }); }); describe('hasMany with alias', () => { - it('throws an error if included DaoFactory is not referenced by alias', function() { - return this.Worker.findOne({ include: [this.Task] }).catch(err => { + it('throws an error if included DaoFactory is not referenced by alias', async function() { + try { + await this.Worker.findOne({ include: [this.Task] }); + } catch (err) { expect(err.message).to.equal('Task is not associated to Worker!'); - }); + } }); describe('alias', () => { - beforeEach(function() { + beforeEach(async function() { this.Worker.hasMany(this.Task, { as: 'ToDos' }); - return this.init(() => { + + await this.init(() => { return this.worker.setToDos([this.task]); }); }); - it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', function() { - return this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }).catch(err => { + it('throws an error indicating an incorrect alias was entered if an association and alias exist but the alias doesn\'t match', async function() { + try { + await this.Worker.findOne({ include: [{ model: this.Task, as: 'Work' }] }); + } catch (err) { expect(err.message).to.equal('Task is associated to Worker using an alias. You\'ve included an alias (Work), but it does not match the alias(es) defined in your association (ToDos).'); - }); + } }); - it('returns the associated task via worker.task', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(worker => { - expect(worker).to.exist; - expect(worker.ToDos).to.exist; - expect(worker.ToDos[0].title).to.equal('homework'); }); + + expect(worker).to.exist; + expect(worker.ToDos).to.exist; + expect(worker.ToDos[0].title).to.equal('homework'); }); - it('returns the associated task via worker.task when daoFactory is aliased with model', function() { - return this.Worker.findOne({ + it('returns the associated task via worker.task when daoFactory is aliased with model', async function() { + const worker = await this.Worker.findOne({ where: { name: 'worker' }, include: [{ model: this.Task, as: 'ToDos' }] - }).then(worker => { - expect(worker.ToDos[0].title).to.equal('homework'); }); + + expect(worker.ToDos[0].title).to.equal('homework'); }); - it('allows mulitple assocations of the same model with different alias', function() { + it('allows mulitple assocations of the same model with different alias', async function() { this.Worker.hasMany(this.Task, { as: 'DoTos' }); - return this.init(() => { + + await this.init(() => { return this.Worker.findOne({ include: [ { model: this.Task, as: 'ToDos' }, @@ -743,179 +733,180 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Tag = this.sequelize.define('Tag', { name: Sequelize.STRING }); }); - it('returns the associated models when using through as string and alias', function() { + it('returns the associated models when using through as string and alias', async function() { this.Product.belongsToMany(this.Tag, { as: 'tags', through: 'product_tag' }); this.Tag.belongsToMany(this.Product, { as: 'products', through: 'product_tag' }); - return this.sequelize.sync().then(() => { - return Promise.all([ - this.Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Handbag' }, - { title: 'Dress' }, - { title: 'Jan' } - ]), - this.Tag.bulkCreate([ - { name: 'Furniture' }, - { name: 'Clothing' }, - { name: 'People' } - ]) - ]).then(() => { - return Promise.all([ - this.Product.findAll(), - this.Tag.findAll() - ]); - }).then(([products, tags]) => { - this.products = products; - this.tags = tags; - return Promise.all([ - products[0].setTags([tags[0], tags[1]]), - products[1].addTag(tags[0]), - products[2].addTag(tags[1]), - products[3].setTags([tags[1]]), - products[4].setTags([tags[2]]) - ]).then(() => { - return Promise.all([ - this.Tag.findOne({ - where: { - id: tags[0].id - }, - include: [ - { model: this.Product, as: 'products' } - ] - }).then(tag => { - expect(tag).to.exist; - expect(tag.products.length).to.equal(2); - }), - tags[1].getProducts().then(products => { - expect(products.length).to.equal(3); - }), - this.Product.findOne({ - where: { - id: products[0].id - }, - include: [ - { model: this.Tag, as: 'tags' } - ] - }).then(product => { - expect(product).to.exist; - expect(product.tags.length).to.equal(2); - }), - products[1].getTags().then(tags => { - expect(tags.length).to.equal(1); - }) - ]); - }); - }); - }); - }); - - it('returns the associated models when using through as model and alias', function() { - // Exactly the same code as the previous test, just with a through model instance, and promisified - const ProductTag = this.sequelize.define('product_tag'); - - this.Product.belongsToMany(this.Tag, { as: 'tags', through: ProductTag }); - this.Tag.belongsToMany(this.Product, { as: 'products', through: ProductTag }); - - return this.sequelize.sync().then(() => { - return Promise.all([ - this.Product.bulkCreate([ - { title: 'Chair' }, - { title: 'Desk' }, - { title: 'Handbag' }, - { title: 'Dress' }, - { title: 'Jan' } - ]), - this.Tag.bulkCreate([ - { name: 'Furniture' }, - { name: 'Clothing' }, - { name: 'People' } - ]) - ]); - }).then(() => { - return Promise.all([ - this.Product.findAll(), - this.Tag.findAll() - ]); - }).then(([products, tags]) => { - this.products = products; - this.tags = tags; - - return Promise.all([ - products[0].setTags([tags[0], tags[1]]), - products[1].addTag(tags[0]), - products[2].addTag(tags[1]), - products[3].setTags([tags[1]]), - products[4].setTags([tags[2]]) - ]); - }).then(() => { - return Promise.all([ - expect(this.Tag.findOne({ + await this.sequelize.sync(); + + await Promise.all([ + this.Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Handbag' }, + { title: 'Dress' }, + { title: 'Jan' } + ]), + this.Tag.bulkCreate([ + { name: 'Furniture' }, + { name: 'Clothing' }, + { name: 'People' } + ]) + ]); + + const [products, tags] = await Promise.all([ + this.Product.findAll(), + this.Tag.findAll() + ]); + + this.products = products; + this.tags = tags; + + await Promise.all([ + products[0].setTags([tags[0], tags[1]]), + products[1].addTag(tags[0]), + products[2].addTag(tags[1]), + products[3].setTags([tags[1]]), + products[4].setTags([tags[2]]) + ]); + + await Promise.all([ + (async () => { + const tag = await this.Tag.findOne({ where: { - id: this.tags[0].id + id: tags[0].id }, include: [ { model: this.Product, as: 'products' } ] - })).to.eventually.have.property('products').to.have.length(2), - expect(this.Product.findOne({ + }); + + expect(tag).to.exist; + expect(tag.products.length).to.equal(2); + })(), + tags[1].getProducts().then(products => { + expect(products.length).to.equal(3); + }), + (async () => { + const product = await this.Product.findOne({ where: { - id: this.products[0].id + id: products[0].id }, include: [ { model: this.Tag, as: 'tags' } ] - })).to.eventually.have.property('tags').to.have.length(2), - expect(this.tags[1].getProducts()).to.eventually.have.length(3), - expect(this.products[1].getTags()).to.eventually.have.length(1) - ]); - }); + }); + + expect(product).to.exist; + expect(product.tags.length).to.equal(2); + })(), + products[1].getTags().then(tags => { + expect(tags.length).to.equal(1); + }) + ]); + }); + + it('returns the associated models when using through as model and alias', async function() { + // Exactly the same code as the previous test, just with a through model instance, and promisified + const ProductTag = this.sequelize.define('product_tag'); + + this.Product.belongsToMany(this.Tag, { as: 'tags', through: ProductTag }); + this.Tag.belongsToMany(this.Product, { as: 'products', through: ProductTag }); + + await this.sequelize.sync(); + + await Promise.all([ + this.Product.bulkCreate([ + { title: 'Chair' }, + { title: 'Desk' }, + { title: 'Handbag' }, + { title: 'Dress' }, + { title: 'Jan' } + ]), + this.Tag.bulkCreate([ + { name: 'Furniture' }, + { name: 'Clothing' }, + { name: 'People' } + ]) + ]); + + const [products, tags] = await Promise.all([ + this.Product.findAll(), + this.Tag.findAll() + ]); + + this.products = products; + this.tags = tags; + + await Promise.all([ + products[0].setTags([tags[0], tags[1]]), + products[1].addTag(tags[0]), + products[2].addTag(tags[1]), + products[3].setTags([tags[1]]), + products[4].setTags([tags[2]]) + ]); + + await Promise.all([ + expect(this.Tag.findOne({ + where: { + id: this.tags[0].id + }, + include: [ + { model: this.Product, as: 'products' } + ] + })).to.eventually.have.property('products').to.have.length(2), + expect(this.Product.findOne({ + where: { + id: this.products[0].id + }, + include: [ + { model: this.Tag, as: 'tags' } + ] + })).to.eventually.have.property('tags').to.have.length(2), + expect(this.tags[1].getProducts()).to.eventually.have.length(3), + expect(this.products[1].getTags()).to.eventually.have.length(1) + ]); }); }); }); describe('queryOptions', () => { - beforeEach(function() { - return this.User.create({ username: 'barfooz' }).then(user => { - this.user = user; - }); + beforeEach(async function() { + const user = await this.User.create({ username: 'barfooz' }); + this.user = user; }); - it('should return a DAO when queryOptions are not set', function() { - return this.User.findOne({ where: { username: 'barfooz' } }).then(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when queryOptions are not set', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' } }); + expect(user).to.be.instanceOf(this.User); }); - it('should return a DAO when raw is false', function() { - return this.User.findOne({ where: { username: 'barfooz' }, raw: false }).then(user => { - expect(user).to.be.instanceOf(this.User); - }); + it('should return a DAO when raw is false', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: false }); + expect(user).to.be.instanceOf(this.User); }); - it('should return raw data when raw is true', function() { - return this.User.findOne({ where: { username: 'barfooz' }, raw: true }).then(user => { - expect(user).to.not.be.instanceOf(this.User); - expect(user).to.be.instanceOf(Object); - }); + it('should return raw data when raw is true', async function() { + const user = await this.User.findOne({ where: { username: 'barfooz' }, raw: true }); + expect(user).to.not.be.instanceOf(this.User); + expect(user).to.be.instanceOf(Object); }); }); - it('should support logging', function() { + it('should support logging', async function() { const spy = sinon.spy(); - return this.User.findOne({ + await this.User.findOne({ where: {}, logging: spy - }).then(() => { - expect(spy.called).to.be.ok; }); + + expect(spy.called).to.be.ok; }); describe('rejectOnEmpty mode', () => { - it('throws error when record not found by findOne', function() { - return expect(this.User.findOne({ + it('throws error when record not found by findOne', async function() { + await expect(this.User.findOne({ where: { username: 'ath-kantam-pradakshnami' }, @@ -923,14 +914,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('throws error when record not found by findByPk', function() { - return expect(this.User.findByPk(4732322332323333232344334354234, { + it('throws error when record not found by findByPk', async function() { + await expect(this.User.findByPk(4732322332323333232344334354234, { rejectOnEmpty: true })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('throws error when record not found by find', function() { - return expect(this.User.findOne({ + it('throws error when record not found by find', async function() { + await expect(this.User.findOne({ where: { username: 'some-username-that-is-not-used-anywhere' }, @@ -938,54 +929,51 @@ describe(Support.getTestDialectTeaser('Model'), () => { })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('works from model options', () => { + it('works from model options', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: true }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findOne({ - where: { - username: 'some-username-that-is-not-used-anywhere' - } - })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); - }); + await Model.sync({ force: true }); + + await expect(Model.findOne({ + where: { + username: 'some-username-that-is-not-used-anywhere' + } + })).to.eventually.be.rejectedWith(Sequelize.EmptyResultError); }); - it('override model options', () => { + it('override model options', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }, { rejectOnEmpty: true }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findOne({ - rejectOnEmpty: false, - where: { - username: 'some-username-that-is-not-used-anywhere' - } - })).to.eventually.be.deep.equal(null); - }); + await Model.sync({ force: true }); + + await expect(Model.findOne({ + rejectOnEmpty: false, + where: { + username: 'some-username-that-is-not-used-anywhere' + } + })).to.eventually.be.deep.equal(null); }); - it('resolve null when disabled', () => { + it('resolve null when disabled', async () => { const Model = current.define('Test', { username: Sequelize.STRING(100) }); - return Model.sync({ force: true }) - .then(() => { - return expect(Model.findOne({ - where: { - username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' - } - })).to.eventually.be.equal(null); - }); + await Model.sync({ force: true }); + + await expect(Model.findOne({ + where: { + username: 'some-username-that-is-not-used-anywhere-for-sure-this-time' + } + })).to.eventually.be.equal(null); }); }); }); diff --git a/test/integration/model/findOrBuild.test.js b/test/integration/model/findOrBuild.test.js index 983a3647402f..25d9eff089a1 100644 --- a/test/integration/model/findOrBuild.test.js +++ b/test/integration/model/findOrBuild.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, age: DataTypes.INTEGER @@ -18,41 +18,43 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.User.hasMany(this.Project); this.Project.belongsTo(this.User); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('findOrBuild', () => { - it('initialize with includes', function() { - return this.User.bulkCreate([ + it('initialize with includes', async function() { + const [, user2] = await this.User.bulkCreate([ { username: 'Mello', age: 10 }, { username: 'Mello', age: 20 } - ], { returning: true }).then(([, user2]) => { - return this.Project.create({ - name: 'Investigate' - }).then(project => user2.setProjects([project])); - }).then(() => { - return this.User.findOrBuild({ - defaults: { - username: 'Mello', - age: 10 - }, - where: { - age: 20 - }, - include: [{ - model: this.Project - }] - }); - }).then(([user, created]) => { - expect(created).to.be.false; - expect(user.get('id')).to.be.ok; - expect(user.get('username')).to.equal('Mello'); - expect(user.get('age')).to.equal(20); - - expect(user.Projects).to.have.length(1); - expect(user.Projects[0].get('name')).to.equal('Investigate'); + ], { returning: true }); + + const project = await this.Project.create({ + name: 'Investigate' + }); + + await user2.setProjects([project]); + + const [user, created] = await this.User.findOrBuild({ + defaults: { + username: 'Mello', + age: 10 + }, + where: { + age: 20 + }, + include: [{ + model: this.Project + }] }); + + expect(created).to.be.false; + expect(user.get('id')).to.be.ok; + expect(user.get('username')).to.equal('Mello'); + expect(user.get('age')).to.equal(20); + + expect(user.Projects).to.have.length(1); + expect(user.Projects[0].get('name')).to.equal('Investigate'); }); }); }); diff --git a/test/integration/model/geography.test.js b/test/integration/model/geography.test.js index 52eebe1c96c7..168f8cfbc9a3 100644 --- a/test/integration/model/geography.test.js +++ b/test/integration/model/geography.test.js @@ -10,227 +10,333 @@ const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.GEOGRAPHY) { describe('GEOGRAPHY', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('works with aliases fields', function() { + it('works with aliases fields', async function() { const Pub = this.sequelize.define('Pub', { location: { field: 'coordinates', type: DataTypes.GEOGRAPHY } }), - point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - - return Pub.sync({ force: true }).then(() => { - return Pub.create({ location: point }); - }).then(pub => { - expect(pub).not.to.be.null; - expect(pub.location).to.be.deep.eql(point); - }); + point = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + await Pub.sync({ force: true }); + const pub = await Pub.create({ location: point }); + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; + const point = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; - const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, - point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; + const point1 = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + point2 = { + type: 'Point', coordinates: [49.807222, -86.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: point1 }; - return User.create(props).then(() => { - return User.update({ geography: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geography: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(point2); }); }); describe('GEOGRAPHY(POINT)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('POINT') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; - const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; + const point = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; - const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, - point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; + const point1 = { + type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + point2 = { + type: 'Point', coordinates: [49.807222, -86.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: point1 }; - return User.create(props).then(() => { - return User.update({ geography: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geography: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(point2); }); }); describe('GEOGRAPHY(LINESTRING)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('LINESTRING') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; - const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]] }; + const point = { + type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; - const point1 = { type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]] }, - point2 = { type: 'LineString', coordinates: [[101.0, 0.0], [102.0, 1.0]] }; + const point1 = { + type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + point2 = { + type: 'LineString', coordinates: [[101.0, 0.0], [102.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: point1 }; - return User.create(props).then(() => { - return User.update({ geography: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geography: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(point2); }); }); describe('GEOGRAPHY(POLYGON)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('POLYGON') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; - const point = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const point = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; - const polygon1 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] - ] }, - polygon2 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const polygon1 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + polygon2 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: polygon1 }; - return User.create(props).then(() => { - return User.update({ geography: polygon2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(polygon2); - }); + await User.create(props); + await User.update({ geography: polygon2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(polygon2); }); }); if (current.dialect.name === 'postgres') { describe('GEOGRAPHY(POLYGON, SRID)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geography: DataTypes.GEOGRAPHY('POLYGON', 4326) }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geography object', function() { + it('should create a geography object', async function() { const User = this.User; - const point = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; - - return User.create({ username: 'username', geography: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geography).to.be.deep.eql(point); - }); + const point = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + const newUser = await User.create({ username: 'username', geography: point }); + expect(newUser).not.to.be.null; + expect(newUser.geography).to.be.deep.eql(point); }); - it('should update a geography object', function() { + it('should update a geography object', async function() { const User = this.User; - const polygon1 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] - ] }, - polygon2 = { type: 'Polygon', coordinates: [ - [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], - [100.0, 1.0], [100.0, 0.0]] - ] }; + const polygon1 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }, + polygon2 = { + type: 'Polygon', coordinates: [ + [[100.0, 0.0], [102.0, 0.0], [102.0, 1.0], + [100.0, 1.0], [100.0, 0.0]] + ], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; const props = { username: 'username', geography: polygon1 }; - return User.create(props).then(() => { - return User.update({ geography: polygon2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geography).to.be.deep.eql(polygon2); - }); + await User.create(props); + await User.update({ geography: polygon2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geography).to.be.deep.eql(polygon2); }); }); } describe('sql injection attacks', () => { - beforeEach(function() { + beforeEach(async function() { this.Model = this.sequelize.define('Model', { location: DataTypes.GEOGRAPHY }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should properly escape the single quotes', function() { - return this.Model.create({ + it('should properly escape the single quotes', async function() { + await this.Model.create({ location: { type: 'Point', properties: { diff --git a/test/integration/model/geometry.test.js b/test/integration/model/geometry.test.js index 0925939f37ed..dea09c8d4ec1 100644 --- a/test/integration/model/geometry.test.js +++ b/test/integration/model/geometry.test.js @@ -12,151 +12,206 @@ const current = Support.sequelize; describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.GEOMETRY) { describe('GEOMETRY', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('works with aliases fields', function() { + it('works with aliases fields', async function() { const Pub = this.sequelize.define('Pub', { location: { field: 'coordinates', type: DataTypes.GEOMETRY } }), point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return Pub.sync({ force: true }).then(() => { - return Pub.create({ location: point }); - }).then(pub => { - expect(pub).not.to.be.null; - expect(pub.location).to.be.deep.eql(point); - }); + await Pub.sync({ force: true }); + const pub = await Pub.create({ location: point }); + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; const props = { username: 'username', geometry: point1 }; - return User.create(props).then(() => { - return User.update({ geometry: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geometry: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(point2); + }); + + it('works with crs field', async function() { + const Pub = this.sequelize.define('Pub', { + location: { field: 'coordinates', type: DataTypes.GEOMETRY } + }), + point = { type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + await Pub.sync({ force: true }); + const pub = await Pub.create({ location: point }); + expect(pub).not.to.be.null; + expect(pub.location).to.be.deep.eql(point); }); }); describe('GEOMETRY(POINT)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY('POINT') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'Point', coordinates: [39.807222, -76.984722] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const point1 = { type: 'Point', coordinates: [39.807222, -76.984722] }, point2 = { type: 'Point', coordinates: [49.807222, -86.984722] }; const props = { username: 'username', geometry: point1 }; - return User.create(props).then(() => { - return User.update({ geometry: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geometry: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(point2); + }); + + it('works with crs field', async function() { + const User = this.User; + const point = { type: 'Point', coordinates: [39.807222, -76.984722], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); }); describe('GEOMETRY(LINESTRING)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY('LINESTRING') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const point1 = { type: 'LineString', coordinates: [[100.0, 0.0], [101.0, 1.0]] }, point2 = { type: 'LineString', coordinates: [[101.0, 0.0], [102.0, 1.0]] }; const props = { username: 'username', geometry: point1 }; - return User.create(props).then(() => { - return User.update({ geometry: point2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(point2); - }); + await User.create(props); + await User.update({ geometry: point2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(point2); + }); + + it('works with crs field', async function() { + const User = this.User; + const point = { type: 'LineString', 'coordinates': [[100.0, 0.0], [101.0, 1.0]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); + }); describe('GEOMETRY(POLYGON)', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { username: DataTypes.STRING, geometry: DataTypes.GEOMETRY('POLYGON') }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('should create a geometry object', function() { + it('should create a geometry object', async function() { const User = this.User; const point = { type: 'Polygon', coordinates: [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] ] }; - return User.create({ username: 'username', geometry: point }).then(newUser => { - expect(newUser).not.to.be.null; - expect(newUser.geometry).to.be.deep.eql(point); - }); + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); + }); + + it('works with crs field', async function() { + const User = this.User; + const point = { type: 'Polygon', coordinates: [ + [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], + [100.0, 1.0], [100.0, 0.0]]], + crs: { + type: 'name', + properties: { + name: 'EPSG:4326' + } + } + }; + + const newUser = await User.create({ username: 'username', geometry: point }); + expect(newUser).not.to.be.null; + expect(newUser.geometry).to.be.deep.eql(point); }); - it('should update a geometry object', function() { + it('should update a geometry object', async function() { const User = this.User; const polygon1 = { type: 'Polygon', coordinates: [ [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]] @@ -167,26 +222,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }; const props = { username: 'username', geometry: polygon1 }; - return User.create(props).then(() => { - return User.update({ geometry: polygon2 }, { where: { username: props.username } }); - }).then(() => { - return User.findOne({ where: { username: props.username } }); - }).then(user => { - expect(user.geometry).to.be.deep.eql(polygon2); - }); + await User.create(props); + await User.update({ geometry: polygon2 }, { where: { username: props.username } }); + const user = await User.findOne({ where: { username: props.username } }); + expect(user.geometry).to.be.deep.eql(polygon2); }); }); describe('sql injection attacks', () => { - beforeEach(function() { + beforeEach(async function() { this.Model = this.sequelize.define('Model', { location: DataTypes.GEOMETRY }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should properly escape the single quotes', function() { - return this.Model.create({ + it('should properly escape the single quotes', async function() { + await this.Model.create({ location: { type: 'Point', properties: { @@ -197,14 +249,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should properly escape the single quotes in coordinates', function() { - + it('should properly escape the single quotes in coordinates', async function() { // MySQL 5.7, those guys finally fixed this if (dialect === 'mysql' && semver.gte(this.sequelize.options.databaseVersion, '5.7.0')) { return; } - return this.Model.create({ + await this.Model.create({ location: { type: 'Point', properties: { diff --git a/test/integration/model/increment.test.js b/test/integration/model/increment.test.js index e2cbd10c24da..dd52ac549e51 100644 --- a/test/integration/model/increment.test.js +++ b/test/integration/model/increment.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = Support.Sequelize, DataTypes = require('../../../lib/data-types'), sinon = require('sinon'); @@ -16,7 +15,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.restore(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true }, aNumber: { type: DataTypes.INTEGER }, @@ -24,26 +23,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { cNumber: { type: DataTypes.INTEGER, field: 'c_number' } }); - return this.User.sync({ force: true }).then(() => { - return this.User.bulkCreate([{ - id: 1, - aNumber: 0, - bNumber: 0 - }, { - id: 2, - aNumber: 0, - bNumber: 0 - }, { - id: 3, - aNumber: 0, - bNumber: 0 - }, { - id: 4, - aNumber: 0, - bNumber: 0, - cNumber: 0 - }]); - }); + await this.User.sync({ force: true }); + + await this.User.bulkCreate([{ + id: 1, + aNumber: 0, + bNumber: 0 + }, { + id: 2, + aNumber: 0, + bNumber: 0 + }, { + id: 3, + aNumber: 0, + bNumber: 0 + }, { + id: 4, + aNumber: 0, + bNumber: 0, + cNumber: 0 + }]); }); [ @@ -53,144 +52,116 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe(method, () => { before(function() { this.assert = (increment, decrement) => { - return method === 'increment' ? increment : decrement; + return method === 'increment' ? increment : decrement; }; }); - it('supports where conditions', function() { - return this.User.findByPk(1).then(() => { - return this.User[method](['aNumber'], { by: 2, where: { id: 1 } }).then(() => { - return this.User.findByPk(2).then(user3 => { - expect(user3.aNumber).to.be.equal(this.assert(0, 0)); - }); - }); - }); + it('supports where conditions', async function() { + await this.User.findByPk(1); + await this.User[method](['aNumber'], { by: 2, where: { id: 1 } }); + const user3 = await this.User.findByPk(2); + expect(user3.aNumber).to.be.equal(this.assert(0, 0)); }); - it('uses correct column names for where conditions', function() { - return this.User[method](['aNumber'], { by: 2, where: { cNumber: 0 } }).then(() => { - return this.User.findByPk(4).then(user4 => { - expect(user4.aNumber).to.be.equal(this.assert(2, -2)); - }); - }); + it('uses correct column names for where conditions', async function() { + await this.User[method](['aNumber'], { by: 2, where: { cNumber: 0 } }); + const user4 = await this.User.findByPk(4); + expect(user4.aNumber).to.be.equal(this.assert(2, -2)); }); - it('should still work right with other concurrent increments', function() { - return this.User.findAll().then(aUsers => { - return Sequelize.Promise.all([ - this.User[method](['aNumber'], { by: 2, where: {} }), - this.User[method](['aNumber'], { by: 2, where: {} }), - this.User[method](['aNumber'], { by: 2, where: {} }) - ]).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 6, aUsers[i].aNumber - 6)); - } - }); - }); - }); + it('should still work right with other concurrent increments', async function() { + const aUsers = await this.User.findAll(); + + await Promise.all([ + this.User[method](['aNumber'], { by: 2, where: {} }), + this.User[method](['aNumber'], { by: 2, where: {} }), + this.User[method](['aNumber'], { by: 2, where: {} }) + ]); + + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 6, aUsers[i].aNumber - 6)); + } }); - it('with array', function() { - return this.User.findAll().then(aUsers => { - return this.User[method](['aNumber'], { by: 2, where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); - } - }); - }); - }); + it('with array', async function() { + const aUsers = await this.User.findAll(); + await this.User[method](['aNumber'], { by: 2, where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); + } }); - it('with single field', function() { - return this.User.findAll().then(aUsers => { - return this.User[method]('aNumber', { by: 2, where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); - } - }); - }); - }); + it('with single field', async function() { + const aUsers = await this.User.findAll(); + await this.User[method]('aNumber', { by: 2, where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 2, aUsers[i].aNumber - 2)); + } }); - it('with single field and no value', function() { - return this.User.findAll().then(aUsers => { - return this.User[method]('aNumber', { where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); - } - }); - }); - }); + it('with single field and no value', async function() { + const aUsers = await this.User.findAll(); + await this.User[method]('aNumber', { where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); + } }); - it('with key value pair', function() { - return this.User.findAll().then(aUsers => { - return this.User[method]({ 'aNumber': 1, 'bNumber': 2 }, { where: { } }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); - expect(bUsers[i].bNumber).to.equal(this.assert(aUsers[i].bNumber + 2, aUsers[i].bNumber - 2)); - } - }); - }); - }); + it('with key value pair', async function() { + const aUsers = await this.User.findAll(); + await this.User[method]({ 'aNumber': 1, 'bNumber': 2 }, { where: { } }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 1, aUsers[i].aNumber - 1)); + expect(bUsers[i].bNumber).to.equal(this.assert(aUsers[i].bNumber + 2, aUsers[i].bNumber - 2)); + } }); - it('should still work right with other concurrent updates', function() { - return this.User.findAll().then(aUsers => { - return this.User.update({ 'aNumber': 2 }, { where: {} }).then(() => { - return this.User[method](['aNumber'], { by: 2, where: {} }).then(() => { - return this.User.findAll().then(bUsers => { - for (let i = 0; i < bUsers.length; i++) { - // for decrement 2 - 2 = 0 - expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 4, aUsers[i].aNumber)); - } - }); - }); - }); - }); + it('should still work right with other concurrent updates', async function() { + const aUsers = await this.User.findAll(); + await this.User.update({ 'aNumber': 2 }, { where: {} }); + await this.User[method](['aNumber'], { by: 2, where: {} }); + const bUsers = await this.User.findAll(); + for (let i = 0; i < bUsers.length; i++) { + // for decrement 2 - 2 = 0 + expect(bUsers[i].aNumber).to.equal(this.assert(aUsers[i].aNumber + 4, aUsers[i].aNumber)); + } }); - it('with timestamps set to true', function() { + it('with timestamps set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; - this.clock.tick(1000); - return User[method]('aNumber', { by: 1, where: {} }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); - }); + this.clock.tick(1000); + await User[method]('aNumber', { by: 1, where: {} }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').afterTime(oldDate); }); - it('with timestamps set to true and options.silent set to true', function() { + it('with timestamps set to true and options.silent set to true', async function() { const User = this.sequelize.define('IncrementUser', { aNumber: DataTypes.INTEGER }, { timestamps: true }); - let oldDate; - - return User.sync({ force: true }).then(() => { - return User.create({ aNumber: 1 }); - }).then(user => { - oldDate = user.updatedAt; - this.clock.tick(1000); - return User[method]('aNumber', { by: 1, silent: true, where: { } }); - }).then(() => { - return expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); - }); + + await User.sync({ force: true }); + const user = await User.create({ aNumber: 1 }); + const oldDate = user.updatedAt; + this.clock.tick(1000); + await User[method]('aNumber', { by: 1, silent: true, where: { } }); + + await expect(User.findByPk(1)).to.eventually.have.property('updatedAt').equalTime(oldDate); }); - it('should work with scopes', function() { + it('should work with scopes', async function() { const User = this.sequelize.define('User', { aNumber: DataTypes.INTEGER, name: DataTypes.STRING @@ -204,33 +175,60 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.bulkCreate([ - { - aNumber: 1, - name: 'Jeff' - }, - { - aNumber: 3, - name: 'Not Jeff' - } - ]); - }).then(() => { - return User.scope('jeff')[method]('aNumber', {}); - }).then(() => { - return User.scope('jeff').findOne(); - }).then(jeff => { - expect(jeff.aNumber).to.equal(this.assert(2, 0)); - }).then(() => { - return User.findOne({ - where: { - name: 'Not Jeff' - } - }); - }).then(notJeff => { - expect(notJeff.aNumber).to.equal(this.assert(3, 3)); + await User.sync({ force: true }); + + await User.bulkCreate([ + { + aNumber: 1, + name: 'Jeff' + }, + { + aNumber: 3, + name: 'Not Jeff' + } + ]); + + await User.scope('jeff')[method]('aNumber', {}); + const jeff = await User.scope('jeff').findOne(); + expect(jeff.aNumber).to.equal(this.assert(2, 0)); + + const notJeff = await User.findOne({ + where: { + name: 'Not Jeff' + } }); + + expect(notJeff.aNumber).to.equal(this.assert(3, 3)); }); + + it('should not care for attributes in the instance scope', async function() { + this.User.addScope('test', { + attributes: ['foo', 'bar'] + }); + const createdUser = await this.User.scope('test').create({ id: 5, aNumber: 5 }); + await createdUser[method]('aNumber', { by: 2 }); + const user = await this.User.findByPk(5); + expect(user.aNumber).to.equal(this.assert(7, 3)); + }); + it('should not care for exclude-attributes in the instance scope', async function() { + this.User.addScope('test', { + attributes: { exclude: ['foo', 'bar'] } + }); + const createdUser = await this.User.scope('test').create({ id: 5, aNumber: 5 }); + await createdUser[method]('aNumber', { by: 2 }); + const user = await this.User.findByPk(5); + expect(user.aNumber).to.equal(this.assert(7, 3)); + }); + it('should not care for include-attributes in the instance scope', async function() { + this.User.addScope('test', { + attributes: { include: ['foo', 'bar'] } + }); + const createdUser = await this.User.scope('test').create({ id: 5, aNumber: 5 }); + await createdUser[method]('aNumber', { by: 2 }); + const user = await this.User.findByPk(5); + expect(user.aNumber).to.equal(this.assert(7, 3)); + }); + }); }); }); diff --git a/test/integration/model/json.test.js b/test/integration/model/json.test.js index e1fa8ef9cd96..d4828e72f2f3 100644 --- a/test/integration/model/json.test.js +++ b/test/integration/model/json.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), Sequelize = require('../../../index'), Op = Sequelize.Op, - Promise = Sequelize.Promise, moment = require('moment'), expect = chai.expect, Support = require('../support'), @@ -13,7 +12,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { if (current.dialect.supports.JSON) { describe('JSON', () => { - beforeEach(function() { + beforeEach(async function() { this.Event = this.sequelize.define('Event', { data: { type: DataTypes.JSON, @@ -23,44 +22,41 @@ describe(Support.getTestDialectTeaser('Model'), () => { json: DataTypes.JSON }); - return this.Event.sync({ force: true }); + await this.Event.sync({ force: true }); }); if (current.dialect.supports.lock) { - it('findOrCreate supports transactions, json and locks', function() { - return current.transaction().then(transaction => { - return this.Event.findOrCreate({ - where: { - json: { some: { input: 'Hello' } } - }, - defaults: { - json: { some: { input: 'Hello' }, input: [1, 2, 3] }, - data: { some: { input: 'There' }, input: [4, 5, 6] } - }, - transaction, - lock: transaction.LOCK.UPDATE, - logging: sql => { - if (sql.includes('SELECT') && !sql.includes('CREATE')) { - expect(sql.includes('FOR UPDATE')).to.be.true; - } + it('findOrCreate supports transactions, json and locks', async function() { + const transaction = await current.transaction(); + + await this.Event.findOrCreate({ + where: { + json: { some: { input: 'Hello' } } + }, + defaults: { + json: { some: { input: 'Hello' }, input: [1, 2, 3] }, + data: { some: { input: 'There' }, input: [4, 5, 6] } + }, + transaction, + lock: transaction.LOCK.UPDATE, + logging: sql => { + if (sql.includes('SELECT') && !sql.includes('CREATE')) { + expect(sql.includes('FOR UPDATE')).to.be.true; } - }).then(() => { - return this.Event.count().then(count => { - expect(count).to.equal(0); - return transaction.commit().then(() => { - return this.Event.count().then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); + } }); + + const count = await this.Event.count(); + expect(count).to.equal(0); + await transaction.commit(); + const count0 = await this.Event.count(); + expect(count0).to.equal(1); }); } describe('create', () => { - it('should create an instance with JSON data', function() { - return this.Event.create({ + it('should create an instance with JSON data', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -68,25 +64,24 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return this.Event.findAll().then(events => { - const event = events[0]; + }); - expect(event.get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); - }); + const events = await this.Event.findAll(); + const event = events[0]; + + expect(event.get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' }); }); }); describe('update', () => { - it('should update with JSON column (dot notation)', function() { - return this.Event.bulkCreate([{ + it('should update with JSON column (dot notation)', async function() { + await this.Event.bulkCreate([{ id: 1, data: { name: { @@ -104,7 +99,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Multiverse Scientist' } - }]).then(() => this.Event.update({ + }]); + + await this.Event.update({ 'data': { name: { first: 'Rick', @@ -116,19 +113,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { where: { 'data.name.first': 'Rick' } - })).then(() => this.Event.findByPk(2)).then(event => { - expect(event.get('data')).to.eql({ - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Galactic Fed Prisioner' - }); + }); + + const event = await this.Event.findByPk(2); + expect(event.get('data')).to.eql({ + name: { + first: 'Rick', + last: 'Sanchez' + }, + employment: 'Galactic Fed Prisioner' }); }); - it('should update with JSON column (JSON notation)', function() { - return this.Event.bulkCreate([{ + it('should update with JSON column (JSON notation)', async function() { + await this.Event.bulkCreate([{ id: 1, data: { name: { @@ -146,7 +144,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Multiverse Scientist' } - }]).then(() => this.Event.update({ + }]); + + await this.Event.update({ 'data': { name: { first: 'Rick', @@ -162,19 +162,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } } - })).then(() => this.Event.findByPk(2)).then(event => { - expect(event.get('data')).to.eql({ - name: { - first: 'Rick', - last: 'Sanchez' - }, - employment: 'Galactic Fed Prisioner' - }); + }); + + const event = await this.Event.findByPk(2); + expect(event.get('data')).to.eql({ + name: { + first: 'Rick', + last: 'Sanchez' + }, + employment: 'Galactic Fed Prisioner' }); }); - it('should update an instance with JSON data', function() { - return this.Event.create({ + it('should update an instance with JSON data', async function() { + const event0 = await this.Event.create({ data: { name: { first: 'Homer', @@ -182,304 +183,288 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(event => { - return event.update({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: null - } - }); - }).then(() => { - return this.Event.findAll().then(events => { - const event = events[0]; + }); - expect(event.get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: null - }); - }); + await event0.update({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: null + } + }); + + const events = await this.Event.findAll(); + const event = events[0]; + + expect(event.get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: null }); }); }); describe('find', () => { - it('should be possible to query a nested value', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }), - this.Event.create({ + it('should be possible to query a nested value', async function() { + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + })]); + + const events = await this.Event.findAll({ + where: { data: { - name: { - first: 'Marge', - last: 'Simpson' - }, employment: 'Housewife' } - }) - ).then(() => { - return this.Event.findAll({ - where: { - data: { - employment: 'Housewife' - } - } - }).then(events => { - const event = events[0]; + } + }); - expect(events.length).to.equal(1); - expect(event.get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - }); - }); + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' }); }); - it('should be possible to query dates with array operators', function() { + it('should be possible to query dates with array operators', async function() { const now = moment().milliseconds(0).toDate(); const before = moment().milliseconds(0).subtract(1, 'day').toDate(); const after = moment().milliseconds(0).add(1, 'day').toDate(); - return Promise.join( - this.Event.create({ + + await Promise.all([this.Event.create({ + json: { + user: 'Homer', + lastLogin: now + } + })]); + + const events0 = await this.Event.findAll({ + where: { json: { - user: 'Homer', lastLogin: now } - }) - ).then(() => { - return this.Event.findAll({ - where: { - json: { - lastLogin: now - } - } - }).then(events => { - const event = events[0]; - - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - lastLogin: now.toISOString() - }); - }); - }).then(() => { - return this.Event.findAll({ - where: { - json: { - lastLogin: { [Op.between]: [before, after] } - } + } + }); + + const event0 = events0[0]; + + expect(events0.length).to.equal(1); + expect(event0.get('json')).to.eql({ + user: 'Homer', + lastLogin: now.toISOString() + }); + + const events = await this.Event.findAll({ + where: { + json: { + lastLogin: { [Op.between]: [before, after] } } - }).then(events => { - const event = events[0]; - - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - lastLogin: now.toISOString() - }); - }); + } + }); + + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('json')).to.eql({ + user: 'Homer', + lastLogin: now.toISOString() }); }); - it('should be possible to query a boolean with array operators', function() { - return Promise.join( - this.Event.create({ + it('should be possible to query a boolean with array operators', async function() { + await Promise.all([this.Event.create({ + json: { + user: 'Homer', + active: true + } + })]); + + const events0 = await this.Event.findAll({ + where: { json: { - user: 'Homer', active: true } - }) - ).then(() => { - return this.Event.findAll({ - where: { - json: { - active: true - } - } - }).then(events => { - const event = events[0]; + } + }); - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - active: true - }); - }); - }).then(() => { - return this.Event.findAll({ - where: { - json: { - active: { [Op.in]: [true, false] } - } + const event0 = events0[0]; + + expect(events0.length).to.equal(1); + expect(event0.get('json')).to.eql({ + user: 'Homer', + active: true + }); + + const events = await this.Event.findAll({ + where: { + json: { + active: { [Op.in]: [true, false] } } - }).then(events => { - const event = events[0]; + } + }); - expect(events.length).to.equal(1); - expect(event.get('json')).to.eql({ - user: 'Homer', - active: true - }); - }); + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('json')).to.eql({ + user: 'Homer', + active: true }); }); - it('should be possible to query a nested integer value', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - age: 40 - } - }), - this.Event.create({ + it('should be possible to query a nested integer value', async function() { + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + age: 40 + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + age: 37 + } + })]); + + const events = await this.Event.findAll({ + where: { data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - age: 37 - } - }) - ).then(() => { - return this.Event.findAll({ - where: { - data: { - age: { - [Op.gt]: 38 - } + age: { + [Op.gt]: 38 } } - }).then(events => { - const event = events[0]; + } + }); - expect(events.length).to.equal(1); - expect(event.get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - age: 40 - }); - }); + const event = events[0]; + + expect(events.length).to.equal(1); + expect(event.get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + age: 40 }); }); - it('should be possible to query a nested null value', function() { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - } - }), - this.Event.create({ + it('should be possible to query a nested null value', async function() { + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: null + } + })]); + + const events = await this.Event.findAll({ + where: { data: { - name: { - first: 'Marge', - last: 'Simpson' - }, employment: null } - }) - ).then(() => { - return this.Event.findAll({ - where: { - data: { - employment: null - } - } - }).then(events => { - expect(events.length).to.equal(1); - expect(events[0].get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: null - }); - }); + } + }); + + expect(events.length).to.equal(1); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: null }); }); - it('should be possible to query for nested fields with hyphens/dashes, #8718', function() { - return Promise.join( - this.Event.create({ + it('should be possible to query for nested fields with hyphens/dashes, #8718', async function() { + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Homer', + last: 'Simpson' + }, + status_report: { + 'red-indicator': { + 'level$$level': true + } + }, + employment: 'Nuclear Safety Inspector' + } + }), this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: null + } + })]); + + const events = await this.Event.findAll({ + where: { data: { - name: { - first: 'Homer', - last: 'Simpson' - }, status_report: { 'red-indicator': { 'level$$level': true } - }, - employment: 'Nuclear Safety Inspector' - } - }), - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: null - } - }) - ).then(() => { - return this.Event.findAll({ - where: { - data: { - status_report: { - 'red-indicator': { - 'level$$level': true - } - } } } - }).then(events => { - expect(events.length).to.equal(1); - expect(events[0].get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - status_report: { - 'red-indicator': { - 'level$$level': true - } - }, - employment: 'Nuclear Safety Inspector' - }); - }); + } + }); + + expect(events.length).to.equal(1); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + status_report: { + 'red-indicator': { + 'level$$level': true + } + }, + employment: 'Nuclear Safety Inspector' }); }); - it('should be possible to query multiple nested values', function() { - return this.Event.create({ + it('should be possible to query multiple nested values', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -487,66 +472,63 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), - this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - }) - ); - }).then(() => { - return this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - }, - employment: { - [Op.ne]: 'None' - } - } + }); + + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' }, - order: [ - ['id', 'ASC'] - ] - }).then(events => { - expect(events.length).to.equal(2); + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); - expect(events[0].get('data')).to.eql({ + const events = await this.Event.findAll({ + where: { + data: { name: { - first: 'Homer', last: 'Simpson' }, - employment: 'Nuclear Safety Inspector' - }); + employment: { + [Op.ne]: 'None' + } + } + }, + order: [ + ['id', 'ASC'] + ] + }); - expect(events[1].get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - }); - }); + expect(events.length).to.equal(2); + + expect(events[0].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + }); + + expect(events[1].get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' }); }); - it('should be possible to query a nested value and order results', function() { - return this.Event.create({ + it('should be possible to query a nested value and order results', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -554,72 +536,69 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - } - }), - this.Event.create({ - data: { - name: { - first: 'Bart', - last: 'Simpson' - }, - employment: 'None' - } - }) - ); - }).then(() => { - return this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } - } + }); + + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' }, - order: [ - ['data.name.first'] - ] - }).then(events => { - expect(events.length).to.equal(3); + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); - expect(events[0].get('data')).to.eql({ + const events = await this.Event.findAll({ + where: { + data: { name: { - first: 'Bart', last: 'Simpson' - }, - employment: 'None' - }); + } + } + }, + order: [ + ['data.name.first'] + ] + }); - expect(events[1].get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); + expect(events.length).to.equal(3); - expect(events[2].get('data')).to.eql({ - name: { - first: 'Marge', - last: 'Simpson' - }, - employment: 'Housewife' - }); - }); + expect(events[0].get('data')).to.eql({ + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + }); + + expect(events[1].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + }); + + expect(events[2].get('data')).to.eql({ + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' }); }); }); describe('destroy', () => { - it('should be possible to destroy with where', function() { + it('should be possible to destroy with where', async function() { const conditionSearch = { where: { data: { @@ -628,54 +607,49 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }; - return Promise.join( - this.Event.create({ - data: { - name: { - first: 'Elliot', - last: 'Alderson' - }, - employment: 'Hacker' - } - }), - this.Event.create({ - data: { - name: { - first: 'Christian', - last: 'Slater' - }, - employment: 'Hacker' - } - }), - this.Event.create({ - data: { - name: { - first: ' Tyrell', - last: 'Wellick' - }, - employment: 'CTO' - } - }) - ).then(() => { - return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); - }).then(() => { - return this.Event.destroy(conditionSearch); - }).then(() => { - return expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0); - }); + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Elliot', + last: 'Alderson' + }, + employment: 'Hacker' + } + }), this.Event.create({ + data: { + name: { + first: 'Christian', + last: 'Slater' + }, + employment: 'Hacker' + } + }), this.Event.create({ + data: { + name: { + first: ' Tyrell', + last: 'Wellick' + }, + employment: 'CTO' + } + })]); + + await expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(2); + await this.Event.destroy(conditionSearch); + + await expect(this.Event.findAll(conditionSearch)).to.eventually.have.length(0); }); }); describe('sql injection attacks', () => { - beforeEach(function() { + beforeEach(async function() { this.Model = this.sequelize.define('Model', { data: DataTypes.JSON }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); - it('should properly escape the single quotes', function() { - return this.Model.create({ + it('should properly escape the single quotes', async function() { + await this.Model.create({ data: { type: 'Point', properties: { @@ -685,8 +659,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should properly escape path keys', function() { - return this.Model.findAll({ + it('should properly escape path keys', async function() { + await this.Model.findAll({ raw: true, attributes: ['id'], where: { @@ -697,16 +671,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should properly escape path keys with sequelize.json', function() { - return this.Model.findAll({ + it('should properly escape path keys with sequelize.json', async function() { + await this.Model.findAll({ raw: true, attributes: ['id'], where: this.sequelize.json("data.id')) AS DECIMAL) = 1 DELETE YOLO INJECTIONS; -- ", '1') }); }); - it('should properly escape the single quotes in array', function() { - return this.Model.create({ + it('should properly escape the single quotes in array', async function() { + await this.Model.create({ data: { type: 'Point', coordinates: [39.807222, "'); DELETE YOLO INJECTIONS; --"] @@ -714,37 +688,37 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - it('should be possible to find with properly escaped select query', function() { - return this.Model.create({ + it('should be possible to find with properly escaped select query', async function() { + await this.Model.create({ data: { type: 'Point', properties: { exploit: "'); DELETE YOLO INJECTIONS; -- " } } - }).then(() => { - return this.Model.findOne({ - where: { - data: { - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - } + }); + + const result = await this.Model.findOne({ + where: { + data: { + type: 'Point', + properties: { + exploit: "'); DELETE YOLO INJECTIONS; -- " } } - }); - }).then(result => { - expect(result.get('data')).to.deep.equal({ - type: 'Point', - properties: { - exploit: "'); DELETE YOLO INJECTIONS; -- " - } - }); + } + }); + + expect(result.get('data')).to.deep.equal({ + type: 'Point', + properties: { + exploit: "'); DELETE YOLO INJECTIONS; -- " + } }); }); - it('should query an instance with JSONB data and order while trying to inject', function() { - return this.Event.create({ + it('should query an instance with JSONB data and order while trying to inject', async function() { + await this.Event.create({ data: { name: { first: 'Homer', @@ -752,66 +726,64 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, employment: 'Nuclear Safety Inspector' } - }).then(() => { - return Promise.join( - this.Event.create({ + }); + + await Promise.all([this.Event.create({ + data: { + name: { + first: 'Marge', + last: 'Simpson' + }, + employment: 'Housewife' + } + }), this.Event.create({ + data: { + name: { + first: 'Bart', + last: 'Simpson' + }, + employment: 'None' + } + })]); + + if (current.options.dialect === 'sqlite') { + const events = await this.Event.findAll({ + where: { data: { name: { - first: 'Marge', last: 'Simpson' - }, - employment: 'Housewife' + } } - }), - this.Event.create({ + }, + order: [ + ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] + ] + }); + + expect(events).to.be.ok; + expect(events[0].get('data')).to.eql({ + name: { + first: 'Homer', + last: 'Simpson' + }, + employment: 'Nuclear Safety Inspector' + }); + return; + } + if (current.options.dialect === 'postgres') { + await expect(this.Event.findAll({ + where: { data: { name: { - first: 'Bart', last: 'Simpson' - }, - employment: 'None' - } - }) - ); - }).then(() => { - if (current.options.dialect === 'sqlite') { - return this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } - } - }, - order: [ - ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] - ] - }).then(events => { - expect(events).to.be.ok; - expect(events[0].get('data')).to.eql({ - name: { - first: 'Homer', - last: 'Simpson' - }, - employment: 'Nuclear Safety Inspector' - }); - }); - } - if (current.options.dialect === 'postgres') { - return expect(this.Event.findAll({ - where: { - data: { - name: { - last: 'Simpson' - } } - }, - order: [ - ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] - ] - })).to.eventually.be.rejectedWith(Error); - } - }); + } + }, + order: [ + ["data.name.first}'); INSERT INJECTION HERE! SELECT ('"] + ] + })).to.eventually.be.rejectedWith(Error); + } }); }); }); diff --git a/test/integration/model/notExist.test.js b/test/integration/model/notExist.test.js new file mode 100644 index 000000000000..e85c736e4ad2 --- /dev/null +++ b/test/integration/model/notExist.test.js @@ -0,0 +1,63 @@ +'use strict'; + +const chai = require('chai'), + expect = chai.expect, + Support = require('../support'), + DataTypes = require('../../../lib/data-types'); + +describe(Support.getTestDialectTeaser('Model'), () => { + beforeEach(async function() { + this.Order = this.sequelize.define('Order', { + id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, + sequence: DataTypes.INTEGER, + amount: DataTypes.DECIMAL, + type: DataTypes.STRING + }); + + await this.sequelize.sync({ force: true }); + + await this.Order.bulkCreate([ + { sequence: 1, amount: 3, type: 'A' }, + { sequence: 2, amount: 4, type: 'A' }, + { sequence: 3, amount: 5, type: 'A' }, + { sequence: 4, amount: 1, type: 'A' }, + { sequence: 1, amount: 2, type: 'B' }, + { sequence: 2, amount: 6, type: 'B' }, + { sequence: 0, amount: 0, type: 'C' } + ]); + }); + + describe('max', () => { + it('type A to C should exist', async function() { + await expect(this.Order.sum('sequence', { where: { type: 'A' } })).to.eventually.be.equal(10); + await expect(this.Order.max('sequence', { where: { type: 'A' } })).to.eventually.be.equal(4); + await expect(this.Order.min('sequence', { where: { type: 'A' } })).to.eventually.be.equal(1); + await expect(this.Order.sum('amount', { where: { type: 'A' } })).to.eventually.be.equal(13); + await expect(this.Order.max('amount', { where: { type: 'A' } })).to.eventually.be.equal(5); + await expect(this.Order.min('amount', { where: { type: 'A' } })).to.eventually.be.equal(1); + + await expect(this.Order.sum('sequence', { where: { type: 'B' } })).to.eventually.be.equal(3); + await expect(this.Order.max('sequence', { where: { type: 'B' } })).to.eventually.be.equal(2); + await expect(this.Order.min('sequence', { where: { type: 'B' } })).to.eventually.be.equal(1); + await expect(this.Order.sum('amount', { where: { type: 'B' } })).to.eventually.be.equal(8); + await expect(this.Order.max('amount', { where: { type: 'B' } })).to.eventually.be.equal(6); + await expect(this.Order.min('amount', { where: { type: 'B' } })).to.eventually.be.equal(2); + + await expect(this.Order.sum('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.max('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.min('sequence', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.sum('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.max('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); + await expect(this.Order.min('amount', { where: { type: 'C' } })).to.eventually.be.equal(0); + }); + + it('type D should not exist', async function() { + await expect(this.Order.sum('sequence', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.max('sequence', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.min('sequence', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.sum('amount', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.max('amount', { where: { type: 'D' } })).to.eventually.be.null; + await expect(this.Order.min('amount', { where: { type: 'D' } })).to.eventually.be.null; + }); + }); +}); diff --git a/test/integration/model/optimistic_locking.test.js b/test/integration/model/optimistic_locking.test.js index ad5d9eb49009..b58282bfc9b3 100644 --- a/test/integration/model/optimistic_locking.test.js +++ b/test/integration/model/optimistic_locking.test.js @@ -8,7 +8,7 @@ const expect = chai.expect; describe(Support.getTestDialectTeaser('Model'), () => { describe('optimistic locking', () => { let Account; - beforeEach(function() { + beforeEach(async function() { Account = this.sequelize.define('Account', { number: { type: DataTypes.INTEGER @@ -16,65 +16,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, { version: true }); - return Account.sync({ force: true }); + await Account.sync({ force: true }); }); - it('should increment the version on save', () => { - return Account.create({ number: 1 }).then(account => { - account.number += 1; - expect(account.version).to.eq(0); - return account.save(); - }).then(account => { - expect(account.version).to.eq(1); - }); + it('should increment the version on save', async () => { + const account0 = await Account.create({ number: 1 }); + account0.number += 1; + expect(account0.version).to.eq(0); + const account = await account0.save(); + expect(account.version).to.eq(1); }); - it('should increment the version on update', () => { - return Account.create({ number: 1 }).then(account => { - expect(account.version).to.eq(0); - return account.update({ number: 2 }); - }).then(account => { - expect(account.version).to.eq(1); - account.number += 1; - return account.save(); - }).then(account => { - expect(account.number).to.eq(3); - expect(account.version).to.eq(2); - }); + it('should increment the version on update', async () => { + const account1 = await Account.create({ number: 1 }); + expect(account1.version).to.eq(0); + const account0 = await account1.update({ number: 2 }); + expect(account0.version).to.eq(1); + account0.number += 1; + const account = await account0.save(); + expect(account.number).to.eq(3); + expect(account.version).to.eq(2); }); - it('prevents stale instances from being saved', () => { - return expect(Account.create({ number: 1 }).then(accountA => { - return Account.findByPk(accountA.id).then(accountB => { - accountA.number += 1; - return accountA.save().then(() => { return accountB; }); - }); - }).then(accountB => { + it('prevents stale instances from being saved', async () => { + await expect((async () => { + const accountA = await Account.create({ number: 1 }); + const accountB0 = await Account.findByPk(accountA.id); + accountA.number += 1; + await accountA.save(); + const accountB = await accountB0; accountB.number += 1; - return accountB.save(); - })).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError); + return await accountB.save(); + })()).to.eventually.be.rejectedWith(Support.Sequelize.OptimisticLockError); }); - it('increment() also increments the version', () => { - return Account.create({ number: 1 }).then(account => { - expect(account.version).to.eq(0); - return account.increment('number', { by: 1 } ); - }).then(account => { - return account.reload(); - }).then(account => { - expect(account.version).to.eq(1); - }); + it('increment() also increments the version', async () => { + const account1 = await Account.create({ number: 1 }); + expect(account1.version).to.eq(0); + const account0 = await account1.increment('number', { by: 1 } ); + const account = await account0.reload(); + expect(account.version).to.eq(1); }); - it('decrement() also increments the version', () => { - return Account.create({ number: 1 }).then(account => { - expect(account.version).to.eq(0); - return account.decrement('number', { by: 1 } ); - }).then(account => { - return account.reload(); - }).then(account => { - expect(account.version).to.eq(1); - }); + it('decrement() also increments the version', async () => { + const account1 = await Account.create({ number: 1 }); + expect(account1.version).to.eq(0); + const account0 = await account1.decrement('number', { by: 1 } ); + const account = await account0.reload(); + expect(account.version).to.eq(1); }); }); }); diff --git a/test/integration/model/paranoid.test.js b/test/integration/model/paranoid.test.js index 81836bdd716e..9033ad795d52 100644 --- a/test/integration/model/paranoid.test.js +++ b/test/integration/model/paranoid.test.js @@ -17,7 +17,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.restore(); }); - it('should be able to soft delete with timestamps', function() { + it('should be able to soft delete with timestamps', async function() { const Account = this.sequelize.define('Account', { ownerId: { type: DataTypes.INTEGER, @@ -32,32 +32,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { timestamps: true }); - return Account.sync({ force: true }) - .then(() => Account.create({ ownerId: 12 })) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - return Account.destroy({ where: { ownerId: 12 } }) - .then(result => { - expect(result).to.be.equal(1); - }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(0); - return Account.count({ paranoid: false }); - }) - .then(count => { - expect(count).to.be.equal(1); - return Account.restore({ where: { ownerId: 12 } }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - }); + await Account.sync({ force: true }); + await Account.create({ ownerId: 12 }); + const count2 = await Account.count(); + expect(count2).to.be.equal(1); + const result = await Account.destroy({ where: { ownerId: 12 } }); + expect(result).to.be.equal(1); + const count1 = await Account.count(); + expect(count1).to.be.equal(0); + const count0 = await Account.count({ paranoid: false }); + expect(count0).to.be.equal(1); + await Account.restore({ where: { ownerId: 12 } }); + const count = await Account.count(); + expect(count).to.be.equal(1); }); - it('should be able to soft delete without timestamps', function() { + it('should be able to soft delete without timestamps', async function() { const Account = this.sequelize.define('Account', { ownerId: { type: DataTypes.INTEGER, @@ -80,26 +70,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { updatedAt: false }); - return Account.sync({ force: true }) - .then(() => Account.create({ ownerId: 12 })) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - return Account.destroy({ where: { ownerId: 12 } }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(0); - return Account.count({ paranoid: false }); - }) - .then(count => { - expect(count).to.be.equal(1); - return Account.restore({ where: { ownerId: 12 } }); - }) - .then(() => Account.count()) - .then(count => { - expect(count).to.be.equal(1); - }); + await Account.sync({ force: true }); + await Account.create({ ownerId: 12 }); + const count2 = await Account.count(); + expect(count2).to.be.equal(1); + await Account.destroy({ where: { ownerId: 12 } }); + const count1 = await Account.count(); + expect(count1).to.be.equal(0); + const count0 = await Account.count({ paranoid: false }); + expect(count0).to.be.equal(1); + await Account.restore({ where: { ownerId: 12 } }); + const count = await Account.count(); + expect(count).to.be.equal(1); }); if (current.dialect.supports.JSON) { @@ -124,12 +106,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - beforeEach(function() { - return this.Model.sync({ force: true }); + beforeEach(async function() { + await this.Model.sync({ force: true }); }); - it('should soft delete with JSON condition', function() { - return this.Model.bulkCreate([{ + it('should soft delete with JSON condition', async function() { + await this.Model.bulkCreate([{ name: 'One', data: { field: { @@ -143,7 +125,9 @@ describe(Support.getTestDialectTeaser('Model'), () => { deep: false } } - }]).then(() => this.Model.destroy({ + }]); + + await this.Model.destroy({ where: { data: { field: { @@ -151,10 +135,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } } - })).then(() => this.Model.findAll()).then(records => { - expect(records.length).to.equal(1); - expect(records[0].get('name')).to.equal('Two'); }); + + const records = await this.Model.findAll(); + expect(records.length).to.equal(1); + expect(records[0].get('name')).to.equal('Two'); }); }); } diff --git a/test/integration/model/schema.test.js b/test/integration/model/schema.test.js index c65db407269f..376ea9758bde 100644 --- a/test/integration/model/schema.test.js +++ b/test/integration/model/schema.test.js @@ -6,8 +6,7 @@ const chai = require('chai'), dialect = Support.getTestDialect(), DataTypes = require('../../../lib/data-types'), current = Support.sequelize, - Op = Support.Sequelize.Op, - Promise = Support.Sequelize.Promise; + Op = Support.Sequelize.Op; const SCHEMA_ONE = 'schema_one'; const SCHEMA_TWO = 'schema_two'; @@ -48,18 +47,17 @@ describe(Support.getTestDialectTeaser('Model'), () => { current.options.schema = null; }); - beforeEach('build restaurant tables', function() { - return current.createSchema(SCHEMA_TWO) - .then(() => { - return Promise.all([ - this.RestaurantOne.sync({ force: true }), - this.RestaurantTwo.sync({ force: true }) - ]); - }); + beforeEach('build restaurant tables', async function() { + await current.createSchema(SCHEMA_TWO); + + await Promise.all([ + this.RestaurantOne.sync({ force: true }), + this.RestaurantTwo.sync({ force: true }) + ]); }); - afterEach('drop schemas', () => { - return current.dropSchema(SCHEMA_TWO); + afterEach('drop schemas', async () => { + await current.dropSchema(SCHEMA_TWO); }); describe('Add data via model.create, retrieve via model.findOne', () => { @@ -68,81 +66,79 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(this.RestaurantTwo._schema).to.equal(SCHEMA_TWO); }); - it('should be able to insert data into default table using create', function() { - return this.RestaurantOne.create({ + it('should be able to insert data into default table using create', async function() { + await this.RestaurantOne.create({ foo: 'one' - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - return this.RestaurantTwo.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.be.null; }); + + const obj0 = await this.RestaurantOne.findOne({ + where: { foo: 'one' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('one'); + + const obj = await this.RestaurantTwo.findOne({ + where: { foo: 'one' } + }); + + expect(obj).to.be.null; }); - it('should be able to insert data into schema table using create', function() { - return this.RestaurantTwo.create({ + it('should be able to insert data into schema table using create', async function() { + await this.RestaurantTwo.create({ foo: 'two' - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - return this.RestaurantOne.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.be.null; }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + + const obj = await this.RestaurantOne.findOne({ + where: { foo: 'two' } + }); + + expect(obj).to.be.null; }); }); describe('Get associated data in public schema via include', () => { - beforeEach(function() { - return Promise.all([ + beforeEach(async function() { + await Promise.all([ this.LocationOne.sync({ force: true }), this.LocationTwo.sync({ force: true }) - ]).then(() => { - return this.LocationTwo.create({ name: 'HQ' }); - }).then(() => { - return this.LocationTwo.findOne({ where: { name: 'HQ' } }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.name).to.equal('HQ'); - locationId = obj.id; - return this.LocationOne.findOne({ where: { name: 'HQ' } }); - }).then(obj => { - expect(obj).to.be.null; - }); + ]); + + await this.LocationTwo.create({ name: 'HQ' }); + const obj0 = await this.LocationTwo.findOne({ where: { name: 'HQ' } }); + expect(obj0).to.not.be.null; + expect(obj0.name).to.equal('HQ'); + locationId = obj0.id; + const obj = await this.LocationOne.findOne({ where: { name: 'HQ' } }); + expect(obj).to.be.null; }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { - return this.RestaurantTwo.create({ + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { + await this.RestaurantTwo.create({ foo: 'two', location_id: locationId - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' }, include: [{ - model: this.LocationTwo, as: 'location' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); - return this.RestaurantOne.findOne({ where: { foo: 'two' } }); - }).then(obj => { - expect(obj).to.be.null; }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' }, include: [{ + model: this.LocationTwo, as: 'location' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + expect(obj0.location).to.not.be.null; + expect(obj0.location.name).to.equal('HQ'); + const obj = await this.RestaurantOne.findOne({ where: { foo: 'two' } }); + expect(obj).to.be.null; }); }); }); @@ -183,337 +179,307 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); - beforeEach('build restaurant tables', function() { - return Promise.all([ + beforeEach('build restaurant tables', async function() { + await Promise.all([ current.createSchema(SCHEMA_ONE), current.createSchema(SCHEMA_TWO) - ]).then(() => { - return Promise.all([ - this.RestaurantOne.sync({ force: true }), - this.RestaurantTwo.sync({ force: true }) - ]); - }); + ]); + + await Promise.all([ + this.RestaurantOne.sync({ force: true }), + this.RestaurantTwo.sync({ force: true }) + ]); }); - afterEach('drop schemas', () => { - return Promise.all([ + afterEach('drop schemas', async () => { + await Promise.all([ current.dropSchema(SCHEMA_ONE), current.dropSchema(SCHEMA_TWO) ]); }); describe('Add data via model.create, retrieve via model.findOne', () => { - it('should be able to insert data into the table in schema_one using create', function() { - let restaurantId; - - return this.RestaurantOne.create({ + it('should be able to insert data into the table in schema_one using create', async function() { + await this.RestaurantOne.create({ foo: 'one', location_id: locationId - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return this.RestaurantOne.findByPk(restaurantId); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - return this.RestaurantTwo.findOne({ where: { foo: 'one' } }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); }); - }); - it('should be able to insert data into the table in schema_two using create', function() { - let restaurantId; + const obj0 = await this.RestaurantOne.findOne({ + where: { foo: 'one' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('one'); + const restaurantId = obj0.id; + const obj = await this.RestaurantOne.findByPk(restaurantId); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); + const RestaurantObj = await this.RestaurantTwo.findOne({ where: { foo: 'one' } }); + expect(RestaurantObj).to.be.null; + }); - return this.RestaurantTwo.create({ + it('should be able to insert data into the table in schema_two using create', async function() { + await this.RestaurantTwo.create({ foo: 'two', location_id: locationId - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return this.RestaurantTwo.findByPk(restaurantId); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - return this.RestaurantOne.findOne({ where: { foo: 'two' } }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' } + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + const restaurantId = obj0.id; + const obj = await this.RestaurantTwo.findByPk(restaurantId); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('two'); + const RestaurantObj = await this.RestaurantOne.findOne({ where: { foo: 'two' } }); + expect(RestaurantObj).to.be.null; }); }); describe('Persist and retrieve data', () => { - it('should be able to insert data into both schemas using instance.save and retrieve/count it', function() { + it('should be able to insert data into both schemas using instance.save and retrieve/count it', async function() { //building and saving in random order to make sure calling // .schema doesn't impact model prototype - let restaurauntModel = new this.RestaurantOne({ bar: 'one.1' }); - - return restaurauntModel.save() - .then(() => { - restaurauntModel = new this.RestaurantTwo({ bar: 'two.1' }); - return restaurauntModel.save(); - }).then(() => { - restaurauntModel = new this.RestaurantOne({ bar: 'one.2' }); - return restaurauntModel.save(); - }).then(() => { - restaurauntModel = new this.RestaurantTwo({ bar: 'two.2' }); - return restaurauntModel.save(); - }).then(() => { - restaurauntModel = new this.RestaurantTwo({ bar: 'two.3' }); - return restaurauntModel.save(); - }).then(() => { - return this.RestaurantOne.findAll(); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return this.RestaurantOne.findAndCountAll(); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.rows.length).to.equal(2); - expect(restaurantsOne.count).to.equal(2); - restaurantsOne.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return this.RestaurantOne.findAll({ - where: { bar: { [Op.like]: '%.1' } } - }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(1); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return this.RestaurantOne.count(); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(2); - return this.RestaurantTwo.findAll(); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return this.RestaurantTwo.findAndCountAll(); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.rows.length).to.equal(3); - expect(restaurantsTwo.count).to.equal(3); - restaurantsTwo.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return this.RestaurantTwo.findAll({ - where: { bar: { [Op.like]: '%.3' } } - }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return this.RestaurantTwo.count(); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(3); - }); + let restaurauntModel = this.RestaurantOne.build({ bar: 'one.1' }); + + await restaurauntModel.save(); + restaurauntModel = this.RestaurantTwo.build({ bar: 'two.1' }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantOne.build({ bar: 'one.2' }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantTwo.build({ bar: 'two.2' }); + await restaurauntModel.save(); + restaurauntModel = this.RestaurantTwo.build({ bar: 'two.3' }); + await restaurauntModel.save(); + const restaurantsOne1 = await this.RestaurantOne.findAll(); + expect(restaurantsOne1).to.not.be.null; + expect(restaurantsOne1.length).to.equal(2); + restaurantsOne1.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsOne0 = await this.RestaurantOne.findAndCountAll(); + expect(restaurantsOne0).to.not.be.null; + expect(restaurantsOne0.rows.length).to.equal(2); + expect(restaurantsOne0.count).to.equal(2); + restaurantsOne0.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + + const restaurantsOne = await this.RestaurantOne.findAll({ + where: { bar: { [Op.like]: '%.1' } } + }); + + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(1); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const count0 = await this.RestaurantOne.count(); + expect(count0).to.not.be.null; + expect(count0).to.equal(2); + const restaurantsTwo1 = await this.RestaurantTwo.findAll(); + expect(restaurantsTwo1).to.not.be.null; + expect(restaurantsTwo1.length).to.equal(3); + restaurantsTwo1.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + const restaurantsTwo0 = await this.RestaurantTwo.findAndCountAll(); + expect(restaurantsTwo0).to.not.be.null; + expect(restaurantsTwo0.rows.length).to.equal(3); + expect(restaurantsTwo0.count).to.equal(3); + restaurantsTwo0.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + + const restaurantsTwo = await this.RestaurantTwo.findAll({ + where: { bar: { [Op.like]: '%.3' } } + }); + + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(1); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + const count = await this.RestaurantTwo.count(); + expect(count).to.not.be.null; + expect(count).to.equal(3); }); }); describe('Get associated data in public schema via include', () => { - beforeEach(function() { + beforeEach(async function() { const Location = this.Location; - return Location.sync({ force: true }) - .then(() => { - return Location.create({ name: 'HQ' }).then(() => { - return Location.findOne({ where: { name: 'HQ' } }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.name).to.equal('HQ'); - locationId = obj.id; - }); - }); - }) - .catch(err => { - expect(err).to.be.null; - }); + try { + await Location.sync({ force: true }); + await Location.create({ name: 'HQ' }); + const obj = await Location.findOne({ where: { name: 'HQ' } }); + expect(obj).to.not.be.null; + expect(obj.name).to.equal('HQ'); + locationId = obj.id; + } catch (err) { + expect(err).to.be.null; + } }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { - return this.RestaurantOne.create({ + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { + await this.RestaurantOne.create({ foo: 'one', location_id: locationId - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' }, include: [{ - model: this.Location, as: 'location' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); }); + + const obj = await this.RestaurantOne.findOne({ + where: { foo: 'one' }, include: [{ + model: this.Location, as: 'location' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); + expect(obj.location).to.not.be.null; + expect(obj.location.name).to.equal('HQ'); }); }); describe('Get schema specific associated data via include', () => { - beforeEach(function() { + beforeEach(async function() { const Employee = this.Employee; - return Promise.all([ + + await Promise.all([ Employee.schema(SCHEMA_ONE).sync({ force: true }), Employee.schema(SCHEMA_TWO).sync({ force: true }) ]); }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { - let restaurantId; - - return this.RestaurantOne.create({ + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { + await this.RestaurantOne.create({ foo: 'one' - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return this.EmployeeOne.create({ - first_name: 'Restaurant', - last_name: 'one', - restaurant_id: restaurantId - }); - }).then(() => { - return this.RestaurantOne.findOne({ - where: { foo: 'one' }, include: [{ - model: this.EmployeeOne, as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('one'); - return obj.getEmployees({ schema: SCHEMA_ONE }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('one'); - return this.EmployeeOne.findOne({ - where: { last_name: 'one' }, include: [{ - model: this.RestaurantOne, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('one'); - return obj.getRestaurant({ schema: SCHEMA_ONE }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('one'); }); - }); + const obj1 = await this.RestaurantOne.findOne({ + where: { foo: 'one' } + }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('one'); + const restaurantId = obj1.id; + + await this.EmployeeOne.create({ + first_name: 'Restaurant', + last_name: 'one', + restaurant_id: restaurantId + }); + + const obj0 = await this.RestaurantOne.findOne({ + where: { foo: 'one' }, include: [{ + model: this.EmployeeOne, as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('one'); + const employees = await obj0.getEmployees({ schema: SCHEMA_ONE }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('one'); + + const obj = await this.EmployeeOne.findOne({ + where: { last_name: 'one' }, include: [{ + model: this.RestaurantOne, as: 'restaurant' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('one'); + const restaurant = await obj.getRestaurant({ schema: SCHEMA_ONE }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('one'); + }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { - let restaurantId; - return this.RestaurantTwo.create({ + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { + await this.RestaurantTwo.create({ foo: 'two' - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' } - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return this.Employee.schema(SCHEMA_TWO).create({ - first_name: 'Restaurant', - last_name: 'two', - restaurant_id: restaurantId - }); - }).then(() => { - return this.RestaurantTwo.findOne({ - where: { foo: 'two' }, include: [{ - model: this.Employee.schema(SCHEMA_TWO), as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('two'); - return obj.getEmployees({ schema: SCHEMA_TWO }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('two'); - return this.Employee.schema(SCHEMA_TWO).findOne({ - where: { last_name: 'two' }, include: [{ - model: this.RestaurantTwo, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('two'); - return obj.getRestaurant({ schema: SCHEMA_TWO }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('two'); }); + + const obj1 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' } + }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('two'); + const restaurantId = obj1.id; + + await this.Employee.schema(SCHEMA_TWO).create({ + first_name: 'Restaurant', + last_name: 'two', + restaurant_id: restaurantId + }); + + const obj0 = await this.RestaurantTwo.findOne({ + where: { foo: 'two' }, include: [{ + model: this.Employee.schema(SCHEMA_TWO), as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('two'); + const employees = await obj0.getEmployees({ schema: SCHEMA_TWO }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('two'); + + const obj = await this.Employee.schema(SCHEMA_TWO).findOne({ + where: { last_name: 'two' }, include: [{ + model: this.RestaurantTwo, as: 'restaurant' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('two'); + const restaurant = await obj.getRestaurant({ schema: SCHEMA_TWO }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('two'); }); }); describe('concurency tests', () => { - it('should build and persist instances to 2 schemas concurrently in any order', function() { + it('should build and persist instances to 2 schemas concurrently in any order', async function() { const Restaurant = this.Restaurant; - let restaurauntModelSchema1 = new (Restaurant.schema(SCHEMA_ONE))({ bar: 'one.1' }); - const restaurauntModelSchema2 = new (Restaurant.schema(SCHEMA_TWO))({ bar: 'two.1' }); - - return restaurauntModelSchema1.save() - .then(() => { - restaurauntModelSchema1 = new (Restaurant.schema(SCHEMA_ONE))({ bar: 'one.2' }); - return restaurauntModelSchema2.save(); - }).then(() => { - return restaurauntModelSchema1.save(); - }).then(() => { - return Restaurant.schema(SCHEMA_ONE).findAll(); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.schema(SCHEMA_TWO).findAll(); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); + let restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.1' }); + const restaurauntModelSchema2 = Restaurant.schema(SCHEMA_TWO).build({ bar: 'two.1' }); + + await restaurauntModelSchema1.save(); + restaurauntModelSchema1 = Restaurant.schema(SCHEMA_ONE).build({ bar: 'one.2' }); + await restaurauntModelSchema2.save(); + await restaurauntModelSchema1.save(); + const restaurantsOne = await Restaurant.schema(SCHEMA_ONE).findAll(); + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(2); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsTwo = await Restaurant.schema(SCHEMA_TWO).findAll(); + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(1); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); }); }); describe('regressions', () => { - it('should be able to sync model with schema', function() { + it('should be able to sync model with schema', async function() { const User = this.sequelize.define('User1', { name: DataTypes.STRING, value: DataTypes.INTEGER @@ -540,23 +506,22 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return Promise.all([ - this.sequelize.queryInterface.describeTable(User.tableName, SCHEMA_ONE), - this.sequelize.queryInterface.describeTable(Task.tableName, SCHEMA_TWO) - ]); - }).then(([user, task]) => { - expect(user).to.be.ok; - expect(task).to.be.ok; - }); + await User.sync({ force: true }); + await Task.sync({ force: true }); + + const [user, task] = await Promise.all([ + this.sequelize.queryInterface.describeTable(User.tableName, SCHEMA_ONE), + this.sequelize.queryInterface.describeTable(Task.tableName, SCHEMA_TWO) + ]); + + expect(user).to.be.ok; + expect(task).to.be.ok; }); // TODO: this should work with MSSQL / MariaDB too // Need to fix addSchema return type if (dialect.match(/^postgres/)) { - it('defaults to schema provided to sync() for references #11276', function() { + it('defaults to schema provided to sync() for references #11276', async function() { const User = this.sequelize.define('UserXYZ', { uid: { type: DataTypes.INTEGER, @@ -570,19 +535,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { Task.belongsTo(User); - return User.sync({ force: true, schema: SCHEMA_ONE }).then(() => { - return Task.sync({ force: true, schema: SCHEMA_ONE }); - }).then(() => { - return User.schema(SCHEMA_ONE).create({}); - }).then(user => { - return Task.schema(SCHEMA_ONE).create({}).then(task => { - return task.setUserXYZ(user).then(() => { - return task.getUserXYZ({ schema: SCHEMA_ONE }); - }); - }); - }).then(user => { - expect(user).to.be.ok; - }); + await User.sync({ force: true, schema: SCHEMA_ONE }); + await Task.sync({ force: true, schema: SCHEMA_ONE }); + const user0 = await User.schema(SCHEMA_ONE).create({}); + const task = await Task.schema(SCHEMA_ONE).create({}); + await task.setUserXYZ(user0); + const user = await task.getUserXYZ({ schema: SCHEMA_ONE }); + expect(user).to.be.ok; }); } }); diff --git a/test/integration/model/scope.test.js b/test/integration/model/scope.test.js index fb4f31428985..aa2db393ffb8 100644 --- a/test/integration/model/scope.test.js +++ b/test/integration/model/scope.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -57,66 +57,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should be able to merge attributes as array', function() { - return this.ScopeMe.scope('lowAccess', 'withName').findOne() - .then(record => { - expect(record.other_value).to.exist; - expect(record.username).to.exist; - expect(record.access_level).to.exist; - }); + it('should be able to merge attributes as array', async function() { + const record = await this.ScopeMe.scope('lowAccess', 'withName').findOne(); + expect(record.other_value).to.exist; + expect(record.username).to.exist; + expect(record.access_level).to.exist; }); - it('should work with Symbol operators', function() { - return this.ScopeMe.scope('highAccess').findOne() - .then(record => { - expect(record.username).to.equal('tobi'); - return this.ScopeMe.scope('lessThanFour').findAll(); - }) - .then(records => { - expect(records).to.have.length(2); - expect(records[0].get('access_level')).to.equal(3); - expect(records[1].get('access_level')).to.equal(3); - return this.ScopeMe.scope('issue8473').findAll(); - }) - .then(records => { - expect(records).to.have.length(1); - expect(records[0].get('access_level')).to.equal(5); - expect(records[0].get('other_value')).to.equal(10); - }); + it('should work with Symbol operators', async function() { + const record = await this.ScopeMe.scope('highAccess').findOne(); + expect(record.username).to.equal('tobi'); + const records0 = await this.ScopeMe.scope('lessThanFour').findAll(); + expect(records0).to.have.length(2); + expect(records0[0].get('access_level')).to.equal(3); + expect(records0[1].get('access_level')).to.equal(3); + const records = await this.ScopeMe.scope('issue8473').findAll(); + expect(records).to.have.length(1); + expect(records[0].get('access_level')).to.equal(5); + expect(records[0].get('other_value')).to.equal(10); }); - it('should keep symbols after default assignment', function() { - return this.ScopeMe.scope('highAccess').findOne() - .then(record => { - expect(record.username).to.equal('tobi'); - return this.ScopeMe.scope('lessThanFour').findAll({ - where: {} - }); - }) - .then(records => { - expect(records).to.have.length(2); - expect(records[0].get('access_level')).to.equal(3); - expect(records[1].get('access_level')).to.equal(3); - return this.ScopeMe.scope('issue8473').findAll(); - }); + it('should keep symbols after default assignment', async function() { + const record = await this.ScopeMe.scope('highAccess').findOne(); + expect(record.username).to.equal('tobi'); + + const records = await this.ScopeMe.scope('lessThanFour').findAll({ + where: {} + }); + + expect(records).to.have.length(2); + expect(records[0].get('access_level')).to.equal(3); + expect(records[1].get('access_level')).to.equal(3); + await this.ScopeMe.scope('issue8473').findAll(); }); - it('should not throw error with sequelize.where', function() { - return this.ScopeMe.scope('like_t').findAll() - .then(records => { - expect(records).to.have.length(2); - }); + it('should not throw error with sequelize.where', async function() { + const records = await this.ScopeMe.scope('like_t').findAll(); + expect(records).to.have.length(2); }); }); }); diff --git a/test/integration/model/scope/aggregate.test.js b/test/integration/model/scope/aggregate.test.js index 7e6aa2100ea3..620fea0b650b 100644 --- a/test/integration/model/scope/aggregate.test.js +++ b/test/integration/model/scope/aggregate.test.js @@ -4,13 +4,12 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Op = Sequelize.Op, expect = chai.expect, - Support = require('../../support'), - Promise = require('../../../../lib/promise'); + Support = require('../../support'); describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('aggregate', () => { - beforeEach(function() { + beforeEach(async function() { this.Child = this.sequelize.define('Child', { priority: Sequelize.INTEGER }); @@ -51,50 +50,48 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Child.belongsTo(this.ScopeMe); this.ScopeMe.hasMany(this.Child); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }).then(() => { - return this.ScopeMe.findAll(); - }).then(records => { - return Promise.all([ - records[0].createChild({ - priority: 1 - }), - records[1].createChild({ - priority: 2 - }) - ]); - }); + await this.sequelize.sync({ force: true }); + const records0 = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + await this.ScopeMe.bulkCreate(records0); + const records = await this.ScopeMe.findAll(); + + await Promise.all([ + records[0].createChild({ + priority: 1 + }), + records[1].createChild({ + priority: 2 + }) + ]); }); - it('should apply defaultScope', function() { - return expect(this.ScopeMe.aggregate( '*', 'count' )).to.eventually.equal(2); + it('should apply defaultScope', async function() { + await expect(this.ScopeMe.aggregate( '*', 'count' )).to.eventually.equal(2); }); - it('should be able to override default scope', function() { - return expect(this.ScopeMe.aggregate( '*', 'count', { where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); + it('should be able to override default scope', async function() { + await expect(this.ScopeMe.aggregate( '*', 'count', { where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); }); - it('should be able to unscope', function() { - return expect(this.ScopeMe.unscoped().aggregate( '*', 'count' )).to.eventually.equal(4); + it('should be able to unscope', async function() { + await expect(this.ScopeMe.unscoped().aggregate( '*', 'count' )).to.eventually.equal(4); }); - it('should be able to apply other scopes', function() { - return expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count' )).to.eventually.equal(3); + it('should be able to apply other scopes', async function() { + await expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count' )).to.eventually.equal(3); }); - it('should be able to merge scopes with where', function() { - return expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count', { where: { username: 'dan' } })).to.eventually.equal(1); + it('should be able to merge scopes with where', async function() { + await expect(this.ScopeMe.scope('lowAccess').aggregate( '*', 'count', { where: { username: 'dan' } })).to.eventually.equal(1); }); - it('should be able to use where on include', function() { - return expect(this.ScopeMe.scope('withInclude').aggregate( 'ScopeMe.id', 'count', { + it('should be able to use where on include', async function() { + await expect(this.ScopeMe.scope('withInclude').aggregate( 'ScopeMe.id', 'count', { plain: true, dataType: new Sequelize.INTEGER(), includeIgnoreAttributes: false, @@ -106,27 +103,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); if (Support.sequelize.dialect.supports.schemas) { - it('aggregate with schema', function() { + it('aggregate with schema', async function() { this.Hero = this.sequelize.define('Hero', { codename: Sequelize.STRING }, { schema: 'heroschema' }); - return this.sequelize.createSchema('heroschema') - .then(() => { - return this.sequelize.sync({ force: true }); - }) - .then(() => { - const records = [ - { codename: 'hulk' }, - { codename: 'rantanplan' } - ]; - return this.Hero.bulkCreate(records); - }) - .then(() => { - return expect( - this.Hero.unscoped().aggregate('*', 'count', - { schema: 'heroschema' })).to.eventually.equal( - 2); - }); + await this.sequelize.createSchema('heroschema'); + await this.sequelize.sync({ force: true }); + const records = [ + { codename: 'hulk' }, + { codename: 'rantanplan' } + ]; + await this.Hero.bulkCreate(records); + + await expect( + this.Hero.unscoped().aggregate('*', 'count', + { schema: 'heroschema' })).to.eventually.equal(2); }); } }); diff --git a/test/integration/model/scope/associations.test.js b/test/integration/model/scope/associations.test.js index 8c47ea857e4e..a004489ab0af 100644 --- a/test/integration/model/scope/associations.test.js +++ b/test/integration/model/scope/associations.test.js @@ -4,13 +4,12 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Op = Sequelize.Op, expect = chai.expect, - Promise = Sequelize.Promise, Support = require('../../support'); describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('associations', () => { - beforeEach(function() { + beforeEach(async function() { const sequelize = this.sequelize; this.ScopeMe = this.sequelize.define('ScopeMe', { @@ -97,214 +96,195 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.ScopeMe.belongsTo(this.Company); this.UserAssociation = this.Company.hasMany(this.ScopeMe, { as: 'users' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - this.ScopeMe.create({ id: 1, username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }), - this.ScopeMe.create({ id: 2, username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }), - this.ScopeMe.create({ id: 3, username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }), - this.ScopeMe.create({ id: 4, username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 }), - this.ScopeMe.create({ id: 5, username: 'bob', email: 'bob@foobar.com', access_level: 1, other_value: 9, parent_id: 5 }), - this.Company.create({ id: 1, active: true }), - this.Company.create({ id: 2, active: false }) - ]); - }).then(([u1, u2, u3, u4, u5, c1, c2]) => { - return Promise.all([ - c1.setUsers([u1, u2, u3, u4]), - c2.setUsers([u5]) - ]); - }); + await this.sequelize.sync({ force: true }); + + const [u1, u2, u3, u4, u5, c1, c2] = await Promise.all([ + this.ScopeMe.create({ id: 1, username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }), + this.ScopeMe.create({ id: 2, username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }), + this.ScopeMe.create({ id: 3, username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }), + this.ScopeMe.create({ id: 4, username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 }), + this.ScopeMe.create({ id: 5, username: 'bob', email: 'bob@foobar.com', access_level: 1, other_value: 9, parent_id: 5 }), + this.Company.create({ id: 1, active: true }), + this.Company.create({ id: 2, active: false }) + ]); + + await Promise.all([ + c1.setUsers([u1, u2, u3, u4]), + c2.setUsers([u5]) + ]); }); describe('include', () => { - it('should scope columns properly', function() { + it('should scope columns properly', async function() { // Will error with ambigous column if id is not scoped properly to `Company`.`id` - return expect(this.Company.findAll({ + await expect(this.Company.findAll({ where: { id: 1 }, include: [this.UserAssociation] })).not.to.be.rejected; }); - it('should apply default scope when including an associations', function() { - return this.Company.findAll({ + it('should apply default scope when including an associations', async function() { + const obj = await this.Company.findAll({ include: [this.UserAssociation] - }).get(0).then(company => { - expect(company.users).to.have.length(2); }); + + const company = await obj[0]; + expect(company.users).to.have.length(2); }); - it('should apply default scope when including a model', function() { - return this.Company.findAll({ + it('should apply default scope when including a model', async function() { + const obj = await this.Company.findAll({ include: [{ model: this.ScopeMe, as: 'users' }] - }).get(0).then(company => { - expect(company.users).to.have.length(2); }); + + const company = await obj[0]; + expect(company.users).to.have.length(2); }); - it('should be able to include a scoped model', function() { - return this.Company.findAll({ + it('should be able to include a scoped model', async function() { + const obj = await this.Company.findAll({ include: [{ model: this.ScopeMe.scope('isTony'), as: 'users' }] - }).get(0).then(company => { - expect(company.users).to.have.length(1); - expect(company.users[0].get('username')).to.equal('tony'); }); + + const company = await obj[0]; + expect(company.users).to.have.length(1); + expect(company.users[0].get('username')).to.equal('tony'); }); }); describe('get', () => { - beforeEach(function() { - return Promise.all([ + beforeEach(async function() { + const [p, companies] = await Promise.all([ this.Project.create(), this.Company.unscoped().findAll() - ]).then(([p, companies]) => { - return p.setCompanies(companies); - }); + ]); + + await p.setCompanies(companies); }); describe('it should be able to unscope', () => { - it('hasMany', function() { - return this.Company.findByPk(1).then(company => { - return company.getUsers({ scope: false }); - }).then(users => { - expect(users).to.have.length(4); - }); + it('hasMany', async function() { + const company = await this.Company.findByPk(1); + const users = await company.getUsers({ scope: false }); + expect(users).to.have.length(4); }); - it('hasOne', function() { - return this.Profile.create({ + it('hasOne', async function() { + await this.Profile.create({ active: false, userId: 1 - }).then(() => { - return this.ScopeMe.findByPk(1); - }).then(user => { - return user.getProfile({ scope: false }); - }).then(profile => { - expect(profile).to.be.ok; }); + + const user = await this.ScopeMe.findByPk(1); + const profile = await user.getProfile({ scope: false }); + expect(profile).to.be.ok; }); - it('belongsTo', function() { - return this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }).then(user => { - return user.getCompany({ scope: false }); - }).then(company => { - expect(company).to.be.ok; - }); + it('belongsTo', async function() { + const user = await this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }); + const company = await user.getCompany({ scope: false }); + expect(company).to.be.ok; }); - it('belongsToMany', function() { - return this.Project.findAll().get(0).then(p => { - return p.getCompanies({ scope: false }); - }).then(companies => { - expect(companies).to.have.length(2); - }); + it('belongsToMany', async function() { + const obj = await this.Project.findAll(); + const p = await obj[0]; + const companies = await p.getCompanies({ scope: false }); + expect(companies).to.have.length(2); }); }); describe('it should apply default scope', () => { - it('hasMany', function() { - return this.Company.findByPk(1).then(company => { - return company.getUsers(); - }).then(users => { - expect(users).to.have.length(2); - }); + it('hasMany', async function() { + const company = await this.Company.findByPk(1); + const users = await company.getUsers(); + expect(users).to.have.length(2); }); - it('hasOne', function() { - return this.Profile.create({ + it('hasOne', async function() { + await this.Profile.create({ active: false, userId: 1 - }).then(() => { - return this.ScopeMe.findByPk(1); - }).then(user => { - return user.getProfile(); - }).then(profile => { - expect(profile).not.to.be.ok; }); + + const user = await this.ScopeMe.findByPk(1); + const profile = await user.getProfile(); + expect(profile).not.to.be.ok; }); - it('belongsTo', function() { - return this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }).then(user => { - return user.getCompany(); - }).then(company => { - expect(company).not.to.be.ok; - }); + it('belongsTo', async function() { + const user = await this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }); + const company = await user.getCompany(); + expect(company).not.to.be.ok; }); - it('belongsToMany', function() { - return this.Project.findAll().get(0).then(p => { - return p.getCompanies(); - }).then(companies => { - expect(companies).to.have.length(1); - expect(companies[0].get('active')).to.be.ok; - }); + it('belongsToMany', async function() { + const obj = await this.Project.findAll(); + const p = await obj[0]; + const companies = await p.getCompanies(); + expect(companies).to.have.length(1); + expect(companies[0].get('active')).to.be.ok; }); }); describe('it should be able to apply another scope', () => { - it('hasMany', function() { - return this.Company.findByPk(1).then(company => { - return company.getUsers({ scope: 'isTony' }); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('tony'); - }); + it('hasMany', async function() { + const company = await this.Company.findByPk(1); + const users = await company.getUsers({ scope: 'isTony' }); + expect(users).to.have.length(1); + expect(users[0].get('username')).to.equal('tony'); }); - it('hasOne', function() { - return this.Profile.create({ + it('hasOne', async function() { + await this.Profile.create({ active: true, userId: 1 - }).then(() => { - return this.ScopeMe.findByPk(1); - }).then(user => { - return user.getProfile({ scope: 'notActive' }); - }).then(profile => { - expect(profile).not.to.be.ok; }); + + const user = await this.ScopeMe.findByPk(1); + const profile = await user.getProfile({ scope: 'notActive' }); + expect(profile).not.to.be.ok; }); - it('belongsTo', function() { - return this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }).then(user => { - return user.getCompany({ scope: 'notActive' }); - }).then(company => { - expect(company).to.be.ok; - }); + it('belongsTo', async function() { + const user = await this.ScopeMe.unscoped().findOne({ where: { username: 'bob' } }); + const company = await user.getCompany({ scope: 'notActive' }); + expect(company).to.be.ok; }); - it('belongsToMany', function() { - return this.Project.findAll().get(0).then(p => { - return p.getCompanies({ scope: 'reversed' }); - }).then(companies => { - expect(companies).to.have.length(2); - expect(companies[0].id).to.equal(2); - expect(companies[1].id).to.equal(1); - }); + it('belongsToMany', async function() { + const obj = await this.Project.findAll(); + const p = await obj[0]; + const companies = await p.getCompanies({ scope: 'reversed' }); + expect(companies).to.have.length(2); + expect(companies[0].id).to.equal(2); + expect(companies[1].id).to.equal(1); }); }); }); describe('scope with includes', () => { - beforeEach(function() { - return Promise.all([ + beforeEach(async function() { + const [c, p1, p2] = await Promise.all([ this.Company.findByPk(1), this.Project.create({ id: 1, active: true }), this.Project.create({ id: 2, active: false }) - ]).then(([c, p1, p2]) => { - return c.setProjects([p1, p2]); - }); + ]); + + await c.setProjects([p1, p2]); }); - it('should scope columns properly', function() { - return expect(this.ScopeMe.scope('includeActiveProjects').findAll()).not.to.be.rejected; + it('should scope columns properly', async function() { + await expect(this.ScopeMe.scope('includeActiveProjects').findAll()).not.to.be.rejected; }); - it('should apply scope conditions', function() { - return this.ScopeMe.scope('includeActiveProjects').findOne({ where: { id: 1 } }).then(user => { - expect(user.company.projects).to.have.length(1); - }); + it('should apply scope conditions', async function() { + const user = await this.ScopeMe.scope('includeActiveProjects').findOne({ where: { id: 1 } }); + expect(user.company.projects).to.have.length(1); }); describe('with different format', () => { - it('should not throw error', function() { + it('should not throw error', async function() { const Child = this.sequelize.define('Child'); const Parent = this.sequelize.define('Parent', {}, { defaultScope: { @@ -323,18 +303,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { Child.belongsTo(Parent); Parent.hasOne(Child); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([Child.create(), Parent.create()]); - }).then(([child, parent]) => { - return parent.setChild(child); - }).then(() => { - return Parent.scope('children', 'alsoChildren').findOne(); - }); + await this.sequelize.sync({ force: true }); + const [child, parent] = await Promise.all([Child.create(), Parent.create()]); + await parent.setChild(child); + + await Parent.scope('children', 'alsoChildren').findOne(); }); }); describe('with find options', () => { - it('should merge includes correctly', function() { + it('should merge includes correctly', async function() { const Child = this.sequelize.define('Child', { name: Sequelize.STRING }); const Parent = this.sequelize.define('Parent', { name: Sequelize.STRING }); Parent.addScope('testScope1', { @@ -347,32 +325,29 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); Parent.hasMany(Child); - return this.sequelize.sync({ force: true }) - .then(() => { - return Promise.all([ - Parent.create({ name: 'parent1' }).then(parent => parent.createChild({ name: 'child1' })), - Parent.create({ name: 'parent2' }).then(parent => parent.createChild({ name: 'child2' })) - ]); - }) - .then(() => { - return Parent.scope('testScope1').findOne({ - include: [{ - model: Child, - attributes: { exclude: ['name'] } - }] - }); - }) - .then(parent => { - expect(parent.get('name')).to.equal('parent2'); - expect(parent.Children).to.have.length(1); - expect(parent.Children[0].dataValues).not.to.have.property('name'); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + Parent.create({ name: 'parent1' }).then(parent => parent.createChild({ name: 'child1' })), + Parent.create({ name: 'parent2' }).then(parent => parent.createChild({ name: 'child2' })) + ]); + + const parent = await Parent.scope('testScope1').findOne({ + include: [{ + model: Child, + attributes: { exclude: ['name'] } + }] + }); + + expect(parent.get('name')).to.equal('parent2'); + expect(parent.Children).to.have.length(1); + expect(parent.Children[0].dataValues).not.to.have.property('name'); }); }); }); describe('scope with options', () => { - it('should return correct object included foreign_key', function() { + it('should return correct object included foreign_key', async function() { const Child = this.sequelize.define('Child', { secret: Sequelize.STRING }, { @@ -388,16 +363,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { Child.belongsTo(Parent); Parent.hasOne(Child); - return this.sequelize.sync({ force: true }) - .then(() => Child.create({ secret: 'super secret' })) - .then(() => Child.scope('public').findOne()) - .then(user => { - expect(user.dataValues).to.have.property('ParentId'); - expect(user.dataValues).not.to.have.property('secret'); - }); + await this.sequelize.sync({ force: true }); + await Child.create({ secret: 'super secret' }); + const user = await Child.scope('public').findOne(); + expect(user.dataValues).to.have.property('ParentId'); + expect(user.dataValues).not.to.have.property('secret'); }); - it('should return correct object included foreign_key with defaultScope', function() { + it('should return correct object included foreign_key with defaultScope', async function() { const Child = this.sequelize.define('Child', { secret: Sequelize.STRING }, { @@ -410,53 +383,49 @@ describe(Support.getTestDialectTeaser('Model'), () => { const Parent = this.sequelize.define('Parent'); Child.belongsTo(Parent); - return this.sequelize.sync({ force: true }) - .then(() => Child.create({ secret: 'super secret' })) - .then(() => Child.findOne()) - .then(user => { - expect(user.dataValues).to.have.property('ParentId'); - expect(user.dataValues).not.to.have.property('secret'); - }); + await this.sequelize.sync({ force: true }); + await Child.create({ secret: 'super secret' }); + const user = await Child.findOne(); + expect(user.dataValues).to.have.property('ParentId'); + expect(user.dataValues).not.to.have.property('secret'); }); - it('should not throw error', function() { + it('should not throw error', async function() { const Clientfile = this.sequelize.define('clientfile'); const Mission = this.sequelize.define('mission', { secret: Sequelize.STRING }); const Building = this.sequelize.define('building'); const MissionAssociation = Clientfile.hasOne(Mission); const BuildingAssociation = Clientfile.hasOne(Building); - return this.sequelize.sync({ force: true }) - .then(() => { - return Clientfile.findAll({ - include: [ - { - association: 'mission', - where: { - secret: 'foo' - } - }, - { - association: 'building' - } - ] - }); - }) - .then(() => { - return Clientfile.findAll({ - include: [ - { - association: MissionAssociation, - where: { - secret: 'foo' - } - }, - { - association: BuildingAssociation - } - ] - }); - }); + await this.sequelize.sync({ force: true }); + + await Clientfile.findAll({ + include: [ + { + association: 'mission', + where: { + secret: 'foo' + } + }, + { + association: 'building' + } + ] + }); + + await Clientfile.findAll({ + include: [ + { + association: MissionAssociation, + where: { + secret: 'foo' + } + }, + { + association: BuildingAssociation + } + ] + }); }); }); }); diff --git a/test/integration/model/scope/count.test.js b/test/integration/model/scope/count.test.js index 8cba88c92f77..cc08fcd17af0 100644 --- a/test/integration/model/scope/count.test.js +++ b/test/integration/model/scope/count.test.js @@ -4,13 +4,12 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Op = Sequelize.Op, expect = chai.expect, - Promise = require('../../../../lib/promise'), Support = require('../../support'); describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('count', () => { - beforeEach(function() { + beforeEach(async function() { this.Child = this.sequelize.define('Child', { priority: Sequelize.INTEGER }); @@ -81,66 +80,64 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Child.belongsTo(this.ScopeMe); this.ScopeMe.hasMany(this.Child); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, aliasValue: 12 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, aliasValue: 5 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, aliasValue: 1 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, aliasValue: 10 } - ]; - return this.ScopeMe.bulkCreate(records); - }).then(() => { - return this.ScopeMe.findAll(); - }).then(records => { - return Promise.all([ - records[0].createChild({ - priority: 1 - }), - records[1].createChild({ - priority: 2 - }) - ]); - }); + await this.sequelize.sync({ force: true }); + const records0 = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, aliasValue: 12 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, aliasValue: 5 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, aliasValue: 1 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, aliasValue: 10 } + ]; + await this.ScopeMe.bulkCreate(records0); + const records = await this.ScopeMe.findAll(); + + await Promise.all([ + records[0].createChild({ + priority: 1 + }), + records[1].createChild({ + priority: 2 + }) + ]); }); - it('should apply defaultScope', function() { - return expect(this.ScopeMe.count()).to.eventually.equal(2); + it('should apply defaultScope', async function() { + await expect(this.ScopeMe.count()).to.eventually.equal(2); }); - it('should be able to override default scope', function() { - return expect(this.ScopeMe.count({ where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); + it('should be able to override default scope', async function() { + await expect(this.ScopeMe.count({ where: { access_level: { [Op.gt]: 5 } } })).to.eventually.equal(1); }); - it('should be able to unscope', function() { - return expect(this.ScopeMe.unscoped().count()).to.eventually.equal(4); + it('should be able to unscope', async function() { + await expect(this.ScopeMe.unscoped().count()).to.eventually.equal(4); }); - it('should be able to apply other scopes', function() { - return expect(this.ScopeMe.scope('lowAccess').count()).to.eventually.equal(3); + it('should be able to apply other scopes', async function() { + await expect(this.ScopeMe.scope('lowAccess').count()).to.eventually.equal(3); }); - it('should be able to merge scopes with where', function() { - return expect(this.ScopeMe.scope('lowAccess').count({ where: { username: 'dan' } })).to.eventually.equal(1); + it('should be able to merge scopes with where', async function() { + await expect(this.ScopeMe.scope('lowAccess').count({ where: { username: 'dan' } })).to.eventually.equal(1); }); - it('should be able to merge scopes with where on aliased fields', function() { - return expect(this.ScopeMe.scope('withAliasedField').count({ where: { aliasValue: 5 } })).to.eventually.equal(1); + it('should be able to merge scopes with where on aliased fields', async function() { + await expect(this.ScopeMe.scope('withAliasedField').count({ where: { aliasValue: 5 } })).to.eventually.equal(1); }); - it('should ignore the order option if it is found within the scope', function() { - return expect(this.ScopeMe.scope('withOrder').count()).to.eventually.equal(4); + it('should ignore the order option if it is found within the scope', async function() { + await expect(this.ScopeMe.scope('withOrder').count()).to.eventually.equal(4); }); - it('should be able to use where on include', function() { - return expect(this.ScopeMe.scope('withInclude').count()).to.eventually.equal(1); + it('should be able to use where on include', async function() { + await expect(this.ScopeMe.scope('withInclude').count()).to.eventually.equal(1); }); - it('should be able to use include with function scope', function() { - return expect(this.ScopeMe.scope('withIncludeFunction').count()).to.eventually.equal(1); + it('should be able to use include with function scope', async function() { + await expect(this.ScopeMe.scope('withIncludeFunction').count()).to.eventually.equal(1); }); - it('should be able to use include with function scope and string association', function() { - return expect(this.ScopeMe.scope('withIncludeFunctionAndStringAssociation').count()).to.eventually.equal(1); + it('should be able to use include with function scope and string association', async function() { + await expect(this.ScopeMe.scope('withIncludeFunctionAndStringAssociation').count()).to.eventually.equal(1); }); }); }); diff --git a/test/integration/model/scope/destroy.test.js b/test/integration/model/scope/destroy.test.js index ba602b8eb229..ce66450c505a 100644 --- a/test/integration/model/scope/destroy.test.js +++ b/test/integration/model/scope/destroy.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('destroy', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -34,70 +34,59 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should apply defaultScope', function() { - return this.ScopeMe.destroy({ where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('username')).to.equal('tony'); - expect(users[1].get('username')).to.equal('fred'); - }); + it('should apply defaultScope', async function() { + await this.ScopeMe.destroy({ where: {} }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(2); + expect(users[0].get('username')).to.equal('tony'); + expect(users[1].get('username')).to.equal('fred'); }); - it('should be able to override default scope', function() { - return this.ScopeMe.destroy({ where: { access_level: { [Op.lt]: 5 } } }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('username')).to.equal('tobi'); - expect(users[1].get('username')).to.equal('dan'); - }); + it('should be able to override default scope', async function() { + await this.ScopeMe.destroy({ where: { access_level: { [Op.lt]: 5 } } }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(2); + expect(users[0].get('username')).to.equal('tobi'); + expect(users[1].get('username')).to.equal('dan'); }); - it('should be able to unscope destroy', function() { - return this.ScopeMe.unscoped().destroy({ where: {} }).then(() => { - return expect(this.ScopeMe.unscoped().findAll()).to.eventually.have.length(0); - }); + it('should be able to unscope destroy', async function() { + await this.ScopeMe.unscoped().destroy({ where: {} }); + await expect(this.ScopeMe.unscoped().findAll()).to.eventually.have.length(0); }); - it('should be able to apply other scopes', function() { - return this.ScopeMe.scope('lowAccess').destroy({ where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('tobi'); - }); + it('should be able to apply other scopes', async function() { + await this.ScopeMe.scope('lowAccess').destroy({ where: {} }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(1); + expect(users[0].get('username')).to.equal('tobi'); }); - it('should be able to merge scopes with where', function() { - return this.ScopeMe.scope('lowAccess').destroy({ where: { username: 'dan' } }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(3); - expect(users[0].get('username')).to.equal('tony'); - expect(users[1].get('username')).to.equal('tobi'); - expect(users[2].get('username')).to.equal('fred'); - }); + it('should be able to merge scopes with where', async function() { + await this.ScopeMe.scope('lowAccess').destroy({ where: { username: 'dan' } }); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(3); + expect(users[0].get('username')).to.equal('tony'); + expect(users[1].get('username')).to.equal('tobi'); + expect(users[2].get('username')).to.equal('fred'); }); - it('should work with empty where', function() { - return this.ScopeMe.scope('lowAccess').destroy().then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('username')).to.equal('tobi'); - }); + it('should work with empty where', async function() { + await this.ScopeMe.scope('lowAccess').destroy(); + const users = await this.ScopeMe.unscoped().findAll(); + expect(users).to.have.length(1); + expect(users[0].get('username')).to.equal('tobi'); }); }); }); diff --git a/test/integration/model/scope/find.test.js b/test/integration/model/scope/find.test.js index 19b9ee5731c9..49604344dcf5 100644 --- a/test/integration/model/scope/find.test.js +++ b/test/integration/model/scope/find.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scopes', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -62,93 +62,83 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.ScopeMe.hasMany(this.DefaultScopeExclude); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7, parent_id: 1 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11, parent_id: 2 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10, parent_id: 1 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7, parent_id: 1 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should be able use where in scope', function() { - return this.ScopeMe.scope({ where: { parent_id: 2 } }).findAll().then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('tobi'); - }); + it('should be able use where in scope', async function() { + const users = await this.ScopeMe.scope({ where: { parent_id: 2 } }).findAll(); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('tobi'); }); - it('should be able to combine scope and findAll where clauses', function() { - return this.ScopeMe.scope({ where: { parent_id: 1 } }).findAll({ where: { access_level: 3 } }).then(users => { - expect(users).to.have.length(2); - expect(['tony', 'fred'].includes(users[0].username)).to.be.true; - expect(['tony', 'fred'].includes(users[1].username)).to.be.true; - }); + it('should be able to combine scope and findAll where clauses', async function() { + const users = await this.ScopeMe.scope({ where: { parent_id: 1 } }).findAll({ where: { access_level: 3 } }); + expect(users).to.have.length(2); + expect(['tony', 'fred'].includes(users[0].username)).to.be.true; + expect(['tony', 'fred'].includes(users[1].username)).to.be.true; }); - it('should be able to use a defaultScope if declared', function() { - return this.ScopeMe.findAll().then(users => { - expect(users).to.have.length(2); - expect([10, 5].includes(users[0].access_level)).to.be.true; - expect([10, 5].includes(users[1].access_level)).to.be.true; - expect(['dan', 'tobi'].includes(users[0].username)).to.be.true; - expect(['dan', 'tobi'].includes(users[1].username)).to.be.true; - }); + it('should be able to use a defaultScope if declared', async function() { + const users = await this.ScopeMe.findAll(); + expect(users).to.have.length(2); + expect([10, 5].includes(users[0].access_level)).to.be.true; + expect([10, 5].includes(users[1].access_level)).to.be.true; + expect(['dan', 'tobi'].includes(users[0].username)).to.be.true; + expect(['dan', 'tobi'].includes(users[1].username)).to.be.true; }); - it('should be able to handle $and in scopes', function() { - return this.ScopeMe.scope('andScope').findAll().then(users => { - expect(users).to.have.length(1); - expect(users[0].username).to.equal('tony'); - }); + it('should be able to handle $and in scopes', async function() { + const users = await this.ScopeMe.scope('andScope').findAll(); + expect(users).to.have.length(1); + expect(users[0].username).to.equal('tony'); }); describe('should not overwrite', () => { - it('default scope with values from previous finds', function() { - return this.ScopeMe.findAll({ where: { other_value: 10 } }).then(users => { - expect(users).to.have.length(1); - - return this.ScopeMe.findAll(); - }).then(users => { - // This should not have other_value: 10 - expect(users).to.have.length(2); - }); + it('default scope with values from previous finds', async function() { + const users0 = await this.ScopeMe.findAll({ where: { other_value: 10 } }); + expect(users0).to.have.length(1); + const users = await this.ScopeMe.findAll(); + // This should not have other_value: 10 + expect(users).to.have.length(2); }); - it('other scopes with values from previous finds', function() { - return this.ScopeMe.scope('highValue').findAll({ where: { access_level: 10 } }).then(users => { - expect(users).to.have.length(1); + it('other scopes with values from previous finds', async function() { + const users0 = await this.ScopeMe.scope('highValue').findAll({ where: { access_level: 10 } }); + expect(users0).to.have.length(1); - return this.ScopeMe.scope('highValue').findAll(); - }).then(users => { - // This should not have other_value: 10 - expect(users).to.have.length(2); - }); + const users = await this.ScopeMe.scope('highValue').findAll(); + // This should not have other_value: 10 + expect(users).to.have.length(2); }); }); - it('should have no problem performing findOrCreate', function() { - return this.ScopeMe.findOrCreate({ where: { username: 'fake' } }).then(([user]) => { - expect(user.username).to.equal('fake'); - }); + it('should have no problem performing findOrCreate', async function() { + const [user] = await this.ScopeMe.findOrCreate({ where: { username: 'fake' } }); + expect(user.username).to.equal('fake'); }); - it('should work when included with default scope', function() { - return this.ScopeMe.findOne({ + it('should work when included with default scope', async function() { + await this.ScopeMe.findOne({ include: [this.DefaultScopeExclude] }); }); }); describe('scope in associations', () => { - it('should work when association with a virtual column queried with default scope', function() { + it('should work when association with a virtual column queried with default scope', async function() { const Game = this.sequelize.define('Game', { name: Sequelize.TEXT }); - + const User = this.sequelize.define('User', { login: Sequelize.TEXT, session: { @@ -164,18 +154,18 @@ describe(Support.getTestDialectTeaser('Model'), () => { } } }); - + Game.hasMany(User); - return this.sequelize.sync({ force: true }).then(() => { - return Game.findAll({ - include: [{ - model: User - }] - }); - }).then(games => { - expect(games).to.have.lengthOf(0); + await this.sequelize.sync({ force: true }); + + const games = await Game.findAll({ + include: [{ + model: User + }] }); + + expect(games).to.have.lengthOf(0); }); }); }); diff --git a/test/integration/model/scope/findAndCountAll.test.js b/test/integration/model/scope/findAndCountAll.test.js index 1bceb2238fe4..529fa64993ea 100644 --- a/test/integration/model/scope/findAndCountAll.test.js +++ b/test/integration/model/scope/findAndCountAll.test.js @@ -11,7 +11,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { describe('findAndCountAll', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -40,59 +40,50 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should apply defaultScope', function() { - return this.ScopeMe.findAndCountAll().then(result => { - expect(result.count).to.equal(2); - expect(result.rows.length).to.equal(2); - }); + it('should apply defaultScope', async function() { + const result = await this.ScopeMe.findAndCountAll(); + expect(result.count).to.equal(2); + expect(result.rows.length).to.equal(2); }); - it('should be able to override default scope', function() { - return this.ScopeMe.findAndCountAll({ where: { access_level: { [Op.gt]: 5 } } }) - .then(result => { - expect(result.count).to.equal(1); - expect(result.rows.length).to.equal(1); - }); + it('should be able to override default scope', async function() { + const result = await this.ScopeMe.findAndCountAll({ where: { access_level: { [Op.gt]: 5 } } }); + expect(result.count).to.equal(1); + expect(result.rows.length).to.equal(1); }); - it('should be able to unscope', function() { - return this.ScopeMe.unscoped().findAndCountAll({ limit: 1 }) - .then(result => { - expect(result.count).to.equal(4); - expect(result.rows.length).to.equal(1); - }); + it('should be able to unscope', async function() { + const result = await this.ScopeMe.unscoped().findAndCountAll({ limit: 1 }); + expect(result.count).to.equal(4); + expect(result.rows.length).to.equal(1); }); - it('should be able to apply other scopes', function() { - return this.ScopeMe.scope('lowAccess').findAndCountAll() - .then(result => { - expect(result.count).to.equal(3); - }); + it('should be able to apply other scopes', async function() { + const result = await this.ScopeMe.scope('lowAccess').findAndCountAll(); + expect(result.count).to.equal(3); }); - it('should be able to merge scopes with where', function() { - return this.ScopeMe.scope('lowAccess') - .findAndCountAll({ where: { username: 'dan' } }).then(result => { - expect(result.count).to.equal(1); - }); + it('should be able to merge scopes with where', async function() { + const result = await this.ScopeMe.scope('lowAccess') + .findAndCountAll({ where: { username: 'dan' } }); + + expect(result.count).to.equal(1); }); - it('should ignore the order option if it is found within the scope', function() { - return this.ScopeMe.scope('withOrder').findAndCountAll() - .then(result => { - expect(result.count).to.equal(4); - }); + it('should ignore the order option if it is found within the scope', async function() { + const result = await this.ScopeMe.scope('withOrder').findAndCountAll(); + expect(result.count).to.equal(4); }); }); }); diff --git a/test/integration/model/scope/merge.test.js b/test/integration/model/scope/merge.test.js index 125a1beadafe..45e840a70789 100644 --- a/test/integration/model/scope/merge.test.js +++ b/test/integration/model/scope/merge.test.js @@ -2,7 +2,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../../support'), combinatorics = require('js-combinatorics'); @@ -10,8 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('simple merge', () => { - beforeEach(function() { - + beforeEach(async function() { this.Foo = this.sequelize.define('foo', { name: Sequelize.STRING }, { timestamps: false }); this.Bar = this.sequelize.define('bar', { name: Sequelize.STRING }, { timestamps: false }); this.Baz = this.sequelize.define('baz', { name: Sequelize.STRING }, { timestamps: false }); @@ -19,10 +17,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Foo.belongsTo(this.Baz, { foreignKey: 'bazId' }); this.Foo.hasOne(this.Bar, { foreignKey: 'fooId' }); - this.createEntries = () => { - return this.Baz.create({ name: 'The Baz' }) - .then(baz => this.Foo.create({ name: 'The Foo', bazId: baz.id })) - .then(foo => this.Bar.create({ name: 'The Bar', fooId: foo.id })); + this.createEntries = async () => { + const baz = await this.Baz.create({ name: 'The Baz' }); + const foo = await this.Foo.create({ name: 'The Foo', bazId: baz.id }); + return this.Bar.create({ name: 'The Bar', fooId: foo.id }); }; this.scopes = { @@ -33,24 +31,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.Foo.addScope('includeBar', this.scopes.includeBar); this.Foo.addScope('includeBaz', this.scopes.includeBaz); - return this.sequelize.sync({ force: true }).then(this.createEntries); - + await this.createEntries(await this.sequelize.sync({ force: true })); }); - it('should merge simple scopes correctly', function() { - return this.Foo.scope('includeBar', 'includeBaz').findOne().then(result => { - const json = result.toJSON(); - expect(json.bar).to.be.ok; - expect(json.baz).to.be.ok; - expect(json.bar.name).to.equal('The Bar'); - expect(json.baz.name).to.equal('The Baz'); - }); + it('should merge simple scopes correctly', async function() { + const result = await this.Foo.scope('includeBar', 'includeBaz').findOne(); + const json = result.toJSON(); + expect(json.bar).to.be.ok; + expect(json.baz).to.be.ok; + expect(json.bar.name).to.equal('The Bar'); + expect(json.baz.name).to.equal('The Baz'); }); }); describe('complex merge', () => { - beforeEach(function() { - + beforeEach(async function() { this.Foo = this.sequelize.define('foo', { name: Sequelize.STRING }, { timestamps: false }); this.Bar = this.sequelize.define('bar', { name: Sequelize.STRING }, { timestamps: false }); this.Baz = this.sequelize.define('baz', { name: Sequelize.STRING }, { timestamps: false }); @@ -143,35 +138,35 @@ describe(Support.getTestDialectTeaser('Model'), () => { 'excludeBazName' ]).toArray(); - return this.sequelize.sync({ force: true }).then(this.createFooWithDescendants); - + await this.createFooWithDescendants(await this.sequelize.sync({ force: true })); }); - it('should merge complex scopes correctly regardless of their order', function() { - return Promise.map(this.scopePermutations, scopes => this.Foo.scope(...scopes).findOne()).then(results => { - const first = results.shift().toJSON(); - for (const result of results) { - expect(result.toJSON()).to.deep.equal(first); - } - }); + it('should merge complex scopes correctly regardless of their order', async function() { + const results = await Promise.all(this.scopePermutations.map(scopes => this.Foo.scope(...scopes).findOne())); + const first = results.shift().toJSON(); + for (const result of results) { + expect(result.toJSON()).to.deep.equal(first); + } }); - it('should merge complex scopes with findAll options correctly regardless of their order', function() { - return Promise.map(this.scopePermutations, ([a, b, c, d]) => this.Foo.scope(a, b, c).findAll(this.scopes[d]).then(x => x[0])).then(results => { - const first = results.shift().toJSON(); - for (const result of results) { - expect(result.toJSON()).to.deep.equal(first); - } - }); + it('should merge complex scopes with findAll options correctly regardless of their order', async function() { + const results = await Promise.all(this.scopePermutations.map(async ([a, b, c, d]) => { + const x = await this.Foo.scope(a, b, c).findAll(this.scopes[d]); + return x[0]; + })); + + const first = results.shift().toJSON(); + for (const result of results) { + expect(result.toJSON()).to.deep.equal(first); + } }); - it('should merge complex scopes with findOne options correctly regardless of their order', function() { - return Promise.map(this.scopePermutations, ([a, b, c, d]) => this.Foo.scope(a, b, c).findOne(this.scopes[d])).then(results => { - const first = results.shift().toJSON(); - for (const result of results) { - expect(result.toJSON()).to.deep.equal(first); - } - }); + it('should merge complex scopes with findOne options correctly regardless of their order', async function() { + const results = await Promise.all(this.scopePermutations.map(([a, b, c, d]) => this.Foo.scope(a, b, c).findOne(this.scopes[d]))); + const first = results.shift().toJSON(); + for (const result of results) { + expect(result.toJSON()).to.deep.equal(first); + } }); }); diff --git a/test/integration/model/scope/update.test.js b/test/integration/model/scope/update.test.js index cfb195f0e12b..50a3d339d6b9 100644 --- a/test/integration/model/scope/update.test.js +++ b/test/integration/model/scope/update.test.js @@ -9,7 +9,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('scope', () => { describe('update', () => { - beforeEach(function() { + beforeEach(async function() { this.ScopeMe = this.sequelize.define('ScopeMe', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -34,73 +34,62 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - const records = [ - { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, - { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, - { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, - { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } - ]; - return this.ScopeMe.bulkCreate(records); - }); + await this.sequelize.sync({ force: true }); + const records = [ + { username: 'tony', email: 'tony@sequelizejs.com', access_level: 3, other_value: 7 }, + { username: 'tobi', email: 'tobi@fakeemail.com', access_level: 10, other_value: 11 }, + { username: 'dan', email: 'dan@sequelizejs.com', access_level: 5, other_value: 10 }, + { username: 'fred', email: 'fred@foobar.com', access_level: 3, other_value: 7 } + ]; + + await this.ScopeMe.bulkCreate(records); }); - it('should apply defaultScope', function() { - return this.ScopeMe.update({ username: 'ruben' }, { where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); - expect(users[1].get('email')).to.equal('dan@sequelizejs.com'); - }); + it('should apply defaultScope', async function() { + await this.ScopeMe.update({ username: 'ruben' }, { where: {} }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); + expect(users).to.have.length(2); + expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); + expect(users[1].get('email')).to.equal('dan@sequelizejs.com'); }); - it('should be able to override default scope', function() { - return this.ScopeMe.update({ username: 'ruben' }, { where: { access_level: { [Op.lt]: 5 } } }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - }).then(users => { - expect(users).to.have.length(2); - expect(users[0].get('email')).to.equal('tony@sequelizejs.com'); - expect(users[1].get('email')).to.equal('fred@foobar.com'); - }); + it('should be able to override default scope', async function() { + await this.ScopeMe.update({ username: 'ruben' }, { where: { access_level: { [Op.lt]: 5 } } }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); + expect(users).to.have.length(2); + expect(users[0].get('email')).to.equal('tony@sequelizejs.com'); + expect(users[1].get('email')).to.equal('fred@foobar.com'); }); - it('should be able to unscope destroy', function() { - return this.ScopeMe.unscoped().update({ username: 'ruben' }, { where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll(); - }).then(rubens => { - expect(rubens.every(r => r.get('username') === 'ruben')).to.be.true; - }); + it('should be able to unscope destroy', async function() { + await this.ScopeMe.unscoped().update({ username: 'ruben' }, { where: {} }); + const rubens = await this.ScopeMe.unscoped().findAll(); + expect(rubens.every(r => r.get('username') === 'ruben')).to.be.true; }); - it('should be able to apply other scopes', function() { - return this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: {} }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: { [Op.ne]: 'ruben' } } }); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); - }); + it('should be able to apply other scopes', async function() { + await this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: {} }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: { [Op.ne]: 'ruben' } } }); + expect(users).to.have.length(1); + expect(users[0].get('email')).to.equal('tobi@fakeemail.com'); }); - it('should be able to merge scopes with where', function() { - return this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: { username: 'dan' } }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); - }).then(users => { - expect(users).to.have.length(1); - expect(users[0].get('email')).to.equal('dan@sequelizejs.com'); - }); + it('should be able to merge scopes with where', async function() { + await this.ScopeMe.scope('lowAccess').update({ username: 'ruben' }, { where: { username: 'dan' } }); + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruben' } }); + expect(users).to.have.length(1); + expect(users[0].get('email')).to.equal('dan@sequelizejs.com'); }); - it('should work with empty where', function() { - return this.ScopeMe.scope('lowAccess').update({ + it('should work with empty where', async function() { + await this.ScopeMe.scope('lowAccess').update({ username: 'ruby' - }).then(() => { - return this.ScopeMe.unscoped().findAll({ where: { username: 'ruby' } }); - }).then(users => { - expect(users).to.have.length(3); - users.forEach(user => { - expect(user.get('username')).to.equal('ruby'); - }); + }); + + const users = await this.ScopeMe.unscoped().findAll({ where: { username: 'ruby' } }); + expect(users).to.have.length(3); + users.forEach(user => { + expect(user.get('username')).to.equal('ruby'); }); }); }); diff --git a/test/integration/model/searchPath.test.js b/test/integration/model/searchPath.test.js index 803c7b85e98e..8f8a41d4600f 100644 --- a/test/integration/model/searchPath.test.js +++ b/test/integration/model/searchPath.test.js @@ -52,453 +52,426 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); - beforeEach('build restaurant tables', function() { + beforeEach('build restaurant tables', async function() { const Restaurant = this.Restaurant; - return current.createSchema('schema_one').then(() => { - return current.createSchema('schema_two'); - }).then(() => { - return Restaurant.sync({ force: true, searchPath: SEARCH_PATH_ONE }); - }).then(() => { - return Restaurant.sync({ force: true, searchPath: SEARCH_PATH_TWO }); - }).catch(err => { + + try { + await current.createSchema('schema_one'); + await current.createSchema('schema_two'); + await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_ONE }); + await Restaurant.sync({ force: true, searchPath: SEARCH_PATH_TWO }); + } catch (err) { expect(err).to.be.null; - }); + } }); - afterEach('drop schemas', () => { - return current.dropSchema('schema_one').then(() => { - return current.dropSchema('schema_two'); - }); + afterEach('drop schemas', async () => { + await current.dropSchema('schema_one'); + await current.dropSchema('schema_two'); }); describe('enum case', () => { - it('able to refresh enum when searchPath is used', function() { - return this.Location.sync({ force: true }); + it('able to refresh enum when searchPath is used', async function() { + await this.Location.sync({ force: true }); }); }); describe('Add data via model.create, retrieve via model.findOne', () => { - it('should be able to insert data into the table in schema_one using create', function() { + it('should be able to insert data into the table in schema_one using create', async function() { const Restaurant = this.Restaurant; - let restaurantId; - return Restaurant.create({ + await Restaurant.create({ foo: 'one', location_id: locationId - }, { searchPath: SEARCH_PATH_ONE }) - .then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_ONE }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - }); + }, { searchPath: SEARCH_PATH_ONE }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('one'); + const restaurantId = obj0.id; + const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_ONE }); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); }); - it('should fail to insert data into schema_two using create', function() { + it('should fail to insert data into schema_two using create', async function() { const Restaurant = this.Restaurant; - return Restaurant.create({ - foo: 'test' - }, { searchPath: SEARCH_PATH_TWO }).catch(err => { + try { + await Restaurant.create({ + foo: 'test' + }, { searchPath: SEARCH_PATH_TWO }); + } catch (err) { expect(err).to.not.be.null; - }); + } }); - it('should be able to insert data into the table in schema_two using create', function() { + it('should be able to insert data into the table in schema_two using create', async function() { const Restaurant = this.Restaurant; - let restaurantId; - return Restaurant.create({ + await Restaurant.create({ foo: 'two', location_id: locationId - }, { searchPath: SEARCH_PATH_TWO }) - .then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_TWO }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - }); + }, { searchPath: SEARCH_PATH_TWO }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO + }); + + expect(obj0).to.not.be.null; + expect(obj0.foo).to.equal('two'); + const restaurantId = obj0.id; + const obj = await Restaurant.findByPk(restaurantId, { searchPath: SEARCH_PATH_TWO }); + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('two'); }); - it('should fail to find schema_one object in schema_two', function() { + it('should fail to find schema_one object in schema_two', async function() { const Restaurant = this.Restaurant; - return Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_TWO }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); + const RestaurantObj = await Restaurant.findOne({ where: { foo: 'one' }, searchPath: SEARCH_PATH_TWO }); + expect(RestaurantObj).to.be.null; }); - it('should fail to find schema_two object in schema_one', function() { + it('should fail to find schema_two object in schema_one', async function() { const Restaurant = this.Restaurant; - return Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_ONE }).then(RestaurantObj => { - expect(RestaurantObj).to.be.null; - }); + const RestaurantObj = await Restaurant.findOne({ where: { foo: 'two' }, searchPath: SEARCH_PATH_ONE }); + expect(RestaurantObj).to.be.null; }); }); describe('Add data via instance.save, retrieve via model.findAll', () => { - it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', function() { + it('should be able to insert data into both schemas using instance.save and retrieve it via findAll', async function() { const Restaurant = this.Restaurant; - let restaurauntModel = new Restaurant({ bar: 'one.1' }); - - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }) - .then(() => { - restaurauntModel = new Restaurant({ bar: 'one.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - }).then(() => { - restaurauntModel = new Restaurant({ bar: 'two.1' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = new Restaurant({ bar: 'two.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = new Restaurant({ bar: 'two.3' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.rows.length).to.equal(2); - expect(restaurantsOne.count).to.equal(2); - restaurantsOne.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.rows.length).to.equal(3); - expect(restaurantsTwo.count).to.equal(3); - restaurantsTwo.rows.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); + let restaurauntModel = Restaurant.build({ bar: 'one.1' }); + + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'one.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'two.1' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.3' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + const restaurantsOne0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); + expect(restaurantsOne0).to.not.be.null; + expect(restaurantsOne0.length).to.equal(2); + restaurantsOne0.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsOne = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_ONE }); + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.rows.length).to.equal(2); + expect(restaurantsOne.count).to.equal(2); + restaurantsOne.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsTwo0 = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); + expect(restaurantsTwo0).to.not.be.null; + expect(restaurantsTwo0.length).to.equal(3); + restaurantsTwo0.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); + const restaurantsTwo = await Restaurant.findAndCountAll({ searchPath: SEARCH_PATH_TWO }); + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.rows.length).to.equal(3); + expect(restaurantsTwo.count).to.equal(3); + restaurantsTwo.rows.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); }); }); describe('Add data via instance.save, retrieve via model.count and model.find', () => { - it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', function() { + it('should be able to insert data into both schemas using instance.save count it and retrieve it via findAll with where', async function() { const Restaurant = this.Restaurant; - let restaurauntModel = new Restaurant({ bar: 'one.1' }); - - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }).then(() => { - restaurauntModel = new Restaurant({ bar: 'one.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); - }).then(() => { - restaurauntModel = new Restaurant({ bar: 'two.1' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = new Restaurant({ bar: 'two.2' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - restaurauntModel = new Restaurant({ bar: 'two.3' }); - return restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return Restaurant.findAll({ - where: { bar: { [Op.like]: 'one%' } }, - searchPath: SEARCH_PATH_ONE - }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.count({ searchPath: SEARCH_PATH_ONE }); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(2); - return Restaurant.findAll({ - where: { bar: { [Op.like]: 'two%' } }, - searchPath: SEARCH_PATH_TWO - }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(3); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - return Restaurant.count({ searchPath: SEARCH_PATH_TWO }); - }).then(count => { - expect(count).to.not.be.null; - expect(count).to.equal(3); + let restaurauntModel = Restaurant.build({ bar: 'one.1' }); + + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'one.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModel = Restaurant.build({ bar: 'two.1' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.2' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + restaurauntModel = Restaurant.build({ bar: 'two.3' }); + await restaurauntModel.save({ searchPath: SEARCH_PATH_TWO }); + + const restaurantsOne = await Restaurant.findAll({ + where: { bar: { [Op.like]: 'one%' } }, + searchPath: SEARCH_PATH_ONE + }); + + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(2); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const count0 = await Restaurant.count({ searchPath: SEARCH_PATH_ONE }); + expect(count0).to.not.be.null; + expect(count0).to.equal(2); + + const restaurantsTwo = await Restaurant.findAll({ + where: { bar: { [Op.like]: 'two%' } }, + searchPath: SEARCH_PATH_TWO + }); + + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(3); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); }); + const count = await Restaurant.count({ searchPath: SEARCH_PATH_TWO }); + expect(count).to.not.be.null; + expect(count).to.equal(3); }); }); describe('Get associated data in public schema via include', () => { - beforeEach(function() { + beforeEach(async function() { const Location = this.Location; - return Location.sync({ force: true }) - .then(() => { - return Location.create({ name: 'HQ' }).then(() => { - return Location.findOne({ where: { name: 'HQ' } }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.name).to.equal('HQ'); - locationId = obj.id; - }); - }); - }) - .catch(err => { - expect(err).to.be.null; - }); + try { + await Location.sync({ force: true }); + await Location.create({ name: 'HQ' }); + const obj = await Location.findOne({ where: { name: 'HQ' } }); + expect(obj).to.not.be.null; + expect(obj.name).to.equal('HQ'); + locationId = obj.id; + } catch (err) { + expect(err).to.be.null; + } }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { const Restaurant = this.Restaurant; const Location = this.Location; - return Restaurant.create({ + await Restaurant.create({ foo: 'one', location_id: locationId - }, { searchPath: SEARCH_PATH_ONE }).then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, include: [{ - model: Location, as: 'location' - }], searchPath: SEARCH_PATH_ONE - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); + }, { searchPath: SEARCH_PATH_ONE }); + + const obj = await Restaurant.findOne({ + where: { foo: 'one' }, include: [{ + model: Location, as: 'location' + }], searchPath: SEARCH_PATH_ONE }); + + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('one'); + expect(obj.location).to.not.be.null; + expect(obj.location.name).to.equal('HQ'); }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { const Restaurant = this.Restaurant; const Location = this.Location; - return Restaurant.create({ + await Restaurant.create({ foo: 'two', location_id: locationId - }, { searchPath: SEARCH_PATH_TWO }).then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, include: [{ - model: Location, as: 'location' - }], searchPath: SEARCH_PATH_TWO - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - expect(obj.location).to.not.be.null; - expect(obj.location.name).to.equal('HQ'); + }, { searchPath: SEARCH_PATH_TWO }); + + const obj = await Restaurant.findOne({ + where: { foo: 'two' }, include: [{ + model: Location, as: 'location' + }], searchPath: SEARCH_PATH_TWO }); + + expect(obj).to.not.be.null; + expect(obj.foo).to.equal('two'); + expect(obj.location).to.not.be.null; + expect(obj.location.name).to.equal('HQ'); }); }); describe('Get schema specific associated data via include', () => { - beforeEach(function() { + beforeEach(async function() { const Employee = this.Employee; - return Employee.sync({ force: true, searchPath: SEARCH_PATH_ONE }) - .then(() => { - return Employee.sync({ force: true, searchPath: SEARCH_PATH_TWO }); - }) - .catch(err => { - expect(err).to.be.null; - }); + + try { + await Employee.sync({ force: true, searchPath: SEARCH_PATH_ONE }); + await Employee.sync({ force: true, searchPath: SEARCH_PATH_TWO }); + } catch (err) { + expect(err).to.be.null; + } }); - it('should be able to insert and retrieve associated data into the table in schema_one', function() { + it('should be able to insert and retrieve associated data into the table in schema_one', async function() { const Restaurant = this.Restaurant; const Employee = this.Employee; - let restaurantId; - - return Restaurant.create({ + await Restaurant.create({ foo: 'one' - }, { searchPath: SEARCH_PATH_ONE }).then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('one'); - restaurantId = obj.id; - return Employee.create({ - first_name: 'Restaurant', - last_name: 'one', - restaurant_id: restaurantId - }, { searchPath: SEARCH_PATH_ONE }); - }).then(() => { - return Restaurant.findOne({ - where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ - model: Employee, as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('one'); - return obj.getEmployees({ searchPath: SEARCH_PATH_ONE }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('one'); - return Employee.findOne({ - where: { last_name: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ - model: Restaurant, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('one'); - return obj.getRestaurant({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('one'); + }, { searchPath: SEARCH_PATH_ONE }); + + const obj1 = await Restaurant.findOne({ + where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE + }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('one'); + const restaurantId = obj1.id; + + await Employee.create({ + first_name: 'Restaurant', + last_name: 'one', + restaurant_id: restaurantId + }, { searchPath: SEARCH_PATH_ONE }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ + model: Employee, as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('one'); + const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_ONE }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('one'); + + const obj = await Employee.findOne({ + where: { last_name: 'one' }, searchPath: SEARCH_PATH_ONE, include: [{ + model: Restaurant, as: 'restaurant' + }] }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('one'); + const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_ONE }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('one'); }); - it('should be able to insert and retrieve associated data into the table in schema_two', function() { + it('should be able to insert and retrieve associated data into the table in schema_two', async function() { const Restaurant = this.Restaurant; const Employee = this.Employee; - let restaurantId; - return Restaurant.create({ + await Restaurant.create({ foo: 'two' - }, { searchPath: SEARCH_PATH_TWO }).then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.foo).to.equal('two'); - restaurantId = obj.id; - return Employee.create({ - first_name: 'Restaurant', - last_name: 'two', - restaurant_id: restaurantId - }, { searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return Restaurant.findOne({ - where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ - model: Employee, as: 'employees' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.employees).to.not.be.null; - expect(obj.employees.length).to.equal(1); - expect(obj.employees[0].last_name).to.equal('two'); - return obj.getEmployees({ searchPath: SEARCH_PATH_TWO }); - }).then(employees => { - expect(employees.length).to.equal(1); - expect(employees[0].last_name).to.equal('two'); - return Employee.findOne({ - where: { last_name: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ - model: Restaurant, as: 'restaurant' - }] - }); - }).then(obj => { - expect(obj).to.not.be.null; - expect(obj.restaurant).to.not.be.null; - expect(obj.restaurant.foo).to.equal('two'); - return obj.getRestaurant({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurant => { - expect(restaurant).to.not.be.null; - expect(restaurant.foo).to.equal('two'); + }, { searchPath: SEARCH_PATH_TWO }); + + const obj1 = await Restaurant.findOne({ + where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO }); + + expect(obj1).to.not.be.null; + expect(obj1.foo).to.equal('two'); + const restaurantId = obj1.id; + + await Employee.create({ + first_name: 'Restaurant', + last_name: 'two', + restaurant_id: restaurantId + }, { searchPath: SEARCH_PATH_TWO }); + + const obj0 = await Restaurant.findOne({ + where: { foo: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ + model: Employee, as: 'employees' + }] + }); + + expect(obj0).to.not.be.null; + expect(obj0.employees).to.not.be.null; + expect(obj0.employees.length).to.equal(1); + expect(obj0.employees[0].last_name).to.equal('two'); + const employees = await obj0.getEmployees({ searchPath: SEARCH_PATH_TWO }); + expect(employees.length).to.equal(1); + expect(employees[0].last_name).to.equal('two'); + + const obj = await Employee.findOne({ + where: { last_name: 'two' }, searchPath: SEARCH_PATH_TWO, include: [{ + model: Restaurant, as: 'restaurant' + }] + }); + + expect(obj).to.not.be.null; + expect(obj.restaurant).to.not.be.null; + expect(obj.restaurant.foo).to.equal('two'); + const restaurant = await obj.getRestaurant({ searchPath: SEARCH_PATH_TWO }); + expect(restaurant).to.not.be.null; + expect(restaurant.foo).to.equal('two'); }); }); describe('concurency tests', () => { - it('should build and persist instances to 2 schemas concurrently in any order', function() { + it('should build and persist instances to 2 schemas concurrently in any order', async function() { const Restaurant = this.Restaurant; - let restaurauntModelSchema1 = new Restaurant({ bar: 'one.1' }); - const restaurauntModelSchema2 = new Restaurant({ bar: 'two.1' }); - - return restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }) - .then(() => { - restaurauntModelSchema1 = new Restaurant({ bar: 'one.2' }); - return restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); - }).then(() => { - return restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); - }).then(() => { - return Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); - }).then(restaurantsOne => { - expect(restaurantsOne).to.not.be.null; - expect(restaurantsOne.length).to.equal(2); - restaurantsOne.forEach(restaurant => { - expect(restaurant.bar).to.contain('one'); - }); - return Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); - }).then(restaurantsTwo => { - expect(restaurantsTwo).to.not.be.null; - expect(restaurantsTwo.length).to.equal(1); - restaurantsTwo.forEach(restaurant => { - expect(restaurant.bar).to.contain('two'); - }); - }); + let restaurauntModelSchema1 = Restaurant.build({ bar: 'one.1' }); + const restaurauntModelSchema2 = Restaurant.build({ bar: 'two.1' }); + + await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); + restaurauntModelSchema1 = Restaurant.build({ bar: 'one.2' }); + await restaurauntModelSchema2.save({ searchPath: SEARCH_PATH_TWO }); + await restaurauntModelSchema1.save({ searchPath: SEARCH_PATH_ONE }); + const restaurantsOne = await Restaurant.findAll({ searchPath: SEARCH_PATH_ONE }); + expect(restaurantsOne).to.not.be.null; + expect(restaurantsOne.length).to.equal(2); + restaurantsOne.forEach(restaurant => { + expect(restaurant.bar).to.contain('one'); + }); + const restaurantsTwo = await Restaurant.findAll({ searchPath: SEARCH_PATH_TWO }); + expect(restaurantsTwo).to.not.be.null; + expect(restaurantsTwo.length).to.equal(1); + restaurantsTwo.forEach(restaurant => { + expect(restaurant.bar).to.contain('two'); + }); }); }); describe('Edit data via instance.update, retrieve updated instance via model.findAll', () => { - it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', function() { + it('should be able to update data via instance update in both schemas, and retrieve it via findAll with where', async function() { const Restaurant = this.Restaurant; - return Promise.all([ - Restaurant.create({ foo: 'one', bar: '1' }, { searchPath: SEARCH_PATH_ONE }) - .then(rnt => rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_ONE })), + const rnt = await Restaurant.create({ foo: 'one', bar: '1' }, { searchPath: SEARCH_PATH_ONE }); + + await Promise.all([ + await rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_ONE }), Restaurant.create({ foo: 'one', bar: '2' }, { searchPath: SEARCH_PATH_ONE }) .then(rnt => rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_ONE })), Restaurant.create({ foo: 'two', bar: '1' }, { searchPath: SEARCH_PATH_TWO }) .then(rnt => rnt.update({ bar: 'x.1' }, { searchPath: SEARCH_PATH_TWO })), Restaurant.create({ foo: 'two', bar: '2' }, { searchPath: SEARCH_PATH_TWO }) .then(rnt => rnt.update({ bar: 'x.2' }, { searchPath: SEARCH_PATH_TWO })) - ]).then(() => Promise.all([ - Restaurant.findAll({ - where: { bar: 'x.1' }, - searchPath: SEARCH_PATH_ONE - }).then(restaurantsOne => { + ]); + + await Promise.all([ + (async () => { + const restaurantsOne = await Restaurant.findAll({ + where: { bar: 'x.1' }, + searchPath: SEARCH_PATH_ONE + }); + expect(restaurantsOne.length).to.equal(1); expect(restaurantsOne[0].foo).to.equal('one'); expect(restaurantsOne[0].bar).to.equal('x.1'); - }), - Restaurant.findAll({ - where: { bar: 'x.2' }, - searchPath: SEARCH_PATH_TWO - }).then(restaurantsTwo => { + })(), + (async () => { + const restaurantsTwo = await Restaurant.findAll({ + where: { bar: 'x.2' }, + searchPath: SEARCH_PATH_TWO + }); + expect(restaurantsTwo.length).to.equal(1); expect(restaurantsTwo[0].foo).to.equal('two'); expect(restaurantsTwo[0].bar).to.equal('x.2'); - }) - ])); + })() + ]); }); }); }); diff --git a/test/integration/model/sum.test.js b/test/integration/model/sum.test.js index 17dff1be2b2d..081592e3a94f 100644 --- a/test/integration/model/sum.test.js +++ b/test/integration/model/sum.test.js @@ -6,7 +6,7 @@ const chai = require('chai'), DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { - beforeEach(function() { + beforeEach(async function() { this.Payment = this.sequelize.define('Payment', { amount: DataTypes.DECIMAL, mood: { @@ -15,28 +15,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }).then(() => { - return this.Payment.bulkCreate([ - { amount: 5, mood: 'neutral' }, - { amount: -5, mood: 'neutral' }, - { amount: 10, mood: 'happy' }, - { amount: 90, mood: 'happy' } - ]); - }); + await this.sequelize.sync({ force: true }); + + await this.Payment.bulkCreate([ + { amount: 5, mood: 'neutral' }, + { amount: -5, mood: 'neutral' }, + { amount: 10, mood: 'happy' }, + { amount: 90, mood: 'happy' } + ]); }); describe('sum', () => { - it('should sum without rows', function() { - return expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).to.eventually.be.equal(0); + it('should sum without rows', async function() { + await expect(this.Payment.sum('amount', { where: { mood: 'sad' } })).to.eventually.be.null; }); - it('should sum when is 0', function() { - return expect(this.Payment.sum('amount', { where: { mood: 'neutral' } })).to.eventually.be.equal(0); + it('should sum when is 0', async function() { + await expect(this.Payment.sum('amount', { where: { mood: 'neutral' } })).to.eventually.be.equal(0); }); - it('should sum', function() { - return expect(this.Payment.sum('amount', { where: { mood: 'happy' } })).to.eventually.be.equal(100); + it('should sum', async function() { + await expect(this.Payment.sum('amount', { where: { mood: 'happy' } })).to.eventually.be.equal(100); }); }); }); diff --git a/test/integration/model/sync.test.js b/test/integration/model/sync.test.js index 6e42b2280846..c615efc78595 100644 --- a/test/integration/model/sync.test.js +++ b/test/integration/model/sync.test.js @@ -8,137 +8,168 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Model'), () => { describe('sync', () => { - beforeEach(function() { + beforeEach(async function() { this.testSync = this.sequelize.define('testSync', { dummy: Sequelize.STRING }); - return this.testSync.drop(); + await this.testSync.drop(); }); - it('should remove a column if it exists in the databases schema but not the model', function() { + it('should remove a column if it exists in the databases schema but not the model', async function() { const User = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.INTEGER, badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } }); - return this.sequelize.sync() - .then(() => { - this.sequelize.define('testSync', { - name: Sequelize.STRING - }); - }) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => User.describe()) - .then(data => { - expect(data).to.not.have.ownProperty('age'); - expect(data).to.not.have.ownProperty('badge_number'); - expect(data).to.not.have.ownProperty('badgeNumber'); - expect(data).to.have.ownProperty('name'); - }); + await this.sequelize.sync(); + this.sequelize.define('testSync', { + name: Sequelize.STRING + }); + await this.sequelize.sync({ alter: true }); + const data = await User.describe(); + expect(data).to.not.have.ownProperty('age'); + expect(data).to.not.have.ownProperty('badge_number'); + expect(data).to.not.have.ownProperty('badgeNumber'); + expect(data).to.have.ownProperty('name'); }); - it('should add a column if it exists in the model but not the database', function() { + it('should add a column if it exists in the model but not the database', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.INTEGER, - height: { type: Sequelize.INTEGER, field: 'height_cm' } - })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('age'); - expect(data).to.have.ownProperty('height_cm'); - expect(data).not.to.have.ownProperty('height'); - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING, + age: Sequelize.INTEGER, + height: { type: Sequelize.INTEGER, field: 'height_cm' } + }); + + await this.sequelize.sync({ alter: true }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('age'); + expect(data).to.have.ownProperty('height_cm'); + expect(data).not.to.have.ownProperty('height'); }); - it('should alter a column using the correct column name (#9515)', function() { + it('should not remove columns if drop is set to false in alter configuration', async function() { const testSync = this.sequelize.define('testSync', { + name: Sequelize.STRING, + age: Sequelize.INTEGER + }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { name: Sequelize.STRING }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING, - badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } - })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('badge_number'); - expect(data).not.to.have.ownProperty('badgeNumber'); - }); + + await this.sequelize.sync({ alter: { drop: false } }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('name'); + expect(data).to.have.ownProperty('age'); }); - it('should change a column if it exists in the model but is different in the database', function() { + it('should remove columns if drop is set to true in alter configuration', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.INTEGER }); - return this.sequelize.sync() - .then(() => this.sequelize.define('testSync', { - name: Sequelize.STRING, - age: Sequelize.STRING - })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.describe()) - .then(data => { - expect(data).to.have.ownProperty('age'); - expect(data.age.type).to.have.string('CHAR'); // CHARACTER VARYING, VARCHAR(n) - }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING + }); + + await this.sequelize.sync({ alter: { drop: true } }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('name'); + expect(data).not.to.have.ownProperty('age'); }); - it('should not alter table if data type does not change', function() { + it('should alter a column using the correct column name (#9515)', async function() { const testSync = this.sequelize.define('testSync', { + name: Sequelize.STRING + }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { + name: Sequelize.STRING, + badgeNumber: { type: Sequelize.INTEGER, field: 'badge_number' } + }); + + await this.sequelize.sync({ alter: true }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('badge_number'); + expect(data).not.to.have.ownProperty('badgeNumber'); + }); + + it('should change a column if it exists in the model but is different in the database', async function() { + const testSync = this.sequelize.define('testSync', { + name: Sequelize.STRING, + age: Sequelize.INTEGER + }); + await this.sequelize.sync(); + + await this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.STRING }); - return this.sequelize.sync() - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(() => this.sequelize.sync({ alter: true })) - .then(() => testSync.findOne()) - .then(data => { - expect(data.dataValues.name).to.eql('test'); - expect(data.dataValues.age).to.eql('1'); - }); + + await this.sequelize.sync({ alter: true }); + const data = await testSync.describe(); + expect(data).to.have.ownProperty('age'); + expect(data.age.type).to.have.string('CHAR'); // CHARACTER VARYING, VARCHAR(n) }); - it('should properly create composite index without affecting individual fields', function() { + it('should not alter table if data type does not change', async function() { + const testSync = this.sequelize.define('testSync', { + name: Sequelize.STRING, + age: Sequelize.STRING + }); + await this.sequelize.sync(); + await testSync.create({ name: 'test', age: '1' }); + await this.sequelize.sync({ alter: true }); + const data = await testSync.findOne(); + expect(data.dataValues.name).to.eql('test'); + expect(data.dataValues.age).to.eql('1'); + }); + + it('should properly create composite index without affecting individual fields', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.STRING }, { indexes: [{ unique: true, fields: ['name', 'age'] }] }); - return this.sequelize.sync() - .then(() => testSync.create({ name: 'test' })) - .then(() => testSync.create({ name: 'test2' })) - .then(() => testSync.create({ name: 'test3' })) - .then(() => testSync.create({ age: '1' })) - .then(() => testSync.create({ age: '2' })) - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(() => testSync.create({ name: 'test', age: '2' })) - .then(() => testSync.create({ name: 'test2', age: '2' })) - .then(() => testSync.create({ name: 'test3', age: '2' })) - .then(() => testSync.create({ name: 'test3', age: '1' })) - .then(data => { - expect(data.dataValues.name).to.eql('test3'); - expect(data.dataValues.age).to.eql('1'); - }); + await this.sequelize.sync(); + await testSync.create({ name: 'test' }); + await testSync.create({ name: 'test2' }); + await testSync.create({ name: 'test3' }); + await testSync.create({ age: '1' }); + await testSync.create({ age: '2' }); + await testSync.create({ name: 'test', age: '1' }); + await testSync.create({ name: 'test', age: '2' }); + await testSync.create({ name: 'test2', age: '2' }); + await testSync.create({ name: 'test3', age: '2' }); + const data = await testSync.create({ name: 'test3', age: '1' }); + expect(data.dataValues.name).to.eql('test3'); + expect(data.dataValues.age).to.eql('1'); }); - it('should properly create composite index that fails on constraint violation', function() { + it('should properly create composite index that fails on constraint violation', async function() { const testSync = this.sequelize.define('testSync', { name: Sequelize.STRING, age: Sequelize.STRING }, { indexes: [{ unique: true, fields: ['name', 'age'] }] }); - return this.sequelize.sync() - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(() => testSync.create({ name: 'test', age: '1' })) - .then(data => expect(data).not.to.be.ok, error => expect(error).to.be.ok); + + try { + await this.sequelize.sync(); + await testSync.create({ name: 'test', age: '1' }); + const data = await testSync.create({ name: 'test', age: '1' }); + await expect(data).not.to.be.ok; + } catch (error) { + await expect(error).to.be.ok; + } }); - it('should properly alter tables when there are foreign keys', function() { + it('should properly alter tables when there are foreign keys', async function() { const foreignKeyTestSyncA = this.sequelize.define('foreignKeyTestSyncA', { dummy: Sequelize.STRING }); @@ -150,13 +181,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { foreignKeyTestSyncA.hasMany(foreignKeyTestSyncB); foreignKeyTestSyncB.belongsTo(foreignKeyTestSyncA); - return this.sequelize.sync({ alter: true }) - .then(() => this.sequelize.sync({ alter: true })); + await this.sequelize.sync({ alter: true }); + + await this.sequelize.sync({ alter: true }); }); describe('indexes', () => { describe('with alter:true', () => { - it('should not duplicate named indexes after multiple sync calls', function() { + it('should not duplicate named indexes after multiple sync calls', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING @@ -176,28 +208,30 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ sync: true }) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => this.sequelize.getQueryInterface().showIndex(User.getTableName())) - .then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(4); - } else { - expect(results).to.have.length(4 + 1); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.name === 'another_index_email_mobile')).to.have.length(1); - expect(results.filter(r => r.name === 'another_index_phone_mobile')).to.have.length(1); - expect(results.filter(r => r.name === 'another_index_email')).to.have.length(1); - expect(results.filter(r => r.name === 'another_index_mobile')).to.have.length(1); - }); + await User.sync({ sync: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + // However it does create an extra "autoindex", except primary == false + expect(results).to.have.length(4 + 1); + } else { + expect(results).to.have.length(4 + 1); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + if (dialect === 'sqlite') { + expect(results.filter(r => r.name === 'sqlite_autoindex_testSyncs_1')).to.have.length(1); + } + expect(results.filter(r => r.name === 'another_index_email_mobile')).to.have.length(1); + expect(results.filter(r => r.name === 'another_index_phone_mobile')).to.have.length(1); + expect(results.filter(r => r.name === 'another_index_email')).to.have.length(1); + expect(results.filter(r => r.name === 'another_index_mobile')).to.have.length(1); }); - it('should not duplicate unnamed indexes after multiple sync calls', function() { + it('should not duplicate unnamed indexes after multiple sync calls', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING @@ -217,24 +251,23 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ sync: true }) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => User.sync({ alter: true })) - .then(() => this.sequelize.getQueryInterface().showIndex(User.getTableName())) - .then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(4); - } else { - expect(results).to.have.length(4 + 1); - expect(results.filter(r => r.primary)).to.have.length(1); - } - }); + await User.sync({ sync: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + await User.sync({ alter: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + // However it does create an extra "autoindex", except primary == false + expect(results).to.have.length(4 + 1); + } else { + expect(results).to.have.length(4 + 1); + expect(results.filter(r => r.primary)).to.have.length(1); + } }); }); - it('should create only one unique index for unique:true column', function() { + it('should create only one unique index for unique:true column', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -242,22 +275,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(1); - } else { - expect(results).to.have.length(2); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(1); + } else { + expect(results).to.have.length(2); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); }); - it('should create only one unique index for unique:true columns', function() { + it('should create only one unique index for unique:true columns', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -269,22 +300,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(2); - } else { - expect(results).to.have.length(3); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(2); - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(2); + } else { + expect(results).to.have.length(3); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(2); }); - it('should create only one unique index for unique:true columns taking care of options.indexes', function() { + it('should create only one unique index for unique:true columns taking care of options.indexes', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -300,23 +329,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { ] }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(3); - } else { - expect(results).to.have.length(4); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(3); - expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(3); + } else { + expect(results).to.have.length(4); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(3); + expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); }); - it('should create only one unique index for unique:name column', function() { + it('should create only one unique index for unique:name column', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -324,27 +351,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(1); - } else { - expect(results).to.have.length(2); - expect(results.filter(r => r.primary)).to.have.length(1); - } + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(1); + } else { + expect(results).to.have.length(2); + expect(results.filter(r => r.primary)).to.have.length(1); + } - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); - if (!['postgres', 'sqlite'].includes(dialect)) { - // Postgres/SQLite doesn't support naming indexes in create table - expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); - } - }); + if (!['postgres', 'sqlite'].includes(dialect)) { + // Postgres/SQLite doesn't support naming indexes in create table + expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); + } }); - it('should create only one unique index for unique:name columns', function() { + it('should create only one unique index for unique:name columns', async function() { const User = this.sequelize.define('testSync', { email: { type: Sequelize.STRING, @@ -356,23 +381,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showIndex(User.getTableName()); - }).then(results => { - if (dialect === 'sqlite') { - // SQLite doesn't treat primary key as index - expect(results).to.have.length(1); - } else { - expect(results).to.have.length(2); - expect(results.filter(r => r.primary)).to.have.length(1); - } - - expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); - if (!['postgres', 'sqlite'].includes(dialect)) { - // Postgres/SQLite doesn't support naming indexes in create table - expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); - } - }); + await User.sync({ force: true }); + const results = await this.sequelize.getQueryInterface().showIndex(User.getTableName()); + if (dialect === 'sqlite') { + // SQLite doesn't treat primary key as index + expect(results).to.have.length(1); + } else { + expect(results).to.have.length(2); + expect(results.filter(r => r.primary)).to.have.length(1); + } + + expect(results.filter(r => r.unique === true && r.primary === false)).to.have.length(1); + if (!['postgres', 'sqlite'].includes(dialect)) { + // Postgres/SQLite doesn't support naming indexes in create table + expect(results.filter(r => r.name === 'wow_my_index')).to.have.length(1); + } }); }); }); diff --git a/test/integration/model/update.test.js b/test/integration/model/update.test.js index 862f7ccae985..fda69409baff 100644 --- a/test/integration/model/update.test.js +++ b/test/integration/model/update.test.js @@ -10,7 +10,7 @@ const _ = require('lodash'); describe(Support.getTestDialectTeaser('Model'), () => { describe('update', () => { - beforeEach(function() { + beforeEach(async function() { this.Account = this.sequelize.define('Account', { ownerId: { type: DataTypes.INTEGER, @@ -21,41 +21,42 @@ describe(Support.getTestDialectTeaser('Model'), () => { type: DataTypes.STRING } }); - return this.Account.sync({ force: true }); + await this.Account.sync({ force: true }); }); - it('should only update the passed fields', function() { - return this.Account - .create({ ownerId: 2 }) - .then(account => this.Account.update({ - name: Math.random().toString() - }, { - where: { - id: account.get('id') - } - })); + it('should only update the passed fields', async function() { + const account = await this.Account + .create({ ownerId: 2 }); + + await this.Account.update({ + name: Math.random().toString() + }, { + where: { + id: account.get('id') + } + }); }); describe('skips update query', () => { - it('if no data to update', function() { + it('if no data to update', async function() { const spy = sinon.spy(); - return this.Account.create({ ownerId: 3 }).then(() => { - return this.Account.update({ - unknownField: 'haha' - }, { - where: { - ownerId: 3 - }, - logging: spy - }); - }).then(result => { - expect(result[0]).to.equal(0); - expect(spy.called, 'Update query was issued when no data to update').to.be.false; + await this.Account.create({ ownerId: 3 }); + + const result = await this.Account.update({ + unknownField: 'haha' + }, { + where: { + ownerId: 3 + }, + logging: spy }); + + expect(result[0]).to.equal(0); + expect(spy.called, 'Update query was issued when no data to update').to.be.false; }); - it('skips when timestamps disabled', function() { + it('skips when timestamps disabled', async function() { const Model = this.sequelize.define('Model', { ownerId: { type: DataTypes.INTEGER, @@ -70,105 +71,113 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); const spy = sinon.spy(); - return Model.sync({ force: true }) - .then(() => Model.create({ ownerId: 3 })) - .then(() => { - return Model.update({ - unknownField: 'haha' - }, { - where: { - ownerId: 3 - }, - logging: spy - }); - }) - .then(result => { - expect(result[0]).to.equal(0); - expect(spy.called, 'Update query was issued when no data to update').to.be.false; - }); + await Model.sync({ force: true }); + await Model.create({ ownerId: 3 }); + + const result = await Model.update({ + unknownField: 'haha' + }, { + where: { + ownerId: 3 + }, + logging: spy + }); + + expect(result[0]).to.equal(0); + expect(spy.called, 'Update query was issued when no data to update').to.be.false; }); }); - it('changed should be false after reload', function() { - return this.Account.create({ ownerId: 2, name: 'foo' }) - .then(account => { - account.name = 'bar'; - expect(account.changed()[0]).to.equal('name'); - return account.reload(); - }) - .then(account => { - expect(account.changed()).to.equal(false); - }); + it('changed should be false after reload', async function() { + const account0 = await this.Account.create({ ownerId: 2, name: 'foo' }); + account0.name = 'bar'; + expect(account0.changed()[0]).to.equal('name'); + const account = await account0.reload(); + expect(account.changed()).to.equal(false); }); - it('should ignore undefined values without throwing not null validation', function() { + it('should ignore undefined values without throwing not null validation', async function() { const ownerId = 2; - return this.Account.create({ + + const account0 = await this.Account.create({ ownerId, name: Math.random().toString() - }).then(account => { - return this.Account.update({ - name: Math.random().toString(), - ownerId: undefined - }, { + }); + + await this.Account.update({ + name: Math.random().toString(), + ownerId: undefined + }, { + where: { + id: account0.get('id') + } + }); + + const account = await this.Account.findOne(); + expect(account.ownerId).to.be.equal(ownerId); + }); + + if (_.get(current.dialect.supports, 'returnValues.returning')) { + it('should return the updated record', async function() { + const account = await this.Account.create({ ownerId: 2 }); + + const [, accounts] = await this.Account.update({ name: 'FooBar' }, { where: { id: account.get('id') - } + }, + returning: true }); - }).then(() => { - return this.Account.findOne(); - }).then(account => { - expect(account.ownerId).to.be.equal(ownerId); + + const firstAcc = accounts[0]; + expect(firstAcc.ownerId).to.be.equal(2); + expect(firstAcc.name).to.be.equal('FooBar'); }); - }); + } - if (_.get(current.dialect.supports, 'returnValues.returning')) { - it('should return the updated record', function() { - return this.Account.create({ ownerId: 2 }).then(account => { - return this.Account.update({ name: 'FooBar' }, { - where: { - id: account.get('id') - }, - returning: true - }).then(([, accounts]) => { - const firstAcc = accounts[0]; - expect(firstAcc.ownerId).to.be.equal(2); - expect(firstAcc.name).to.be.equal('FooBar'); - }); + if (_.get(current.dialect.supports, 'returnValues.output')) { + it('should output the updated record', async function() { + const account = await this.Account.create({ ownerId: 2 }); + + const [, accounts] = await this.Account.update({ name: 'FooBar' }, { + where: { + id: account.get('id') + }, + returning: true }); + + const firstAcc = accounts[0]; + expect(firstAcc.name).to.be.equal('FooBar'); + + await firstAcc.reload(); + expect(firstAcc.ownerId, 'Reloaded as output update only return primary key and changed fields').to.be.equal(2); }); } if (current.dialect.supports['LIMIT ON UPDATE']) { - it('should only update one row', function() { - return this.Account.create({ + it('should only update one row', async function() { + await this.Account.create({ ownerId: 2, name: 'Account Name 1' - }) - .then(() => { - return this.Account.create({ - ownerId: 2, - name: 'Account Name 2' - }); - }) - .then(() => { - return this.Account.create({ - ownerId: 2, - name: 'Account Name 3' - }); - }) - .then(() => { - const options = { - where: { - ownerId: 2 - }, - limit: 1 - }; - return this.Account.update({ name: 'New Name' }, options); - }) - .then(account => { - expect(account[0]).to.equal(1); - }); + }); + + await this.Account.create({ + ownerId: 2, + name: 'Account Name 2' + }); + + await this.Account.create({ + ownerId: 2, + name: 'Account Name 3' + }); + + const options = { + where: { + ownerId: 2 + }, + limit: 1 + }; + const account = await this.Account.update({ name: 'New Name' }, options); + expect(account[0]).to.equal(1); }); } }); diff --git a/test/integration/model/upsert.test.js b/test/integration/model/upsert.test.js index 597fcd4afdb5..26e51c7bbc26 100644 --- a/test/integration/model/upsert.test.js +++ b/test/integration/model/upsert.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), Sequelize = require('../../../index'), - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../../lib/data-types'), @@ -23,7 +22,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.clock.reset(); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { username: DataTypes.STRING, foo: { @@ -55,62 +54,56 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); if (current.dialect.supports.upserts) { describe('upsert', () => { - it('works with upsert on id', function() { - return this.User.upsert({ id: 42, username: 'john' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ id: 42, username: 'doe' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findByPk(42); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); + it('works with upsert on id', async function() { + const [, created0] = await this.User.upsert({ id: 42, username: 'john' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } + + this.clock.tick(1000); + const [, created] = await this.User.upsert({ id: 42, username: 'doe' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + + const user = await this.User.findByPk(42); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('works with upsert on a composite key', function() { - return this.User.upsert({ foo: 'baz', bar: 19, username: 'john' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findOne({ where: { foo: 'baz', bar: 19 } }); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); + it('works with upsert on a composite key', async function() { + const [, created0] = await this.User.upsert({ foo: 'baz', bar: 19, username: 'john' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } + + this.clock.tick(1000); + const [, created] = await this.User.upsert({ foo: 'baz', bar: 19, username: 'doe' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + + const user = await this.User.findOne({ where: { foo: 'baz', bar: 19 } }); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('should work with UUIDs wth default values', function() { + it('should work with UUIDs wth default values', async function() { const User = this.sequelize.define('User', { id: { primaryKey: true, @@ -119,18 +112,16 @@ describe(Support.getTestDialectTeaser('Model'), () => { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4 }, - name: { type: Sequelize.STRING } }); - return User.sync({ force: true }).then(() => { - return User.upsert({ name: 'John Doe' }); - }); + await User.sync({ force: true }); + await User.upsert({ name: 'John Doe' }); }); - it('works with upsert on a composite primary key', function() { + it('works with upsert on a composite primary key', async function() { const User = this.sequelize.define('user', { a: { type: Sequelize.STRING, @@ -143,47 +134,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return Promise.all([ - // Create two users - User.upsert({ a: 'a', b: 'b', username: 'john' }), - User.upsert({ a: 'a', b: 'a', username: 'curt' }) - ]); - }).then(([created1, created2]) => { - if (dialect === 'sqlite') { - expect(created1).to.be.undefined; - expect(created2).to.be.undefined; - } else { - expect(created1).to.be.ok; - expect(created2).to.be.ok; - } - - this.clock.tick(1000); - // Update the first one - return User.upsert({ a: 'a', b: 'b', username: 'doe' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return User.findOne({ where: { a: 'a', b: 'b' } }); - }).then(user1 => { - expect(user1.createdAt).to.be.ok; - expect(user1.username).to.equal('doe'); - expect(user1.updatedAt).to.be.afterTime(user1.createdAt); - - return User.findOne({ where: { a: 'a', b: 'a' } }); - }).then(user2 => { - // The second one should not be updated - expect(user2.createdAt).to.be.ok; - expect(user2.username).to.equal('curt'); - expect(user2.updatedAt).to.equalTime(user2.createdAt); - }); + await User.sync({ force: true }); + + const [created1, created2] = await Promise.all([ + // Create two users + User.upsert({ a: 'a', b: 'b', username: 'john' }), + User.upsert({ a: 'a', b: 'a', username: 'curt' }) + ]); + + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created1[1]).to.be.null; + expect(created2[1]).to.be.null; + } else { + expect(created1[1]).to.be.true; + expect(created2[1]).to.be.true; + } + + this.clock.tick(1000); + // Update the first one + const [, created] = await User.upsert({ a: 'a', b: 'b', username: 'doe' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + + const user1 = await User.findOne({ where: { a: 'a', b: 'b' } }); + expect(user1.createdAt).to.be.ok; + expect(user1.username).to.equal('doe'); + expect(user1.updatedAt).to.be.afterTime(user1.createdAt); + + const user2 = await User.findOne({ where: { a: 'a', b: 'a' } }); + // The second one should not be updated + expect(user2.createdAt).to.be.ok; + expect(user2.username).to.equal('curt'); + expect(user2.updatedAt).to.equalTime(user2.createdAt); }); - it('supports validations', function() { + it('supports validations', async function() { const User = this.sequelize.define('user', { email: { type: Sequelize.STRING, @@ -193,10 +181,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return expect(User.upsert({ email: 'notanemail' })).to.eventually.be.rejectedWith(Sequelize.ValidationError); + await expect(User.upsert({ email: 'notanemail' })).to.eventually.be.rejectedWith(Sequelize.ValidationError); }); - it('supports skipping validations', function() { + it('supports skipping validations', async function() { const User = this.sequelize.define('user', { email: { type: Sequelize.STRING, @@ -208,169 +196,151 @@ describe(Support.getTestDialectTeaser('Model'), () => { const options = { validate: false }; - return User.sync({ force: true }) - .then(() => User.upsert({ id: 1, email: 'notanemail' }, options)) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - }); + await User.sync({ force: true }); + const [, created] = await User.upsert({ id: 1, email: 'notanemail' }, options); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.true; + } }); - it('works with BLOBs', function() { - return this.User.upsert({ id: 42, username: 'john', blob: Buffer.from('kaj') }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.User.upsert({ id: 42, username: 'doe', blob: Buffer.from('andrea') }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findByPk(42); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.blob.toString()).to.equal('andrea'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); + it('works with BLOBs', async function() { + const [, created0] = await this.User.upsert({ id: 42, username: 'john', blob: Buffer.from('kaj') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const [, created] = await this.User.upsert({ id: 42, username: 'doe', blob: Buffer.from('andrea') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + + const user = await this.User.findByPk(42); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.blob.toString()).to.equal('andrea'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('works with .field', function() { - return this.User.upsert({ id: 42, baz: 'foo' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - return this.User.upsert({ id: 42, baz: 'oof' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.User.findByPk(42); - }).then(user => { - expect(user.baz).to.equal('oof'); - }); + it('works with .field', async function() { + const [, created0] = await this.User.upsert({ id: 42, baz: 'foo' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + + const [, created] = await this.User.upsert({ id: 42, baz: 'oof' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + + const user = await this.User.findByPk(42); + expect(user.baz).to.equal('oof'); }); - it('works with primary key using .field', function() { - return this.ModelWithFieldPK.upsert({ userId: 42, foo: 'first' }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - - this.clock.tick(1000); - return this.ModelWithFieldPK.upsert({ userId: 42, foo: 'second' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - - return this.ModelWithFieldPK.findOne({ where: { userId: 42 } }); - }).then(instance => { - expect(instance.foo).to.equal('second'); - }); + it('works with primary key using .field', async function() { + const [, created0] = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'first' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const [, created] = await this.ModelWithFieldPK.upsert({ userId: 42, foo: 'second' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + + const instance = await this.ModelWithFieldPK.findOne({ where: { userId: 42 } }); + expect(instance.foo).to.equal('second'); }); - it('works with database functions', function() { - return this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1') }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } + it('works with database functions', async function() { + const [, created0] = await this.User.upsert({ id: 42, username: 'john', foo: this.sequelize.fn('upper', 'mixedCase1') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + + this.clock.tick(1000); + const [, created] = await this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + const user = await this.User.findByPk(42); + expect(user.createdAt).to.be.ok; + expect(user.username).to.equal('doe'); + expect(user.foo).to.equal('MIXEDCASE2'); + }); - this.clock.tick(1000); - return this.User.upsert({ id: 42, username: 'doe', foo: this.sequelize.fn('upper', 'mixedCase2') }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - return this.User.findByPk(42); - }).then(user => { - expect(user.createdAt).to.be.ok; - expect(user.username).to.equal('doe'); - expect(user.foo).to.equal('MIXEDCASE2'); - }); + it('does not overwrite createdAt time on update', async function() { + const clock = sinon.useFakeTimers(); + await this.User.create({ id: 42, username: 'john' }); + const user0 = await this.User.findByPk(42); + const originalCreatedAt = user0.createdAt; + const originalUpdatedAt = user0.updatedAt; + clock.tick(5000); + await this.User.upsert({ id: 42, username: 'doe' }); + const user = await this.User.findByPk(42); + expect(user.updatedAt).to.be.gt(originalUpdatedAt); + expect(user.createdAt).to.deep.equal(originalCreatedAt); + clock.restore(); }); - it('does not overwrite createdAt time on update', function() { - let originalCreatedAt; - let originalUpdatedAt; + it('does not overwrite createdAt when supplied as an explicit insert value when using fields', async function() { const clock = sinon.useFakeTimers(); - return this.User.create({ id: 42, username: 'john' }).then(() => { - return this.User.findByPk(42); - }).then(user => { - originalCreatedAt = user.createdAt; - originalUpdatedAt = user.updatedAt; - clock.tick(5000); - return this.User.upsert({ id: 42, username: 'doe' }); - }).then(() => { - return this.User.findByPk(42); - }).then(user => { - expect(user.updatedAt).to.be.gt(originalUpdatedAt); - expect(user.createdAt).to.deep.equal(originalCreatedAt); - clock.restore(); - }); + const originalCreatedAt = new Date('2010-01-01T12:00:00.000Z'); + await this.User.upsert({ id: 42, username: 'john', createdAt: originalCreatedAt }, { fields: ['id', 'username'] }); + const user = await this.User.findByPk(42); + expect(user.createdAt).to.deep.equal(originalCreatedAt); + clock.restore(); }); - it('does not update using default values', function() { - return this.User.create({ id: 42, username: 'john', baz: 'new baz value' }).then(() => { - return this.User.findByPk(42); - }).then(user => { - // 'username' should be 'john' since it was set - expect(user.username).to.equal('john'); - // 'baz' should be 'new baz value' since it was set - expect(user.baz).to.equal('new baz value'); - return this.User.upsert({ id: 42, username: 'doe' }); - }).then(() => { - return this.User.findByPk(42); - }).then(user => { - // 'username' was updated - expect(user.username).to.equal('doe'); - // 'baz' should still be 'new baz value' since it was not updated - expect(user.baz).to.equal('new baz value'); - }); + it('does not update using default values', async function() { + await this.User.create({ id: 42, username: 'john', baz: 'new baz value' }); + const user0 = await this.User.findByPk(42); + // 'username' should be 'john' since it was set + expect(user0.username).to.equal('john'); + // 'baz' should be 'new baz value' since it was set + expect(user0.baz).to.equal('new baz value'); + await this.User.upsert({ id: 42, username: 'doe' }); + const user = await this.User.findByPk(42); + // 'username' was updated + expect(user.username).to.equal('doe'); + // 'baz' should still be 'new baz value' since it was not updated + expect(user.baz).to.equal('new baz value'); }); - it('does not update when setting current values', function() { - return this.User.create({ id: 42, username: 'john' }).then(() => { - return this.User.findByPk(42); - }).then(user => { - return this.User.upsert({ id: user.id, username: user.username }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - // After set node-mysql flags = '-FOUND_ROWS' / foundRows=false - // result from upsert should be false when upsert a row to its current value - // https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html - expect(created).to.equal(false); - } - }); + it('does not update when setting current values', async function() { + await this.User.create({ id: 42, username: 'john' }); + const user = await this.User.findByPk(42); + const [, created] = await this.User.upsert({ id: user.id, username: user.username }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + // After set node-mysql flags = '-FOUND_ROWS' / foundRows=false + // result from upsert should be false when upsert a row to its current value + // https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html + expect(created).to.equal(false); + } }); - it('works when two separate uniqueKeys are passed', function() { + it('works when two separate uniqueKeys are passed', async function() { const User = this.sequelize.define('User', { username: { type: Sequelize.STRING, @@ -385,34 +355,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); const clock = sinon.useFakeTimers(); - return User.sync({ force: true }).then(() => { - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - clock.tick(1000); - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - clock.tick(1000); - return User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); - }) - .then(user => { - expect(user.createdAt).to.be.ok; - expect(user.city).to.equal('New City'); - expect(user.updatedAt).to.be.afterTime(user.createdAt); - }); - }); + await User.sync({ force: true }); + const [, created0] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + clock.tick(1000); + const [, created] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + clock.tick(1000); + const user = await User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); + expect(user.createdAt).to.be.ok; + expect(user.city).to.equal('New City'); + expect(user.updatedAt).to.be.afterTime(user.createdAt); }); - it('works when indexes are created via indexes array', function() { + it('works when indexes are created via indexes array', async function() { const User = this.sequelize.define('User', { username: Sequelize.STRING, email: Sequelize.STRING, @@ -427,31 +391,25 @@ describe(Support.getTestDialectTeaser('Model'), () => { }] }); - return User.sync({ force: true }).then(() => { - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - return User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - return User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); - }) - .then(user => { - expect(user.createdAt).to.be.ok; - expect(user.city).to.equal('New City'); - }); - }); + await User.sync({ force: true }); + const [, created0] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + const [, created] = await User.upsert({ username: 'user1', email: 'user1@domain.ext', city: 'New City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.false; + } + const user = await User.findOne({ where: { username: 'user1', email: 'user1@domain.ext' } }); + expect(user.createdAt).to.be.ok; + expect(user.city).to.equal('New City'); }); - it('works when composite indexes are created via indexes array', () => { + it('works when composite indexes are created via indexes array', async () => { const User = current.define('User', { name: DataTypes.STRING, address: DataTypes.STRING, @@ -463,32 +421,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { }] }); - return User.sync({ force: true }).then(() => { - return User.upsert({ name: 'user1', address: 'address', city: 'City' }) - .then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).to.be.ok; - } - return User.upsert({ name: 'user1', address: 'address', city: 'New City' }); - }).then(created => { - if (dialect === 'sqlite') { - expect(created).to.be.undefined; - } else { - expect(created).not.to.be.ok; - } - return User.findOne({ where: { name: 'user1', address: 'address' } }); - }) - .then(user => { - expect(user.createdAt).to.be.ok; - expect(user.city).to.equal('New City'); - }); - }); + await User.sync({ force: true }); + const [, created0] = await User.upsert({ name: 'user1', address: 'address', city: 'City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.ok; + } + const [, created] = await User.upsert({ name: 'user1', address: 'address', city: 'New City' }); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).not.to.be.ok; + } + const user = await User.findOne({ where: { name: 'user1', address: 'address' } }); + expect(user.createdAt).to.be.ok; + expect(user.city).to.equal('New City'); }); if (dialect === 'mssql') { - it('Should throw foreignKey violation for MERGE statement as ForeignKeyConstraintError', function() { + it('Should throw foreignKey violation for MERGE statement as ForeignKeyConstraintError', async function() { const User = this.sequelize.define('User', { username: { type: DataTypes.STRING, @@ -503,16 +455,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { username: DataTypes.STRING }); Posts.belongsTo(User, { foreignKey: 'username' }); - return this.sequelize.sync({ force: true }) - .then(() => User.create({ username: 'user1' })) - .then(() => { - return expect(Posts.upsert({ title: 'Title', username: 'user2' })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'user1' }); + await expect(Posts.upsert({ title: 'Title', username: 'user2' })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); } if (dialect.match(/^postgres/)) { - it('works when deletedAt is Infinity and part of primary key', function() { + it('works when deletedAt is Infinity and part of primary key', async function() { const User = this.sequelize.define('User', { name: { type: DataTypes.STRING, @@ -529,43 +479,49 @@ describe(Support.getTestDialectTeaser('Model'), () => { paranoid: true }); - return User.sync({ force: true }).then(() => { - return Promise.all([ - User.create({ name: 'user1' }), - User.create({ name: 'user2', deletedAt: Infinity }), - - // this record is soft deleted - User.create({ name: 'user3', deletedAt: -Infinity }) - ]).then(() => { - return User.upsert({ name: 'user1', address: 'address' }); - }).then(() => { - return User.findAll({ - where: { address: null } - }); - }).then(users => { - expect(users).to.have.lengthOf(2); - }); + await User.sync({ force: true }); + + await Promise.all([ + User.create({ name: 'user1' }), + User.create({ name: 'user2', deletedAt: Infinity }), + + // this record is soft deleted + User.create({ name: 'user3', deletedAt: -Infinity }) + ]); + + await User.upsert({ name: 'user1', address: 'address' }); + + const users = await User.findAll({ + where: { address: null } }); + + expect(users).to.have.lengthOf(2); }); } if (current.dialect.supports.returnValues) { - describe('with returning option', () => { - it('works with upsert on id', function() { - return this.User.upsert({ id: 42, username: 'john' }, { returning: true }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('john'); - expect(created).to.be.true; + describe('returns values', () => { + it('works with upsert on id', async function() { + const [user0, created0] = await this.User.upsert({ id: 42, username: 'john' }, { returning: true }); + expect(user0.get('id')).to.equal(42); + expect(user0.get('username')).to.equal('john'); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } - return this.User.upsert({ id: 42, username: 'doe' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('doe'); + const [user, created] = await this.User.upsert({ id: 42, username: 'doe' }, { returning: true }); + expect(user.get('id')).to.equal(42); + expect(user.get('username')).to.equal('doe'); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { expect(created).to.be.false; - }); + } }); - it('works for table with custom primary key field', function() { + it('works for table with custom primary key field', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.INTEGER, @@ -578,22 +534,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.upsert({ id: 42, username: 'john' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('john'); - expect(created).to.be.true; + await User.sync({ force: true }); + const [user0, created0] = await User.upsert({ id: 42, username: 'john' }, { returning: true }); + expect(user0.get('id')).to.equal(42); + expect(user0.get('username')).to.equal('john'); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } - return User.upsert({ id: 42, username: 'doe' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal(42); - expect(user.get('username')).to.equal('doe'); + const [user, created] = await User.upsert({ id: 42, username: 'doe' }, { returning: true }); + expect(user.get('id')).to.equal(42); + expect(user.get('username')).to.equal('doe'); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { expect(created).to.be.false; - }); + } }); - it('works for non incrementing primaryKey', function() { + it('works for non incrementing primaryKey', async function() { const User = this.sequelize.define('User', { id: { type: DataTypes.STRING, @@ -605,19 +566,44 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return User.sync({ force: true }).then(() => { - return User.upsert({ id: 'surya', username: 'john' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal('surya'); - expect(user.get('username')).to.equal('john'); - expect(created).to.be.true; + await User.sync({ force: true }); + const [user0, created0] = await User.upsert({ id: 'surya', username: 'john' }, { returning: true }); + expect(user0.get('id')).to.equal('surya'); + expect(user0.get('username')).to.equal('john'); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created0).to.be.null; + } else { + expect(created0).to.be.true; + } - return User.upsert({ id: 'surya', username: 'doe' }, { returning: true }); - }).then(([user, created]) => { - expect(user.get('id')).to.equal('surya'); - expect(user.get('username')).to.equal('doe'); + const [user, created] = await User.upsert({ id: 'surya', username: 'doe' }, { returning: true }); + expect(user.get('id')).to.equal('surya'); + expect(user.get('username')).to.equal('doe'); + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { expect(created).to.be.false; + } + }); + + it('should return default value set by the database (upsert)', async function() { + const User = this.sequelize.define('User', { + name: { type: DataTypes.STRING, primaryKey: true }, + code: { type: Sequelize.INTEGER, defaultValue: Sequelize.literal(2020) } }); + + await User.sync({ force: true }); + + const [user, created] = await User.upsert({ name: 'Test default value' }, { returning: true }); + + expect(user.name).to.be.equal('Test default value'); + expect(user.code).to.be.equal(2020); + + if (dialect === 'sqlite' || dialect === 'postgres') { + expect(created).to.be.null; + } else { + expect(created).to.be.true; + } }); }); } diff --git a/test/integration/operators.test.js b/test/integration/operators.test.js index 9a8717cd7d2d..46baf746cdea 100644 --- a/test/integration/operators.test.js +++ b/test/integration/operators.test.js @@ -3,7 +3,6 @@ const chai = require('chai'), Sequelize = require('../../index'), Op = Sequelize.Op, - Promise = Sequelize.Promise, expect = chai.expect, Support = require('../support'), DataTypes = require('../../lib/data-types'), @@ -11,7 +10,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Operators'), () => { describe('REGEXP', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('user', { id: { type: DataTypes.INTEGER, @@ -29,145 +28,93 @@ describe(Support.getTestDialectTeaser('Operators'), () => { timestamps: false }); - return Promise.all([ - this.sequelize.getQueryInterface().createTable('users', { - userId: { - type: DataTypes.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true - }, - full_name: { - type: DataTypes.STRING - } - }) - ]); + await this.sequelize.getQueryInterface().createTable('users', { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true + }, + full_name: { + type: DataTypes.STRING + } + }); }); if (dialect === 'mysql' || dialect === 'postgres') { describe('case sensitive', () => { - it('should work with a regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.regexp]: '^Foo' - } - } - }); - }).then(user => { - expect(user).to.be.ok; + it('should work with a regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.regexp]: '^Foo' } + } }); + expect(user).to.be.ok; }); - it('should work with a not regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.notRegexp]: '^Foo' - } - } - }); - }).then(user => { - expect(user).to.not.be.ok; + it('should work with a not regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.notRegexp]: '^Foo' } + } }); + expect(user).to.not.be.ok; }); - it('should properly escape regular expressions', function() { - return this.User.bulkCreate([{ - name: 'John' - }, { - name: 'Bob' - }]).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.notRegexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.regexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users).length(2); + it('should properly escape regular expressions', async function() { + await this.User.bulkCreate([{ name: 'John' }, { name: 'Bob' }]); + await this.User.findAll({ + where: { + name: { [Op.notRegexp]: "Bob'; drop table users --" } + } }); + await this.User.findAll({ + where: { + name: { [Op.regexp]: "Bob'; drop table users --" } + } + }); + expect(await this.User.findAll()).to.have.length(2); }); }); } if (dialect === 'postgres') { describe('case insensitive', () => { - it('should work with a case-insensitive regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.iRegexp]: '^foo' - } - } - }); - }).then(user => { - expect(user).to.be.ok; + it('should work with a case-insensitive regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.iRegexp]: '^foo' } + } }); + expect(user).to.be.ok; }); - it('should work with a case-insensitive not regexp where', function() { - return this.User.create({ - name: 'Foobar' - }).then(() => { - return this.User.findOne({ - where: { - name: { - [Op.notIRegexp]: '^foo' - } - } - }); - }).then(user => { - expect(user).to.not.be.ok; + it('should work with a case-insensitive not regexp where', async function() { + await this.User.create({ name: 'Foobar' }); + const user = await this.User.findOne({ + where: { + name: { [Op.notIRegexp]: '^foo' } + } }); + expect(user).to.not.be.ok; }); - it('should properly escape regular expressions', function() { - return this.User.bulkCreate([{ - name: 'John' - }, { - name: 'Bob' - }]).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.iRegexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll({ - where: { - name: { - [Op.notIRegexp]: "Bob'; drop table users --" - } - } - }); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users).length(2); + it('should properly escape regular expressions', async function() { + await this.User.bulkCreate([{ name: 'John' }, { name: 'Bob' }]); + await this.User.findAll({ + where: { + name: { [Op.iRegexp]: "Bob'; drop table users --" } + } + }); + await this.User.findAll({ + where: { + name: { [Op.notIRegexp]: "Bob'; drop table users --" } + } }); + expect(await this.User.findAll()).to.have.length(2); }); }); } diff --git a/test/integration/pool.test.js b/test/integration/pool.test.js index 9131d4d5b393..0bd4bdfa1202 100644 --- a/test/integration/pool.test.js +++ b/test/integration/pool.test.js @@ -6,6 +6,7 @@ const Support = require('./support'); const dialect = Support.getTestDialect(); const sinon = require('sinon'); const Sequelize = Support.Sequelize; +const delay = require('delay'); function assertSameConnection(newConnection, oldConnection) { switch (dialect) { @@ -19,7 +20,7 @@ function assertSameConnection(newConnection, oldConnection) { break; case 'mssql': - expect(newConnection.unwrap().dummyId).to.equal(oldConnection.unwrap().dummyId).and.to.be.ok; + expect(newConnection.dummyId).to.equal(oldConnection.dummyId).and.to.be.ok; break; default: @@ -39,8 +40,8 @@ function assertNewConnection(newConnection, oldConnection) { break; case 'mssql': - expect(newConnection.unwrap().dummyId).to.not.be.ok; - expect(oldConnection.unwrap().dummyId).to.be.ok; + expect(newConnection.dummyId).to.not.be.ok; + expect(oldConnection.dummyId).to.be.ok; break; default: @@ -48,9 +49,8 @@ function assertNewConnection(newConnection, oldConnection) { } } -function unwrapAndAttachMSSQLUniqueId(connection) { +function attachMSSQLUniqueId(connection) { if (dialect === 'mssql') { - connection = connection.unwrap(); connection.dummyId = Math.random(); } @@ -69,175 +69,118 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { }); describe('network / connection errors', () => { - it('should obtain new connection when old connection is abruptly closed', () => { - const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 5000 + it('should obtain new connection when old connection is abruptly closed', async () => { + function simulateUnexpectedError(connection) { + // should never be returned again + if (dialect === 'mssql') { + connection = attachMSSQLUniqueId(connection); } - }); + connection.emit('error', { code: 'ECONNRESET' }); + } + const sequelize = Support.createSequelizeInstance({ + pool: { max: 1, idle: 5000 } + }); const cm = sequelize.connectionManager; - let conn; - - return sequelize - .sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - conn = connection; - - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } - - // simulate an unexpected error - // should never be returned again - connection.emit('error', { - code: 'ECONNRESET' - }); - }) - .then(() => { - // Get next available connection - return cm.getConnection(); - }) - .then(connection => { - assertNewConnection(connection, conn); + await sequelize.sync(); - expect(sequelize.connectionManager.pool.size).to.equal(1); - expect(cm.validate(conn)).to.be.not.ok; + const firstConnection = await cm.getConnection(); + simulateUnexpectedError(firstConnection); + const secondConnection = await cm.getConnection(); - return cm.releaseConnection(connection); - }); + assertNewConnection(secondConnection, firstConnection); + expect(cm.pool.size).to.equal(1); + expect(cm.validate(firstConnection)).to.be.not.ok; + + await cm.releaseConnection(secondConnection); }); - it('should obtain new connection when released connection dies inside pool', () => { - const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 5000 + it('should obtain new connection when released connection dies inside pool', async () => { + function simulateUnexpectedError(connection) { + // should never be returned again + if (dialect === 'mssql') { + attachMSSQLUniqueId(connection).close(); + } else if (dialect === 'postgres') { + connection.end(); + } else { + connection.close(); } - }); + } + const sequelize = Support.createSequelizeInstance({ + pool: { max: 1, idle: 5000 } + }); const cm = sequelize.connectionManager; - let oldConnection; + await sequelize.sync(); - return sequelize - .sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - oldConnection = connection; - - return cm.releaseConnection(connection); - }) - .then(() => { - let connection = oldConnection; - - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } - - // simulate an unexpected error - // should never be returned again - if (dialect.match(/postgres/)) { - connection.end(); - } else { - connection.close(); - } - }) - .then(() => { - // Get next available connection - return cm.getConnection(); - }) - .then(connection => { - assertNewConnection(connection, oldConnection); + const oldConnection = await cm.getConnection(); + await cm.releaseConnection(oldConnection); + simulateUnexpectedError(oldConnection); + const newConnection = await cm.getConnection(); - expect(sequelize.connectionManager.pool.size).to.equal(1); - expect(cm.validate(oldConnection)).to.be.not.ok; + assertNewConnection(newConnection, oldConnection); + expect(cm.pool.size).to.equal(1); + expect(cm.validate(oldConnection)).to.be.not.ok; - return cm.releaseConnection(connection); - }); + await cm.releaseConnection(newConnection); }); }); describe('idle', () => { - it('should maintain connection within idle range', () => { + it('should maintain connection within idle range', async () => { const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 10 - } + pool: { max: 1, idle: 100 } }); - const cm = sequelize.connectionManager; - let conn; + await sequelize.sync(); - return sequelize.sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - conn = connection; + const firstConnection = await cm.getConnection(); - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } + // TODO - Do we really need this call? + attachMSSQLUniqueId(firstConnection); - // returning connection back to pool - return cm.releaseConnection(conn); - }) - .then(() => { - // Get next available connection - return Sequelize.Promise.delay(9).then(() => cm.getConnection()); - }) - .then(connection => { - assertSameConnection(connection, conn); - expect(cm.validate(conn)).to.be.ok; + // returning connection back to pool + await cm.releaseConnection(firstConnection); + + // Wait a little and then get next available connection + await delay(90); + const secondConnection = await cm.getConnection(); - return cm.releaseConnection(connection); - }); + assertSameConnection(secondConnection, firstConnection); + expect(cm.validate(firstConnection)).to.be.ok; + + await cm.releaseConnection(secondConnection); }); - it('should get new connection beyond idle range', () => { + it('should get new connection beyond idle range', async () => { const sequelize = Support.createSequelizeInstance({ - pool: { - max: 1, - idle: 100, - evict: 10 - } + pool: { max: 1, idle: 100, evict: 10 } }); - const cm = sequelize.connectionManager; - let conn; + await sequelize.sync(); - return sequelize.sync() - .then(() => cm.getConnection()) - .then(connection => { - // Save current connection - conn = connection; + const firstConnection = await cm.getConnection(); - if (dialect === 'mssql') { - connection = unwrapAndAttachMSSQLUniqueId(connection); - } + // TODO - Do we really need this call? + attachMSSQLUniqueId(firstConnection); - // returning connection back to pool - return cm.releaseConnection(conn); - }) - .then(() => { - // Get next available connection - return Sequelize.Promise.delay(110).then(() => cm.getConnection()); - }) - .then(connection => { - assertNewConnection(connection, conn); - expect(cm.validate(conn)).not.to.be.ok; + // returning connection back to pool + await cm.releaseConnection(firstConnection); - return cm.releaseConnection(connection); - }); + // Wait a little and then get next available connection + await delay(110); + + const secondConnection = await cm.getConnection(); + + assertNewConnection(secondConnection, firstConnection); + expect(cm.validate(firstConnection)).not.to.be.ok; + + await cm.releaseConnection(secondConnection); }); }); describe('acquire', () => { - it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection', function() { + it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection', async function() { this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { dialect, databaseVersion: '1.2.3', @@ -247,13 +190,14 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { }); this.sinon.stub(this.testInstance.connectionManager, '_connect') - .returns(new Sequelize.Promise(() => {})); + .returns(new Promise(() => {})); - return expect(this.testInstance.authenticate()) - .to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); + await expect( + this.testInstance.authenticate() + ).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); }); - it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection for transaction', function() { + it('should reject with ConnectionAcquireTimeoutError when unable to acquire connection for transaction', async function() { this.testInstance = new Sequelize('localhost', 'ffd', 'dfdf', { dialect, databaseVersion: '1.2.3', @@ -264,11 +208,13 @@ describe(Support.getTestDialectTeaser('Pooling'), () => { }); this.sinon.stub(this.testInstance.connectionManager, '_connect') - .returns(new Sequelize.Promise(() => {})); + .returns(new Promise(() => {})); - return expect(this.testInstance.transaction(() => { - return this.testInstance.transaction(() => {}); - })).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); + await expect( + this.testInstance.transaction(async () => { + await this.testInstance.transaction(() => {}); + }) + ).to.eventually.be.rejectedWith(Sequelize.ConnectionAcquireTimeoutError); }); }); }); diff --git a/test/integration/query-interface.test.js b/test/integration/query-interface.test.js index dd0eb1ecd360..d37481c147fb 100644 --- a/test/integration/query-interface.test.js +++ b/test/integration/query-interface.test.js @@ -15,262 +15,223 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('dropAllSchema', () => { - it('should drop all schema', function() { - return this.queryInterface.dropAllSchemas( - { skip: [this.sequelize.config.database] }) - .then(() => { - return this.queryInterface.showAllSchemas(); - }) - .then(schemaNames => { - - return this.queryInterface.createSchema('newSchema') - .then(() => { - return this.queryInterface.showAllSchemas(); - }) - .then(newSchemaNames => { - if (!current.dialect.supports.schemas) return; - expect(newSchemaNames).to.have.length(schemaNames.length + 1); - return this.queryInterface.dropSchema('newSchema'); - }); - }); + it('should drop all schema', async function() { + await this.queryInterface.dropAllSchemas({ + skip: [this.sequelize.config.database] + }); + const schemaNames = await this.queryInterface.showAllSchemas(); + await this.queryInterface.createSchema('newSchema'); + const newSchemaNames = await this.queryInterface.showAllSchemas(); + if (!current.dialect.supports.schemas) return; + expect(newSchemaNames).to.have.length(schemaNames.length + 1); + await this.queryInterface.dropSchema('newSchema'); }); }); describe('showAllTables', () => { - it('should not contain views', function() { - const cleanup = () => { - // NOTE: The syntax "DROP VIEW [IF EXISTS]"" is not part of the standard - // and might not be available on all RDBMSs. Therefore "DROP VIEW" is - // the compatible option, which can throw an error in case the VIEW does - // not exist. In case of error, it is ignored by reflect()+tap(). - return this.sequelize.query('DROP VIEW V_Fail').reflect(); - }; - return this.queryInterface - .createTable('my_test_table', { name: DataTypes.STRING }) - .tap(cleanup) - .then(() => this.sequelize.query('CREATE VIEW V_Fail AS SELECT 1 Id')) - .then(() => this.queryInterface.showAllTables()) - .tap(cleanup) - .then(tableNames => { - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.deep.equal(['my_test_table']); - }); + it('should not contain views', async function() { + async function cleanup(sequelize) { + await sequelize.query('DROP VIEW IF EXISTS V_Fail'); + } + await this.queryInterface.createTable('my_test_table', { name: DataTypes.STRING }); + await cleanup(this.sequelize); + await this.sequelize.query('CREATE VIEW V_Fail AS SELECT 1 Id'); + let tableNames = await this.queryInterface.showAllTables(); + await cleanup(this.sequelize); + if (tableNames[0] && tableNames[0].tableName) { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.deep.equal(['my_test_table']); }); if (dialect !== 'sqlite' && dialect !== 'postgres') { // NOTE: sqlite doesn't allow querying between databases and // postgres requires creating a new connection to create a new table. - it('should not show tables in other databases', function() { - return this.queryInterface - .createTable('my_test_table1', { name: DataTypes.STRING }) - .then(() => this.sequelize.query('CREATE DATABASE my_test_db')) - .then(() => this.sequelize.query(`CREATE TABLE my_test_db${dialect === 'mssql' ? '.dbo' : ''}.my_test_table2 (id INT)`)) - .then(() => this.queryInterface.showAllTables()) - .tap(() => this.sequelize.query('DROP DATABASE my_test_db')) - .then(tableNames => { - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.deep.equal(['my_test_table1']); - }); + it('should not show tables in other databases', async function() { + await this.queryInterface.createTable('my_test_table1', { name: DataTypes.STRING }); + await this.sequelize.query('CREATE DATABASE my_test_db'); + await this.sequelize.query(`CREATE TABLE my_test_db${dialect === 'mssql' ? '.dbo' : ''}.my_test_table2 (id INT)`); + let tableNames = await this.queryInterface.showAllTables(); + await this.sequelize.query('DROP DATABASE my_test_db'); + if (tableNames[0] && tableNames[0].tableName) { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.deep.equal(['my_test_table1']); }); + } - if (dialect === 'mysql' || dialect === 'mariadb') { - it('should show all tables in all databases', function() { - return this.queryInterface - .createTable('my_test_table1', { name: DataTypes.STRING }) - .then(() => this.sequelize.query('CREATE DATABASE my_test_db')) - .then(() => this.sequelize.query('CREATE TABLE my_test_db.my_test_table2 (id INT)')) - .then(() => this.sequelize.query(this.queryInterface.QueryGenerator.showTablesQuery(), { - raw: true, - type: this.sequelize.QueryTypes.SHOWTABLES - })) - .tap(() => this.sequelize.query('DROP DATABASE my_test_db')) - .then(tableNames => { - if (tableNames[0] && tableNames[0].tableName) { - tableNames = tableNames.map(v => v.tableName); - } - tableNames.sort(); - expect(tableNames).to.deep.equal(['my_test_table1', 'my_test_table2']); - }); - }); - } + if (dialect === 'mysql' || dialect === 'mariadb') { + it('should show all tables in all databases', async function() { + await this.queryInterface.createTable('my_test_table1', { name: DataTypes.STRING }); + await this.sequelize.query('CREATE DATABASE my_test_db'); + await this.sequelize.query('CREATE TABLE my_test_db.my_test_table2 (id INT)'); + let tableNames = await this.sequelize.query( + this.queryInterface.queryGenerator.showTablesQuery(), + { + raw: true, + type: this.sequelize.QueryTypes.SHOWTABLES + } + ); + await this.sequelize.query('DROP DATABASE my_test_db'); + if (tableNames[0] && tableNames[0].tableName) { + tableNames = tableNames.map(v => v.tableName); + } + tableNames.sort(); + + expect(tableNames).to.include('my_test_table1'); + expect(tableNames).to.include('my_test_table2'); + }); } }); describe('renameTable', () => { - it('should rename table', function() { - return this.queryInterface - .createTable('my_test_table', { - name: DataTypes.STRING - }) - .then(() => this.queryInterface.renameTable('my_test_table', 'my_test_table_new')) - .then(() => this.queryInterface.showAllTables()) - .then(tableNames => { - if (dialect === 'mssql' || dialect === 'mariadb') { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.contain('my_test_table_new'); - expect(tableNames).to.not.contain('my_test_table'); - }); + it('should rename table', async function() { + await this.queryInterface.createTable('my_test_table', { + name: DataTypes.STRING + }); + await this.queryInterface.renameTable('my_test_table', 'my_test_table_new'); + let tableNames = await this.queryInterface.showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.contain('my_test_table_new'); + expect(tableNames).to.not.contain('my_test_table'); }); }); describe('dropAllTables', () => { - it('should drop all tables', function() { - const filterMSSQLDefault = tableNames => tableNames.filter(t => t.tableName !== 'spt_values'); - return this.queryInterface.dropAllTables() - .then(() => { - return this.queryInterface.showAllTables(); - }) - .then(tableNames => { - // MSSQL include spt_values table which is system defined, hence cant be dropped - tableNames = filterMSSQLDefault(tableNames); - expect(tableNames).to.be.empty; - return this.queryInterface.createTable('table', { name: DataTypes.STRING }); - }) - .then(() => { - return this.queryInterface.showAllTables(); - }) - .then(tableNames => { - tableNames = filterMSSQLDefault(tableNames); - expect(tableNames).to.have.length(1); - return this.queryInterface.dropAllTables(); - }) - .then(() => { - return this.queryInterface.showAllTables(); - }) - .then(tableNames => { - // MSSQL include spt_values table which is system defined, hence cant be dropped - tableNames = filterMSSQLDefault(tableNames); - expect(tableNames).to.be.empty; - }); + it('should drop all tables', async function() { + + // MSSQL includes `spt_values` table which is system defined, hence can't be dropped + const showAllTablesIgnoringSpecialMSSQLTable = async () => { + const tableNames = await this.queryInterface.showAllTables(); + return tableNames.filter(t => t.tableName !== 'spt_values'); + }; + + await this.queryInterface.dropAllTables(); + + expect( + await showAllTablesIgnoringSpecialMSSQLTable() + ).to.be.empty; + + await this.queryInterface.createTable('table', { name: DataTypes.STRING }); + + expect( + await showAllTablesIgnoringSpecialMSSQLTable() + ).to.have.length(1); + + await this.queryInterface.dropAllTables(); + + expect( + await showAllTablesIgnoringSpecialMSSQLTable() + ).to.be.empty; }); - it('should be able to skip given tables', function() { - return this.queryInterface.createTable('skipme', { + it('should be able to skip given tables', async function() { + await this.queryInterface.createTable('skipme', { name: DataTypes.STRING - }) - .then(() => this.queryInterface.dropAllTables({ skip: ['skipme'] })) - .then(() => this.queryInterface.showAllTables()) - .then(tableNames => { - if (dialect === 'mssql' || dialect === 'mariadb') { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.contain('skipme'); - }); + }); + await this.queryInterface.dropAllTables({ skip: ['skipme'] }); + let tableNames = await this.queryInterface.showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.contain('skipme'); }); }); describe('indexes', () => { - beforeEach(function() { - return this.queryInterface.dropTable('Group').then(() => { - return this.queryInterface.createTable('Group', { - username: DataTypes.STRING, - isAdmin: DataTypes.BOOLEAN, - from: DataTypes.STRING - }); + beforeEach(async function() { + await this.queryInterface.dropTable('Group'); + await this.queryInterface.createTable('Group', { + username: DataTypes.STRING, + isAdmin: DataTypes.BOOLEAN, + from: DataTypes.STRING }); }); - it('adds, reads and removes an index to the table', function() { - return this.queryInterface.addIndex('Group', ['username', 'isAdmin']).then(() => { - return this.queryInterface.showIndex('Group').then(indexes => { - let indexColumns = _.uniq(indexes.map(index => { return index.name; })); - expect(indexColumns).to.include('group_username_is_admin'); - return this.queryInterface.removeIndex('Group', ['username', 'isAdmin']).then(() => { - return this.queryInterface.showIndex('Group').then(indexes => { - indexColumns = _.uniq(indexes.map(index => { return index.name; })); - expect(indexColumns).to.be.empty; - }); - }); - }); - }); + it('adds, reads and removes an index to the table', async function() { + await this.queryInterface.addIndex('Group', ['username', 'isAdmin']); + let indexes = await this.queryInterface.showIndex('Group'); + let indexColumns = _.uniq(indexes.map(index => index.name)); + expect(indexColumns).to.include('group_username_is_admin'); + await this.queryInterface.removeIndex('Group', ['username', 'isAdmin']); + indexes = await this.queryInterface.showIndex('Group'); + indexColumns = _.uniq(indexes.map(index => index.name)); + expect(indexColumns).to.be.empty; }); - it('works with schemas', function() { - return this.sequelize.createSchema('schema').then(() => { - return this.queryInterface.createTable('table', { - name: { - type: DataTypes.STRING - }, - isAdmin: { - type: DataTypes.STRING - } - }, { - schema: 'schema' - }); - }).then(() => { - return this.queryInterface.addIndex({ - schema: 'schema', - tableName: 'table' - }, ['name', 'isAdmin'], null, 'schema_table').then(() => { - return this.queryInterface.showIndex({ - schema: 'schema', - tableName: 'table' - }).then(indexes => { - expect(indexes.length).to.eq(1); - const index = indexes[0]; - expect(index.name).to.eq('table_name_is_admin'); - }); - }); + it('works with schemas', async function() { + await this.sequelize.createSchema('schema'); + await this.queryInterface.createTable('table', { + name: { + type: DataTypes.STRING + }, + isAdmin: { + type: DataTypes.STRING + } + }, { + schema: 'schema' + }); + await this.queryInterface.addIndex( + { schema: 'schema', tableName: 'table' }, + ['name', 'isAdmin'], + null, + 'schema_table' + ); + const indexes = await this.queryInterface.showIndex({ + schema: 'schema', + tableName: 'table' }); + expect(indexes.length).to.eq(1); + expect(indexes[0].name).to.eq('table_name_is_admin'); }); - it('does not fail on reserved keywords', function() { - return this.queryInterface.addIndex('Group', ['from']); + it('does not fail on reserved keywords', async function() { + await this.queryInterface.addIndex('Group', ['from']); }); }); describe('renameColumn', () => { - it('rename a simple column', function() { + it('rename a simple column', async function() { const Users = this.sequelize.define('_Users', { username: DataTypes.STRING }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); - }).then(() => { - return this.queryInterface.describeTable('_Users'); - }).then(table => { - expect(table).to.have.property('pseudo'); - expect(table).to.not.have.property('username'); - }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); + const table = await this.queryInterface.describeTable('_Users'); + expect(table).to.have.property('pseudo'); + expect(table).to.not.have.property('username'); }); - it('works with schemas', function() { - return this.sequelize.createSchema('archive').then(() => { - const Users = this.sequelize.define('User', { - username: DataTypes.STRING - }, { - tableName: 'Users', - schema: 'archive' - }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn({ - schema: 'archive', - tableName: 'Users' - }, 'username', 'pseudo'); - }); - }).then(() => { - return this.queryInterface.describeTable({ - schema: 'archive', - tableName: 'Users' - }); - }).then(table => { - expect(table).to.have.property('pseudo'); - expect(table).to.not.have.property('username'); + it('works with schemas', async function() { + await this.sequelize.createSchema('archive'); + const Users = this.sequelize.define('User', { + username: DataTypes.STRING + }, { + tableName: 'Users', + schema: 'archive' + }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn({ + schema: 'archive', + tableName: 'Users' + }, 'username', 'pseudo'); + const table = await this.queryInterface.describeTable({ + schema: 'archive', + tableName: 'Users' }); + expect(table).to.have.property('pseudo'); + expect(table).to.not.have.property('username'); }); - it('rename a column non-null without default value', function() { + it('rename a column non-null without default value', async function() { const Users = this.sequelize.define('_Users', { username: { type: DataTypes.STRING, @@ -278,17 +239,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); - }).then(() => { - return this.queryInterface.describeTable('_Users'); - }).then(table => { - expect(table).to.have.property('pseudo'); - expect(table).to.not.have.property('username'); - }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn('_Users', 'username', 'pseudo'); + const table = await this.queryInterface.describeTable('_Users'); + expect(table).to.have.property('pseudo'); + expect(table).to.not.have.property('username'); }); - it('rename a boolean column non-null without default value', function() { + it('rename a boolean column non-null without default value', async function() { const Users = this.sequelize.define('_Users', { active: { type: DataTypes.BOOLEAN, @@ -297,17 +255,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'active', 'enabled'); - }).then(() => { - return this.queryInterface.describeTable('_Users'); - }).then(table => { - expect(table).to.have.property('enabled'); - expect(table).to.not.have.property('active'); - }); + await Users.sync({ force: true }); + await this.queryInterface.renameColumn('_Users', 'active', 'enabled'); + const table = await this.queryInterface.describeTable('_Users'); + expect(table).to.have.property('enabled'); + expect(table).to.not.have.property('active'); }); - it('renames a column primary key autoIncrement column', function() { + it('renames a column primary key autoIncrement column', async function() { const Fruits = this.sequelize.define('Fruit', { fruitId: { type: DataTypes.INTEGER, @@ -317,213 +272,216 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Fruits.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('Fruit', 'fruitId', 'fruit_id'); - }).then(() => { - return this.queryInterface.describeTable('Fruit'); - }).then(table => { - expect(table).to.have.property('fruit_id'); - expect(table).to.not.have.property('fruitId'); - }); + await Fruits.sync({ force: true }); + await this.queryInterface.renameColumn('Fruit', 'fruitId', 'fruit_id'); + const table = await this.queryInterface.describeTable('Fruit'); + expect(table).to.have.property('fruit_id'); + expect(table).to.not.have.property('fruitId'); }); - it('shows a reasonable error message when column is missing', function() { + it('shows a reasonable error message when column is missing', async function() { const Users = this.sequelize.define('_Users', { username: DataTypes.STRING }, { freezeTableName: true }); - const outcome = Users.sync({ force: true }).then(() => { - return this.queryInterface.renameColumn('_Users', 'email', 'pseudo'); - }); - - return expect(outcome).to.be.rejectedWith('Table _Users doesn\'t have the column email'); + await Users.sync({ force: true }); + await expect( + this.queryInterface.renameColumn('_Users', 'email', 'pseudo') + ).to.be.rejectedWith('Table _Users doesn\'t have the column email'); }); }); describe('addColumn', () => { - beforeEach(function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable('users', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); - }); - }); - - it('should be able to add a foreign key reference', function() { - return this.queryInterface.createTable('level', { + beforeEach(async function() { + await this.sequelize.createSchema('archive'); + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.addColumn('users', 'level_id', { - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'set null' - }); - }).then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.have.property('level_id'); }); }); - it('addColumn expected error', function() { - return this.queryInterface.createTable('level2', { + it('should be able to add a foreign key reference', async function() { + await this.queryInterface.createTable('level', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - expect(this.queryInterface.addColumn.bind(this, 'users', 'level_id')).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - expect(this.queryInterface.addColumn.bind(this, null, 'level_id')).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); - expect(this.queryInterface.addColumn.bind(this, 'users', null, {})).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); }); + await this.queryInterface.addColumn('users', 'level_id', { + type: DataTypes.INTEGER, + references: { + model: 'level', + key: 'id' + }, + onUpdate: 'cascade', + onDelete: 'set null' + }); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.have.property('level_id'); }); - it('should work with schemas', function() { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { + it('addColumn expected error', async function() { + await this.queryInterface.createTable('level2', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.addColumn({ - tableName: 'users', - schema: 'archive' - }, 'level_id', { - type: DataTypes.INTEGER - }).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.have.property('level_id'); - }); }); + + const testArgs = (...args) => expect(this.queryInterface.addColumn(...args)) + .to.be.rejectedWith(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)'); + + await testArgs('users', 'level_id'); + await testArgs(null, 'level_id'); + await testArgs('users', null, {}); + }); + + it('should work with schemas', async function() { + await this.queryInterface.createTable( + { tableName: 'users', schema: 'archive' }, + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + } + } + ); + await this.queryInterface.addColumn( + { tableName: 'users', schema: 'archive' }, + 'level_id', + { type: DataTypes.INTEGER } + ); + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' + }); + expect(table).to.have.property('level_id'); }); - it('should work with enums (1)', function() { - return this.queryInterface.addColumn('users', 'someEnum', DataTypes.ENUM('value1', 'value2', 'value3')); + it('should work with enums (1)', async function() { + await this.queryInterface.addColumn('users', 'someEnum', DataTypes.ENUM('value1', 'value2', 'value3')); }); - it('should work with enums (2)', function() { - return this.queryInterface.addColumn('users', 'someOtherEnum', { + it('should work with enums (2)', async function() { + await this.queryInterface.addColumn('users', 'someOtherEnum', { type: DataTypes.ENUM, values: ['value1', 'value2', 'value3'] }); }); + + if (dialect === 'postgres') { + it('should be able to add a column of type of array of enums', async function() { + await this.queryInterface.addColumn('users', 'tags', { + allowNull: false, + type: Sequelize.ARRAY(Sequelize.ENUM( + 'Value1', + 'Value2', + 'Value3' + )) + }); + const result = await this.queryInterface.describeTable('users'); + expect(result).to.have.property('tags'); + expect(result.tags.type).to.equal('ARRAY'); + expect(result.tags.allowNull).to.be.false; + }); + } }); describe('describeForeignKeys', () => { - beforeEach(function() { - return this.queryInterface.createTable('users', { + beforeEach(async function() { + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.createTable('hosts', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - admin: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - } + }); + await this.queryInterface.createTable('hosts', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + admin: { + type: DataTypes.INTEGER, + references: { + model: 'users', + key: 'id' + } + }, + operator: { + type: DataTypes.INTEGER, + references: { + model: 'users', + key: 'id' }, - operator: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - }, - onUpdate: 'cascade' + onUpdate: 'cascade' + }, + owner: { + type: DataTypes.INTEGER, + references: { + model: 'users', + key: 'id' }, - owner: { - type: DataTypes.INTEGER, - references: { - model: 'users', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'set null' - } - }); + onUpdate: 'cascade', + onDelete: 'set null' + } }); }); - it('should get a list of foreign keys for the table', function() { - const sql = this.queryInterface.QueryGenerator.getForeignKeysQuery('hosts', this.sequelize.config.database); - return this.sequelize.query(sql, { type: this.sequelize.QueryTypes.FOREIGNKEYS }).then(fks => { - expect(fks).to.have.length(3); - const keys = Object.keys(fks[0]), - keys2 = Object.keys(fks[1]), - keys3 = Object.keys(fks[2]); - - if (dialect === 'postgres' || dialect === 'postgres-native') { - expect(keys).to.have.length(6); - expect(keys2).to.have.length(7); - expect(keys3).to.have.length(7); - } else if (dialect === 'sqlite') { - expect(keys).to.have.length(8); - } else if (dialect === 'mysql' || dialect === 'mssql') { - expect(keys).to.have.length(12); - } else { - console.log(`This test doesn't support ${dialect}`); - } - return fks; - }).then(fks => { - if (dialect === 'mysql') { - return this.sequelize.query( - this.queryInterface.QueryGenerator.getForeignKeyQuery('hosts', 'admin'), - {} - ) - .then(([fk]) => { - expect(fks[0]).to.deep.eql(fk[0]); - }); - } - return; - }); + it('should get a list of foreign keys for the table', async function() { + + const foreignKeys = await this.sequelize.query( + this.queryInterface.queryGenerator.getForeignKeysQuery( + 'hosts', + this.sequelize.config.database + ), + { type: this.sequelize.QueryTypes.FOREIGNKEYS } + ); + + expect(foreignKeys).to.have.length(3); + + if (dialect === 'postgres') { + expect(Object.keys(foreignKeys[0])).to.have.length(6); + expect(Object.keys(foreignKeys[1])).to.have.length(7); + expect(Object.keys(foreignKeys[2])).to.have.length(7); + } else if (dialect === 'sqlite') { + expect(Object.keys(foreignKeys[0])).to.have.length(8); + } else if (dialect === 'mysql' || dialect === 'mariadb' || dialect === 'mssql') { + expect(Object.keys(foreignKeys[0])).to.have.length(12); + } else { + throw new Error(`This test doesn't support ${dialect}`); + } + + if (dialect === 'mysql') { + const [foreignKeysViaDirectMySQLQuery] = await this.sequelize.query( + this.queryInterface.queryGenerator.getForeignKeyQuery('hosts', 'admin') + ); + expect(foreignKeysViaDirectMySQLQuery[0]).to.deep.equal(foreignKeys[0]); + } }); - it('should get a list of foreign key references details for the table', function() { - return this.queryInterface.getForeignKeyReferencesForTable('hosts', this.sequelize.options) - .then(references => { - expect(references).to.have.length(3); - const keys = references.map(reference => { - expect(reference.tableName).to.eql('hosts'); - expect(reference.referencedColumnName).to.eql('id'); - expect(reference.referencedTableName).to.eql('users'); - return reference.columnName; - }); - expect(keys).to.have.same.members(['owner', 'operator', 'admin']); - }); + it('should get a list of foreign key references details for the table', async function() { + const references = await this.queryInterface.getForeignKeyReferencesForTable('hosts', this.sequelize.options); + expect(references).to.have.length(3); + for (const ref of references) { + expect(ref.tableName).to.equal('hosts'); + expect(ref.referencedColumnName).to.equal('id'); + expect(ref.referencedTableName).to.equal('users'); + } + const columnNames = references.map(reference => reference.columnName); + expect(columnNames).to.have.same.members(['owner', 'operator', 'admin']); }); }); describe('constraints', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('users', { username: DataTypes.STRING, email: DataTypes.STRING, @@ -533,203 +491,161 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.Post = this.sequelize.define('posts', { username: DataTypes.STRING }); - return this.sequelize.sync({ force: true }); + await this.sequelize.sync({ force: true }); }); describe('unique', () => { - it('should add, read & remove unique constraint', function() { - return this.queryInterface.addConstraint('users', ['email'], { - type: 'unique' - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('users_email_uk'); - return this.queryInterface.removeConstraint('users', 'users_email_uk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_email_uk'); - }); + it('should add, read & remove unique constraint', async function() { + await this.queryInterface.addConstraint('users', { type: 'unique', fields: ['email'] }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('users_email_uk'); + await this.queryInterface.removeConstraint('users', 'users_email_uk'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_email_uk'); }); - it('should add a constraint after another', function() { - return this.queryInterface.addConstraint('users', ['username'], { - type: 'unique' - }).then(() => this.queryInterface.addConstraint('users', ['email'], { - type: 'unique' - })) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('users_email_uk'); - expect(constraints).to.include('users_username_uk'); - return this.queryInterface.removeConstraint('users', 'users_email_uk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_email_uk'); - expect(constraints).to.include('users_username_uk'); - return this.queryInterface.removeConstraint('users', 'users_username_uk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_email_uk'); - expect(constraints).to.not.include('users_username_uk'); - }); + it('should add a constraint after another', async function() { + await this.queryInterface.addConstraint('users', { type: 'unique', fields: ['username'] }); + await this.queryInterface.addConstraint('users', { type: 'unique', fields: ['email'] }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('users_email_uk'); + expect(constraints).to.include('users_username_uk'); + await this.queryInterface.removeConstraint('users', 'users_email_uk'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_email_uk'); + expect(constraints).to.include('users_username_uk'); + await this.queryInterface.removeConstraint('users', 'users_username_uk'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_email_uk'); + expect(constraints).to.not.include('users_username_uk'); }); }); if (current.dialect.supports.constraints.check) { describe('check', () => { - it('should add, read & remove check constraint', function() { - return this.queryInterface.addConstraint('users', ['roles'], { + it('should add, read & remove check constraint', async function() { + await this.queryInterface.addConstraint('users', { type: 'check', + fields: ['roles'], where: { roles: ['user', 'admin', 'guest', 'moderator'] }, name: 'check_user_roles' - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('check_user_roles'); - return this.queryInterface.removeConstraint('users', 'check_user_roles'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('check_user_roles'); - }); + }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('check_user_roles'); + await this.queryInterface.removeConstraint('users', 'check_user_roles'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('check_user_roles'); }); - it('addconstraint missing type', function() { - expect(this.queryInterface.addConstraint.bind(this, 'users', ['roles'], { - where: { roles: ['user', 'admin', 'guest', 'moderator'] }, - name: 'check_user_roles' - })).to.throw(Error, 'Constraint type must be specified through options.type'); + it('addconstraint missing type', async function() { + await expect( + this.queryInterface.addConstraint('users', { + fields: ['roles'], + where: { roles: ['user', 'admin', 'guest', 'moderator'] }, + name: 'check_user_roles' + }) + ).to.be.rejectedWith(Error, 'Constraint type must be specified through options.type'); }); }); } if (current.dialect.supports.constraints.default) { describe('default', () => { - it('should add, read & remove default constraint', function() { - return this.queryInterface.addConstraint('users', ['roles'], { + it('should add, read & remove default constraint', async function() { + await this.queryInterface.addConstraint('users', { + fields: ['roles'], type: 'default', defaultValue: 'guest' - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('users_roles_df'); - return this.queryInterface.removeConstraint('users', 'users_roles_df'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('users_roles_df'); - }); + }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('users_roles_df'); + await this.queryInterface.removeConstraint('users', 'users_roles_df'); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('users_roles_df'); }); }); } - describe('primary key', () => { - it('should add, read & remove primary key constraint', function() { - return this.queryInterface.removeColumn('users', 'id') - .then(() => { - return this.queryInterface.changeColumn('users', 'username', { - type: DataTypes.STRING, - allowNull: false - }); - }) - .then(() => { - return this.queryInterface.addConstraint('users', ['username'], { - type: 'PRIMARY KEY' - }); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - //The name of primaryKey constraint is always PRIMARY in case of mysql - if (dialect === 'mysql' || dialect === 'mariadb') { - expect(constraints).to.include('PRIMARY'); - return this.queryInterface.removeConstraint('users', 'PRIMARY'); - } - expect(constraints).to.include('users_username_pk'); - return this.queryInterface.removeConstraint('users', 'users_username_pk'); - }) - .then(() => this.queryInterface.showConstraint('users')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - if (dialect === 'mysql' || dialect === 'mariadb') { - expect(constraints).to.not.include('PRIMARY'); - } else { - expect(constraints).to.not.include('users_username_pk'); - } - }); + it('should add, read & remove primary key constraint', async function() { + await this.queryInterface.removeColumn('users', 'id'); + await this.queryInterface.changeColumn('users', 'username', { + type: DataTypes.STRING, + allowNull: false + }); + await this.queryInterface.addConstraint('users', { + fields: ['username'], + type: 'PRIMARY KEY' + }); + let constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + + // The name of primaryKey constraint is always `PRIMARY` in case of MySQL and MariaDB + const expectedConstraintName = dialect === 'mysql' || dialect === 'mariadb' ? 'PRIMARY' : 'users_username_pk'; + + expect(constraints).to.include(expectedConstraintName); + await this.queryInterface.removeConstraint('users', expectedConstraintName); + constraints = await this.queryInterface.showConstraint('users'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include(expectedConstraintName); }); }); describe('foreign key', () => { - it('should add, read & remove foreign key constraint', function() { - return this.queryInterface.removeColumn('users', 'id') - .then(() => { - return this.queryInterface.changeColumn('users', 'username', { - type: DataTypes.STRING, - allowNull: false - }); - }) - .then(() => { - return this.queryInterface.addConstraint('users', { - type: 'PRIMARY KEY', - fields: ['username'] - }); - }) - .then(() => { - return this.queryInterface.addConstraint('posts', ['username'], { - references: { - table: 'users', - field: 'username' - }, - onDelete: 'cascade', - onUpdate: 'cascade', - type: 'foreign key' - }); - }) - .then(() => this.queryInterface.showConstraint('posts')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.include('posts_username_users_fk'); - return this.queryInterface.removeConstraint('posts', 'posts_username_users_fk'); - }) - .then(() => this.queryInterface.showConstraint('posts')) - .then(constraints => { - constraints = constraints.map(constraint => constraint.constraintName); - expect(constraints).to.not.include('posts_username_users_fk'); - }); + it('should add, read & remove foreign key constraint', async function() { + await this.queryInterface.removeColumn('users', 'id'); + await this.queryInterface.changeColumn('users', 'username', { + type: DataTypes.STRING, + allowNull: false + }); + await this.queryInterface.addConstraint('users', { + type: 'PRIMARY KEY', + fields: ['username'] + }); + await this.queryInterface.addConstraint('posts', { + fields: ['username'], + references: { + table: 'users', + field: 'username' + }, + onDelete: 'cascade', + onUpdate: 'cascade', + type: 'foreign key' + }); + let constraints = await this.queryInterface.showConstraint('posts'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.include('posts_username_users_fk'); + await this.queryInterface.removeConstraint('posts', 'posts_username_users_fk'); + constraints = await this.queryInterface.showConstraint('posts'); + constraints = constraints.map(constraint => constraint.constraintName); + expect(constraints).to.not.include('posts_username_users_fk'); }); }); describe('unknown constraint', () => { - it('should throw non existent constraints as UnknownConstraintError', function() { - const promise = this.queryInterface - .removeConstraint('users', 'unknown__constraint__name', { + it('should throw non existent constraints as UnknownConstraintError', async function() { + try { + await this.queryInterface.removeConstraint('users', 'unknown__constraint__name', { type: 'unique' - }) - .catch(e => { - expect(e.table).to.equal('users'); - expect(e.constraint).to.equal('unknown__constraint__name'); - - throw e; }); - - return expect(promise).to.eventually.be.rejectedWith(Sequelize.UnknownConstraintError); + throw new Error('Error not thrown...'); + } catch (error) { + expect(error).to.be.instanceOf(Sequelize.UnknownConstraintError); + expect(error.table).to.equal('users'); + expect(error.constraint).to.equal('unknown__constraint__name'); + } }); }); }); diff --git a/test/integration/query-interface/changeColumn.test.js b/test/integration/query-interface/changeColumn.test.js index 62ccc9a2ec6d..7bc12b77a2d8 100644 --- a/test/integration/query-interface/changeColumn.test.js +++ b/test/integration/query-interface/changeColumn.test.js @@ -12,47 +12,47 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('changeColumn', () => { - it('should support schemas', function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - currency: DataTypes.INTEGER - }).then(() => { - return this.queryInterface.changeColumn({ - tableName: 'users', - schema: 'archive' - }, 'currency', { - type: DataTypes.FLOAT - }); - }).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - if (dialect === 'postgres' || dialect === 'postgres-native') { - expect(table.currency.type).to.equal('DOUBLE PRECISION'); - } else { - expect(table.currency.type).to.equal('FLOAT'); - } - }); + it('should support schemas', async function() { + await this.sequelize.createSchema('archive'); + + await this.queryInterface.createTable({ + tableName: 'users', + schema: 'archive' + }, { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + currency: DataTypes.INTEGER + }); + + await this.queryInterface.changeColumn({ + tableName: 'users', + schema: 'archive' + }, 'currency', { + type: DataTypes.FLOAT + }); + + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' }); + + if (dialect === 'postgres' || dialect === 'postgres-native') { + expect(table.currency.type).to.equal('DOUBLE PRECISION'); + } else { + expect(table.currency.type).to.equal('FLOAT'); + } }); - it('should change columns', function() { - return this.queryInterface.createTable({ + it('should change columns', async function() { + await this.queryInterface.createTable({ tableName: 'users' }, { id: { @@ -61,67 +61,67 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { autoIncrement: true }, currency: DataTypes.INTEGER - }).then(() => { - return this.queryInterface.changeColumn('users', 'currency', { - type: DataTypes.FLOAT, - allowNull: true - }); - }).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users' - }); - }).then(table => { - if (dialect === 'postgres' || dialect === 'postgres-native') { - expect(table.currency.type).to.equal('DOUBLE PRECISION'); - } else { - expect(table.currency.type).to.equal('FLOAT'); - } }); + + await this.queryInterface.changeColumn('users', 'currency', { + type: DataTypes.FLOAT, + allowNull: true + }); + + const table = await this.queryInterface.describeTable({ + tableName: 'users' + }); + + if (dialect === 'postgres' || dialect === 'postgres-native') { + expect(table.currency.type).to.equal('DOUBLE PRECISION'); + } else { + expect(table.currency.type).to.equal('FLOAT'); + } }); // MSSQL doesn't support using a modified column in a check constraint. // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql if (dialect !== 'mssql') { - it('should work with enums (case 1)', function() { - return this.queryInterface.createTable({ + it('should work with enums (case 1)', async function() { + await this.queryInterface.createTable({ tableName: 'users' }, { firstName: DataTypes.STRING - }).then(() => { - return this.queryInterface.changeColumn('users', 'firstName', { - type: DataTypes.ENUM(['value1', 'value2', 'value3']) - }); + }); + + await this.queryInterface.changeColumn('users', 'firstName', { + type: DataTypes.ENUM(['value1', 'value2', 'value3']) }); }); - it('should work with enums (case 2)', function() { - return this.queryInterface.createTable({ + it('should work with enums (case 2)', async function() { + await this.queryInterface.createTable({ tableName: 'users' }, { firstName: DataTypes.STRING - }).then(() => { - return this.queryInterface.changeColumn('users', 'firstName', { - type: DataTypes.ENUM, - values: ['value1', 'value2', 'value3'] - }); + }); + + await this.queryInterface.changeColumn('users', 'firstName', { + type: DataTypes.ENUM, + values: ['value1', 'value2', 'value3'] }); }); - it('should work with enums with schemas', function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { - firstName: DataTypes.STRING - }); - }).then(() => { - return this.queryInterface.changeColumn({ - tableName: 'users', - schema: 'archive' - }, 'firstName', { - type: DataTypes.ENUM(['value1', 'value2', 'value3']) - }); + it('should work with enums with schemas', async function() { + await this.sequelize.createSchema('archive'); + + await this.queryInterface.createTable({ + tableName: 'users', + schema: 'archive' + }, { + firstName: DataTypes.STRING + }); + + await this.queryInterface.changeColumn({ + tableName: 'users', + schema: 'archive' + }, 'firstName', { + type: DataTypes.ENUM(['value1', 'value2', 'value3']) }); }); } @@ -129,8 +129,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { //SQlite natively doesn't support ALTER Foreign key if (dialect !== 'sqlite') { describe('should support foreign keys', () => { - beforeEach(function() { - return this.queryInterface.createTable('users', { + beforeEach(async function() { + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -140,41 +140,39 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { type: DataTypes.INTEGER, allowNull: false } - }).then(() => { - return this.queryInterface.createTable('level', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - } - }); }); - }); - it('able to change column to foreign key', function() { - return this.queryInterface.getForeignKeyReferencesForTable('users').then( foreignKeys => { - expect(foreignKeys).to.be.an('array'); - expect(foreignKeys).to.be.empty; - return this.queryInterface.changeColumn('users', 'level_id', { + await this.queryInterface.createTable('level', { + id: { type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }); - }).then(() => { - return this.queryInterface.getForeignKeyReferencesForTable('users'); - }).then(newForeignKeys => { - expect(newForeignKeys).to.be.an('array'); - expect(newForeignKeys).to.have.lengthOf(1); - expect(newForeignKeys[0].columnName).to.be.equal('level_id'); + primaryKey: true, + autoIncrement: true + } }); }); - it('able to change column property without affecting other properties', function() { - let firstTable, firstForeignKeys; + it('able to change column to foreign key', async function() { + const foreignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + expect(foreignKeys).to.be.an('array'); + expect(foreignKeys).to.be.empty; + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + references: { + model: 'level', + key: 'id' + }, + onUpdate: 'cascade', + onDelete: 'cascade' + }); + + const newForeignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + expect(newForeignKeys).to.be.an('array'); + expect(newForeignKeys).to.have.lengthOf(1); + expect(newForeignKeys[0].columnName).to.be.equal('level_id'); + }); + + it('able to change column property without affecting other properties', async function() { // 1. look for users table information // 2. change column level_id on users to have a Foreign Key // 3. look for users table Foreign Keys information @@ -182,59 +180,177 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { // 5. look for new foreign keys information // 6. look for new table structure information // 7. compare foreign keys and tables(before and after the changes) - return this.queryInterface.describeTable({ + const firstTable = await this.queryInterface.describeTable({ + tableName: 'users' + }); + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + references: { + model: 'level', + key: 'id' + }, + onUpdate: 'cascade', + onDelete: 'cascade' + }); + + const keys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + const firstForeignKeys = keys; + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + allowNull: true + }); + + const newForeignKeys = await this.queryInterface.getForeignKeyReferencesForTable('users'); + expect(firstForeignKeys.length).to.be.equal(newForeignKeys.length); + expect(firstForeignKeys[0].columnName).to.be.equal('level_id'); + expect(firstForeignKeys[0].columnName).to.be.equal(newForeignKeys[0].columnName); + + const describedTable = await this.queryInterface.describeTable({ tableName: 'users' - }).then( describedTable => { - firstTable = describedTable; - return this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - references: { - model: 'level', - key: 'id' - }, - onUpdate: 'cascade', - onDelete: 'cascade' - }); - }).then( () => { - return this.queryInterface.getForeignKeyReferencesForTable('users'); - }).then( keys => { - firstForeignKeys = keys; - return this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - allowNull: true - }); - }).then( () => { - return this.queryInterface.getForeignKeyReferencesForTable('users'); - }).then( newForeignKeys => { - expect(firstForeignKeys.length).to.be.equal(newForeignKeys.length); - expect(firstForeignKeys[0].columnName).to.be.equal('level_id'); - expect(firstForeignKeys[0].columnName).to.be.equal(newForeignKeys[0].columnName); - - return this.queryInterface.describeTable({ - tableName: 'users' - }); - }).then( describedTable => { - expect(describedTable.level_id).to.have.property('allowNull'); - expect(describedTable.level_id.allowNull).to.not.equal(firstTable.level_id.allowNull); - expect(describedTable.level_id.allowNull).to.be.equal(true); }); + + expect(describedTable.level_id).to.have.property('allowNull'); + expect(describedTable.level_id.allowNull).to.not.equal(firstTable.level_id.allowNull); + expect(describedTable.level_id.allowNull).to.be.equal(true); }); - it('should change the comment of column', function() { - return this.queryInterface.describeTable({ + it('should change the comment of column', async function() { + const describedTable = await this.queryInterface.describeTable({ tableName: 'users' - }).then(describedTable => { - expect(describedTable.level_id.comment).to.be.equal(null); - return this.queryInterface.changeColumn('users', 'level_id', { - type: DataTypes.INTEGER, - comment: 'FooBar' - }); - }).then(() => { - return this.queryInterface.describeTable({ tableName: 'users' }); - }).then(describedTable2 => { - expect(describedTable2.level_id.comment).to.be.equal('FooBar'); }); + + expect(describedTable.level_id.comment).to.be.equal(null); + + await this.queryInterface.changeColumn('users', 'level_id', { + type: DataTypes.INTEGER, + comment: 'FooBar' + }); + + const describedTable2 = await this.queryInterface.describeTable({ tableName: 'users' }); + expect(describedTable2.level_id.comment).to.be.equal('FooBar'); + }); + }); + } + + if (dialect === 'sqlite') { + it('should not remove unique constraints when adding or modifying columns', async function() { + await this.queryInterface.createTable({ + tableName: 'Foos' + }, { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + name: { + allowNull: false, + unique: true, + type: DataTypes.STRING + }, + email: { + allowNull: false, + unique: true, + type: DataTypes.STRING + } }); + + await this.queryInterface.addColumn('Foos', 'phone', { + type: DataTypes.STRING, + defaultValue: null, + allowNull: true + }); + + let table = await this.queryInterface.describeTable({ + tableName: 'Foos' + }); + expect(table.phone.allowNull).to.equal(true, '(1) phone column should allow null values'); + expect(table.phone.defaultValue).to.equal(null, '(1) phone column should have a default value of null'); + expect(table.email.unique).to.equal(true, '(1) email column should remain unique'); + expect(table.name.unique).to.equal(true, '(1) name column should remain unique'); + + await this.queryInterface.changeColumn('Foos', 'email', { + type: DataTypes.STRING, + allowNull: true + }); + + table = await this.queryInterface.describeTable({ + tableName: 'Foos' + }); + expect(table.email.allowNull).to.equal(true, '(2) email column should allow null values'); + expect(table.email.unique).to.equal(true, '(2) email column should remain unique'); + expect(table.name.unique).to.equal(true, '(2) name column should remain unique'); + }); + + it('should add unique constraints to 2 columns and keep allowNull', async function() { + await this.queryInterface.createTable({ + tableName: 'Foos' + }, { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER + }, + name: { + allowNull: false, + type: DataTypes.STRING + }, + email: { + allowNull: true, + type: DataTypes.STRING + } + }); + + await this.queryInterface.changeColumn('Foos', 'name', { + type: DataTypes.STRING, + unique: true + }); + await this.queryInterface.changeColumn('Foos', 'email', { + type: DataTypes.STRING, + unique: true + }); + + const table = await this.queryInterface.describeTable({ + tableName: 'Foos' + }); + expect(table.name.allowNull).to.equal(false); + expect(table.name.unique).to.equal(true); + expect(table.email.allowNull).to.equal(true); + expect(table.email.unique).to.equal(true); + }); + + it('should not remove foreign keys when adding or modifying columns', async function() { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), + User = this.sequelize.define('User', { username: DataTypes.STRING }); + + User.hasOne(Task); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + await this.queryInterface.addColumn('Tasks', 'bar', DataTypes.INTEGER); + let refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1, 'should keep foreign key after adding column'); + expect(refs[0].columnName).to.equal('UserId'); + expect(refs[0].referencedTableName).to.equal('Users'); + expect(refs[0].referencedColumnName).to.equal('id'); + + await this.queryInterface.changeColumn('Tasks', 'bar', DataTypes.STRING); + refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1, 'should keep foreign key after changing column'); + expect(refs[0].columnName).to.equal('UserId'); + expect(refs[0].referencedTableName).to.equal('Users'); + expect(refs[0].referencedColumnName).to.equal('id'); + + await this.queryInterface.renameColumn('Tasks', 'bar', 'foo'); + refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1, 'should keep foreign key after renaming column'); + expect(refs[0].columnName).to.equal('UserId'); + expect(refs[0].referencedTableName).to.equal('Users'); + expect(refs[0].referencedColumnName).to.equal('id'); }); } }); diff --git a/test/integration/query-interface/createTable.test.js b/test/integration/query-interface/createTable.test.js index 51a4304922c3..31f2af637138 100644 --- a/test/integration/query-interface/createTable.test.js +++ b/test/integration/query-interface/createTable.test.js @@ -12,29 +12,31 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); - // FIXME: These tests should make assertions against the created table using describeTable describe('createTable', () => { - it('should create a auto increment primary key', function() { - return this.queryInterface.createTable('TableWithPK', { + it('should create a auto increment primary key', async function() { + await this.queryInterface.createTable('TableWithPK', { table_id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true } - }).then(() => { - return this.queryInterface.insert(null, 'TableWithPK', {}, { raw: true, returning: true, plain: true }) - .then(([response]) => { - expect(response.table_id || typeof response !== 'object' && response).to.be.ok; - }); }); + + const result = await this.queryInterface.describeTable('TableWithPK'); + + if (dialect === 'mssql' || dialect === 'mysql' || dialect === 'mariadb') { + expect(result.table_id.autoIncrement).to.be.true; + } else if (dialect === 'postgres') { + expect(result.table_id.defaultValue).to.equal('nextval("TableWithPK_table_id_seq"::regclass)'); + } }); - it('should create unique constraint with uniqueKeys', function() { - return this.queryInterface.createTable('MyTable', { + it('should create unique constraint with uniqueKeys', async function() { + await this.queryInterface.createTable('MyTable', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -55,131 +57,123 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { fields: ['name'] } } - }).then(() => { - return this.queryInterface.showIndex('MyTable'); - }).then(indexes => { - switch (dialect) { - case 'postgres': - case 'postgres-native': - case 'sqlite': - case 'mssql': - - // name + email - expect(indexes[0].unique).to.be.true; - expect(indexes[0].fields[0].attribute).to.equal('name'); - expect(indexes[0].fields[1].attribute).to.equal('email'); - - // name - expect(indexes[1].unique).to.be.true; - expect(indexes[1].fields[0].attribute).to.equal('name'); - break; - - case 'mariadb': - case 'mysql': - // name + email - expect(indexes[1].unique).to.be.true; - expect(indexes[1].fields[0].attribute).to.equal('name'); - expect(indexes[1].fields[1].attribute).to.equal('email'); - - // name - expect(indexes[2].unique).to.be.true; - expect(indexes[2].fields[0].attribute).to.equal('name'); - break; - - default: - throw new Error(`Not implemented fpr ${dialect}`); - } }); + + const indexes = await this.queryInterface.showIndex('MyTable'); + switch (dialect) { + case 'postgres': + case 'postgres-native': + case 'sqlite': + case 'mssql': + + // name + email + expect(indexes[0].unique).to.be.true; + expect(indexes[0].fields[0].attribute).to.equal('name'); + expect(indexes[0].fields[1].attribute).to.equal('email'); + + // name + expect(indexes[1].unique).to.be.true; + expect(indexes[1].fields[0].attribute).to.equal('name'); + break; + case 'mariadb': + case 'mysql': + // name + email + expect(indexes[1].unique).to.be.true; + expect(indexes[1].fields[0].attribute).to.equal('name'); + expect(indexes[1].fields[1].attribute).to.equal('email'); + + // name + expect(indexes[2].unique).to.be.true; + expect(indexes[2].fields[0].attribute).to.equal('name'); + break; + default: + throw new Error(`Not implemented fpr ${dialect}`); + } }); - it('should work with schemas', function() { - return this.sequelize.createSchema('hero').then(() => { - return this.queryInterface.createTable('User', { - name: { - type: DataTypes.STRING - } - }, { - schema: 'hero' - }); + it('should work with schemas', async function() { + await this.sequelize.createSchema('hero'); + + await this.queryInterface.createTable('User', { + name: { + type: DataTypes.STRING + } + }, { + schema: 'hero' }); }); describe('enums', () => { - it('should work with enums (1)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (1)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: DataTypes.ENUM('value1', 'value2', 'value3') - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (2)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (2)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: { type: DataTypes.ENUM, values: ['value1', 'value2', 'value3'] } - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.someEnum.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (3)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (3)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: { type: DataTypes.ENUM, values: ['value1', 'value2', 'value3'], field: 'otherName' } - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (4)', function() { - return this.queryInterface.createSchema('archive').then(() => { - return this.queryInterface.createTable('SomeTable', { - someEnum: { - type: DataTypes.ENUM, - values: ['value1', 'value2', 'value3'], - field: 'otherName' - } - }, { schema: 'archive' }); - }).then(() => { - return this.queryInterface.describeTable('SomeTable', { schema: 'archive' }); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); + it('should work with enums (4)', async function() { + await this.queryInterface.createSchema('archive'); + + await this.queryInterface.createTable('SomeTable', { + someEnum: { + type: DataTypes.ENUM, + values: ['value1', 'value2', 'value3'], + field: 'otherName' } - }); + }, { schema: 'archive' }); + + const table = await this.queryInterface.describeTable('SomeTable', { schema: 'archive' }); + if (dialect.includes('postgres')) { + expect(table.otherName.special).to.deep.equal(['value1', 'value2', 'value3']); + } }); - it('should work with enums (5)', function() { - return this.queryInterface.createTable('SomeTable', { + it('should work with enums (5)', async function() { + await this.queryInterface.createTable('SomeTable', { someEnum: { type: DataTypes.ENUM(['COMMENT']), comment: 'special enum col' } - }).then(() => { - return this.queryInterface.describeTable('SomeTable'); - }).then(table => { - if (dialect.includes('postgres')) { - expect(table.someEnum.special).to.deep.equal(['COMMENT']); - expect(table.someEnum.comment).to.equal('special enum col'); - } }); + + const table = await this.queryInterface.describeTable('SomeTable'); + if (dialect.includes('postgres')) { + expect(table.someEnum.special).to.deep.equal(['COMMENT']); + expect(table.someEnum.comment).to.equal('special enum col'); + } }); }); }); diff --git a/test/integration/query-interface/describeTable.test.js b/test/integration/query-interface/describeTable.test.js index 7671423b0e16..5785a8ba5e6e 100644 --- a/test/integration/query-interface/describeTable.test.js +++ b/test/integration/query-interface/describeTable.test.js @@ -12,13 +12,13 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('describeTable', () => { if (Support.sequelize.dialect.supports.schemas) { - it('reads the metadata of the table with schema', function() { + it('reads the metadata of the table with schema', async function() { const MyTable1 = this.sequelize.define('my_table', { username1: DataTypes.STRING }); @@ -27,36 +27,25 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { username2: DataTypes.STRING }, { schema: 'test_meta' }); - return this.sequelize.createSchema('test_meta') - .then(() => { - return MyTable1.sync({ force: true }); - }) - .then(() => { - return MyTable2.sync({ force: true }); - }) - .then(() => { - return this.queryInterface.describeTable('my_tables', 'test_meta'); - }) - .then(metadata => { - expect(metadata.username2).not.to.be.undefined; - }) - .then(() => { - return this.queryInterface.describeTable('my_tables'); - }) - .then(metadata => { - expect(metadata.username1).not.to.be.undefined; - return this.sequelize.dropSchema('test_meta'); - }); + await this.sequelize.createSchema('test_meta'); + await MyTable1.sync({ force: true }); + await MyTable2.sync({ force: true }); + const metadata0 = await this.queryInterface.describeTable('my_tables', 'test_meta'); + expect(metadata0.username2).not.to.be.undefined; + const metadata = await this.queryInterface.describeTable('my_tables'); + expect(metadata.username1).not.to.be.undefined; + + await this.sequelize.dropSchema('test_meta'); }); } - it('rejects when no data is available', function() { - return expect( + it('rejects when no data is available', async function() { + await expect( this.queryInterface.describeTable('_some_random_missing_table') ).to.be.rejectedWith('No description found for "_some_random_missing_table" table. Check the table name and schema; remember, they _are_ case sensitive.'); }); - it('reads the metadata of the table', function() { + it('reads the metadata of the table', async function() { const Users = this.sequelize.define('_Users', { username: DataTypes.STRING, city: { @@ -68,81 +57,79 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { enumVals: DataTypes.ENUM('hello', 'world') }, { freezeTableName: true }); - return Users.sync({ force: true }).then(() => { - return this.queryInterface.describeTable('_Users').then(metadata => { - const id = metadata.id; - const username = metadata.username; - const city = metadata.city; - const isAdmin = metadata.isAdmin; - const enumVals = metadata.enumVals; - - expect(id.primaryKey).to.be.true; - - if (['mysql', 'mssql'].includes(dialect)) { - expect(id.autoIncrement).to.be.true; - } - - let assertVal = 'VARCHAR(255)'; - switch (dialect) { - case 'postgres': - assertVal = 'CHARACTER VARYING(255)'; - break; - case 'mssql': - assertVal = 'NVARCHAR(255)'; - break; - } - expect(username.type).to.equal(assertVal); - expect(username.allowNull).to.be.true; - - switch (dialect) { - case 'sqlite': - expect(username.defaultValue).to.be.undefined; - break; - default: - expect(username.defaultValue).to.be.null; - } - - switch (dialect) { - case 'sqlite': - expect(city.defaultValue).to.be.null; - break; - } - - assertVal = 'TINYINT(1)'; - switch (dialect) { - case 'postgres': - assertVal = 'BOOLEAN'; - break; - case 'mssql': - assertVal = 'BIT'; - break; - } - expect(isAdmin.type).to.equal(assertVal); - expect(isAdmin.allowNull).to.be.true; - switch (dialect) { - case 'sqlite': - expect(isAdmin.defaultValue).to.be.undefined; - break; - default: - expect(isAdmin.defaultValue).to.be.null; - } - - if (dialect.match(/^postgres/)) { - expect(enumVals.special).to.be.instanceof(Array); - expect(enumVals.special).to.have.length(2); - } else if (dialect === 'mysql') { - expect(enumVals.type).to.eql('ENUM(\'hello\',\'world\')'); - } - - if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { - expect(city.comment).to.equal('Users City'); - expect(username.comment).to.equal(null); - } - }); - }); + await Users.sync({ force: true }); + const metadata = await this.queryInterface.describeTable('_Users'); + const id = metadata.id; + const username = metadata.username; + const city = metadata.city; + const isAdmin = metadata.isAdmin; + const enumVals = metadata.enumVals; + + expect(id.primaryKey).to.be.true; + + if (['mysql', 'mssql'].includes(dialect)) { + expect(id.autoIncrement).to.be.true; + } + + let assertVal = 'VARCHAR(255)'; + switch (dialect) { + case 'postgres': + assertVal = 'CHARACTER VARYING(255)'; + break; + case 'mssql': + assertVal = 'NVARCHAR(255)'; + break; + } + expect(username.type).to.equal(assertVal); + expect(username.allowNull).to.be.true; + + switch (dialect) { + case 'sqlite': + expect(username.defaultValue).to.be.undefined; + break; + default: + expect(username.defaultValue).to.be.null; + } + + switch (dialect) { + case 'sqlite': + expect(city.defaultValue).to.be.null; + break; + } + + assertVal = 'TINYINT(1)'; + switch (dialect) { + case 'postgres': + assertVal = 'BOOLEAN'; + break; + case 'mssql': + assertVal = 'BIT'; + break; + } + expect(isAdmin.type).to.equal(assertVal); + expect(isAdmin.allowNull).to.be.true; + switch (dialect) { + case 'sqlite': + expect(isAdmin.defaultValue).to.be.undefined; + break; + default: + expect(isAdmin.defaultValue).to.be.null; + } + + if (dialect.match(/^postgres/)) { + expect(enumVals.special).to.be.instanceof(Array); + expect(enumVals.special).to.have.length(2); + } else if (dialect === 'mysql') { + expect(enumVals.type).to.eql('ENUM(\'hello\',\'world\')'); + } + + if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') { + expect(city.comment).to.equal('Users City'); + expect(username.comment).to.equal(null); + } }); - it('should correctly determine the primary key columns', function() { + it('should correctly determine the primary key columns', async function() { const Country = this.sequelize.define('_Country', { code: { type: DataTypes.STRING, primaryKey: true }, name: { type: DataTypes.STRING, allowNull: false } @@ -160,26 +147,20 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { } }, { freezeTableName: true }); - return Country.sync({ force: true }).then(() => { - return this.queryInterface.describeTable('_Country').then( - metacountry => { - expect(metacountry.code.primaryKey).to.eql(true); - expect(metacountry.name.primaryKey).to.eql(false); - - return Alumni.sync({ force: true }).then(() => { - return this.queryInterface.describeTable('_Alumni').then( - metalumni => { - expect(metalumni.year.primaryKey).to.eql(true); - expect(metalumni.num.primaryKey).to.eql(true); - expect(metalumni.username.primaryKey).to.eql(false); - expect(metalumni.dob.primaryKey).to.eql(false); - expect(metalumni.dod.primaryKey).to.eql(false); - expect(metalumni.ctrycod.primaryKey).to.eql(false); - expect(metalumni.city.primaryKey).to.eql(false); - }); - }); - }); - }); + await Country.sync({ force: true }); + const metacountry = await this.queryInterface.describeTable('_Country'); + expect(metacountry.code.primaryKey).to.eql(true); + expect(metacountry.name.primaryKey).to.eql(false); + + await Alumni.sync({ force: true }); + const metalumni = await this.queryInterface.describeTable('_Alumni'); + expect(metalumni.year.primaryKey).to.eql(true); + expect(metalumni.num.primaryKey).to.eql(true); + expect(metalumni.username.primaryKey).to.eql(false); + expect(metalumni.dob.primaryKey).to.eql(false); + expect(metalumni.dod.primaryKey).to.eql(false); + expect(metalumni.ctrycod.primaryKey).to.eql(false); + expect(metalumni.city.primaryKey).to.eql(false); }); }); }); diff --git a/test/integration/query-interface/dropEnum.test.js b/test/integration/query-interface/dropEnum.test.js index 9ee1c2a0b9de..136e30331a5c 100644 --- a/test/integration/query-interface/dropEnum.test.js +++ b/test/integration/query-interface/dropEnum.test.js @@ -12,43 +12,29 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('dropEnum', () => { - beforeEach(function() { - return this.queryInterface.createTable('menus', { - structuretype: { - type: DataTypes.ENUM('menus', 'submenu', 'routine'), - allowNull: true - }, - sequence: { - type: DataTypes.INTEGER, - allowNull: true - }, - name: { - type: DataTypes.STRING, - allowNull: true - } + beforeEach(async function() { + await this.queryInterface.createTable('menus', { + structuretype: DataTypes.ENUM('menus', 'submenu', 'routine'), + sequence: DataTypes.INTEGER, + name: DataTypes.STRING }); }); if (dialect === 'postgres') { - it('should be able to drop the specified enum', function() { - return this.queryInterface.removeColumn('menus', 'structuretype').then(() => { - return this.queryInterface.pgListEnums('menus'); - }).then(enumList => { - expect(enumList).to.have.lengthOf(1); - expect(enumList[0]).to.have.property('enum_name').and.to.equal('enum_menus_structuretype'); - }).then(() => { - return this.queryInterface.dropEnum('enum_menus_structuretype'); - }).then(() => { - return this.queryInterface.pgListEnums('menus'); - }).then(enumList => { - expect(enumList).to.be.an('array'); - expect(enumList).to.have.lengthOf(0); - }); + it('should be able to drop the specified enum', async function() { + await this.queryInterface.removeColumn('menus', 'structuretype'); + const enumList0 = await this.queryInterface.pgListEnums('menus'); + expect(enumList0).to.have.lengthOf(1); + expect(enumList0[0]).to.have.property('enum_name').and.to.equal('enum_menus_structuretype'); + await this.queryInterface.dropEnum('enum_menus_structuretype'); + const enumList = await this.queryInterface.pgListEnums('menus'); + expect(enumList).to.be.an('array'); + expect(enumList).to.have.lengthOf(0); }); } }); diff --git a/test/integration/query-interface/getForeignKeyReferencesForTable.test.js b/test/integration/query-interface/getForeignKeyReferencesForTable.test.js new file mode 100644 index 000000000000..feacd9e8d457 --- /dev/null +++ b/test/integration/query-interface/getForeignKeyReferencesForTable.test.js @@ -0,0 +1,44 @@ +'use strict'; + +const chai = require('chai'); +const expect = chai.expect; +const Support = require('../support'); +const DataTypes = require('../../../lib/data-types'); + +describe(Support.getTestDialectTeaser('QueryInterface'), () => { + beforeEach(function() { + this.sequelize.options.quoteIdenifiers = true; + this.queryInterface = this.sequelize.getQueryInterface(); + }); + + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); + }); + + describe('getForeignKeyReferencesForTable', () => { + it('should be able to provide existing foreign keys', async function() { + const Task = this.sequelize.define('Task', { title: DataTypes.STRING }), + User = this.sequelize.define('User', { username: DataTypes.STRING }); + + User.hasOne(Task); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + const expectedObject = { + columnName: 'UserId', + referencedColumnName: 'id', + referencedTableName: 'Users' + }; + + let refs = await this.queryInterface.getForeignKeyReferencesForTable({ tableName: 'Tasks' }); + expect(refs.length).to.equal(1); + expect(refs[0]).deep.include.all(expectedObject); + + refs = await this.queryInterface.getForeignKeyReferencesForTable('Tasks'); + expect(refs.length).to.equal(1); + expect(refs[0]).deep.include.all(expectedObject); + + }); + }); +}); diff --git a/test/integration/query-interface/removeColumn.test.js b/test/integration/query-interface/removeColumn.test.js index 69338852049e..983d7a9d8ace 100644 --- a/test/integration/query-interface/removeColumn.test.js +++ b/test/integration/query-interface/removeColumn.test.js @@ -12,14 +12,14 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { this.queryInterface = this.sequelize.getQueryInterface(); }); - afterEach(function() { - return Support.dropTestSchemas(this.sequelize); + afterEach(async function() { + await Support.dropTestSchemas(this.sequelize); }); describe('removeColumn', () => { describe('(without a schema)', () => { - beforeEach(function() { - return this.queryInterface.createTable('users', { + beforeEach(async function() { + await this.queryInterface.createTable('users', { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -46,41 +46,31 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { }); }); - it('should be able to remove a column with a default value', function() { - return this.queryInterface.removeColumn('users', 'firstName').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('firstName'); - }); + it('should be able to remove a column with a default value', async function() { + await this.queryInterface.removeColumn('users', 'firstName'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('firstName'); }); - it('should be able to remove a column without default value', function() { - return this.queryInterface.removeColumn('users', 'lastName').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('lastName'); - }); + it('should be able to remove a column without default value', async function() { + await this.queryInterface.removeColumn('users', 'lastName'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('lastName'); }); - it('should be able to remove a column with a foreign key constraint', function() { - return this.queryInterface.removeColumn('users', 'manager').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('manager'); - }); + it('should be able to remove a column with a foreign key constraint', async function() { + await this.queryInterface.removeColumn('users', 'manager'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('manager'); }); - it('should be able to remove a column with primaryKey', function() { - return this.queryInterface.removeColumn('users', 'manager').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('manager'); - return this.queryInterface.removeColumn('users', 'id'); - }).then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('id'); - }); + it('should be able to remove a column with primaryKey', async function() { + await this.queryInterface.removeColumn('users', 'manager'); + const table0 = await this.queryInterface.describeTable('users'); + expect(table0).to.not.have.property('manager'); + await this.queryInterface.removeColumn('users', 'id'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('id'); }); // From MSSQL documentation on ALTER COLUMN: @@ -88,85 +78,83 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { // - Used in a CHECK or UNIQUE constraint. // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql#arguments if (dialect !== 'mssql') { - it('should be able to remove a column with unique contraint', function() { - return this.queryInterface.removeColumn('users', 'email').then(() => { - return this.queryInterface.describeTable('users'); - }).then(table => { - expect(table).to.not.have.property('email'); - }); + it('should be able to remove a column with unique contraint', async function() { + await this.queryInterface.removeColumn('users', 'email'); + const table = await this.queryInterface.describeTable('users'); + expect(table).to.not.have.property('email'); }); } }); describe('(with a schema)', () => { - beforeEach(function() { - return this.sequelize.createSchema('archive').then(() => { - return this.queryInterface.createTable({ - tableName: 'users', - schema: 'archive' - }, { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - firstName: { - type: DataTypes.STRING, - defaultValue: 'Someone' - }, - lastName: { - type: DataTypes.STRING - }, - email: { - type: DataTypes.STRING, - unique: true - } - }); + beforeEach(async function() { + await this.sequelize.createSchema('archive'); + + await this.queryInterface.createTable({ + tableName: 'users', + schema: 'archive' + }, { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + firstName: { + type: DataTypes.STRING, + defaultValue: 'Someone' + }, + lastName: { + type: DataTypes.STRING + }, + email: { + type: DataTypes.STRING, + unique: true + } }); }); - it('should be able to remove a column with a default value', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column with a default value', async function() { + await this.queryInterface.removeColumn({ tableName: 'users', schema: 'archive' }, 'firstName' - ).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('firstName'); + ); + + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' }); + + expect(table).to.not.have.property('firstName'); }); - it('should be able to remove a column without default value', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column without default value', async function() { + await this.queryInterface.removeColumn({ tableName: 'users', schema: 'archive' }, 'lastName' - ).then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('lastName'); + ); + + const table = await this.queryInterface.describeTable({ + tableName: 'users', + schema: 'archive' }); + + expect(table).to.not.have.property('lastName'); }); - it('should be able to remove a column with primaryKey', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column with primaryKey', async function() { + await this.queryInterface.removeColumn({ + tableName: 'users', + schema: 'archive' + }, 'id'); + + const table = await this.queryInterface.describeTable({ tableName: 'users', schema: 'archive' - }, 'id').then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('id'); }); + + expect(table).to.not.have.property('id'); }); // From MSSQL documentation on ALTER COLUMN: @@ -174,18 +162,18 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => { // - Used in a CHECK or UNIQUE constraint. // https://docs.microsoft.com/en-us/sql/t-sql/statements/alter-table-transact-sql#arguments if (dialect !== 'mssql') { - it('should be able to remove a column with unique contraint', function() { - return this.queryInterface.removeColumn({ + it('should be able to remove a column with unique contraint', async function() { + await this.queryInterface.removeColumn({ + tableName: 'users', + schema: 'archive' + }, 'email'); + + const table = await this.queryInterface.describeTable({ tableName: 'users', schema: 'archive' - }, 'email').then(() => { - return this.queryInterface.describeTable({ - tableName: 'users', - schema: 'archive' - }); - }).then(table => { - expect(table).to.not.have.property('email'); }); + + expect(table).to.not.have.property('email'); }); } }); diff --git a/test/integration/replication.test.js b/test/integration/replication.test.js index 2bcc9c2bcadf..1ba98bb12912 100644 --- a/test/integration/replication.test.js +++ b/test/integration/replication.test.js @@ -13,13 +13,13 @@ describe(Support.getTestDialectTeaser('Replication'), () => { let sandbox; let readSpy, writeSpy; - beforeEach(function() { + beforeEach(async function() { sandbox = sinon.createSandbox(); this.sequelize = Support.getSequelizeInstance(null, null, null, { replication: { - write: Support.getConnectionOptions(), - read: [Support.getConnectionOptions()] + write: Support.getConnectionOptionsWithoutPool(), + read: [Support.getConnectionOptionsWithoutPool()] } }); @@ -33,11 +33,9 @@ describe(Support.getTestDialectTeaser('Replication'), () => { } }); - return this.User.sync({ force: true }) - .then(() => { - readSpy = sandbox.spy(this.sequelize.connectionManager.pool.read, 'acquire'); - writeSpy = sandbox.spy(this.sequelize.connectionManager.pool.write, 'acquire'); - }); + await this.User.sync({ force: true }); + readSpy = sandbox.spy(this.sequelize.connectionManager.pool.read, 'acquire'); + writeSpy = sandbox.spy(this.sequelize.connectionManager.pool.write, 'acquire'); }); afterEach(() => { @@ -54,25 +52,25 @@ describe(Support.getTestDialectTeaser('Replication'), () => { chai.expect(readSpy.notCalled).eql(true); } - it('should be able to make a write', function() { - return this.User.create({ + it('should be able to make a write', async function() { + await expectWriteCalls(await this.User.create({ firstName: Math.random().toString() - }).then(expectWriteCalls); + })); }); - it('should be able to make a read', function() { - return this.User.findAll().then(expectReadCalls); + it('should be able to make a read', async function() { + await expectReadCalls(await this.User.findAll()); }); - it('should run read-only transactions on the replica', function() { - return this.sequelize.transaction({ readOnly: true }, transaction => { + it('should run read-only transactions on the replica', async function() { + await expectReadCalls(await this.sequelize.transaction({ readOnly: true }, transaction => { return this.User.findAll({ transaction }); - }).then(expectReadCalls); + })); }); - it('should run non-read-only transactions on the primary', function() { - return this.sequelize.transaction(transaction => { + it('should run non-read-only transactions on the primary', async function() { + await expectWriteCalls(await this.sequelize.transaction(transaction => { return this.User.findAll({ transaction }); - }).then(expectWriteCalls); + })); }); }); diff --git a/test/integration/schema.test.js b/test/integration/schema.test.js index f5756b2a8e51..4bfe96b97a80 100644 --- a/test/integration/schema.test.js +++ b/test/integration/schema.test.js @@ -6,43 +6,37 @@ const chai = require('chai'), DataTypes = require('../../lib/data-types'); describe(Support.getTestDialectTeaser('Schema'), () => { - beforeEach(function() { - return this.sequelize.createSchema('testschema'); + beforeEach(async function() { + await this.sequelize.createSchema('testschema'); }); - afterEach(function() { - return this.sequelize.dropSchema('testschema'); + afterEach(async function() { + await this.sequelize.dropSchema('testschema'); }); - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelize.define('User', { aNumber: { type: DataTypes.INTEGER } }, { schema: 'testschema' }); - return this.User.sync({ force: true }); + await this.User.sync({ force: true }); }); - it('supports increment', function() { - return this.User.create({ aNumber: 1 }).then(user => { - return user.increment('aNumber', { by: 3 }); - }).then(result => { - return result.reload(); - }).then(user => { - expect(user).to.be.ok; - expect(user.aNumber).to.be.equal(4); - }); + it('supports increment', async function() { + const user0 = await this.User.create({ aNumber: 1 }); + const result = await user0.increment('aNumber', { by: 3 }); + const user = await result.reload(); + expect(user).to.be.ok; + expect(user.aNumber).to.be.equal(4); }); - it('supports decrement', function() { - return this.User.create({ aNumber: 10 }).then(user => { - return user.decrement('aNumber', { by: 3 }); - }).then(result => { - return result.reload(); - }).then(user => { - expect(user).to.be.ok; - expect(user.aNumber).to.be.equal(7); - }); + it('supports decrement', async function() { + const user0 = await this.User.create({ aNumber: 10 }); + const result = await user0.decrement('aNumber', { by: 3 }); + const user = await result.reload(); + expect(user).to.be.ok; + expect(user.aNumber).to.be.equal(7); }); }); diff --git a/test/integration/sequelize.test.js b/test/integration/sequelize.test.js old mode 100755 new mode 100644 index 12878a65d408..78a404bb9e0e --- a/test/integration/sequelize.test.js +++ b/test/integration/sequelize.test.js @@ -7,7 +7,6 @@ const dialect = Support.getTestDialect(); const _ = require('lodash'); const Sequelize = require('../../index'); const config = require('../config/config'); -const moment = require('moment'); const Transaction = require('../../lib/transaction'); const sinon = require('sinon'); const current = Support.sequelize; @@ -37,6 +36,15 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(sequelize.config.host).to.equal('127.0.0.1'); }); + it('should set operators aliases on dialect queryGenerator', () => { + const operatorsAliases = { fake: true }; + const sequelize = Support.createSequelizeInstance({ operatorsAliases }); + + expect(sequelize).to.have.property('dialect'); + expect(sequelize.dialect).to.have.property('queryGenerator'); + expect(sequelize.dialect.queryGenerator).to.have.property('OperatorsAliasMap'); + expect(sequelize.dialect.queryGenerator.OperatorsAliasMap).to.be.eql(operatorsAliases); + }); if (dialect === 'sqlite') { it('should work with connection strings (1)', () => { @@ -51,69 +59,79 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { } if (dialect === 'postgres') { - const getConnectionUri = o => `${o.protocol}://${o.username}:${o.password}@${o.host}${o.port ? `:${o.port}` : ''}/${o.database}`; + const getConnectionUri = o => `${o.protocol}://${o.username}:${o.password}@${o.host}${o.port ? `:${o.port}` : ''}/${o.database}${o.options ? `?options=${o.options}` : ''}`; it('should work with connection strings (postgres protocol)', () => { - const connectionUri = getConnectionUri(Object.assign(config[dialect], { protocol: 'postgres' })); + const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgres' }); // postgres://... new Sequelize(connectionUri); }); it('should work with connection strings (postgresql protocol)', () => { - const connectionUri = getConnectionUri(Object.assign(config[dialect], { protocol: 'postgresql' })); + const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgresql' }); // postgresql://... new Sequelize(connectionUri); }); + it('should work with options in the connection string (postgresql protocol)', async () => { + const connectionUri = getConnectionUri({ ...config[dialect], protocol: 'postgresql', options: '-c%20search_path%3dtest_schema' }); + const sequelize = new Sequelize(connectionUri); + const result = await sequelize.query('SHOW search_path'); + expect(result[0].search_path).to.equal('test_schema'); + }); + } }); if (dialect !== 'sqlite') { describe('authenticate', () => { describe('with valid credentials', () => { - it('triggers the success event', function() { - return this.sequelize.authenticate(); + it('triggers the success event', async function() { + await this.sequelize.authenticate(); }); }); describe('with an invalid connection', () => { beforeEach(function() { - const options = Object.assign({}, this.sequelize.options, { port: '99999' }); + const options = { ...this.sequelize.options, port: '99999' }; this.sequelizeWithInvalidConnection = new Sequelize('wat', 'trololo', 'wow', options); }); - it('triggers the error event', function() { - return this - .sequelizeWithInvalidConnection - .authenticate() - .catch(err => { - expect(err).to.not.be.null; - }); + it('triggers the error event', async function() { + try { + await this + .sequelizeWithInvalidConnection + .authenticate(); + } catch (err) { + expect(err).to.not.be.null; + } }); - it('triggers an actual RangeError or ConnectionError', function() { - return this - .sequelizeWithInvalidConnection - .authenticate() - .catch(err => { - expect( - err instanceof RangeError || - err instanceof Sequelize.ConnectionError - ).to.be.ok; - }); + it('triggers an actual RangeError or ConnectionError', async function() { + try { + await this + .sequelizeWithInvalidConnection + .authenticate(); + } catch (err) { + expect( + err instanceof RangeError || + err instanceof Sequelize.ConnectionError + ).to.be.ok; + } }); - it('triggers the actual adapter error', function() { - return this - .sequelizeWithInvalidConnection - .authenticate() - .catch(err => { - console.log(err); - expect( - err.message.includes('connect ECONNREFUSED') || - err.message.includes('invalid port number') || - err.message.match(/should be >=? 0 and < 65536/) || - err.message.includes('Login failed for user') || - err.message.includes('must be > 0 and < 65536') - ).to.be.ok; - }); + it('triggers the actual adapter error', async function() { + try { + await this + .sequelizeWithInvalidConnection + .authenticate(); + } catch (err) { + console.log(err); + expect( + err.message.includes('connect ECONNREFUSED') || + err.message.includes('invalid port number') || + err.message.match(/should be >=? 0 and < 65536/) || + err.message.includes('Login failed for user') || + err.message.includes('must be > 0 and < 65536') + ).to.be.ok; + } }); }); @@ -122,38 +140,41 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { this.sequelizeWithInvalidCredentials = new Sequelize('localhost', 'wtf', 'lol', this.sequelize.options); }); - it('triggers the error event', function() { - return this - .sequelizeWithInvalidCredentials - .authenticate() - .catch(err => { - expect(err).to.not.be.null; - }); + it('triggers the error event', async function() { + try { + await this + .sequelizeWithInvalidCredentials + .authenticate(); + } catch (err) { + expect(err).to.not.be.null; + } }); - it('triggers an actual sequlize error', function() { - return this - .sequelizeWithInvalidCredentials - .authenticate() - .catch(err => { - expect(err).to.be.instanceof(Sequelize.Error); - }); + it('triggers an actual sequlize error', async function() { + try { + await this + .sequelizeWithInvalidCredentials + .authenticate(); + } catch (err) { + expect(err).to.be.instanceof(Sequelize.Error); + } }); - it('triggers the error event when using replication', () => { - return new Sequelize('sequelize', null, null, { - dialect, - replication: { - read: { - host: 'localhost', - username: 'omg', - password: 'lol' + it('triggers the error event when using replication', async () => { + try { + await new Sequelize('sequelize', null, null, { + dialect, + replication: { + read: { + host: 'localhost', + username: 'omg', + password: 'lol' + } } - } - }).authenticate() - .catch(err => { - expect(err).to.not.be.null; - }); + }).authenticate(); + } catch (err) { + expect(err).to.not.be.null; + } }); }); }); @@ -206,612 +227,6 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); }); - describe('query', () => { - afterEach(function() { - this.sequelize.options.quoteIdentifiers = true; - console.log.restore && console.log.restore(); - }); - - beforeEach(function() { - this.User = this.sequelize.define('User', { - username: { - type: DataTypes.STRING, - unique: true - }, - emailAddress: { - type: DataTypes.STRING, - field: 'email_address' - } - }); - - this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ - qq('createdAt') }, ${qq('updatedAt') - }) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; - - return this.User.sync({ force: true }); - }); - - it('executes a query the internal way', function() { - return this.sequelize.query(this.insertQuery, { raw: true }); - }); - - it('executes a query if only the sql is passed', function() { - return this.sequelize.query(this.insertQuery); - }); - - it('executes a query if a placeholder value is an array', function() { - return this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + - `${qq('createdAt')}, ${qq('updatedAt')}) VALUES ?;`, { - replacements: [[ - ['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'], - ['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'] - ]] - }) - .then(() => this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: this.sequelize.QueryTypes.SELECT - })) - .then(rows => { - expect(rows).to.be.lengthOf(2); - expect(rows[0].username).to.be.equal('john'); - expect(rows[1].username).to.be.equal('michael'); - }); - }); - - describe('retry', () => { - it('properly bind parameters on extra retries', function() { - const payload = { - username: 'test', - createdAt: '2010-10-10 00:00:00', - updatedAt: '2010-10-10 00:00:00' - }; - - const spy = sinon.spy(); - - return expect(this.User.create(payload).then(() => this.sequelize.query(` - INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt); - `, { - bind: payload, - logging: spy, - retry: { - max: 3, - match: [ - /Validation/ - ] - } - }))).to.be.rejectedWith(Sequelize.UniqueConstraintError).then(() => { - expect(spy.callCount).to.eql(3); - }); - }); - }); - - describe('logging', () => { - it('executes a query with global benchmarking option and custom logger', () => { - const logger = sinon.spy(); - const sequelize = Support.createSequelizeInstance({ - logging: logger, - benchmark: true - }); - - return sequelize.query('select 1;').then(() => { - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); - }); - - it('executes a query with benchmarking option and custom logger', function() { - const logger = sinon.spy(); - - return this.sequelize.query('select 1;', { - logging: logger, - benchmark: true - }).then(() => { - expect(logger.calledOnce).to.be.true; - expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); - expect(typeof logger.args[0][1] === 'number').to.be.true; - }); - }); - describe('log sql when set logQueryParameters', () => { - beforeEach(function() { - this.sequelize = Support.createSequelizeInstance({ - benchmark: true, - logQueryParameters: true - }); - this.User = this.sequelize.define('User', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - username: { - type: DataTypes.STRING - }, - emailAddress: { - type: DataTypes.STRING - } - }, { - timestamps: false - }); - - return this.User.sync({ force: true }); - }); - it('add parameters in log sql', function() { - let createSql, updateSql; - return this.User.create({ - username: 'john', - emailAddress: 'john@gmail.com' - }, { - logging: s =>{ - createSql = s; - } - }).then(user=>{ - user.username = 'li'; - return user.save({ - logging: s =>{ - updateSql = s; - } - }); - }).then(()=>{ - expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); - expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); - }); - }); - - it('add parameters in log sql when use bind value', function() { - let logSql; - const typeCast = dialect === 'postgres' ? '::text' : ''; - return this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }) - .then(()=>{ - expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); - }); - }); - }); - - }); - - it('executes select queries correctly', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`select * from ${qq(this.User.tableName)}`); - }).then(([users]) => { - expect(users.map(u => { return u.username; })).to.include('john'); - }); - }); - - it('executes select queries correctly when quoteIdentifiers is false', function() { - const seq = Object.create(this.sequelize); - - seq.options.quoteIdentifiers = false; - return seq.query(this.insertQuery).then(() => { - return seq.query(`select * from ${qq(this.User.tableName)}`); - }).then(([users]) => { - expect(users.map(u => { return u.username; })).to.include('john'); - }); - }); - - it('executes select query with dot notation results', function() { - return this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`).then(() => { - return this.sequelize.query(this.insertQuery); - }).then(() => { - return this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); - }).then(([users]) => { - expect(users).to.deep.equal([{ 'user.username': 'john' }]); - }); - }); - - it('executes select query with dot notation results and nest it', function() { - return this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`).then(() => { - return this.sequelize.query(this.insertQuery); - }).then(() => { - return this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); - }).then(users => { - expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); - }); - }); - - if (dialect === 'mysql') { - it('executes stored procedures', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query('DROP PROCEDURE IF EXISTS foo').then(() => { - return this.sequelize.query( - `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` - ).then(() => { - return this.sequelize.query('CALL foo()').then(users => { - expect(users.map(u => { return u.username; })).to.include('john'); - }); - }); - }); - }); - }); - } else { - console.log('FIXME: I want to be supported in this dialect as well :-('); - } - - it('uses the passed model', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User - }); - }).then(users => { - expect(users[0]).to.be.instanceof(this.User); - }); - }); - - it('maps the field names to attributes based on the passed model', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - model: this.User, - mapToModel: true - }); - }).then(users => { - expect(users[0].emailAddress).to.be.equal('john@gmail.com'); - }); - }); - - it('arbitrarily map the field names', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'userName', email_address: 'email' } - }); - }).then(users => { - expect(users[0].userName).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); - }); - }); - - it('keeps field names that are mapped to the same name', function() { - return this.sequelize.query(this.insertQuery).then(() => { - return this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { - type: 'SELECT', - fieldMap: { username: 'username', email_address: 'email' } - }); - }).then(users => { - expect(users[0].username).to.be.equal('john'); - expect(users[0].email).to.be.equal('john@gmail.com'); - }); - }); - - it('reject if `values` and `options.replacements` are both passed', function() { - return this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `options.bind` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `sql.bind` and `options.bind` cannot be set at the same time'); - }); - - it('reject if `options.replacements` and `options.bind` are both passed', function() { - return this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `sql.values` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `sql.bind` and `options.replacements`` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('reject if `options.bind` and `sql.replacements` are both passed', function() { - return this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); - }); - - it('properly adds and escapes replacement value', function() { - let logSql; - const number = 1, - date = new Date(), - string = 't\'e"st', - boolean = true, - buffer = Buffer.from('t\'e"st'); - - date.setMilliseconds(0); - return this.sequelize.query({ - query: 'select ? as number, ? as date,? as string,? as boolean,? as buffer', - values: [number, date, string, boolean, buffer] - }, { - type: this.sequelize.QueryTypes.SELECT, - logging(s) { - logSql = s; - } - }).then(result => { - const res = result[0] || {}; - res.date = res.date && new Date(res.date); - res.boolean = res.boolean && true; - if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { - res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); - } - expect(res).to.deep.equal({ - number, - date, - string, - boolean, - buffer - }); - expect(logSql).to.not.include('?'); - }); - }); - - it('it allows to pass custom class instances', function() { - let logSql; - class SQLStatement { - constructor() { - this.values = [1, 2]; - } - get query() { - return 'select ? as foo, ? as bar'; - } - } - return this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); - }); - - it('uses properties `query` and `values` if query is tagged', function() { - let logSql; - return this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - expect(logSql).to.not.include('?'); - }); - }); - - it('uses properties `query` and `bind` if query is tagged', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - return this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - } else if (dialect === 'mssql') { - expect(logSql).to.include('@0'); - expect(logSql).to.include('@1'); - } else if (dialect === 'mysql') { - expect(logSql.match(/\?/g).length).to.equal(2); - } - }); - }); - - it('dot separated attributes when doing a raw query without nest', function() { - const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', - sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - - return expect(this.sequelize.query(sql, { raw: true, nest: false }).get(0)).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); - }); - - it('destructs dot separated attributes when doing a raw query using nest', function() { - const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', - sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; - - return this.sequelize.query(sql, { raw: true, nest: true }).then(result => { - expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); - }); - }); - - it('replaces token with the passed array', function() { - return this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - }); - }); - - it('replaces named parameters with the passed object', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) - .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); - }); - - it('replaces named parameters with the passed object and ignore those which does not qualify', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); - }); - - it('replaces named parameters with the passed object using the same key twice', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - }); - - it('replaces named parameters with the passed object having a null property', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).get(0)) - .to.eventually.deep.equal([{ foo: 1, bar: null }]); - }); - - it('reject when key is missing in the passed object', function() { - return this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed number', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed empty object', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed string', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('reject with the passed date', function() { - return this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) - .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); - }); - - it('binds token with the passed array', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - return this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }).then(result => { - expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); - }); - - it('binds named parameters with the passed object', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - } - if (dialect === 'sqlite') { - expect(logSql).to.include('$one'); - } - }); - }); - - it('binds named parameters with the passed object using the same key twice', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); - if (dialect === 'postgres') { - expect(logSql).to.include('$1'); - expect(logSql).to.include('$2'); - expect(logSql).to.not.include('$3'); - } - }); - }); - - it('binds named parameters with the passed object having a null property', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); - }); - }); - - it('binds named parameters array handles escaped $$', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - let logSql; - return this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); - if (dialect === 'postgres' || dialect === 'sqlite') { - expect(logSql).to.include('$1'); - } - }); - }); - - it('binds named parameters object handles escaped $$', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); - }); - }); - - if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { - it('does not improperly escape arrays of strings bound to named parameters', function() { - return this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }).then(result => { - expect(result[0]).to.deep.equal([{ foo: '"string"' }]); - }); - }); - } - - it('reject when binds passed with object and numeric $1 is also present', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when binds passed as array and $alpha is also present', function() { - const typeCast = dialect === 'postgres' ? '::int' : ''; - return this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is $0 with the passed array', function() { - return this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is $01 with the passed array', function() { - return this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is missing in the passed array', function() { - return this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject when bind key is missing in the passed object', function() { - return this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed number for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed empty object for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed string for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('reject with the passed date for bind', function() { - return this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) - .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); - }); - - it('handles AS in conjunction with functions just fine', function() { - let datetime = dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'; - if (dialect === 'mssql') { - datetime = 'GETDATE()'; - } - - return this.sequelize.query(`SELECT ${datetime} AS t`).then(([result]) => { - expect(moment(result[0].t).isValid()).to.be.true; - }); - }); - - if (Support.getTestDialect() === 'postgres') { - it('replaces named parameters with the passed object and ignores casts', function() { - return expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).get(0)) - .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); - }); - - it('supports WITH queries', function() { - return expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').get(0)) - .to.eventually.deep.equal([{ 'sum': '5050' }]); - }); - } - - if (Support.getTestDialect() === 'sqlite') { - it('binds array parameters for upsert are replaced. $$ unescapes only once', function() { - let logSql; - return this.sequelize.query('select $1 as foo, $2 as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: [1, 2], logging(s) { logSql = s; } }).then(() => { - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); - }); - - it('binds named parameters for upsert are replaced. $$ unescapes only once', function() { - let logSql; - return this.sequelize.query('select $one as foo, $two as bar, \'$$$$\' as baz', { type: this.sequelize.QueryTypes.UPSERT, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }).then(() => { - // sqlite.exec does not return a result - expect(logSql).to.not.include('$one'); - expect(logSql).to.include('\'$$\''); - }); - }); - } - - }); - describe('set', () => { it('should be configurable with global functions', function() { const defaultSetterMethod = sinon.spy(), @@ -833,7 +248,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { 'override': overrideGetterMethod } }; - const TestEntity = this.sequelize.define('TestEntity', {}, { + const testEntity = this.sequelize.define('TestEntity', {}, { 'setterMethods': { 'custom': customSetterMethod, 'override': customOverrideSetterMethod @@ -845,7 +260,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); // Create Instance to test - const instance = new TestEntity(); + const instance = testEntity.build(); // Call Getters instance.default; @@ -871,40 +286,35 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (dialect === 'mysql') { describe('set', () => { - it("should return an promised error if transaction isn't defined", function() { - expect(() => { - this.sequelize.set({ foo: 'bar' }); - }).to.throw(TypeError, 'options.transaction is required'); + it("should return an promised error if transaction isn't defined", async function() { + await expect(this.sequelize.set({ foo: 'bar' })) + .to.be.rejectedWith(TypeError, 'options.transaction is required'); }); - it('one value', function() { - return this.sequelize.transaction().then(t => { - this.t = t; - return this.sequelize.set({ foo: 'bar' }, { transaction: t }); - }).then(() => { - return this.sequelize.query('SELECT @foo as `foo`', { plain: true, transaction: this.t }); - }).then(data => { - expect(data).to.be.ok; - expect(data.foo).to.be.equal('bar'); - return this.t.commit(); - }); + it('one value', async function() { + const t = await this.sequelize.transaction(); + this.t = t; + await this.sequelize.set({ foo: 'bar' }, { transaction: t }); + const data = await this.sequelize.query('SELECT @foo as `foo`', { plain: true, transaction: this.t }); + expect(data).to.be.ok; + expect(data.foo).to.be.equal('bar'); + await this.t.commit(); }); - it('multiple values', function() { - return this.sequelize.transaction().then(t => { - this.t = t; - return this.sequelize.set({ - foo: 'bar', - foos: 'bars' - }, { transaction: t }); - }).then(() => { - return this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', { plain: true, transaction: this.t }); - }).then(data => { - expect(data).to.be.ok; - expect(data.foo).to.be.equal('bar'); - expect(data.foos).to.be.equal('bars'); - return this.t.commit(); - }); + it('multiple values', async function() { + const t = await this.sequelize.transaction(); + this.t = t; + + await this.sequelize.set({ + foo: 'bar', + foos: 'bars' + }, { transaction: t }); + + const data = await this.sequelize.query('SELECT @foo as `foo`, @foos as `foos`', { plain: true, transaction: this.t }); + expect(data).to.be.ok; + expect(data.foo).to.be.equal('bar'); + expect(data.foos).to.be.equal('bars'); + await this.t.commit(); }); }); } @@ -946,22 +356,20 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { expect(DAO.options.rowFormat).to.equal('default'); }); - it('uses the passed tableName', function() { + it('uses the passed tableName', async function() { const Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: 'photos' }); - return Photo.sync({ force: true }).then(() => { - return this.sequelize.getQueryInterface().showAllTables().then(tableNames => { - if (dialect === 'mssql' || dialect === 'mariadb') { - tableNames = tableNames.map(v => v.tableName); - } - expect(tableNames).to.include('photos'); - }); - }); + await Photo.sync({ force: true }); + let tableNames = await this.sequelize.getQueryInterface().showAllTables(); + if (dialect === 'mssql' || dialect === 'mariadb') { + tableNames = tableNames.map(v => v.tableName); + } + expect(tableNames).to.include('photos'); }); }); describe('truncate', () => { - it('truncates all models', function() { - const Project = this.sequelize.define(`project${config.rand()}`, { + it('truncates all models', async function() { + const Project = this.sequelize.define(`project${Support.rand()}`, { id: { type: DataTypes.INTEGER, primaryKey: true, @@ -970,47 +378,38 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { title: DataTypes.STRING }); - return this.sequelize.sync({ force: true }).then(() => { - return Project.create({ title: 'bla' }); - }).then(project => { - expect(project).to.exist; - expect(project.title).to.equal('bla'); - expect(project.id).to.equal(1); - return this.sequelize.truncate().then(() => { - return Project.findAll({}); - }); - }).then(projects => { - expect(projects).to.exist; - expect(projects).to.have.length(0); - }); + await this.sequelize.sync({ force: true }); + const project = await Project.create({ title: 'bla' }); + expect(project).to.exist; + expect(project.title).to.equal('bla'); + expect(project.id).to.equal(1); + await this.sequelize.truncate(); + const projects = await Project.findAll({}); + expect(projects).to.exist; + expect(projects).to.have.length(0); }); }); describe('sync', () => { - it('synchronizes all models', function() { - const Project = this.sequelize.define(`project${config.rand()}`, { title: DataTypes.STRING }); - const Task = this.sequelize.define(`task${config.rand()}`, { title: DataTypes.STRING }); - - return Project.sync({ force: true }).then(() => { - return Task.sync({ force: true }).then(() => { - return Project.create({ title: 'bla' }).then(() => { - return Task.create({ title: 'bla' }).then(task => { - expect(task).to.exist; - expect(task.title).to.equal('bla'); - }); - }); - }); - }); + it('synchronizes all models', async function() { + const Project = this.sequelize.define(`project${Support.rand()}`, { title: DataTypes.STRING }); + const Task = this.sequelize.define(`task${Support.rand()}`, { title: DataTypes.STRING }); + + await Project.sync({ force: true }); + await Task.sync({ force: true }); + await Project.create({ title: 'bla' }); + const task = await Task.create({ title: 'bla' }); + expect(task).to.exist; + expect(task.title).to.equal('bla'); }); - it('works with correct database credentials', function() { + it('works with correct database credentials', async function() { const User = this.sequelize.define('User', { username: DataTypes.STRING }); - return User.sync().then(() => { - expect(true).to.be.true; - }); + await User.sync(); + expect(true).to.be.true; }); - it('fails with incorrect match condition', function() { + it('fails with incorrect match condition', async function() { const sequelize = new Sequelize('cyber_bird', 'user', 'pass', { dialect: this.sequelize.options.dialect }); @@ -1018,43 +417,44 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true, match: /$phoenix/ })) + await expect(sequelize.sync({ force: true, match: /$phoenix/ })) .to.be.rejectedWith('Database "cyber_bird" does not match sync match parameter "/$phoenix/"'); }); if (dialect !== 'sqlite') { - it('fails for incorrect connection even when no models are defined', function() { + it('fails for incorrect connection even when no models are defined', async function() { const sequelize = new Sequelize('cyber_bird', 'user', 'pass', { dialect: this.sequelize.options.dialect }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('fails with incorrect database credentials (1)', function() { + it('fails with incorrect database credentials (1)', async function() { this.sequelizeWithInvalidCredentials = new Sequelize('omg', 'bar', null, _.omit(this.sequelize.options, ['host'])); const User2 = this.sequelizeWithInvalidCredentials.define('User', { name: DataTypes.STRING, bio: DataTypes.TEXT }); - return User2.sync() - .then(() => { expect.fail(); }) - .catch(err => { - if (dialect === 'postgres' || dialect === 'postgres-native') { - assert([ - 'fe_sendauth: no password supplied', - 'role "bar" does not exist', - 'FATAL: role "bar" does not exist', - 'password authentication failed for user "bar"' - ].includes(err.message.trim())); - } else if (dialect === 'mssql') { - expect(err.message).to.equal('Login failed for user \'bar\'.'); - } else { - expect(err.message.toString()).to.match(/.*Access denied.*/); - } - }); + try { + await User2.sync(); + expect.fail(); + } catch (err) { + if (dialect === 'postgres' || dialect === 'postgres-native') { + assert([ + 'fe_sendauth: no password supplied', + 'role "bar" does not exist', + 'FATAL: role "bar" does not exist', + 'password authentication failed for user "bar"' + ].some(fragment => err.message.includes(fragment))); + } else if (dialect === 'mssql') { + expect(err.message).to.equal('Login failed for user \'bar\'.'); + } else { + expect(err.message.toString()).to.match(/.*Access denied.*/); + } + } }); - it('fails with incorrect database credentials (2)', function() { + it('fails with incorrect database credentials (2)', async function() { const sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect }); @@ -1062,10 +462,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('fails with incorrect database credentials (3)', function() { + it('fails with incorrect database credentials (3)', async function() { const sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect, port: 99999 @@ -1074,10 +474,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('fails with incorrect database credentials (4)', function() { + it('fails with incorrect database credentials (4)', async function() { const sequelize = new Sequelize('db', 'user', 'pass', { dialect: this.sequelize.options.dialect, port: 99999, @@ -1087,10 +487,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { sequelize.define('Project', { title: Sequelize.STRING }); sequelize.define('Task', { title: Sequelize.STRING }); - return expect(sequelize.sync({ force: true })).to.be.rejected; + await expect(sequelize.sync({ force: true })).to.be.rejected; }); - it('returns an error correctly if unable to sync a foreign key referenced model', function() { + it('returns an error correctly if unable to sync a foreign key referenced model', async function() { this.sequelize.define('Application', { authorID: { type: Sequelize.BIGINT, @@ -1102,10 +502,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { } }); - return expect(this.sequelize.sync()).to.be.rejected; + await expect(this.sequelize.sync()).to.be.rejected; }); - it('handles this dependant foreign key constraints', function() { + it('handles this dependant foreign key constraints', async function() { const block = this.sequelize.define('block', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING @@ -1130,17 +530,16 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { foreignKeyConstraint: true }); - return this.sequelize.sync(); + await this.sequelize.sync(); }); } - it('return the sequelize instance after syncing', function() { - return this.sequelize.sync().then(sequelize => { - expect(sequelize).to.deep.equal(this.sequelize); - }); + it('return the sequelize instance after syncing', async function() { + const sequelize = await this.sequelize.sync(); + expect(sequelize).to.deep.equal(this.sequelize); }); - it('return the single dao after syncing', function() { + it('return the single dao after syncing', async function() { const block = this.sequelize.define('block', { id: { type: DataTypes.INTEGER, primaryKey: true }, name: DataTypes.STRING @@ -1150,12 +549,11 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { paranoid: false }); - return block.sync().then(result => { - expect(result).to.deep.equal(block); - }); + const result = await block.sync(); + expect(result).to.deep.equal(block); }); - it('handles alter: true with underscore correctly', function() { + it('handles alter: true with underscore correctly', async function() { this.sequelize.define('access_metric', { user_id: { type: DataTypes.INTEGER @@ -1164,7 +562,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { underscored: true }); - return this.sequelize.sync({ + await this.sequelize.sync({ alter: true }); }); @@ -1180,18 +578,16 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { this.User = this.sequelize.define('UserTest', { username: DataTypes.STRING }); }); - it('through Sequelize.sync()', function() { + it('through Sequelize.sync()', async function() { this.spy.resetHistory(); - return this.sequelize.sync({ force: true, logging: false }).then(() => { - expect(this.spy.notCalled).to.be.true; - }); + await this.sequelize.sync({ force: true, logging: false }); + expect(this.spy.notCalled).to.be.true; }); - it('through DAOFactory.sync()', function() { + it('through DAOFactory.sync()', async function() { this.spy.resetHistory(); - return this.User.sync({ force: true, logging: false }).then(() => { - expect(this.spy.notCalled).to.be.true; - }); + await this.User.sync({ force: true, logging: false }); + expect(this.spy.notCalled).to.be.true; }); }); @@ -1208,33 +604,10 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }); describe('drop should work', () => { - it('correctly succeeds', function() { + it('correctly succeeds', async function() { const User = this.sequelize.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return User.drop(); - }); - }); - }); - - describe('import', () => { - it('imports a dao definition from a file absolute path', function() { - const Project = this.sequelize.import('assets/project'); - expect(Project).to.exist; - }); - - it('imports a dao definition with a default export', function() { - const Project = this.sequelize.import('assets/es6project'); - expect(Project).to.exist; - }); - - it('imports a dao definition from a function', function() { - const Project = this.sequelize.import('Project', (sequelize, DataTypes) => { - return sequelize.define(`Project${parseInt(Math.random() * 9999999999999999, 10)}`, { - name: DataTypes.STRING - }); - }); - - expect(Project).to.exist; + await User.sync({ force: true }); + await User.drop(); }); }); @@ -1254,13 +627,13 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { DataTypes.ENUM('scheduled', 'active', 'finished') ].forEach(status => { describe('enum', () => { - beforeEach(function() { + beforeEach(async function() { this.sequelize = Support.createSequelizeInstance({ typeValidation: true }); this.Review = this.sequelize.define('review', { status }); - return this.Review.sync({ force: true }); + await this.Review.sync({ force: true }); }); it('raises an error if no values are defined', function() { @@ -1271,25 +644,24 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { }).to.throw(Error, 'Values for ENUM have not been defined.'); }); - it('correctly stores values', function() { - return this.Review.create({ status: 'active' }).then(review => { - expect(review.status).to.equal('active'); - }); + it('correctly stores values', async function() { + const review = await this.Review.create({ status: 'active' }); + expect(review.status).to.equal('active'); }); - it('correctly loads values', function() { - return this.Review.create({ status: 'active' }).then(() => { - return this.Review.findAll().then(reviews => { - expect(reviews[0].status).to.equal('active'); - }); - }); + it('correctly loads values', async function() { + await this.Review.create({ status: 'active' }); + const reviews = await this.Review.findAll(); + expect(reviews[0].status).to.equal('active'); }); - it("doesn't save an instance if value is not in the range of enums", function() { - return this.Review.create({ status: 'fnord' }).catch(err => { + it("doesn't save an instance if value is not in the range of enums", async function() { + try { + await this.Review.create({ status: 'fnord' }); + } catch (err) { expect(err).to.be.instanceOf(Error); expect(err.message).to.equal('"fnord" is not a valid choice in ["scheduled","active","finished"]'); - }); + } }); }); }); @@ -1301,18 +673,17 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { { id: { type: DataTypes.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } } ].forEach(customAttributes => { - it('should be able to override options on the default attributes', function() { + it('should be able to override options on the default attributes', async function() { const Picture = this.sequelize.define('picture', _.cloneDeep(customAttributes)); - return Picture.sync({ force: true }).then(() => { - Object.keys(customAttributes).forEach(attribute => { - Object.keys(customAttributes[attribute]).forEach(option => { - const optionValue = customAttributes[attribute][option]; - if (typeof optionValue === 'function' && optionValue() instanceof DataTypes.ABSTRACT) { - expect(Picture.rawAttributes[attribute][option] instanceof optionValue).to.be.ok; - } else { - expect(Picture.rawAttributes[attribute][option]).to.be.equal(optionValue); - } - }); + await Picture.sync({ force: true }); + Object.keys(customAttributes).forEach(attribute => { + Object.keys(customAttributes[attribute]).forEach(option => { + const optionValue = customAttributes[attribute][option]; + if (typeof optionValue === 'function' && optionValue() instanceof DataTypes.ABSTRACT) { + expect(Picture.rawAttributes[attribute][option] instanceof optionValue).to.be.ok; + } else { + expect(Picture.rawAttributes[attribute][option]).to.be.equal(optionValue); + } }); }); }); @@ -1322,236 +693,181 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { if (current.dialect.supports.transactions) { describe('transaction', () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelizeWithTransaction = sequelize; - }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelizeWithTransaction = sequelize; }); it('is a transaction method available', () => { expect(Support.Sequelize).to.respondTo('transaction'); }); - it('passes a transaction object to the callback', function() { - return this.sequelizeWithTransaction.transaction().then(t => { - expect(t).to.be.instanceOf(Transaction); - }); + it('passes a transaction object to the callback', async function() { + const t = await this.sequelizeWithTransaction.transaction(); + expect(t).to.be.instanceOf(Transaction); }); - it('allows me to define a callback on the result', function() { - return this.sequelizeWithTransaction.transaction().then(t => { - return t.commit(); - }); + it('allows me to define a callback on the result', async function() { + const t = await this.sequelizeWithTransaction.transaction(); + await t.commit(); }); if (dialect === 'sqlite') { - it('correctly scopes transaction from other connections', function() { + it('correctly scopes transaction from other connections', async function() { const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); - const count = transaction => { - const sql = this.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); + const count = async transaction => { + const sql = this.sequelizeWithTransaction.getQueryInterface().queryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); + + const result = await this.sequelizeWithTransaction.query(sql, { plain: true, transaction }); - return this.sequelizeWithTransaction.query(sql, { plain: true, transaction }).then(result => { - return result.cnt; - }); + return result.cnt; }; - return TransactionTest.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction(); - }).then(t1 => { - this.t1 = t1; - return this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); - }).then(() => { - return expect(count()).to.eventually.equal(0); - }).then(() => { - return expect(count(this.t1)).to.eventually.equal(1); - }).then(() => { - return this.t1.commit(); - }).then(() => { - return expect(count()).to.eventually.equal(1); - }); + await TransactionTest.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + this.t1 = t1; + await this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); + await expect(count()).to.eventually.equal(0); + await expect(count(this.t1)).to.eventually.equal(1); + await this.t1.commit(); + + await expect(count()).to.eventually.equal(1); }); } else { - it('correctly handles multiple transactions', function() { + it('correctly handles multiple transactions', async function() { const TransactionTest = this.sequelizeWithTransaction.define('TransactionTest', { name: DataTypes.STRING }, { timestamps: false }); const aliasesMapping = new Map([['_0', 'cnt']]); - const count = transaction => { - const sql = this.sequelizeWithTransaction.getQueryInterface().QueryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); + const count = async transaction => { + const sql = this.sequelizeWithTransaction.getQueryInterface().queryGenerator.selectQuery('TransactionTests', { attributes: [['count(*)', 'cnt']] }); - return this.sequelizeWithTransaction.query(sql, { plain: true, transaction, aliasesMapping }).then(result => { - return parseInt(result.cnt, 10); - }); + const result = await this.sequelizeWithTransaction.query(sql, { plain: true, transaction, aliasesMapping }); + + return parseInt(result.cnt, 10); }; - return TransactionTest.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction(); - }).then(t1 => { - this.t1 = t1; - return this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); - }).then(() => { - return this.sequelizeWithTransaction.transaction(); - }).then(t2 => { - this.t2 = t2; - return this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('bar');`, { transaction: t2 }); - }).then(() => { - return expect(count()).to.eventually.equal(0); - }).then(() => { - return expect(count(this.t1)).to.eventually.equal(1); - }).then(() => { - return expect(count(this.t2)).to.eventually.equal(1); - }).then(() => { - - return this.t2.rollback(); - }).then(() => { - return expect(count()).to.eventually.equal(0); - }).then(() => { - return this.t1.commit(); - }).then(() => { - return expect(count()).to.eventually.equal(1); - }); + await TransactionTest.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + this.t1 = t1; + await this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('foo');`, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction(); + this.t2 = t2; + await this.sequelizeWithTransaction.query(`INSERT INTO ${qq('TransactionTests')} (${qq('name')}) VALUES ('bar');`, { transaction: t2 }); + await expect(count()).to.eventually.equal(0); + await expect(count(this.t1)).to.eventually.equal(1); + await expect(count(this.t2)).to.eventually.equal(1); + await this.t2.rollback(); + await expect(count()).to.eventually.equal(0); + await this.t1.commit(); + + await expect(count()).to.eventually.equal(1); }); } - it('supports nested transactions using savepoints', function() { + it('supports nested transactions using savepoints', async function() { const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction().then(t1 => { - return User.create({ username: 'foo' }, { transaction: t1 }).then(user => { - return this.sequelizeWithTransaction.transaction({ transaction: t1 }).then(t2 => { - return user.update({ username: 'bar' }, { transaction: t2 }).then(() => { - return t2.commit().then(() => { - return user.reload({ transaction: t1 }).then(newUser => { - expect(newUser.username).to.equal('bar'); - return t1.commit(); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + const user = await User.create({ username: 'foo' }, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction({ transaction: t1 }); + await user.update({ username: 'bar' }, { transaction: t2 }); + await t2.commit(); + const newUser = await user.reload({ transaction: t1 }); + expect(newUser.username).to.equal('bar'); + + await t1.commit(); }); describe('supports rolling back to savepoints', () => { - beforeEach(function() { + beforeEach(async function() { this.User = this.sequelizeWithTransaction.define('user', {}); - return this.sequelizeWithTransaction.sync({ force: true }); + await this.sequelizeWithTransaction.sync({ force: true }); }); - it('rolls back to the first savepoint, undoing everything', function() { - return this.sequelizeWithTransaction.transaction().then(transaction => { - this.transaction = transaction; - - return this.sequelizeWithTransaction.transaction({ transaction }); - }).then(sp1 => { - this.sp1 = sp1; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); - }).then(sp2 => { - this.sp2 = sp2; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(2); - - return this.sp1.rollback(); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(0); - - return this.transaction.rollback(); - }); + it('rolls back to the first savepoint, undoing everything', async function() { + const transaction = await this.sequelizeWithTransaction.transaction(); + this.transaction = transaction; + + const sp1 = await this.sequelizeWithTransaction.transaction({ transaction }); + this.sp1 = sp1; + await this.User.create({}, { transaction: this.transaction }); + const sp2 = await this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); + this.sp2 = sp2; + await this.User.create({}, { transaction: this.transaction }); + const users0 = await this.User.findAll({ transaction: this.transaction }); + expect(users0).to.have.length(2); + + await this.sp1.rollback(); + const users = await this.User.findAll({ transaction: this.transaction }); + expect(users).to.have.length(0); + + await this.transaction.rollback(); }); - it('rolls back to the most recent savepoint, only undoing recent changes', function() { - return this.sequelizeWithTransaction.transaction().then(transaction => { - this.transaction = transaction; - - return this.sequelizeWithTransaction.transaction({ transaction }); - }).then(sp1 => { - this.sp1 = sp1; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); - }).then(sp2 => { - this.sp2 = sp2; - return this.User.create({}, { transaction: this.transaction }); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(2); - - return this.sp2.rollback(); - }).then(() => { - return this.User.findAll({ transaction: this.transaction }); - }).then(users => { - expect(users).to.have.length(1); - - return this.transaction.rollback(); - }); + it('rolls back to the most recent savepoint, only undoing recent changes', async function() { + const transaction = await this.sequelizeWithTransaction.transaction(); + this.transaction = transaction; + + const sp1 = await this.sequelizeWithTransaction.transaction({ transaction }); + this.sp1 = sp1; + await this.User.create({}, { transaction: this.transaction }); + const sp2 = await this.sequelizeWithTransaction.transaction({ transaction: this.transaction }); + this.sp2 = sp2; + await this.User.create({}, { transaction: this.transaction }); + const users0 = await this.User.findAll({ transaction: this.transaction }); + expect(users0).to.have.length(2); + + await this.sp2.rollback(); + const users = await this.User.findAll({ transaction: this.transaction }); + expect(users).to.have.length(1); + + await this.transaction.rollback(); }); }); - it('supports rolling back a nested transaction', function() { + it('supports rolling back a nested transaction', async function() { const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction().then(t1 => { - return User.create({ username: 'foo' }, { transaction: t1 }).then(user => { - return this.sequelizeWithTransaction.transaction({ transaction: t1 }).then(t2 => { - return user.update({ username: 'bar' }, { transaction: t2 }).then(() => { - return t2.rollback().then(() => { - return user.reload({ transaction: t1 }).then(newUser => { - expect(newUser.username).to.equal('foo'); - return t1.commit(); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + const user = await User.create({ username: 'foo' }, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction({ transaction: t1 }); + await user.update({ username: 'bar' }, { transaction: t2 }); + await t2.rollback(); + const newUser = await user.reload({ transaction: t1 }); + expect(newUser.username).to.equal('foo'); + + await t1.commit(); }); - it('supports rolling back outermost transaction', function() { + it('supports rolling back outermost transaction', async function() { const User = this.sequelizeWithTransaction.define('Users', { username: DataTypes.STRING }); - return User.sync({ force: true }).then(() => { - return this.sequelizeWithTransaction.transaction().then(t1 => { - return User.create({ username: 'foo' }, { transaction: t1 }).then(user => { - return this.sequelizeWithTransaction.transaction({ transaction: t1 }).then(t2 => { - return user.update({ username: 'bar' }, { transaction: t2 }).then(() => { - return t1.rollback().then(() => { - return User.findAll().then(users => { - expect(users.length).to.equal(0); - }); - }); - }); - }); - }); - }); - }); + await User.sync({ force: true }); + const t1 = await this.sequelizeWithTransaction.transaction(); + const user = await User.create({ username: 'foo' }, { transaction: t1 }); + const t2 = await this.sequelizeWithTransaction.transaction({ transaction: t1 }); + await user.update({ username: 'bar' }, { transaction: t2 }); + await t1.rollback(); + const users = await User.findAll(); + expect(users.length).to.equal(0); }); }); } }); describe('databaseVersion', () => { - it('should database/dialect version', function() { - return this.sequelize.databaseVersion().then(version => { - expect(typeof version).to.equal('string'); - expect(version).to.be.ok; - }); + it('should database/dialect version', async function() { + const version = await this.sequelize.databaseVersion(); + expect(typeof version).to.equal('string'); + expect(version).to.be.ok; }); }); describe('paranoid deletedAt non-null default value', () => { - it('should use defaultValue of deletedAt in paranoid clause and restore', function() { + it('should use defaultValue of deletedAt in paranoid clause and restore', async function() { const epochObj = new Date(0), epoch = Number(epochObj); const User = this.sequelize.define('user', { @@ -1564,44 +880,38 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => { paranoid: true }); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'user1' }).then(user => { - expect(Number(user.deletedAt)).to.equal(epoch); - return User.findOne({ - where: { - username: 'user1' - } - }).then(user => { - expect(user).to.exist; - expect(Number(user.deletedAt)).to.equal(epoch); - return user.destroy(); - }).then(destroyedUser => { - expect(destroyedUser.deletedAt).to.exist; - expect(Number(destroyedUser.deletedAt)).not.to.equal(epoch); - return User.findByPk(destroyedUser.id, { paranoid: false }); - }).then(fetchedDestroyedUser => { - expect(fetchedDestroyedUser.deletedAt).to.exist; - expect(Number(fetchedDestroyedUser.deletedAt)).not.to.equal(epoch); - return fetchedDestroyedUser.restore(); - }).then(restoredUser => { - expect(Number(restoredUser.deletedAt)).to.equal(epoch); - return User.destroy({ where: { - username: 'user1' - } }); - }).then(() => { - return User.count(); - }).then(count => { - expect(count).to.equal(0); - return User.restore(); - }).then(() => { - return User.findAll(); - }).then(nonDeletedUsers => { - expect(nonDeletedUsers.length).to.equal(1); - nonDeletedUsers.forEach(u => { - expect(Number(u.deletedAt)).to.equal(epoch); - }); - }); - }); + await this.sequelize.sync({ force: true }); + const user = await User.create({ username: 'user1' }); + expect(Number(user.deletedAt)).to.equal(epoch); + + const user0 = await User.findOne({ + where: { + username: 'user1' + } + }); + + expect(user0).to.exist; + expect(Number(user0.deletedAt)).to.equal(epoch); + const destroyedUser = await user0.destroy(); + expect(destroyedUser.deletedAt).to.exist; + expect(Number(destroyedUser.deletedAt)).not.to.equal(epoch); + const fetchedDestroyedUser = await User.findByPk(destroyedUser.id, { paranoid: false }); + expect(fetchedDestroyedUser.deletedAt).to.exist; + expect(Number(fetchedDestroyedUser.deletedAt)).not.to.equal(epoch); + const restoredUser = await fetchedDestroyedUser.restore(); + expect(Number(restoredUser.deletedAt)).to.equal(epoch); + + await User.destroy({ where: { + username: 'user1' + } }); + + const count = await User.count(); + expect(count).to.equal(0); + await User.restore(); + const nonDeletedUsers = await User.findAll(); + expect(nonDeletedUsers.length).to.equal(1); + nonDeletedUsers.forEach(u => { + expect(Number(u.deletedAt)).to.equal(epoch); }); }); }); diff --git a/test/integration/sequelize.transaction.test.js b/test/integration/sequelize.transaction.test.js index b6f09ef5663f..86feee7f1793 100644 --- a/test/integration/sequelize.transaction.test.js +++ b/test/integration/sequelize.transaction.test.js @@ -3,151 +3,135 @@ const chai = require('chai'), expect = chai.expect, Support = require('./support'), - Promise = require('../../lib/promise'), Transaction = require('../../lib/transaction'), - current = Support.sequelize; + current = Support.sequelize, + delay = require('delay'); if (current.dialect.supports.transactions) { describe(Support.getTestDialectTeaser('Sequelize#transaction'), () => { describe('then', () => { - it('gets triggered once a transaction has been successfully committed', function() { + it('gets triggered once a transaction has been successfully committed', async function() { let called = false; - return this + + const t = await this .sequelize - .transaction().then(t => { - return t.commit().then(() => { - called = 1; - }); - }) - .then(() => { - expect(called).to.be.ok; - }); + .transaction(); + + await t.commit(); + called = 1; + expect(called).to.be.ok; }); - it('gets triggered once a transaction has been successfully rolled back', function() { + it('gets triggered once a transaction has been successfully rolled back', async function() { let called = false; - return this + + const t = await this .sequelize - .transaction().then(t => { - return t.rollback().then(() => { - called = 1; - }); - }) - .then(() => { - expect(called).to.be.ok; - }); + .transaction(); + + await t.rollback(); + called = 1; + expect(called).to.be.ok; }); if (Support.getTestDialect() !== 'sqlite') { - it('works for long running transactions', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.User = sequelize.define('User', { - name: Support.Sequelize.STRING - }, { timestamps: false }); - - return sequelize.sync({ force: true }); - }).then(() => { - return this.sequelize.transaction(); - }).then(t => { - let query = 'select sleep(2);'; - - switch (Support.getTestDialect()) { - case 'postgres': - query = 'select pg_sleep(2);'; - break; - case 'sqlite': - query = 'select sqlite3_sleep(2000);'; - break; - case 'mssql': - query = 'WAITFOR DELAY \'00:00:02\';'; - break; - default: - break; - } - - return this.sequelize.query(query, { transaction: t }).then(() => { - return this.User.create({ name: 'foo' }); - }).then(() => { - return this.sequelize.query(query, { transaction: t }); - }).then(() => { - return t.commit(); - }); - }).then(() => { - return this.User.findAll(); - }).then(users => { - expect(users.length).to.equal(1); - expect(users[0].name).to.equal('foo'); - }); + it('works for long running transactions', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.User = sequelize.define('User', { + name: Support.Sequelize.STRING + }, { timestamps: false }); + + await sequelize.sync({ force: true }); + const t = await this.sequelize.transaction(); + let query = 'select sleep(2);'; + + switch (Support.getTestDialect()) { + case 'postgres': + query = 'select pg_sleep(2);'; + break; + case 'sqlite': + query = 'select sqlite3_sleep(2000);'; + break; + case 'mssql': + query = 'WAITFOR DELAY \'00:00:02\';'; + break; + default: + break; + } + + await this.sequelize.query(query, { transaction: t }); + await this.User.create({ name: 'foo' }); + await this.sequelize.query(query, { transaction: t }); + await t.commit(); + const users = await this.User.findAll(); + expect(users.length).to.equal(1); + expect(users[0].name).to.equal('foo'); }); } }); describe('complex long running example', () => { - it('works with promise syntax', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const Test = sequelize.define('Test', { - id: { type: Support.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, - name: { type: Support.Sequelize.STRING } - }); - - return sequelize.sync({ force: true }).then(() => { - return sequelize.transaction().then(transaction => { - expect(transaction).to.be.instanceOf(Transaction); - - return Test - .create({ name: 'Peter' }, { transaction }) - .then(() => { - return Promise.delay(1000).then(() => { - return transaction - .commit() - .then(() => { return Test.count(); }) - .then(count => { - expect(count).to.equal(1); - }); - }); - }); - }); - }); + it('works with promise syntax', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const Test = sequelize.define('Test', { + id: { type: Support.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, + name: { type: Support.Sequelize.STRING } }); + + await sequelize.sync({ force: true }); + const transaction = await sequelize.transaction(); + expect(transaction).to.be.instanceOf(Transaction); + + await Test + .create({ name: 'Peter' }, { transaction }); + + await delay(1000); + + await transaction + .commit(); + + const count = await Test.count(); + expect(count).to.equal(1); }); }); describe('concurrency', () => { describe('having tables with uniqueness constraints', () => { - beforeEach(function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - this.sequelize = sequelize; - - this.Model = sequelize.define('Model', { - name: { type: Support.Sequelize.STRING, unique: true } - }, { - timestamps: false - }); - - return this.Model.sync({ force: true }); + beforeEach(async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + this.sequelize = sequelize; + + this.Model = sequelize.define('Model', { + name: { type: Support.Sequelize.STRING, unique: true } + }, { + timestamps: false }); + + await this.Model.sync({ force: true }); }); - it('triggers the error event for the second transactions', function() { - return this.sequelize.transaction().then(t1 => { - return this.sequelize.transaction().then(t2 => { - return this.Model.create({ name: 'omnom' }, { transaction: t1 }).then(() => { - return Promise.all([ - this.Model.create({ name: 'omnom' }, { transaction: t2 }).catch(err => { - expect(err).to.be.ok; - return t2.rollback(); - }), - Promise.delay(100).then(() => { - return t1.commit(); - }) - ]); - }); - }); - }); + it('triggers the error event for the second transactions', async function() { + const t1 = await this.sequelize.transaction(); + const t2 = await this.sequelize.transaction(); + await this.Model.create({ name: 'omnom' }, { transaction: t1 }); + + await Promise.all([ + (async () => { + try { + return await this.Model.create({ name: 'omnom' }, { transaction: t2 }); + } catch (err) { + expect(err).to.be.ok; + return t2.rollback(); + } + })(), + delay(100).then(() => { + return t1.commit(); + }) + ]); }); }); }); diff --git a/test/integration/sequelize/deferrable.test.js b/test/integration/sequelize/deferrable.test.js index d24dff864028..332baf4b2779 100644 --- a/test/integration/sequelize/deferrable.test.js +++ b/test/integration/sequelize/deferrable.test.js @@ -3,9 +3,7 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = require('../../../index'), - config = require('../../config/config') - ; + Sequelize = require('../../../index'); if (!Support.sequelize.dialect.supports.deferrableConstraints) { return; @@ -13,97 +11,136 @@ if (!Support.sequelize.dialect.supports.deferrableConstraints) { describe(Support.getTestDialectTeaser('Sequelize'), () => { describe('Deferrable', () => { - beforeEach(function() { - this.run = function(deferrable, options = {}) { - const taskTableName = options.taskTableName || `tasks_${config.rand()}`; - const transactionOptions = Object.assign({}, { deferrable: Sequelize.Deferrable.SET_DEFERRED }, options); - const userTableName = `users_${config.rand()}`; - - const User = this.sequelize.define( - 'User', { name: Sequelize.STRING }, { tableName: userTableName } - ); - - const Task = this.sequelize.define( - 'Task', { - title: Sequelize.STRING, - user_id: { - allowNull: false, - type: Sequelize.INTEGER, - references: { - model: userTableName, - key: 'id', - deferrable - } - } - }, { - tableName: taskTableName - } - ); - - return User.sync({ force: true }).then(() => { - return Task.sync({ force: true }); - }).then(() => { - return this.sequelize.transaction(transactionOptions, t => { - return Task - .create({ title: 'a task', user_id: -1 }, { transaction: t }) - .then(task => { - return Promise.all([task, User.create({}, { transaction: t })]); - }) - .then(([task, user]) => { - task.user_id = user.id; - return task.save({ transaction: t }); - }); + const describeDeferrableTest = (title, defineModels) => { + describe(title, () => { + beforeEach(function() { + this.run = async function(deferrable, options) { + options = options || {}; + + const taskTableName = options.taskTableName || `tasks_${Support.rand()}`; + const transactionOptions = { deferrable: Sequelize.Deferrable.SET_DEFERRED, ...options }; + const userTableName = `users_${Support.rand()}`; + + const { Task, User } = await defineModels({ sequelize: this.sequelize, userTableName, deferrable, taskTableName }); + + return this.sequelize.transaction(transactionOptions, async t => { + const task0 = await Task + .create({ title: 'a task', user_id: -1 }, { transaction: t }); + + const [task, user] = await Promise.all([task0, User.create({}, { transaction: t })]); + task.user_id = user.id; + return task.save({ transaction: t }); + }); + }; + }); + + describe('NOT', () => { + it('does not allow the violation of the foreign key constraint', async function() { + await expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); }); }); - }; - }); - describe('NOT', () => { - it('does not allow the violation of the foreign key constraint', function() { - return expect(this.run(Sequelize.Deferrable.NOT)).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); - }); + describe('INITIALLY_IMMEDIATE', () => { + it('allows the violation of the foreign key constraint if the transaction is deferred', async function() { + const task = await this + .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE); - describe('INITIALLY_IMMEDIATE', () => { - it('allows the violation of the foreign key constraint if the transaction is deferred', function() { - return this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE) - .then(task => { expect(task.title).to.equal('a task'); expect(task.user_id).to.equal(1); }); - }); - it('does not allow the violation of the foreign key constraint if the transaction is not deffered', function() { - return expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { - deferrable: undefined - })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); - }); + it('does not allow the violation of the foreign key constraint if the transaction is not deffered', async function() { + await expect(this.run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { + deferrable: undefined + })).to.eventually.be.rejectedWith(Sequelize.ForeignKeyConstraintError); + }); + + it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', async function() { + const taskTableName = `tasks_${Support.rand()}`; - it('allows the violation of the foreign key constraint if the transaction deferres only the foreign key constraint', function() { - const taskTableName = `tasks_${config.rand()}`; + const task = await this + .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { + deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]), + taskTableName + }); - return this - .run(Sequelize.Deferrable.INITIALLY_IMMEDIATE, { - deferrable: Sequelize.Deferrable.SET_DEFERRED([`${taskTableName}_user_id_fkey`]), - taskTableName - }) - .then(task => { expect(task.title).to.equal('a task'); expect(task.user_id).to.equal(1); }); - }); - }); + }); + + describe('INITIALLY_DEFERRED', () => { + it('allows the violation of the foreign key constraint', async function() { + const task = await this + .run(Sequelize.Deferrable.INITIALLY_DEFERRED); - describe('INITIALLY_DEFERRED', () => { - it('allows the violation of the foreign key constraint', function() { - return this - .run(Sequelize.Deferrable.INITIALLY_DEFERRED) - .then(task => { expect(task.title).to.equal('a task'); expect(task.user_id).to.equal(1); }); + }); + }); + }; + + describeDeferrableTest('set in define', async ({ sequelize, userTableName, deferrable, taskTableName }) => { + const User = sequelize.define( + 'User', { name: Sequelize.STRING }, { tableName: userTableName } + ); + + const Task = sequelize.define( + 'Task', { + title: Sequelize.STRING, + user_id: { + allowNull: false, + type: Sequelize.INTEGER, + references: { + model: userTableName, + key: 'id', + deferrable + } + } + }, { + tableName: taskTableName + } + ); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + return { Task, User }; + }); + + describeDeferrableTest('set in addConstraint', async ({ sequelize, userTableName, deferrable, taskTableName }) => { + const User = sequelize.define( + 'User', { name: Sequelize.STRING }, { tableName: userTableName } + ); + + const Task = sequelize.define( + 'Task', { + title: Sequelize.STRING, + user_id: { + allowNull: false, + type: Sequelize.INTEGER + } + }, { + tableName: taskTableName + } + ); + + await User.sync({ force: true }); + await Task.sync({ force: true }); + + await sequelize.getQueryInterface().addConstraint(taskTableName, { + fields: ['user_id'], + type: 'foreign key', + name: `${taskTableName}_user_id_fkey`, + deferrable, + references: { + table: userTableName, + field: 'id' + } }); + + return { Task, User }; }); }); }); diff --git a/test/integration/sequelize/query.test.js b/test/integration/sequelize/query.test.js new file mode 100644 index 000000000000..37dd1f6e4ce1 --- /dev/null +++ b/test/integration/sequelize/query.test.js @@ -0,0 +1,695 @@ +'use strict'; + +const { expect } = require('chai'); +const Support = require('../support'); +const Sequelize = Support.Sequelize; +const DataTypes = Support.Sequelize.DataTypes; +const dialect = Support.getTestDialect(); +const sinon = require('sinon'); +const moment = require('moment'); +const { DatabaseError, UniqueConstraintError, ForeignKeyConstraintError } = Support.Sequelize; + +const qq = str => { + if (dialect === 'postgres' || dialect === 'mssql') { + return `"${str}"`; + } + if (dialect === 'mysql' || dialect === 'mariadb' || dialect === 'sqlite') { + return `\`${str}\``; + } + return str; +}; + +describe(Support.getTestDialectTeaser('Sequelize'), () => { + describe('query', () => { + afterEach(function() { + this.sequelize.options.quoteIdentifiers = true; + console.log.restore && console.log.restore(); + }); + + beforeEach(async function() { + this.User = this.sequelize.define('User', { + username: { + type: DataTypes.STRING, + unique: true + }, + emailAddress: { + type: DataTypes.STRING, + field: 'email_address' + } + }); + + this.insertQuery = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ + qq('createdAt') }, ${qq('updatedAt') + }) VALUES ('john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; + + await this.User.sync({ force: true }); + }); + + it('executes a query the internal way', async function() { + await this.sequelize.query(this.insertQuery, { raw: true }); + }); + + it('executes a query if only the sql is passed', async function() { + await this.sequelize.query(this.insertQuery); + }); + + it('executes a query if a placeholder value is an array', async function() { + await this.sequelize.query(`INSERT INTO ${qq(this.User.tableName)} (username, email_address, ` + + `${qq('createdAt')}, ${qq('updatedAt')}) VALUES ?;`, { + replacements: [[ + ['john', 'john@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'], + ['michael', 'michael@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10'] + ]] + }); + + const rows = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: this.sequelize.QueryTypes.SELECT + }); + + expect(rows).to.be.lengthOf(2); + expect(rows[0].username).to.be.equal('john'); + expect(rows[1].username).to.be.equal('michael'); + }); + + describe('QueryTypes', () => { + it('RAW', async function() { + await this.sequelize.query(this.insertQuery, { + type: Sequelize.QueryTypes.RAW + }); + + const [rows, count] = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: Sequelize.QueryTypes.RAW + }); + + expect(rows).to.be.an.instanceof(Array); + expect(count).to.be.ok; + }); + }); + + describe('retry', () => { + it('properly bind parameters on extra retries', async function() { + const payload = { + username: 'test', + createdAt: '2010-10-10 00:00:00', + updatedAt: '2010-10-10 00:00:00' + }; + + const spy = sinon.spy(); + + await this.User.create(payload); + + await expect(this.sequelize.query(` + INSERT INTO ${qq(this.User.tableName)} (username,${qq('createdAt')},${qq('updatedAt')}) VALUES ($username,$createdAt,$updatedAt); + `, { + bind: payload, + logging: spy, + retry: { + max: 3, + match: [ + /Validation/ + ] + } + })).to.be.rejectedWith(Sequelize.UniqueConstraintError); + + expect(spy.callCount).to.eql(3); + }); + }); + + describe('logging', () => { + it('executes a query with global benchmarking option and custom logger', async () => { + const logger = sinon.spy(); + const sequelize = Support.createSequelizeInstance({ + logging: logger, + benchmark: true + }); + + await sequelize.query('select 1;'); + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/); + expect(typeof logger.args[0][1] === 'number').to.be.true; + }); + + it('executes a query with benchmarking option and custom logger', async function() { + const logger = sinon.spy(); + + await this.sequelize.query('select 1;', { + logging: logger, + benchmark: true + }); + + expect(logger.calledOnce).to.be.true; + expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/); + expect(typeof logger.args[0][1] === 'number').to.be.true; + }); + + describe('with logQueryParameters', () => { + beforeEach(async function() { + this.sequelize = Support.createSequelizeInstance({ + benchmark: true, + logQueryParameters: true + }); + this.User = this.sequelize.define('User', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + username: { + type: DataTypes.STRING + }, + emailAddress: { + type: DataTypes.STRING + } + }, { + timestamps: false + }); + + await this.User.sync({ force: true }); + }); + + it('add parameters in log sql', async function() { + let createSql, updateSql; + + const user = await this.User.create({ + username: 'john', + emailAddress: 'john@gmail.com' + }, { + logging: s =>{ + createSql = s; + } + }); + + user.username = 'li'; + + await user.save({ + logging: s =>{ + updateSql = s; + } + }); + + expect(createSql).to.match(/; ("john", "john@gmail.com"|{"(\$1|0)":"john","(\$2|1)":"john@gmail.com"})/); + expect(updateSql).to.match(/; ("li", 1|{"(\$1|0)":"li","(\$2|1)":1})/); + }); + + it('add parameters in log sql when use bind value', async function() { + let logSql; + const typeCast = dialect === 'postgres' ? '::text' : ''; + await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { bind: ['foo', 'bar'], logging: s=>logSql = s }); + expect(logSql).to.match(/; ("foo", "bar"|{"(\$1|0)":"foo","(\$2|1)":"bar"})/); + }); + }); + }); + + it('executes select queries correctly', async function() { + await this.sequelize.query(this.insertQuery); + const [users] = await this.sequelize.query(`select * from ${qq(this.User.tableName)}`); + expect(users.map(u => { return u.username; })).to.include('john'); + }); + + it('executes select queries correctly when quoteIdentifiers is false', async function() { + const seq = Object.create(this.sequelize); + + seq.options.quoteIdentifiers = false; + await seq.query(this.insertQuery); + const [users] = await seq.query(`select * from ${qq(this.User.tableName)}`); + expect(users.map(u => { return u.username; })).to.include('john'); + }); + + it('executes select query with dot notation results', async function() { + await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); + await this.sequelize.query(this.insertQuery); + const [users] = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`); + expect(users).to.deep.equal([{ 'user.username': 'john' }]); + }); + + it('executes select query with dot notation results and nest it', async function() { + await this.sequelize.query(`DELETE FROM ${qq(this.User.tableName)}`); + await this.sequelize.query(this.insertQuery); + const users = await this.sequelize.query(`select username as ${qq('user.username')} from ${qq(this.User.tableName)}`, { raw: true, nest: true }); + expect(users.map(u => { return u.user; })).to.deep.equal([{ 'username': 'john' }]); + }); + + if (dialect === 'mysql') { + it('executes stored procedures', async function() { + await this.sequelize.query(this.insertQuery); + await this.sequelize.query('DROP PROCEDURE IF EXISTS foo'); + + await this.sequelize.query( + `CREATE PROCEDURE foo()\nSELECT * FROM ${this.User.tableName};` + ); + + const users = await this.sequelize.query('CALL foo()'); + expect(users.map(u => { return u.username; })).to.include('john'); + }); + } else { + console.log('FIXME: I want to be supported in this dialect as well :-('); + } + + it('uses the passed model', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + model: this.User + }); + + expect(users[0]).to.be.instanceof(this.User); + }); + + it('maps the field names to attributes based on the passed model', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + model: this.User, + mapToModel: true + }); + + expect(users[0].emailAddress).to.be.equal('john@gmail.com'); + }); + + it('arbitrarily map the field names', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: 'SELECT', + fieldMap: { username: 'userName', email_address: 'email' } + }); + + expect(users[0].userName).to.be.equal('john'); + expect(users[0].email).to.be.equal('john@gmail.com'); + }); + + it('keeps field names that are mapped to the same name', async function() { + await this.sequelize.query(this.insertQuery); + + const users = await this.sequelize.query(`SELECT * FROM ${qq(this.User.tableName)};`, { + type: 'SELECT', + fieldMap: { username: 'username', email_address: 'email' } + }); + + expect(users[0].username).to.be.equal('john'); + expect(users[0].email).to.be.equal('john@gmail.com'); + }); + + // Only run stacktrace tests on Node 12+, since only Node 12+ supports + // async stacktraces + const nodeVersionMatch = process.version.match(/^v([0-9]+)/); + let nodeMajorVersion = 0; + if (nodeVersionMatch && nodeVersionMatch[1]) { + nodeMajorVersion = parseInt(nodeVersionMatch[1], 10); + } + + if (nodeMajorVersion >= 12) { + describe('stacktraces', () => { + beforeEach(async function() { + this.UserVisit = this.sequelize.define('UserVisit', { + userId: { + type: DataTypes.STRING, + field: 'user_id' + }, + visitedAt: { + type: DataTypes.DATE, + field: 'visited_at' + } + }, { + indexes: [ + { name: 'user_id', fields: ['user_id'] } + ] + }); + + this.User.hasMany(this.UserVisit, { foreignKey: 'user_id' }); + this.UserVisit.belongsTo(this.User, { foreignKey: 'user_id', targetKey: 'id' }); + + await this.UserVisit.sync({ force: true }); + }); + + it('emits full stacktraces for generic database error', async function() { + let error = null; + try { + await this.sequelize.query(`select * from ${qq(this.User.tableName)} where ${qq('unknown_column')} = 1`); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(DatabaseError); + expect(error.stack).to.contain('query.test'); + }); + + it('emits full stacktraces for unique constraint error', async function() { + const query = `INSERT INTO ${qq(this.User.tableName)} (username, email_address, ${ + qq('createdAt') }, ${qq('updatedAt') + }) VALUES ('duplicate', 'duplicate@gmail.com', '2012-01-01 10:10:10', '2012-01-01 10:10:10')`; + + // Insert 1 row + await this.sequelize.query(query); + + let error = null; + try { + // Try inserting a duplicate row + await this.sequelize.query(query); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(UniqueConstraintError); + expect(error.stack).to.contain('query.test'); + }); + + it('emits full stacktraces for constraint validation error', async function() { + let error = null; + try { + // Try inserting a row that has a really high userId to any existing username + await this.sequelize.query( + `INSERT INTO ${qq(this.UserVisit.tableName)} (user_id, visited_at, ${qq( + 'createdAt' + )}, ${qq( + 'updatedAt' + )}) VALUES (123456789, '2012-01-01 10:10:10', '2012-01-01 10:10:10', '2012-01-01 10:10:10')` + ); + } catch (err) { + error = err; + } + + expect(error).to.be.instanceOf(ForeignKeyConstraintError); + expect(error.stack).to.contain('query.test'); + }); + }); + } + + describe('rejections', () => { + it('reject if `values` and `options.replacements` are both passed', async function() { + await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { raw: true, replacements: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `sql.values` and `options.replacements` cannot be set at the same time'); + }); + + it('reject if `sql.bind` and `options.bind` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `sql.bind` and `options.bind` cannot be set at the same time'); + }); + + it('reject if `options.replacements` and `options.bind` are both passed', async function() { + await this.sequelize.query('select $1 + ? as foo, $2 + ? as bar', { raw: true, bind: [1, 2], replacements: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject if `sql.bind` and `sql.values` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2], values: [1, 2] }, { raw: true }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject if `sql.bind` and `options.replacements`` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $2 + ? as bar', bind: [1, 2] }, { raw: true, replacements: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject if `options.bind` and `sql.replacements` are both passed', async function() { + await this.sequelize.query({ query: 'select $1 + ? as foo, $1 _ ? as bar', values: [1, 2] }, { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, 'Both `replacements` and `bind` cannot be set at the same time'); + }); + + it('reject when key is missing in the passed object', async function() { + await this.sequelize.query('select :one as foo, :two as bar, :three as baz', { raw: true, replacements: { one: 1, two: 2 } }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed number', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 2 }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed empty object', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: {} }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed string', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: 'foobar' }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject with the passed date', async function() { + await this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: new Date() }) + .should.be.rejectedWith(Error, /Named parameter ":\w+" has no value in the given object\./g); + }); + + it('reject when binds passed with object and numeric $1 is also present', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + + await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, '$1' as baz`, { raw: true, bind: { one: 1, two: 2 } }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when binds passed as array and $alpha is also present', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + + await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar, '$foo' as baz`, { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is $0 with the passed array', async function() { + await this.sequelize.query('select $1 as foo, $0 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is $01 with the passed array', async function() { + await this.sequelize.query('select $1 as foo, $01 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is missing in the passed array', async function() { + await this.sequelize.query('select $1 as foo, $2 as bar, $3 as baz', { raw: true, bind: [1, 2] }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject when bind key is missing in the passed object', async function() { + await this.sequelize.query('select $one as foo, $two as bar, $three as baz', { raw: true, bind: { one: 1, two: 2 } }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed number for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 2 }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed empty object for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: {} }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed string for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: 'foobar' }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + + it('reject with the passed date for bind', async function() { + await this.sequelize.query('select $one as foo, $two as bar', { raw: true, bind: new Date() }) + .should.be.rejectedWith(Error, /Named bind parameter "\$\w+" has no value in the given object\./g); + }); + }); + + it('properly adds and escapes replacement value', async function() { + let logSql; + const number = 1, + date = new Date(), + string = 't\'e"st', + boolean = true, + buffer = Buffer.from('t\'e"st'); + + date.setMilliseconds(0); + + const result = await this.sequelize.query({ + query: 'select ? as number, ? as date,? as string,? as boolean,? as buffer', + values: [number, date, string, boolean, buffer] + }, { + type: this.sequelize.QueryTypes.SELECT, + logging(s) { + logSql = s; + } + }); + + const res = result[0] || {}; + res.date = res.date && new Date(res.date); + res.boolean = res.boolean && true; + if (typeof res.buffer === 'string' && res.buffer.startsWith('\\x')) { + res.buffer = Buffer.from(res.buffer.substring(2), 'hex'); + } + expect(res).to.deep.equal({ + number, + date, + string, + boolean, + buffer + }); + expect(logSql).to.not.include('?'); + }); + + it('it allows to pass custom class instances', async function() { + let logSql; + class SQLStatement { + constructor() { + this.values = [1, 2]; + } + get query() { + return 'select ? as foo, ? as bar'; + } + } + const result = await this.sequelize.query(new SQLStatement(), { type: this.sequelize.QueryTypes.SELECT, logging: s => logSql = s } ); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + expect(logSql).to.not.include('?'); + }); + + it('uses properties `query` and `values` if query is tagged', async function() { + let logSql; + const result = await this.sequelize.query({ query: 'select ? as foo, ? as bar', values: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + expect(logSql).to.not.include('?'); + }); + + it('uses properties `query` and `bind` if query is tagged', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query({ query: `select $1${typeCast} as foo, $2${typeCast} as bar`, bind: [1, 2] }, { type: this.sequelize.QueryTypes.SELECT, logging(s) { logSql = s; } }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + expect(logSql).to.include('$2'); + } else if (dialect === 'mssql') { + expect(logSql).to.include('@0'); + expect(logSql).to.include('@1'); + } else if (dialect === 'mysql') { + expect(logSql.match(/\?/g).length).to.equal(2); + } + }); + + it('dot separated attributes when doing a raw query without nest', async function() { + const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', + sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; + + await expect(this.sequelize.query(sql, { raw: true, nest: false }).then(obj => obj[0])).to.eventually.deep.equal([{ 'foo.bar.baz': 1 }]); + }); + + it('destructs dot separated attributes when doing a raw query using nest', async function() { + const tickChar = dialect === 'postgres' || dialect === 'mssql' ? '"' : '`', + sql = `select 1 as ${Sequelize.Utils.addTicks('foo.bar.baz', tickChar)}`; + + const result = await this.sequelize.query(sql, { raw: true, nest: true }); + expect(result).to.deep.equal([{ foo: { bar: { baz: 1 } } }]); + }); + + it('replaces token with the passed array', async function() { + const result = await this.sequelize.query('select ? as foo, ? as bar', { type: this.sequelize.QueryTypes.SELECT, replacements: [1, 2] }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + }); + + it('replaces named parameters with the passed object', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2 }]); + }); + + it('replaces named parameters with the passed object and ignore those which does not qualify', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, \'00:00\' as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: '00:00' }]); + }); + + it('replaces named parameters with the passed object using the same key twice', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, :one as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); + }); + + it('replaces named parameters with the passed object having a null property', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar', { raw: true, replacements: { one: 1, two: null } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: null }]); + }); + + it('binds token with the passed array', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $1${typeCast} as foo, $2${typeCast} as bar`, { type: this.sequelize.QueryTypes.SELECT, bind: [1, 2], logging(s) { logSql = s;} }); + expect(result).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + } + }); + + it('binds named parameters with the passed object', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2 }]); + if (dialect === 'postgres') { + expect(logSql).to.include('$1'); + } + if (dialect === 'sqlite') { + expect(logSql).to.include('$one'); + } + }); + + it('binds named parameters with the passed object using the same key twice', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar, $one${typeCast} as baz`, { raw: true, bind: { one: 1, two: 2 }, logging(s) { logSql = s; } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: 2, baz: 1 }]); + if (dialect === 'postgres') { + expect(logSql).to.include('$1'); + expect(logSql).to.include('$2'); + expect(logSql).to.not.include('$3'); + } + }); + + it('binds named parameters with the passed object having a null property', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + const result = await this.sequelize.query(`select $one${typeCast} as foo, $two${typeCast} as bar`, { raw: true, bind: { one: 1, two: null } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: null }]); + }); + + it('binds named parameters array handles escaped $$', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + let logSql; + const result = await this.sequelize.query(`select $1${typeCast} as foo, '$$ / $$1' as bar`, { raw: true, bind: [1], logging(s) { logSql = s;} }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $1' }]); + if (dialect === 'postgres' || dialect === 'sqlite') { + expect(logSql).to.include('$1'); + } + }); + + it('binds named parameters object handles escaped $$', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + const result = await this.sequelize.query(`select $one${typeCast} as foo, '$$ / $$one' as bar`, { raw: true, bind: { one: 1 } }); + expect(result[0]).to.deep.equal([{ foo: 1, bar: '$ / $one' }]); + }); + + it('escape where has $ on the middle of characters', async function() { + const typeCast = dialect === 'postgres' ? '::int' : ''; + const result = await this.sequelize.query(`select $one${typeCast} as foo$bar`, { raw: true, bind: { one: 1 } }); + expect(result[0]).to.deep.equal([{ foo$bar: 1 }]); + }); + + if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') { + it('does not improperly escape arrays of strings bound to named parameters', async function() { + const result = await this.sequelize.query('select :stringArray as foo', { raw: true, replacements: { stringArray: ['"string"'] } }); + expect(result[0]).to.deep.equal([{ foo: '"string"' }]); + }); + } + + it('handles AS in conjunction with functions just fine', async function() { + let datetime = dialect === 'sqlite' ? 'date(\'now\')' : 'NOW()'; + if (dialect === 'mssql') { + datetime = 'GETDATE()'; + } + + const [result] = await this.sequelize.query(`SELECT ${datetime} AS t`); + expect(moment(result[0].t).isValid()).to.be.true; + }); + + if (Support.getTestDialect() === 'postgres') { + it('replaces named parameters with the passed object and ignores casts', async function() { + await expect(this.sequelize.query('select :one as foo, :two as bar, \'1000\'::integer as baz', { raw: true, replacements: { one: 1, two: 2 } }).then(obj => obj[0])) + .to.eventually.deep.equal([{ foo: 1, bar: 2, baz: 1000 }]); + }); + + it('supports WITH queries', async function() { + await expect(this.sequelize.query('WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100) SELECT sum(n) FROM t').then(obj => obj[0])) + .to.eventually.deep.equal([{ 'sum': '5050' }]); + }); + } + }); +}); diff --git a/test/integration/support.js b/test/integration/support.js index 875f34c46bfa..44d68aa1b044 100644 --- a/test/integration/support.js +++ b/test/integration/support.js @@ -1,27 +1,72 @@ 'use strict'; +// Store local references to `setTimeout` and `clearTimeout` asap, so that we can use them within `p-timeout`, +// avoiding to be affected unintentionally by `sinon.useFakeTimers()` called by the tests themselves. +const { setTimeout, clearTimeout } = global; + +const pTimeout = require('p-timeout'); const Support = require('../support'); -const runningQueries = new Set(); +const CLEANUP_TIMEOUT = Number.parseInt(process.env.SEQ_TEST_CLEANUP_TIMEOUT, 10) || 10000; + +let runningQueries = new Set(); before(function() { - this.sequelize.hooks.add('beforeQuery', (options, query) => { + this.sequelize.addHook('beforeQuery', (options, query) => { runningQueries.add(query); }); - this.sequelize.hooks.add('afterQuery', (options, query) => { + this.sequelize.addHook('afterQuery', (options, query) => { runningQueries.delete(query); }); }); -beforeEach(function() { - return Support.clearDatabase(this.sequelize); +beforeEach(async function() { + await Support.clearDatabase(this.sequelize); }); -afterEach(function() { - if (runningQueries.size === 0) { - return; +afterEach(async function() { + // Note: recall that throwing an error from a `beforeEach` or `afterEach` hook in Mocha causes the entire test suite to abort. + + let runningQueriesProblem; + + if (runningQueries.size > 0) { + runningQueriesProblem = `Expected 0 queries running after this test, but there are still ${ + runningQueries.size + } queries running in the database (or, at least, the \`afterQuery\` Sequelize hook did not fire for them):\n\n${ + // prettier-ignore + [...runningQueries].map(query => ` ${query.uuid}: ${query.sql}`).join('\n') + }`; + } + + runningQueries = new Set(); + + try { + await pTimeout( + Support.clearDatabase(this.sequelize), + CLEANUP_TIMEOUT, + `Could not clear database after this test in less than ${CLEANUP_TIMEOUT}ms. This test crashed the DB, and testing cannot continue. Aborting.`, + { customTimers: { setTimeout, clearTimeout } } + ); + } catch (error) { + let message = error.message; + if (runningQueriesProblem) { + message += `\n\n Also, ${runningQueriesProblem}`; + } + message += `\n\n Full test name:\n ${this.currentTest.fullTitle()}`; + + // Throw, aborting the entire Mocha execution + throw new Error(message); + } + + if (runningQueriesProblem) { + if (this.test.ctx.currentTest.state === 'passed') { + // `this.test.error` is an obscure Mocha API that allows failing a test from the `afterEach` hook + // This is better than throwing because throwing would cause the entire Mocha execution to abort + this.test.error(new Error(`This test passed, but ${runningQueriesProblem}`)); + } else { + console.log(` ${runningQueriesProblem}`); + } } - throw new Error(`Expected 0 running queries. ${runningQueries.size} queries still running in ${this.currentTest.fullTitle()}`); }); module.exports = Support; diff --git a/test/integration/timezone.test.js b/test/integration/timezone.test.js index c7bb83aee87c..8349288e74db 100644 --- a/test/integration/timezone.test.js +++ b/test/integration/timezone.test.js @@ -3,9 +3,7 @@ const chai = require('chai'), expect = chai.expect, Support = require('./support'), - dialect = Support.getTestDialect(), - Sequelize = require('../../index'), - Promise = Sequelize.Promise; + dialect = Support.getTestDialect(); if (dialect !== 'sqlite') { // Sqlite does not support setting timezone @@ -20,7 +18,7 @@ if (dialect !== 'sqlite') { }); }); - it('returns the same value for current timestamp', function() { + it('returns the same value for current timestamp', async function() { let now = 'now()'; const startQueryTime = Date.now(); @@ -29,48 +27,45 @@ if (dialect !== 'sqlite') { } const query = `SELECT ${now} as now`; - return Promise.all([ + + const [now1, now2] = await Promise.all([ this.sequelize.query(query, { type: this.sequelize.QueryTypes.SELECT }), this.sequelizeWithTimezone.query(query, { type: this.sequelize.QueryTypes.SELECT }) - ]).then(([now1, now2]) => { - const elapsedQueryTime = Date.now() - startQueryTime + 1001; - expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); - }); + ]); + + const elapsedQueryTime = Date.now() - startQueryTime + 1001; + expect(now1[0].now.getTime()).to.be.closeTo(now2[0].now.getTime(), elapsedQueryTime); }); if (dialect === 'mysql' || dialect === 'mariadb') { - it('handles existing timestamps', function() { + it('handles existing timestamps', async function() { const NormalUser = this.sequelize.define('user', {}), TimezonedUser = this.sequelizeWithTimezone.define('user', {}); - return this.sequelize.sync({ force: true }).then(() => { - return NormalUser.create({}); - }).then(normalUser => { - this.normalUser = normalUser; - return TimezonedUser.findByPk(normalUser.id); - }).then(timezonedUser => { - // Expect 7 hours difference, in milliseconds. - // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp - // this test does not apply to PG, since it stores the timezone along with the timestamp. - expect(this.normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 1000); - }); + await this.sequelize.sync({ force: true }); + const normalUser = await NormalUser.create({}); + this.normalUser = normalUser; + const timezonedUser = await TimezonedUser.findByPk(normalUser.id); + // Expect 7 hours difference, in milliseconds. + // This difference is expected since two instances, configured for each their timezone is trying to read the same timestamp + // this test does not apply to PG, since it stores the timezone along with the timestamp. + expect(this.normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 7 * 1000, 1000); }); - it('handles named timezones', function() { + it('handles named timezones', async function() { const NormalUser = this.sequelize.define('user', {}), TimezonedUser = this.sequelizeWithNamedTimezone.define('user', {}); - return this.sequelize.sync({ force: true }).then(() => { - return TimezonedUser.create({}); - }).then(timezonedUser => { - return Promise.all([ - NormalUser.findByPk(timezonedUser.id), - TimezonedUser.findByPk(timezonedUser.id) - ]); - }).then(([normalUser, timezonedUser]) => { - // Expect 5 hours difference, in milliseconds, +/- 1 hour for DST - expect(normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 4 * 1000 * -1, 60 * 60 * 1000); - }); + await this.sequelize.sync({ force: true }); + const timezonedUser0 = await TimezonedUser.create({}); + + const [normalUser, timezonedUser] = await Promise.all([ + NormalUser.findByPk(timezonedUser0.id), + TimezonedUser.findByPk(timezonedUser0.id) + ]); + + // Expect 5 hours difference, in milliseconds, +/- 1 hour for DST + expect(normalUser.createdAt.getTime() - timezonedUser.createdAt.getTime()).to.be.closeTo(60 * 60 * 4 * 1000 * -1, 60 * 60 * 1000); }); } }); diff --git a/test/integration/transaction.test.js b/test/integration/transaction.test.js index 934f165ba9c3..d4605dca3caf 100644 --- a/test/integration/transaction.test.js +++ b/test/integration/transaction.test.js @@ -1,15 +1,14 @@ 'use strict'; -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - dialect = Support.getTestDialect(), - Sequelize = require('../../index'), - Promise = Sequelize.Promise, - QueryTypes = require('../../lib/query-types'), - Transaction = require('../../lib/transaction'), - sinon = require('sinon'), - current = Support.sequelize; +const chai = require('chai'); +const expect = chai.expect; +const Support = require('./support'); +const dialect = Support.getTestDialect(); +const { Sequelize, QueryTypes, DataTypes, Transaction } = require('../../index'); +const sinon = require('sinon'); +const current = Support.sequelize; +const delay = require('delay'); +const pSettle = require('p-settle'); if (current.dialect.supports.transactions) { @@ -55,264 +54,430 @@ if (current.dialect.supports.transactions) { }); describe('autoCallback', () => { - it('supports automatically committing', function() { - return this.sequelize.transaction(() => { - return Promise.resolve(); - }); + it('supports automatically committing', async function() { + await this.sequelize.transaction(async () => {}); }); - it('supports automatically rolling back with a thrown error', function() { + it('supports automatically rolling back with a thrown error', async function() { let t; - return expect(this.sequelize.transaction(transaction => { + + await expect(this.sequelize.transaction(transaction => { t = transaction; throw new Error('Yolo'); - })).to.eventually.be.rejected.then(() => { - expect(t.finished).to.be.equal('rollback'); - }); + })).to.eventually.be.rejected; + + expect(t.finished).to.be.equal('rollback'); }); - it('supports automatically rolling back with a rejection', function() { + it('supports automatically rolling back with a rejection', async function() { let t; - return expect(this.sequelize.transaction(transaction => { + + await expect(this.sequelize.transaction(async transaction => { t = transaction; - return Promise.reject(new Error('Swag')); - })).to.eventually.be.rejected.then(() => { - expect(t.finished).to.be.equal('rollback'); - }); + throw new Error('Swag'); + })).to.eventually.be.rejected; + + expect(t.finished).to.be.equal('rollback'); }); - it('supports running hooks when a transaction is committed', function() { + it('supports running hooks when a transaction is committed', async function() { const hook = sinon.spy(); let transaction; - return expect(this.sequelize.transaction(t => { - transaction = t; - transaction.hooks.add('afterCommit', hook); - return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT }); - }).then(() => { + + await expect((async () => { + await this.sequelize.transaction(t => { + transaction = t; + transaction.afterCommit(hook); + return this.sequelize.query('SELECT 1+1', { transaction, type: QueryTypes.SELECT }); + }); + expect(hook).to.have.been.calledOnce; expect(hook).to.have.been.calledWith(transaction); - }) + })() ).to.eventually.be.fulfilled; }); - it('does not run hooks when a transaction is rolled back', function() { + it('does not run hooks when a transaction is rolled back', async function() { const hook = sinon.spy(); - return expect(this.sequelize.transaction(transaction => { - transaction.hooks.add('afterCommit', hook); - return Promise.reject(new Error('Rollback')); + + await expect(this.sequelize.transaction(async transaction => { + transaction.afterCommit(hook); + throw new Error('Rollback'); }) - ).to.eventually.be.rejected.then(() => { - expect(hook).to.not.have.been.called; - }); + ).to.eventually.be.rejected; + + expect(hook).to.not.have.been.called; }); - //Promise rejection test is specific to postgres if (dialect === 'postgres') { - it('do not rollback if already committed', function() { - const SumSumSum = this.sequelize.define('transaction', { - value: { - type: Support.Sequelize.DECIMAL(10, 3), - field: 'value' - } - }), - transTest = val => { - return this.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }, t => { - return SumSumSum.sum('value', { transaction: t }).then(() => { - return SumSumSum.create({ value: -val }, { transaction: t }); - }); + // See #3689, #3726 and #6972 (https://github.com/sequelize/sequelize/pull/6972/files#diff-533eac602d424db379c3d72af5089e9345fd9d3bbe0a26344503c22a0a5764f7L75) + it('does not try to rollback a transaction that failed upon committing with SERIALIZABLE isolation level (#3689)', async function() { + // See https://wiki.postgresql.org/wiki/SSI + + const Dots = this.sequelize.define('dots', { color: Sequelize.STRING }); + await Dots.sync({ force: true }); + + const initialData = [ + { color: 'red' }, + { color: 'green' }, + { color: 'green' }, + { color: 'red' }, + { color: 'green' }, + { color: 'red' }, + { color: 'green' }, + { color: 'green' }, + { color: 'green' }, + { color: 'red' }, + { color: 'red' }, + { color: 'red' }, + { color: 'green' }, + { color: 'red' }, + { color: 'red' }, + { color: 'red' }, + { color: 'green' }, + { color: 'red' } + ]; + + await Dots.bulkCreate(initialData); + + const isolationLevel = Transaction.ISOLATION_LEVELS.SERIALIZABLE; + + let firstTransactionGotNearCommit = false; + let secondTransactionGotNearCommit = false; + + const firstTransaction = async () => { + await this.sequelize.transaction({ isolationLevel }, async t => { + await Dots.update({ color: 'red' }, { + where: { color: 'green' }, + transaction: t }); - }; - // Attention: this test is a bit racy. If you find a nicer way to test this: go ahead - return SumSumSum.sync({ force: true }).then(() => { - return expect(Promise.all([transTest(80), transTest(80), transTest(80)])).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'); - }); + await delay(1500); + firstTransactionGotNearCommit = true; + }); + }; + + const secondTransaction = async () => { + await delay(500); + await this.sequelize.transaction({ isolationLevel }, async t => { + await Dots.update({ color: 'green' }, { + where: { color: 'red' }, + transaction: t + }); + + // Sanity check - in this test we want this line to be reached before the + // first transaction gets to commit + expect(firstTransactionGotNearCommit).to.be.false; + + secondTransactionGotNearCommit = true; + }); + }; + + await expect( + Promise.all([firstTransaction(), secondTransaction()]) + ).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'); + + expect(firstTransactionGotNearCommit).to.be.true; + expect(secondTransactionGotNearCommit).to.be.true; + + // Only the second transaction worked + expect(await Dots.count({ where: { color: 'red' } })).to.equal(0); + expect(await Dots.count({ where: { color: 'green' } })).to.equal(initialData.length); }); } }); - it('does not allow queries after commit', function() { - return this.sequelize.transaction().then(t => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }).then(() => { - return t.commit(); - }).then(() => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); - }); - }).throw(new Error('Expected error not thrown')) - .catch(err => { - expect(err.message).to.match(/commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/); - expect(err.sql).to.equal('SELECT 1+1'); - }); + it('does not allow queries after commit', async function() { + const t = await this.sequelize.transaction(); + await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + await t.commit(); + await expect(this.sequelize.query('SELECT 1+1', { transaction: t, raw: true })).to.be.eventually.rejectedWith( + Error, + /commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ + ).and.have.deep.property('sql').that.equal('SELECT 1+1'); }); - it('does not allow queries immediately after commit call', function() { - return expect( - this.sequelize.transaction().then(t => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }).then(() => { - return Promise.join( - expect(t.commit()).to.eventually.be.fulfilled, - this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }) - .throw(new Error('Expected error not thrown')) - .catch(err => { - expect(err.message).to.match(/commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/); - expect(err.sql).to.equal('SELECT 1+1'); - }) - ); - }); - }) - ).to.be.eventually.fulfilled; + it('does not allow queries immediately after commit call', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + await Promise.all([ + expect(t.commit()).to.eventually.be.fulfilled, + expect(this.sequelize.query('SELECT 1+1', { transaction: t, raw: true })).to.be.eventually.rejectedWith( + Error, + /commit has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ + ).and.have.deep.property('sql').that.equal('SELECT 1+1') + ]); + })()).to.be.eventually.fulfilled; }); - it('does not allow queries after rollback', function() { - return expect( - this.sequelize.transaction().then(t => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }).then(() => { - return t.rollback(); - }).then(() => { - return this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); - }); - }) + it('does not allow queries after rollback', async function() { + await expect( + (async () => { + const t = await this.sequelize.transaction(); + await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + await t.rollback(); + return await this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }); + })() ).to.eventually.be.rejected; }); - it('should not rollback if connection was not acquired', function() { + it('should not rollback if connection was not acquired', async function() { this.sinon.stub(this.sequelize.connectionManager, '_connect') - .returns(new Sequelize.Promise(() => {})); + .returns(new Promise(() => {})); const transaction = new Transaction(this.sequelize); - return expect(transaction.rollback()) + await expect(transaction.rollback()) .to.eventually.be.rejectedWith('Transaction cannot be rolled back because it never started'); }); - it('does not allow queries immediately after rollback call', function() { - return expect( - this.sequelize.transaction().then(t => { - return Promise.join( + it('does not allow queries immediately after rollback call', async function() { + await expect( + this.sequelize.transaction().then(async t => { + await Promise.all([ expect(t.rollback()).to.eventually.be.fulfilled, - this.sequelize.query('SELECT 1+1', { transaction: t, raw: true }) - .throw(new Error('Expected error not thrown')) - .catch(err => { - expect(err.message).to.match(/rollback has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/); - expect(err.sql).to.equal('SELECT 1+1'); - }) - ); + expect(this.sequelize.query('SELECT 1+1', { transaction: t, raw: true })).to.be.eventually.rejectedWith( + Error, + /rollback has been called on this transaction\([^)]+\), you can no longer use it\. \(The rejected query is attached as the 'sql' property of this error\)/ + ).and.have.deep.property('sql').that.equal('SELECT 1+1') + ]); }) ).to.eventually.be.fulfilled; }); - it('does not allow commits after commit', function() { - return expect( - this.sequelize.transaction().then(t => { - return t.commit().then(() => { - return t.commit(); - }); - }) + it('does not allow commits after commit', async function() { + await expect( + (async () => { + const t = await this.sequelize.transaction(); + await t.commit(); + return await t.commit(); + })() ).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: commit'); }); - it('should run hooks if a non-auto callback transaction is committed', function() { + it('should run hooks if a non-auto callback transaction is committed', async function() { const hook = sinon.spy(); let transaction; - return expect( - this.sequelize.transaction().then(t => { - transaction = t; - transaction.hooks.add('afterCommit', hook); - return t.commit().then(() => { + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + await t.commit(); expect(hook).to.have.been.calledOnce; expect(hook).to.have.been.calledWith(t); - }); - }).catch(err => { - // Cleanup this transaction so other tests don't - // fail due to an open transaction - if (!transaction.finished) { - return transaction.rollback().then(() => { + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); throw err; - }); + } + throw err; } - throw err; - }) + })() ).to.eventually.be.fulfilled; }); - it('should not run hooks if a non-auto callback transaction is rolled back', function() { + it('should not run hooks if a non-auto callback transaction is rolled back', async function() { const hook = sinon.spy(); - return expect( - this.sequelize.transaction().then(t => { - t.hooks.add('afterCommit', hook); - return t.rollback().then(() => { - expect(hook).to.not.have.been.called; - }); - }) + + await expect( + (async () => { + const t = await this.sequelize.transaction(); + t.afterCommit(hook); + await t.rollback(); + expect(hook).to.not.have.been.called; + })() ).to.eventually.be.fulfilled; }); - it('does not allow commits after rollback', function() { - return expect(this.sequelize.transaction().then(t => { - return t.rollback().then(() => { - return t.commit(); - }); - })).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: rollback'); + it('should throw an error if null is passed to afterCommit', async function() { + const hook = null; + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + return await t.commit(); + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw err; + } + throw err; + } + })() + ).to.eventually.be.rejectedWith('"fn" must be a function'); }); - it('does not allow rollbacks after commit', function() { - return expect(this.sequelize.transaction().then(t => { - return t.commit().then(() => { - return t.rollback(); - }); - })).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: commit'); + it('should throw an error if undefined is passed to afterCommit', async function() { + const hook = undefined; + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + return await t.commit(); + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw err; + } + throw err; + } + })() + ).to.eventually.be.rejectedWith('"fn" must be a function'); }); - it('does not allow rollbacks after rollback', function() { - return expect(this.sequelize.transaction().then(t => { - return t.rollback().then(() => { - return t.rollback(); - }); - })).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: rollback'); + it('should throw an error if an object is passed to afterCommit', async function() { + const hook = {}; + let transaction; + + await expect( + (async () => { + try { + const t = await this.sequelize.transaction(); + transaction = t; + transaction.afterCommit(hook); + return await t.commit(); + } catch (err) { + // Cleanup this transaction so other tests don't + // fail due to an open transaction + if (!transaction.finished) { + await transaction.rollback(); + throw err; + } + throw err; + } + })() + ).to.eventually.be.rejectedWith('"fn" must be a function'); }); - it('works even if a transaction: null option is passed', function() { + it('does not allow commits after rollback', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await t.rollback(); + return await t.commit(); + })()).to.be.rejectedWith('Transaction cannot be committed because it has been finished with state: rollback'); + }); + + it('does not allow rollbacks after commit', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await t.commit(); + return await t.rollback(); + })()).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: commit'); + }); + + it('does not allow rollbacks after rollback', async function() { + await expect((async () => { + const t = await this.sequelize.transaction(); + await t.rollback(); + return await t.rollback(); + })()).to.be.rejectedWith('Transaction cannot be rolled back because it has been finished with state: rollback'); + }); + + it('works even if a transaction: null option is passed', async function() { this.sinon.spy(this.sequelize, 'query'); - return this.sequelize.transaction({ + const t = await this.sequelize.transaction({ transaction: null - }).then(t => { - return t.commit().then(() => { - expect(this.sequelize.query.callCount).to.be.greaterThan(0); - - for (let i = 0; i < this.sequelize.query.callCount; i++) { - expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); - } - }); }); + + await t.commit(); + expect(this.sequelize.query.callCount).to.be.greaterThan(0); + + for (let i = 0; i < this.sequelize.query.callCount; i++) { + expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); + } }); - it('works even if a transaction: undefined option is passed', function() { + it('works even if a transaction: undefined option is passed', async function() { this.sinon.spy(this.sequelize, 'query'); - return this.sequelize.transaction({ + const t = await this.sequelize.transaction({ transaction: undefined - }).then(t => { - return t.commit().then(() => { - expect(this.sequelize.query.callCount).to.be.greaterThan(0); - - for (let i = 0; i < this.sequelize.query.callCount; i++) { - expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); - } - }); }); + + await t.commit(); + expect(this.sequelize.query.callCount).to.be.greaterThan(0); + + for (let i = 0; i < this.sequelize.query.callCount; i++) { + expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t); + } }); if (dialect === 'mysql' || dialect === 'mariadb') { describe('deadlock handling', () => { - it('should treat deadlocked transaction as rollback', function() { - const Task = this.sequelize.define('task', { + // Create the `Task` table and ensure it's initialized with 2 rows + const getAndInitializeTaskModel = async sequelize => { + const Task = sequelize.define('task', { id: { type: Sequelize.INTEGER, primaryKey: true } }); + await sequelize.sync({ force: true }); + await Task.create({ id: 0 }); + await Task.create({ id: 1 }); + return Task; + }; + + // Lock the row with id of `from`, and then try to update the row + // with id of `to` + const update = async (sequelize, Task, from, to) => { + await sequelize + .transaction(async transaction => { + try { + try { + await Task.findAll({ + where: { id: { [Sequelize.Op.eq]: from } }, + lock: transaction.LOCK.UPDATE, + transaction + }); + + await delay(10); + + await Task.update( + { id: to }, + { + where: { id: { [Sequelize.Op.ne]: to } }, + lock: transaction.LOCK.UPDATE, + transaction + } + ); + } catch (e) { + console.log(e.message); + } + + await Task.create({ id: 2 }, { transaction }); + } catch (e) { + console.log(e.message); + } + + throw new Error('Rollback!'); + }) + .catch(() => {}); + }; + + it('should treat deadlocked transaction as rollback', async function() { + const Task = await getAndInitializeTaskModel(this.sequelize); + // This gets called twice simultaneously, and we expect at least one of the calls to encounter a // deadlock (which effectively rolls back the active transaction). // We only expect createTask() to insert rows if a transaction is active. If deadlocks are handled @@ -320,101 +485,206 @@ if (current.dialect.supports.transactions) { // execute a query, we expect the newly-created rows to be destroyed when we forcibly rollback by // throwing an error. // tl;dr; This test is designed to ensure that this function never inserts and commits a new row. - const update = (from, to) => this.sequelize.transaction(transaction => { - return Task.findAll({ - where: { - id: { - [Sequelize.Op.eq]: from + await Promise.all([update(this.sequelize, Task, 1, 0), update(this.sequelize, Task, 0, 1)]); + + const count = await Task.count(); + // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. + expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); + }); + + it('should release the connection for a deadlocked transaction (1/2)', async function() { + const Task = await getAndInitializeTaskModel(this.sequelize); + + // 1 of 2 queries should deadlock and be rolled back by InnoDB + this.sinon.spy(this.sequelize.connectionManager, 'releaseConnection'); + await Promise.all([update(this.sequelize, Task, 1, 0), update(this.sequelize, Task, 0, 1)]); + + // Verify that both of the connections were released + expect(this.sequelize.connectionManager.releaseConnection.callCount).to.equal(2); + + // Verify that a follow-up READ_COMMITTED works as expected. + // For unknown reasons, we need to explicitly rollback on MariaDB, + // even though the transaction should've automatically been rolled + // back. + // Otherwise, this READ_COMMITTED doesn't work as expected. + const User = this.sequelize.define('user', { + username: Support.Sequelize.STRING + }); + await this.sequelize.sync({ force: true }); + await this.sequelize.transaction( + { isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, + async transaction => { + const users0 = await User.findAll({ transaction }); + expect(users0).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + expect(users).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction + } + ); + }); + + it('should release the connection for a deadlocked transaction (2/2)', async function() { + const verifyDeadlock = async () => { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN + }, { timestamps: false }); + + await this.sequelize.sync({ force: true }); + const { id } = await User.create({ username: 'jan' }); + + // First, we start a transaction T1 and perform a SELECT with it using the `LOCK.SHARE` mode (setting a shared mode lock on the row). + // This will cause other sessions to be able to read the row but not modify it. + // So, if another transaction tries to update those same rows, it will wait until T1 commits (or rolls back). + // https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html + const t1 = await this.sequelize.transaction(); + const t1Jan = await User.findByPk(id, { lock: t1.LOCK.SHARE, transaction: t1 }); + + // Then we start another transaction T2 and see that it can indeed read the same row. + const t2 = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }); + const t2Jan = await User.findByPk(id, { transaction: t2 }); + + // Then, we want to see that an attempt to update that row from T2 will be queued until T1 commits. + // However, before commiting T1 we will also perform an update via T1 on the same rows. + // This should cause T2 to notice that it can't function anymore, so it detects a deadlock and automatically rolls itself back (and throws an error). + // Meanwhile, T1 should still be ok. + const executionOrder = []; + const [t2AttemptData, t1AttemptData] = await pSettle([ + (async () => { + try { + executionOrder.push('Begin attempt to update via T2'); + await t2Jan.update({ awesome: false }, { transaction: t2 }); + executionOrder.push('Done updating via T2'); // Shouldn't happen + } catch (error) { + executionOrder.push('Failed to update via T2'); + throw error; } - }, - lock: 'UPDATE', - transaction - }) - .then(() => Promise.delay(10)) - .then(() => { - return Task.update({ id: to }, { - where: { - id: { - [Sequelize.Op.ne]: to - } - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - }) - .catch(e => { console.log(e.message); }) - .then(() => Task.create({ id: 2 }, { transaction })) - .catch(e => { console.log(e.message); }) - .then(() => { throw new Error('Rollback!'); }); - }).catch(() => {}); - - return this.sequelize.sync({ force: true }) - .then(() => Task.create({ id: 0 })) - .then(() => Task.create({ id: 1 })) - .then(() => Promise.all([ - update(1, 0), - update(0, 1) - ])) - .then(() => { - return Task.count().then(count => { - // If we were actually inside a transaction when we called `Task.create({ id: 2 })`, no new rows should be added. - expect(count).to.equal(2, 'transactions were fully rolled-back, and no new rows were added'); - }); - }); + + await delay(30); + + try { + // We shouldn't reach this point, but if we do, let's at least commit the transaction + // to avoid forever occupying one connection of the pool with a pending transaction. + executionOrder.push('Attempting to commit T2'); + await t2.commit(); + executionOrder.push('Done committing T2'); + } catch { + executionOrder.push('Failed to commit T2'); + } + })(), + (async () => { + await delay(100); + + try { + executionOrder.push('Begin attempt to update via T1'); + await t1Jan.update({ awesome: true }, { transaction: t1 }); + executionOrder.push('Done updating via T1'); + } catch (error) { + executionOrder.push('Failed to update via T1'); // Shouldn't happen + throw error; + } + + await delay(150); + + try { + executionOrder.push('Attempting to commit T1'); + await t1.commit(); + executionOrder.push('Done committing T1'); + } catch { + executionOrder.push('Failed to commit T1'); // Shouldn't happen + } + })() + ]); + + expect(t1AttemptData.isFulfilled).to.be.true; + expect(t2AttemptData.isRejected).to.be.true; + expect(t2AttemptData.reason.message).to.include('Deadlock found when trying to get lock; try restarting transaction'); + expect(t1.finished).to.equal('commit'); + expect(t2.finished).to.equal('rollback'); + + const expectedExecutionOrder = [ + 'Begin attempt to update via T2', + 'Begin attempt to update via T1', // 100ms after + 'Done updating via T1', // right after + 'Failed to update via T2', // right after + 'Attempting to commit T1', // 150ms after + 'Done committing T1' // right after + ]; + + // The order things happen in the database must be the one shown above. However, sometimes it can happen that + // the calls in the JavaScript event loop that are communicating with the database do not match exactly this order. + // In particular, it is possible that the JS event loop logs `'Failed to update via T2'` before logging `'Done updating via T1'`, + // even though the database updated T1 first (and then rushed to declare a deadlock for T2). + + const anotherAcceptableExecutionOrderFromJSPerspective = [ + 'Begin attempt to update via T2', + 'Begin attempt to update via T1', // 100ms after + 'Failed to update via T2', // right after + 'Done updating via T1', // right after + 'Attempting to commit T1', // 150ms after + 'Done committing T1' // right after + ]; + + const executionOrderOk = Support.isDeepEqualToOneOf( + executionOrder, + [ + expectedExecutionOrder, + anotherAcceptableExecutionOrderFromJSPerspective + ] + ); + + if (!executionOrderOk) { + throw new Error(`Unexpected execution order: ${executionOrder.join(' > ')}`); + } + }; + + for (let i = 0; i < 3 * Support.getPoolMax(); i++) { + await verifyDeadlock(); + await delay(10); + } }); }); } if (dialect === 'sqlite') { - it('provides persistent transactions', () => { + it('provides persistent transactions', async () => { const sequelize = new Support.Sequelize('database', 'username', 'password', { dialect: 'sqlite' }), User = sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN }); - let persistentTransaction; - return sequelize.transaction().then(t => { - return sequelize.sync({ transaction: t }).then(( ) => { - return t; - }); - }).then(t => { - return User.create({}, { transaction: t }).then(( ) => { - return t.commit(); - }); - }).then(() => { - return sequelize.transaction().then(t => { - persistentTransaction = t; - }); - }).then(() => { - return User.findAll({ transaction: persistentTransaction }).then(users => { - expect(users.length).to.equal(1); - return persistentTransaction.commit(); - }); - }); + const t1 = await sequelize.transaction(); + await sequelize.sync({ transaction: t1 }); + const t0 = t1; + await User.create({}, { transaction: t0 }); + await t0.commit(); + const persistentTransaction = await sequelize.transaction(); + const users = await User.findAll({ transaction: persistentTransaction }); + expect(users.length).to.equal(1); + + await persistentTransaction.commit(); }); } if (current.dialect.supports.transactionOptions.type) { describe('transaction types', () => { - it('should support default transaction type DEFERRED', function() { - return this.sequelize.transaction({ - }).then(t => { - return t.rollback().then(() => { - expect(t.options.type).to.equal('DEFERRED'); - }); + it('should support default transaction type DEFERRED', async function() { + const t = await this.sequelize.transaction({ }); + + await t.rollback(); + expect(t.options.type).to.equal('DEFERRED'); }); Object.keys(Transaction.TYPES).forEach(key => { - it(`should allow specification of ${key} type`, function() { - return this.sequelize.transaction({ + it(`should allow specification of ${key} type`, async function() { + const t = await this.sequelize.transaction({ type: key - }).then(t => { - return t.rollback().then(() => { - expect(t.options.type).to.equal(Transaction.TYPES[key]); - }); }); + + await t.rollback(); + expect(t.options.type).to.equal(Transaction.TYPES[key]); }); }); @@ -423,50 +693,116 @@ if (current.dialect.supports.transactions) { } if (dialect === 'sqlite') { - it('automatically retries on SQLITE_BUSY failure', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { username: Support.Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - const newTransactionFunc = function() { - return sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE }).then(t => { - return User.create({}, { transaction: t }).then(( ) => { - return t.commit(); - }); - }); - }; - return Promise.join(newTransactionFunc(), newTransactionFunc()).then(() => { - return User.findAll().then(users => { - expect(users.length).to.equal(2); - }); - }); - }); + it('automatically retries on SQLITE_BUSY failure', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { username: Support.Sequelize.STRING }); + await User.sync({ force: true }); + const newTransactionFunc = async function() { + const t = await sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE }); + await User.create({}, { transaction: t }); + return t.commit(); + }; + await Promise.all([newTransactionFunc(), newTransactionFunc()]); + const users = await User.findAll(); + expect(users.length).to.equal(2); + }); + + it('fails with SQLITE_BUSY when retry.match is changed', async function() { + const sequelize = await Support.prepareTransactionTest(this.sequelize); + const User = sequelize.define('User', { id: { type: Support.Sequelize.INTEGER, primaryKey: true }, username: Support.Sequelize.STRING }); + await User.sync({ force: true }); + const newTransactionFunc = async function() { + const t = await sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: { match: ['NO_MATCH'] } }); + // introduce delay to force the busy state race condition to fail + await delay(1000); + await User.create({ id: null, username: `test ${t.id}` }, { transaction: t }); + return t.commit(); + }; + await expect(Promise.all([newTransactionFunc(), newTransactionFunc()])).to.be.rejectedWith('SQLITE_BUSY: database is locked'); + }); + + } + + describe('isolation levels', () => { + it('should read the most recent committed rows when using the READ COMMITTED isolation level', async function() { + const User = this.sequelize.define('user', { + username: Support.Sequelize.STRING }); + + await expect( + this.sequelize.sync({ force: true }).then(() => { + return this.sequelize.transaction( + { isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }, + async transaction => { + const users0 = await User.findAll({ transaction }); + expect(users0).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + expect(users).to.have.lengthOf(1); // We SHOULD see the created user inside the transaction + } + ); + }) + ).to.eventually.be.fulfilled; }); - it('fails with SQLITE_BUSY when retry.match is changed', function() { - return Support.prepareTransactionTest(this.sequelize).then(sequelize => { - const User = sequelize.define('User', { id: { type: Support.Sequelize.INTEGER, primaryKey: true }, username: Support.Sequelize.STRING }); - return User.sync({ force: true }).then(() => { - const newTransactionFunc = function() { - return sequelize.transaction({ type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: { match: ['NO_MATCH'] } }).then(t => { - // introduce delay to force the busy state race condition to fail - return Promise.delay(1000).then(() => { - return User.create({ id: null, username: `test ${t.id}` }, { transaction: t }).then(() => { - return t.commit(); - }); - }); - }); - }; - return expect(Promise.join(newTransactionFunc(), newTransactionFunc())).to.be.rejectedWith('SQLITE_BUSY: database is locked'); + // mssql is excluded because it implements REPREATABLE READ using locks rather than a snapshot, and will see the new row + if (!['sqlite', 'mssql'].includes(dialect)) { + it('should not read newly committed rows when using the REPEATABLE READ isolation level', async function() { + const User = this.sequelize.define('user', { + username: Support.Sequelize.STRING }); + + await expect( + this.sequelize.sync({ force: true }).then(() => { + return this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.REPEATABLE_READ }, async transaction => { + const users0 = await User.findAll({ transaction }); + await expect( users0 ).to.have.lengthOf(0); + await User.create({ username: 'jan' }); // Create a User outside of the transaction + const users = await User.findAll({ transaction }); + return expect( users ).to.have.lengthOf(0); // We SHOULD NOT see the created user inside the transaction + }); + }) + ).to.eventually.be.fulfilled; }); - }); + } + + // PostgreSQL is excluded because it detects Serialization Failure on commit instead of acquiring locks on the read rows + if (!['sqlite', 'postgres', 'postgres-native'].includes(dialect)) { + it('should block updates after reading a row using SERIALIZABLE', async function() { + const User = this.sequelize.define('user', { + username: Support.Sequelize.STRING + }), + transactionSpy = sinon.spy(); + + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const transaction = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE }); + await User.findAll( { transaction } ); + + await Promise.all([ + // Update should not succeed before transaction has committed + User.update({ username: 'joe' }, { + where: { + username: 'jan' + } + }).then(() => { + expect(transactionSpy).to.have.been.called; + expect(transaction.finished).to.equal('commit'); + }), + + delay(4000) + .then(transactionSpy) + .then(() => transaction.commit()) + ]); + }); + } + + }); - } if (current.dialect.supports.lock) { describe('row locking', () => { - it('supports for update', function() { + it('supports for update', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN @@ -474,190 +810,185 @@ if (current.dialect.supports.transactions) { t1Spy = sinon.spy(), t2Spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findOne({ - where: { - username: 'jan' - }, - lock: t1.LOCK.UPDATE, - transaction: t1 - }).then(t1Jan => { - return this.sequelize.transaction({ - isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED - }).then(t2 => { - return Promise.join( - User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.UPDATE, - transaction: t2 - }).then(() => { - t2Spy(); - return t2.commit().then(() => { - expect(t2Spy).to.have.been.calledAfter(t1Spy); // Find should not succeed before t1 has committed - }); - }), - - t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - t1Spy(); - return Promise.delay(2000).then(() => { - return t1.commit(); - }); - }) - ); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const t1 = await this.sequelize.transaction(); + + const t1Jan = await User.findOne({ + where: { + username: 'jan' + }, + lock: t1.LOCK.UPDATE, + transaction: t1 + }); + + const t2 = await this.sequelize.transaction({ + isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }); + + await Promise.all([(async () => { + await User.findOne({ + where: { + username: 'jan' + }, + lock: t2.LOCK.UPDATE, + transaction: t2 + }); + + t2Spy(); + await t2.commit(); + expect(t2Spy).to.have.been.calledAfter(t1Spy); // Find should not succeed before t1 has committed + })(), (async () => { + await t1Jan.update({ + awesome: true + }, { + transaction: t1 + }); + + t1Spy(); + await delay(2000); + return await t1.commit(); + })()]); }); if (current.dialect.supports.skipLocked) { - it('supports for update with skip locked', function() { + it('supports for update with skip locked', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.all([ - User.create( - { username: 'jan' } - ), - User.create( - { username: 'joe' } - ) - ]); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findAll({ - limit: 1, - lock: true, - transaction: t1 - }).then(results => { - const firstUserId = results[0].id; - return this.sequelize.transaction().then(t2 => { - return User.findAll({ - limit: 1, - lock: true, - skipLocked: true, - transaction: t2 - }).then(secondResults => { - expect(secondResults[0].id).to.not.equal(firstUserId); - return Promise.all([ - t1.commit(), - t2.commit() - ]); - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + await Promise.all([ + User.create( + { username: 'jan' } + ), + User.create( + { username: 'joe' } + ) + ]); + + const t1 = await this.sequelize.transaction(); + + const results = await User.findAll({ + limit: 1, + lock: true, + transaction: t1 + }); + + const firstUserId = results[0].id; + const t2 = await this.sequelize.transaction(); + + const secondResults = await User.findAll({ + limit: 1, + lock: true, + skipLocked: true, + transaction: t2 }); + + expect(secondResults[0].id).to.not.equal(firstUserId); + + await Promise.all([ + t1.commit(), + t2.commit() + ]); }); } - it('fail locking with outer joins', function() { + it('fail locking with outer joins', async function() { const User = this.sequelize.define('User', { username: Support.Sequelize.STRING }), Task = this.sequelize.define('Task', { title: Support.Sequelize.STRING, active: Support.Sequelize.BOOLEAN }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ username: 'John' }), - Task.create({ title: 'Get rich', active: false }), - (john, task1) => { - return john.setTasks([task1]); - }) - .then(() => { - return this.sequelize.transaction(t1 => { - - if (current.dialect.supports.lockOuterJoinFailure) { - - return expect(User.findOne({ - where: { - username: 'John' - }, - include: [Task], - lock: t1.LOCK.UPDATE, - transaction: t1 - })).to.be.rejectedWith('FOR UPDATE cannot be applied to the nullable side of an outer join'); - } - - return User.findOne({ - where: { - username: 'John' - }, - include: [Task], - lock: t1.LOCK.UPDATE, - transaction: t1 - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1] = await Promise.all([ + User.create({ username: 'John' }), + Task.create({ title: 'Get rich', active: false }) + ]); + + await john.setTasks([task1]); + + await this.sequelize.transaction(t1 => { + + if (current.dialect.supports.lockOuterJoinFailure) { + + return expect(User.findOne({ + where: { + username: 'John' + }, + include: [Task], + lock: t1.LOCK.UPDATE, + transaction: t1 + })).to.be.rejectedWith('FOR UPDATE cannot be applied to the nullable side of an outer join'); + } + + return User.findOne({ + where: { + username: 'John' + }, + include: [Task], + lock: t1.LOCK.UPDATE, + transaction: t1 + }); }); }); if (current.dialect.supports.lockOf) { - it('supports for update of table', function() { + it('supports for update of table', async function() { const User = this.sequelize.define('User', { username: Support.Sequelize.STRING }, { tableName: 'Person' }), Task = this.sequelize.define('Task', { title: Support.Sequelize.STRING, active: Support.Sequelize.BOOLEAN }); User.belongsToMany(Task, { through: 'UserTasks' }); Task.belongsToMany(User, { through: 'UserTasks' }); - return this.sequelize.sync({ force: true }).then(() => { - return Promise.join( - User.create({ username: 'John' }), - Task.create({ title: 'Get rich', active: false }), - Task.create({ title: 'Die trying', active: false }), - (john, task1) => { - return john.setTasks([task1]); - }) - .then(() => { - return this.sequelize.transaction(t1 => { - return User.findOne({ - where: { - username: 'John' - }, - include: [Task], - lock: { - level: t1.LOCK.UPDATE, - of: User - }, - transaction: t1 - }).then(t1John => { - // should not be blocked by the lock of the other transaction - return this.sequelize.transaction(t2 => { - return Task.update({ - active: true - }, { - where: { - active: false - }, - transaction: t2 - }); - }).then(() => { - return t1John.save({ - transaction: t1 - }); - }); - }); - }); + await this.sequelize.sync({ force: true }); + + const [john, task1] = await Promise.all([ + User.create({ username: 'John' }), + Task.create({ title: 'Get rich', active: false }), + Task.create({ title: 'Die trying', active: false }) + ]); + + await john.setTasks([task1]); + + await this.sequelize.transaction(async t1 => { + const t1John = await User.findOne({ + where: { + username: 'John' + }, + include: [Task], + lock: { + level: t1.LOCK.UPDATE, + of: User + }, + transaction: t1 + }); + + // should not be blocked by the lock of the other transaction + await this.sequelize.transaction(t2 => { + return Task.update({ + active: true + }, { + where: { + active: false + }, + transaction: t2 }); + }); + + return t1John.save({ + transaction: t1 + }); }); }); } if (current.dialect.supports.lockKey) { - it('supports for key share', function() { + it('supports for key share', async function() { const User = this.sequelize.define('user', { username: Support.Sequelize.STRING, awesome: Support.Sequelize.BOOLEAN @@ -665,110 +996,165 @@ if (current.dialect.supports.transactions) { t1Spy = sinon.spy(), t2Spy = sinon.spy(); - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findOne({ - where: { - username: 'jan' - }, - lock: t1.LOCK.NO_KEY_UPDATE, - transaction: t1 - }).then(t1Jan => { - return this.sequelize.transaction().then(t2 => { - return Promise.join( - User.findOne({ - where: { - username: 'jan' - }, - lock: t2.LOCK.KEY_SHARE, - transaction: t2 - }).then(() => { - t2Spy(); - return t2.commit(); - }), - t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - return Promise.delay(2000).then(() => { - t1Spy(); - expect(t1Spy).to.have.been.calledAfter(t2Spy); - return t1.commit(); - }); - }) - ); - }); - }); - }); + await this.sequelize.sync({ force: true }); + await User.create({ username: 'jan' }); + const t1 = await this.sequelize.transaction(); + + const t1Jan = await User.findOne({ + where: { + username: 'jan' + }, + lock: t1.LOCK.NO_KEY_UPDATE, + transaction: t1 }); - }); - } - it('supports for share', function() { - const User = this.sequelize.define('user', { - username: Support.Sequelize.STRING, - awesome: Support.Sequelize.BOOLEAN - }), - t1Spy = sinon.spy(), - t2FindSpy = sinon.spy(), - t2UpdateSpy = sinon.spy(); - - return this.sequelize.sync({ force: true }).then(() => { - return User.create({ username: 'jan' }); - }).then(() => { - return this.sequelize.transaction().then(t1 => { - return User.findOne({ + const t2 = await this.sequelize.transaction(); + + await Promise.all([(async () => { + await User.findOne({ where: { username: 'jan' }, - lock: t1.LOCK.SHARE, + lock: t2.LOCK.KEY_SHARE, + transaction: t2 + }); + + t2Spy(); + return await t2.commit(); + })(), (async () => { + await t1Jan.update({ + awesome: true + }, { transaction: t1 - }).then(t1Jan => { - return this.sequelize.transaction({ - isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED - }).then(t2 => { - return Promise.join( - User.findOne({ - where: { - username: 'jan' - }, - transaction: t2 - }).then(t2Jan => { - t2FindSpy(); - return t2Jan.update({ - awesome: false - }, { - transaction: t2 - }).then(() => { - t2UpdateSpy(); - return t2.commit().then(() => { - expect(t2FindSpy).to.have.been.calledBefore(t1Spy); // The find call should have returned - expect(t2UpdateSpy).to.have.been.calledAfter(t1Spy); // But the update call should not happen before the first transaction has committed - }); - }); - }), - - t1Jan.update({ - awesome: true - }, { - transaction: t1 - }).then(() => { - return Promise.delay(2000).then(() => { - t1Spy(); - return t1.commit(); - }); - }) - ); - }); }); - }); + + await delay(2000); + t1Spy(); + expect(t1Spy).to.have.been.calledAfter(t2Spy); + return await t1.commit(); + })()]); }); + } + + it('supports for share (i.e. `SELECT ... LOCK IN SHARE MODE`)', async function() { + const verifySelectLockInShareMode = async () => { + const User = this.sequelize.define('user', { + username: DataTypes.STRING, + awesome: DataTypes.BOOLEAN + }, { timestamps: false }); + + await this.sequelize.sync({ force: true }); + const { id } = await User.create({ username: 'jan' }); + + // First, we start a transaction T1 and perform a SELECT with it using the `LOCK.SHARE` mode (setting a shared mode lock on the row). + // This will cause other sessions to be able to read the row but not modify it. + // So, if another transaction tries to update those same rows, it will wait until T1 commits (or rolls back). + // https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html + const t1 = await this.sequelize.transaction(); + await User.findByPk(id, { lock: t1.LOCK.SHARE, transaction: t1 }); + + // Then we start another transaction T2 and see that it can indeed read the same row. + const t2 = await this.sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED }); + const t2Jan = await User.findByPk(id, { transaction: t2 }); + + // Then, we want to see that an attempt to update that row from T2 will be queued until T1 commits. + const executionOrder = []; + const [t2AttemptData, t1AttemptData] = await pSettle([ + (async () => { + try { + executionOrder.push('Begin attempt to update via T2'); + await t2Jan.update({ awesome: false }, { transaction: t2 }); + executionOrder.push('Done updating via T2'); + } catch (error) { + executionOrder.push('Failed to update via T2'); // Shouldn't happen + throw error; + } + + await delay(30); + + try { + executionOrder.push('Attempting to commit T2'); + await t2.commit(); + executionOrder.push('Done committing T2'); + } catch { + executionOrder.push('Failed to commit T2'); // Shouldn't happen + } + })(), + (async () => { + await delay(100); + + try { + executionOrder.push('Begin attempt to read via T1'); + await User.findAll({ transaction: t1 }); + executionOrder.push('Done reading via T1'); + } catch (error) { + executionOrder.push('Failed to read via T1'); // Shouldn't happen + throw error; + } + + await delay(150); + + try { + executionOrder.push('Attempting to commit T1'); + await t1.commit(); + executionOrder.push('Done committing T1'); + } catch { + executionOrder.push('Failed to commit T1'); // Shouldn't happen + } + })() + ]); + + expect(t1AttemptData.isFulfilled).to.be.true; + expect(t2AttemptData.isFulfilled).to.be.true; + expect(t1.finished).to.equal('commit'); + expect(t2.finished).to.equal('commit'); + + const expectedExecutionOrder = [ + 'Begin attempt to update via T2', + 'Begin attempt to read via T1', // 100ms after + 'Done reading via T1', // right after + 'Attempting to commit T1', // 150ms after + 'Done committing T1', // right after + 'Done updating via T2', // right after + 'Attempting to commit T2', // 30ms after + 'Done committing T2' // right after + ]; + + // The order things happen in the database must be the one shown above. However, sometimes it can happen that + // the calls in the JavaScript event loop that are communicating with the database do not match exactly this order. + // In particular, it is possible that the JS event loop logs `'Done updating via T2'` before logging `'Done committing T1'`, + // even though the database committed T1 first (and then rushed to complete the pending update query from T2). + + const anotherAcceptableExecutionOrderFromJSPerspective = [ + 'Begin attempt to update via T2', + 'Begin attempt to read via T1', // 100ms after + 'Done reading via T1', // right after + 'Attempting to commit T1', // 150ms after + 'Done updating via T2', // right after + 'Done committing T1', // right after + 'Attempting to commit T2', // 30ms after + 'Done committing T2' // right after + ]; + + const executionOrderOk = Support.isDeepEqualToOneOf( + executionOrder, + [ + expectedExecutionOrder, + anotherAcceptableExecutionOrderFromJSPerspective + ] + ); + + if (!executionOrderOk) { + throw new Error(`Unexpected execution order: ${executionOrder.join(' > ')}`); + } + }; + + for (let i = 0; i < 3 * Support.getPoolMax(); i++) { + await verifySelectLockInShareMode(); + await delay(10); + } }); }); } }); - } diff --git a/test/integration/trigger.test.js b/test/integration/trigger.test.js index 16fd3ae505e4..6877fbdd8841 100644 --- a/test/integration/trigger.test.js +++ b/test/integration/trigger.test.js @@ -22,7 +22,7 @@ if (current.dialect.supports.tmpTableTrigger) { 'select * from deleted\n' + 'end\n'; - beforeEach(function() { + beforeEach(async function() { User = this.sequelize.define('user', { username: { type: Sequelize.STRING, @@ -32,56 +32,52 @@ if (current.dialect.supports.tmpTableTrigger) { hasTrigger: true }); - return User.sync({ force: true }).then(() => { - return this.sequelize.query(triggerQuery, { type: this.sequelize.QueryTypes.RAW }); - }); + await User.sync({ force: true }); + + await this.sequelize.query(triggerQuery, { type: this.sequelize.QueryTypes.RAW }); }); - it('should return output rows after insert', () => { - return User.create({ + it('should return output rows after insert', async () => { + await User.create({ username: 'triggertest' - }).then(() => { - return expect(User.findOne({ username: 'triggertest' })).to.eventually.have.property('username').which.equals('triggertest'); }); + + await expect(User.findOne({ username: 'triggertest' })).to.eventually.have.property('username').which.equals('triggertest'); }); - it('should return output rows after instance update', () => { - return User.create({ + it('should return output rows after instance update', async () => { + const user = await User.create({ username: 'triggertest' - }).then(user => { - user.username = 'usernamechanged'; - return user.save(); - }) - .then(() => { - return expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); - }); + }); + + user.username = 'usernamechanged'; + await user.save(); + await expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); }); - it('should return output rows after Model update', () => { - return User.create({ + it('should return output rows after Model update', async () => { + const user = await User.create({ username: 'triggertest' - }).then(user => { - return User.update({ - username: 'usernamechanged' - }, { - where: { - id: user.get('id') - } - }); - }) - .then(() => { - return expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); - }); + }); + + await User.update({ + username: 'usernamechanged' + }, { + where: { + id: user.get('id') + } + }); + + await expect(User.findOne({ username: 'usernamechanged' })).to.eventually.have.property('username').which.equals('usernamechanged'); }); - it('should successfully delete with a trigger on the table', () => { - return User.create({ + it('should successfully delete with a trigger on the table', async () => { + const user = await User.create({ username: 'triggertest' - }).then(user => { - return user.destroy(); - }).then(() => { - return expect(User.findOne({ username: 'triggertest' })).to.eventually.be.null; }); + + await user.destroy(); + await expect(User.findOne({ username: 'triggertest' })).to.eventually.be.null; }); }); }); diff --git a/test/integration/utils.test.js b/test/integration/utils.test.js index 90d181b2e936..622047e61b21 100644 --- a/test/integration/utils.test.js +++ b/test/integration/utils.test.js @@ -91,7 +91,7 @@ describe(Support.getTestDialectTeaser('Utils'), () => { if (Support.getTestDialect() === 'postgres') { describe('json', () => { beforeEach(function() { - this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; + this.queryGenerator = this.sequelize.getQueryInterface().queryGenerator; }); it('successfully parses a complex nested condition hash', function() { @@ -138,33 +138,33 @@ describe(Support.getTestDialectTeaser('Utils'), () => { describe('Sequelize.fn', () => { let Airplane; - beforeEach(function() { + beforeEach(async function() { Airplane = this.sequelize.define('Airplane', { wings: DataTypes.INTEGER, engines: DataTypes.INTEGER }); - return Airplane.sync({ force: true }).then(() => { - return Airplane.bulkCreate([ - { - wings: 2, - engines: 0 - }, { - wings: 4, - engines: 1 - }, { - wings: 2, - engines: 2 - } - ]); - }); + await Airplane.sync({ force: true }); + + await Airplane.bulkCreate([ + { + wings: 2, + engines: 0 + }, { + wings: 4, + engines: 1 + }, { + wings: 2, + engines: 2 + } + ]); }); if (Support.getTestDialect() !== 'mssql') { - it('accepts condition object (with cast)', function() { + it('accepts condition object (with cast)', async function() { const type = Support.getTestDialect() === 'mysql' ? 'unsigned' : 'int'; - return Airplane.findAll({ + const [airplane] = await Airplane.findAll({ attributes: [ [this.sequelize.fn('COUNT', '*'), 'count'], [Sequelize.fn('SUM', Sequelize.cast({ @@ -179,18 +179,18 @@ describe(Support.getTestDialectTeaser('Utils'), () => { } }, type)), 'count-engines-wings'] ] - }).then(([airplane]) => { - // TODO: `parseInt` should not be needed, see #10533 - expect(parseInt(airplane.get('count'), 10)).to.equal(3); - expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); - expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); + + // TODO: `parseInt` should not be needed, see #10533 + expect(parseInt(airplane.get('count'), 10)).to.equal(3); + expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); + expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); } if (Support.getTestDialect() !== 'mssql' && Support.getTestDialect() !== 'postgres') { - it('accepts condition object (auto casting)', function() { - return Airplane.findAll({ + it('accepts condition object (auto casting)', async function() { + const [airplane] = await Airplane.findAll({ attributes: [ [this.sequelize.fn('COUNT', '*'), 'count'], [Sequelize.fn('SUM', { @@ -205,12 +205,12 @@ describe(Support.getTestDialectTeaser('Utils'), () => { } }), 'count-engines-wings'] ] - }).then(([airplane]) => { - // TODO: `parseInt` should not be needed, see #10533 - expect(airplane.get('count')).to.equal(3); - expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); - expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); + + // TODO: `parseInt` should not be needed, see #10533 + expect(airplane.get('count')).to.equal(3); + expect(parseInt(airplane.get('count-engines'), 10)).to.equal(1); + expect(parseInt(airplane.get('count-engines-wings'), 10)).to.equal(2); }); } }); diff --git a/test/integration/vectors.test.js b/test/integration/vectors.test.js index e1b7cfc7d0a0..e604d57a4267 100644 --- a/test/integration/vectors.test.js +++ b/test/integration/vectors.test.js @@ -8,22 +8,21 @@ const chai = require('chai'), chai.should(); describe(Support.getTestDialectTeaser('Vectors'), () => { - it('should not allow insert backslash', function() { + it('should not allow insert backslash', async function() { const Student = this.sequelize.define('student', { name: Sequelize.STRING }, { tableName: 'student' }); - return Student.sync({ force: true }).then(() => { - return Student.create({ - name: 'Robert\\\'); DROP TABLE "students"; --' - }).then(result => { - expect(result.get('name')).to.equal('Robert\\\'); DROP TABLE "students"; --'); - return Student.findAll(); - }).then(result => { - expect(result[0].name).to.equal('Robert\\\'); DROP TABLE "students"; --'); - }); + await Student.sync({ force: true }); + + const result0 = await Student.create({ + name: 'Robert\\\'); DROP TABLE "students"; --' }); + + expect(result0.get('name')).to.equal('Robert\\\'); DROP TABLE "students"; --'); + const result = await Student.findAll(); + expect(result[0].name).to.equal('Robert\\\'); DROP TABLE "students"; --'); }); }); diff --git a/test/support.js b/test/support.js index c3d9dcddbf7e..57b351a8d984 100644 --- a/test/support.js +++ b/test/support.js @@ -2,16 +2,14 @@ const fs = require('fs'); const path = require('path'); +const { isDeepStrictEqual } = require('util'); +const _ = require('lodash'); const Sequelize = require('../index'); const Config = require('./config/config'); const chai = require('chai'); const expect = chai.expect; const AbstractQueryGenerator = require('../lib/dialects/abstract/query-generator'); -const sinon = require('sinon'); -sinon.usingPromise(require('bluebird')); - -chai.use(require('chai-spies')); chai.use(require('chai-datetime')); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); @@ -23,106 +21,146 @@ process.on('uncaughtException', e => { console.error('An unhandled exception occurred:'); throw e; }); -Sequelize.Promise.onPossiblyUnhandledRejection(e => { + +let onNextUnhandledRejection = null; +let unhandledRejections = null; + +process.on('unhandledRejection', e => { + if (unhandledRejections) { + unhandledRejections.push(e); + } + const onNext = onNextUnhandledRejection; + if (onNext) { + onNextUnhandledRejection = null; + onNext(e); + } + if (onNext || unhandledRejections) return; console.error('An unhandled rejection occurred:'); throw e; }); -Sequelize.Promise.longStackTraces(); + +if (global.afterEach) { + afterEach(() => { + onNextUnhandledRejection = null; + unhandledRejections = null; + }); +} + +let lastSqliteInstance; const Support = { Sequelize, - prepareTransactionTest(sequelize) { + /** + * Returns a Promise that will reject with the next unhandled rejection that occurs + * during this test (instead of failing the test) + */ + nextUnhandledRejection() { + return new Promise((resolve, reject) => onNextUnhandledRejection = reject); + }, + + /** + * Pushes all unhandled rejections that occur during this test onto destArray + * (instead of failing the test). + * + * @param {Error[]} destArray the array to push unhandled rejections onto. If you omit this, + * one will be created and returned for you. + * + * @returns {Error[]} destArray + */ + captureUnhandledRejections(destArray = []) { + return unhandledRejections = destArray; + }, + + async prepareTransactionTest(sequelize) { const dialect = Support.getTestDialect(); if (dialect === 'sqlite') { const p = path.join(__dirname, 'tmp', 'db.sqlite'); + if (lastSqliteInstance) { + await lastSqliteInstance.close(); + } if (fs.existsSync(p)) { fs.unlinkSync(p); } - const options = Object.assign({}, sequelize.options, { storage: p }), + const options = { ...sequelize.options, storage: p }, _sequelize = new Sequelize(sequelize.config.database, null, null, options); - return _sequelize.sync({ force: true }).return(_sequelize); + await _sequelize.sync({ force: true }); + lastSqliteInstance = _sequelize; + return _sequelize; } - return Sequelize.Promise.resolve(sequelize); + return sequelize; }, - createSequelizeInstance(options = {}) { + createSequelizeInstance(options) { + options = options || {}; options.dialect = this.getTestDialect(); const config = Config[options.dialect]; - const sequelizeOptions = { + const sequelizeOptions = _.defaults(options, { host: options.host || config.host, logging: process.env.SEQ_LOG ? console.log : false, dialect: options.dialect, port: options.port || process.env.SEQ_PORT || config.port, pool: config.pool, dialectOptions: options.dialectOptions || config.dialectOptions || {}, - minifyAliases: options.minifyAliases || config.minifyAliases, - ...options - }; + minifyAliases: options.minifyAliases || config.minifyAliases + }); if (process.env.DIALECT === 'postgres-native') { sequelizeOptions.native = true; } - if (config.storage) { + if (config.storage || config.storage === '') { sequelizeOptions.storage = config.storage; } return this.getSequelizeInstance(config.database, config.username, config.password, sequelizeOptions); }, - getConnectionOptions() { - const config = Config[this.getTestDialect()]; - + getConnectionOptionsWithoutPool() { + // Do not break existing config object - shallow clone before `delete config.pool` + const config = { ...Config[this.getTestDialect()] }; delete config.pool; - return config; }, - getSequelizeInstance(db, user, pass, options = {}) { + getSequelizeInstance(db, user, pass, options) { + options = options || {}; options.dialect = options.dialect || this.getTestDialect(); return new Sequelize(db, user, pass, options); }, - clearDatabase(sequelize) { - return sequelize - .getQueryInterface() - .dropAllTables() - .then(() => { - sequelize.modelManager.models = []; - sequelize.models = {}; - - return sequelize - .getQueryInterface() - .dropAllEnums(); - }) - .then(() => { - return this.dropTestSchemas(sequelize); - }); - }, + async clearDatabase(sequelize) { + const qi = sequelize.getQueryInterface(); + await qi.dropAllTables(); + sequelize.modelManager.models = []; + sequelize.models = {}; - dropTestSchemas(sequelize) { + if (qi.dropAllEnums) { + await qi.dropAllEnums(); + } + await this.dropTestSchemas(sequelize); + }, + async dropTestSchemas(sequelize) { const queryInterface = sequelize.getQueryInterface(); - if (!queryInterface.QueryGenerator._dialect.supports.schemas) { + if (!queryInterface.queryGenerator._dialect.supports.schemas) { return this.sequelize.drop({}); } - return sequelize.showAllSchemas().then(schemas => { - const schemasPromise = []; - schemas.forEach(schema => { - const schemaName = schema.name ? schema.name : schema; - if (schemaName !== sequelize.config.database) { - schemasPromise.push(sequelize.dropSchema(schemaName)); - } - }); - return Promise.all(schemasPromise.map(p => p.catch(e => e))) - .then(() => {}, () => {}); + const schemas = await sequelize.showAllSchemas(); + const schemasPromise = []; + schemas.forEach(schema => { + const schemaName = schema.name ? schema.name : schema; + if (schemaName !== sequelize.config.database) { + schemasPromise.push(sequelize.dropSchema(schemaName)); + } }); + + await Promise.all(schemasPromise.map(p => p.catch(e => e))); }, getSupportedDialects() { @@ -169,6 +207,10 @@ const Support = { return `[${dialect.toUpperCase()}] ${moduleName}`; }, + getPoolMax() { + return Config[this.getTestDialect()].pool.max; + }, + expectsql(query, assertions) { const expectations = assertions.query || assertions; let expectation = expectations[Support.sequelize.dialect.name]; @@ -196,6 +238,14 @@ const Support = { const bind = assertions.bind[Support.sequelize.dialect.name] || assertions.bind['default'] || assertions.bind; expect(query.bind).to.deep.equal(bind); } + }, + + rand() { + return Math.floor(Math.random() * 10e5); + }, + + isDeepEqualToOneOf(actual, expectedOptions) { + return expectedOptions.some(expected => isDeepStrictEqual(actual, expected)); } }; diff --git a/scripts/teaser b/test/teaser.js similarity index 88% rename from scripts/teaser rename to test/teaser.js index b72e1f4126da..1dd3a22c72f9 100644 --- a/scripts/teaser +++ b/test/teaser.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +'use strict'; if (!process.env.DIALECT) { throw new Error('Environment variable DIALECT is undefined'); @@ -8,4 +9,4 @@ const DIALECT = process.env.DIALECT; const header = '#'.repeat(DIALECT.length + 22); const message = `${header}\n# Running tests for ${DIALECT} #\n${header}`; -console.log(message); \ No newline at end of file +console.log(message); diff --git a/test/tmp/.gitkeep b/test/tmp/.gitkeep index e69de29bb2d1..8b137891791f 100644 --- a/test/tmp/.gitkeep +++ b/test/tmp/.gitkeep @@ -0,0 +1 @@ + diff --git a/test/unit/associations/belongs-to-many.test.js b/test/unit/associations/belongs-to-many.test.js index 73f2e03f580e..c3b12ab6d3e3 100644 --- a/test/unit/associations/belongs-to-many.test.js +++ b/test/unit/associations/belongs-to-many.test.js @@ -90,7 +90,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { User.belongsToMany(Task, { through: 'UserTasks', as: 'task' }); - const user = new User(); + const user = User.build(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); @@ -158,13 +158,13 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { User.belongsToMany(Task, { through: UserTasks }); Task.belongsToMany(User, { through: UserTasks }); - const user = new User({ + const user = User.build({ id: 42 }), - task1 = new Task({ + task1 = Task.build({ id: 15 }), - task2 = new Task({ + task2 = Task.build({ id: 16 }); @@ -180,14 +180,13 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { this.destroy.restore(); }); - it('uses one insert into statement', function() { - return user.setTasks([task1, task2]).then(() => { - expect(this.findAll).to.have.been.calledOnce; - expect(this.bulkCreate).to.have.been.calledOnce; - }); + it('uses one insert into statement', async function() { + await user.setTasks([task1, task2]); + expect(this.findAll).to.have.been.calledOnce; + expect(this.bulkCreate).to.have.been.calledOnce; }); - it('uses one delete from statement', function() { + it('uses one delete from statement', async function() { this.findAll .onFirstCall().resolves([]) .onSecondCall().resolves([ @@ -195,12 +194,10 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { { userId: 42, taskId: 16 } ]); - return user.setTasks([task1, task2]).then(() => { - return user.setTasks(null); - }).then(() => { - expect(this.findAll).to.have.been.calledTwice; - expect(this.destroy).to.have.been.calledOnce; - }); + await user.setTasks([task1, task2]); + await user.setTasks(null); + expect(this.findAll).to.have.been.calledTwice; + expect(this.destroy).to.have.been.calledOnce; }); }); @@ -332,7 +329,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'productId', 'tagId']); }); - it('should setup hasOne relations to source and target from join model with defined foreign/other keys', function() { + it('should setup hasMany relations to source and target from join model with defined foreign/other keys', function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING }), @@ -406,6 +403,45 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'productId', 'tagId']); }); + it('should setup hasOne relations to source and target from join model with defined source keys', function() { + const Product = this.sequelize.define('Product', { + title: DataTypes.STRING, + productSecondaryId: DataTypes.STRING + }), + Tag = this.sequelize.define('Tag', { + name: DataTypes.STRING, + tagSecondaryId: DataTypes.STRING + }), + ProductTag = this.sequelize.define('ProductTag', { + id: { + primaryKey: true, + type: DataTypes.INTEGER, + autoIncrement: true + }, + priority: DataTypes.INTEGER + }, { + timestamps: false + }); + + Product.Tags = Product.belongsToMany(Tag, { through: ProductTag, sourceKey: 'productSecondaryId' }); + Tag.Products = Tag.belongsToMany(Product, { through: ProductTag, sourceKey: 'tagSecondaryId' }); + + expect(Product.Tags.oneFromSource).to.be.an.instanceOf(HasOne); + expect(Product.Tags.oneFromTarget).to.be.an.instanceOf(HasOne); + + expect(Tag.Products.oneFromSource).to.be.an.instanceOf(HasOne); + expect(Tag.Products.oneFromTarget).to.be.an.instanceOf(HasOne); + + expect(Tag.Products.oneFromSource.sourceKey).to.equal(Tag.Products.sourceKey); + expect(Tag.Products.oneFromTarget.sourceKey).to.equal(Tag.Products.targetKey); + + expect(Product.Tags.oneFromSource.sourceKey).to.equal(Product.Tags.sourceKey); + expect(Product.Tags.oneFromTarget.sourceKey).to.equal(Product.Tags.targetKey); + + expect(Object.keys(ProductTag.rawAttributes).length).to.equal(4); + expect(Object.keys(ProductTag.rawAttributes)).to.deep.equal(['id', 'priority', 'ProductProductSecondaryId', 'TagTagSecondaryId']); + }); + it('should setup belongsTo relations to source and target from join model with only foreign keys defined', function() { const Product = this.sequelize.define('Product', { title: DataTypes.STRING @@ -708,7 +744,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { describe('beforeBelongsToManyAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -726,7 +762,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -734,7 +770,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { describe('afterBelongsToManyAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -753,7 +789,7 @@ describe(Support.getTestDialectTeaser('belongsToMany'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.belongsToMany(this.Tasks, { through: 'projects_and_tasks', hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/associations/belongs-to.test.js b/test/unit/associations/belongs-to.test.js index 03ae834013d8..a431edfc0aa8 100644 --- a/test/unit/associations/belongs-to.test.js +++ b/test/unit/associations/belongs-to.test.js @@ -45,7 +45,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { User.belongsTo(Task, { as: 'task' }); - const user = new User(); + const user = User.build(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); @@ -59,7 +59,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { describe('beforeBelongsToAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.belongsTo(this.Tasks, { hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -77,7 +77,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.belongsTo(this.Tasks, { hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -85,7 +85,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { describe('afterBelongsToAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.belongsTo(this.Tasks, { hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -105,7 +105,7 @@ describe(Support.getTestDialectTeaser('belongsTo'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.belongsTo(this.Tasks, { hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/associations/has-many.test.js b/test/unit/associations/has-many.test.js index f77bf9a638cc..6cadab6f330f 100644 --- a/test/unit/associations/has-many.test.js +++ b/test/unit/associations/has-many.test.js @@ -6,7 +6,6 @@ const chai = require('chai'), stub = sinon.stub, _ = require('lodash'), Support = require('../support'), - Promise = Support.Sequelize.Promise, DataTypes = require('../../../lib/data-types'), HasMany = require('../../../lib/associations/has-many'), Op = require('../../../lib/operators'), @@ -27,13 +26,13 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { User.hasMany(Task); - const user = new User({ + const user = User.build({ id: 42 }), - task1 = new Task({ + task1 = Task.build({ id: 15 }), - task2 = new Task({ + task2 = Task.build({ id: 16 }); @@ -47,14 +46,13 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { this.update.restore(); }); - it('uses one update statement for addition', function() { - return user.setTasks([task1, task2]).then(() => { - expect(this.findAll).to.have.been.calledOnce; - expect(this.update).to.have.been.calledOnce; - }); + it('uses one update statement for addition', async function() { + await user.setTasks([task1, task2]); + expect(this.findAll).to.have.been.calledOnce; + expect(this.update).to.have.been.calledOnce; }); - it('uses one delete from statement', function() { + it('uses one delete from statement', async function() { this.findAll .onFirstCall().resolves([]) .onSecondCall().resolves([ @@ -62,13 +60,11 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { { userId: 42, taskId: 16 } ]); - return user.setTasks([task1, task2]).then(() => { - this.update.resetHistory(); - return user.setTasks(null); - }).then(() => { - expect(this.findAll).to.have.been.calledTwice; - expect(this.update).to.have.been.calledOnce; - }); + await user.setTasks([task1, task2]); + this.update.resetHistory(); + await user.setTasks(null); + expect(this.findAll).to.have.been.calledTwice; + expect(this.update).to.have.been.calledOnce; }); }); @@ -118,7 +114,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { User.hasMany(Task, { as: 'task' }); - const user = new User(); + const user = User.build(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); @@ -130,7 +126,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { Project.hasMany(Task); - const company = new Project(); + const company = Project.build(); expect(company.hasTasks).not.to.be.a('function'); }); @@ -144,14 +140,14 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { idC = Math.random().toString(), foreignKey = 'user_id'; - it('should fetch associations for a single instance', () => { + it('should fetch associations for a single instance', async () => { const findAll = stub(Task, 'findAll').resolves([ - new Task({}), - new Task({}) + Task.build({}), + Task.build({}) ]); User.Tasks = User.hasMany(Task, { foreignKey }); - const actual = User.Tasks.get(new User({ id: idA })); + const actual = User.Tasks.get(User.build({ id: idA })); const where = { [foreignKey]: idA @@ -160,36 +156,37 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { expect(findAll).to.have.been.calledOnce; expect(findAll.firstCall.args[0].where).to.deep.equal(where); - return actual.then(results => { + try { + const results = await actual; expect(results).to.be.an('array'); expect(results.length).to.equal(2); - }).finally(() => { + } finally { findAll.restore(); - }); + } }); - it('should fetch associations for multiple source instances', () => { + it('should fetch associations for multiple source instances', async () => { const findAll = stub(Task, 'findAll').returns( Promise.resolve([ - new Task({ + Task.build({ 'user_id': idA }), - new Task({ + Task.build({ 'user_id': idA }), - new Task({ + Task.build({ 'user_id': idA }), - new Task({ + Task.build({ 'user_id': idB }) ])); User.Tasks = User.hasMany(Task, { foreignKey }); const actual = User.Tasks.get([ - new User({ id: idA }), - new User({ id: idB }), - new User({ id: idC }) + User.build({ id: idA }), + User.build({ id: idB }), + User.build({ id: idC }) ]); expect(findAll).to.have.been.calledOnce; @@ -197,16 +194,17 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { expect(findAll.firstCall.args[0].where[foreignKey]).to.have.property(Op.in); expect(findAll.firstCall.args[0].where[foreignKey][Op.in]).to.deep.equal([idA, idB, idC]); - return actual.then(result => { + try { + const result = await actual; expect(result).to.be.an('object'); expect(Object.keys(result)).to.deep.equal([idA, idB, idC]); expect(result[idA].length).to.equal(3); expect(result[idB].length).to.equal(1); expect(result[idC].length).to.equal(0); - }).finally(() => { + } finally { findAll.restore(); - }); + } }); }); describe('association hooks', () => { @@ -217,7 +215,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { describe('beforeHasManyAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.hasMany(this.Tasks, { hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -234,7 +232,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.hasMany(this.Tasks, { hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -242,7 +240,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { describe('afterHasManyAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.hasMany(this.Tasks, { hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -261,7 +259,7 @@ describe(Support.getTestDialectTeaser('hasMany'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.hasMany(this.Tasks, { hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/associations/has-one.test.js b/test/unit/associations/has-one.test.js index 4ae092484a6f..de3f11c5ed62 100644 --- a/test/unit/associations/has-one.test.js +++ b/test/unit/associations/has-one.test.js @@ -56,7 +56,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { User.hasOne(Task, { as: 'task' }); - const user = new User(); + const user = User.build(); _.each(methods, (alias, method) => { expect(user[method]()).to.be.a('function'); @@ -70,7 +70,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { describe('beforeHasOneAssociate', () => { it('should trigger', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.hasOne(this.Tasks, { hooks: true }); const beforeAssociateArgs = beforeAssociate.getCall(0).args; @@ -88,7 +88,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { }); it('should not trigger association hooks', function() { const beforeAssociate = sinon.spy(); - this.Projects.hooks.add('beforeAssociate', beforeAssociate); + this.Projects.beforeAssociate(beforeAssociate); this.Projects.hasOne(this.Tasks, { hooks: false }); expect(beforeAssociate).to.not.have.been.called; }); @@ -96,7 +96,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { describe('afterHasOneAssociate', () => { it('should trigger', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.hasOne(this.Tasks, { hooks: true }); const afterAssociateArgs = afterAssociate.getCall(0).args; @@ -116,7 +116,7 @@ describe(Support.getTestDialectTeaser('hasOne'), () => { }); it('should not trigger association hooks', function() { const afterAssociate = sinon.spy(); - this.Projects.hooks.add('afterAssociate', afterAssociate); + this.Projects.afterAssociate(afterAssociate); this.Projects.hasOne(this.Tasks, { hooks: false }); expect(afterAssociate).to.not.have.been.called; }); diff --git a/test/unit/configuration.test.js b/test/unit/configuration.test.js index fbe858f521d3..0cade77cc358 100644 --- a/test/unit/configuration.test.js +++ b/test/unit/configuration.test.js @@ -179,5 +179,18 @@ describe('Sequelize', () => { expect(dialectOptions.application_name).to.equal('client'); expect(dialectOptions.ssl).to.equal('true'); }); + + it('should handle JSON options', () => { + const sequelizeWithOptions = new Sequelize('mysql://example.com:9821/dbname?options={"encrypt":true}&anotherOption=1'); + expect(sequelizeWithOptions.options.dialectOptions.options.encrypt).to.be.true; + expect(sequelizeWithOptions.options.dialectOptions.anotherOption).to.equal('1'); + }); + + it('should use query string host if specified', () => { + const sequelize = new Sequelize('mysql://localhost:9821/dbname?host=example.com'); + + const options = sequelize.options; + expect(options.host).to.equal('example.com'); + }); }); }); diff --git a/test/unit/connection-manager.test.js b/test/unit/connection-manager.test.js index 137bf2c4759f..0b55ae53a6e4 100644 --- a/test/unit/connection-manager.test.js +++ b/test/unit/connection-manager.test.js @@ -20,7 +20,7 @@ describe('connection manager', () => { this.sequelize = Support.createSequelizeInstance(); }); - it('should resolve connection on dialect connection manager', function() { + it('should resolve connection on dialect connection manager', async function() { const connection = {}; this.dialect.connectionManager.connect.resolves(connection); @@ -28,16 +28,15 @@ describe('connection manager', () => { const config = {}; - return expect(connectionManager._connect(config)).to.eventually.equal(connection).then(() => { - expect(this.dialect.connectionManager.connect).to.have.been.calledWith(config); - }); + await expect(connectionManager._connect(config)).to.eventually.equal(connection); + expect(this.dialect.connectionManager.connect).to.have.been.calledWith(config); }); - it('should let beforeConnect hook modify config', function() { + it('should let beforeConnect hook modify config', async function() { const username = Math.random().toString(), password = Math.random().toString(); - this.sequelize.hooks.add('beforeConnect', config => { + this.sequelize.beforeConnect(config => { config.username = username; config.password = password; return config; @@ -45,25 +44,23 @@ describe('connection manager', () => { const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._connect({}).then(() => { - expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ - username, - password - }); + await connectionManager._connect({}); + expect(this.dialect.connectionManager.connect).to.have.been.calledWith({ + username, + password }); }); - it('should call afterConnect', function() { + it('should call afterConnect', async function() { const spy = sinon.spy(); - this.sequelize.hooks.add('afterConnect', spy); + this.sequelize.afterConnect(spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._connect({}).then(() => { - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - expect(spy.firstCall.args[1]).to.eql({}); - }); + await connectionManager._connect({}); + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(this.connection); + expect(spy.firstCall.args[1]).to.eql({}); }); }); @@ -80,28 +77,26 @@ describe('connection manager', () => { this.sequelize = Support.createSequelizeInstance(); }); - it('should call beforeDisconnect', function() { + it('should call beforeDisconnect', async function() { const spy = sinon.spy(); - this.sequelize.hooks.add('beforeDisconnect', spy); + this.sequelize.beforeDisconnect(spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._disconnect(this.connection).then(() => { - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - }); + await connectionManager._disconnect(this.connection); + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(this.connection); }); - it('should call afterDisconnect', function() { + it('should call afterDisconnect', async function() { const spy = sinon.spy(); - this.sequelize.hooks.add('afterDisconnect', spy); + this.sequelize.afterDisconnect(spy); const connectionManager = new ConnectionManager(this.dialect, this.sequelize); - return connectionManager._disconnect(this.connection).then(() => { - expect(spy.callCount).to.equal(1); - expect(spy.firstCall.args[0]).to.equal(this.connection); - }); + await connectionManager._disconnect(this.connection); + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.equal(this.connection); }); }); }); diff --git a/test/unit/dialects/abstract/query-generator.test.js b/test/unit/dialects/abstract/query-generator.test.js index 30469516d9fc..6a216cec5af5 100644 --- a/test/unit/dialects/abstract/query-generator.test.js +++ b/test/unit/dialects/abstract/query-generator.test.js @@ -41,6 +41,69 @@ describe('QueryGenerator', () => { expect(() => QG.whereItemQuery('test', { $in: [4] })) .to.throw('Invalid value { \'$in\': [ 4 ] }'); + + // simulate transaction passed into where query argument + class Sequelize { + constructor() { + this.config = { + password: 'password' + }; + } + } + + class Transaction { + constructor() { + this.sequelize = new Sequelize(); + } + } + + expect(() => QG.whereItemQuery('test', new Transaction())).to.throw( + 'Invalid value Transaction { sequelize: Sequelize { config: [Object] } }' + ); + }); + + it('should parse set aliases strings as operators', function() { + const QG = getAbstractQueryGenerator(this.sequelize), + aliases = { + OR: Op.or, + '!': Op.not, + '^^': Op.gt + }; + + QG.setOperatorsAliases(aliases); + + QG.whereItemQuery('OR', [{ test: { '^^': 5 } }, { test: { '!': 3 } }, { test: { [Op.in]: [4] } }]) + .should.be.equal('(test > 5 OR test != 3 OR test IN (4))'); + + QG.whereItemQuery(Op.and, [{ test: { [Op.between]: [2, 5] } }, { test: { '!': 3 } }, { test: { '^^': 4 } }]) + .should.be.equal('(test BETWEEN 2 AND 5 AND test != 3 AND test > 4)'); + + expect(() => QG.whereItemQuery('OR', [{ test: { '^^': 5 } }, { test: { $not: 3 } }, { test: { [Op.in]: [4] } }])) + .to.throw('Invalid value { \'$not\': 3 }'); + + expect(() => QG.whereItemQuery('OR', [{ test: { $gt: 5 } }, { test: { '!': 3 } }, { test: { [Op.in]: [4] } }])) + .to.throw('Invalid value { \'$gt\': 5 }'); + + expect(() => QG.whereItemQuery('$or', [{ test: 5 }, { test: 3 }])) + .to.throw('Invalid value { test: 5 }'); + + expect(() => QG.whereItemQuery('$and', [{ test: 5 }, { test: 3 }])) + .to.throw('Invalid value { test: 5 }'); + + expect(() => QG.whereItemQuery('test', { $gt: 5 })) + .to.throw('Invalid value { \'$gt\': 5 }'); + + expect(() => QG.whereItemQuery('test', { $between: [2, 5] })) + .to.throw('Invalid value { \'$between\': [ 2, 5 ] }'); + + expect(() => QG.whereItemQuery('test', { $ne: 3 })) + .to.throw('Invalid value { \'$ne\': 3 }'); + + expect(() => QG.whereItemQuery('test', { $not: 3 })) + .to.throw('Invalid value { \'$not\': 3 }'); + + expect(() => QG.whereItemQuery('test', { $in: [4] })) + .to.throw('Invalid value { \'$in\': [ 4 ] }'); }); it('should correctly parse sequelize.where with .fn as logic', function() { @@ -54,6 +117,12 @@ describe('QueryGenerator', () => { QG.handleSequelizeMethod(this.sequelize.where(this.sequelize.col('foo'), Op.not, null)) .should.be.equal('foo IS NOT NULL'); }); + + it('should correctly escape $ in sequelize.fn arguments', function() { + const QG = getAbstractQueryGenerator(this.sequelize); + QG.handleSequelizeMethod(this.sequelize.fn('upper', '$user')) + .should.include('$$user'); + }); }); describe('format', () => { @@ -64,4 +133,3 @@ describe('QueryGenerator', () => { }); }); }); - diff --git a/test/unit/dialects/mariadb/query-generator.test.js b/test/unit/dialects/mariadb/query-generator.test.js index 8823f303202f..b320c10d5d38 100644 --- a/test/unit/dialects/mariadb/query-generator.test.js +++ b/test/unit/dialects/mariadb/query-generator.test.js @@ -6,6 +6,7 @@ const chai = require('chai'), dialect = Support.getTestDialect(), _ = require('lodash'), Op = require('../../../../lib/operators'), + IndexHints = require('../../../../lib/index-hints'), QueryGenerator = require('../../../../lib/dialects/mariadb/query-generator'); if (dialect === 'mariadb') { @@ -70,38 +71,38 @@ if (dialect === 'mariadb') { }, { arguments: [{ skip: ['test'] }], - expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\',\'test\');' + expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'test\');' }, { arguments: [{ skip: ['test', 'Te\'st2'] }], - expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\',\'test\',\'Te\\\'st2\');' + expectation: 'SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\', \'test\', \'Te\\\'st2\');' } ], arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\' WHERE `bar` = \'biz\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' } ], @@ -764,6 +765,53 @@ if (dialect === 'mariadb') { arguments: ['User', 'email'], expectation: "SELECT CONSTRAINT_NAME as constraint_name,CONSTRAINT_NAME as constraintName,CONSTRAINT_SCHEMA as constraintSchema,CONSTRAINT_SCHEMA as constraintCatalog,TABLE_NAME as tableName,TABLE_SCHEMA as tableSchema,TABLE_SCHEMA as tableCatalog,COLUMN_NAME as columnName,REFERENCED_TABLE_SCHEMA as referencedTableSchema,REFERENCED_TABLE_SCHEMA as referencedTableCatalog,REFERENCED_TABLE_NAME as referencedTableName,REFERENCED_COLUMN_NAME as referencedColumnName FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE (REFERENCED_TABLE_NAME = 'User' AND REFERENCED_COLUMN_NAME = 'email') OR (TABLE_NAME = 'User' AND COLUMN_NAME = 'email' AND REFERENCED_TABLE_NAME IS NOT NULL)" } + ], + + selectFromTableFragment: [ + { + arguments: [{}, null, ['*'], '`Project`'], + expectation: 'SELECT * FROM `Project`' + }, { + arguments: [ + { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name'] }] }, + null, + ['*'], + '`Project`' + ], + expectation: 'SELECT * FROM `Project` USE INDEX (`index_project_on_name`)' + }, { + arguments: [ + { indexHints: [{ type: IndexHints.FORCE, values: ['index_project_on_name'] }] }, + null, + ['*'], + '`Project`' + ], + expectation: 'SELECT * FROM `Project` FORCE INDEX (`index_project_on_name`)' + }, { + arguments: [ + { indexHints: [{ type: IndexHints.IGNORE, values: ['index_project_on_name'] }] }, + null, + ['*'], + '`Project`' + ], + expectation: 'SELECT * FROM `Project` IGNORE INDEX (`index_project_on_name`)' + }, { + arguments: [ + { indexHints: [{ type: IndexHints.USE, values: ['index_project_on_name', 'index_project_on_name_and_foo'] }] }, + null, + ['*'], + '`Project`' + ], + expectation: 'SELECT * FROM `Project` USE INDEX (`index_project_on_name`,`index_project_on_name_and_foo`)' + }, { + arguments: [ + { indexHints: [{ type: 'FOO', values: ['index_project_on_name'] }] }, + null, + ['*'], + '`Project`' + ], + expectation: 'SELECT * FROM `Project`' + } ] }; @@ -786,7 +834,7 @@ if (dialect === 'mariadb') { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); diff --git a/test/unit/dialects/mssql/connection-manager.test.js b/test/unit/dialects/mssql/connection-manager.test.js index bad513bfceb4..f9ec050586a9 100644 --- a/test/unit/dialects/mssql/connection-manager.test.js +++ b/test/unit/dialects/mssql/connection-manager.test.js @@ -5,7 +5,6 @@ const chai = require('chai'), Sequelize = require('../../../../index'), Support = require('../../support'), dialect = Support.getTestDialect(), - tedious = require('tedious'), sinon = require('sinon'); if (dialect === 'mssql') { @@ -29,16 +28,23 @@ if (dialect === 'mssql') { this.config.password, this.config ); - - this.connectionStub = sinon.stub(tedious, 'Connection'); + this.Connection = {}; + const self = this; + this.connectionStub = sinon.stub(this.instance.connectionManager, 'lib').value({ + Connection: function FakeConnection() { + return self.Connection; + } + }); }); afterEach(function() { this.connectionStub.restore(); }); - it('connectionManager._connect() does not delete `domain` from config.dialectOptions', function() { - this.connectionStub.returns({ + it('connectionManager._connect() does not delete `domain` from config.dialectOptions', async function() { + this.Connection = { + STATE: {}, + state: '', once(event, cb) { if (event === 'connect') { setTimeout(() => { @@ -48,16 +54,17 @@ if (dialect === 'mssql') { }, removeListener: () => {}, on: () => {} - }); + }; expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); - return this.instance.dialect.connectionManager._connect(this.config).then(() => { - expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); - }); + await this.instance.dialect.connectionManager._connect(this.config); + expect(this.config.dialectOptions.domain).to.equal('TEST.COM'); }); - it('connectionManager._connect() should reject if end was called and connect was not', function() { - this.connectionStub.returns({ + it('connectionManager._connect() should reject if end was called and connect was not', async function() { + this.Connection = { + STATE: {}, + state: '', once(event, cb) { if (event === 'end') { setTimeout(() => { @@ -67,13 +74,36 @@ if (dialect === 'mssql') { }, removeListener: () => {}, on: () => {} - }); + }; + + try { + await this.instance.dialect.connectionManager._connect(this.config); + } catch (err) { + expect(err.name).to.equal('SequelizeConnectionError'); + expect(err.parent.message).to.equal('Connection was closed by remote server'); + } + }); + + it('connectionManager._connect() should call connect if state is initialized', async function() { + const connectStub = sinon.stub(); + const INITIALIZED = { name: 'INITIALIZED' }; + this.Connection = { + STATE: { INITIALIZED }, + state: INITIALIZED, + connect: connectStub, + once(event, cb) { + if (event === 'connect') { + setTimeout(() => { + cb(); + }, 500); + } + }, + removeListener: () => {}, + on: () => {} + }; - return this.instance.dialect.connectionManager._connect(this.config) - .catch(err => { - expect(err.name).to.equal('SequelizeConnectionError'); - expect(err.parent.message).to.equal('Connection was closed by remote server'); - }); + await this.instance.dialect.connectionManager._connect(this.config); + expect(connectStub.called).to.equal(true); }); }); } diff --git a/test/unit/dialects/mssql/query-generator.test.js b/test/unit/dialects/mssql/query-generator.test.js index bcb2f277e4d1..71fb0b11ddf4 100644 --- a/test/unit/dialects/mssql/query-generator.test.js +++ b/test/unit/dialects/mssql/query-generator.test.js @@ -3,6 +3,8 @@ const Support = require('../../support'); const expectsql = Support.expectsql; const current = Support.sequelize; +const DataTypes = require('../../../../lib/data-types'); +const Op = require('../../../../lib/operators'); const TableHints = require('../../../../lib/table-hints'); const QueryGenerator = require('../../../../lib/dialects/mssql/query-generator'); @@ -21,6 +23,56 @@ if (current.dialect.name === 'mssql') { }); }); + it('upsertQuery with falsey values', function() { + const testTable = this.sequelize.define( + 'test_table', + { + Name: { + type: DataTypes.STRING, + primaryKey: true + }, + Age: { + type: DataTypes.INTEGER + }, + IsOnline: { + type: DataTypes.BOOLEAN, + primaryKey: true + } + }, + { + freezeTableName: true, + timestamps: false + } + ); + + const insertValues = { + Name: 'Charlie', + Age: 24, + IsOnline: false + }; + + const updateValues = { + Age: 24 + }; + + const whereValues = [ + { + Name: 'Charlie', + IsOnline: false + } + ]; + + const where = { + [Op.or]: whereValues + }; + + // the main purpose of this test is to validate this does not throw + expectsql(this.queryGenerator.upsertQuery('test_table', updateValues, insertValues, where, testTable), { + mssql: + "MERGE INTO [test_table] WITH(HOLDLOCK) AS [test_table_target] USING (VALUES(24)) AS [test_table_source]([Age]) ON [test_table_target].[Name] = [test_table_source].[Name] AND [test_table_target].[IsOnline] = [test_table_source].[IsOnline] WHEN MATCHED THEN UPDATE SET [test_table_target].[Name] = N'Charlie', [test_table_target].[Age] = 24, [test_table_target].[IsOnline] = 0 WHEN NOT MATCHED THEN INSERT ([Age]) VALUES(24) OUTPUT $action, INSERTED.*;" + }); + }); + it('createDatabaseQuery with collate', function() { expectsql(this.queryGenerator.createDatabaseQuery('myDatabase', { collate: 'Latin1_General_CS_AS_KS_WS' }), { mssql: "IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'myDatabase' ) BEGIN CREATE DATABASE [myDatabase] COLLATE N'Latin1_General_CS_AS_KS_WS'; END;" @@ -136,12 +188,48 @@ if (current.dialect.name === 'mssql') { // With offset expectsql(modifiedGen.selectFromTableFragment({ offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' + mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' }); // With both limit and offset expectsql(modifiedGen.selectFromTableFragment({ limit: 10, offset: 10 }, { primaryKeyField: 'id' }, ['id', 'name'], 'myTable', 'myOtherName'), { - mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' + mssql: 'SELECT TOP 100 PERCENT id, name FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, * FROM myTable AS myOtherName) AS myOtherName WHERE row_num > 10) AS myOtherName' + }); + + // With limit, offset, include, and where + const foo = this.sequelize.define('Foo', { + id: { + type: DataTypes.INTEGER, + field: 'id', + primaryKey: true + } + }, { + tableName: 'Foos' + }); + const bar = this.sequelize.define('Bar', { + id: { + type: DataTypes.INTEGER, + field: 'id', + primaryKey: true + } + }, { + tableName: 'Bars' + }); + foo.Bar = foo.belongsTo(bar, { foreignKey: 'barId' }); + let options = { limit: 10, offset: 10, + include: [ + { + model: bar, + association: foo.Bar, + as: 'Bars', + required: true + } + ] + }; + foo._conformIncludes(options); + options = foo._validateIncludedElements(options); + expectsql(modifiedGen.selectFromTableFragment(options, foo, ['[Foo].[id]', '[Foo].[barId]'], foo.tableName, 'Foo', '[Bar].[id] = 12'), { + mssql: 'SELECT TOP 100 PERCENT [Foo].[id], [Foo].[barId] FROM (SELECT TOP 10 * FROM (SELECT ROW_NUMBER() OVER (ORDER BY [id]) as row_num, Foo.* FROM (SELECT DISTINCT Foo.* FROM Foos AS Foo INNER JOIN [Bars] AS [Bar] ON [Foo].[barId] = [Bar].[id] WHERE [Bar].[id] = 12) AS Foo) AS Foo WHERE row_num > 10) AS Foo' }); }); @@ -260,37 +348,37 @@ if (current.dialect.name === 'mssql') { [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' OUTPUT INSERTED.*' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\' OUTPUT INSERTED.* WHERE [bar] = N\'biz\'' }, { title: 'Should use the plus operator without returning clause', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE [myTable] SET [foo]=[foo]+ N\'bar\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' OUTPUT INSERTED.*' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]- -1 OUTPUT INSERTED.*' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\' OUTPUT INSERTED.* WHERE [bar] = N\'biz\'' }, { title: 'Should use the minus operator without returning clause', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE [myTable] SET [foo]=[foo]- N\'bar\'' } ].forEach(test => { diff --git a/test/unit/dialects/mssql/query.test.js b/test/unit/dialects/mssql/query.test.js index ef151977fb05..5487f5057458 100644 --- a/test/unit/dialects/mssql/query.test.js +++ b/test/unit/dialects/mssql/query.test.js @@ -15,29 +15,27 @@ let sandbox, query; if (dialect === 'mssql') { describe('[MSSQL Specific] Query', () => { - describe('beginTransaction', () => { - beforeEach(() => { - sandbox = sinon.createSandbox(); - const options = { - transaction: { name: 'transactionName' }, - isolationLevel: 'REPEATABLE_READ', - logging: false - }; - sandbox.stub(connectionStub, 'beginTransaction').callsArg(0); - query = new Query(connectionStub, sequelize, options); - }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + const options = { + transaction: { name: 'transactionName' }, + isolationLevel: 'REPEATABLE_READ', + logging: false + }; + sandbox.stub(connectionStub, 'beginTransaction').callsArg(0); + query = new Query(connectionStub, sequelize, options); + }); - it('should call beginTransaction with correct arguments', () => { - return query._run(connectionStub, 'BEGIN TRANSACTION') - .then(() => { - expect(connectionStub.beginTransaction.called).to.equal(true); - expect(connectionStub.beginTransaction.args[0][1]).to.equal('transactionName'); - expect(connectionStub.beginTransaction.args[0][2]).to.equal(tediousIsolationLevel.REPEATABLE_READ); - }); - }); + afterEach(() => { + sandbox.restore(); + }); - afterEach(() => { - sandbox.restore(); + describe('beginTransaction', () => { + it('should call beginTransaction with correct arguments', async () => { + await query._run(connectionStub, 'BEGIN TRANSACTION'); + expect(connectionStub.beginTransaction.called).to.equal(true); + expect(connectionStub.beginTransaction.args[0][1]).to.equal('transactionName'); + expect(connectionStub.beginTransaction.args[0][2]).to.equal(tediousIsolationLevel.REPEATABLE_READ); }); }); @@ -64,5 +62,25 @@ if (dialect === 'mssql') { expect(result[0]).to.equal(expected); }); }); + + describe('getSQLTypeFromJsType', () => { + const TYPES = tedious.TYPES; + it('should return correct parameter type', () => { + expect(query.getSQLTypeFromJsType(2147483647, TYPES)).to.eql({ type: TYPES.Int, typeOptions: {} }); + expect(query.getSQLTypeFromJsType(-2147483648, TYPES)).to.eql({ type: TYPES.Int, typeOptions: {} }); + + expect(query.getSQLTypeFromJsType(2147483648, TYPES)).to.eql({ type: TYPES.BigInt, typeOptions: {} }); + expect(query.getSQLTypeFromJsType(-2147483649, TYPES)).to.eql({ type: TYPES.BigInt, typeOptions: {} }); + + expect(query.getSQLTypeFromJsType(Buffer.from('abc'), TYPES)).to.eql({ type: TYPES.VarBinary, typeOptions: {} }); + }); + + it('should return parameter type correct scale for float', () => { + expect(query.getSQLTypeFromJsType(1.23, TYPES)).to.eql({ type: TYPES.Numeric, typeOptions: { precision: 30, scale: 2 } }); + expect(query.getSQLTypeFromJsType(0.30000000000000004, TYPES)).to.eql({ type: TYPES.Numeric, typeOptions: { precision: 30, scale: 17 } }); + expect(query.getSQLTypeFromJsType(2.5e-15, TYPES)).to.eql({ type: TYPES.Numeric, typeOptions: { precision: 30, scale: 16 } }); + }); + }); + }); } diff --git a/test/unit/dialects/mssql/resource-lock.test.js b/test/unit/dialects/mssql/resource-lock.test.js deleted file mode 100644 index bad1f185a531..000000000000 --- a/test/unit/dialects/mssql/resource-lock.test.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -const ResourceLock = require('../../../../lib/dialects/mssql/resource-lock'), - Promise = require('../../../../lib/promise'), - assert = require('assert'), - Support = require('../../support'), - dialect = Support.getTestDialect(); - -if (dialect === 'mssql') { - describe('[MSSQL Specific] ResourceLock', () => { - it('should process requests serially', () => { - const expected = {}; - const lock = new ResourceLock(expected); - let last = 0; - - function validateResource(actual) { - assert.equal(actual, expected); - } - - return Promise.all([ - Promise.using(lock.lock(), resource => { - validateResource(resource); - assert.equal(last, 0); - last = 1; - - return Promise.delay(15); - }), - Promise.using(lock.lock(), resource => { - validateResource(resource); - assert.equal(last, 1); - last = 2; - }), - Promise.using(lock.lock(), resource => { - validateResource(resource); - assert.equal(last, 2); - last = 3; - - return Promise.delay(5); - }) - ]); - }); - - it('should still return resource after failure', () => { - const expected = {}; - const lock = new ResourceLock(expected); - - function validateResource(actual) { - assert.equal(actual, expected); - } - - return Promise.all([ - Promise.using(lock.lock(), resource => { - validateResource(resource); - - throw new Error('unexpected error'); - }).catch(() => {}), - Promise.using(lock.lock(), validateResource) - ]); - }); - - it('should be able to.lock resource without waiting on lock', () => { - const expected = {}; - const lock = new ResourceLock(expected); - - assert.equal(lock.unwrap(), expected); - }); - }); -} diff --git a/test/unit/dialects/mysql/query-generator.test.js b/test/unit/dialects/mysql/query-generator.test.js index 51d3d8119851..fb57dd7e895b 100644 --- a/test/unit/dialects/mysql/query-generator.test.js +++ b/test/unit/dialects/mysql/query-generator.test.js @@ -39,27 +39,27 @@ if (dialect === 'mysql') { arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\' WHERE `bar` = \'biz\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' } ], @@ -785,7 +785,7 @@ if (dialect === 'mysql') { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); diff --git a/test/unit/dialects/mysql/query.test.js b/test/unit/dialects/mysql/query.test.js index 12d9c247ccc2..2f0465c55541 100644 --- a/test/unit/dialects/mysql/query.test.js +++ b/test/unit/dialects/mysql/query.test.js @@ -19,7 +19,7 @@ describe('[MYSQL/MARIADB Specific] Query', () => { console.log.restore(); }); - it('check iterable', () => { + it('check iterable', async () => { const validWarning = []; const invalidWarning = {}; const warnings = [validWarning, undefined, invalidWarning]; @@ -28,10 +28,9 @@ describe('[MYSQL/MARIADB Specific] Query', () => { const stub = sinon.stub(query, 'run'); stub.onFirstCall().resolves(warnings); - return query.logWarnings('dummy-results').then(results => { - expect('dummy-results').to.equal(results); - expect(true).to.equal(console.log.calledOnce); - }); + const results = await query.logWarnings('dummy-results'); + expect('dummy-results').to.equal(results); + expect(true).to.equal(console.log.calledOnce); }); }); }); diff --git a/test/unit/dialects/postgres/query-generator.test.js b/test/unit/dialects/postgres/query-generator.test.js index d25043d5360d..e7b085d6db16 100644 --- a/test/unit/dialects/postgres/query-generator.test.js +++ b/test/unit/dialects/postgres/query-generator.test.js @@ -44,49 +44,52 @@ if (dialect.startsWith('postgres')) { expectation: 'CREATE DATABASE "myDatabase" ENCODING = \'UTF8\' LC_COLLATE = \'en_US.UTF-8\' LC_CTYPE = \'zh_TW.UTF-8\' TEMPLATE = \'template0\';' } ], + dropDatabaseQuery: [ { arguments: ['myDatabase'], expectation: 'DROP DATABASE IF EXISTS "myDatabase";' } ], + arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], + expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' RETURNING *' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\' WHERE "bar" = \'biz\' RETURNING *' }, { title: 'Should use the plus operator without returning clause', - arguments: ['+', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE "myTable" SET "foo"="foo"+ \'bar\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *' + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], + expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' RETURNING *' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }, {}, {}], - expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], + expectation: 'UPDATE "myTable" SET "foo"="foo"- -1 RETURNING *' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }, {}], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\' WHERE "bar" = \'biz\' RETURNING *' }, { title: 'Should use the minus operator without returning clause', - arguments: ['-', 'myTable', { foo: 'bar' }, {}, { returning: false }], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, { returning: false }], expectation: 'UPDATE "myTable" SET "foo"="foo"- \'bar\'' } ], + attributesToSQL: [ { arguments: [{ id: 'INTEGER' }], @@ -1254,7 +1257,7 @@ if (dialect.startsWith('postgres')) { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); @@ -1262,5 +1265,45 @@ if (dialect.startsWith('postgres')) { }); }); }); + + describe('fromArray()', () => { + beforeEach(function() { + this.queryGenerator = new QueryGenerator({ + sequelize: this.sequelize, + _dialect: this.sequelize.dialect + }); + }); + + const tests = [ + { + title: 'should convert an enum with no quoted strings to an array', + arguments: '{foo,bar,foobar}', + expectation: ['foo', 'bar', 'foobar'] + }, { + title: 'should convert an enum starting with a quoted string to an array', + arguments: '{"foo bar",foo,bar}', + expectation: ['foo bar', 'foo', 'bar'] + }, { + title: 'should convert an enum ending with a quoted string to an array', + arguments: '{foo,bar,"foo bar"}', + expectation: ['foo', 'bar', 'foo bar'] + }, { + title: 'should convert an enum with a quoted string in the middle to an array', + arguments: '{foo,"foo bar",bar}', + expectation: ['foo', 'foo bar', 'bar'] + }, { + title: 'should convert an enum full of quoted strings to an array', + arguments: '{"foo bar","foo bar","foo bar"}', + expectation: ['foo bar', 'foo bar', 'foo bar'] + } + ]; + + _.each(tests, test => { + it(test.title, function() { + const convertedText = this.queryGenerator.fromArray(test.arguments); + expect(convertedText).to.deep.equal(test.expectation); + }); + }); + }); }); } diff --git a/test/unit/dialects/sqlite/connection-manager.test.js b/test/unit/dialects/sqlite/connection-manager.test.js new file mode 100644 index 000000000000..793b20bdd17c --- /dev/null +++ b/test/unit/dialects/sqlite/connection-manager.test.js @@ -0,0 +1,31 @@ +'use strict'; + +const chai = require('chai'), + expect = chai.expect, + Support = require('../../support'), + Sequelize = Support.Sequelize, + dialect = Support.getTestDialect(), + sinon = require('sinon'); + +if (dialect === 'sqlite') { + describe('[SQLITE Specific] ConnectionManager', () => { + describe('getConnection', () => { + it('should forward empty string storage to SQLite connector to create temporary disk-based database', () => { + // storage='' means anonymous disk-based database + const sequelize = new Sequelize('', '', '', { dialect: 'sqlite', storage: '' }); + + sinon.stub(sequelize.connectionManager, 'lib').value({ + Database: function FakeDatabase(_s, _m, cb) { + cb(); + return {}; + } + }); + sinon.stub(sequelize.connectionManager, 'connections').value({ default: { run: () => {} } }); + + const options = {}; + sequelize.dialect.connectionManager.getConnection(options); + expect(options.storage).to.be.equal(''); + }); + }); + }); +} diff --git a/test/unit/dialects/sqlite/query-generator.test.js b/test/unit/dialects/sqlite/query-generator.test.js index 3ccfd481ae8a..3e5b66797063 100644 --- a/test/unit/dialects/sqlite/query-generator.test.js +++ b/test/unit/dialects/sqlite/query-generator.test.js @@ -23,27 +23,27 @@ if (dialect === 'sqlite') { arithmeticQuery: [ { title: 'Should use the plus operator', - arguments: ['+', 'myTable', { foo: 'bar' }, {}], + arguments: ['+', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\'' }, { title: 'Should use the plus operator with where clause', - arguments: ['+', 'myTable', { foo: 'bar' }, { bar: 'biz' }], + arguments: ['+', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`+ \'bar\' WHERE `bar` = \'biz\'' }, { title: 'Should use the minus operator', - arguments: ['-', 'myTable', { foo: 'bar' }], + arguments: ['-', 'myTable', {}, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\'' }, { title: 'Should use the minus operator with negative value', - arguments: ['-', 'myTable', { foo: -1 }], + arguments: ['-', 'myTable', {}, { foo: -1 }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- -1' }, { title: 'Should use the minus operator with where clause', - arguments: ['-', 'myTable', { foo: 'bar' }, { bar: 'biz' }], + arguments: ['-', 'myTable', { bar: 'biz' }, { foo: 'bar' }, {}, {}], expectation: 'UPDATE `myTable` SET `foo`=`foo`- \'bar\' WHERE `bar` = \'biz\'' } ], @@ -152,6 +152,10 @@ if (dialect === 'sqlite') { { arguments: ['myTable', { id: 'INTEGER PRIMARY KEY AUTOINCREMENT', name: 'VARCHAR(255)', surname: 'VARCHAR(255)' }, { uniqueKeys: { uniqueConstraint: { fields: ['name', 'surname'], customIndex: true } } }], expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255), `surname` VARCHAR(255), UNIQUE (`name`, `surname`));' + }, + { + arguments: ['myTable', { foo1: 'INTEGER PRIMARY KEY NOT NULL', foo2: 'INTEGER PRIMARY KEY NOT NULL' }], + expectation: 'CREATE TABLE IF NOT EXISTS `myTable` (`foo1` INTEGER NOT NULL, `foo2` INTEGER NOT NULL, PRIMARY KEY (`foo1`, `foo2`));' } ], @@ -603,7 +607,7 @@ if (dialect === 'sqlite') { title: 'Properly quotes column names', arguments: ['myTable', 'foo', 'commit', { commit: 'VARCHAR(255)', bar: 'VARCHAR(255)' }], expectation: - 'CREATE TEMPORARY TABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + + 'CREATE TABLE IF NOT EXISTS `myTable_backup` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + 'INSERT INTO `myTable_backup` SELECT `foo` AS `commit`, `bar` FROM `myTable`;' + 'DROP TABLE `myTable`;' + 'CREATE TABLE IF NOT EXISTS `myTable` (`commit` VARCHAR(255), `bar` VARCHAR(255));' + @@ -623,6 +627,13 @@ if (dialect === 'sqlite') { 'INSERT INTO `myTable` SELECT `commit`, `bar` FROM `myTable_backup`;' + 'DROP TABLE `myTable_backup`;' } + ], + getForeignKeysQuery: [ + { + title: 'Property quotes table names', + arguments: ['myTable'], + expectation: 'PRAGMA foreign_key_list(`myTable`)' + } ] }; @@ -645,7 +656,7 @@ if (dialect === 'sqlite') { } // Options would normally be set by the query interface that instantiates the query-generator, but here we specify it explicitly - this.queryGenerator.options = Object.assign({}, this.queryGenerator.options, test.context && test.context.options || {}); + this.queryGenerator.options = { ...this.queryGenerator.options, ...test.context && test.context.options }; const conditions = this.queryGenerator[suiteTitle](...test.arguments); expect(conditions).to.deep.equal(test.expectation); diff --git a/test/unit/errors.test.js b/test/unit/errors.test.js index 9e84edb3a54e..b8a7c1e12186 100644 --- a/test/unit/errors.test.js +++ b/test/unit/errors.test.js @@ -52,4 +52,31 @@ describe('errors', () => { expect(stackParts[1]).to.match(/^ {4}at throwError \(.*errors.test.js:\d+:\d+\)$/); }); }); + + describe('AggregateError', () => { + it('get .message works', () => { + const { AggregateError } = errors; + expect(String( + new AggregateError([ + new Error('foo'), + new Error('bar\nbaz'), + new AggregateError([ + new Error('this\nis\na\ntest'), + new Error('qux') + ]) + ]) + )).to.equal( + `AggregateError of: + Error: foo + Error: bar + baz + AggregateError of: + Error: this + is + a + test + Error: qux +`); + }); + }); }); diff --git a/test/unit/hooks.test.js b/test/unit/hooks.test.js index 0b62aacd71f6..92ee7f961ee5 100644 --- a/test/unit/hooks.test.js +++ b/test/unit/hooks.test.js @@ -3,8 +3,6 @@ const chai = require('chai'), sinon = require('sinon'), expect = chai.expect, - Sequelize = require('../../index'), - Promise = Sequelize.Promise, Support = require('./support'), _ = require('lodash'), current = Support.sequelize; @@ -15,21 +13,20 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('does not expose non-model hooks', function() { - for (const badHook of ['beforeDefine', 'afterDefine', 'beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect', 'beforeInit', 'afterInit', 'beforeBulkSync', 'afterBulkSync']) { - expect(() => this.Model.hooks.add(badHook, () => {})).to.throw(/is only applicable/); + for (const badHook of ['beforeDefine', 'afterDefine', 'beforeConnect', 'afterConnect', 'beforeDisconnect', 'afterDisconnect', 'beforeInit', 'afterInit']) { + expect(this.Model).to.not.have.property(badHook); } }); describe('arguments', () => { - it('hooks can modify passed arguments', function() { - this.Model.hooks.add('beforeCreate', options => { + it('hooks can modify passed arguments', async function() { + this.Model.addHook('beforeCreate', options => { options.answer = 41; }); const options = {}; - return this.Model.hooks.run('beforeCreate', options).then(() => { - expect(options.answer).to.equal(41); - }); + await this.Model.runHooks('beforeCreate', options); + expect(options.answer).to.equal(41); }); }); @@ -62,16 +59,15 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - it('calls beforeSave/afterSave', function() { - return this.Model.create({}).then(() => { - expect(this.afterCreateHook).to.have.been.calledOnce; - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); + it('calls beforeSave/afterSave', async function() { + await this.Model.create({}); + expect(this.afterCreateHook).to.have.been.calledOnce; + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; }); }); - describe('defined by hooks.add method', () => { + describe('defined by addHook method', () => { beforeEach(function() { this.beforeSaveHook = sinon.spy(); this.afterSaveHook = sinon.spy(); @@ -80,15 +76,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { name: Support.Sequelize.STRING }); - this.Model.hooks.add('beforeSave', this.beforeSaveHook); - this.Model.hooks.add('afterSave', this.afterSaveHook); + this.Model.addHook('beforeSave', this.beforeSaveHook); + this.Model.addHook('afterSave', this.afterSaveHook); }); - it('calls beforeSave/afterSave', function() { - return this.Model.create({}).then(() => { - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); + it('calls beforeSave/afterSave', async function() { + await this.Model.create({}); + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; }); }); @@ -101,15 +96,14 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { name: Support.Sequelize.STRING }); - this.Model.hooks.add('beforeSave', this.beforeSaveHook); - this.Model.hooks.add('afterSave', this.afterSaveHook); + this.Model.addHook('beforeSave', this.beforeSaveHook); + this.Model.addHook('afterSave', this.afterSaveHook); }); - it('calls beforeSave/afterSave', function() { - return this.Model.create({}).then(() => { - expect(this.beforeSaveHook).to.have.been.calledOnce; - expect(this.afterSaveHook).to.have.been.calledOnce; - }); + it('calls beforeSave/afterSave', async function() { + await this.Model.create({}); + expect(this.beforeSaveHook).to.have.been.calledOnce; + expect(this.afterSaveHook).to.have.been.calledOnce; }); }); }); @@ -128,83 +122,88 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { expect(this.hook3).to.have.been.calledOnce; }); - it('using hooks.add', function() { - this.Model.hooks.add('beforeCreate', this.hook1); - this.Model.hooks.add('beforeCreate', this.hook2); - this.Model.hooks.add('beforeCreate', this.hook3); + it('using addHook', async function() { + this.Model.addHook('beforeCreate', this.hook1); + this.Model.addHook('beforeCreate', this.hook2); + this.Model.addHook('beforeCreate', this.hook3); + + await this.Model.runHooks('beforeCreate'); + }); + + it('using function', async function() { + this.Model.beforeCreate(this.hook1); + this.Model.beforeCreate(this.hook2); + this.Model.beforeCreate(this.hook3); - return this.Model.hooks.run('beforeCreate'); + await this.Model.runHooks('beforeCreate'); }); - it('using define', function() { - return current.define('M', {}, { + it('using define', async function() { + await current.define('M', {}, { hooks: { beforeCreate: [this.hook1, this.hook2, this.hook3] } - }).hooks.run('beforeCreate'); + }).runHooks('beforeCreate'); }); - it('using a mixture', function() { + it('using a mixture', async function() { const Model = current.define('M', {}, { hooks: { beforeCreate: this.hook1 } }); - Model.hooks.add('beforeCreate', this.hook2); - Model.hooks.add('beforeCreate', this.hook3); + Model.beforeCreate(this.hook2); + Model.addHook('beforeCreate', this.hook3); - return Model.hooks.run('beforeCreate'); + await Model.runHooks('beforeCreate'); }); }); - it('stops execution when a hook throws', function() { - this.Model.hooks.add('beforeCreate', () => { + it('stops execution when a hook throws', async function() { + this.Model.beforeCreate(() => { this.hook1(); throw new Error('No!'); }); - this.Model.hooks.add('beforeCreate', this.hook2); + this.Model.beforeCreate(this.hook2); - return expect(this.Model.hooks.run('beforeCreate')).to.be.rejected.then(() => { - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).not.to.have.been.called; - }); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejected; + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).not.to.have.been.called; }); - it('stops execution when a hook rejects', function() { - this.Model.hooks.add('beforeCreate', () => { + it('stops execution when a hook rejects', async function() { + this.Model.beforeCreate(async () => { this.hook1(); - return Promise.reject(new Error('No!')); + throw new Error('No!'); }); - this.Model.hooks.add('beforeCreate', this.hook2); + this.Model.beforeCreate(this.hook2); - return expect(this.Model.hooks.run('beforeCreate')).to.be.rejected.then(() => { - expect(this.hook1).to.have.been.calledOnce; - expect(this.hook2).not.to.have.been.called; - }); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejected; + expect(this.hook1).to.have.been.calledOnce; + expect(this.hook2).not.to.have.been.called; }); }); describe('global hooks', () => { - describe('using hooks.add', () => { + describe('using addHook', () => { - it('invokes the global hook', function() { + it('invokes the global hook', async function() { const globalHook = sinon.spy(); - current.hooks.add('beforeUpdate', globalHook); + current.addHook('beforeUpdate', globalHook); - return this.Model.hooks.run('beforeUpdate').then(() => { - expect(globalHook).to.have.been.calledOnce; - }); + await this.Model.runHooks('beforeUpdate'); + expect(globalHook).to.have.been.calledOnce; }); - it('invokes the global hook, when the model also has a hook', () => { + it('invokes the global hook, when the model also has a hook', async () => { const globalHookBefore = sinon.spy(), globalHookAfter = sinon.spy(), localHook = sinon.spy(); - current.hooks.add('beforeUpdate', globalHookBefore); + current.addHook('beforeUpdate', globalHookBefore); const Model = current.define('m', {}, { hooks: { @@ -212,16 +211,15 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - current.hooks.add('beforeUpdate', globalHookAfter); + current.addHook('beforeUpdate', globalHookAfter); - return Model.hooks.run('beforeUpdate').then(() => { - expect(globalHookBefore).to.have.been.calledOnce; - expect(globalHookAfter).to.have.been.calledOnce; - expect(localHook).to.have.been.calledOnce; + await Model.runHooks('beforeUpdate'); + expect(globalHookBefore).to.have.been.calledOnce; + expect(globalHookAfter).to.have.been.calledOnce; + expect(localHook).to.have.been.calledOnce; - expect(localHook).to.have.been.calledBefore(globalHookBefore); - expect(localHook).to.have.been.calledBefore(globalHookAfter); - }); + expect(localHook).to.have.been.calledBefore(globalHookBefore); + expect(localHook).to.have.been.calledBefore(globalHookAfter); }); }); @@ -237,19 +235,18 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); }); - it('runs the global hook when no hook is passed', function() { + it('runs the global hook when no hook is passed', async function() { const Model = this.sequelize.define('M', {}, { hooks: { beforeUpdate: _.noop // Just to make sure we can define other hooks without overwriting the global one } }); - return Model.hooks.run('beforeCreate').then(() => { - expect(this.beforeCreate).to.have.been.calledOnce; - }); + await Model.runHooks('beforeCreate'); + expect(this.beforeCreate).to.have.been.calledOnce; }); - it('does not run the global hook when the model specifies its own hook', function() { + it('does not run the global hook when the model specifies its own hook', async function() { const localHook = sinon.spy(), Model = this.sequelize.define('M', {}, { hooks: { @@ -257,71 +254,70 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { } }); - return Model.hooks.run('beforeCreate').then(() => { - expect(this.beforeCreate).not.to.have.been.called; - expect(localHook).to.have.been.calledOnce; - }); + await Model.runHooks('beforeCreate'); + expect(this.beforeCreate).not.to.have.been.called; + expect(localHook).to.have.been.calledOnce; }); }); }); describe('#removeHook', () => { - it('should remove hook', function() { + it('should remove hook', async function() { const hook1 = sinon.spy(), hook2 = sinon.spy(); - this.Model.hooks.add('beforeCreate', hook1); + this.Model.addHook('beforeCreate', 'myHook', hook1); + this.Model.beforeCreate('myHook2', hook2); - return this.Model.hooks.run('beforeCreate').then(() => { - expect(hook1).to.have.been.calledOnce; + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; - hook1.resetHistory(); + hook1.resetHistory(); + hook2.resetHistory(); - this.Model.hooks.remove('beforeCreate', hook1); + this.Model.removeHook('beforeCreate', 'myHook'); + this.Model.removeHook('beforeCreate', 'myHook2'); - return this.Model.hooks.run('beforeCreate'); - }).then(() => { - expect(hook1).not.to.have.been.called; - expect(hook2).not.to.have.been.called; - }); + await this.Model.runHooks('beforeCreate'); + expect(hook1).not.to.have.been.called; + expect(hook2).not.to.have.been.called; }); - it('should not remove other hooks', function() { + it('should not remove other hooks', async function() { const hook1 = sinon.spy(), hook2 = sinon.spy(), hook3 = sinon.spy(), hook4 = sinon.spy(); - this.Model.hooks.add('beforeCreate', hook1); - this.Model.hooks.add('beforeCreate', hook2); - this.Model.hooks.add('beforeCreate', hook3); - this.Model.hooks.add('beforeCreate', hook4); - - return this.Model.hooks.run('beforeCreate').then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; - expect(hook3).to.have.been.calledOnce; - expect(hook4).to.have.been.calledOnce; - - hook1.resetHistory(); - hook2.resetHistory(); - hook3.resetHistory(); - hook4.resetHistory(); - - this.Model.hooks.remove('beforeCreate', hook2); - - return this.Model.hooks.run('beforeCreate'); - }).then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).not.to.have.been.called; - expect(hook3).to.have.been.calledOnce; - expect(hook4).to.have.been.calledOnce; - }); + this.Model.addHook('beforeCreate', hook1); + this.Model.addHook('beforeCreate', 'myHook', hook2); + this.Model.beforeCreate('myHook2', hook3); + this.Model.beforeCreate(hook4); + + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; + expect(hook3).to.have.been.calledOnce; + expect(hook4).to.have.been.calledOnce; + + hook1.resetHistory(); + hook2.resetHistory(); + hook3.resetHistory(); + hook4.resetHistory(); + + this.Model.removeHook('beforeCreate', 'myHook'); + + await this.Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).not.to.have.been.called; + expect(hook3).to.have.been.calledOnce; + expect(hook4).to.have.been.calledOnce; }); }); - describe('#hooks.add', () => { - it('should add additional hook when previous exists', function() { + describe('#addHook', () => { + it('should add additional hook when previous exists', async function() { const hook1 = sinon.spy(), hook2 = sinon.spy(); @@ -329,46 +325,45 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { hooks: { beforeCreate: hook1 } }); - Model.hooks.add('beforeCreate', hook2); + Model.addHook('beforeCreate', hook2); - return Model.hooks.run('beforeCreate').then(() => { - expect(hook1).to.have.been.calledOnce; - expect(hook2).to.have.been.calledOnce; - }); + await Model.runHooks('beforeCreate'); + expect(hook1).to.have.been.calledOnce; + expect(hook2).to.have.been.calledOnce; }); }); describe('promises', () => { - it('can return a promise', function() { - this.Model.hooks.add('beforeBulkCreate', () => { - return Sequelize.Promise.resolve(); + it('can return a promise', async function() { + this.Model.beforeBulkCreate(async () => { + // This space intentionally left blank }); - return expect(this.Model.hooks.run('beforeBulkCreate')).to.be.fulfilled; + await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; }); - it('can return undefined', function() { - this.Model.hooks.add('beforeBulkCreate', () => { + it('can return undefined', async function() { + this.Model.beforeBulkCreate(() => { // This space intentionally left blank }); - return expect(this.Model.hooks.run('beforeBulkCreate')).to.be.fulfilled; + await expect(this.Model.runHooks('beforeBulkCreate')).to.be.fulfilled; }); - it('can return an error by rejecting', function() { - this.Model.hooks.add('beforeCreate', () => { - return Promise.reject(new Error('Forbidden')); + it('can return an error by rejecting', async function() { + this.Model.beforeCreate(async () => { + throw new Error('Forbidden'); }); - return expect(this.Model.hooks.run('beforeCreate')).to.be.rejectedWith('Forbidden'); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); }); - it('can return an error by throwing', function() { - this.Model.hooks.add('beforeCreate', () => { + it('can return an error by throwing', async function() { + this.Model.beforeCreate(() => { throw new Error('Forbidden'); }); - return expect(this.Model.hooks.run('beforeCreate')).to.be.rejectedWith('Forbidden'); + await expect(this.Model.runHooks('beforeCreate')).to.be.rejectedWith('Forbidden'); }); }); @@ -381,10 +376,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { }); it('runs all beforInit/afterInit hooks', function() { - Support.Sequelize.hooks.add('beforeInit', this.hook1); - Support.Sequelize.hooks.add('beforeInit', this.hook2); - Support.Sequelize.hooks.add('afterInit', this.hook3); - Support.Sequelize.hooks.add('afterInit', this.hook4); + Support.Sequelize.addHook('beforeInit', 'h1', this.hook1); + Support.Sequelize.addHook('beforeInit', 'h2', this.hook2); + Support.Sequelize.addHook('afterInit', 'h3', this.hook3); + Support.Sequelize.addHook('afterInit', 'h4', this.hook4); Support.createSequelizeInstance(); @@ -394,10 +389,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { expect(this.hook4).to.have.been.calledOnce; // cleanup hooks on Support.Sequelize - Support.Sequelize.hooks.remove('beforeInit', this.hook1); - Support.Sequelize.hooks.remove('beforeInit', this.hook2); - Support.Sequelize.hooks.remove('afterInit', this.hook3); - Support.Sequelize.hooks.remove('afterInit', this.hook4); + Support.Sequelize.removeHook('beforeInit', 'h1'); + Support.Sequelize.removeHook('beforeInit', 'h2'); + Support.Sequelize.removeHook('afterInit', 'h3'); + Support.Sequelize.removeHook('afterInit', 'h4'); Support.createSequelizeInstance(); @@ -410,10 +405,10 @@ describe(Support.getTestDialectTeaser('Hooks'), () => { it('runs all beforDefine/afterDefine hooks', function() { const sequelize = Support.createSequelizeInstance(); - sequelize.hooks.add('beforeDefine', this.hook1); - sequelize.hooks.add('beforeDefine', this.hook2); - sequelize.hooks.add('afterDefine', this.hook3); - sequelize.hooks.add('afterDefine', this.hook4); + sequelize.addHook('beforeDefine', this.hook1); + sequelize.addHook('beforeDefine', this.hook2); + sequelize.addHook('afterDefine', this.hook3); + sequelize.addHook('afterDefine', this.hook4); sequelize.define('Test', {}); expect(this.hook1).to.have.been.calledOnce; expect(this.hook2).to.have.been.calledOnce; diff --git a/test/unit/increment.test.js b/test/unit/increment.test.js index 2cf276057a2b..4568912ba9e9 100644 --- a/test/unit/increment.test.js +++ b/test/unit/increment.test.js @@ -18,14 +18,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { count: Sequelize.BIGINT }); - it('should reject if options are missing', () => { - return expect(() => Model.increment(['id', 'count'])) - .to.throw('Missing where attribute in the options parameter'); + it('should reject if options are missing', async () => { + await expect(Model.increment(['id', 'count'])) + .to.be.rejectedWith('Missing where attribute in the options parameter'); }); - it('should reject if options.where are missing', () => { - return expect(() => Model.increment(['id', 'count'], { by: 10 })) - .to.throw('Missing where attribute in the options parameter'); + it('should reject if options.where are missing', async () => { + await expect(Model.increment(['id', 'count'], { by: 10 })) + .to.be.rejectedWith('Missing where attribute in the options parameter'); }); }); }); diff --git a/test/unit/instance-validator.test.js b/test/unit/instance-validator.test.js index 4cefb222e453..590d3ef7dc10 100644 --- a/test/unit/instance-validator.test.js +++ b/test/unit/instance-validator.test.js @@ -30,7 +30,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('validate', () => { it('runs the validation sequence and hooks when the hooks option is true', function() { - const instanceValidator = new InstanceValidator(new this.User(), { hooks: true }); + const instanceValidator = new InstanceValidator(this.User.build(), { hooks: true }); const _validate = sinon.spy(instanceValidator, '_validate'); const _validateAndRunHooks = sinon.spy(instanceValidator, '_validateAndRunHooks'); @@ -41,7 +41,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); it('runs the validation sequence but skips hooks if the hooks option is false', function() { - const instanceValidator = new InstanceValidator(new this.User(), { hooks: false }); + const instanceValidator = new InstanceValidator(this.User.build(), { hooks: false }); const _validate = sinon.spy(instanceValidator, '_validate'); const _validateAndRunHooks = sinon.spy(instanceValidator, '_validateAndRunHooks'); @@ -51,21 +51,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { expect(_validateAndRunHooks).to.not.have.been.called; }); - it('fulfills when validation is successful', function() { - const instanceValidator = new InstanceValidator(new this.User()); + it('fulfills when validation is successful', async function() { + const instanceValidator = new InstanceValidator(this.User.build()); const result = instanceValidator.validate(); - return expect(result).to.be.fulfilled; + await expect(result).to.be.fulfilled; }); - it('rejects with a validation error when validation fails', function() { - const instanceValidator = new InstanceValidator(new this.User({ fails: true })); + it('rejects with a validation error when validation fails', async function() { + const instanceValidator = new InstanceValidator(this.User.build({ fails: true })); const result = instanceValidator.validate(); - return expect(result).to.be.rejectedWith(SequelizeValidationError); + await expect(result).to.be.rejectedWith(SequelizeValidationError); }); - it('has a useful default error message for not null validation failures', () => { + it('has a useful default error message for not null validation failures', async () => { const User = Support.sequelize.define('user', { name: { type: Support.Sequelize.STRING, @@ -73,85 +73,80 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - const instanceValidator = new InstanceValidator(new User()); + const instanceValidator = new InstanceValidator(User.build()); const result = instanceValidator.validate(); - return expect(result).to.be.rejectedWith(SequelizeValidationError, /user\.name cannot be null/); + await expect(result).to.be.rejectedWith(SequelizeValidationError, /user\.name cannot be null/); }); }); describe('_validateAndRunHooks', () => { beforeEach(function() { - this.successfulInstanceValidator = new InstanceValidator(new this.User()); + this.successfulInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(this.successfulInstanceValidator, '_validate').resolves(); }); - it('should run beforeValidate and afterValidate hooks when _validate is successful', function() { + it('should run beforeValidate and afterValidate hooks when _validate is successful', async function() { const beforeValidate = sinon.spy(); const afterValidate = sinon.spy(); - this.User.hooks.add('beforeValidate', beforeValidate); - this.User.hooks.add('afterValidate', afterValidate); + this.User.beforeValidate(beforeValidate); + this.User.afterValidate(afterValidate); - return expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.fulfilled.then(() => { - expect(beforeValidate).to.have.been.calledOnce; - expect(afterValidate).to.have.been.calledOnce; - }); + await expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.fulfilled; + expect(beforeValidate).to.have.been.calledOnce; + expect(afterValidate).to.have.been.calledOnce; }); - it('should run beforeValidate hook but not afterValidate hook when _validate is unsuccessful', function() { - const failingInstanceValidator = new InstanceValidator(new this.User()); + it('should run beforeValidate hook but not afterValidate hook when _validate is unsuccessful', async function() { + const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const beforeValidate = sinon.spy(); const afterValidate = sinon.spy(); - this.User.hooks.add('beforeValidate', beforeValidate); - this.User.hooks.add('afterValidate', afterValidate); + this.User.beforeValidate(beforeValidate); + this.User.afterValidate(afterValidate); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => { - expect(beforeValidate).to.have.been.calledOnce; - expect(afterValidate).to.not.have.been.called; - }); + await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(beforeValidate).to.have.been.calledOnce; + expect(afterValidate).to.not.have.been.called; }); - it('should emit an error from after hook when afterValidate fails', function() { - this.User.hooks.add('afterValidate', () => { + it('should emit an error from after hook when afterValidate fails', async function() { + this.User.afterValidate(() => { throw new Error('after validation error'); }); - return expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.rejectedWith('after validation error'); + await expect(this.successfulInstanceValidator._validateAndRunHooks()).to.be.rejectedWith('after validation error'); }); describe('validatedFailed hook', () => { - it('should call validationFailed hook when validation fails', function() { - const failingInstanceValidator = new InstanceValidator(new this.User()); + it('should call validationFailed hook when validation fails', async function() { + const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new Error()); const validationFailedHook = sinon.spy(); - this.User.hooks.add('validationFailed', validationFailedHook); + this.User.validationFailed(validationFailedHook); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(() => { - expect(validationFailedHook).to.have.been.calledOnce; - }); + await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(validationFailedHook).to.have.been.calledOnce; }); - it('should not replace the validation error in validationFailed hook by default', function() { - const failingInstanceValidator = new InstanceValidator(new this.User()); + it('should not replace the validation error in validationFailed hook by default', async function() { + const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().resolves(); - this.User.hooks.add('validationFailed', validationFailedHook); + this.User.validationFailed(validationFailedHook); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => { - expect(err.name).to.equal('SequelizeValidationError'); - }); + const err = await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(err.name).to.equal('SequelizeValidationError'); }); - it('should replace the validation error if validationFailed hook creates a new error', function() { - const failingInstanceValidator = new InstanceValidator(new this.User()); + it('should replace the validation error if validationFailed hook creates a new error', async function() { + const failingInstanceValidator = new InstanceValidator(this.User.build()); sinon.stub(failingInstanceValidator, '_validate').rejects(new SequelizeValidationError()); const validationFailedHook = sinon.stub().throws(new Error('validation failed hook error')); - this.User.hooks.add('validationFailed', validationFailedHook); + this.User.validationFailed(validationFailedHook); - return expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected.then(err => { - expect(err.message).to.equal('validation failed hook error'); - }); + const err = await expect(failingInstanceValidator._validateAndRunHooks()).to.be.rejected; + expect(err.message).to.equal('validation failed hook error'); }); }); }); diff --git a/test/unit/instance/build.test.js b/test/unit/instance/build.test.js index 6bbaa569764a..f2d671c98e77 100644 --- a/test/unit/instance/build.test.js +++ b/test/unit/instance/build.test.js @@ -8,7 +8,7 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('build', () => { - it('should populate NOW default values', () => { + it('should populate NOW default values', async () => { const Model = current.define('Model', { created_time: { type: DataTypes.DATE, @@ -37,7 +37,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }, { timestamp: false }), - instance = new Model({ ip: '127.0.0.1', ip2: '0.0.0.0' }); + instance = Model.build({ ip: '127.0.0.1', ip2: '0.0.0.0' }); expect(instance.get('created_time')).to.be.ok; expect(instance.get('created_time')).to.be.an.instanceof(Date); @@ -45,7 +45,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(instance.get('updated_time')).to.be.ok; expect(instance.get('updated_time')).to.be.an.instanceof(Date); - return instance.validate(); + await instance.validate(); }); it('should populate explicitly undefined UUID primary keys', () => { @@ -57,7 +57,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: DataTypes.UUIDV4 } }), - instance = new Model({ + instance = Model.build({ id: undefined }); @@ -76,7 +76,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: 2 } }), - instance = new Model({ + instance = Model.build({ number1: undefined }); @@ -93,11 +93,11 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: { foo: 'bar' } } }), - instance = new Model(); + instance = Model.build(); instance.data.foo = 'biz'; expect(instance.get('data')).to.eql({ foo: 'biz' }); - expect(new Model().get('data')).to.eql({ foo: 'bar' }); + expect(Model.build().get('data')).to.eql({ foo: 'bar' }); }); }); }); diff --git a/test/unit/instance/changed.test.js b/test/unit/instance/changed.test.js index 99bb754f063f..a44ae2047093 100644 --- a/test/unit/instance/changed.test.js +++ b/test/unit/instance/changed.test.js @@ -18,7 +18,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for changed primitive', function() { - const user = new this.User({ + const user = this.User.build({ name: 'a' }, { isNewRecord: false, @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return falsy for unchanged primitive', function() { - const user = new this.User({ + const user = this.User.build({ name: 'a', meta: null }, { @@ -48,7 +48,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for multiple changed values', function() { - const user = new this.User({ + const user = this.User.build({ name: 'a', birthday: new Date(new Date() - 10) }, { @@ -67,7 +67,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const firstDate = new Date(milliseconds); const secondDate = new Date(milliseconds); - const user = new this.User({ + const user = this.User.build({ birthday: firstDate }, { isNewRecord: false, @@ -78,25 +78,22 @@ describe(Support.getTestDialectTeaser('Instance'), () => { expect(user.changed('birthday')).to.equal(false); }); - it('should return true for changed JSON with same object', function() { - const user = new this.User({ - meta: { - city: 'Copenhagen' - } - }, { - isNewRecord: false, - raw: true - }); - - const meta = user.get('meta'); - meta.city = 'Stockholm'; - - user.set('meta', meta); - expect(user.changed('meta')).to.equal(true); + it('should not detect changes when equal', function() { + for (const value of [null, 1, 'asdf', new Date(), [], {}, Buffer.from('')]) { + const t = new this.User({ + json: value + }, { + isNewRecord: false, + raw: true + }); + t.json = value; + expect(t.changed('json')).to.be.false; + expect(t.changed()).to.be.false; + } }); it('should return true for JSON dot.separated key with changed values', function() { - const user = new this.User({ + const user = this.User.build({ meta: { city: 'Stockholm' } @@ -110,7 +107,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return false for JSON dot.separated key with same value', function() { - const user = new this.User({ + const user = this.User.build({ meta: { city: 'Gothenburg' } @@ -124,7 +121,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return true for JSON dot.separated key with object', function() { - const user = new this.User({ + const user = this.User.build({ meta: { address: { street: 'Main street', number: '40' } } @@ -138,7 +135,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should return false for JSON dot.separated key with same object', function() { - const user = new this.User({ + const user = this.User.build({ meta: { address: { street: 'Main street', number: '40' } } @@ -157,7 +154,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { attributes[attr] = null; } - const user = new this.User(attributes, { + const user = this.User.build(attributes, { isNewRecord: false, raw: true }); @@ -173,7 +170,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('setDataValue', () => { it('should return falsy for unchanged primitive', function() { - const user = new this.User({ + const user = this.User.build({ name: 'a', meta: null }, { diff --git a/test/unit/instance/decrement.test.js b/test/unit/instance/decrement.test.js index e648f17d0919..24fa2e991a7e 100644 --- a/test/unit/instance/decrement.test.js +++ b/test/unit/instance/decrement.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow decrements even if options are not given', () => { - instance = new Model({ id: 3 }, { isNewRecord: false }); + instance = Model.build({ id: 3 }, { isNewRecord: false }); expect(() => { instance.decrement(['id']); }).to.not.throw(); diff --git a/test/unit/instance/destroy.test.js b/test/unit/instance/destroy.test.js index bcee8941f6c8..4932da5b473c 100644 --- a/test/unit/instance/destroy.test.js +++ b/test/unit/instance/destroy.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow destroies even if options are not given', () => { - instance = new Model({ id: 1 }, { isNewRecord: false }); + instance = Model.build({ id: 1 }, { isNewRecord: false }); expect(() => { instance.destroy(); }).to.not.throw(); diff --git a/test/unit/instance/get.test.js b/test/unit/instance/get.test.js index e96885c32d5c..516c5a0763a5 100644 --- a/test/unit/instance/get.test.js +++ b/test/unit/instance/get.test.js @@ -20,13 +20,13 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('invokes getter if raw: false', function() { - new this.User().get('name'); + this.User.build().get('name'); expect(this.getSpy).to.have.been.called; }); it('does not invoke getter if raw: true', function() { - new this.User().get('name', { raw: true }); + this.User.build().get('name', { raw: true }); expect(this.getSpy).not.to.have.been.called; }); diff --git a/test/unit/instance/increment.test.js b/test/unit/instance/increment.test.js index 9811e04c9212..891bb56b48c0 100644 --- a/test/unit/instance/increment.test.js +++ b/test/unit/instance/increment.test.js @@ -33,7 +33,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow increments even if options are not given', () => { - instance = new Model({ id: 1 }, { isNewRecord: false }); + instance = Model.build({ id: 1 }, { isNewRecord: false }); expect(() => { instance.increment(['id']); }).to.not.throw(); diff --git a/test/unit/instance/is-soft-deleted.test.js b/test/unit/instance/is-soft-deleted.test.js index 69e209ef29cd..6f49a1b63088 100644 --- a/test/unit/instance/is-soft-deleted.test.js +++ b/test/unit/instance/is-soft-deleted.test.js @@ -31,14 +31,14 @@ describe(Support.getTestDialectTeaser('Instance'), () => { paranoid: true }); - this.paranoidUser = new ParanoidUser({ + this.paranoidUser = ParanoidUser.build({ name: 'a' }, { isNewRecord: false, raw: true }); - this.user = new User({ + this.user = User.build({ name: 'a' }, { isNewRecord: false, diff --git a/test/unit/instance/previous.test.js b/test/unit/instance/previous.test.js index 9d0d2846b14c..6b468a53414b 100644 --- a/test/unit/instance/previous.test.js +++ b/test/unit/instance/previous.test.js @@ -22,7 +22,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - const instance = new Model({ text: 'a', textCustom: 'abc' }); + const instance = Model.build({ text: 'a', textCustom: 'abc' }); expect(instance.previous('text')).to.be.not.ok; expect(instance.previous('textCustom')).to.be.not.ok; diff --git a/test/unit/instance/reload.test.js b/test/unit/instance/reload.test.js index 55f9b9de3265..e7ab006bc5a9 100644 --- a/test/unit/instance/reload.test.js +++ b/test/unit/instance/reload.test.js @@ -38,7 +38,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow reloads even if options are not given', () => { - instance = new Model({ id: 1 }, { isNewRecord: false }); + instance = Model.build({ id: 1 }, { isNewRecord: false }); expect(() => { instance.reload(); }).to.not.throw(); diff --git a/test/unit/instance/restore.test.js b/test/unit/instance/restore.test.js index 0799b39bb8f6..b2b6083acee9 100644 --- a/test/unit/instance/restore.test.js +++ b/test/unit/instance/restore.test.js @@ -38,7 +38,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow restores even if options are not given', () => { - instance = new Model({ id: 1 }, { isNewRecord: false }); + instance = Model.build({ id: 1 }, { isNewRecord: false }); expect(() => { instance.restore(); }).to.not.throw(); diff --git a/test/unit/instance/save.test.js b/test/unit/instance/save.test.js index 17e0488494ca..5b87e675514d 100644 --- a/test/unit/instance/save.test.js +++ b/test/unit/instance/save.test.js @@ -9,15 +9,13 @@ const chai = require('chai'), describe(Support.getTestDialectTeaser('Instance'), () => { describe('save', () => { - it('should disallow saves if no primary key values is present', () => { + it('should disallow saves if no primary key values is present', async () => { const Model = current.define('User', { }), - instance = new Model({}, { isNewRecord: false }); + instance = Model.build({}, { isNewRecord: false }); - expect(() => { - instance.save(); - }).to.throw(); + await expect(instance.save()).to.be.rejected; }); describe('options tests', () => { @@ -44,7 +42,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { }); it('should allow saves even if options are not given', () => { - instance = new Model({}); + instance = Model.build({}); expect(() => { instance.save(); }).to.not.throw(); diff --git a/test/unit/instance/set.test.js b/test/unit/instance/set.test.js index b12609dccc52..dc2ef6056ee7 100644 --- a/test/unit/instance/set.test.js +++ b/test/unit/instance/set.test.js @@ -3,8 +3,6 @@ const chai = require('chai'), expect = chai.expect, Support = require('../support'), - Sequelize = require('../../../index'), - Promise = Sequelize.Promise, DataTypes = require('../../../lib/data-types'), current = Support.sequelize, sinon = require('sinon'); @@ -15,9 +13,9 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = current.define('User', { meta: DataTypes.JSONB }); - const user = new User({ + const user = User.build({ meta: { - location: 'Stockholm' + location: 'Stockhollm' } }, { isNewRecord: false, @@ -41,9 +39,9 @@ describe(Support.getTestDialectTeaser('Instance'), () => { defaultValue: {} } }); - const user1 = new User({}); - user1.set('meta.location', 'Stockholm'); - const user2 = new User({}); + const user1 = User.build({}); + user1.set('meta.location', 'Stockhollm'); + const user2 = User.build({}); expect(user2.get('meta')).to.deep.equal({}); }); @@ -54,7 +52,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { allowNull: true } }); - const user1 = new User({ + const user1 = User.build({ date: null }); user1.set('date', '1970-01-01'); @@ -66,7 +64,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = current.define('User', { date: DataTypes.DATE }); - const user = new User({ + const user = User.build({ date: ' ' }, { isNewRecord: false, @@ -80,9 +78,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { describe('custom setter', () => { before(function() { - this.stubCreate = sinon.stub(current.getQueryInterface(), 'insert').callsFake(instance => { - return Promise.resolve([instance, 1]); - }); + this.stubCreate = sinon.stub(current.getQueryInterface(), 'insert').callsFake(async instance => [instance, 1]); }); after(function() { @@ -105,52 +101,48 @@ describe(Support.getTestDialectTeaser('Instance'), () => { } }); - it('does not set field to changed if field is set to the same value with custom setter using primitive value', () => { - const user = new User({ + it('does not set field to changed if field is set to the same value with custom setter using primitive value', async () => { + const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', '+1 (0) 234567'); // Canonical equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.false; - }); + user.set('phoneNumber', '+1 (0) 234567');// Canonical equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.false; }); - it('sets field to changed if field is set to the another value with custom setter using primitive value', () => { - const user = new User({ + it('sets field to changed if field is set to the another value with custom setter using primitive value', async () => { + const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', '+1 (0) 765432'); // Canonical non-equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.true; - }); + user.set('phoneNumber', '+1 (0) 765432');// Canonical non-equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.true; }); - it('does not set field to changed if field is set to the same value with custom setter using object', () => { - const user = new User({ + it('does not set field to changed if field is set to the same value with custom setter using object', async () => { + const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', { country: '1', area: '234', local: '567' }); // Canonical equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.false; - }); + user.set('phoneNumber', { country: '1', area: '234', local: '567' });// Canonical equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.false; }); - it('sets field to changed if field is set to the another value with custom setter using object', () => { - const user = new User({ + it('sets field to changed if field is set to the another value with custom setter using object', async () => { + const user = User.build({ phoneNumber: '+1 234 567' }); - return user.save().then(() => { - expect(user.changed('phoneNumber')).to.be.false; + await user.save(); + expect(user.changed('phoneNumber')).to.be.false; - user.set('phoneNumber', { country: '1', area: '765', local: '432' }); // Canonical non-equivalent of existing phone number - expect(user.changed('phoneNumber')).to.be.true; - }); + user.set('phoneNumber', { country: '1', area: '765', local: '432' });// Canonical non-equivalent of existing phone number + expect(user.changed('phoneNumber')).to.be.true; }); }); }); diff --git a/test/unit/instance/to-json.test.js b/test/unit/instance/to-json.test.js index 821ee3dace21..ef84e93388ff 100644 --- a/test/unit/instance/to-json.test.js +++ b/test/unit/instance/to-json.test.js @@ -12,7 +12,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { const User = current.define('User', { name: DataTypes.STRING }); - const user = new User({ name: 'my-name' }); + const user = User.build({ name: 'my-name' }); const json1 = user.toJSON(); expect(json1).to.have.property('name').and.be.equal('my-name'); @@ -28,7 +28,7 @@ describe(Support.getTestDialectTeaser('Instance'), () => { name: DataTypes.STRING, permissions: DataTypes.JSON }); - const user = new User({ name: 'my-name', permissions: { admin: true, special: 'foobar' } }); + const user = User.build({ name: 'my-name', permissions: { admin: true, special: 'foobar' } }); const json = user.toJSON(); expect(json) diff --git a/test/unit/model/bulkcreate.test.js b/test/unit/model/bulkcreate.test.js index 870a90d070a8..81a8bb403b12 100644 --- a/test/unit/model/bulkcreate.test.js +++ b/test/unit/model/bulkcreate.test.js @@ -30,14 +30,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('validations', () => { - it('should not fail for renamed fields', function() { - return this.Model.bulkCreate([ + it('should not fail for renamed fields', async function() { + await this.Model.bulkCreate([ { accountId: 42 } - ], { validate: true }).then(() => { - expect(this.stub.getCall(0).args[1]).to.deep.equal([ - { account_id: 42, id: null } - ]); - }); + ], { validate: true }); + + expect(this.stub.getCall(0).args[1]).to.deep.equal([ + { account_id: 42, id: null } + ]); }); }); }); diff --git a/test/unit/model/count.test.js b/test/unit/model/count.test.js index 6ffe587ed50e..08838b351057 100644 --- a/test/unit/model/count.test.js +++ b/test/unit/model/count.test.js @@ -38,32 +38,28 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('should pass the same options to model.aggregate as findAndCountAll', () => { - it('with includes', function() { + it('with includes', async function() { const queryObject = { include: [this.Project] }; - return this.User.count(queryObject) - .then(() => this.User.findAndCountAll(queryObject)) - .then(() => { - const count = this.stub.getCall(0).args; - const findAndCountAll = this.stub.getCall(1).args; - expect(count).to.eql(findAndCountAll); - }); + await this.User.count(queryObject); + await this.User.findAndCountAll(queryObject); + const count = this.stub.getCall(0).args; + const findAndCountAll = this.stub.getCall(1).args; + expect(count).to.eql(findAndCountAll); }); - it('attributes should be stripped in case of findAndCountAll', function() { + it('attributes should be stripped in case of findAndCountAll', async function() { const queryObject = { attributes: ['username'] }; - return this.User.count(queryObject) - .then(() => this.User.findAndCountAll(queryObject)) - .then(() => { - const count = this.stub.getCall(0).args; - const findAndCountAll = this.stub.getCall(1).args; - expect(count).not.to.eql(findAndCountAll); - count[2].attributes = undefined; - expect(count).to.eql(findAndCountAll); - }); + await this.User.count(queryObject); + await this.User.findAndCountAll(queryObject); + const count = this.stub.getCall(0).args; + const findAndCountAll = this.stub.getCall(1).args; + expect(count).not.to.eql(findAndCountAll); + count[2].attributes = undefined; + expect(count).to.eql(findAndCountAll); }); }); diff --git a/test/unit/model/destroy.test.js b/test/unit/model/destroy.test.js index e39e23696545..8cd1233ffbc8 100644 --- a/test/unit/model/destroy.test.js +++ b/test/unit/model/destroy.test.js @@ -1,10 +1,11 @@ 'use strict'; -const { expect } = require('chai'); -const Support = require('../support'); -const current = Support.sequelize; -const sinon = require('sinon'); -const DataTypes = require('../../../lib/data-types'); +const chai = require('chai'), + expect = chai.expect, + Support = require('../support'), + current = Support.sequelize, + sinon = require('sinon'), + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { @@ -33,13 +34,10 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.stubDelete.restore(); }); - it('can detect complex objects', () => { + it('can detect complex objects', async () => { const Where = function() { this.secretValue = '1'; }; - expect(() => { - User.destroy({ where: new Where() }); - }).to.throw(); - + await expect(User.destroy({ where: new Where() })).to.be.rejected; }); }); }); diff --git a/test/unit/model/find-and-count-all.test.js b/test/unit/model/find-and-count-all.test.js index 2c53fffddf69..0cf1b2e8c17e 100644 --- a/test/unit/model/find-and-count-all.test.js +++ b/test/unit/model/find-and-count-all.test.js @@ -5,8 +5,7 @@ const chai = require('chai'), Support = require('../support'), current = Support.sequelize, sinon = require('sinon'), - DataTypes = require('../../../lib/data-types'), - Promise = require('bluebird').getNewLibraryCopy(); + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { describe('findAndCountAll', () => { @@ -14,9 +13,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { before(function() { this.stub = sinon.stub(); - Promise.onPossiblyUnhandledRejection(() => { - this.stub(); - }); + process.on('unhandledRejection', this.stub); this.User = current.define('User', { username: DataTypes.STRING, @@ -33,14 +30,13 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.count.resetBehavior(); }); - it('with errors in count and findAll both', function() { - return this.User.findAndCountAll({}) - .then(() => { - throw new Error(); - }) - .catch(() => { - expect(this.stub.callCount).to.eql(0); - }); + it('with errors in count and findAll both', async function() { + try { + await this.User.findAndCountAll({}); + throw new Error(); + } catch (err) { + expect(this.stub.callCount).to.eql(0); + } }); }); }); diff --git a/test/unit/model/find-create-find.test.js b/test/unit/model/find-create-find.test.js index 907e22dfab4f..521dfa2116be 100644 --- a/test/unit/model/find-create-find.test.js +++ b/test/unit/model/find-create-find.test.js @@ -2,11 +2,10 @@ const chai = require('chai'), expect = chai.expect, + { EmptyResultError, UniqueConstraintError } = require('../../../lib/errors'), Support = require('../support'), - UniqueConstraintError = require('../../../lib/errors').UniqueConstraintError, current = Support.sequelize, - sinon = require('sinon'), - Promise = require('bluebird'); + sinon = require('sinon'); describe(Support.getTestDialectTeaser('Model'), () => { describe('findCreateFind', () => { @@ -14,53 +13,54 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.sinon = sinon.createSandbox(); - this.sinon.usingPromise(Promise); }); afterEach(function() { this.sinon.restore(); }); - it('should return the result of the first find call if not empty', function() { + it('should return the result of the first find call if not empty', async function() { const result = {}, where = { prop: Math.random().toString() }, findSpy = this.sinon.stub(Model, 'findOne').resolves(result); - return expect(Model.findCreateFind({ + await expect(Model.findCreateFind({ where - })).to.eventually.eql([result, false]).then(() => { - expect(findSpy).to.have.been.calledOnce; - expect(findSpy.getCall(0).args[0].where).to.equal(where); - }); + })).to.eventually.eql([result, false]); + + expect(findSpy).to.have.been.calledOnce; + expect(findSpy.getCall(0).args[0].where).to.equal(where); }); - it('should create if first find call is empty', function() { + it('should create if first find call is empty', async function() { const result = {}, where = { prop: Math.random().toString() }, createSpy = this.sinon.stub(Model, 'create').resolves(result); this.sinon.stub(Model, 'findOne').resolves(null); - return expect(Model.findCreateFind({ + await expect(Model.findCreateFind({ where - })).to.eventually.eql([result, true]).then(() => { - expect(createSpy).to.have.been.calledWith(where); - }); + })).to.eventually.eql([result, true]); + + expect(createSpy).to.have.been.calledWith(where); }); - it('should do a second find if create failed do to unique constraint', function() { - const result = {}, - where = { prop: Math.random().toString() }, - findSpy = this.sinon.stub(Model, 'findOne'); + [EmptyResultError, UniqueConstraintError].forEach(Error => { + it(`should do a second find if create failed due to an error of type ${Error.name}`, async function() { + const result = {}, + where = { prop: Math.random().toString() }, + findSpy = this.sinon.stub(Model, 'findOne'); - this.sinon.stub(Model, 'create').rejects(new UniqueConstraintError()); + this.sinon.stub(Model, 'create').rejects(new Error()); - findSpy.onFirstCall().resolves(null); - findSpy.onSecondCall().resolves(result); + findSpy.onFirstCall().resolves(null); + findSpy.onSecondCall().resolves(result); + + await expect(Model.findCreateFind({ + where + })).to.eventually.eql([result, false]); - return expect(Model.findCreateFind({ - where - })).to.eventually.eql([result, false]).then(() => { expect(findSpy).to.have.been.calledTwice; expect(findSpy.getCall(1).args[0].where).to.equal(where); }); diff --git a/test/unit/model/find-or-create.test.js b/test/unit/model/find-or-create.test.js new file mode 100644 index 000000000000..087273a11a3d --- /dev/null +++ b/test/unit/model/find-or-create.test.js @@ -0,0 +1,70 @@ +'use strict'; + +const chai = require('chai'), + expect = chai.expect, + Support = require('../support'), + current = Support.sequelize, + cls = require('cls-hooked'), + sinon = require('sinon'), + stub = sinon.stub; + +describe(Support.getTestDialectTeaser('Model'), () => { + describe('method findOrCreate', () => { + before(() => { + current.constructor.useCLS(cls.createNamespace('sequelize')); + }); + + after(() => { + cls.destroyNamespace('sequelize'); + delete current.constructor._cls; + }); + + beforeEach(function() { + this.User = current.define('User', {}, { + name: 'John' + }); + + this.transactionStub = stub(this.User.sequelize, 'transaction').rejects(new Error('abort')); + + this.clsStub = stub(current.constructor._cls, 'get').returns({ id: 123 }); + }); + + afterEach(function() { + this.transactionStub.restore(); + this.clsStub.restore(); + }); + + it('should use transaction from cls if available', async function() { + const options = { + where: { + name: 'John' + } + }; + + try { + await this.User.findOrCreate(options); + expect.fail('expected to fail'); + } catch (err) { + if (!/abort/.test(err.message)) throw err; + expect(this.clsStub.calledOnce).to.equal(true, 'expected to ask for transaction'); + } + }); + + it('should not use transaction from cls if provided as argument', async function() { + const options = { + where: { + name: 'John' + }, + transaction: { id: 123 } + }; + + try { + await this.User.findOrCreate(options); + expect.fail('expected to fail'); + } catch (err) { + if (!/abort/.test(err.message)) throw err; + expect(this.clsStub.called).to.equal(false); + } + }); + }); +}); diff --git a/test/unit/model/findall.test.js b/test/unit/model/findall.test.js index 12180e22c157..ed01ea620e04 100644 --- a/test/unit/model/findall.test.js +++ b/test/unit/model/findall.test.js @@ -45,7 +45,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { }, { timestamps: false }); before(function() { - this.stub = sinon.stub(current.getQueryInterface(), 'select').callsFake(() => new Model({})); + this.stub = sinon.stub(current.getQueryInterface(), 'select').callsFake(() => Model.build({})); this.warnOnInvalidOptionsStub = sinon.stub(Model, 'warnOnInvalidOptions'); }); @@ -65,70 +65,69 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(this.warnOnInvalidOptionsStub.calledOnce).to.equal(true); }); - it('Throws an error when the attributes option is formatted incorrectly', () => { - const errorFunction = Model.findAll.bind(Model, { attributes: 'name' }); - expect(errorFunction).to.throw(sequelizeErrors.QueryError); + it('Throws an error when the attributes option is formatted incorrectly', async () => { + await expect(Model.findAll({ attributes: 'name' })).to.be.rejectedWith(sequelizeErrors.QueryError); }); }); describe('attributes include / exclude', () => { - it('allows me to include additional attributes', function() { - return Model.findAll({ + it('allows me to include additional attributes', async function() { + await Model.findAll({ attributes: { include: ['foobar'] } - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id', - 'name', - 'foobar' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'id', + 'name', + 'foobar' + ]); }); - it('allows me to exclude attributes', function() { - return Model.findAll({ + it('allows me to exclude attributes', async function() { + await Model.findAll({ attributes: { exclude: ['name'] } - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'id' + ]); }); - it('include takes precendence over exclude', function() { - return Model.findAll({ + it('include takes precendence over exclude', async function() { + await Model.findAll({ attributes: { exclude: ['name'], include: ['name'] } - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'id', - 'name' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'id', + 'name' + ]); }); - it('works for models without PK #4607', function() { + it('works for models without PK #4607', async function() { const Model = current.define('model', {}, { timestamps: false }); const Foo = current.define('foo'); Model.hasOne(Foo); Model.removeAttribute('id'); - return Model.findAll({ + await Model.findAll({ attributes: { include: ['name'] }, include: [Foo] - }).then(() => { - expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ - 'name' - ]); }); + + expect(this.stub.getCall(0).args[2].attributes).to.deep.equal([ + 'name' + ]); }); }); diff --git a/test/unit/model/findone.test.js b/test/unit/model/findone.test.js index b898255d5ce5..93ebe7d21080 100644 --- a/test/unit/model/findone.test.js +++ b/test/unit/model/findone.test.js @@ -23,15 +23,14 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('should not add limit when querying on a primary key', () => { - it('with id primary key', function() { + it('with id primary key', async function() { const Model = current.define('model'); - return Model.findOne({ where: { id: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { id: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); - it('with custom primary key', function() { + it('with custom primary key', async function() { const Model = current.define('model', { uid: { type: DataTypes.INTEGER, @@ -40,12 +39,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { uid: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { uid: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); - it('with blob primary key', function() { + it('with blob primary key', async function() { const Model = current.define('model', { id: { type: DataTypes.BLOB, @@ -54,22 +52,20 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { id: Buffer.from('foo') } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { id: Buffer.from('foo') } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); }); - it('should add limit when using { $ gt on the primary key', function() { + it('should add limit when using { $ gt on the primary key', async function() { const Model = current.define('model'); - return Model.findOne({ where: { id: { [Op.gt]: 42 } } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); - }); + await Model.findOne({ where: { id: { [Op.gt]: 42 } } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); }); describe('should not add limit when querying on an unique key', () => { - it('with custom unique key', function() { + it('with custom unique key', async function() { const Model = current.define('model', { unique: { type: DataTypes.INTEGER, @@ -77,12 +73,11 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { unique: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { unique: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); - it('with blob unique key', function() { + it('with blob unique key', async function() { const Model = current.define('model', { unique: { type: DataTypes.BLOB, @@ -90,13 +85,12 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { unique: Buffer.from('foo') } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); - }); + await Model.findOne({ where: { unique: Buffer.from('foo') } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').not.to.have.property('limit'); }); }); - it('should add limit when using multi-column unique key', function() { + it('should add limit when using multi-column unique key', async function() { const Model = current.define('model', { unique1: { type: DataTypes.INTEGER, @@ -108,9 +102,8 @@ describe(Support.getTestDialectTeaser('Model'), () => { } }); - return Model.findOne({ where: { unique1: 42 } }).then(() => { - expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); - }); + await Model.findOne({ where: { unique1: 42 } }); + expect(this.stub.getCall(0).args[0]).to.be.an('object').to.have.property('limit'); }); }); }); diff --git a/test/unit/model/include.test.js b/test/unit/model/include.test.js index ab78b6e8f550..6fc39725f795 100644 --- a/test/unit/model/include.test.js +++ b/test/unit/model/include.test.js @@ -405,6 +405,26 @@ describe(Support.getTestDialectTeaser('Model'), () => { expect(options.include[0].subQueryFilter).to.equal(false); }); + it('should not tag a separate hasMany association with subQuery true', function() { + const options = Sequelize.Model._validateIncludedElements({ + model: this.Company, + include: [ + { + association: this.Company.Employees, + separate: true, + include: [ + { association: this.User.Tasks, required: true } + ] + } + ], + required: true + }); + + expect(options.subQuery).to.equal(false); + expect(options.include[0].subQuery).to.equal(false); + expect(options.include[0].subQueryFilter).to.equal(false); + }); + it('should tag a hasMany association with where', function() { const options = Sequelize.Model._validateIncludedElements({ model: this.User, diff --git a/test/unit/model/overwriting-builtins.test.js b/test/unit/model/overwriting-builtins.test.js index dac4cf177009..c547bb9b09a9 100644 --- a/test/unit/model/overwriting-builtins.test.js +++ b/test/unit/model/overwriting-builtins.test.js @@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { set: DataTypes.STRING }); - const user = new User({ set: 'A' }); + const user = User.build({ set: 'A' }); expect(user.get('set')).to.equal('A'); user.set('set', 'B'); expect(user.get('set')).to.equal('B'); diff --git a/test/unit/model/removeAttribute.test.js b/test/unit/model/removeAttribute.test.js index dddbab40cc5c..918f834ccd6f 100644 --- a/test/unit/model/removeAttribute.test.js +++ b/test/unit/model/removeAttribute.test.js @@ -30,7 +30,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { Model.removeAttribute('id'); - const instance = new Model(); + const instance = Model.build(); expect(instance.dataValues).not.to.include.keys('undefined'); }); }); diff --git a/test/unit/model/scope.test.js b/test/unit/model/scope.test.js index 87e471a773fb..c479cc19de7e 100644 --- a/test/unit/model/scope.test.js +++ b/test/unit/model/scope.test.js @@ -207,6 +207,27 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); + it('should be keep original scope definition clean', () => { + expect(Company.scope('projects', 'users', 'alsoUsers')._scope).to.deep.equal({ + include: [ + { model: Project }, + { model: User, where: { something: 42 } } + ] + }); + + expect(Company.options.scopes.alsoUsers).to.deep.equal({ + include: [ + { model: User, where: { something: 42 } } + ] + }); + + expect(Company.options.scopes.users).to.deep.equal({ + include: [ + { model: User } + ] + }); + }); + it('should be able to override the default scope', () => { expect(Company.scope('somethingTrue')._scope).to.deep.equal(scopes.somethingTrue); }); @@ -516,4 +537,4 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/test/unit/model/update.test.js b/test/unit/model/update.test.js index 8b6717d15d33..042b53faaa7e 100644 --- a/test/unit/model/update.test.js +++ b/test/unit/model/update.test.js @@ -1,10 +1,11 @@ 'use strict'; -const { expect } = require('chai'); -const Support = require('../support'); -const current = Support.sequelize; -const sinon = require('sinon'); -const DataTypes = require('../../../lib/data-types'); +const chai = require('chai'), + expect = chai.expect, + Support = require('../support'), + current = Support.sequelize, + sinon = require('sinon'), + DataTypes = require('../../../lib/data-types'); describe(Support.getTestDialectTeaser('Model'), () => { describe('method update', () => { @@ -31,25 +32,21 @@ describe(Support.getTestDialectTeaser('Model'), () => { }); describe('properly clones input values', () => { - it('with default options', function() { - return this.User.update(this.updates, { where: { secretValue: '1' } }).then(() => { - expect(this.updates).to.be.deep.eql(this.cloneUpdates); - }); + it('with default options', async function() { + await this.User.update(this.updates, { where: { secretValue: '1' } }); + expect(this.updates).to.be.deep.eql(this.cloneUpdates); }); - it('when using fields option', function() { - return this.User.update(this.updates, { where: { secretValue: '1' }, fields: ['name'] }).then(() => { - expect(this.updates).to.be.deep.eql(this.cloneUpdates); - }); + it('when using fields option', async function() { + await this.User.update(this.updates, { where: { secretValue: '1' }, fields: ['name'] }); + expect(this.updates).to.be.deep.eql(this.cloneUpdates); }); }); - it('can detect complexe objects', function() { + it('can detect complexe objects', async function() { const Where = function() { this.secretValue = '1'; }; - expect(() => { - this.User.update(this.updates, { where: new Where() }); - }).to.throw(); + await expect(this.User.update(this.updates, { where: new Where() })).to.be.rejected; }); }); }); diff --git a/test/unit/model/upsert.test.js b/test/unit/model/upsert.test.js index e59b9851665b..85e42bbd8784 100644 --- a/test/unit/model/upsert.test.js +++ b/test/unit/model/upsert.test.js @@ -43,7 +43,7 @@ describe(Support.getTestDialectTeaser('Model'), () => { beforeEach(function() { this.query = sinon.stub(current, 'query').resolves(); - this.stub = sinon.stub(current.getQueryInterface(), 'upsert').resolves([true, undefined]); + this.stub = sinon.stub(current.getQueryInterface(), 'upsert').resolves([this.User.build(), true]); }); afterEach(function() { @@ -51,48 +51,45 @@ describe(Support.getTestDialectTeaser('Model'), () => { this.stub.restore(); }); - it('skip validations for missing fields', function() { - return expect(this.User.upsert({ + it('skip validations for missing fields', async function() { + await expect(this.User.upsert({ name: 'Grumpy Cat' })).not.to.be.rejectedWith(Sequelize.ValidationError); }); - it('creates new record with correct field names', function() { - return this.User + it('creates new record with correct field names', async function() { + await this.User .upsert({ name: 'Young Cat', virtualValue: 999 - }) - .then(() => { - expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ - 'name', 'value', 'created_at', 'updatedAt' - ]); }); + + expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ + 'name', 'value', 'created_at', 'updatedAt' + ]); }); - it('creates new record with timestamps disabled', function() { - return this.UserNoTime + it('creates new record with timestamps disabled', async function() { + await this.UserNoTime .upsert({ name: 'Young Cat' - }) - .then(() => { - expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ - 'name' - ]); }); + + expect(Object.keys(this.stub.getCall(0).args[1])).to.deep.equal([ + 'name' + ]); }); - it('updates all changed fields by default', function() { - return this.User + it('updates all changed fields by default', async function() { + await this.User .upsert({ name: 'Old Cat', virtualValue: 111 - }) - .then(() => { - expect(Object.keys(this.stub.getCall(0).args[2])).to.deep.equal([ - 'name', 'value', 'updatedAt' - ]); }); + + expect(Object.keys(this.stub.getCall(0).args[2])).to.deep.equal([ + 'name', 'value', 'updatedAt' + ]); }); }); } diff --git a/test/unit/model/validation.test.js b/test/unit/model/validation.test.js index f9d8bc971025..56942e7f281e 100644 --- a/test/unit/model/validation.test.js +++ b/test/unit/model/validation.test.js @@ -4,11 +4,9 @@ const chai = require('chai'), sinon = require('sinon'), expect = chai.expect, Sequelize = require('../../../index'), - Promise = Sequelize.Promise, Op = Sequelize.Op, Support = require('../support'), - current = Support.sequelize, - config = require('../../config/config'); + current = Support.sequelize; describe(Support.getTestDialectTeaser('InstanceValidator'), () => { @@ -182,31 +180,30 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { const applyFailTest = function applyFailTest(validatorDetails, i, validator) { const failingValue = validatorDetails.fail[i]; - it(`correctly specifies an instance as invalid using a value of "${failingValue}" for the validation "${validator}"`, function() { + it(`correctly specifies an instance as invalid using a value of "${failingValue}" for the validation "${validator}"`, async function() { const validations = {}, message = `${validator}(${failingValue})`; validations[validator] = validatorDetails.spec || {}; validations[validator].msg = message; - const UserFail = this.sequelize.define(`User${config.rand()}`, { + const UserFail = this.sequelize.define(`User${Support.rand()}`, { name: { type: Sequelize.STRING, validate: validations } }); - const failingUser = new UserFail({ name: failingValue }); + const failingUser = UserFail.build({ name: failingValue }); - return expect(failingUser.validate()).to.be.rejected.then(_errors => { - expect(_errors.get('name')[0].message).to.equal(message); - expect(_errors.get('name')[0].value).to.equal(failingValue); - }); + const _errors = await expect(failingUser.validate()).to.be.rejected; + expect(_errors.get('name')[0].message).to.equal(message); + expect(_errors.get('name')[0].value).to.equal(failingValue); }); }, applyPassTest = function applyPassTest(validatorDetails, j, validator, type) { const succeedingValue = validatorDetails.pass[j]; - it(`correctly specifies an instance as valid using a value of "${succeedingValue}" for the validation "${validator}"`, function() { + it(`correctly specifies an instance as valid using a value of "${succeedingValue}" for the validation "${validator}"`, async function() { const validations = {}, message = `${validator}(${succeedingValue})`; @@ -221,14 +218,14 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { validations[validator] = true; } - const UserSuccess = this.sequelize.define(`User${config.rand()}`, { + const UserSuccess = this.sequelize.define(`User${Support.rand()}`, { name: { type: Sequelize.STRING, validate: validations } }); - const successfulUser = new UserSuccess({ name: succeedingValue }); - return expect(successfulUser.validate()).not.to.be.rejected; + const successfulUser = UserSuccess.build({ name: succeedingValue }); + await expect(successfulUser.validate()).not.to.be.rejected; }); }; @@ -273,7 +270,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').callsFake(() => new Promise.resolve([new User({}), 1])); + this.stub = sinon.stub(current, 'query').callsFake(async () => Promise.resolve([User.build({}), 1])); }); after(function() { @@ -282,74 +279,70 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should not throw', () => { describe('create', () => { - it('should allow number as a string', () => { - return expect(User.create({ + it('should allow number as a string', async () => { + await expect(User.create({ age: '12' })).not.to.be.rejected; }); - it('should allow decimal as a string', () => { - return expect(User.create({ + it('should allow decimal as a string', async () => { + await expect(User.create({ number: '12.6' })).not.to.be.rejected; }); - it('should allow dates as a string', () => { - return expect(User.findOne({ + it('should allow dates as a string', async () => { + await expect(User.findOne({ where: { date: '2000-12-16' } })).not.to.be.rejected; }); - it('should allow decimal big numbers as a string', () => { - return expect(User.create({ + it('should allow decimal big numbers as a string', async () => { + await expect(User.create({ number: '2321312301230128391820831289123012' })).not.to.be.rejected; }); - it('should allow decimal as scientific notation', () => { - return Promise.join( - expect(User.create({ - number: '2321312301230128391820e219' - })).not.to.be.rejected, - expect(User.create({ - number: '2321312301230128391820e+219' - })).not.to.be.rejected, - expect(User.create({ - number: '2321312301230128391820f219' - })).to.be.rejected - ); + it('should allow decimal as scientific notation', async () => { + await Promise.all([expect(User.create({ + number: '2321312301230128391820e219' + })).not.to.be.rejected, expect(User.create({ + number: '2321312301230128391820e+219' + })).not.to.be.rejected, expect(User.create({ + number: '2321312301230128391820f219' + })).to.be.rejected]); }); - it('should allow string as a number', () => { - return expect(User.create({ + it('should allow string as a number', async () => { + await expect(User.create({ name: 12 })).not.to.be.rejected; }); - it('should allow 0/1 as a boolean', () => { - return expect(User.create({ + it('should allow 0/1 as a boolean', async () => { + await expect(User.create({ awesome: 1 })).not.to.be.rejected; }); - it('should allow 0/1 string as a boolean', () => { - return expect(User.create({ + it('should allow 0/1 string as a boolean', async () => { + await expect(User.create({ awesome: '1' })).not.to.be.rejected; }); - it('should allow true/false string as a boolean', () => { - return expect(User.create({ + it('should allow true/false string as a boolean', async () => { + await expect(User.create({ awesome: 'true' })).not.to.be.rejected; }); }); describe('findAll', () => { - it('should allow $in', () => { - return expect(User.findAll({ + it('should allow $in', async () => { + await expect(User.findAll({ where: { name: { [Op.like]: { @@ -360,8 +353,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { })).not.to.be.rejected; }); - it('should allow $like for uuid', () => { - return expect(User.findAll({ + it('should allow $like for uuid', async () => { + await expect(User.findAll({ where: { uid: { [Op.like]: '12345678%' @@ -375,8 +368,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should throw validationerror', () => { describe('create', () => { - it('should throw when passing string', () => { - return expect(User.create({ + it('should throw when passing string', async () => { + await expect(User.create({ age: 'jan' })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -393,8 +386,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('should throw when passing decimal', () => { - return expect(User.create({ + it('should throw when passing decimal', async () => { + await expect(User.create({ age: 4.5 })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -413,8 +406,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); describe('update', () => { - it('should throw when passing string', () => { - return expect(User.update({ + it('should throw when passing string', async () => { + await expect(User.update({ age: 'jan' }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -431,8 +424,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); }); - it('should throw when passing decimal', () => { - return expect(User.update({ + it('should throw when passing decimal', async () => { + await expect(User.update({ age: 4.5 }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError) .which.eventually.have.property('errors') @@ -480,7 +473,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); before(function() { - this.stub = sinon.stub(current, 'query').resolves([new User(), 1]); + this.stub = sinon.stub(current, 'query').resolves([User.build(), 1]); }); after(function() { @@ -489,8 +482,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should not throw', () => { describe('create', () => { - it('custom validation functions are successful', () => { - return expect(User.create({ + it('custom validation functions are successful', async () => { + await expect(User.create({ age: 1, name: 'noerror' })).not.to.be.rejected; @@ -498,8 +491,8 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { }); describe('update', () => { - it('custom validation functions are successful', () => { - return expect(User.update({ + it('custom validation functions are successful', async () => { + await expect(User.update({ age: 1, name: 'noerror' }, { where: {} })).not.to.be.rejected; @@ -510,28 +503,28 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should throw validationerror', () => { describe('create', () => { - it('custom attribute validation function fails', () => { - return expect(User.create({ + it('custom attribute validation function fails', async () => { + await expect(User.create({ age: -1 })).to.be.rejectedWith(Sequelize.ValidationError); }); - it('custom model validation function fails', () => { - return expect(User.create({ + it('custom model validation function fails', async () => { + await expect(User.create({ name: 'error' })).to.be.rejectedWith(Sequelize.ValidationError); }); }); describe('update', () => { - it('custom attribute validation function fails', () => { - return expect(User.update({ + it('custom attribute validation function fails', async () => { + await expect(User.update({ age: -1 }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); }); - it('when custom model validation function fails', () => { - return expect(User.update({ + it('when custom model validation function fails', async () => { + await expect(User.update({ name: 'error' }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -545,17 +538,16 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { name: Sequelize.STRING }, { validate: { - customFn() { + async customFn() { if (this.get('name') === 'error') { - return Promise.reject(new Error('Error from model validation promise')); + throw new Error('Error from model validation promise'); } - return Promise.resolve(); } } }); before(function() { - this.stub = sinon.stub(current, 'query').resolves([new User(), 1]); + this.stub = sinon.stub(current, 'query').resolves([User.build(), 1]); }); after(function() { @@ -564,16 +556,16 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should not throw', () => { describe('create', () => { - it('custom model validation functions are successful', () => { - return expect(User.create({ + it('custom model validation functions are successful', async () => { + await expect(User.create({ name: 'noerror' })).not.to.be.rejected; }); }); describe('update', () => { - it('custom model validation functions are successful', () => { - return expect(User.update({ + it('custom model validation functions are successful', async () => { + await expect(User.update({ name: 'noerror' }, { where: {} })).not.to.be.rejected; }); @@ -583,16 +575,16 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { describe('should throw validationerror', () => { describe('create', () => { - it('custom model validation function fails', () => { - return expect(User.create({ + it('custom model validation function fails', async () => { + await expect(User.create({ name: 'error' })).to.be.rejectedWith(Sequelize.ValidationError); }); }); describe('update', () => { - it('when custom model validation function fails', () => { - return expect(User.update({ + it('when custom model validation function fails', async () => { + await expect(User.update({ name: 'error' }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); }); @@ -624,7 +616,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - this.stub = sinon.stub(current, 'query').resolves([new this.User(), 1]); + this.stub = sinon.stub(current, 'query').resolves([this.User.build(), 1]); }); after(function() { @@ -636,21 +628,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 10, name: null - })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 10, name: null - }, { where: {} })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + }, { where: {} })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); }); @@ -659,21 +651,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 11, name: null - })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.been.calledOnce; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 11, name: null - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.been.calledOnce; }); }); @@ -693,7 +685,7 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { } }); - this.stub = sinon.stub(current, 'query').resolves([new this.User(), 1]); + this.stub = sinon.stub(current, 'query').resolves([this.User.build(), 1]); }); after(function() { @@ -705,21 +697,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 99, name: null - })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.not.been.called; - }); + })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.not.been.called; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 99, name: null - }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError).then(() => { - return expect(this.customValidator).to.have.not.been.called; - }); + }, { where: {} })).to.be.rejectedWith(Sequelize.ValidationError); + + await expect(this.customValidator).to.have.not.been.called; }); }); @@ -728,21 +720,21 @@ describe(Support.getTestDialectTeaser('InstanceValidator'), () => { this.customValidator.resetHistory(); }); - it('on create', function() { - return expect(this.User.create({ + it('on create', async function() { + await expect(this.User.create({ age: 99, name: 'foo' - })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); - it('on update', function() { - return expect(this.User.update({ + it('on update', async function() { + await expect(this.User.update({ age: 99, name: 'foo' - }, { where: {} })).not.to.be.rejected.then(() => { - return expect(this.customValidator).to.have.been.calledOnce; - }); + }, { where: {} })).not.to.be.rejected; + + await expect(this.customValidator).to.have.been.calledOnce; }); }); diff --git a/test/unit/promise.test.js b/test/unit/promise.test.js deleted file mode 100644 index effbd76b70eb..000000000000 --- a/test/unit/promise.test.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -const chai = require('chai'), - expect = chai.expect, - Support = require('./support'), - Sequelize = Support.Sequelize, - Promise = Sequelize.Promise, - Bluebird = require('bluebird'); - -describe('Promise', () => { - it('should be an independent copy of bluebird library', () => { - expect(Promise.prototype.then).to.be.a('function'); - expect(Promise).to.not.equal(Bluebird); - expect(Promise.prototype).to.not.equal(Bluebird.prototype); - }); -}); diff --git a/test/unit/sql/add-column.test.js b/test/unit/sql/add-column.test.js index b27b928976aa..b4179ed427d4 100644 --- a/test/unit/sql/add-column.test.js +++ b/test/unit/sql/add-column.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; if (['mysql', 'mariadb'].includes(current.dialect.name)) { diff --git a/test/unit/sql/add-constraint.test.js b/test/unit/sql/add-constraint.test.js index 2a0875ac138f..239d8d7052e2 100644 --- a/test/unit/sql/add-constraint.test.js +++ b/test/unit/sql/add-constraint.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; const Op = Support.Sequelize.Op; const expect = require('chai').expect; const sinon = require('sinon'); @@ -157,6 +157,25 @@ if (current.dialect.supports.constraints.addConstraint) { }); }); + it('supports composite keys', () => { + expectsql( + sql.addConstraintQuery('myTable', { + type: 'foreign key', + fields: ['myColumn', 'anotherColumn'], + references: { + table: 'myOtherTable', + fields: ['id1', 'id2'] + }, + onUpdate: 'cascade', + onDelete: 'cascade' + }), + { + default: + 'ALTER TABLE [myTable] ADD CONSTRAINT [myTable_myColumn_anotherColumn_myOtherTable_fk] FOREIGN KEY ([myColumn], [anotherColumn]) REFERENCES [myOtherTable] ([id1], [id2]) ON UPDATE CASCADE ON DELETE CASCADE;' + } + ); + }); + it('uses onDelete, onUpdate', () => { expectsql(sql.addConstraintQuery('myTable', { type: 'foreign key', diff --git a/test/unit/sql/create-schema.test.js b/test/unit/sql/create-schema.test.js index 7dcbc87473f4..b7e38cb622b6 100644 --- a/test/unit/sql/create-schema.test.js +++ b/test/unit/sql/create-schema.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const expectsql = Support.expectsql; const current = Support.sequelize; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; describe(Support.getTestDialectTeaser('SQL'), () => { if (current.dialect.name === 'postgres') { diff --git a/test/unit/sql/create-table.test.js b/test/unit/sql/create-table.test.js index 86af72afde72..53fa2623482a 100644 --- a/test/unit/sql/create-table.test.js +++ b/test/unit/sql/create-table.test.js @@ -4,7 +4,8 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator, + _ = require('lodash'); describe(Support.getTestDialectTeaser('SQL'), () => { describe('createTable', () => { @@ -85,29 +86,23 @@ describe(Support.getTestDialectTeaser('SQL'), () => { if (current.dialect.name === 'postgres') { describe('IF NOT EXISTS version check', () => { - let backup; - before(() => { - backup = sql.sequelize.options.databaseVersion; - }); - - after(() => { - sql.sequelize.options.databaseVersion = backup; - }); + const modifiedSQL = _.clone(sql); + const createTableQueryModified = sql.createTableQuery.bind(modifiedSQL); it('it will not have IF NOT EXISTS for version 9.0 or below', () => { - sql.sequelize.options.databaseVersion = '9.0.0'; - expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { + modifiedSQL.sequelize.options.databaseVersion = '9.0.0'; + expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { postgres: 'CREATE TABLE "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' }); }); it('it will have IF NOT EXISTS for version 9.1 or above', () => { - sql.sequelize.options.databaseVersion = '9.1.0'; - expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { + modifiedSQL.sequelize.options.databaseVersion = '9.1.0'; + expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' }); }); it('it will have IF NOT EXISTS for default version', () => { - sql.sequelize.options.databaseVersion = 0; - expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { + modifiedSQL.sequelize.options.databaseVersion = 0; + expectsql(createTableQueryModified(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), { postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));' }); }); diff --git a/test/unit/sql/delete.test.js b/test/unit/sql/delete.test.js index 54bcbc1e5ba1..7c671eda050e 100644 --- a/test/unit/sql/delete.test.js +++ b/test/unit/sql/delete.test.js @@ -7,7 +7,7 @@ const Support = require('../support'), expectsql = Support.expectsql, current = Support.sequelize, Sequelize = Support.Sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/enum.test.js b/test/unit/sql/enum.test.js index f26105172e6d..d9edb6fa4587 100644 --- a/test/unit/sql/enum.test.js +++ b/test/unit/sql/enum.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, expect = require('chai').expect; diff --git a/test/unit/sql/generateJoin.test.js b/test/unit/sql/generateJoin.test.js old mode 100755 new mode 100644 index 1d97594ae752..ba29f63a5924 --- a/test/unit/sql/generateJoin.test.js +++ b/test/unit/sql/generateJoin.test.js @@ -7,7 +7,7 @@ const Support = require('../support'), _ = require('lodash'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -172,6 +172,22 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } ); + testsql( + 'include[0]', + { + model: User, + subQuery: true, + include: [ + { + association: User.Company, right: true + } + ] + }, + { + default: `${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [company] AS [Company] ON [User].[companyId] = [Company].[id]` + } + ); + testsql( 'include[0].include[0]', { diff --git a/test/unit/sql/get-constraint-snippet.test.js b/test/unit/sql/get-constraint-snippet.test.js index 9e757b41db94..997d45e53ba2 100644 --- a/test/unit/sql/get-constraint-snippet.test.js +++ b/test/unit/sql/get-constraint-snippet.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; const expect = require('chai').expect; const Op = Support.Sequelize.Op; diff --git a/test/unit/sql/group.test.js b/test/unit/sql/group.test.js new file mode 100644 index 000000000000..4059c6477350 --- /dev/null +++ b/test/unit/sql/group.test.js @@ -0,0 +1,54 @@ +'use strict'; + +const Support = require('../support'), + DataTypes = require('../../../lib/data-types'), + util = require('util'), + expectsql = Support.expectsql, + current = Support.sequelize, + sql = current.dialect.queryGenerator; + + +describe(Support.getTestDialectTeaser('SQL'), () => { + describe('group', () => { + const testsql = function(options, expectation) { + const model = options.model; + + it(util.inspect(options, { depth: 2 }), () => { + return expectsql( + sql.selectQuery( + options.table || model && model.getTableName(), + options, + options.model + ), + expectation + ); + }); + }; + + const User = Support.sequelize.define('User', { + name: { + type: DataTypes.STRING, + field: 'name', + allowNull: false + } + }); + + testsql({ + model: User, + group: ['name'] + }, { + default: 'SELECT * FROM `Users` AS `User` GROUP BY `name`;', + postgres: 'SELECT * FROM "Users" AS "User" GROUP BY "name";', + mssql: 'SELECT * FROM [Users] AS [User] GROUP BY [name];' + }); + + testsql({ + model: User, + group: [] + }, { + default: 'SELECT * FROM `Users` AS `User`;', + postgres: 'SELECT * FROM "Users" AS "User";', + mssql: 'SELECT * FROM [Users] AS [User];' + }); + }); +}); diff --git a/test/unit/sql/index.test.js b/test/unit/sql/index.test.js index a54b6dd32327..18f6161c3ad3 100644 --- a/test/unit/sql/index.test.js +++ b/test/unit/sql/index.test.js @@ -3,7 +3,7 @@ const Support = require('../support'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Support.Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -171,6 +171,67 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); } + + if (current.dialect.supports.index.operator) { + it('operator with multiple fields', () => { + expectsql(sql.addIndexQuery('table', { + fields: ['column1', 'column2'], + using: 'gist', + operator: 'inet_ops' + }), { + postgres: 'CREATE INDEX "table_column1_column2" ON "table" USING gist ("column1" inet_ops, "column2" inet_ops)' + }); + }); + it('operator in fields', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'column', + operator: 'inet_ops' + }], + using: 'gist' + }), { + postgres: 'CREATE INDEX "table_column" ON "table" USING gist ("column" inet_ops)' + }); + }); + it('operator in fields with order', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'column', + order: 'DESC', + operator: 'inet_ops' + }], + using: 'gist' + }), { + postgres: 'CREATE INDEX "table_column" ON "table" USING gist ("column" inet_ops DESC)' + }); + }); + it('operator in multiple fields #1', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'column1', + order: 'DESC', + operator: 'inet_ops' + }, 'column2'], + using: 'gist' + }), { + postgres: 'CREATE INDEX "table_column1_column2" ON "table" USING gist ("column1" inet_ops DESC, "column2")' + }); + }); + it('operator in multiple fields #2', () => { + expectsql(sql.addIndexQuery('table', { + fields: [{ + name: 'path', + operator: 'text_pattern_ops' + }, 'level', { + name: 'name', + operator: 'varchar_pattern_ops' + }], + using: 'btree' + }), { + postgres: 'CREATE INDEX "table_path_level_name" ON "table" USING btree ("path" text_pattern_ops, "level", "name" varchar_pattern_ops)' + }); + }); + } }); describe('removeIndex', () => { diff --git a/test/unit/sql/insert.test.js b/test/unit/sql/insert.test.js index 1cefd2bdaffd..c9ba66b98e9e 100644 --- a/test/unit/sql/insert.test.js +++ b/test/unit/sql/insert.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -28,14 +28,34 @@ describe(Support.getTestDialectTeaser('SQL'), () => { expectsql(sql.insertQuery(User.tableName, { user_name: 'triggertest' }, User.rawAttributes, options), { query: { - mssql: 'declare @tmp table ([id] INTEGER,[user_name] NVARCHAR(255));INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id],INSERTED.[user_name] into @tmp VALUES ($1);select * from @tmp;', - postgres: 'INSERT INTO "users" ("user_name") VALUES ($1) RETURNING *;', + mssql: 'DECLARE @tmp TABLE ([id] INTEGER,[user_name] NVARCHAR(255)); INSERT INTO [users] ([user_name]) OUTPUT INSERTED.[id],INSERTED.[user_name] INTO @tmp VALUES ($1); SELECT * FROM @tmp;', + postgres: 'INSERT INTO "users" ("user_name") VALUES ($1) RETURNING "id","user_name";', default: 'INSERT INTO `users` (`user_name`) VALUES ($1);' }, bind: ['triggertest'] }); }); + + it('allow insert primary key with 0', () => { + const M = Support.sequelize.define('m', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + } + }); + + expectsql(sql.insertQuery(M.tableName, { id: 0 }, M.rawAttributes), + { + query: { + mssql: 'SET IDENTITY_INSERT [ms] ON; INSERT INTO [ms] ([id]) VALUES ($1); SET IDENTITY_INSERT [ms] OFF;', + postgres: 'INSERT INTO "ms" ("id") VALUES ($1);', + default: 'INSERT INTO `ms` (`id`) VALUES ($1);' + }, + bind: [0] + }); + }); }); describe('dates', () => { @@ -52,7 +72,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { timestamps: false }); - expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20)) }, User.rawAttributes, {}), + expectsql(timezoneSequelize.dialect.queryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20)) }, User.rawAttributes, {}), { query: { postgres: 'INSERT INTO "users" ("date") VALUES ($1);', @@ -81,7 +101,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { timestamps: false }); - expectsql(timezoneSequelize.dialect.QueryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89)) }, User.rawAttributes, {}), + expectsql(timezoneSequelize.dialect.queryGenerator.insertQuery(User.tableName, { date: new Date(Date.UTC(2015, 0, 20, 1, 2, 3, 89)) }, User.rawAttributes, {}), { query: { postgres: 'INSERT INTO "users" ("date") VALUES ($1);', @@ -161,5 +181,24 @@ describe(Support.getTestDialectTeaser('SQL'), () => { sqlite: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\') ON CONFLICT (`user_name`) DO UPDATE SET `user_name`=EXCLUDED.`user_name`,`pass_word`=EXCLUDED.`pass_word`,`updated_at`=EXCLUDED.`updated_at`;' }); }); + + it('allow bulk insert primary key with 0', () => { + const M = Support.sequelize.define('m', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + } + }); + + expectsql(sql.bulkInsertQuery(M.tableName, [{ id: 0 }, { id: null }], {}, M.fieldRawAttributesMap), + { + query: { + mssql: 'SET IDENTITY_INSERT [ms] ON; INSERT INTO [ms] DEFAULT VALUES;INSERT INTO [ms] ([id]) VALUES (0),(NULL);; SET IDENTITY_INSERT [ms] OFF;', + postgres: 'INSERT INTO "ms" ("id") VALUES (0),(DEFAULT);', + default: 'INSERT INTO `ms` (`id`) VALUES (0),(NULL);' + } + }); + }); }); }); diff --git a/test/unit/sql/json.test.js b/test/unit/sql/json.test.js index 3b01548c7ef0..ac092134f1b7 100644 --- a/test/unit/sql/json.test.js +++ b/test/unit/sql/json.test.js @@ -6,7 +6,7 @@ const Support = require('../support'), expectsql = Support.expectsql, Sequelize = Support.Sequelize, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation if (current.dialect.supports.JSON) { diff --git a/test/unit/sql/offset-limit.test.js b/test/unit/sql/offset-limit.test.js index be3bef63cdab..5b5fc3afb36e 100644 --- a/test/unit/sql/offset-limit.test.js +++ b/test/unit/sql/offset-limit.test.js @@ -4,7 +4,7 @@ const Support = require('../support'), util = require('util'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -79,5 +79,14 @@ describe(Support.getTestDialectTeaser('SQL'), () => { mysql: " LIMIT '\\';DELETE FROM user', 10", mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY" }); + + testsql({ + limit: 10, + order: [], // When the order is an empty array, one is automagically prepended + model: { primaryKeyField: 'id', name: 'tableRef' } + }, { + default: ' LIMIT 10', + mssql: ' ORDER BY [tableRef].[id] OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY' + }); }); }); diff --git a/test/unit/sql/order.test.js b/test/unit/sql/order.test.js old mode 100755 new mode 100644 index 89e353d9f0d6..355c5e599b5c --- a/test/unit/sql/order.test.js +++ b/test/unit/sql/order.test.js @@ -8,7 +8,7 @@ const DataTypes = require('../../../lib/data-types'); const Model = require('../../../lib/model'); const expectsql = Support.expectsql; const current = Support.sequelize; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/remove-column.test.js b/test/unit/sql/remove-column.test.js index 87be3eb84a04..9b5d186513c0 100644 --- a/test/unit/sql/remove-column.test.js +++ b/test/unit/sql/remove-column.test.js @@ -3,7 +3,7 @@ const Support = require('../support'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation diff --git a/test/unit/sql/remove-constraint.test.js b/test/unit/sql/remove-constraint.test.js index c16d9f339fed..76920461d7bc 100644 --- a/test/unit/sql/remove-constraint.test.js +++ b/test/unit/sql/remove-constraint.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; if (current.dialect.supports.constraints.dropConstraint) { describe(Support.getTestDialectTeaser('SQL'), () => { diff --git a/test/unit/sql/select.test.js b/test/unit/sql/select.test.js old mode 100755 new mode 100644 index ad56827f6cf3..6aa33a5d8efe --- a/test/unit/sql/select.test.js +++ b/test/unit/sql/select.test.js @@ -8,7 +8,7 @@ const Support = require('../support'), expect = chai.expect, expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Support.Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -159,7 +159,6 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }) AS [user] ORDER BY [subquery_order_0] ASC;` }); - testsql({ table: User.getTableName(), model: User, @@ -374,7 +373,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); - it('include (subQuery alias)', () => { + it('include (right outer join)', () => { const User = Support.sequelize.define('User', { name: DataTypes.STRING, age: DataTypes.INTEGER @@ -389,31 +388,184 @@ describe(Support.getTestDialectTeaser('SQL'), () => { freezeTableName: true }); - User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'postaliasname' }); + User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); expectsql(sql.selectQuery('User', { - table: User.getTableName(), - model: User, attributes: ['name', 'age'], include: Model._validateIncludedElements({ include: [{ attributes: ['title'], association: User.Posts, + right: true + }], + model: User + }).include, + model: User + }, User), { + default: `SELECT [User].[name], [User].[age], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title] FROM [User] AS [User] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];` + }); + }); + + it('include through (right outer join)', () => { + const User = Support.sequelize.define('user', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + field: 'id_user' + } + }); + const Project = Support.sequelize.define('project', { + title: DataTypes.STRING + }); + + const ProjectUser = Support.sequelize.define('project_user', { + userId: { + type: DataTypes.INTEGER, + field: 'user_id' + }, + projectId: { + type: DataTypes.INTEGER, + field: 'project_id' + } + }, { timestamps: false }); + + User.Projects = User.belongsToMany(Project, { through: ProjectUser }); + Project.belongsToMany(User, { through: ProjectUser }); + + expectsql(sql.selectQuery('User', { + attributes: ['id_user', 'id'], + include: Model._validateIncludedElements({ + include: [{ + model: Project, + right: true + }], + model: User + }).include, + model: User + }, User), { + default: `SELECT [user].[id_user], [user].[id], [projects].[id] AS [projects.id], [projects].[title] AS [projects.title], [projects].[createdAt] AS [projects.createdAt], [projects].[updatedAt] AS [projects.updatedAt], [projects->project_user].[user_id] AS [projects.project_user.userId], [projects->project_user].[project_id] AS [projects.project_user.projectId] FROM [User] AS [user] ${current.dialect.supports['RIGHT JOIN'] ? 'RIGHT' : 'LEFT'} OUTER JOIN ( [project_users] AS [projects->project_user] INNER JOIN [projects] AS [projects] ON [projects].[id] = [projects->project_user].[project_id]) ON [user].[id_user] = [projects->project_user].[user_id];` + }); + }); + + describe('include (subQuery alias)', () => { + const User = Support.sequelize.define('User', { + name: DataTypes.STRING, + age: DataTypes.INTEGER + }, + { + freezeTableName: true + }); + const Post = Support.sequelize.define('Post', { + title: DataTypes.STRING + }, + { + freezeTableName: true + }); + + User.Posts = User.hasMany(Post, { foreignKey: 'user_id', as: 'postaliasname' }); + + it('w/o filters', () => { + expectsql(sql.selectQuery('User', { + table: User.getTableName(), + model: User, + attributes: ['name', 'age'], + include: Model._validateIncludedElements({ + include: [{ + attributes: ['title'], + association: User.Posts, + subQuery: true, + required: true + }], + as: 'User' + }).include, + subQuery: true + }, User), { + default: 'SELECT [User].* FROM ' + + '(SELECT [User].[name], [User].[age], [User].[id] AS [id], [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM [User] AS [User] ' + + 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id] ' + + `WHERE ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id])${sql.addLimitAndOffset({ limit: 1, tableAs: 'postaliasname' }, User)} ) IS NOT NULL) AS [User];` + }); + }); + + it('w/ nested column filter', () => { + expectsql(sql.selectQuery('User', { + table: User.getTableName(), + model: User, + attributes: ['name', 'age'], + where: { '$postaliasname.title$': 'test' }, + include: Model._validateIncludedElements({ + include: [{ + attributes: ['title'], + association: User.Posts, + subQuery: true, + required: true + }], + as: 'User' + }).include, + subQuery: true + }, User), { + default: 'SELECT [User].* FROM ' + + '(SELECT [User].[name], [User].[age], [User].[id] AS [id], [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM [User] AS [User] ' + + 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id] ' + + `WHERE [postaliasname].[title] = ${sql.escape('test')} AND ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id])${sql.addLimitAndOffset({ limit: 1, tableAs: 'postaliasname' }, User)} ) IS NOT NULL) AS [User];` + }); + }); + }); + + it('include w/ subQuery + nested filter + paging', () => { + const User = Support.sequelize.define('User', { + scopeId: DataTypes.INTEGER + }); + + const Company = Support.sequelize.define('Company', { + name: DataTypes.STRING, + public: DataTypes.BOOLEAN, + scopeId: DataTypes.INTEGER + }); + + const Profession = Support.sequelize.define('Profession', { + name: DataTypes.STRING, + scopeId: DataTypes.INTEGER + }); + + User.Company = User.belongsTo(Company, { foreignKey: 'companyId' }); + User.Profession = User.belongsTo(Profession, { foreignKey: 'professionId' }); + Company.Users = Company.hasMany(User, { as: 'Users', foreignKey: 'companyId' }); + Profession.Users = Profession.hasMany(User, { as: 'Users', foreignKey: 'professionId' }); + + expectsql(sql.selectQuery('Company', { + table: Company.getTableName(), + model: Company, + attributes: ['name', 'public'], + where: { '$Users.Profession.name$': 'test', [Op.and]: { scopeId: [42] } }, + include: Model._validateIncludedElements({ + include: [{ + association: Company.Users, + attributes: [], + include: [{ + association: User.Profession, + attributes: [], + required: true + }], subQuery: true, required: true }], - as: 'User' + model: Company }).include, + limit: 5, + offset: 0, subQuery: true - }, User), { - default: 'SELECT [User].*, [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM ' + - '(SELECT [User].[name], [User].[age], [User].[id] AS [id] FROM [User] AS [User] ' + - 'WHERE ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id]) LIMIT 1 ) IS NOT NULL) AS [User] ' + - 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id];', - mssql: 'SELECT [User].*, [postaliasname].[id] AS [postaliasname.id], [postaliasname].[title] AS [postaliasname.title] FROM ' + - '(SELECT [User].[name], [User].[age], [User].[id] AS [id] FROM [User] AS [User] ' + - 'WHERE ( SELECT [user_id] FROM [Post] AS [postaliasname] WHERE ([postaliasname].[user_id] = [User].[id]) ORDER BY [postaliasname].[id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ) IS NOT NULL) AS [User] ' + - 'INNER JOIN [Post] AS [postaliasname] ON [User].[id] = [postaliasname].[user_id];' + }, Company), { + default: 'SELECT [Company].* FROM (' + + 'SELECT [Company].[name], [Company].[public], [Company].[id] AS [id] FROM [Company] AS [Company] ' + + 'INNER JOIN [Users] AS [Users] ON [Company].[id] = [Users].[companyId] ' + + 'INNER JOIN [Professions] AS [Users->Profession] ON [Users].[professionId] = [Users->Profession].[id] ' + + `WHERE ([Company].[scopeId] IN (42)) AND [Users->Profession].[name] = ${sql.escape('test')} AND ( ` + + 'SELECT [Users].[companyId] FROM [Users] AS [Users] ' + + 'INNER JOIN [Professions] AS [Profession] ON [Users].[professionId] = [Profession].[id] ' + + `WHERE ([Users].[companyId] = [Company].[id])${sql.addLimitAndOffset({ limit: 1, tableAs: 'Users' }, User)} ` + + `) IS NOT NULL${sql.addLimitAndOffset({ limit: 5, offset: 0, tableAs: 'Company' }, Company)}) AS [Company];` }); }); @@ -674,6 +826,42 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); + it('attributes with dot notation', () => { + const User = Support.sequelize.define('User', { + name: DataTypes.STRING, + age: DataTypes.INTEGER, + 'status.label': DataTypes.STRING + }, + { + freezeTableName: true + }); + const Post = Support.sequelize.define('Post', { + title: DataTypes.STRING, + 'status.label': DataTypes.STRING + }, + { + freezeTableName: true + }); + + User.Posts = User.hasMany(Post, { foreignKey: 'user_id' }); + + expectsql(sql.selectQuery('User', { + attributes: ['name', 'age', 'status.label'], + include: Model._validateIncludedElements({ + include: [{ + attributes: ['title', 'status.label'], + association: User.Posts + }], + model: User + }).include, + model: User, + dotNotation: true + }, User), { + default: 'SELECT [User].[name], [User].[age], [User].[status.label], [Posts].[id] AS [Posts.id], [Posts].[title] AS [Posts.title], [Posts].[status.label] AS [Posts.status.label] FROM [User] AS [User] LEFT OUTER JOIN [Post] AS [Posts] ON [User].[id] = [Posts].[user_id];', + postgres: 'SELECT "User".name, "User".age, "User"."status.label", Posts.id AS "Posts.id", Posts.title AS "Posts.title", Posts."status.label" AS "Posts.status.label" FROM "User" AS "User" LEFT OUTER JOIN Post AS Posts ON "User".id = Posts.user_id;' + }); + }); + }); describe('raw query', () => { diff --git a/test/unit/sql/show-constraints.test.js b/test/unit/sql/show-constraints.test.js index 7b0d4d487552..f6cbc239fddb 100644 --- a/test/unit/sql/show-constraints.test.js +++ b/test/unit/sql/show-constraints.test.js @@ -3,7 +3,7 @@ const Support = require('../support'); const current = Support.sequelize; const expectsql = Support.expectsql; -const sql = current.dialect.QueryGenerator; +const sql = current.dialect.queryGenerator; describe(Support.getTestDialectTeaser('SQL'), () => { describe('showConstraint', () => { diff --git a/test/unit/sql/update.test.js b/test/unit/sql/update.test.js index 1cdd9e1d631f..71f99050b380 100644 --- a/test/unit/sql/update.test.js +++ b/test/unit/sql/update.test.js @@ -4,12 +4,36 @@ const Support = require('../support'), DataTypes = require('../../../lib/data-types'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator; + sql = current.dialect.queryGenerator; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation describe(Support.getTestDialectTeaser('SQL'), () => { describe('update', () => { + it('supports returning false', () => { + const User = Support.sequelize.define('user', { + username: { + type: DataTypes.STRING, + field: 'user_name' + } + }, { + timestamps: false + }); + + const options = { + returning: false + }; + expectsql(sql.updateQuery(User.tableName, { user_name: 'triggertest' }, { id: 2 }, options, User.rawAttributes), + { + query: { + default: 'UPDATE [users] SET [user_name]=$1 WHERE [id] = $2' + }, + bind: { + default: ['triggertest', 2] + } + }); + }); + it('with temp table for trigger', () => { const User = Support.sequelize.define('user', { username: { @@ -28,8 +52,8 @@ describe(Support.getTestDialectTeaser('SQL'), () => { expectsql(sql.updateQuery(User.tableName, { user_name: 'triggertest' }, { id: 2 }, options, User.rawAttributes), { query: { - mssql: 'declare @tmp table ([id] INTEGER,[user_name] NVARCHAR(255)); UPDATE [users] SET [user_name]=$1 OUTPUT INSERTED.[id],INSERTED.[user_name] into @tmp WHERE [id] = $2;select * from @tmp', - postgres: 'UPDATE "users" SET "user_name"=$1 WHERE "id" = $2 RETURNING *', + mssql: 'DECLARE @tmp TABLE ([id] INTEGER,[user_name] NVARCHAR(255)); UPDATE [users] SET [user_name]=$1 OUTPUT INSERTED.[id],INSERTED.[user_name] INTO @tmp WHERE [id] = $2; SELECT * FROM @tmp', + postgres: 'UPDATE "users" SET "user_name"=$1 WHERE "id" = $2 RETURNING "id","user_name"', default: 'UPDATE `users` SET `user_name`=$1 WHERE `id` = $2' }, bind: { @@ -38,8 +62,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { }); }); - - it('Works with limit', () => { + it('works with limit', () => { const User = Support.sequelize.define('User', { username: { type: DataTypes.STRING @@ -53,7 +76,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { expectsql(sql.updateQuery(User.tableName, { username: 'new.username' }, { username: 'username' }, { limit: 1 }), { query: { - mssql: 'UPDATE TOP(1) [Users] SET [username]=$1 OUTPUT INSERTED.* WHERE [username] = $2', + mssql: 'UPDATE TOP(1) [Users] SET [username]=$1 WHERE [username] = $2', mariadb: 'UPDATE `Users` SET `username`=$1 WHERE `username` = $2 LIMIT 1', mysql: 'UPDATE `Users` SET `username`=$1 WHERE `username` = $2 LIMIT 1', sqlite: 'UPDATE `Users` SET `username`=$1 WHERE rowid IN (SELECT rowid FROM `Users` WHERE `username` = $2 LIMIT 1)', diff --git a/test/unit/sql/where.test.js b/test/unit/sql/where.test.js old mode 100755 new mode 100644 index 4d303130e09a..7739395f2d8e --- a/test/unit/sql/where.test.js +++ b/test/unit/sql/where.test.js @@ -7,7 +7,7 @@ const Support = require('../support'), _ = require('lodash'), expectsql = Support.expectsql, current = Support.sequelize, - sql = current.dialect.QueryGenerator, + sql = current.dialect.queryGenerator, Op = Support.Sequelize.Op; // Notice: [] will be replaced by dialect specific tick/quote character when there is not dialect specific expectation but only a default expectation @@ -54,8 +54,8 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: 'WHERE [User].[id] = 1' }); - it("{ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {schema: 'yolo', tableName: 'User'})) }", () => { - expectsql(sql.whereQuery({ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { schema: 'yolo', tableName: 'User' })) }), { + it("{ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.queryGenerator, {schema: 'yolo', tableName: 'User'})) }", () => { + expectsql(sql.whereQuery({ id: 1 }, { prefix: current.literal(sql.quoteTable.call(current.dialect.queryGenerator, { schema: 'yolo', tableName: 'User' })) }), { default: 'WHERE [yolo.User].[id] = 1', postgres: 'WHERE "yolo"."User"."id" = 1', mariadb: 'WHERE `yolo`.`User`.`id` = 1', @@ -441,6 +441,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: "[username] LIKE 'swagger%'", mssql: "[username] LIKE N'swagger%'" }); + + testsql('username', { + [Op.startsWith]: current.literal('swagger') + }, { + default: "[username] LIKE 'swagger%'", + mssql: "[username] LIKE N'swagger%'" + }); }); describe('Op.endsWith', () => { @@ -450,6 +457,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: "[username] LIKE '%swagger'", mssql: "[username] LIKE N'%swagger'" }); + + testsql('username', { + [Op.endsWith]: current.literal('swagger') + }, { + default: "[username] LIKE '%swagger'", + mssql: "[username] LIKE N'%swagger'" + }); }); describe('Op.substring', () => { @@ -459,6 +473,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { default: "[username] LIKE '%swagger%'", mssql: "[username] LIKE N'%swagger%'" }); + + testsql('username', { + [Op.substring]: current.literal('swagger') + }, { + default: "[username] LIKE '%swagger%'", + mssql: "[username] LIKE N'%swagger%'" + }); }); describe('Op.between', () => { @@ -919,7 +940,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => { field: { type: new DataTypes.JSONB() }, - prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, { tableName: 'User' })) + prefix: current.literal(sql.quoteTable.call(current.dialect.queryGenerator, { tableName: 'User' })) }, { mariadb: "(json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.nested.prop')) != 'None')", mysql: "(json_unquote(json_extract(`User`.`data`,'$.\\\"nested\\\".\\\"attribute\\\"')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.\\\"nested\\\".\\\"prop\\\"')) != 'None')", @@ -1185,6 +1206,20 @@ describe(Support.getTestDialectTeaser('SQL'), () => { } } + if (current.dialect.supports.TSVESCTOR) { + describe('Op.match', () => { + testsql( + 'username', + { + [Op.match]: Support.sequelize.fn('to_tsvector', 'swagger') + }, + { + postgres: "[username] @@ to_tsvector('swagger')" + } + ); + }); + } + describe('fn', () => { it('{name: this.sequelize.fn(\'LOWER\', \'DERP\')}', function() { expectsql(sql.whereQuery({ name: this.sequelize.fn('LOWER', 'DERP') }), { @@ -1228,5 +1263,13 @@ describe(Support.getTestDialectTeaser('SQL'), () => { current.where(current.fn('lower', current.col('name')), null)], { default: '(SUM([hours]) > 0 AND lower([name]) IS NULL)' }); + + testsql(current.where(current.col('hours'), Op.between, [0, 5]), { + default: '[hours] BETWEEN 0 AND 5' + }); + + testsql(current.where(current.col('hours'), Op.notBetween, [0, 5]), { + default: '[hours] NOT BETWEEN 0 AND 5' + }); }); }); diff --git a/test/unit/transaction.test.js b/test/unit/transaction.test.js index 4de9d67c9fe0..2ab1d1d877f3 100644 --- a/test/unit/transaction.test.js +++ b/test/unit/transaction.test.js @@ -33,7 +33,7 @@ describe('Transaction', () => { this.stubConnection.restore(); }); - it('should run auto commit query only when needed', function() { + it('should run auto commit query only when needed', async function() { const expectations = { all: [ 'START TRANSACTION;' @@ -45,9 +45,33 @@ describe('Transaction', () => { 'BEGIN TRANSACTION;' ] }; - return current.transaction(() => { + + await current.transaction(async () => { + expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); + }); + }); + + it('should set isolation level correctly', async function() { + const expectations = { + all: [ + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;', + 'START TRANSACTION;' + ], + postgres: [ + 'START TRANSACTION;', + 'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;' + ], + sqlite: [ + 'BEGIN DEFERRED TRANSACTION;', + 'PRAGMA read_uncommitted = ON;' + ], + mssql: [ + 'BEGIN TRANSACTION;' + ] + }; + + await current.transaction({ isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED }, async () => { expect(this.stub.args.map(arg => arg[0])).to.deep.equal(expectations[dialect] || expectations.all); - return Sequelize.Promise.resolve(); }); }); }); diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js index 5a6ee2a77ddd..3258834234c7 100644 --- a/test/unit/utils.test.js +++ b/test/unit/utils.test.js @@ -240,33 +240,9 @@ describe(Support.getTestDialectTeaser('Utils'), () => { }); }); - describe('stack', () => { - it('stack trace starts after call to Util.stack()', function this_here_test() { // eslint-disable-line - // We need a named function to be able to capture its trace - function a() { - return b(); - } - - function b() { - return c(); - } - - function c() { - return Utils.stack(); - } - - const stack = a(); - - expect(stack[0].getFunctionName()).to.eql('c'); - expect(stack[1].getFunctionName()).to.eql('b'); - expect(stack[2].getFunctionName()).to.eql('a'); - expect(stack[3].getFunctionName()).to.eql('this_here_test'); - }); - }); - describe('Sequelize.cast', () => { const sql = Support.sequelize; - const generator = sql.queryInterface.QueryGenerator; + const generator = sql.queryInterface.queryGenerator; const run = generator.handleSequelizeMethod.bind(generator); const expectsql = Support.expectsql; diff --git a/types/index.d.ts b/types/index.d.ts index 7bcd710bc997..d604421b9b65 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -15,6 +15,11 @@ export * from './lib/associations/index'; export * from './lib/errors'; export { BaseError as Error } from './lib/errors'; export { useInflection } from './lib/utils'; -export { Promise } from './lib/promise'; export { Utils, QueryTypes, Op, TableHints, IndexHints, DataTypes, Deferrable }; export { Validator as validator } from './lib/utils/validator-extras'; + +/** + * Type helper for making certain fields of an object optional. This is helpful + * for creating the `CreationAttributes` from your `Attributes` for a Model. + */ +export type Optional = Omit & Partial>; diff --git a/types/lib/associations/base.d.ts b/types/lib/associations/base.d.ts index 75603eba713f..186005992fed 100644 --- a/types/lib/associations/base.d.ts +++ b/types/lib/associations/base.d.ts @@ -1,4 +1,4 @@ -import { ColumnOptions, Model, ModelCtor } from '../model'; +import { ColumnOptions, Model, ModelCtor, Hookable } from '../model'; export abstract class Association { public associationType: string; @@ -42,17 +42,7 @@ export interface ForeignKeyOptions extends ColumnOptions { /** * Options provided when associating models */ -export interface AssociationOptions { - /** - * Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. - * For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks - * for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking - * any hooks. - * - * @default false - */ - hooks?: boolean; - +export interface AssociationOptions extends Hookable { /** * The alias of this model, in singular form. See also the `name` option passed to `sequelize.define`. If * you create multiple associations between the same tables, you should provide an alias to be able to diff --git a/types/lib/associations/belongs-to-many.d.ts b/types/lib/associations/belongs-to-many.d.ts index e7af47d59da6..c82e9294d971 100644 --- a/types/lib/associations/belongs-to-many.d.ts +++ b/types/lib/associations/belongs-to-many.d.ts @@ -2,16 +2,16 @@ import { BulkCreateOptions, CreateOptions, Filterable, + FindAttributeOptions, FindOptions, InstanceDestroyOptions, InstanceUpdateOptions, Model, ModelCtor, + ModelType, Transactionable, WhereOptions, } from '../model'; -import { Promise } from '../promise'; -import { Transaction } from '../transaction'; import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, MultiAssociationAccessors } from './base'; /** @@ -20,8 +20,15 @@ import { Association, AssociationScope, ForeignKeyOptions, ManyToManyOptions, Mu export interface ThroughOptions { /** * The model used to join both sides of the N:M association. + * Can be a string if you want the model to be generated by sequelize. */ - model: typeof Model; + model: ModelType | string; + + /** + * If true the generated join table will be paranoid + * @default false + */ + paranoid?: boolean; /** * A key/value set that will be used for association create and find defaults on the through model. @@ -53,7 +60,7 @@ export interface BelongsToManyOptions extends ManyToManyOptions { * The name of the table that is used to join source and target in n:m associations. Can also be a * sequelize model if you want to define the junction table yourself and add extra attributes to it. */ - through: typeof Model | string | ThroughOptions; + through: ModelType | string | ThroughOptions; /** * The name of the foreign key in the join table (representing the target model) or an object representing @@ -98,7 +105,11 @@ export class BelongsToMany ext * The options for the getAssociations mixin of the belongsToMany association. * @see BelongsToManyGetAssociationsMixin */ -export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions { +export interface BelongsToManyGetAssociationsMixinOptions extends FindOptions { + /** + * A list of the attributes from the join table that you want to select. + */ + joinTableAttributes?: FindAttributeOptions /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -139,9 +150,9 @@ export type BelongsToManyGetAssociationsMixin = ( * @see BelongsToManySetAssociationsMixin */ export interface BelongsToManySetAssociationsMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, + extends FindOptions, + BulkCreateOptions, + InstanceUpdateOptions, InstanceDestroyOptions { through?: JoinTableAttributes; } @@ -181,9 +192,9 @@ export type BelongsToManySetAssociationsMixin = ( * @see BelongsToManyAddAssociationsMixin */ export interface BelongsToManyAddAssociationsMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, + extends FindOptions, + BulkCreateOptions, + InstanceUpdateOptions, InstanceDestroyOptions { through?: JoinTableAttributes; } @@ -223,9 +234,9 @@ export type BelongsToManyAddAssociationsMixin = ( * @see BelongsToManyAddAssociationMixin */ export interface BelongsToManyAddAssociationMixinOptions - extends FindOptions, - BulkCreateOptions, - InstanceUpdateOptions, + extends FindOptions, + BulkCreateOptions, + InstanceUpdateOptions, InstanceDestroyOptions { through?: JoinTableAttributes; } @@ -264,7 +275,7 @@ export type BelongsToManyAddAssociationMixin = ( * The options for the createAssociation mixin of the belongsToMany association. * @see BelongsToManyCreateAssociationMixin */ -export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions { +export interface BelongsToManyCreateAssociationMixinOptions extends CreateOptions { through?: JoinTableAttributes; } /** @@ -292,8 +303,8 @@ export interface BelongsToManyCreateAssociationMixinOptions extends CreateOption * @see https://sequelize.org/master/class/lib/associations/belongs-to-many.js~BelongsToMany.html * @see Instance */ -export type BelongsToManyCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type BelongsToManyCreateAssociationMixin = ( + values?: Model['_creationAttributes'], options?: BelongsToManyCreateAssociationMixinOptions ) => Promise; @@ -445,7 +456,7 @@ export type BelongsToManyHasAssociationsMixin = ( * The options for the countAssociations mixin of the belongsToMany association. * @see BelongsToManyCountAssociationsMixin */ -export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable { +export interface BelongsToManyCountAssociationsMixinOptions extends Transactionable, Filterable { /** * Apply a scope on the related model, or remove its default scope by passing false. */ diff --git a/types/lib/associations/belongs-to.d.ts b/types/lib/associations/belongs-to.d.ts index 26c87aab4e2e..fd2a5e356b2a 100644 --- a/types/lib/associations/belongs-to.d.ts +++ b/types/lib/associations/belongs-to.d.ts @@ -1,6 +1,5 @@ import { DataType } from '../data-types'; import { CreateOptions, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; -import { Promise } from '../promise'; import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; // type ModelCtor = InstanceType; @@ -31,7 +30,7 @@ export class BelongsTo extends * The options for the getAssociation mixin of the belongsTo association. * @see BelongsToGetAssociationMixin */ -export interface BelongsToGetAssociationMixinOptions extends FindOptions { +export interface BelongsToGetAssociationMixinOptions extends FindOptions { /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -62,7 +61,7 @@ export type BelongsToGetAssociationMixin = (options?: BelongsToGetAssoci * The options for the setAssociation mixin of the belongsTo association. * @see BelongsToSetAssociationMixin */ -export interface BelongsToSetAssociationMixinOptions extends SaveOptions { +export interface BelongsToSetAssociationMixinOptions extends SaveOptions { /** * Skip saving this after setting the foreign key if false. */ @@ -96,7 +95,8 @@ export type BelongsToSetAssociationMixin = ( * The options for the createAssociation mixin of the belongsTo association. * @see BelongsToCreateAssociationMixin */ -export interface BelongsToCreateAssociationMixinOptions extends CreateOptions, BelongsToSetAssociationMixinOptions {} +export interface BelongsToCreateAssociationMixinOptions + extends CreateOptions, BelongsToSetAssociationMixinOptions {} /** * The createAssociation mixin applied to models with belongsTo. @@ -116,8 +116,8 @@ export interface BelongsToCreateAssociationMixinOptions extends CreateOptions, B * @see https://sequelize.org/master/class/lib/associations/belongs-to.js~BelongsTo.html * @see Instance */ -export type BelongsToCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type BelongsToCreateAssociationMixin = ( + values?: TModel['_creationAttributes'], options?: BelongsToCreateAssociationMixinOptions ) => Promise; diff --git a/types/lib/associations/has-many.d.ts b/types/lib/associations/has-many.d.ts index c0baa9d58313..d98a96485af3 100644 --- a/types/lib/associations/has-many.d.ts +++ b/types/lib/associations/has-many.d.ts @@ -8,7 +8,6 @@ import { ModelCtor, Transactionable, } from '../model'; -import { Promise } from '../promise'; import { Association, ManyToManyOptions, MultiAssociationAccessors } from './base'; /** @@ -37,7 +36,7 @@ export class HasMany extends A * The options for the getAssociations mixin of the hasMany association. * @see HasManyGetAssociationsMixin */ -export interface HasManyGetAssociationsMixinOptions extends FindOptions { +export interface HasManyGetAssociationsMixinOptions extends FindOptions { /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -75,7 +74,7 @@ export type HasManyGetAssociationsMixin = (options?: HasManyGetAssociati * The options for the setAssociations mixin of the hasMany association. * @see HasManySetAssociationsMixin */ -export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} +export interface HasManySetAssociationsMixinOptions extends FindOptions, InstanceUpdateOptions {} /** * The setAssociations mixin applied to models with hasMany. @@ -111,7 +110,7 @@ export type HasManySetAssociationsMixin = ( * The options for the addAssociations mixin of the hasMany association. * @see HasManyAddAssociationsMixin */ -export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} +export interface HasManyAddAssociationsMixinOptions extends InstanceUpdateOptions {} /** * The addAssociations mixin applied to models with hasMany. @@ -147,7 +146,7 @@ export type HasManyAddAssociationsMixin = ( * The options for the addAssociation mixin of the hasMany association. * @see HasManyAddAssociationMixin */ -export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} +export interface HasManyAddAssociationMixinOptions extends InstanceUpdateOptions {} /** * The addAssociation mixin applied to models with hasMany. @@ -183,7 +182,7 @@ export type HasManyAddAssociationMixin = ( * The options for the createAssociation mixin of the hasMany association. * @see HasManyCreateAssociationMixin */ -export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} +export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} /** * The createAssociation mixin applied to models with hasMany. @@ -210,8 +209,8 @@ export interface HasManyCreateAssociationMixinOptions extends CreateOptions {} * @see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html * @see Instance */ -export type HasManyCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type HasManyCreateAssociationMixin = ( + values?: Model['_creationAttributes'], options?: HasManyCreateAssociationMixinOptions ) => Promise; @@ -219,7 +218,7 @@ export type HasManyCreateAssociationMixin = ( * The options for the removeAssociation mixin of the hasMany association. * @see HasManyRemoveAssociationMixin */ -export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} +export interface HasManyRemoveAssociationMixinOptions extends InstanceUpdateOptions {} /** * The removeAssociation mixin applied to models with hasMany. @@ -255,7 +254,7 @@ export type HasManyRemoveAssociationMixin = ( * The options for the removeAssociations mixin of the hasMany association. * @see HasManyRemoveAssociationsMixin */ -export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} +export interface HasManyRemoveAssociationsMixinOptions extends InstanceUpdateOptions {} /** * The removeAssociations mixin applied to models with hasMany. @@ -363,7 +362,7 @@ export type HasManyHasAssociationsMixin = ( * The options for the countAssociations mixin of the hasMany association. * @see HasManyCountAssociationsMixin */ -export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable { +export interface HasManyCountAssociationsMixinOptions extends Transactionable, Filterable { /** * Apply a scope on the related model, or remove its default scope by passing false. */ diff --git a/types/lib/associations/has-one.d.ts b/types/lib/associations/has-one.d.ts index 4711f8e9bfb4..e81784b3d88a 100644 --- a/types/lib/associations/has-one.d.ts +++ b/types/lib/associations/has-one.d.ts @@ -1,6 +1,5 @@ import { DataType } from '../data-types'; import { CreateOptions, FindOptions, Model, ModelCtor, SaveOptions } from '../model'; -import { Promise } from '../promise'; import { Association, AssociationOptions, SingleAssociationAccessors } from './base'; /** @@ -29,7 +28,7 @@ export class HasOne extends As * The options for the getAssociation mixin of the hasOne association. * @see HasOneGetAssociationMixin */ -export interface HasOneGetAssociationMixinOptions extends FindOptions { +export interface HasOneGetAssociationMixinOptions extends FindOptions { /** * Apply a scope on the related model, or remove its default scope by passing false. */ @@ -60,7 +59,7 @@ export type HasOneGetAssociationMixin = (options?: HasOneGetAssociationM * The options for the setAssociation mixin of the hasOne association. * @see HasOneSetAssociationMixin */ -export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions { +export interface HasOneSetAssociationMixinOptions extends HasOneGetAssociationMixinOptions, SaveOptions { /** * Skip saving this after setting the foreign key if false. */ @@ -94,7 +93,7 @@ export type HasOneSetAssociationMixin = ( * The options for the createAssociation mixin of the hasOne association. * @see HasOneCreateAssociationMixin */ -export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} +export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociationMixinOptions, CreateOptions {} /** * The createAssociation mixin applied to models with hasOne. @@ -114,7 +113,7 @@ export interface HasOneCreateAssociationMixinOptions extends HasOneSetAssociatio * @see https://sequelize.org/master/class/lib/associations/has-one.js~HasOne.html * @see Instance */ -export type HasOneCreateAssociationMixin = ( - values?: { [attribute: string]: unknown }, +export type HasOneCreateAssociationMixin = ( + values?: TModel['_creationAttributes'], options?: HasOneCreateAssociationMixinOptions ) => Promise; diff --git a/types/lib/connection-manager.d.ts b/types/lib/connection-manager.d.ts index 071bb32a6fd8..8bb7674918b9 100644 --- a/types/lib/connection-manager.d.ts +++ b/types/lib/connection-manager.d.ts @@ -1,5 +1,3 @@ -import { Promise } from './promise'; - export interface GetConnectionOptions { /** * Set which replica to use. Available options are `read` and `write` diff --git a/types/lib/data-types.d.ts b/types/lib/data-types.d.ts index 617b01e84ec0..d95f78d83dfd 100644 --- a/types/lib/data-types.d.ts +++ b/types/lib/data-types.d.ts @@ -112,6 +112,8 @@ export const TEXT: TextDataTypeConstructor; interface TextDataTypeConstructor extends AbstractDataTypeConstructor { new (length?: TextLength): TextDataType; + new (options?: TextDataTypeOptions): TextDataType; + (length?: TextLength): TextDataType; (options?: TextDataTypeOptions): TextDataType; } diff --git a/types/lib/errors.d.ts b/types/lib/errors.d.ts index ff5e9151b226..b4b84a781aee 100644 --- a/types/lib/errors.d.ts +++ b/types/lib/errors.d.ts @@ -1,3 +1,5 @@ +import Model from "./model"; + /** * The Base Error all Sequelize Errors inherit from. */ @@ -33,29 +35,53 @@ export class ValidationError extends BaseError { export class ValidationErrorItem { /** An error message */ - public readonly message: string; + public readonly message: string; - /** The type of the validation error */ - public readonly type: string; + /** The type/origin of the validation error */ + public readonly type: string | null; /** The field that triggered the validation error */ - public readonly path: string; + public readonly path: string | null; /** The value that generated the error */ - public readonly value: string; + public readonly value: string | null; + + /** The DAO instance that caused the validation error */ + public readonly instance: Model | null; + + /** A validation "key", used for identification */ + public readonly validatorKey: string | null; + + /** Property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable */ + public readonly validatorName: string | null; + + /** Parameters used with the BUILT-IN validator function, if applicable */ + public readonly validatorArgs: unknown[]; public readonly original: Error; /** - * Validation Error Item - * Instances of this class are included in the `ValidationError.errors` property. + * Creates a new ValidationError item. Instances of this class are included in the `ValidationError.errors` property. * * @param message An error message - * @param type The type of the validation error + * @param type The type/origin of the validation error * @param path The field that triggered the validation error * @param value The value that generated the error + * @param instance the DAO instance that caused the validation error + * @param validatorKey a validation "key", used for identification + * @param fnName property name of the BUILT-IN validator function that caused the validation error (e.g. "in" or "len"), if applicable + * @param fnArgs parameters used with the BUILT-IN validator function, if applicable */ - constructor(message?: string, type?: string, path?: string, value?: string); + constructor( + message?: string, + type?: string, + path?: string, + value?: string, + instance?: object, + validatorKey?: string, + fnName?: string, + fnArgs?: unknown[] + ); } export interface CommonErrorProperties { @@ -130,6 +156,16 @@ export class ExclusionConstraintError extends DatabaseError { constructor(options: { parent?: Error; message?: string; constraint?: string; fields?: string[]; table?: string }); } +/** + * Thrown when constraint name is not found in the database + */ +export class UnknownConstraintError extends DatabaseError { + public constraint: string; + public fields: { [field: string]: string }; + public table: string; + constructor(options: { parent?: Error; message?: string; constraint?: string; fields?: string[]; table?: string }); +} + /** * Thrown when attempting to update a stale model instance */ @@ -175,3 +211,20 @@ export class InvalidConnectionError extends ConnectionError {} * Thrown when a connection to a database times out */ export class ConnectionTimedOutError extends ConnectionError {} + +/** + * Thrown when queued operations were aborted because a connection was closed + */ +export class AsyncQueueError extends BaseError {} + +export class AggregateError extends BaseError { + /** + * AggregateError. A wrapper for multiple errors. + * + * @param {Error[]} errors The aggregated errors that occurred + */ + constructor(errors: Error[]); + + /** the aggregated errors that occurred */ + public readonly errors: Error[]; +} diff --git a/types/lib/hooks.d.ts b/types/lib/hooks.d.ts index fa2f9f02b185..f62f7f61573e 100644 --- a/types/lib/hooks.d.ts +++ b/types/lib/hooks.d.ts @@ -1,94 +1,80 @@ +import { ModelType } from '../index'; import { ValidationOptions } from './instance-validator'; import Model, { BulkCreateOptions, CountOptions, CreateOptions, - DestroyOptions, - FindOptions, + DestroyOptions, FindOptions, InstanceDestroyOptions, + InstanceRestoreOptions, InstanceUpdateOptions, ModelAttributes, - ModelOptions, - UpdateOptions, - SaveOptions, - UpsertOptions, - RestoreOptions, + ModelOptions, RestoreOptions, UpdateOptions, UpsertOptions } from './model'; +import { AbstractQuery } from './query'; +import { QueryOptions } from './query-interface'; import { Config, Options, Sequelize, SyncOptions } from './sequelize'; -import { Association, AssociationOptions, Transaction } from '..'; +import { DeepWriteable } from './utils'; export type HookReturn = Promise | void; -export interface AssociateBeforeData { - source: S; - target: T; - type: typeof Association; -} - -export interface AssociateAfterData extends AssociateBeforeData { - association: Association; -} - /** * Options for Model.init. We mostly duplicate the Hooks here, since there is no way to combine the two * interfaces. */ -export interface ModelHooks { +export interface ModelHooks { beforeValidate(instance: M, options: ValidationOptions): HookReturn; afterValidate(instance: M, options: ValidationOptions): HookReturn; - - beforeCreate(attributes: M, options: CreateOptions): HookReturn; - afterCreate(attributes: M, options: CreateOptions): HookReturn; - + beforeCreate(attributes: M, options: CreateOptions): HookReturn; + afterCreate(attributes: M, options: CreateOptions): HookReturn; beforeDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; afterDestroy(instance: M, options: InstanceDestroyOptions): HookReturn; - - beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; - afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; - - beforeSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; - afterSave(instance: M, options: InstanceUpdateOptions | CreateOptions): HookReturn; - - beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; - afterBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; - - beforeBulkDestroy(options: DestroyOptions): HookReturn; - afterBulkDestroy(options: DestroyOptions): HookReturn; - - beforeBulkUpdate(options: UpdateOptions): HookReturn; - afterBulkUpdate(options: UpdateOptions): HookReturn; - - beforeFind(options: FindOptions): HookReturn; - afterFind(instancesOrInstance: M[] | M, options: FindOptions): HookReturn; - - beforeCount(options: CountOptions): HookReturn; - - beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; - beforeFindAfterOptions(options: FindOptions): HookReturn; - afterFind(instancesOrInstance: M[] | M | null, options: FindOptions): HookReturn; + beforeRestore(instance: M, options: InstanceRestoreOptions): HookReturn; + afterRestore(instance: M, options: InstanceRestoreOptions): HookReturn; + beforeUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; + afterUpdate(instance: M, options: InstanceUpdateOptions): HookReturn; + beforeUpsert(attributes: M, options: UpsertOptions): HookReturn; + afterUpsert(attributes: [ M, boolean | null ], options: UpsertOptions): HookReturn; + beforeSave( + instance: M, + options: InstanceUpdateOptions | CreateOptions + ): HookReturn; + afterSave( + instance: M, + options: InstanceUpdateOptions | CreateOptions + ): HookReturn; + beforeBulkCreate(instances: M[], options: BulkCreateOptions): HookReturn; + afterBulkCreate(instances: readonly M[], options: BulkCreateOptions): HookReturn; + beforeBulkDestroy(options: DestroyOptions): HookReturn; + afterBulkDestroy(options: DestroyOptions): HookReturn; + beforeBulkRestore(options: RestoreOptions): HookReturn; + afterBulkRestore(options: RestoreOptions): HookReturn; + beforeBulkUpdate(options: UpdateOptions): HookReturn; + afterBulkUpdate(options: UpdateOptions): HookReturn; + beforeFind(options: FindOptions): HookReturn; + beforeCount(options: CountOptions): HookReturn; + beforeFindAfterExpandIncludeAll(options: FindOptions): HookReturn; + beforeFindAfterOptions(options: FindOptions): HookReturn; + afterFind(instancesOrInstance: readonly M[] | M | null, options: FindOptions): HookReturn; beforeSync(options: SyncOptions): HookReturn; afterSync(options: SyncOptions): HookReturn; - beforeBulkSync(options: SyncOptions): HookReturn; afterBulkSync(options: SyncOptions): HookReturn; - - beforeUpsert(values: object, options: UpsertOptions): HookReturn; - afterUpsert(instance: M, options: UpsertOptions): HookReturn; - - beforeAssociate(assoc: AssociateBeforeData, options: AssociationOptions): HookReturn; - afterAssociate(assoc: AssociateAfterData, options: AssociationOptions): HookReturn; - - beforeRestore(instance: M, options: RestoreOptions): HookReturn; - afterRestore(instance: M, options: RestoreOptions): HookReturn; - + beforeQuery(options: QueryOptions, query: AbstractQuery): HookReturn; + afterQuery(options: QueryOptions, query: AbstractQuery): HookReturn; } -export interface SequelizeHooks extends ModelHooks { - beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; - afterDefine(model: typeof Model): void; + +export interface SequelizeHooks< + M extends Model = Model, + TAttributes = any, + TCreationAttributes = TAttributes +> extends ModelHooks { + beforeDefine(attributes: ModelAttributes, options: ModelOptions): void; + afterDefine(model: ModelType): void; beforeInit(config: Config, options: Options): void; afterInit(sequelize: Sequelize): void; - beforeConnect(config: Config): HookReturn; + beforeConnect(config: DeepWriteable): HookReturn; afterConnect(connection: unknown, config: Config): HookReturn; beforeDisconnect(connection: unknown): HookReturn; afterDisconnect(connection: unknown): HookReturn; @@ -97,19 +83,101 @@ export interface SequelizeHooks extends ModelHooks { /** * Virtual class for deduplication */ -export class Hooks { +export class Hooks< + M extends Model = Model, + TModelAttributes extends {} = any, + TCreationAttributes extends {} = TModelAttributes +> { + /** + * A dummy variable that doesn't exist on the real object. This exists so + * Typescript can infer the type of the attributes in static functions. Don't + * try to access this! + */ + _model: M; + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + */ + _attributes: TModelAttributes; + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + */ + _creationAttributes: TCreationAttributes; + /** * Add a hook to the model + * + * @param name Provide a name for the hook function. It can be used to remove the hook later or to order + * hooks based on some sort of priority system in the future. + */ + public static addHook< + H extends Hooks, + K extends keyof SequelizeHooks + >( + this: HooksStatic, + hookType: K, + name: string, + fn: SequelizeHooks[K] + ): HooksCtor; + public static addHook< + H extends Hooks, + K extends keyof SequelizeHooks + >( + this: HooksStatic, + hookType: K, + fn: SequelizeHooks[K] + ): HooksCtor; + + /** + * Remove hook from the model + */ + public static removeHook( + this: HooksStatic, + hookType: keyof SequelizeHooks, + name: string, + ): HooksCtor; + + /** + * Check whether the mode has any hooks of this type */ - add(hookType: K, fn: H[K]): this; + public static hasHook( + this: HooksStatic, + hookType: keyof SequelizeHooks, + ): boolean; + public static hasHooks( + this: HooksStatic, + hookType: keyof SequelizeHooks, + ): boolean; + /** + * Add a hook to the model + * + * @param name Provide a name for the hook function. It can be used to remove the hook later or to order + * hooks based on some sort of priority system in the future. + */ + public addHook>( + hookType: K, + name: string, + fn: SequelizeHooks[K] + ): this; + public addHook>( + hookType: K, fn: SequelizeHooks[K]): this; /** * Remove hook from the model */ - remove(hookType: K, fn: Function): this; + public removeHook>( + hookType: K, + name: string + ): this; /** * Check whether the mode has any hooks of this type */ - has(hookType: K): boolean; + public hasHook>(hookType: K): boolean; + public hasHooks>(hookType: K): boolean; } + +export type HooksCtor = typeof Hooks & { new(): H }; + +export type HooksStatic = { new(): H }; diff --git a/types/lib/instance-validator.d.ts b/types/lib/instance-validator.d.ts index 0d441995cce2..c2f3469d81ac 100644 --- a/types/lib/instance-validator.d.ts +++ b/types/lib/instance-validator.d.ts @@ -1,4 +1,6 @@ -export interface ValidationOptions { +import { Hookable } from "./model"; + +export interface ValidationOptions extends Hookable { /** * An array of strings. All properties that are in this array will not be validated */ @@ -7,9 +9,4 @@ export interface ValidationOptions { * An array of strings. Only the properties that are in this array will be validated */ fields?: string[]; - /** - * Run before and after validate hooks. - * @default true. - */ - hooks?: boolean; } diff --git a/types/lib/model-manager.d.ts b/types/lib/model-manager.d.ts index 29eac4961375..41e72dae6636 100644 --- a/types/lib/model-manager.d.ts +++ b/types/lib/model-manager.d.ts @@ -1,4 +1,4 @@ -import { Model } from './model'; +import { Model, ModelType } from './model'; import { Sequelize } from './sequelize'; export class ModelManager { @@ -7,8 +7,8 @@ export class ModelManager { public all: typeof Model[]; constructor(sequelize: Sequelize); - public addModel(model: T): T; - public removeModel(model: typeof Model): void; + public addModel(model: T): T; + public removeModel(model: ModelType): void; public getModel(against: unknown, options?: { attribute?: string }): typeof Model; } diff --git a/types/lib/model.d.ts b/types/lib/model.d.ts index a0337f967e88..4f760f7e8655 100644 --- a/types/lib/model.d.ts +++ b/types/lib/model.d.ts @@ -1,26 +1,14 @@ -import { - Association, - BelongsTo, - BelongsToMany, - BelongsToManyOptions, - BelongsToOptions, - HasMany, - HasManyOptions, - HasOne, - HasOneOptions, -} from './associations/index'; +import { IndexHints } from '..'; +import { Association, BelongsTo, BelongsToMany, BelongsToManyOptions, BelongsToOptions, HasMany, HasManyOptions, HasOne, HasOneOptions } from './associations/index'; import { DataType } from './data-types'; import { Deferrable } from './deferrable'; -import { HookReturn, SequelizeHooks, ModelHooks, Hooks } from './hooks'; +import { HookReturn, Hooks, ModelHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; -import { ModelManager } from './model-manager'; -import Op = require('./operators'); -import { Promise } from './promise'; -import { QueryOptions, IndexesOptions } from './query-interface'; -import { Config, Options, Sequelize, SyncOptions } from './sequelize'; -import { Transaction } from './transaction'; +import { IndexesOptions, QueryOptions, TableName } from './query-interface'; +import { Sequelize, SyncOptions } from './sequelize'; +import { LOCK, Transaction } from './transaction'; import { Col, Fn, Literal, Where } from './utils'; -import { IndexHints } from '..'; +import Op = require('./operators'); export interface Logging { /** @@ -47,7 +35,7 @@ export interface Transactionable { /** * Transaction to run query under */ - transaction?: Transaction; + transaction?: Transaction | null; } export interface SearchPathable { @@ -57,11 +45,11 @@ export interface SearchPathable { searchPath?: string; } -export interface Filterable { +export interface Filterable { /** * Attribute has to be matched for rows to be selected for the given action. */ - where?: WhereOptions; + where?: WhereOptions; } export interface Projectable { @@ -115,13 +103,19 @@ export interface ScopeOptions { * any arguments, or an array, where the first element is the name of the method, and consecutive elements * are arguments to that method. Pass null to remove all scopes, including the default. */ - method: string | [string, ...unknown[]]; + method: string | readonly [string, ...unknown[]]; } /** * The type accepted by every `where` option */ -export type WhereOptions = WhereAttributeHash | AndOperator | OrOperator | Literal | Where; +export type WhereOptions = + | WhereAttributeHash + | AndOperator + | OrOperator + | Literal + | Fn + | Where; /** * Example: `[Op.any]: [2,3]` becomes `ANY ARRAY[2, 3]::INTEGER` @@ -129,15 +123,15 @@ export type WhereOptions = WhereAttributeHash | AndOperator | OrOperator | Liter * _PG only_ */ export interface AnyOperator { - [Op.any]: (string | number)[]; + [Op.any]: readonly (string | number)[]; } -/** Undocumented? */ +/** TODO: Undocumented? */ export interface AllOperator { - [Op.all]: (string | number | Date | Literal)[]; + [Op.all]: readonly (string | number | Date | Literal)[]; } -export type Rangable = [number, number] | [Date, Date] | Literal; +export type Rangable = readonly [number, number] | readonly [Date, Date] | readonly [string, string] | Literal; /** * Operators that can be used in WhereOptions @@ -150,7 +144,11 @@ export interface WhereOperators { * * _PG only_ */ - [Op.any]?: (string | number | Literal)[] | Literal; + + /** Example: `[Op.eq]: 6,` becomes `= 6` */ + [Op.eq]?: null | boolean | string | number | Literal | WhereOperators; + + [Op.any]?: readonly (string | number | Literal)[] | Literal; /** Example: `[Op.gte]: 6,` becomes `>= 6` */ [Op.gte]?: number | string | Date | Literal; @@ -161,20 +159,26 @@ export interface WhereOperators { /** Example: `[Op.lte]: 10,` becomes `<= 10` */ [Op.lte]?: number | string | Date | Literal; + /** Example: `[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` becomes `@@ to_tsquery('fat & rat')` */ + [Op.match]?: Fn; + /** Example: `[Op.ne]: 20,` becomes `!= 20` */ - [Op.ne]?: string | number | Literal | WhereOperators; + [Op.ne]?: null | string | number | Literal | WhereOperators; /** Example: `[Op.not]: true,` becomes `IS NOT TRUE` */ - [Op.not]?: boolean | string | number | Literal | WhereOperators; + [Op.not]?: null | boolean | string | number | Literal | WhereOperators; + + /** Example: `[Op.is]: null,` becomes `IS NULL` */ + [Op.is]?: null; /** Example: `[Op.between]: [6, 10],` becomes `BETWEEN 6 AND 10` */ - [Op.between]?: [number, number]; + [Op.between]?: Rangable; /** Example: `[Op.in]: [1, 2],` becomes `IN [1, 2]` */ - [Op.in]?: (string | number | Literal)[] | Literal; + [Op.in]?: readonly (string | number | Literal)[] | Literal; /** Example: `[Op.notIn]: [1, 2],` becomes `NOT IN [1, 2]` */ - [Op.notIn]?: (string | number | Literal)[] | Literal; + [Op.notIn]?: readonly (string | number | Literal)[] | Literal; /** * Examples: @@ -211,14 +215,14 @@ export interface WhereOperators { * * Example: `[Op.contains]: [1, 2]` becomes `@> [1, 2]` */ - [Op.contains]?: (string | number)[] | Rangable; + [Op.contains]?: readonly (string | number)[] | Rangable; /** * PG array contained by operator * * Example: `[Op.contained]: [1, 2]` becomes `<@ [1, 2]` */ - [Op.contained]?: (string | number)[] | Rangable; + [Op.contained]?: readonly (string | number)[] | Rangable; /** Example: `[Op.gt]: 6,` becomes `> 6` */ [Op.gt]?: number | string | Date | Literal; @@ -233,7 +237,7 @@ export interface WhereOperators { [Op.notILike]?: string | Literal | AnyOperator | AllOperator; /** Example: `[Op.notBetween]: [11, 15],` becomes `NOT BETWEEN 11 AND 15` */ - [Op.notBetween]?: [number, number]; + [Op.notBetween]?: Rangable; /** * Strings starts with value. @@ -316,13 +320,13 @@ export interface WhereOperators { } /** Example: `[Op.or]: [{a: 5}, {a: 6}]` becomes `(a = 5 OR a = 6)` */ -export interface OrOperator { - [Op.or]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; +export interface OrOperator { + [Op.or]: WhereOptions | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; } /** Example: `[Op.and]: {a: 5}` becomes `AND (a = 5)` */ -export interface AndOperator { - [Op.and]: WhereOptions | WhereOptions[] | WhereValue | WhereValue[]; +export interface AndOperator { + [Op.and]: WhereOptions | readonly WhereOptions[] | WhereValue | readonly WhereValue[]; } /** @@ -330,31 +334,34 @@ export interface AndOperator { */ export interface WhereGeometryOptions { type: string; - coordinates: (number[] | number)[]; + coordinates: readonly (number[] | number)[]; } /** * Used for the right hand side of WhereAttributeHash. * WhereAttributeHash is in there for JSON columns. */ -export type WhereValue = - | string // literal value - | number // literal value - | boolean // literal value +export type WhereValue = + | string + | number + | bigint + | boolean + | Date + | Buffer | null | WhereOperators - | WhereAttributeHash // for JSON columns + | WhereAttributeHash // for JSON columns | Col // reference another column | Fn - | OrOperator - | AndOperator + | OrOperator + | AndOperator | WhereGeometryOptions - | (string | number | WhereAttributeHash)[]; // implicit [Op.or] + | readonly (string | number | Buffer | WhereAttributeHash)[]; // implicit [Op.or] /** * A hash of attributes to describe your search. */ -export interface WhereAttributeHash { +export type WhereAttributeHash = { /** * Possible key values: * - A simple attribute name @@ -366,22 +373,35 @@ export interface WhereAttributeHash { * } * } */ - [field: string]: WhereValue | WhereOptions; + [field in keyof TAttributes]?: WhereValue | WhereOptions; } /** * Through options for Include Options */ -export interface IncludeThroughOptions extends Filterable, Projectable {} +export interface IncludeThroughOptions extends Filterable, Projectable { + /** + * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / + * `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural + */ + as?: string; + + /** + * If true, only non-deleted records will be returned from the join table. + * If false, both deleted and non-deleted records will be returned. + * Only applies if through model is paranoid. + */ + paranoid?: boolean; +} /** * Options for eager-loading associated models, also allowing for all associations to be loaded at once */ -export type Includeable = typeof Model | Association | IncludeOptions | { all: true } | string; +export type Includeable = ModelType | Association | IncludeOptions | { all: true, nested?: true } | string; /** * Complex include options */ -export interface IncludeOptions extends Filterable, Projectable, Paranoid { +export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * Mark the include as duplicating, will prevent a subquery from being used. */ @@ -389,7 +409,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * The model you want to eagerly load */ - model?: typeof Model; + model?: ModelType; /** * The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / @@ -405,13 +425,13 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { /** * Custom `on` clause, overrides default. */ - on?: WhereOptions; + on?: WhereOptions; /** * Note that this converts the eager load to an inner join, * unless you explicitly set `required: false` */ - where?: WhereOptions; + where?: WhereOptions; /** * If true, converts to an inner join, which means that the parent model will only be loaded if it has any @@ -419,6 +439,11 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { */ required?: boolean; + /** + * If true, converts to a right join if dialect support it. Ignored if `include.required` is true. + */ + right?: boolean; + /** * Limit include. Only available when setting `separate` to true. */ @@ -450,7 +475,7 @@ export interface IncludeOptions extends Filterable, Projectable, Paranoid { subQuery?: boolean; } -type OrderItemModel = typeof Model | { model: typeof Model; as: string } | string +type OrderItemAssociation = Association | ModelStatic | { model: ModelStatic; as: string } | string type OrderItemColumn = string | Col | Fn | Literal export type OrderItem = | string @@ -458,32 +483,32 @@ export type OrderItem = | Col | Literal | [OrderItemColumn, string] - | [OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemColumn, string] - | [OrderItemModel, OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemModel, OrderItemColumn, string] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn, string] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn] - | [OrderItemModel, OrderItemModel, OrderItemModel, OrderItemModel, OrderItemColumn, string] -export type Order = string | Fn | Col | Literal | OrderItem[]; + | [OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn] + | [OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemAssociation, OrderItemColumn, string] +export type Order = Fn | Col | Literal | OrderItem[]; /** * Please note if this is used the aliased property will not be available on the model instance * as a property but only via `instance.get('alias')`. */ -export type ProjectionAlias = [string | Literal | Fn, string]; +export type ProjectionAlias = readonly [string | Literal | Fn | Col, string]; export type FindAttributeOptions = | (string | ProjectionAlias)[] | { - exclude: string[]; - include?: (string | ProjectionAlias)[]; - } + exclude: string[]; + include?: (string | ProjectionAlias)[]; + } | { - exclude?: string[]; - include: (string | ProjectionAlias)[]; - }; + exclude?: string[]; + include: (string | ProjectionAlias)[]; + }; export interface IndexHint { type: IndexHints; @@ -504,15 +529,17 @@ type Omit = Pick> * * A hash of options to describe the scope of the search */ -export interface FindOptions extends QueryOptions, Filterable, Projectable, Paranoid, IndexHintable { +export interface FindOptions + extends QueryOptions, Filterable, Projectable, Paranoid, IndexHintable +{ /** - * A list of associations to eagerly load using a left join. Supported is either - * `{ include: [ Model1, Model2, ...]}`, `{ include: [{ model: Model1, as: 'Alias' }]}` or + * A list of associations to eagerly load using a left join (a single association is also supported). Supported is either + * `{ include: Model1 }`, `{ include: [ Model1, Model2, ...]}`, `{ include: [{ model: Model1, as: 'Alias' }]}` or * `{ include: [{ all: true }]}`. * If your association are set up with an `as` (eg. `X.hasMany(Y, { as: 'Z }`, you need to specify Z in * the as attribute when eager loading Y). */ - include?: Includeable[]; + include?: Includeable | Includeable[]; /** * Specifies an ordering. If a string is provided, it will be escaped. Using an array, you can provide @@ -542,8 +569,10 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para * Postgres also supports transaction.LOCK.KEY_SHARE, transaction.LOCK.NO_KEY_UPDATE and specific model * locks with joins. See [transaction.LOCK for an example](transaction#lock) */ - lock?: Transaction.LOCK | { level: Transaction.LOCK; of: typeof Model }; - + lock?: + | LOCK + | { level: LOCK; of: ModelStatic } + | boolean; /** * Skip locked rows. Only supported in Postgres. */ @@ -557,7 +586,7 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para /** * Select group rows after groups and aggregates are computed. */ - having?: WhereOptions; + having?: WhereOptions; /** * Use sub queries (internal) @@ -565,7 +594,7 @@ export interface FindOptions extends QueryOptions, Filterable, Projectable, Para subQuery?: boolean; } -export interface NonNullFindOptions extends FindOptions { +export interface NonNullFindOptions extends FindOptions { /** * Throw if nothing was found. */ @@ -575,11 +604,13 @@ export interface NonNullFindOptions extends FindOptions { /** * Options for Model.count method */ -export interface CountOptions extends Logging, Transactionable, Filterable, Projectable, Paranoid, Poolable { +export interface CountOptions + extends Logging, Transactionable, Filterable, Projectable, Paranoid, Poolable +{ /** * Include options. See `find` for details */ - include?: Includeable[]; + include?: Includeable | Includeable[]; /** * Apply COUNT(DISTINCT(col)) @@ -602,7 +633,7 @@ export interface CountOptions extends Logging, Transactionable, Filterable, Proj /** * Options for Model.count when GROUP BY is used */ -export interface CountWithOptions extends CountOptions { +export interface CountWithOptions extends CountOptions { /** * GROUP BY in sql * Used in conjunction with `attributes`. @@ -611,10 +642,10 @@ export interface CountWithOptions extends CountOptions { group: GroupOption; } -export interface FindAndCountOptions extends CountOptions, FindOptions {} +export interface FindAndCountOptions extends CountOptions, FindOptions { } /** - * Options for Model constructor + * Options for Model.build method */ export interface BuildOptions { /** @@ -628,11 +659,11 @@ export interface BuildOptions { isNewRecord?: boolean; /** - * an array of include options - Used to build prefetched/included model instances. See `set` + * An array of include options. A single option is also supported - Used to build prefetched/included model instances. See `set` * * TODO: See set */ - include?: Includeable[]; + include?: Includeable | Includeable[]; } export interface Silent { @@ -647,16 +678,21 @@ export interface Silent { /** * Options for Model.create method */ -export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable { +export interface CreateOptions extends BuildOptions, Logging, Silent, Transactionable, Hookable { /** * If set, only columns matching those in fields will be saved */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** - * On Duplicate + * dialect specific ON CONFLICT DO NOTHING / INSERT IGNORE */ - onDuplicate?: string; + ignoreDuplicates?: boolean; + + /** + * Return the affected rows (only for postgres) + */ + returning?: boolean | (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -664,41 +700,48 @@ export interface CreateOptions extends BuildOptions, Logging, Silent, Transactio * @default true */ validate?: boolean; + +} + +export interface Hookable { + + /** + * If `false` the applicable hooks will not be called. + * The default value depends on the context. + */ + hooks?: boolean + } /** * Options for Model.findOrCreate method */ -export interface FindOrCreateOptions extends Logging, Transactionable { +export interface FindOrCreateOptions + extends FindOptions +{ /** - * A hash of search attributes. + * The fields to insert / update. Defaults to all fields */ - where: WhereOptions; - + fields?: (keyof TAttributes)[]; /** * Default values to use if building a new instance */ - defaults?: object; + defaults?: TCreationAttributes; } /** * Options for Model.upsert method */ -export interface UpsertOptions extends Logging, Transactionable, SearchPathable { +export interface UpsertOptions extends Logging, Transactionable, SearchPathable, Hookable { /** * The fields to insert / update. Defaults to all fields */ - fields?: string[]; - - /** - * Run before / after bulk create hooks? - */ - hooks?: boolean; + fields?: (keyof TAttributes)[]; /** * Return the affected rows (only for postgres) */ - returning?: boolean; + returning?: boolean | (keyof TAttributes)[]; /** * Run validations before the row is inserted @@ -709,11 +752,11 @@ export interface UpsertOptions extends Logging, Transactionable, SearchPathable /** * Options for Model.bulkCreate method */ -export interface BulkCreateOptions extends Logging, Transactionable { +export interface BulkCreateOptions extends Logging, Transactionable, Hookable, SearchPathable { /** * Fields to insert (defaults to all fields) */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -721,11 +764,6 @@ export interface BulkCreateOptions extends Logging, Transactionable { */ validate?: boolean; - /** - * Run before / after bulk create hooks? - */ - hooks?: boolean; - /** * Run before / after create hooks for each individual Instance? BulkCreate hooks will still be run if * options.hooks is true. @@ -733,7 +771,7 @@ export interface BulkCreateOptions extends Logging, Transactionable { individualHooks?: boolean; /** - * Ignore duplicate values for primary keys? (not supported by postgres) + * Ignore duplicate values for primary keys? * * @default false */ @@ -743,23 +781,23 @@ export interface BulkCreateOptions extends Logging, Transactionable { * Fields to update if row key already exists (on duplicate key update)? (only supported by MySQL, * MariaDB, SQLite >= 3.24.0 & Postgres >= 9.5). By default, all fields are updated. */ - updateOnDuplicate?: string[]; + updateOnDuplicate?: (keyof TAttributes)[]; /** * Include options. See `find` for details */ - include?: Includeable[]; + include?: Includeable | Includeable[]; /** * Return all columns or only the specified columns for the affected rows (only for postgres) */ - returning?: boolean | string[]; + returning?: boolean | (keyof TAttributes)[]; } /** * The options passed to Model.destroy in addition to truncate */ -export interface TruncateOptions extends Logging, Transactionable, Filterable { +export interface TruncateOptions extends Logging, Transactionable, Filterable, Hookable { /** * Only used in conjuction with TRUNCATE. Truncates all tables that have foreign-key references to the * named table, or to any tables added to the group due to CASCADE. @@ -768,11 +806,6 @@ export interface TruncateOptions extends Logging, Transactionable, Filterable { */ cascade?: boolean; - /** - * Run before / after bulk destroy hooks? - */ - hooks?: boolean; - /** * If set to true, destroy will SELECT all records matching the where parameter and will execute before / * after destroy hooks on each row @@ -799,7 +832,7 @@ export interface TruncateOptions extends Logging, Transactionable, Filterable { /** * Options used for Model.destroy */ -export interface DestroyOptions extends TruncateOptions { +export interface DestroyOptions extends TruncateOptions { /** * If set to true, dialects that support it will use TRUNCATE instead of DELETE FROM. If a table is * truncated the where and limit options are ignored @@ -810,11 +843,7 @@ export interface DestroyOptions extends TruncateOptions { /** * Options for Model.restore */ -export interface RestoreOptions extends Logging, Transactionable, Filterable { - /** - * Run before / after bulk restore hooks? - */ - hooks?: boolean; +export interface RestoreOptions extends Logging, Transactionable, Filterable, Hookable { /** * If set to true, restore will find all records within the where parameter and will execute before / after @@ -831,16 +860,16 @@ export interface RestoreOptions extends Logging, Transactionable, Filterable { /** * Options used for Model.update */ -export interface UpdateOptions extends Logging, Transactionable { +export interface UpdateOptions extends Logging, Transactionable, Paranoid, Hookable { /** * Options to describe the scope of the search. */ - where: WhereOptions; + where: WhereOptions; /** * Fields to update (defaults to all fields) */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * Should each row be subject to validation before it is inserted. The whole insert will fail if one row @@ -850,13 +879,6 @@ export interface UpdateOptions extends Logging, Transactionable { */ validate?: boolean; - /** - * Run before / after bulk update hooks? - * - * @default true - */ - hooks?: boolean; - /** * Whether or not to update the side effects of any virtual setters. * @@ -875,7 +897,7 @@ export interface UpdateOptions extends Logging, Transactionable { /** * Return the affected rows (only for postgres) */ - returning?: boolean; + returning?: boolean | (keyof TAttributes)[]; /** * How many rows to update (only for mysql and mariadb) @@ -891,7 +913,9 @@ export interface UpdateOptions extends Logging, Transactionable { /** * Options used for Model.aggregate */ -export interface AggregateOptions extends QueryOptions, Filterable, Paranoid { +export interface AggregateOptions + extends QueryOptions, Filterable, Paranoid +{ /** * The type of the result. If `field` is a field in this Model, the default will be the type of that field, * otherwise defaults to float. @@ -909,12 +933,13 @@ export interface AggregateOptions extends QueryOpt /** * Options used for Instance.increment method */ -export interface IncrementDecrementOptions extends Logging, Transactionable, Silent, SearchPathable, Filterable {} +export interface IncrementDecrementOptions + extends Logging, Transactionable, Silent, SearchPathable, Filterable { } /** * Options used for Instance.increment method */ -export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptions { +export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptions { /** * The number to increment by * @@ -926,7 +951,7 @@ export interface IncrementDecrementOptionsWithBy extends IncrementDecrementOptio /** * Options used for Instance.restore method */ -export interface InstanceRestoreOptions extends Logging, Transactionable {} +export interface InstanceRestoreOptions extends Logging, Transactionable { } /** * Options used for Instance.destroy method @@ -941,7 +966,8 @@ export interface InstanceDestroyOptions extends Logging, Transactionable { /** * Options used for Instance.update method */ -export interface InstanceUpdateOptions extends SaveOptions, SetOptions, Filterable {} +export interface InstanceUpdateOptions extends + SaveOptions, SetOptions, Filterable { } /** * Options used for Instance.set method @@ -961,12 +987,12 @@ export interface SetOptions { /** * Options used for Instance.save method */ -export interface SaveOptions extends Logging, Transactionable, Silent { +export interface SaveOptions extends Logging, Transactionable, Silent, Hookable { /** * An optional array of strings, representing database columns. If fields is provided, only those columns * will be validated and saved. */ - fields?: string[]; + fields?: (keyof TAttributes)[]; /** * If false, validations won't be run. @@ -974,6 +1000,13 @@ export interface SaveOptions extends Logging, Transactionable, Silent { * @default true */ validate?: boolean; + + /** + * A flag that defines if null values should be passed as values or not. + * + * @default false + */ + omitNull?: boolean; } /** @@ -987,15 +1020,15 @@ export interface SaveOptions extends Logging, Transactionable, Silent { */ export interface ModelValidateOptions { /** - * is: ["^[a-z]+$",'i'] // will only allow letters - * is: /^[a-z]+[Op./i] // same as the previous example using real RegExp + * - `{ is: ['^[a-z]+$','i'] }` will only allow letters + * - `{ is: /^[a-z]+$/i }` also only allows letters */ - is?: string | (string | RegExp)[] | RegExp | { msg: string; args: string | (string | RegExp)[] | RegExp }; + is?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; /** - * not: ["[a-z]",'i'] // will not allow letters + * - `{ not: ['[a-z]','i'] }` will not allow letters */ - not?: string | (string | RegExp)[] | RegExp | { msg: string; args: string | (string | RegExp)[] | RegExp }; + not?: string | readonly (string | RegExp)[] | RegExp | { msg: string; args: string | readonly (string | RegExp)[] | RegExp }; /** * checks for email format (foo@bar.com) @@ -1090,22 +1123,22 @@ export interface ModelValidateOptions { /** * check the value is not one of these */ - notIn?: string[][] | { msg: string; args: string[][] }; + notIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; /** * check the value is one of these */ - isIn?: string[][] | { msg: string; args: string[][] }; - + isIn?: ReadonlyArray | { msg: string; args: ReadonlyArray }; + /** * don't allow specific substrings */ - notContains?: string[] | string | { msg: string; args: string[] | string }; + notContains?: readonly string[] | string | { msg: string; args: readonly string[] | string }; /** * only allow values with length between 2 and 10 */ - len?: [number, number] | { msg: string; args: [number, number] }; + len?: readonly [number, number] | { msg: string; args: readonly [number, number] }; /** * only allow uuids @@ -1130,12 +1163,12 @@ export interface ModelValidateOptions { /** * only allow values */ - max?: number | { msg: string; args: [number] }; + max?: number | { msg: string; args: readonly [number] }; /** * only allow values >= 23 */ - min?: number | { msg: string; args: [number] }; + min?: number | { msg: string; args: readonly [number] }; /** * only allow arrays @@ -1147,20 +1180,10 @@ export interface ModelValidateOptions { */ isCreditCard?: boolean | { msg: string; args: boolean }; + // TODO: Enforce 'rest' indexes to have type `(value: unknown) => boolean` + // Blocked by: https://github.com/microsoft/TypeScript/issues/7765 /** - * custom validations are also possible - * - * Implementation notes : - * - * We can't enforce any other method to be a function, so : - * - * ```typescript - * [name: string] : ( value : unknown ) => boolean; - * ``` - * - * doesn't work in combination with the properties above - * - * @see https://github.com/Microsoft/TypeScript/issues/1889 + * Custom validations are also possible */ [name: string]: unknown; } @@ -1202,11 +1225,11 @@ export interface ModelSetterOptions { /** * Interface for Define Scope Options */ -export interface ModelScopeOptions { +export interface ModelScopeOptions { /** * Name of the scope and it's query */ - [scopeName: string]: FindOptions | ((...args: any[]) => FindOptions); + [scopeName: string]: FindOptions | ((...args: readonly any[]) => FindOptions); } /** @@ -1238,7 +1261,7 @@ export interface ModelAttributeColumnReferencesOptions { /** * If this column references another table, provide it here as a Model, or a string */ - model?: string | typeof Model; + model?: TableName | ModelType; /** * The column of the foreign table that this column references @@ -1256,7 +1279,7 @@ export interface ModelAttributeColumnReferencesOptions { /** * Column options for the model schema attributes */ -export interface ModelAttributeColumnOptions extends ColumnOptions { +export interface ModelAttributeColumnOptions extends ColumnOptions { /** * A string or a data type */ @@ -1290,9 +1313,9 @@ export interface ModelAttributeColumnOptions extends ColumnOptions { comment?: string; /** - * An object with reference configurations + * An object with reference configurations or the column name as string */ - references?: ModelAttributeColumnReferencesOptions; + references?: string | ModelAttributeColumnReferencesOptions; /** * What should happen when the referenced key is updated. One of CASCADE, RESTRICT, SET DEFAULT, SET NULL or @@ -1331,29 +1354,29 @@ export interface ModelAttributeColumnOptions extends ColumnOptions { * }, { sequelize }) * ``` */ - values?: string[]; + values?: readonly string[]; /** * Provide a custom getter for this column. Use `this.getDataValue(String)` to manipulate the underlying * values. */ - get?(): unknown; + get?(this: M): unknown; /** * Provide a custom setter for this column. Use `this.setDataValue(String, Value)` to manipulate the * underlying values. */ - set?(val: unknown): void; + set?(this: M, val: unknown): void; } /** - * Interface for Attributes provided for a column + * Interface for Attributes provided for all columns in a model */ -export interface ModelAttributes { +export type ModelAttributes = { /** * The description of a database column */ - [name: string]: DataType | ModelAttributeColumnOptions; + [name in keyof TAttributes]: DataType | ModelAttributeColumnOptions; } /** @@ -1369,13 +1392,13 @@ export interface ModelOptions { * Define the default search scope to use for this model. Scopes have the same form as the options passed to * find / findAll. */ - defaultScope?: FindOptions; + defaultScope?: FindOptions; /** * More scopes, defined in the same way as defaultScope above. See `Model.scope` for more information about * how scopes are defined, and what you can do with them */ - scopes?: ModelScopeOptions; + scopes?: ModelScopeOptions; /** * Don't persits null values. This means that all columns with null values will not be saved. @@ -1423,7 +1446,7 @@ export interface ModelOptions { /** * Indexes for the provided database table */ - indexes?: ModelIndexesOptions[]; + indexes?: readonly ModelIndexesOptions[]; /** * Override the name of the createdAt column if a string is provided, or disable it if false. Timestamps @@ -1475,7 +1498,7 @@ export interface ModelOptions { * See Hooks for more information about hook * functions and their signatures. Each property can either be a function, or an array of functions. */ - hooks?: Partial>; + hooks?: Partial>; /** * An object of model wide validations. Validations have access to all model values via `this`. If the @@ -1508,7 +1531,7 @@ export interface ModelOptions { /** * Options passed to [[Model.init]] */ -export interface InitOptions extends ModelOptions { +export interface InitOptions extends ModelOptions { /** * The sequelize connection. Required ATM. */ @@ -1525,7 +1548,31 @@ export interface AddScopeOptions { override: boolean; } -export abstract class Model { +export abstract class Model + extends Hooks, TModelAttributes, TCreationAttributes> +{ + /** + * A dummy variable that doesn't exist on the real object. This exists so + * Typescript can infer the type of the attributes in static functions. Don't + * try to access this! + * + * Before using these, I'd tried typing out the functions without them, but + * Typescript fails to infer `TAttributes` in signatures like the below. + * + * ```ts + * public static findOne, TAttributes>( + * this: { new(): M }, + * options: NonNullFindOptions + * ): Promise; + * ``` + */ + _attributes: TModelAttributes; + /** + * A similar dummy variable that doesn't exist on the real object. Do not + * try to access this in real code. + */ + _creationAttributes: TCreationAttributes; + /** The name of the database table */ public static readonly tableName: string; @@ -1537,7 +1584,7 @@ export abstract class Model { /** * The name of the primary key attributes */ - public static readonly primaryKeyAttributes: string[]; + public static readonly primaryKeyAttributes: readonly string[]; /** * An object hash from alias to association object @@ -1561,8 +1608,6 @@ export abstract class Model { */ public static readonly sequelize?: Sequelize; - public static readonly hooks: Hooks; - /** * Initialize a model, representing a table in the DB, with attributes and options. * @@ -1604,8 +1649,12 @@ export abstract class Model { * An object, where each attribute is a column of the table. Each column can be either a DataType, a * string or a type-description object, with the properties described below: * @param options These options are merged with the default define options provided to the Sequelize constructor + * @return Return the initialized model */ - public static init(this: ModelCtor, attributes: ModelAttributes, options: InitOptions): void; + public static init, M extends InstanceType>( + this: MS, + attributes: ModelAttributes, options: InitOptions + ): MS; /** * Remove attribute from model definition @@ -1618,7 +1667,7 @@ export abstract class Model { * Sync this Model to the DB, that is create the table. Upon success, the callback will be called with the * model instance (this) */ - public static sync(options?: SyncOptions): Promise; + public static sync(options?: SyncOptions): Promise; /** * Drop the table represented by this Model @@ -1637,10 +1686,10 @@ export abstract class Model { * @param options */ public static schema( - this: { new (): M } & typeof Model, + this: ModelStatic, schema: string, options?: SchemaOptions - ): { new (): M } & typeof Model; + ): ModelCtor; /** * Get the tablename of the model, taking schema into account. The method will return The name as a string @@ -1706,10 +1755,10 @@ export abstract class Model { * @return Model A reference to the model, with the scope(s) applied. Calling scope again on the returned * model will clear the previous scope. */ - public static scope( - this: M, - options?: string | ScopeOptions | (string | ScopeOptions)[] | WhereAttributeHash - ): M; + public static scope( + this: ModelStatic, + options?: string | ScopeOptions | readonly (string | ScopeOptions)[] | WhereAttributeHash + ): ModelCtor; /** * Add a new scope to the model @@ -1719,8 +1768,18 @@ export abstract class Model { * error if a scope with that name already exists. Pass `override: true` in the options * object to silence this error. */ - public static addScope(name: string, scope: FindOptions, options?: AddScopeOptions): void; - public static addScope(name: string, scope: (...args: any[]) => FindOptions, options?: AddScopeOptions): void; + public static addScope( + this: ModelStatic, + name: string, + scope: FindOptions, + options?: AddScopeOptions + ): void; + public static addScope( + this: ModelStatic, + name: string, + scope: (...args: readonly any[]) => FindOptions, + options?: AddScopeOptions + ): void; /** * Search for multiple instances. @@ -1784,32 +1843,36 @@ export abstract class Model { * * @see {Sequelize#query} */ - public static findAll(this: { new (): M } & typeof Model, options?: FindOptions): Promise; + public static findAll( + this: ModelStatic, + options?: FindOptions): Promise; /** * Search for a single instance by its primary key. This applies LIMIT 1, so the listener will * always be called with a single instance. */ public static findByPk( - this: { new (): M } & typeof Model, - identifier?: Identifier, - options?: Omit - ): Promise; - public static findByPk( - this: { new (): M } & typeof Model, + this: ModelStatic, identifier: Identifier, - options: Omit + options: Omit, 'where'> ): Promise; + public static findByPk( + this: ModelStatic, + identifier?: Identifier, + options?: Omit, 'where'> + ): Promise; /** - * Search for a single instance. This applies LIMIT 1, so the listener will always be called with a single - * instance. + * Search for a single instance. Returns the first instance found, or null if none can be found. */ public static findOne( - this: { new (): M } & typeof Model, - options?: FindOptions + this: ModelStatic, + options: NonNullFindOptions + ): Promise; + public static findOne( + this: ModelStatic, + options?: FindOptions ): Promise; - public static findOne(this: { new (): M } & typeof Model, options: NonNullFindOptions): Promise; /** * Run an aggregation method on the specified field @@ -1820,24 +1883,30 @@ export abstract class Model { * @return Returns the aggregate result cast to `options.dataType`, unless `options.plain` is false, in * which case the complete data result is returned. */ - public static aggregate( - this: { new (): M } & typeof Model, - field: keyof M, + public static aggregate( + this: ModelStatic, + field: keyof M['_attributes'] | '*', aggregateFunction: string, - options?: AggregateOptions + options?: AggregateOptions ): Promise; /** * Count number of records if group by is used */ - public static count(options: CountWithOptions): Promise<{ [key: string]: number }>; + public static count( + this: ModelStatic, + options: CountWithOptions + ): Promise<{ [key: string]: number }>; /** * Count the number of records matching the provided where clause. * * If you provide an `include` option, the number of matching associations will be counted instead. */ - public static count(options?: CountOptions): Promise; + public static count( + this: ModelStatic, + options?: CountOptions + ): Promise; /** * Find all the rows matching your query, within a specified offset / limit, and get the total number of @@ -1866,7 +1935,7 @@ export abstract class Model { * include: [ * { model: Profile, required: true} * ], - * limit 3 + * limit: 3 * }); * ``` * Because the include for `Profile` has `required` set it will result in an inner join, and only the users @@ -1875,63 +1944,78 @@ export abstract class Model { * profiles will be counted */ public static findAndCountAll( - this: { new (): M } & typeof Model, - options?: FindAndCountOptions + this: ModelStatic, + options?: FindAndCountOptions & { group: GroupOption } + ): Promise<{ rows: M[]; count: number[] }>; + public static findAndCountAll( + this: ModelStatic, + options?: FindAndCountOptions ): Promise<{ rows: M[]; count: number }>; /** * Find the maximum value of field */ - public static max( - this: { new (): M } & typeof Model, - field: keyof M, - options?: AggregateOptions + public static max( + this: ModelStatic, + field: keyof M['_attributes'], + options?: AggregateOptions ): Promise; /** * Find the minimum value of field */ - public static min( - this: { new (): M } & typeof Model, - field: keyof M, - options?: AggregateOptions + public static min( + this: ModelStatic, + field: keyof M['_attributes'], + options?: AggregateOptions ): Promise; /** * Find the sum of field */ - public static sum( - this: { new (): M } & typeof Model, - field: keyof M, - options?: AggregateOptions + public static sum( + this: ModelStatic, + field: keyof M['_attributes'], + options?: AggregateOptions ): Promise; /** - * Builds multiple model instances in one go. + * Builds a new model instance. Values is an object of key value pairs, must be defined but can be empty. + */ + public static build( + this: ModelStatic, + record?: M['_creationAttributes'], + options?: BuildOptions + ): M; + + /** + * Undocumented bulkBuild */ public static bulkBuild( - this: { new (): M } & typeof Model, - records: object[], + this: ModelStatic, + records: ReadonlyArray, options?: BuildOptions ): M[]; /** * Builds a new model instance and calls save on it. */ - public static create( - this: { new (): M } & typeof Model, - values?: object, - options?: CreateOptions - ): Promise; - public static create(values: object, options: CreateOptions & { returning: false }): Promise; + public static create< + M extends Model, + O extends CreateOptions = CreateOptions + >( + this: ModelStatic, + values?: M['_creationAttributes'], + options?: O + ): Promise; /** * Find a row that matches the query, or build (but don't save) the row if none is found. - * The successfull result of the promise will be (instance, initialized) - Make sure to use `.then(([...]))` + * The successful result of the promise will be (instance, initialized) - Make sure to use `.then(([...]))` */ public static findOrBuild( - this: { new (): M } & typeof Model, - options: FindOrCreateOptions + this: ModelStatic, + options: FindOrCreateOptions ): Promise<[M, boolean]>; /** @@ -1946,8 +2030,8 @@ export abstract class Model { * will be created instead, and any unique constraint violation will be handled internally. */ public static findOrCreate( - this: { new (): M } & typeof Model, - options: FindOrCreateOptions + this: ModelStatic, + options: FindOrCreateOptions ): Promise<[M, boolean]>; /** @@ -1955,8 +2039,8 @@ export abstract class Model { * Will execute a find call, if empty then attempt to create, if unique constraint then attempt to find again */ public static findCreateFind( - this: { new (): M } & typeof Model, - options: FindOrCreateOptions + this: ModelStatic, + options: FindOrCreateOptions ): Promise<[M, boolean]>; /** @@ -1974,21 +2058,15 @@ export abstract class Model { * regardless * of whether the row already existed or not * - * **Note** that SQLite returns undefined for created, no matter if the row was created or updated. This is + * **Note** that SQLite returns null for created, no matter if the row was created or updated. This is * because SQLite always runs INSERT OR IGNORE + UPDATE, in a single query, so there is no way to know * whether the row was inserted or not. */ public static upsert( - this: { new (): M } & typeof Model, - values: object, - options?: UpsertOptions & { returning?: false | undefined } - ): Promise; - - public static upsert ( - this: { new (): M } & typeof Model, - values: object, - options?: UpsertOptions & { returning: true } - ): Promise<[ M, boolean ]>; + this: ModelStatic, + values: M['_creationAttributes'], + options?: UpsertOptions + ): Promise<[M, boolean | null]>; /** * Create and insert multiple instances in bulk. @@ -2002,66 +2080,104 @@ export abstract class Model { * @param records List of objects (key/value pairs) to create instances from */ public static bulkCreate( - this: { new (): M } & typeof Model, - records: object[], - options?: BulkCreateOptions + this: ModelStatic, + records: ReadonlyArray, + options?: BulkCreateOptions ): Promise; /** * Truncate all instances of the model. This is a convenient method for Model.destroy({ truncate: true }). */ - public static truncate(options?: TruncateOptions): Promise; + public static truncate( + this: ModelStatic, + options?: TruncateOptions + ): Promise; /** * Delete multiple instances, or set their deletedAt timestamp to the current time if `paranoid` is enabled. * * @return Promise The number of destroyed rows */ - public static destroy(options?: DestroyOptions): Promise; + public static destroy( + this: ModelStatic, + options?: DestroyOptions + ): Promise; /** * Restore multiple instances if `paranoid` is enabled. */ - public static restore(options?: RestoreOptions): Promise; + public static restore( + this: ModelStatic, + options?: RestoreOptions + ): Promise; /** * Update multiple instances that match the where options. The promise returns an array with one or two * elements. The first element is always the number of affected rows, while the second element is the actual - * affected rows (only supported in postgres with `options.returning` true.) + * affected rows (only supported in postgres and mssql with `options.returning` true.) */ public static update( - this: { new (): M } & typeof Model, - values: object, - options: UpdateOptions + this: ModelStatic, + values: { + [key in keyof M['_attributes']]?: M['_attributes'][key] | Fn | Col | Literal; + }, + options: UpdateOptions ): Promise<[number, M[]]>; /** * Increments a single field. */ - public static increment( - this: { new (): M }, - field: K, - options: IncrementDecrementOptionsWithBy + public static increment( + this: ModelStatic, + field: keyof M['_attributes'], + options: IncrementDecrementOptionsWithBy ): Promise; /** * Increments multiple fields by the same value. */ - public static increment( - this: { new (): M }, - fields: K[], - options: IncrementDecrementOptionsWithBy + public static increment( + this: ModelStatic, + fields: ReadonlyArray, + options: IncrementDecrementOptionsWithBy ): Promise; /** * Increments multiple fields by different values. */ - public static increment( - this: { new (): M }, - fields: { [key in K]?: number }, - options: IncrementDecrementOptions + public static increment( + this: ModelStatic, + fields: { [key in keyof M['_attributes']]?: number }, + options: IncrementDecrementOptions + ): Promise; + + /** + * Decrements a single field. + */ + public static decrement( + this: ModelStatic, + field: keyof M['_attributes'], + options: IncrementDecrementOptionsWithBy + ): Promise; + + /** + * Decrements multiple fields by the same value. + */ + public static decrement( + this: ModelStatic, + fields: (keyof M['_attributes'])[], + options: IncrementDecrementOptionsWithBy ): Promise; + /** + * Decrements multiple fields by different values. + */ + public static decrement( + this: ModelStatic, + fields: { [key in keyof M['_attributes']]?: number }, + options: IncrementDecrementOptions + ): Promise; + /** * Run a describe query on the table. The result will be return to the listener as a hash of attributes and * their types. @@ -2071,7 +2187,362 @@ export abstract class Model { /** * Unscope the model */ - public static unscoped(this: M): M; + public static unscoped(this: M): M; + + /** + * A hook that is run before validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeValidate( + this: ModelStatic, + name: string, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + public static beforeValidate( + this: ModelStatic, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + + /** + * A hook that is run after validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterValidate( + this: ModelStatic, + name: string, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + public static afterValidate( + this: ModelStatic, + fn: (instance: M, options: ValidationOptions) => HookReturn + ): void; + + /** + * A hook that is run before creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static beforeCreate( + this: ModelStatic, + name: string, + fn: (instance: M, options: CreateOptions) => HookReturn + ): void; + public static beforeCreate( + this: ModelStatic, + fn: (instance: M, options: CreateOptions) => HookReturn + ): void; + + /** + * A hook that is run after creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static afterCreate( + this: ModelStatic, + name: string, + fn: (instance: M, options: CreateOptions) => HookReturn + ): void; + public static afterCreate( + this: ModelStatic, + fn: (instance: M, options: CreateOptions) => HookReturn + ): void; + + /** + * A hook that is run before destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeDestroy( + this: ModelStatic, + name: string, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + public static beforeDestroy( + this: ModelStatic, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + + /** + * A hook that is run after destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterDestroy( + this: ModelStatic, + name: string, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + public static afterDestroy( + this: ModelStatic, + fn: (instance: M, options: InstanceDestroyOptions) => HookReturn + ): void; + + /** + * A hook that is run before updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeUpdate( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions) => HookReturn + ): void; + public static beforeUpdate( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions) => HookReturn + ): void; + + /** + * A hook that is run after updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterUpdate( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions) => HookReturn + ): void; + public static afterUpdate( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions) => HookReturn + ): void; + + /** + * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeSave( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + ): void; + public static beforeSave( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + ): void; + + /** + * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterSave( + this: ModelStatic, + name: string, + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + ): void; + public static afterSave( + this: ModelStatic, + fn: (instance: M, options: UpdateOptions | SaveOptions) => HookReturn + ): void; + + /** + * A hook that is run before creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public static beforeBulkCreate( + this: ModelStatic, + name: string, + fn: (instances: M[], options: BulkCreateOptions) => HookReturn + ): void; + public static beforeBulkCreate( + this: ModelStatic, + fn: (instances: M[], options: BulkCreateOptions) => HookReturn + ): void; + + /** + * A hook that is run after creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public static afterBulkCreate( + this: ModelStatic, + name: string, + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn + ): void; + public static afterBulkCreate( + this: ModelStatic, + fn: (instances: readonly M[], options: BulkCreateOptions) => HookReturn + ): void; + + /** + * A hook that is run before destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeBulkDestroy( + this: ModelStatic, + name: string, fn: (options: BulkCreateOptions) => HookReturn): void; + public static beforeBulkDestroy( + this: ModelStatic, + fn: (options: BulkCreateOptions) => HookReturn + ): void; + + /** + * A hook that is run after destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterBulkDestroy( + this: ModelStatic, + name: string, fn: (options: DestroyOptions) => HookReturn + ): void; + public static afterBulkDestroy( + this: ModelStatic, + fn: (options: DestroyOptions) => HookReturn + ): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeBulkUpdate( + this: ModelStatic, + name: string, fn: (options: UpdateOptions) => HookReturn + ): void; + public static beforeBulkUpdate( + this: ModelStatic, + fn: (options: UpdateOptions) => HookReturn + ): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterBulkUpdate( + this: ModelStatic, + name: string, fn: (options: UpdateOptions) => HookReturn + ): void; + public static afterBulkUpdate( + this: ModelStatic, + fn: (options: UpdateOptions) => HookReturn + ): void; + + /** + * A hook that is run before a find (select) query + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFind( + this: ModelStatic, + name: string, fn: (options: FindOptions) => HookReturn + ): void; + public static beforeFind( + this: ModelStatic, + fn: (options: FindOptions) => HookReturn + ): void; + + /** + * A hook that is run before a count query + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeCount( + this: ModelStatic, + name: string, fn: (options: CountOptions) => HookReturn + ): void; + public static beforeCount( + this: ModelStatic, + fn: (options: CountOptions) => HookReturn + ): void; + + /** + * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFindAfterExpandIncludeAll( + this: ModelStatic, + name: string, fn: (options: FindOptions) => HookReturn + ): void; + public static beforeFindAfterExpandIncludeAll( + this: ModelStatic, + fn: (options: FindOptions) => HookReturn + ): void; + + /** + * A hook that is run before a find (select) query, after all option parsing is complete + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFindAfterOptions( + this: ModelStatic, + name: string, fn: (options: FindOptions) => HookReturn + ): void; + public static beforeFindAfterOptions( + this: ModelStatic, + fn: (options: FindOptions) => void + ): HookReturn; + + /** + * A hook that is run after a find (select) query + * + * @param name + * @param fn A callback function that is called with instance(s), options + */ + public static afterFind( + this: ModelStatic, + name: string, + fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions) => HookReturn + ): void; + public static afterFind( + this: ModelStatic, + fn: (instancesOrInstance: readonly M[] | M | null, options: FindOptions) => HookReturn + ): void; + + /** + * A hook that is run before sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public static beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run before Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static afterSync(fn: (options: SyncOptions) => HookReturn): void; /** * Creates an association between this (the source) and the provided target. The foreign key is added @@ -2083,7 +2554,7 @@ export abstract class Model { * @param options Options for the association */ public static hasOne( - this: ModelCtor, target: ModelCtor, options?: HasOneOptions + this: ModelStatic, target: ModelStatic, options?: HasOneOptions ): HasOne; /** @@ -2096,7 +2567,7 @@ export abstract class Model { * @param options Options for the association */ public static belongsTo( - this: ModelCtor, target: ModelCtor, options?: BelongsToOptions + this: ModelStatic, target: ModelStatic, options?: BelongsToOptions ): BelongsTo; /** @@ -2153,7 +2624,7 @@ export abstract class Model { * @param options Options for the association */ public static hasMany( - this: ModelCtor, target: ModelCtor, options?: HasManyOptions + this: ModelStatic, target: ModelStatic, options?: HasManyOptions ): HasMany; /** @@ -2206,7 +2677,7 @@ export abstract class Model { * */ public static belongsToMany( - this: ModelCtor, target: ModelCtor, options: BelongsToManyOptions + this: ModelStatic, target: ModelStatic, options: BelongsToManyOptions ): BelongsToMany; /** @@ -2223,7 +2694,7 @@ export abstract class Model { * Builds a new model instance. * @param values an object of key value pairs */ - constructor(values?: object, options?: BuildOptions); + constructor(values?: TCreationAttributes, options?: BuildOptions); /** * Get an object representing the query for this instance, use with `options.where` @@ -2233,12 +2704,12 @@ export abstract class Model { /** * Get the value of the underlying data value */ - public getDataValue(key: K): this[K]; + public getDataValue(key: K): TModelAttributes[K]; /** * Update the underlying data value */ - public setDataValue(key: K, value: this[K]): void; + public setDataValue(key: K, value: TModelAttributes[K]): void; /** * If no key is given, returns all values of the instance, also invoking virtual getters. @@ -2248,7 +2719,7 @@ export abstract class Model { * * @param options.plain If set to true, included instances will be returned as plain objects */ - public get(options?: { plain?: boolean; clone?: boolean }): object; + public get(options?: { plain?: boolean; clone?: boolean }): TModelAttributes; public get(key: K, options?: { plain?: boolean; clone?: boolean }): this[K]; public get(key: string, options?: { plain?: boolean; clone?: boolean }): unknown; @@ -2267,7 +2738,7 @@ export abstract class Model { * * Set can also be used to build instances for associations, if you have values for those. * When using set with associations you need to make sure the property key matches the alias of the - * association while also making sure that the proper include options have been set (from the constructor or + * association while also making sure that the proper include options have been set (from .build() or * .findOne()) * * If called with a dot.seperated key on a JSON/JSONB attribute it will set the value nested and flag the @@ -2276,10 +2747,10 @@ export abstract class Model { * @param options.raw If set to true, field and virtual setters will be ignored * @param options.reset Clear all previously set data values */ - public set(key: K, value: this[K], options?: SetOptions): this; - public set(keys: Partial, options?: SetOptions): this; - public setAttributes(key: K, value: this[K], options?: SetOptions): this; - public setAttributes(keys: object, options?: SetOptions): this; + public set(key: K, value: TModelAttributes[K], options?: SetOptions): this; + public set(keys: Partial, options?: SetOptions): this; + public setAttributes(key: K, value: TModelAttributes[K], options?: SetOptions): this; + public setAttributes(keys: Partial, options?: SetOptions): this; /** * If changed is called with a string it will return a boolean indicating whether the value of that key in @@ -2298,16 +2769,19 @@ export abstract class Model { /** * Returns the previous value for key from `_previousDataValues`. */ - public previous(key: K): this[K]; + public previous(): Partial; + public previous(key: K): TCreationAttributes[K] | undefined; /** - * Validate this instance, and if the validation passes, persist it to the database. + * Validates this instance, and if the validation passes, persists it to the database. * - * On success, the callback will be called with this instance. On validation error, the callback will be - * called with an instance of `Sequelize.ValidationError`. This error will have a property for each of the - * fields for which validation failed, with the error message for that field. + * Returns a Promise that resolves to the saved instance (or rejects with a `Sequelize.ValidationError`, which will have a property for each of the fields for which the validation failed, with the error message for that field). + * + * This method is optimized to perform an UPDATE only into the fields that changed. If nothing has changed, no SQL query will be performed. + * + * This method is not aware of eager loaded associations. In other words, if some other model instance (child) was eager loaded with this instance (parent), and you change something in the child, calling `save()` will simply ignore the change that happened on the child. */ - public save(options?: SaveOptions): Promise; + public save(options?: SaveOptions): Promise; /** * Refresh the current instance in-place, i.e. update the object with current data from the DB and return @@ -2315,7 +2789,7 @@ export abstract class Model { * return a new instance. With this method, all references to the Instance are updated with the new data * and no new objects are created. */ - public reload(options?: FindOptions): Promise; + public reload(options?: FindOptions): Promise; /** * Validate the attribute of this instance according to validation rules set in the model definition. @@ -2330,8 +2804,13 @@ export abstract class Model { /** * This is the same as calling `set` and then calling `save`. */ - public update(key: K, value: this[K], options?: InstanceUpdateOptions): Promise; - public update(keys: object, options?: InstanceUpdateOptions): Promise; + public update(key: K, value: TModelAttributes[K] | Col | Fn | Literal, options?: InstanceUpdateOptions): Promise; + public update( + keys: { + [key in keyof TModelAttributes]?: TModelAttributes[key] | Fn | Col | Literal; + }, + options?: InstanceUpdateOptions + ): Promise; /** * Destroy the row corresponding to this instance. Depending on your setting for paranoid, the row will @@ -2364,9 +2843,9 @@ export abstract class Model { * If an array is provided, the same is true for each column. * If and object is provided, each column is incremented by the value given. */ - public increment( - fields: K | K[] | Partial, - options?: IncrementDecrementOptionsWithBy + public increment( + fields: K | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy ): Promise; /** @@ -2389,9 +2868,9 @@ export abstract class Model { * If an array is provided, the same is true for each column. * If and object is provided, each column is decremented by the value given */ - public decrement( - fields: K | K[] | Partial, - options?: IncrementDecrementOptionsWithBy + public decrement( + fields: K | readonly K[] | Partial, + options?: IncrementDecrementOptionsWithBy ): Promise; /** @@ -2400,19 +2879,35 @@ export abstract class Model { public equals(other: this): boolean; /** - * Check if this is eqaul to one of `others` by calling equals + * Check if this is equal to one of `others` by calling equals */ - public equalsOneOf(others: this[]): boolean; + public equalsOneOf(others: readonly this[]): boolean; /** * Convert the instance to a JSON representation. Proxies to calling `get` with no keys. This means get all * values gotten from the DB, and apply all custom getters. */ - public toJSON(): object; + public toJSON(): T; + + /** + * Helper method to determine if a instance is "soft deleted". This is + * particularly useful if the implementer renamed the deletedAt attribute to + * something different. This method requires paranoid to be enabled. + * + * Throws an error if paranoid is not enabled. + */ + public isSoftDeleted(): boolean; } -export type ModelType = typeof Model; +export type ModelType = new () => Model; + +// Do not switch the order of `typeof Model` and `{ new(): M }`. For +// instances created by `sequelize.define` to typecheck well, `typeof Model` +// must come first for unknown reasons. +export type ModelCtor = typeof Model & { new(): M }; + +export type ModelDefined = ModelCtor>; -export type ModelCtor = { new (): M } & ModelType; +export type ModelStatic = { new(): M }; export default Model; diff --git a/types/lib/operators.d.ts b/types/lib/operators.d.ts index 85bc0ddb6678..18d66589b9ef 100644 --- a/types/lib/operators.d.ts +++ b/types/lib/operators.d.ts @@ -243,6 +243,18 @@ declare const Op: { * ``` */ readonly lte: unique symbol; + /** + * Operator @@ + * + * ```js + * [Op.match]: Sequelize.fn('to_tsquery', 'fat & rat')` + * ``` + * In SQL + * ```sql + * @@ to_tsquery('fat & rat') + * ``` + */ + readonly match: unique symbol; /** * Operator != * @@ -388,7 +400,7 @@ declare const Op: { */ readonly overlap: unique symbol; /** - * Internal placeholder + * Internal placeholder * * ```js * [Op.placeholder]: true @@ -457,7 +469,7 @@ declare const Op: { readonly substring: unique symbol; /** * Operator VALUES - * + * * ```js * [Op.values]: [4, 5, 6] * ``` diff --git a/types/lib/promise.d.ts b/types/lib/promise.d.ts deleted file mode 100644 index 2ee910deb9e2..000000000000 --- a/types/lib/promise.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -// tslint:disable-next-line:no-implicit-dependencies -import * as Bluebird from 'bluebird'; - -export const Promise: typeof Bluebird; -export type Promise = Bluebird; -export default Promise; diff --git a/types/lib/query-interface.d.ts b/types/lib/query-interface.d.ts index b53b377472fa..ec4529af6d42 100644 --- a/types/lib/query-interface.d.ts +++ b/types/lib/query-interface.d.ts @@ -1,11 +1,24 @@ import { DataType } from './data-types'; -import { Logging, Model, ModelAttributeColumnOptions, ModelAttributes, Transactionable, WhereOptions, Filterable, Poolable } from './model'; -import { Promise } from './promise'; +import { + Logging, + Model, + ModelAttributeColumnOptions, + ModelAttributes, + Transactionable, + WhereOptions, + Filterable, + Poolable, + ModelCtor, ModelStatic, ModelType +} from './model'; import QueryTypes = require('./query-types'); import { Sequelize, RetryOptions } from './sequelize'; import { Transaction } from './transaction'; +import { SetRequired } from './../type-helpers/set-required'; +import { Fn, Literal } from './utils'; +import { Deferrable } from './deferrable'; type BindOrReplacements = { [key: string]: unknown } | unknown[]; +type FieldMap = { [key: string]: string }; /** * Interface for query options @@ -62,17 +75,22 @@ export interface QueryOptions extends Logging, Transactionable, Poolable { mapToModel?: boolean; retry?: RetryOptions; + + /** + * Map returned fields to arbitrary names for SELECT query type if `options.fieldMaps` is present. + */ + fieldMap?: FieldMap; } -export interface QueryOptionsWithWhere extends QueryOptions, Filterable { +export interface QueryOptionsWithWhere extends QueryOptions, Filterable { } -export interface QueryOptionsWithModel extends QueryOptions { +export interface QueryOptionsWithModel extends QueryOptions { /** * A sequelize model used to build the returned model instances (used to be called callee) */ - model: typeof Model; + model: ModelStatic; } export interface QueryOptionsWithType extends QueryOptions { @@ -120,6 +138,15 @@ export interface QueryInterfaceDropAllTablesOptions extends QueryInterfaceOption skip?: string[]; } +export interface TableNameWithSchema { + tableName: string; + schema?: string; + delimiter?: string; + as?: string; + name?: string; +} +export type TableName = string | TableNameWithSchema; + export type IndexType = 'UNIQUE' | 'FULLTEXT' | 'SPATIAL'; export type IndexMethod = 'BTREE' | 'HASH' | 'GIST' | 'SPGIST' | 'GIN' | 'BRIN' | string; @@ -155,9 +182,9 @@ export interface IndexesOptions { * An array of the fields to index. Each field can either be a string containing the name of the field, * a sequelize object (e.g `sequelize.fn`), or an object with the following attributes: `name` * (field name), `length` (create a prefix index of length chars), `order` (the direction the column - * should be sorted in), `collate` (the collation (sort order) for the column) + * should be sorted in), `collate` (the collation (sort order) for the column), `operator` (likes IndexesOptions['operator']) */ - fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string })[]; + fields?: (string | { name: string; length?: number; order?: 'ASC' | 'DESC'; collate?: string; operator?: string } | Fn | Literal)[]; /** * The method to create the index by (`USING` statement in SQL). BTREE and HASH are supported by mysql and @@ -173,7 +200,7 @@ export interface IndexesOptions { /** * Optional where parameter for index. Can be used to limit the index to certain rows. */ - where?: WhereOptions; + where?: WhereOptions; /** * Prefix to append to the index name. @@ -183,37 +210,40 @@ export interface IndexesOptions { export interface QueryInterfaceIndexOptions extends IndexesOptions, QueryInterfaceOptions {} -export interface AddUniqueConstraintOptions { - type: 'unique'; +export interface BaseConstraintOptions { name?: string; + fields: string[]; } -export interface AddDefaultConstraintOptions { +export interface AddUniqueConstraintOptions extends BaseConstraintOptions { + type: 'unique'; + deferrable?: Deferrable; +} + +export interface AddDefaultConstraintOptions extends BaseConstraintOptions { type: 'default'; - name?: string; defaultValue?: unknown; } -export interface AddCheckConstraintOptions { +export interface AddCheckConstraintOptions extends BaseConstraintOptions { type: 'check'; - name?: string; - where?: WhereOptions; + where?: WhereOptions; } -export interface AddPrimaryKeyConstraintOptions { +export interface AddPrimaryKeyConstraintOptions extends BaseConstraintOptions { type: 'primary key'; - name?: string; + deferrable?: Deferrable; } -export interface AddForeignKeyConstraintOptions { +export interface AddForeignKeyConstraintOptions extends BaseConstraintOptions { type: 'foreign key'; - name?: string; references?: { - table: string; + table: TableName; field: string; }; onDelete: string; onUpdate: string; + deferrable?: Deferrable; } export type AddConstraintOptions = @@ -233,10 +263,23 @@ export interface FunctionParam { direction?: string; } +export interface ColumnDescription { + type: string; + allowNull: boolean; + defaultValue: string; + primaryKey: boolean; + autoIncrement: boolean; + comment: string | null; +} + +export interface ColumnsDescription { + [key: string]: ColumnDescription; +} + /** * The interface that Sequelize uses to talk to all databases. * -* This interface is available through sequelize.QueryInterface. It should not be commonly used, but it's +* This interface is available through sequelize.queryInterface. It should not be commonly used, but it's * referenced anyway, so it can be used. */ export class QueryInterface { @@ -245,7 +288,7 @@ export class QueryInterface { * * We don't have a definition for the QueryGenerator, because I doubt it is commonly in use separately. */ - public QueryGenerator: unknown; + public queryGenerator: unknown; /** * Returns the current sequelize instance. @@ -292,9 +335,9 @@ export class QueryInterface { * @param attributes Hash of attributes, key is attribute name, value is data type * @param options Table options. */ - public createTable( - tableName: string | { schema?: string; tableName?: string }, - attributes: ModelAttributes, + public createTable( + tableName: TableName, + attributes: ModelAttributes, options?: QueryInterfaceCreateTableOptions ): Promise; @@ -304,14 +347,14 @@ export class QueryInterface { * @param tableName Table name. * @param options Query options, particularly "force". */ - public dropTable(tableName: string, options?: QueryInterfaceDropTableOptions): Promise; + public dropTable(tableName: TableName, options?: QueryInterfaceDropTableOptions): Promise; /** * Drops all tables. * * @param options */ - public dropAllTables(options?: QueryInterfaceDropTableOptions): Promise; + public dropAllTables(options?: QueryInterfaceDropAllTablesOptions): Promise; /** * Drops all defined enums @@ -323,7 +366,7 @@ export class QueryInterface { /** * Renames a table */ - public renameTable(before: string, after: string, options?: QueryInterfaceOptions): Promise; + public renameTable(before: TableName, after: TableName, options?: QueryInterfaceOptions): Promise; /** * Returns all tables @@ -334,15 +377,15 @@ export class QueryInterface { * Describe a table */ public describeTable( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, options?: string | { schema?: string; schemaDelimiter?: string } & Logging - ): Promise; + ): Promise; /** * Adds a new column to a table */ public addColumn( - table: string | { schema?: string; tableName?: string }, + table: TableName, key: string, attribute: ModelAttributeColumnOptions | DataType, options?: QueryInterfaceOptions @@ -352,7 +395,7 @@ export class QueryInterface { * Removes a column from a table */ public removeColumn( - table: string | { schema?: string; tableName?: string }, + table: TableName, attribute: string, options?: QueryInterfaceOptions ): Promise; @@ -361,7 +404,7 @@ export class QueryInterface { * Changes a column */ public changeColumn( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, attributeName: string, dataTypeOrOptions?: DataType | ModelAttributeColumnOptions, options?: QueryInterfaceOptions @@ -371,7 +414,7 @@ export class QueryInterface { * Renames a column */ public renameColumn( - tableName: string | { schema?: string; tableName?: string }, + tableName: TableName, attrNameBefore: string, attrNameAfter: string, options?: QueryInterfaceOptions @@ -381,36 +424,35 @@ export class QueryInterface { * Adds a new index to a table */ public addIndex( - tableName: string, + tableName: TableName, attributes: string[], options?: QueryInterfaceIndexOptions, rawTablename?: string ): Promise; public addIndex( - tableName: string, - options: QueryInterfaceIndexOptions & { fields: string[] }, + tableName: TableName, + options: SetRequired, rawTablename?: string ): Promise; /** * Removes an index of a table */ - public removeIndex(tableName: string, indexName: string, options?: QueryInterfaceIndexOptions): Promise; - public removeIndex(tableName: string, attributes: string[], options?: QueryInterfaceIndexOptions): Promise; + public removeIndex(tableName: TableName, indexName: string, options?: QueryInterfaceIndexOptions): Promise; + public removeIndex(tableName: TableName, attributes: string[], options?: QueryInterfaceIndexOptions): Promise; /** * Adds constraints to a table */ public addConstraint( - tableName: string, - attributes: string[], + tableName: TableName, options?: AddConstraintOptions & QueryInterfaceOptions ): Promise; /** * Removes constraints from a table */ - public removeConstraint(tableName: string, constraintName: string, options?: QueryInterfaceOptions): Promise; + public removeConstraint(tableName: TableName, constraintName: string, options?: QueryInterfaceOptions): Promise; /** * Shows the index of a table @@ -423,23 +465,29 @@ export class QueryInterface { public nameIndexes(indexes: string[], rawTablename: string): Promise; /** - * Returns all foreign key constraints of a table + * Returns all foreign key constraints of requested tables + */ + public getForeignKeysForTables(tableNames: string[], options?: QueryInterfaceOptions): Promise; + + /** + * Get foreign key references details for the table */ - public getForeignKeysForTables(tableNames: string, options?: QueryInterfaceOptions): Promise; + public getForeignKeyReferencesForTable(tableName: TableName, options?: QueryInterfaceOptions): Promise; /** * Inserts a new record */ - public insert(instance: Model, tableName: string, values: object, options?: QueryOptions): Promise; + public insert(instance: Model | null, tableName: string, values: object, options?: QueryOptions): Promise; /** * Inserts or Updates a record in the database */ public upsert( - tableName: string, - values: object, + tableName: TableName, + insertValues: object, updateValues: object, - model: typeof Model, + where: object, + model: ModelType, options?: QueryOptions ): Promise; @@ -447,20 +495,20 @@ export class QueryInterface { * Inserts multiple records at once */ public bulkInsert( - tableName: string, + tableName: TableName, records: object[], options?: QueryOptions, attributes?: string[] | string - ): Promise; + ): Promise; /** * Updates a row */ - public update( - instance: Model, - tableName: string, + public update( + instance: M, + tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions ): Promise; @@ -468,9 +516,9 @@ export class QueryInterface { * Updates multiple rows at once */ public bulkUpdate( - tableName: string, + tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions, attributes?: string[] | string ): Promise; @@ -478,31 +526,36 @@ export class QueryInterface { /** * Deletes a row */ - public delete(instance: Model | null, tableName: string, identifier: WhereOptions, options?: QueryOptions): Promise; + public delete( + instance: Model | null, + tableName: TableName, + identifier: WhereOptions, + options?: QueryOptions + ): Promise; /** * Deletes multiple rows at once */ public bulkDelete( - tableName: string, - identifier: WhereOptions, + tableName: TableName, + identifier: WhereOptions, options?: QueryOptions, - model?: typeof Model + model?: ModelType ): Promise; /** * Returns selected rows */ - public select(model: typeof Model | null, tableName: string, options?: QueryOptionsWithWhere): Promise; + public select(model: ModelType | null, tableName: TableName, options?: QueryOptionsWithWhere): Promise; /** * Increments a row value */ - public increment( + public increment( instance: Model, - tableName: string, + tableName: TableName, values: object, - identifier: WhereOptions, + identifier: WhereOptions, options?: QueryOptions ): Promise; @@ -510,10 +563,10 @@ export class QueryInterface { * Selects raw without parsing the string into an object */ public rawSelect( - tableName: string, + tableName: TableName, options: QueryOptionsWithWhere, attributeSelector: string | string[], - model?: typeof Model + model?: ModelType ): Promise; /** @@ -521,7 +574,7 @@ export class QueryInterface { * parameters. */ public createTrigger( - tableName: string, + tableName: TableName, triggerName: string, timingType: string, fireOnArray: { @@ -536,13 +589,13 @@ export class QueryInterface { /** * Postgres only. Drops the specified trigger. */ - public dropTrigger(tableName: string, triggerName: string, options?: QueryInterfaceOptions): Promise; + public dropTrigger(tableName: TableName, triggerName: string, options?: QueryInterfaceOptions): Promise; /** * Postgres only. Renames a trigger */ public renameTrigger( - tableName: string, + tableName: TableName, oldTriggerName: string, newTriggerName: string, options?: QueryInterfaceOptions @@ -585,7 +638,7 @@ export class QueryInterface { /** * Escape a table name */ - public quoteTable(identifier: string): string; + public quoteTable(identifier: TableName): string; /** * Split an identifier into .-separated tokens and quote each part. If force is true, the identifier will be diff --git a/types/lib/query.d.ts b/types/lib/query.d.ts new file mode 100644 index 000000000000..b051d71bd54f --- /dev/null +++ b/types/lib/query.d.ts @@ -0,0 +1,328 @@ +import { IncludeOptions } from '..'; +import { Connection } from './connection-manager'; +import { + Model, ModelType +} from './model'; +import { Sequelize } from './sequelize'; +import QueryTypes = require('./query-types'); + +type BindOrReplacements = { [key: string]: unknown } | unknown[]; +type FieldMap = { [key: string]: string }; + + +export interface AbstractQueryGroupJoinDataOptions { + checkExisting: boolean; +} + +export interface AbstractQueryOptions { + instance?: Model; + model?: ModelType; + type?: QueryTypes; + + fieldMap?: boolean; + plain: boolean; + raw: boolean; + nest: boolean; + hasJoin: boolean; + + /** + * A function that gets executed while running the query to log the sql. + */ + logging?: boolean | ((sql: string, timing?: number) => void); + + include: boolean; + includeNames: unknown[]; + includeMap: any; + + originalAttributes: unknown[]; + attributes: unknown[]; +} + +export interface AbstractQueryFormatBindOptions { + /** + * skip unescaping $$ + */ + skipUnescape: boolean; + + /** + * do not replace (but do unescape $$) + */ + skipValueReplace: boolean; +} + +type replacementFuncType = ((match: string, key: string, values: unknown[], timeZone?: string, dialect?: string, options?: AbstractQueryFormatBindOptions) => undefined | string); + +/** +* An abstract class that Sequelize uses to add query support for a dialect. +* +* This interface is only exposed when running before/afterQuery lifecycle events. +*/ +export class AbstractQuery { + /** + * Returns a unique identifier assigned to a query internally by Sequelize. + */ + public uuid: unknown; + + /** + * A Sequelize connection instance. + * + * @type {Connection} + * @memberof AbstractQuery + */ + public connection: Connection; + + /** + * If provided, returns the model instance. + * + * @type {Model} + * @memberof AbstractQuery + */ + public instance: Model; + + /** + * Model type definition. + * + * @type {ModelType} + * @memberof AbstractQuery + */ + public model: ModelType; + + /** + * Returns the current sequelize instance. + */ + public sequelize: Sequelize; + + /** + * + * @type {AbstractQueryOptions} + * @memberof AbstractQuery + */ + public options: AbstractQueryOptions; + + constructor(connection: Connection, sequelize: Sequelize, options?: AbstractQueryOptions); + + /** + * rewrite query with parameters + * + * Examples: + * + * query.formatBindParameters('select $1 as foo', ['fooval']); + * + * query.formatBindParameters('select $foo as foo', { foo: 'fooval' }); + * + * Options + * skipUnescape: bool, skip unescaping $$ + * skipValueReplace: bool, do not replace (but do unescape $$). Check correct syntax and if all values are available + * + * @param {string} sql + * @param {object|Array} values + * @param {string} dialect + * @param {Function} [replacementFunc] + * @param {object} [options] + * @private + */ + static formatBindParameters(sql: string, values: object | Array, dialect: string, replacementFunc: replacementFuncType, options: AbstractQueryFormatBindOptions): undefined | [string, unknown[]]; + + /** + * Execute the passed sql query. + * + * Examples: + * + * query.run('SELECT 1') + * + * @private + */ + private run(): Error + + /** + * Check the logging option of the instance and print deprecation warnings. + * + * @private + */ + private checkLoggingOption(): void; + + /** + * Get the attributes of an insert query, which contains the just inserted id. + * + * @returns {string} The field name. + * @private + */ + private getInsertIdField(): string; + + /** + * Returns the unique constraint error message for the associated field. + * + * @param field {string} the field name associated with the unique constraint. + * + * @returns {string} The unique constraint error message. + * @private + */ + private getUniqueConstraintErrorMessage(field: string): string; + + /** + * Checks if the query type is RAW + * @returns {boolean} + */ + public isRawQuery(): boolean; + + /** + * Checks if the query type is VERSION + * @returns {boolean} + */ + public isVersionQuery(): boolean; + + /** + * Checks if the query type is UPSERT + * @returns {boolean} + */ + public isUpsertQuery(): boolean; + + /** + * Checks if the query type is INSERT + * @returns {boolean} + */ + public isInsertQuery(results?: unknown[], metaData?: unknown): boolean; + + /** + * Sets auto increment field values (if applicable). + * + * @param results {Array} + * @param metaData {object} + * @returns {boolean} + */ + public handleInsertQuery(results?: unknown[], metaData?: unknown): void; + + /** + * Checks if the query type is SHOWTABLES + * @returns {boolean} + */ + public isShowTablesQuery(): boolean; + + /** + * Flattens and plucks values from results. + * + * @params {Array} + * @returns {Array} + */ + public handleShowTablesQuery(results: unknown[]): unknown[]; + + /** + * Checks if the query type is SHOWINDEXES + * @returns {boolean} + */ + public isShowIndexesQuery(): boolean; + + /** + * Checks if the query type is SHOWCONSTRAINTS + * @returns {boolean} + */ + public isShowConstraintsQuery(): boolean; + + /** + * Checks if the query type is DESCRIBE + * @returns {boolean} + */ + public isDescribeQuery(): boolean; + + /** + * Checks if the query type is SELECT + * @returns {boolean} + */ + public isSelectQuery(): boolean; + + /** + * Checks if the query type is BULKUPDATE + * @returns {boolean} + */ + public isBulkUpdateQuery(): boolean; + + /** + * Checks if the query type is BULKDELETE + * @returns {boolean} + */ + public isBulkDeleteQuery(): boolean; + + /** + * Checks if the query type is FOREIGNKEYS + * @returns {boolean} + */ + public isForeignKeysQuery(): boolean; + + /** + * Checks if the query type is UPDATE + * @returns {boolean} + */ + public isUpdateQuery(): boolean; + + /** + * Maps raw fields to attribute names (if applicable). + * + * @params {Model[]} results from a select query. + * @returns {Model} the first model instance within the select. + */ + public handleSelectQuery(results: Model[]): Model; + + /** + * Checks if the query starts with 'show' or 'describe' + * @returns {boolean} + */ + public isShowOrDescribeQuery(): boolean; + + /** + * Checks if the query starts with 'call' + * @returns {boolean} + */ + public isCallQuery(): boolean; + + /** + * @param {string} sql + * @param {Function} debugContext + * @param {Array|object} parameters + * @protected + * @returns {Function} A function to call after the query was completed. + */ + protected _logQuery(sql: string, debugContext: ((msg: string) => any), parameters: unknown[]): () => void; + + /** + * The function takes the result of the query execution and groups + * the associated data by the callee. + * + * Example: + * groupJoinData([ + * { + * some: 'data', + * id: 1, + * association: { foo: 'bar', id: 1 } + * }, { + * some: 'data', + * id: 1, + * association: { foo: 'bar', id: 2 } + * }, { + * some: 'data', + * id: 1, + * association: { foo: 'bar', id: 3 } + * } + * ]) + * + * Result: + * Something like this: + * + * [ + * { + * some: 'data', + * id: 1, + * association: [ + * { foo: 'bar', id: 1 }, + * { foo: 'bar', id: 2 }, + * { foo: 'bar', id: 3 } + * ] + * } + * ] + * + * @param {Array} rows + * @param {object} includeOptions + * @param {object} options + * @private + */ + static _groupJoinData(rows: unknown[], includeOptions: IncludeOptions, options: AbstractQueryGroupJoinDataOptions): unknown[]; +} diff --git a/types/lib/sequelize.d.ts b/types/lib/sequelize.d.ts index 65880172c64e..a17d0fdb9021 100644 --- a/types/lib/sequelize.d.ts +++ b/types/lib/sequelize.d.ts @@ -1,6 +1,5 @@ import * as DataTypes from './data-types'; -import * as Deferrable from './deferrable'; -import { HookReturn, SequelizeHooks, Hooks } from './hooks'; +import { HookReturn, Hooks, SequelizeHooks } from './hooks'; import { ValidationOptions } from './instance-validator'; import { AndOperator, @@ -20,23 +19,30 @@ import { WhereAttributeHash, WhereOperators, ModelCtor, + Hookable, + ModelType, } from './model'; import { ModelManager } from './model-manager'; -import * as Op from './operators'; -import { Promise } from './promise'; -import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType } from './query-interface'; +import { QueryInterface, QueryOptions, QueryOptionsWithModel, QueryOptionsWithType, ColumnsDescription } from './query-interface'; import QueryTypes = require('./query-types'); import { Transaction, TransactionOptions } from './transaction'; -import { Cast, Col, Fn, Json, Literal, Where } from './utils'; -// tslint:disable-next-line:no-duplicate-imports -import * as Utils from './utils'; -import { validator } from './utils/validator-extras'; +import { Cast, Col, DeepWriteable, Fn, Json, Literal, Where } from './utils'; import { ConnectionManager } from './connection-manager'; +/** + * Additional options for table altering during sync + */ +export interface SyncAlterOptions { + /** + * Prevents any drop statements while altering a table when set to `false` + */ + drop?: boolean; +} + /** * Sync Options */ -export interface SyncOptions extends Logging { +export interface SyncOptions extends Logging, Hookable { /** * If force is true, each DAO will do DROP TABLE IF EXISTS ..., before it tries to create its own table */ @@ -44,9 +50,9 @@ export interface SyncOptions extends Logging { /** * If alter is true, each DAO will do ALTER TABLE ... CHANGE ... - * Alters tables to fit models. Not recommended for production use. Deletes data in columns that were removed or had their type changed in the model. + * Alters tables to fit models. Provide an object for additional configuration. Not recommended for production use. If not further configured deletes data in columns that were removed or had their type changed in the model. */ - alter?: boolean; + alter?: boolean | SyncAlterOptions; /** * Match a regex against the database name before syncing, a safety check for cases where force: true is @@ -64,13 +70,9 @@ export interface SyncOptions extends Logging { */ searchPath?: string; - /** - * If hooks is true then beforeSync, afterSync, beforeBulkSync, afterBulkSync hooks will be called - */ - hooks?: boolean; } -export interface DefaultSetOptions {} +export interface DefaultSetOptions { } /** * Connection Pool options @@ -101,6 +103,11 @@ export interface PoolOptions { */ evict?: number; + /** + * The number of times to use a connection before closing and replacing it. Default is Infinity + */ + maxUses?: number; + /** * A function that validates a connection. Called with client. The default function checks that client is an * object, and that its state is not disconnected @@ -125,6 +132,13 @@ export interface ReplicationOptions { write: ConnectionOptions; } +/** + * Used to map operators to their Symbol representations + */ +export interface OperatorsAliases { + [K: string]: symbol; +} + /** * Final config options generated by sequelize. */ @@ -153,7 +167,7 @@ export interface Config { }; } -export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql' | 'mariadb'; +export type Dialect = 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql'; export interface RetryOptions { match?: (RegExp | string | Function)[]; @@ -314,6 +328,13 @@ export interface Options extends Logging { */ isolationLevel?: string; + /** + * Set the default transaction type. See Sequelize.Transaction.TYPES for possible options. Sqlite only. + * + * @default 'DEFERRED' + */ + transactionType?: Transaction.TYPES; + /** * Run built in type validators on insert and update, e.g. validate that arguments passed to integer * fields are integer-like. @@ -322,6 +343,16 @@ export interface Options extends Logging { */ typeValidation?: boolean; + /** + * Sets available operator aliases. + * See (https://sequelize.org/master/manual/querying.html#operators) for more information. + * WARNING: Setting this to boolean value was deprecated and is no-op. + * + * @default all aliases + */ + operatorsAliases?: OperatorsAliases; + + /** * The PostgreSQL `standard_conforming_strings` session parameter. Set to `false` to not set the option. * WARNING: Setting this to false may expose vulnerabilities and is not recommended! @@ -330,10 +361,18 @@ export interface Options extends Logging { */ standardConformingStrings?: boolean; + /** + * The PostgreSQL `client_min_messages` session parameter. + * Set to `false` to not override the database's default. + * + * @default 'warning' + */ + clientMinMessages?: string | boolean; + /** * Sets global permanent hooks. */ - hooks?: Partial; + hooks?: Partial>; /** * Set to `true` to automatically minify aliases generated by sequelize. @@ -344,7 +383,7 @@ export interface Options extends Logging { minifyAliases?: boolean; /** - * Set to `true` to show bind patameters in log. + * Set to `true` to show bind parameters in log. * * @default false */ @@ -353,7 +392,7 @@ export interface Options extends Logging { retry?: RetryOptions; } -export interface QueryOptionsTransactionRequired {} +export interface QueryOptionsTransactionRequired { } /** * This is the main class, the entry point to sequelize. To use it, you just need to @@ -367,7 +406,7 @@ export interface QueryOptionsTransactionRequired {} * should also be installed in your project. You don't need to import it however, as * sequelize will take care of that. */ -export class Sequelize { +export class Sequelize extends Hooks { // -------------------- Utilities ------------------------------------------------------------------------ @@ -455,6 +494,318 @@ export class Sequelize { */ public static where: typeof where; + /** + * A hook that is run before validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; + public static beforeValidate(fn: (instance: Model, options: ValidationOptions) => void): void; + + /** + * A hook that is run after validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; + public static afterValidate(fn: (instance: Model, options: ValidationOptions) => void): void; + + /** + * A hook that is run before creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public static beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + + /** + * A hook that is run after creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public static afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + + /** + * A hook that is run before destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + public static beforeDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + + /** + * A hook that is run after destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + public static afterDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + + /** + * A hook that is run before updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public static beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + + /** + * A hook that is run after updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public static afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + + /** + * A hook that is run before creating or updating a single instance, It proxies `beforeCreate` and `beforeUpdate` + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static beforeSave( + name: string, + fn: (instance: Model, options: UpdateOptions | CreateOptions) => void + ): void; + public static beforeSave(fn: (instance: Model, options: UpdateOptions | CreateOptions) => void): void; + + /** + * A hook that is run after creating or updating a single instance, It proxies `afterCreate` and `afterUpdate` + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public static afterSave( + name: string, + fn: (instance: Model, options: UpdateOptions | CreateOptions) => void + ): void; + public static afterSave( + fn: (instance: Model, options: UpdateOptions | CreateOptions) => void + ): void; + + /** + * A hook that is run before creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public static beforeBulkCreate( + name: string, + fn: (instances: Model[], options: BulkCreateOptions) => void + ): void; + public static beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + + /** + * A hook that is run after creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public static afterBulkCreate( + name: string, fn: (instances: Model[], options: BulkCreateOptions) => void + ): void; + public static afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + + /** + * A hook that is run before destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; + public static beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; + + /** + * A hook that is run after destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; + public static afterBulkDestroy(fn: (options: DestroyOptions) => void): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public static beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public static afterBulkUpdate(fn: (options: UpdateOptions) => void): void; + + /** + * A hook that is run before a find (select) query + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFind(name: string, fn: (options: FindOptions) => void): void; + public static beforeFind(fn: (options: FindOptions) => void): void; + + /** + * A hook that is run before a connection is established + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeConnect(name: string, fn: (options: DeepWriteable) => void): void; + public static beforeConnect(fn: (options: DeepWriteable) => void): void; + + /** + * A hook that is run after a connection is established + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterConnect(name: string, fn: (connection: unknown, options: Config) => void): void; + public static afterConnect(fn: (connection: unknown, options: Config) => void): void; + + /** + * A hook that is run before a connection is released + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeDisconnect(name: string, fn: (connection: unknown) => void): void; + public static beforeDisconnect(fn: (connection: unknown) => void): void; + + /** + * A hook that is run after a connection is released + * + * @param name + * @param fn A callback function that is called with options + */ + public static afterDisconnect(name: string, fn: (connection: unknown) => void): void; + public static afterDisconnect(fn: (connection: unknown) => void): void; + + /** + * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; + public static beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; + + /** + * A hook that is run before a find (select) query, after all option parsing is complete + * + * @param name + * @param fn A callback function that is called with options + */ + public static beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; + public static beforeFindAfterOptions(fn: (options: FindOptions) => void): void; + + /** + * A hook that is run after a find (select) query + * + * @param name + * @param fn A callback function that is called with instance(s), options + */ + public static afterFind( + name: string, + fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void + ): void; + public static afterFind( + fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void + ): void; + + /** + * A hook that is run before a define call + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public static beforeDefine( + name: string, + fn: (attributes: ModelAttributes, options: ModelOptions) => void + ): void; + public static beforeDefine( + fn: (attributes: ModelAttributes, options: ModelOptions) => void + ): void; + + /** + * A hook that is run after a define call + * + * @param name + * @param fn A callback function that is called with factory + */ + public static afterDefine(name: string, fn: (model: ModelType) => void): void; + public static afterDefine(fn: (model: ModelType) => void): void; + + /** + * A hook that is run before Sequelize() call + * + * @param name + * @param fn A callback function that is called with config, options + */ + public static beforeInit(name: string, fn: (config: Config, options: Options) => void): void; + public static beforeInit(fn: (config: Config, options: Options) => void): void; + + /** + * A hook that is run after Sequelize() call + * + * @param name + * @param fn A callback function that is called with sequelize + */ + public static afterInit(name: string, fn: (sequelize: Sequelize) => void): void; + public static afterInit(fn: (sequelize: Sequelize) => void): void; + + /** + * A hook that is run before sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public static beforeBulkSync(dname: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public static afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run before Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public static beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static beforeSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public static afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public static afterSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * Use CLS with Sequelize. + * CLS namespace provided is stored as `Sequelize._cls` + * and Promise is patched to use the namespace, using `cls-hooked` module. + * + * @param namespace + */ + public static useCLS(namespace: object): typeof Sequelize; + /** * A reference to Sequelize constructor from sequelize. Useful for accessing DataTypes, Errors etc. */ @@ -476,9 +827,6 @@ export class Sequelize { [key: string]: ModelCtor; }; - public readonly hooks: Hooks; - public static readonly hooks: Hooks; - /** * Instantiate sequelize with name of database, username and password * @@ -519,11 +867,246 @@ export class Sequelize { */ constructor(uri: string, options?: Options); + /** + * A hook that is run before validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public beforeValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; + public beforeValidate(fn: (instance: Model, options: ValidationOptions) => void): void; + + /** + * A hook that is run after validation + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public afterValidate(name: string, fn: (instance: Model, options: ValidationOptions) => void): void; + public afterValidate(fn: (instance: Model, options: ValidationOptions) => void): void; + + /** + * A hook that is run before creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public beforeCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public beforeCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + + /** + * A hook that is run after creating a single instance + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public afterCreate(name: string, fn: (attributes: Model, options: CreateOptions) => void): void; + public afterCreate(fn: (attributes: Model, options: CreateOptions) => void): void; + + /** + * A hook that is run before destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public beforeDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + public beforeDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + + /** + * A hook that is run after destroying a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public afterDestroy(name: string, fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + public afterDestroy(fn: (instance: Model, options: InstanceDestroyOptions) => void): void; + + /** + * A hook that is run before updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public beforeUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public beforeUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + + /** + * A hook that is run after updating a single instance + * + * @param name + * @param fn A callback function that is called with instance, options + */ + public afterUpdate(name: string, fn: (instance: Model, options: UpdateOptions) => void): void; + public afterUpdate(fn: (instance: Model, options: UpdateOptions) => void): void; + + /** + * A hook that is run before creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public beforeBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public beforeBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + + /** + * A hook that is run after creating instances in bulk + * + * @param name + * @param fn A callback function that is called with instances, options + */ + public afterBulkCreate(name: string, fn: (instances: Model[], options: BulkCreateOptions) => void): void; + public afterBulkCreate(fn: (instances: Model[], options: BulkCreateOptions) => void): void; + + /** + * A hook that is run before destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public beforeBulkDestroy(name: string, fn: (options: BulkCreateOptions) => void): void; + public beforeBulkDestroy(fn: (options: BulkCreateOptions) => void): void; + + /** + * A hook that is run after destroying instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public afterBulkDestroy(name: string, fn: (options: DestroyOptions) => void): void; + public afterBulkDestroy(fn: (options: DestroyOptions) => void): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public beforeBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public beforeBulkUpdate(fn: (options: UpdateOptions) => void): void; + + /** + * A hook that is run after updating instances in bulk + * + * @param name + * @param fn A callback function that is called with options + */ + public afterBulkUpdate(name: string, fn: (options: UpdateOptions) => void): void; + public afterBulkUpdate(fn: (options: UpdateOptions) => void): void; + + /** + * A hook that is run before a find (select) query + * + * @param name + * @param fn A callback function that is called with options + */ + public beforeFind(name: string, fn: (options: FindOptions) => void): void; + public beforeFind(fn: (options: FindOptions) => void): void; + + /** + * A hook that is run before a find (select) query, after any { include: {all: ...} } options are expanded + * + * @param name + * @param fn A callback function that is called with options + */ + public beforeFindAfterExpandIncludeAll(name: string, fn: (options: FindOptions) => void): void; + public beforeFindAfterExpandIncludeAll(fn: (options: FindOptions) => void): void; + + /** + * A hook that is run before a find (select) query, after all option parsing is complete + * + * @param name + * @param fn A callback function that is called with options + */ + public beforeFindAfterOptions(name: string, fn: (options: FindOptions) => void): void; + public beforeFindAfterOptions(fn: (options: FindOptions) => void): void; + + /** + * A hook that is run after a find (select) query + * + * @param name + * @param fn A callback function that is called with instance(s), options + */ + public afterFind( + name: string, + fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void + ): void; + public afterFind(fn: (instancesOrInstance: Model[] | Model | null, options: FindOptions) => void): void; + + /** + * A hook that is run before a define call + * + * @param name + * @param fn A callback function that is called with attributes, options + */ + public beforeDefine(name: string, fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; + public beforeDefine(fn: (attributes: ModelAttributes, options: ModelOptions) => void): void; + + /** + * A hook that is run after a define call + * + * @param name + * @param fn A callback function that is called with factory + */ + public afterDefine(name: string, fn: (model: ModelType) => void): void; + public afterDefine(fn: (model: ModelType) => void): void; + + /** + * A hook that is run before Sequelize() call + * + * @param name + * @param fn A callback function that is called with config, options + */ + public beforeInit(name: string, fn: (config: Config, options: Options) => void): void; + public beforeInit(fn: (config: Config, options: Options) => void): void; + + /** + * A hook that is run after Sequelize() call + * + * @param name + * @param fn A callback function that is called with sequelize + */ + public afterInit(name: string, fn: (sequelize: Sequelize) => void): void; + public afterInit(fn: (sequelize: Sequelize) => void): void; + + /** + * A hook that is run before sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public beforeBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public beforeBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after sequelize.sync call + * @param fn A callback function that is called with options passed to sequelize.sync + */ + public afterBulkSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public afterBulkSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run before Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public beforeSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public beforeSync(fn: (options: SyncOptions) => HookReturn): void; + + /** + * A hook that is run after Model.sync call + * @param fn A callback function that is called with options passed to Model.sync + */ + public afterSync(name: string, fn: (options: SyncOptions) => HookReturn): void; + public afterSync(fn: (options: SyncOptions) => HookReturn): void; + /** * Returns the specified dialect. */ public getDialect(): string; + /** + * Returns the database name. + */ + + public getDatabaseName(): string; + /** * Returns an instance of QueryInterface. */ @@ -582,7 +1165,11 @@ export class Sequelize { * @param options These options are merged with the default define options provided to the Sequelize * constructor */ - public define(modelName: string, attributes: ModelAttributes, options?: ModelOptions): typeof Model; + public define( + modelName: string, + attributes: ModelAttributes, + options?: ModelOptions + ): ModelCtor; /** * Fetch a Model which is already defined @@ -598,44 +1185,19 @@ export class Sequelize { */ public isDefined(modelName: string): boolean; - /** - * Imports a model defined in another file - * - * Imported models are cached, so multiple calls to import with the same path will not load the file - * multiple times - * - * See https://github.com/sequelize/sequelize/blob/master/examples/using-multiple-model-files/Task.js for a - * short example of how to define your models in separate files so that they can be imported by - * sequelize.import - * - * @param path The path to the file that holds the model you want to import. If the part is relative, it - * will be resolved relatively to the calling file - * - * @param defineFunction An optional function that provides model definitions. Useful if you do not - * want to use the module root as the define function - */ - public import( - path: string, - defineFunction?: (sequelize: Sequelize, dataTypes: typeof DataTypes) => T - ): T; - /** * Execute a query on the DB, optionally bypassing all the Sequelize goodness. * * By default, the function will return two arguments: an array of results, and a metadata object, - * containing number of affected rows etc. Use `.then(([...]))` to access the results. + * containing number of affected rows etc. Use `const [results, meta] = await ...` to access the results. * * If you are running a type of query where you don't need the metadata, for example a `SELECT` query, you * can pass in a query type to make sequelize format the results: * * ```js - * sequelize.query('SELECT...').then(([results, metadata]) { - * // Raw query - use spread - * }); + * const [results, metadata] = await sequelize.query('SELECT...'); // Raw query - use array destructuring * - * sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }).then(results => { - * // SELECT query - use then - * }) + * const results = await sequelize.query('SELECT...', { type: sequelize.QueryTypes.SELECT }); // SELECT query - no destructuring * ``` * * @param sql @@ -648,21 +1210,14 @@ export class Sequelize { public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; - public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise<{ - [key: string]: { - type: string; - allowNull: boolean; - defaultValue: string; - primaryKey: boolean; - autoIncrement: boolean; - comment: string | null; - } - }>; + public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; public query( sql: string | { query: string; values: unknown[] }, - options: QueryOptionsWithModel + options: QueryOptionsWithModel ): Promise; + public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType & { plain: true }): Promise; public query(sql: string | { query: string; values: unknown[] }, options: QueryOptionsWithType): Promise; + public query(sql: string | { query: string; values: unknown[] }, options: (QueryOptions | QueryOptionsWithType) & { plain: true }): Promise<{ [key: string]: unknown }>; public query(sql: string | { query: string; values: unknown[] }, options?: QueryOptions | QueryOptionsWithType): Promise<[unknown[], unknown]>; /** @@ -747,7 +1302,7 @@ export class Sequelize { * * @param [options] The options passed to Model.destroy in addition to truncate */ - public truncate(options?: DestroyOptions): Promise; + public truncate(options?: DestroyOptions): Promise; /** * Drop all tables defined through this sequelize instance. This is done by calling Model.drop on each model @@ -769,12 +1324,14 @@ export class Sequelize { * in order for the query to happen under that transaction * * ```js - * sequelize.transaction().then(t => { - * return User.findOne(..., { transaction: t}).then(user => { - * return user.update(..., { transaction: t}); - * }) - * .then(t.commit.bind(t)) - * .catch(t.rollback.bind(t)); + * try { + * const transaction = await sequelize.transaction(); + * const user = await User.findOne(..., { transaction }); + * await user.update(..., { transaction }); + * await transaction.commit(); + * } catch(err) { + * await transaction.rollback(); + * } * }) * ``` * @@ -782,17 +1339,29 @@ export class Sequelize { * supported: * * ```js - * sequelize.transaction(t => { // Note that we use a callback rather than a promise.then() - * return User.findOne(..., { transaction: t}).then(user => { - * return user.update(..., { transaction: t}); + * try { + * await sequelize.transaction(transaction => { // Note that we pass a callback rather than awaiting the call with no arguments + * const user = await User.findOne(..., {transaction}); + * await user.update(..., {transaction}); * }); - * }).then(() => { - * // Commited - * }).catch(err => { + * // Committed + * } catch(err) { * // Rolled back * console.error(err); - * }); + * } + * ``` + * + * If you have [CLS](https://github.com/Jeff-Lewis/cls-hooked) enabled, the transaction + * will automatically be passed to any query that runs witin the callback. To enable CLS, add it do your + * project, create a namespace and set it on the sequelize constructor: + * + * ```js + * const cls = require('cls-hooked'); + * const namespace = cls.createNamespace('....'); + * const Sequelize = require('sequelize'); + * Sequelize.useCLS(namespace); * ``` + * Note, that CLS is enabled for all sequelize instances, and all instances will share the same namespace * * @param options Transaction Options * @param autoCallback Callback for the transaction @@ -863,14 +1432,14 @@ export function literal(val: string): Literal; * * @param args Each argument will be joined by AND */ -export function and(...args: (WhereOperators | WhereAttributeHash | Where)[]): AndOperator; +export function and(...args: (WhereOperators | WhereAttributeHash | Where)[]): AndOperator; /** * An OR query * * @param args Each argument will be joined by OR */ -export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): OrOperator; +export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): OrOperator; /** * Creates an object representing nested where conditions for postgres's json data-type. @@ -883,7 +1452,7 @@ export function or(...args: (WhereOperators | WhereAttributeHash | Where)[]): Or export function json(conditionsOrPath: string | object, value?: string | number | boolean): Json; export type AttributeType = Fn | Col | Literal | ModelAttributeColumnOptions | string; -export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string; +export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOperators | string | symbol | null; /** * A way of specifying attr = condition. @@ -903,7 +1472,7 @@ export type LogicType = Fn | Col | Literal | OrOperator | AndOperator | WhereOpe * @param logic The condition. Can be both a simply type, or a further condition (`.or`, `.and`, `.literal` * etc.) */ -export function where(attr: AttributeType, comparator: string, logic: LogicType): Where; +export function where(attr: AttributeType, comparator: string | symbol, logic: LogicType): Where; export function where(attr: AttributeType, logic: LogicType): Where; export default Sequelize; diff --git a/types/lib/transaction.d.ts b/types/lib/transaction.d.ts index 2c74aa9ac298..0ebc2cad0933 100644 --- a/types/lib/transaction.d.ts +++ b/types/lib/transaction.d.ts @@ -1,12 +1,6 @@ import { Deferrable } from './deferrable'; import { Logging } from './model'; -import { Promise } from './promise'; import { Sequelize } from './sequelize'; -import { Hooks } from './hooks'; - -export interface TransactionHooks { - afterCommit(): void; -} /** * The transaction object is used to identify a running transaction. It is created by calling @@ -15,9 +9,6 @@ export interface TransactionHooks { * To run a query under a transaction, you should pass the transaction in the options object. */ export class Transaction { - - public readonly hooks: Hooks; - constructor(sequelize: Sequelize, options: TransactionOptions); /** @@ -29,6 +20,23 @@ export class Transaction { * Rollback (abort) the transaction */ public rollback(): Promise; + + /** + * Adds hook that is run after a transaction is committed + */ + public afterCommit(fn: (transaction: this) => void | Promise): void; + + /** + * Returns possible options for row locking + */ + static get LOCK(): LOCK; + + /** + * Same as its static version, but can also be called on instances of + * transactions to get possible options for row locking directly from the + * instance. + */ + get LOCK(): LOCK; } // tslint:disable-next-line no-namespace @@ -51,15 +59,14 @@ export namespace Transaction { * Pass in the desired level as the first argument: * * ```js - * return sequelize.transaction({isolationLevel: Sequelize.Transaction.SERIALIZABLE}, transaction => { - * - * // your transactions - * - * }).then(result => { + * try { + * await sequelize.transaction({isolationLevel: Sequelize.Transaction.SERIALIZABLE}, transaction => { + * // your transactions + * }); * // transaction has been committed. Do something after the commit if required. - * }).catch(err => { + * } catch(err) { * // do something with the err. - * }); + * } * ``` */ enum ISOLATION_LEVELS { @@ -74,54 +81,61 @@ export namespace Transaction { IMMEDIATE = 'IMMEDIATE', EXCLUSIVE = 'EXCLUSIVE', } +} +/** + * Possible options for row locking. Used in conjunction with `find` calls: + * + * ```js + * t1 // is a transaction + * t1.LOCK.UPDATE, + * t1.LOCK.SHARE, + * t1.LOCK.KEY_SHARE, // Postgres 9.3+ only + * t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only + * ``` + * + * Usage: + * ```js + * t1 // is a transaction + * Model.findAll({ + * where: ..., + * transaction: t1, + * lock: t1.LOCK... + * }); + * ``` + * + * Postgres also supports specific locks while eager loading by using OF: + * ```js + * UserModel.findAll({ + * where: ..., + * include: [TaskModel, ...], + * transaction: t1, + * lock: { + * level: t1.LOCK..., + * of: UserModel + * } + * }); + * ``` + * UserModel will be locked but TaskModel won't! + */ +export enum LOCK { + UPDATE = 'UPDATE', + SHARE = 'SHARE', /** - * Possible options for row locking. Used in conjunction with `find` calls: - * - * ```js - * t1 // is a transaction - * t1.LOCK.UPDATE, - * t1.LOCK.SHARE, - * t1.LOCK.KEY_SHARE, // Postgres 9.3+ only - * t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only - * ``` - * - * Usage: - * ```js - * t1 // is a transaction - * Model.findAll({ - * where: ..., - * transaction: t1, - * lock: t1.LOCK... - * }); - * ``` - * - * Postgres also supports specific locks while eager loading by using OF: - * ```js - * UserModel.findAll({ - * where: ..., - * include: [TaskModel, ...], - * transaction: t1, - * lock: { - * level: t1.LOCK..., - * of: UserModel - * } - * }); - * ``` - * UserModel will be locked but TaskModel won't! + * Postgres 9.3+ only */ - enum LOCK { - UPDATE = 'UPDATE', - SHARE = 'SHARE', - /** - * Postgres 9.3+ only - */ - KEY_SHARE = 'KEY SHARE', - /** - * Postgres 9.3+ only - */ - NO_KEY_UPDATE = 'NO KEY UPDATE', - } + KEY_SHARE = 'KEY SHARE', + /** + * Postgres 9.3+ only + */ + NO_KEY_UPDATE = 'NO KEY UPDATE', +} + +interface LOCK { + UPDATE: LOCK.UPDATE; + SHARE: LOCK.SHARE; + KEY_SHARE: LOCK.KEY_SHARE; + NO_KEY_UPDATE: LOCK.NO_KEY_UPDATE; } /** @@ -135,7 +149,7 @@ export interface TransactionOptions extends Logging { /** * Parent transaction. */ - transaction?: Transaction; + transaction?: Transaction | null; } export default Transaction; diff --git a/types/lib/utils.d.ts b/types/lib/utils.d.ts index 7c8169befd8c..02552a46c1f8 100644 --- a/types/lib/utils.d.ts +++ b/types/lib/utils.d.ts @@ -1,8 +1,10 @@ import { DataType } from './data-types'; -import { Model, WhereOptions } from './model'; +import { Model, ModelCtor, ModelType, WhereOptions } from './model'; export type Primitive = 'string' | 'number' | 'boolean'; +export type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; + export interface Inflector { singularize(str: string): string; pluralize(str: string): string; @@ -24,20 +26,25 @@ export function formatNamedParameters(sql: string, parameters: { }, dialect: string): string; export function cloneDeep(obj: T, fn?: (el: unknown) => unknown): T; -export interface OptionsForMapping { +export interface OptionsForMapping { attributes?: string[]; - where?: WhereOptions; + where?: WhereOptions; } /** Expand and normalize finder options */ -export function mapFinderOptions(options: T, model: typeof Model): T; +export function mapFinderOptions>( + options: T, + model: ModelCtor +): T; /* Used to map field names in attributes and where conditions */ -export function mapOptionFieldNames(options: T, model: typeof Model): T; +export function mapOptionFieldNames>( + options: T, model: ModelCtor +): T; -export function mapWhereFieldNames(attributes: object, model: typeof Model): object; +export function mapWhereFieldNames(attributes: object, model: ModelType): object; /** Used to map field names in values */ -export function mapValueFieldNames(dataValues: object, fields: string[], model: typeof Model): object; +export function mapValueFieldNames(dataValues: object, fields: string[], model: ModelType): object; export function isColString(value: string): boolean; export function canTreatArrayAsAnd(arr: unknown[]): boolean; @@ -115,5 +122,3 @@ export class Where extends SequelizeMethod { constructor(attr: object, comparator: string, logic: string | object); constructor(attr: object, logic: string | object); } - -export { Promise } from './promise'; diff --git a/types/test/attributes.ts b/types/test/attributes.ts new file mode 100644 index 000000000000..2c754923debd --- /dev/null +++ b/types/test/attributes.ts @@ -0,0 +1,44 @@ +import { Model } from "sequelize/lib/model"; + +interface UserCreationAttributes { + name: string; +} + +interface UserAttributes extends UserCreationAttributes { + id: number; +} + +class User + extends Model + implements UserAttributes { + public id!: number; + public name!: string; + + public readonly projects?: Project[]; + public readonly address?: Address; +} + +interface ProjectCreationAttributes { + ownerId: number; + name: string; +} + +interface ProjectAttributes extends ProjectCreationAttributes { + id: number; +} + +class Project + extends Model + implements ProjectAttributes { + public id!: number; + public ownerId!: number; + public name!: string; +} + +class Address extends Model { + public userId!: number; + public address!: string; +} + +// both models should be accepted in include +User.findAll({ include: [Project, Address] }); diff --git a/types/test/connection.ts b/types/test/connection.ts index 15e665e9d945..5c2006fb053a 100644 --- a/types/test/connection.ts +++ b/types/test/connection.ts @@ -1,33 +1,26 @@ +import { expectTypeOf } from "expect-type"; import { QueryTypes, Sequelize, SyncOptions } from 'sequelize'; import { User } from 'models/User'; export const sequelize = new Sequelize('uri'); -sequelize.hooks.add('afterBulkSync', (options: SyncOptions) => { +sequelize.afterBulkSync((options: SyncOptions) => { console.log('synced'); }); -sequelize - .query('SELECT * FROM `test`', { - type: QueryTypes.SELECT, - }) - .then(rows => { - rows.forEach(row => { - console.log(row); - }); - }); +async function test() { + expectTypeOf( + await sequelize.query('SELECT * FROM `test`', { type: QueryTypes.SELECT }) + ).toEqualTypeOf(); -sequelize -.query('INSERT into test set test=1', { - type: QueryTypes.INSERT, -}) -.then(([aiId, affected]) => { - console.log(aiId, affected); -}); + expectTypeOf( + await sequelize.query('INSERT into test set test=1', { type: QueryTypes.INSERT }) + ).toEqualTypeOf<[number, number]>(); +} sequelize.transaction(async transaction => { - const rows = await sequelize - .query('SELECT * FROM `user`', { + expectTypeOf( + await sequelize.query('SELECT * FROM `user`', { retry: { max: 123, }, @@ -35,12 +28,15 @@ sequelize.transaction(async transaction => { transaction, logging: true, }) + ).toEqualTypeOf(); }); -sequelize.query('SELECT * FROM `user` WHERE status = $1', +sequelize.query( + 'SELECT * FROM `user` WHERE status = $1', { bind: ['active'], type: QueryTypes.SELECT } ); -sequelize.query('SELECT * FROM `user` WHERE status = $status', +sequelize.query( + 'SELECT * FROM `user` WHERE status = $status', { bind: { status: 'active' }, type: QueryTypes.SELECT } ); diff --git a/types/test/count.ts b/types/test/count.ts index 2859203d1683..57607f4e1f14 100644 --- a/types/test/count.ts +++ b/types/test/count.ts @@ -1,9 +1,16 @@ -import { Model, Promise, Op } from 'sequelize'; +import { expectTypeOf } from "expect-type"; +import { Model, Op } from 'sequelize'; class MyModel extends Model {} -const grouped: Promise<{ [key: string]: number }> = MyModel.count({ group: 'tag' }); -const counted: Promise = MyModel.count(); -const countedDistinct: Promise = MyModel.count({ col: 'tag', distinct: true }); - -const countedDistinctOnReader: Promise = MyModel.count({ where: { updatedAt: { [Op.gte]: new Date() } }, useMaster: false }) +expectTypeOf(MyModel.count()).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ group: 'tag' })).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ col: 'tag', distinct: true })).toEqualTypeOf>(); +expectTypeOf(MyModel.count({ + where: { + updatedAt: { + [Op.gte]: new Date() + } + }, + useMaster: false +})).toEqualTypeOf>(); diff --git a/types/test/create.ts b/types/test/create.ts new file mode 100644 index 000000000000..a370249b47f6 --- /dev/null +++ b/types/test/create.ts @@ -0,0 +1,74 @@ +import { expectTypeOf } from 'expect-type' +import { User } from './models/User'; + +async () => { + const user = await User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: false, + returning: true, + }); + expectTypeOf(user).toEqualTypeOf() + + const voidUsers = await Promise.all([ + User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: true, + returning: false, + }), + User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: true, + returning: true, + }), + User.create({ + id: 123, + firstName: '', + }, { + ignoreDuplicates: false, + returning: false, + }), + User.create({ + id: 123, + firstName: '', + }, { returning: false }), + User.create({ + id: 123, + firstName: '', + }, { ignoreDuplicates: true }), + ]); + expectTypeOf(voidUsers).toEqualTypeOf<[void, void, void, void, void]>() + + const emptyUsers = await Promise.all([ + User.create(), + User.create(undefined), + User.create(undefined, undefined), + ]); + expectTypeOf(emptyUsers).toEqualTypeOf<[User, User, User]>() + + const partialUser = await User.create({ + id: 123, + firstName: '', + lastName: '', + }, { + fields: ['firstName'], + returning: ['id'], + }); + expectTypeOf(partialUser).toEqualTypeOf() + + // @ts-expect-error missing attribute + await User.create({ + id: 123, + }); + await User.create({ + id: 123, + firstName: '', + // @ts-expect-error unknown attribute + unknown: '', + }); +}; diff --git a/types/test/data-types.ts b/types/test/data-types.ts index fe991f8e7a3c..01d463f2135b 100644 --- a/types/test/data-types.ts +++ b/types/test/data-types.ts @@ -1,32 +1,34 @@ -import { INTEGER, IntegerDataType, TINYINT } from 'sequelize'; -import { SmallIntegerDataType, SMALLINT, MEDIUMINT, MediumIntegerDataType, BigIntDataType, BIGINT } from '../lib/data-types'; +import { expectTypeOf } from 'expect-type'; +import { DataTypes } from 'sequelize'; -let tinyint: IntegerDataType; -tinyint = TINYINT(); -tinyint = new TINYINT(); -tinyint = TINYINT.UNSIGNED.ZEROFILL(); -tinyint = new TINYINT.UNSIGNED.ZEROFILL(); +const { TINYINT, SMALLINT, MEDIUMINT, BIGINT, INTEGER } = DataTypes; -let smallint: SmallIntegerDataType; -smallint = SMALLINT(); -smallint = new SMALLINT(); -smallint = SMALLINT.UNSIGNED.ZEROFILL(); -smallint = new SMALLINT.UNSIGNED.ZEROFILL(); +// TINYINT +expectTypeOf(TINYINT()).toEqualTypeOf(); +expectTypeOf(new TINYINT()).toEqualTypeOf(); +expectTypeOf(TINYINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new TINYINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -let mediumint: MediumIntegerDataType; -mediumint = MEDIUMINT(); -mediumint = new MEDIUMINT(); -mediumint = MEDIUMINT.UNSIGNED.ZEROFILL(); -mediumint = new MEDIUMINT.UNSIGNED.ZEROFILL(); +// SMALLINT +expectTypeOf(SMALLINT()).toEqualTypeOf(); +expectTypeOf(new SMALLINT()).toEqualTypeOf(); +expectTypeOf(SMALLINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new SMALLINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -let int: IntegerDataType; -int = INTEGER(); -int = new INTEGER(); -int = INTEGER.UNSIGNED.ZEROFILL(); -int = new INTEGER.UNSIGNED.ZEROFILL(); +// MEDIUMINT +expectTypeOf(MEDIUMINT()).toEqualTypeOf(); +expectTypeOf(new MEDIUMINT()).toEqualTypeOf(); +expectTypeOf(MEDIUMINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new MEDIUMINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); -let bigint: BigIntDataType; -bigint = BIGINT(); -bigint = new BIGINT(); -bigint = BIGINT.UNSIGNED.ZEROFILL(); -bigint = new BIGINT.UNSIGNED.ZEROFILL(); +// BIGINT +expectTypeOf(BIGINT()).toEqualTypeOf(); +expectTypeOf(new BIGINT()).toEqualTypeOf(); +expectTypeOf(BIGINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new BIGINT.UNSIGNED.ZEROFILL()).toEqualTypeOf(); + +// INTEGER +expectTypeOf(INTEGER()).toEqualTypeOf(); +expectTypeOf(new INTEGER()).toEqualTypeOf(); +expectTypeOf(INTEGER.UNSIGNED.ZEROFILL()).toEqualTypeOf(); +expectTypeOf(new INTEGER.UNSIGNED.ZEROFILL()).toEqualTypeOf(); diff --git a/types/test/define.ts b/types/test/define.ts index c3f39da11e11..54d422c88747 100644 --- a/types/test/define.ts +++ b/types/test/define.ts @@ -1,32 +1,71 @@ -import { DataTypes, Model } from 'sequelize'; +import { expectTypeOf } from 'expect-type'; +import { BuildOptions, DataTypes, Model, Optional } from 'sequelize'; import { sequelize } from './connection'; // I really wouldn't recommend this, but if you want you can still use define() and interfaces -interface User extends Model { - id: number; - username: string; - firstName: string; - lastName: string; - createdAt: Date; - updatedAt: Date; +interface UserAttributes { + id: number; + username: string; + firstName: string; + lastName: string; } -type UserModel = { - new (): User - customStaticMethod(): unknown -} & typeof Model; +interface UserCreationAttributes extends Optional {} -const User = sequelize.define('User', { firstName: DataTypes.STRING }, { tableName: 'users' }) as UserModel; +interface UserModel extends Model, UserAttributes {} + +const User = sequelize.define( + 'User', + { + id: { type: DataTypes.NUMBER, primaryKey: true }, + username: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + }, + { tableName: 'users' }, +); async function test() { - User.customStaticMethod(); + expectTypeOf().toMatchTypeOf(User.build()); + + const user = await User.findOne(); + expectTypeOf(user).toEqualTypeOf(); + + if (!user) return; + user.firstName = 'John'; + await user.save(); +} + +// The below doesn't define Attribute types, but should still work +interface UntypedUserModel extends Model, UserAttributes {} + +type UntypedUserModelStatic = typeof Model & { + new (values?: keyof any, options?: BuildOptions): UntypedUserModel; + customStaticMethod(): unknown; +}; +const UntypedUser = sequelize.define( + 'User', + { + id: { type: DataTypes.NUMBER, primaryKey: true }, + username: DataTypes.STRING, + firstName: DataTypes.STRING, + lastName: DataTypes.STRING, + }, + { tableName: 'users' }, +) as UntypedUserModelStatic; + +UntypedUser.customStaticMethod = () => {}; - const user: User = new User(); +async function testUntyped() { + UntypedUser.customStaticMethod(); - const user2: User = (await User.findOne()) as User; + expectTypeOf().toMatchTypeOf(UntypedUser.build()); - user2.firstName = 'John'; + const user = await UntypedUser.findOne(); + expectTypeOf(user).toEqualTypeOf(); - await user2.save(); + if (!user) return; + user.firstName = 'John'; + await user.save(); } diff --git a/types/test/e2e/docs-example.ts b/types/test/e2e/docs-example.ts index a8427e8fccd9..0840e5a6a0f7 100644 --- a/types/test/e2e/docs-example.ts +++ b/types/test/e2e/docs-example.ts @@ -1,6 +1,6 @@ // This file is used as example. -import {BuildOptions, DataTypes, Model, Sequelize} from 'sequelize'; +import { BuildOptions, DataTypes, Model, Sequelize } from 'sequelize'; import { Association, HasManyAddAssociationMixin, @@ -120,8 +120,6 @@ Address.belongsTo(User, {targetKey: 'id'}); User.hasOne(Address,{sourceKey: 'id'}); async function stuff() { - // Please note that when using async/await you lose the `bluebird` promise context - // and you fall back to native const newUser = await User.create({ name: 'Johnny', preferredName: 'John', @@ -169,11 +167,9 @@ const MyDefineModel = sequelize.define('MyDefineModel', { } }); -function stuffTwo() { - MyDefineModel.findByPk(1, { +async function stuffTwo() { + const myModel = await MyDefineModel.findByPk(1, { rejectOnEmpty: true, - }) - .then(myModel => { - console.log(myModel.id); }); + console.log(myModel.id); } diff --git a/types/test/errors.ts b/types/test/errors.ts index 89f9d6375e7b..0a938a37f264 100644 --- a/types/test/errors.ts +++ b/types/test/errors.ts @@ -1,58 +1,9 @@ -// Error === BaseError -import { BaseError, EmptyResultError, Error, UniqueConstraintError } from 'sequelize'; -import { User } from './models/User'; +import { expectTypeOf } from "expect-type"; +import { BaseError, EmptyResultError, Error as AliasedBaseError, UniqueConstraintError } from 'sequelize'; import { OptimisticLockError } from '../lib/errors'; -async function test() { - try { - await User.create({ username: 'john_doe' }); - } catch (e) { - if (e instanceof UniqueConstraintError) { - throw new Error((e as UniqueConstraintError).sql); - } - } - - try { - await User.findOne({ - rejectOnEmpty: true, - where: { - username: 'something_that_doesnt_exist', - }, - }); - } catch (e) { - if (!(e instanceof EmptyResultError)) { - throw new Error('should return emptyresulterror'); - } - } - - - class CustomError extends Error {} - - try { - await User.findOne({ - rejectOnEmpty: new CustomError('User does not exist'), - where: { - username: 'something_that_doesnt_exist', - }, - }); - } catch (e) { - if (!(e instanceof CustomError)) { - throw new Error('should return CustomError'); - } - if (e.message !== 'User does not exist') { - throw new Error('should return CustomError with the proper message') - } - } - - try { - const user: User | null = await User.findByPk(1); - if (user != null) { - user.username = 'foo'; - user.save(); - } - } catch (e) { - if (!(e instanceof OptimisticLockError)) { - throw new Error('should return OptimisticLockError'); - } - } -} +expectTypeOf().toEqualTypeOf(); +expectTypeOf().toHaveProperty('sql').toBeString(); +expectTypeOf().toMatchTypeOf(); +expectTypeOf().toMatchTypeOf(); +expectTypeOf().toMatchTypeOf(); diff --git a/types/test/findById.ts b/types/test/findByPk.ts similarity index 100% rename from types/test/findById.ts rename to types/test/findByPk.ts diff --git a/types/test/findOne.ts b/types/test/findOne.ts new file mode 100644 index 000000000000..1a71a0647d9f --- /dev/null +++ b/types/test/findOne.ts @@ -0,0 +1,6 @@ +import { User } from "./models/User"; + +User.findOne({ where: { firstName: 'John' } }); + +// @ts-expect-error +User.findOne({ where: { blah: 'blah2' } }); diff --git a/types/test/hooks.ts b/types/test/hooks.ts index 9c99d9b93673..8bc11bde189a 100644 --- a/types/test/hooks.ts +++ b/types/test/hooks.ts @@ -1,42 +1,90 @@ -import {Model, SaveOptions, Sequelize, FindOptions} from "sequelize" +import { expectTypeOf } from "expect-type"; +import { FindOptions, Model, QueryOptions, SaveOptions, Sequelize, UpsertOptions } from "sequelize"; import { ModelHooks } from "../lib/hooks"; +import { AbstractQuery } from "../lib/query"; +import { Config } from '../lib/sequelize'; +import { DeepWriteable } from '../lib/utils'; +import { SemiDeepWritable } from "./type-helpers/deep-writable"; -/* - * covers types/lib/sequelize.d.ts - */ +{ + class TestModel extends Model {} -Sequelize.hooks.add('beforeSave', (t: TestModel, options: SaveOptions) => {}); -Sequelize.hooks.add('afterSave', (t: TestModel, options: SaveOptions) => {}); -Sequelize.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {}); -Sequelize.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => {}); + const hooks: Partial = { + beforeSave(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? + }, + afterSave(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toMatchTypeOf(); // TODO consider `.toEqualTypeOf` instead ? + }, + afterFind(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + }, + beforeUpsert(m, options) { + expectTypeOf(m).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + }, + afterUpsert(m, options) { + expectTypeOf(m).toEqualTypeOf<[ TestModel, boolean | null ]>(); + expectTypeOf(options).toEqualTypeOf(); + }, + beforeQuery(options, query) { + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(query).toEqualTypeOf(); + }, + afterQuery(options, query) { + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(query).toEqualTypeOf(); + }, + }; -Sequelize.hooks.add('beforeSave', m => { + const sequelize = new Sequelize('uri', { hooks }); + TestModel.init({}, { sequelize, hooks }); -}); -/* - * covers types/lib/hooks.d.ts - */ + TestModel.addHook('beforeSave', hooks.beforeSave!); + TestModel.addHook('afterSave', hooks.afterSave!); + TestModel.addHook('afterFind', hooks.afterFind!); + TestModel.addHook('beforeUpsert', hooks.beforeUpsert!); + TestModel.addHook('afterUpsert', hooks.afterUpsert!); -export const sequelize = new Sequelize('uri', { - hooks: { - beforeSave (m: Model, options: SaveOptions) {}, - afterSave (m: Model, options: SaveOptions) {}, - afterFind (m: Model[] | Model | null, options: FindOptions) {}, - } -}); + TestModel.beforeSave(hooks.beforeSave!); + TestModel.afterSave(hooks.afterSave!); + TestModel.afterFind(hooks.afterFind!); -class TestModel extends Model { + Sequelize.beforeSave(hooks.beforeSave!); + Sequelize.afterSave(hooks.afterSave!); + Sequelize.afterFind(hooks.afterFind!); + Sequelize.afterFind('namedAfterFind', hooks.afterFind!); } -const hooks: Partial = { - beforeSave(t: TestModel, options: SaveOptions) { }, - afterSave(t: TestModel, options: SaveOptions) { }, - afterFind(t: TestModel | TestModel[] | null, options: FindOptions) { }, -}; +// #12959 +{ + const hooks: ModelHooks = 0 as any; -TestModel.init({}, {sequelize, hooks }) - -TestModel.hooks.add('beforeSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.hooks.add('afterSave', (t: TestModel, options: SaveOptions) => { }); -TestModel.hooks.add('afterFind', (t: TestModel[] | TestModel | null, options: FindOptions) => { }); + hooks.beforeValidate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeCreate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeDestroy = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeRestore = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeUpdate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeSave = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkCreate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkDestroy = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkRestore = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkUpdate = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeFind = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeCount = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeFindAfterExpandIncludeAll = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeFindAfterOptions = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeBulkSync = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeQuery = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; + hooks.beforeUpsert = (...args) => { expectTypeOf(args).toEqualTypeOf>() }; +} +{ + Sequelize.beforeConnect('name', config => expectTypeOf(config).toEqualTypeOf>()); + Sequelize.beforeConnect(config => expectTypeOf(config).toEqualTypeOf>()); + Sequelize.addHook('beforeConnect', (...args) => { expectTypeOf(args).toEqualTypeOf<[DeepWriteable]>(); }) +} diff --git a/types/test/include.ts b/types/test/include.ts index efeed55273ee..7561e8d0b685 100644 --- a/types/test/include.ts +++ b/types/test/include.ts @@ -20,6 +20,8 @@ MyModel.findAll({ order: [['id', 'DESC'], [ 'AssociatedModel', MyModel, 'id', 'DESC' ], [ MyModel, 'id' ] ], separate: true, where: { state: Sequelize.col('project.state') }, + all: true, + nested: true, }, ], }); diff --git a/types/test/model.ts b/types/test/model.ts index b3ca9fef8e54..0eb206404244 100644 --- a/types/test/model.ts +++ b/types/test/model.ts @@ -1,5 +1,8 @@ -import { Association, HasOne, Model, Sequelize, DataTypes } from 'sequelize'; +import { expectTypeOf } from "expect-type"; +import { Association, BelongsToManyGetAssociationsMixin, DataTypes, HasOne, Model, Optional, Sequelize } from 'sequelize'; +import { ModelDefined } from '../lib/model'; +expectTypeOf().toMatchTypeOf(); class MyModel extends Model { public num!: number; public static associations: { @@ -12,10 +15,24 @@ class MyModel extends Model { class OtherModel extends Model {} -const assoc: Association = MyModel.associations.other; - const Instance: MyModel = new MyModel({ int: 10 }); -const num: number = Instance.get('num'); + +expectTypeOf(Instance.get('num')).toEqualTypeOf(); + +MyModel.findOne({ + include: [ + { + through: { + as: "OtherModel", + attributes: ['num'] + } + } + ] +}); + +MyModel.findOne({ + include: [ { through: { paranoid: true } } ] +}); MyModel.findOne({ include: [ @@ -27,9 +44,41 @@ MyModel.hasOne(OtherModel, { as: 'OtherModelAlias' }); MyModel.findOne({ include: ['OtherModelAlias'] }); +MyModel.findOne({ include: OtherModel }); + +MyModel.findAndCountAll({ include: OtherModel }).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf(); + expectTypeOf(rows).toEqualTypeOf(); +}); + +MyModel.findAndCountAll({ include: OtherModel, group: ['MyModel.num'] }).then(({ count, rows }) => { + expectTypeOf(count).toEqualTypeOf(); + expectTypeOf(rows).toEqualTypeOf(); +}); + +MyModel.count({ include: OtherModel }); + +MyModel.count({ include: [MyModel], where: { '$num$': [10, 120] } }); + +MyModel.build({ int: 10 }, { include: OtherModel }); + +MyModel.bulkCreate([{ int: 10 }], { include: OtherModel, searchPath: 'public' }); + +MyModel.update({}, { where: { foo: 'bar' }, paranoid: false}); + const sequelize = new Sequelize('mysql://user:user@localhost:3306/mydb'); -MyModel.init({}, { +const model: typeof MyModel = MyModel.init({ + virtual: { + type: new DataTypes.VIRTUAL(DataTypes.BOOLEAN, ['num']), + get() { + return this.getDataValue('num') + 2; + }, + set(value: number) { + this.setDataValue('num', value - 2); + } + } +}, { indexes: [ { fields: ['foo'], @@ -67,8 +116,108 @@ UserModel.findCreateFind({ } }) +/** + * Tests for findOrCreate() type. + */ + +UserModel.findOrCreate({ + fields: [ "jane.doe" ], + where: { + username: "jane.doe" + }, + defaults: { + username: "jane.doe" + } +}) + /** * Test for primaryKeyAttributes. */ class TestModel extends Model {}; -TestModel.primaryKeyAttributes; \ No newline at end of file +TestModel.primaryKeyAttributes; + +/** + * Test for joinTableAttributes on BelongsToManyGetAssociationsMixin + */ +class SomeModel extends Model { + public getOthers!: BelongsToManyGetAssociationsMixin +} + +const someInstance = new SomeModel(); +someInstance.getOthers({ + joinTableAttributes: { include: ['id'] } +}); + +/** + * Test for through options in creating a BelongsToMany association + */ +class Film extends Model {} + +class Actor extends Model {} + +Film.belongsToMany(Actor, { + through: { + model: 'FilmActors', + paranoid: true + } +}); + +Actor.belongsToMany(Film, { + through: { + model: 'FilmActors', + paranoid: true + } +}); + +interface ModelAttributes { + id: number; + name: string; +} + +interface CreationAttributes extends Optional {} + +const ModelWithAttributes: ModelDefined< + ModelAttributes, + CreationAttributes +> = sequelize.define('efs', { + name: DataTypes.STRING +}); + +const modelWithAttributes = ModelWithAttributes.build(); + +/** + * Tests for set() type + */ +expectTypeOf(modelWithAttributes.set).toBeFunction(); +expectTypeOf(modelWithAttributes.set).parameter(0).toEqualTypeOf>(); + +/** + * Tests for previous() type + */ +expectTypeOf(modelWithAttributes.previous).toBeFunction(); +expectTypeOf(modelWithAttributes.previous).toBeCallableWith('name'); +expectTypeOf(modelWithAttributes.previous).parameter(0).toEqualTypeOf(); +expectTypeOf(modelWithAttributes.previous).parameter(0).not.toEqualTypeOf<'unreferencedAttribute'>(); +expectTypeOf(modelWithAttributes.previous).returns.toEqualTypeOf(); +expectTypeOf(modelWithAttributes.previous('name')).toEqualTypeOf(); +expectTypeOf(modelWithAttributes.previous()).toEqualTypeOf>(); + +/** + * Tests for toJson() type + */ +interface FilmToJson { + id: number; + name?: string; +} +class FilmModelToJson extends Model implements FilmToJson { + id!: number; + name?: string; +} +const film = FilmModelToJson.build(); + +const result = film.toJSON(); +expectTypeOf(result).toEqualTypeOf() + +type FilmNoNameToJson = Omit +const resultDerived = film.toJSON(); +expectTypeOf(resultDerived).toEqualTypeOf() \ No newline at end of file diff --git a/types/test/models/User.ts b/types/test/models/User.ts index 91ab57558457..d69639358238 100644 --- a/types/test/models/User.ts +++ b/types/test/models/User.ts @@ -7,11 +7,26 @@ import { FindOptions, Model, ModelCtor, - Op + Op, + Optional } from 'sequelize'; import { sequelize } from '../connection'; -export class User extends Model { +export interface UserAttributes { + id: number; + username: string; + firstName: string; + lastName: string; + groupId: number; +} + +/** + * In this case, we make most fields optional. In real cases, + * only fields that have default/autoincrement values should be made optional. + */ +export interface UserCreationAttributes extends Optional {} + +export class User extends Model implements UserAttributes { public static associations: { group: BelongsTo; }; @@ -20,11 +35,11 @@ export class User extends Model { public username!: string; public firstName!: string; public lastName!: string; + public groupId!: number; public createdAt!: Date; public updatedAt!: Date; // mixins for association (optional) - public groupId!: number; public group?: UserGroup; public getGroup!: BelongsToGetAssociationMixin; public setGroup!: BelongsToSetAssociationMixin; @@ -33,9 +48,14 @@ export class User extends Model { User.init( { + id: { + type: DataTypes.NUMBER, + primaryKey: true, + }, firstName: DataTypes.STRING, lastName: DataTypes.STRING, username: DataTypes.STRING, + groupId: DataTypes.NUMBER, }, { version: true, @@ -46,7 +66,7 @@ User.init( }, setterMethods: { b(val: string) { - (this).username = val; + this.username = val; }, }, scopes: { @@ -71,29 +91,30 @@ User.init( } ); -User.hooks.add('afterSync', () => { +User.afterSync(() => { sequelize.getQueryInterface().addIndex(User.tableName, { - fields: ['lastName'], - using: 'BTREE', - name: 'lastNameIdx', - concurrently: true, + fields: ['lastName'], + using: 'BTREE', + name: 'lastNameIdx', + concurrently: true, }) }) // Hooks -User.hooks.add('afterFind', (users: User[], options: FindOptions) => { +User.afterFind((users, options) => { console.log('found'); }); -User.hooks.add('afterBulkCreate', (users: User[]) => { - -}) - // TODO: VSCode shows the typing being correctly narrowed but doesn't do it correctly -User.hooks.add('beforeFind', (options: FindOptions) => { +User.addHook('beforeFind', 'test', (options: FindOptions) => { return undefined; }); +User.addHook('afterDestroy', async (instance, options) => { + // `options` from `afterDestroy` should be passable to `sequelize.transaction` + await instance.sequelize.transaction(options, async () => undefined); +}); + // Model#addScope User.addScope('withoutFirstName', { where: { @@ -123,3 +144,6 @@ User.scope([ 'custom2', { method: [ 'custom', 32 ] } ]) + +const instance = new User({ username: 'foo', firstName: 'bar', lastName: 'baz' }); +instance.isSoftDeleted() diff --git a/types/test/models/UserGroup.ts b/types/test/models/UserGroup.ts index 498d4e75875d..1c170f2302e7 100644 --- a/types/test/models/UserGroup.ts +++ b/types/test/models/UserGroup.ts @@ -10,10 +10,15 @@ import { HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, HasManySetAssociationsMixin, - Model, + Model } from 'sequelize'; import { sequelize } from '../connection'; +// associate +// it is important to import _after_ the model above is already exported so the circular reference works. +import { User } from './User'; +// This class doesn't extend the generic Model, but should still +// function just fine, with a bit less safe type-checking export class UserGroup extends Model { public static associations: { users: HasMany @@ -28,7 +33,7 @@ export class UserGroup extends Model { public setUsers!: HasManySetAssociationsMixin; public addUser!: HasManyAddAssociationMixin; public addUsers!: HasManyAddAssociationsMixin; - public createUser!: HasManyCreateAssociationMixin; + public createUser!: HasManyCreateAssociationMixin; public countUsers!: HasManyCountAssociationsMixin; public hasUser!: HasManyHasAssociationMixin; public removeUser!: HasManyRemoveAssociationMixin; @@ -39,7 +44,4 @@ export class UserGroup extends Model { // instead of this, you could also use decorators UserGroup.init({ name: DataTypes.STRING }, { sequelize }); -// associate -// it is important to import _after_ the model above is already exported so the circular reference works. -import { User } from './User'; export const Users = UserGroup.hasMany(User, { as: 'users', foreignKey: 'groupId' }); diff --git a/types/test/promise.ts b/types/test/promise.ts deleted file mode 100644 index 82f8b0da9921..000000000000 --- a/types/test/promise.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Promise } from 'sequelize'; - -let promise: Promise = Promise.resolve(1); -promise.then((arg: number) => ({})).then((a: {}) => void 0); - -promise = new Promise(resolve => resolve()); diff --git a/types/test/query-interface.ts b/types/test/query-interface.ts index dd5956e9bb1c..09c403b23810 100644 --- a/types/test/query-interface.ts +++ b/types/test/query-interface.ts @@ -1,65 +1,88 @@ -import { DataTypes } from 'sequelize'; +import { DataTypes, Model, fn, literal, col } from 'sequelize'; // tslint:disable-next-line:no-submodule-imports import { QueryInterface } from 'sequelize/lib/query-interface'; declare let queryInterface: QueryInterface; -queryInterface.createTable( - 'nameOfTheNewTable', - { - attr1: DataTypes.STRING, - attr2: DataTypes.INTEGER, - attr3: { - allowNull: false, - defaultValue: false, - type: DataTypes.BOOLEAN, - }, - // foreign key usage - attr4: { - onDelete: 'cascade', - onUpdate: 'cascade', - references: { - key: 'id', - model: 'another_table_name', +async function test() { + await queryInterface.createTable( + 'nameOfTheNewTable', + { + attr1: DataTypes.STRING, + attr2: DataTypes.INTEGER, + attr3: { + allowNull: false, + defaultValue: false, + type: DataTypes.BOOLEAN, + }, + // foreign key usage + attr4: { + onDelete: 'cascade', + onUpdate: 'cascade', + references: { + key: 'id', + model: 'another_table_name', + }, + type: DataTypes.INTEGER, + }, + attr5: { + onDelete: 'cascade', + onUpdate: 'cascade', + references: { + key: 'id', + model: { schema: '', tableName: 'another_table_name' }, + }, + type: DataTypes.INTEGER, + }, + createdAt: { + type: DataTypes.DATE, + }, + id: { + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER, + }, + updatedAt: { + type: DataTypes.DATE, }, - type: DataTypes.INTEGER, - }, - createdAt: { - type: DataTypes.DATE, - }, - id: { - autoIncrement: true, - primaryKey: true, - type: DataTypes.INTEGER, - }, - updatedAt: { - type: DataTypes.DATE, }, - }, - { - charset: 'latin1', // default: null - collate: 'latin1_general_ci', - engine: 'MYISAM', // default: 'InnoDB' - uniqueKeys: { - test: { - customIndex: true, - fields: ['attr2', 'attr3'], + { + charset: 'latin1', // default: null + collate: 'latin1_general_ci', + engine: 'MYISAM', // default: 'InnoDB' + uniqueKeys: { + test: { + customIndex: true, + fields: ['attr2', 'attr3'], + } } } - } -); + ); + await queryInterface.createTable({ tableName: '' }, {}); + + await queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.dropTable({ schema: '', tableName: 'nameOfTheExistingTable' }); + + await queryInterface.bulkDelete({ tableName: 'foo', schema: 'bar' }, {}, {}); + + const bulkInsertRes: Promise = queryInterface.bulkInsert({ tableName: 'foo', as: 'bar', name: 'as' }, [{}], {}); -queryInterface.dropTable('nameOfTheExistingTable'); + await queryInterface.bulkUpdate({ tableName: 'foo', delimiter: 'bar', as: 'baz', name: 'quz' }, {}, {}); -queryInterface.dropAllTables(); + await queryInterface.dropTrigger({ tableName: 'foo', as: 'bar', name: 'baz' }, 'foo', {}); -queryInterface.renameTable('Person', 'User'); + await queryInterface.quoteTable({ tableName: 'foo', delimiter: 'bar' }); -queryInterface.showAllTables().then(tableNames => { - // do nothing -}); + await queryInterface.dropAllTables(); + + await queryInterface.renameTable('Person', 'User'); + await queryInterface.renameTable( + { schema: '', tableName: 'Person' }, + { schema: '', tableName: 'User' }, + ); + + const tableNames: string[] = await queryInterface.showAllTables(); -queryInterface.describeTable('Person').then(attributes => { /* attributes will be something like: @@ -76,87 +99,127 @@ queryInterface.describeTable('Person').then(attributes => { } } */ -}); - -queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', DataTypes.STRING); - -// or + const attributes: object = await queryInterface.describeTable('Person'); -queryInterface.addColumn( - { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, - 'nameOfTheNewAttribute', - DataTypes.STRING -); + await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', DataTypes.STRING); -// or + // or -queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', { - allowNull: false, - type: DataTypes.STRING, -}); + await queryInterface.addColumn( + { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, + 'nameOfTheNewAttribute', + DataTypes.STRING + ); -queryInterface.removeColumn('Person', 'signature'); + // or -// or + await queryInterface.addColumn('nameOfAnExistingTable', 'nameOfTheNewAttribute', { + allowNull: false, + type: DataTypes.STRING, + }); -queryInterface.removeColumn({ tableName: 'Person', schema: 'nameOfSchema' }, 'signature'); + await queryInterface.removeColumn('Person', 'signature'); -queryInterface.changeColumn('nameOfAnExistingTable', 'nameOfAnExistingAttribute', { - allowNull: false, - defaultValue: 0.0, - type: DataTypes.FLOAT, -}); + // or -// or + await queryInterface.removeColumn({ tableName: 'Person', schema: 'nameOfSchema' }, 'signature'); -queryInterface.changeColumn( - { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, - 'nameOfAnExistingAttribute', - { + await queryInterface.changeColumn('nameOfAnExistingTable', 'nameOfAnExistingAttribute', { allowNull: false, defaultValue: 0.0, type: DataTypes.FLOAT, - } -); - -queryInterface.renameColumn('Person', 'signature', 'sig'); + }); -// This example will create the index person_firstname_lastname -queryInterface.addIndex('Person', ['firstname', 'lastname']); + // or -// This example will create a unique index with the name SuperDuperIndex using the optional 'options' field. -// Possible options: -// - indexName: The name of the index. Default is __ -// - parser: For FULLTEXT columns set your parser -// - indexType: Set a type for the index, e.g. BTREE. See the documentation of the used dialect -// - logging: A function that receives the sql query, e.g. console.log -queryInterface.addIndex('Person', ['firstname', 'lastname'], { - name: 'SuperDuperIndex', - type: 'UNIQUE', -}); - -queryInterface.removeIndex('Person', 'SuperDuperIndex'); - -// or - -queryInterface.removeIndex('Person', ['firstname', 'lastname']); + await queryInterface.changeColumn( + { tableName: 'nameOfAnExistingTable', schema: 'nameOfSchema' }, + 'nameOfAnExistingAttribute', + { + allowNull: false, + defaultValue: 0.0, + type: DataTypes.FLOAT, + } + ); + + await queryInterface.renameColumn('Person', 'signature', 'sig'); + await queryInterface.renameColumn({ schema: '', tableName: 'Person' }, 'signature', 'sig'); + + // This example will create the index person_firstname_lastname + await queryInterface.addIndex('Person', ['firstname', 'lastname']); + await queryInterface.addIndex({ schema: '', tableName: 'Person' }, ['firstname', 'lastname']); + + // This example will create a unique index with the name SuperDuperIndex using the optional 'options' field. + // Possible options: + // - indexName: The name of the index. Default is __ + // - parser: For FULLTEXT columns set your parser + // - indexType: Set a type for the index, e.g. BTREE. See the documentation of the used dialect + // - logging: A function that receives the sql query, e.g. console.log + await queryInterface.addIndex('Person', ['firstname', 'lastname'], { + name: 'SuperDuperIndex', + type: 'UNIQUE', + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_a', + fields: [ + { name: 'foo_b', order: 'DESC' }, + 'foo_c', + { name: 'foo_d', order: 'ASC', collate: 'foobar', length: 42 } + ], + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_b_lower', + fields: [ + fn('lower', col('foo_b')) + ], + }); + + await queryInterface.addIndex('Foo', { + name: 'foo_c_lower', + fields: [ + literal('LOWER(foo_c)') + ] + }) + + await queryInterface.removeIndex('Person', 'SuperDuperIndex'); + await queryInterface.removeIndex({ schema: '', tableName: 'Person' }, 'SuperDuperIndex'); + + // or + + await queryInterface.removeIndex('Person', ['firstname', 'lastname']); + + await queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', { + name: 'firstnamexlastname', + fields: ['firstname', 'lastname'], + type: 'unique', + transaction: trx, + })) + + await queryInterface.removeConstraint('Person', 'firstnamexlastname'); + await queryInterface.removeConstraint({ schema: '', tableName: 'Person' }, 'firstnamexlastname'); + + await queryInterface.select(null, 'Person', { + where: { + a: 1, + }, + }); + await queryInterface.select(null, { schema: '', tableName: 'Person' }, { + where: { + a: 1, + }, + }); -queryInterface.sequelize.transaction(trx => queryInterface.addConstraint('Person', ['firstname', 'lastname'], { - name: 'firstnamexlastname', - type: 'unique', - transaction: trx, -})) + await queryInterface.delete(null, 'Person', { + where: { + a: 1, + }, + }); -queryInterface.removeConstraint('Person', 'firstnamexlastname'); + class TestModel extends Model {} -queryInterface.select(null, 'Person', { - where: { - a: 1, - }, -}); + await queryInterface.upsert("test", {"a": 1}, {"b": 2}, {"c": 3}, TestModel, {}); -queryInterface.delete(null, 'Person', { - where: { - a: 1, - }, -}); + await queryInterface.insert(null, 'test', {}); +} diff --git a/types/test/sequelize.ts b/types/test/sequelize.ts index f1f9b5f2fc36..2b5af1c5dfb4 100644 --- a/types/test/sequelize.ts +++ b/types/test/sequelize.ts @@ -1,9 +1,12 @@ -import { Config, Sequelize, Model, QueryTypes } from 'sequelize'; +import { Config, Sequelize, Model, QueryTypes, ModelCtor } from 'sequelize'; import { Fn } from '../lib/utils'; +Sequelize.useCLS({ +}); + export const sequelize = new Sequelize({ hooks: { - afterConnect: (connection, config: Config) => { + afterConnect: (connection: unknown, config: Config) => { // noop } }, @@ -17,34 +20,35 @@ export const sequelize = new Sequelize({ } }); +const databaseName = sequelize.getDatabaseName(); + const conn = sequelize.connectionManager; // hooks -sequelize.hooks.add('beforeCreate', () => { +sequelize.beforeCreate('test', () => { // noop }); sequelize - .hooks.add('beforeConnect', (config: Config) => { + .addHook('beforeConnect', (config: Config) => { // noop }) - .add('beforeBulkSync', () => { + .addHook('beforeBulkSync', () => { // noop }); -Sequelize.hooks.add('beforeCreate', () => { +Sequelize.addHook('beforeCreate', () => { // noop -}) -.add('beforeBulkCreate', () => { +}).addHook('beforeBulkCreate', () => { // noop }); -Sequelize.hooks.add('beforeConnect', () => { +Sequelize.beforeConnect(() => { }); -Sequelize.hooks.add('afterConnect', () => { +Sequelize.afterConnect(() => { }); @@ -52,12 +56,21 @@ const rnd: Fn = sequelize.random(); class Model1 extends Model{} class Model2 extends Model{} -const myModel: typeof Model1 = sequelize.models.asd; +const myModel: ModelCtor = sequelize.models.asd; myModel.hasOne(Model2) myModel.findAll(); -sequelize.query('SELECT * FROM `user`', { type: QueryTypes.RAW }).then(result => { - const data = result[0]; - const arraysOnly = (a: any[]) => a; - arraysOnly(data); -}); +async function test() { + const [results, meta]: [unknown[], unknown] = await sequelize.query('SELECT * FROM `user`', { type: QueryTypes.RAW }); + + const res2: { count: number } = await sequelize + .query<{ count: number }>("SELECT COUNT(1) as count FROM `user`", { + type: QueryTypes.SELECT, + plain: true + }); + + const res3: { [key: string]: unknown; } = await sequelize + .query("SELECT COUNT(1) as count FROM `user`", { + plain: true + }) +} diff --git a/types/test/transaction.ts b/types/test/transaction.ts index ca2095359909..b77cf12e4eaf 100644 --- a/types/test/transaction.ts +++ b/types/test/transaction.ts @@ -5,10 +5,10 @@ export const sequelize = new Sequelize('uri'); async function trans() { const a: number = await sequelize.transaction(async transaction => { - transaction.hooks.add('afterCommit', () => console.log('transaction complete')); + transaction.afterCommit(() => console.log('transaction complete')); User.create( { - data: 123, + firstName: 'John', }, { transaction, @@ -18,6 +18,48 @@ async function trans() { }); } +async function trans2() { + return await sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.log('transaction complete')); + User.findAll( + { + transaction, + lock: transaction.LOCK.UPDATE, + } + ); + return 1; + }); +} + +async function trans3() { + return await sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.log('transaction complete')); + User.findAll( + { + transaction, + lock: true, + } + ); + return 1; + }); +} + +async function trans4() { + return await sequelize.transaction(async transaction => { + transaction.afterCommit(() => console.log('transaction complete')); + User.findAll( + { + transaction, + lock: { + level: transaction.LOCK.UPDATE, + of: User, + }, + } + ); + return 1; + }); +} + async function transact() { const t = await sequelize.transaction({ deferrable: Deferrable.SET_DEFERRED(['test']), @@ -36,3 +78,9 @@ async function nestedTransact() { }); await tr.commit(); } + +async function excludeFromTransaction() { + await sequelize.transaction(async t => + await sequelize.query('SELECT 1', { transaction: null }) + ); +} diff --git a/types/test/tsconfig.json b/types/test/tsconfig.json index b14c71d38043..843200282732 100644 --- a/types/test/tsconfig.json +++ b/types/test/tsconfig.json @@ -9,7 +9,6 @@ "sequelize": ["../"], "sequelize/*": ["../"] }, - "types": ["node"], "lib": ["es2016"] }, "include": ["../index.d.ts", "./**/sequelize.d.ts", "./**/*.ts"] diff --git a/types/test/type-helpers/deep-writable.ts b/types/test/type-helpers/deep-writable.ts new file mode 100644 index 000000000000..19d34e5db1eb --- /dev/null +++ b/types/test/type-helpers/deep-writable.ts @@ -0,0 +1,48 @@ +/** + * Adapted from krzkaczor/ts-essentials + * + * https://github.com/krzkaczor/ts-essentials/blob/v7.1.0/lib/types.ts#L165 + * + * Thank you! + */ + +import { Model, Sequelize, ModelCtor, ModelType, ModelDefined, ModelStatic } from "sequelize"; + +type Builtin = string | number | boolean | bigint | symbol | undefined | null | Function | Date | Error | RegExp; +type SequelizeBasic = Builtin | Sequelize | Model | ModelCtor | ModelType | ModelDefined | ModelStatic; + +// type ToMutableArrayIfNeeded = T extends readonly any[] +// ? { -readonly [K in keyof T]: ToMutableArrayIfNeeded } +// : T; + +type NoReadonlyArraysDeep = T extends SequelizeBasic + ? T + : T extends readonly any[] + ? { -readonly [K in keyof T]: NoReadonlyArraysDeep } + : T extends Record + ? { [K in keyof T]: NoReadonlyArraysDeep } + : T; + +type ShallowWritable = T extends Record ? { -readonly [K in keyof T]: T[K] } : T; + +export type SemiDeepWritable = ShallowWritable>; + +export type DeepWritable = T extends SequelizeBasic + ? T + : T extends Map + ? Map, DeepWritable> + : T extends ReadonlyMap + ? Map, DeepWritable> + : T extends WeakMap + ? WeakMap, DeepWritable> + : T extends Set + ? Set> + : T extends ReadonlySet + ? Set> + : T extends WeakSet + ? WeakSet> + : T extends Promise + ? Promise> + : T extends {} + ? { -readonly [K in keyof T]: DeepWritable } + : T; diff --git a/types/test/typescriptDocs/Define.ts b/types/test/typescriptDocs/Define.ts new file mode 100644 index 000000000000..9e222311053d --- /dev/null +++ b/types/test/typescriptDocs/Define.ts @@ -0,0 +1,38 @@ +/** + * Keep this file in sync with the code in the "Usage of `sequelize.define`" + * section in typescript.md + */ +import { Sequelize, Model, DataTypes, Optional } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// We recommend you declare an interface for the attributes, for stricter typechecking +interface UserAttributes { + id: number; + name: string; +} + +// Some fields are optional when calling UserModel.create() or UserModel.build() +interface UserCreationAttributes extends Optional {} + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance + extends Model, + UserAttributes {} + +const UserModel = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + } +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} diff --git a/types/test/typescriptDocs/DefineNoAttributes.ts b/types/test/typescriptDocs/DefineNoAttributes.ts new file mode 100644 index 000000000000..ac2d70069a13 --- /dev/null +++ b/types/test/typescriptDocs/DefineNoAttributes.ts @@ -0,0 +1,30 @@ +/** + * Keep this file in sync with the code in the "Usage of `sequelize.define`" + * that doesn't have attribute types in typescript.md + */ +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +// We need to declare an interface for our model that is basically what our class would be +interface UserInstance extends Model { + id: number; + name: string; +} + +const UserModel = sequelize.define('User', { + id: { + primaryKey: true, + type: DataTypes.INTEGER.UNSIGNED, + }, + name: { + type: DataTypes.STRING, + }, +}); + +async function doStuff() { + const instance = await UserModel.findByPk(1, { + rejectOnEmpty: true, + }); + console.log(instance.id); +} diff --git a/types/test/typescriptDocs/ModelInit.ts b/types/test/typescriptDocs/ModelInit.ts new file mode 100644 index 000000000000..163cb8ec88ee --- /dev/null +++ b/types/test/typescriptDocs/ModelInit.ts @@ -0,0 +1,217 @@ +/** + * Keep this file in sync with the code in the "Usage" section in typescript.md + */ +import { + Sequelize, + Model, + ModelDefined, + DataTypes, + HasManyGetAssociationsMixin, + HasManyAddAssociationMixin, + HasManyHasAssociationMixin, + Association, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + Optional, +} from "sequelize"; + +const sequelize = new Sequelize("mysql://root:asd123@localhost:3306/mydb"); + +// These are all the attributes in the User model +interface UserAttributes { + id: number; + name: string; + preferredName: string | null; +} + +// Some attributes are optional in `User.build` and `User.create` calls +interface UserCreationAttributes extends Optional {} + +class User extends Model + implements UserAttributes { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields + + // timestamps! + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + // Since TS cannot determine model association at compile time + // we have to declare them here purely virtually + // these will not exist until `Model.init` was called. + public getProjects!: HasManyGetAssociationsMixin; // Note the null assertions! + public addProject!: HasManyAddAssociationMixin; + public hasProject!: HasManyHasAssociationMixin; + public countProjects!: HasManyCountAssociationsMixin; + public createProject!: HasManyCreateAssociationMixin; + + // You can also pre-declare possible inclusions, these will only be populated if you + // actively include a relation. + public readonly projects?: Project[]; // Note this is optional since it's only populated when explicitly requested in code + + public static associations: { + projects: Association; + }; +} + +interface ProjectAttributes { + id: number; + ownerId: number; + name: string; +} + +interface ProjectCreationAttributes extends Optional {} + +class Project extends Model + implements ProjectAttributes { + public id!: number; + public ownerId!: number; + public name!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +interface AddressAttributes { + userId: number; + address: string; +} + +// You can write `extends Model` instead, +// but that will do the exact same thing as below +class Address extends Model implements AddressAttributes { + public userId!: number; + public address!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +// You can also define modules in a functional way +interface NoteAttributes { + id: number; + title: string; + content: string; +} + +// You can also set multiple attributes optional at once +interface NoteCreationAttributes + extends Optional {} + +Project.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + ownerId: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + }, + { + sequelize, + tableName: "projects", + } +); + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: "users", + sequelize, // passing the `sequelize` instance is required + } +); + +Address.init( + { + userId: { + type: DataTypes.INTEGER.UNSIGNED, + }, + address: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + }, + { + tableName: "address", + sequelize, // passing the `sequelize` instance is required + } +); + +// And with a functional approach defining a module looks like this +const Note: ModelDefined< + NoteAttributes, + NoteCreationAttributes +> = sequelize.define( + "Note", + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + title: { + type: new DataTypes.STRING(64), + defaultValue: "Unnamed Note", + }, + content: { + type: new DataTypes.STRING(4096), + allowNull: false, + }, + }, + { + tableName: "notes", + } +); + +// Here we associate which actually populates out pre-declared `association` static and other methods. +User.hasMany(Project, { + sourceKey: "id", + foreignKey: "ownerId", + as: "projects", // this determines the name in `associations`! +}); + +Address.belongsTo(User, { targetKey: "id" }); +User.hasOne(Address, { sourceKey: "id" }); + +async function doStuffWithUser() { + const newUser = await User.create({ + name: "Johnny", + preferredName: "John", + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const project = await newUser.createProject({ + name: "first!", + }); + + const ourUser = await User.findByPk(1, { + include: [User.associations.projects], + rejectOnEmpty: true, // Specifying true here removes `null` from the return type! + }); + + // Note the `!` null assertion since TS can't know if we included + // the model or not + console.log(ourUser.projects![0].name); +} \ No newline at end of file diff --git a/types/test/typescriptDocs/ModelInitNoAttributes.ts b/types/test/typescriptDocs/ModelInitNoAttributes.ts new file mode 100644 index 000000000000..b53ea86fc12a --- /dev/null +++ b/types/test/typescriptDocs/ModelInitNoAttributes.ts @@ -0,0 +1,47 @@ +/** + * Keep this file in sync with the code in the "Usage without strict types for + * attributes" section in typescript.md + */ +import { Sequelize, Model, DataTypes } from 'sequelize'; + +const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb'); + +class User extends Model { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public name!: string; + public preferredName!: string | null; // for nullable fields +} + +User.init( + { + id: { + type: DataTypes.INTEGER.UNSIGNED, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: new DataTypes.STRING(128), + allowNull: false, + }, + preferredName: { + type: new DataTypes.STRING(128), + allowNull: true, + }, + }, + { + tableName: 'users', + sequelize, // passing the `sequelize` instance is required + }, +); + +async function doStuffWithUserModel() { + const newUser = await User.create({ + name: 'Johnny', + preferredName: 'John', + }); + console.log(newUser.id, newUser.name, newUser.preferredName); + + const foundUser = await User.findOne({ where: { name: 'Johnny' } }); + if (foundUser === null) return; + console.log(foundUser.name); +} diff --git a/types/test/update.ts b/types/test/update.ts new file mode 100644 index 000000000000..9412eebc89ad --- /dev/null +++ b/types/test/update.ts @@ -0,0 +1,37 @@ +import { Model, fn, col, literal } from 'sequelize'; +import { User } from './models/User'; + +class TestModel extends Model { +} + +TestModel.update({}, { where: {} }); +TestModel.update({}, { where: {}, returning: false }); +TestModel.update({}, { where: {}, returning: true }); +TestModel.update({}, { where: {}, returning: ['foo'] }); + + +User.update({}, { where: {} }); +User.update({ + id: 123, + username: fn('FN'), + firstName: col('id'), + lastName: literal('Smith'), +}, { where: {} }); +User.update({}, { where: {}, returning: true }); +User.update({}, { where: {}, returning: false }); +User.update({}, { where: {}, returning: ['username'] }); +User.build().update({ + id: 123, + username: fn('FN'), + firstName: col('id'), + lastName: literal('Smith'), +}); +// @ts-expect-error invalid `returning` +User.update({}, { where: {}, returning: ['foo'] }); +// @ts-expect-error no `where` +User.update({}, {}); +// @ts-expect-error invalid attribute +User.update({ foo: '' }, { where: {} }); +// @ts-expect-error invalid attribute +User.build().update({ foo: '' }); + diff --git a/types/test/upsert.ts b/types/test/upsert.ts index efc6f81b6a06..5879482fbe22 100644 --- a/types/test/upsert.ts +++ b/types/test/upsert.ts @@ -1,41 +1,45 @@ import {Model} from "sequelize" import {sequelize} from './connection'; -class TestModel extends Model { +class TestModel extends Model<{ foo: string; bar: string }, {}> { } -TestModel.init({}, {sequelize}) +TestModel.init({ + foo: '', + bar: '', +}, {sequelize}) -sequelize.transaction(trx => { - TestModel.upsert({}, { +sequelize.transaction(async trx => { + const res1: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, - fields: ['testField'], + fields: ['foo'], hooks: true, logging: true, returning: true, searchPath: 'DEFAULT', transaction: trx, validate: true, - }).then((res: [ TestModel, boolean ]) => {}); + }); - TestModel.upsert({}, { + const res2: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, - fields: ['testField'], + fields: ['foo'], hooks: true, logging: true, returning: false, searchPath: 'DEFAULT', transaction: trx, validate: true, - }).then((created: boolean) => {}); + }); - return TestModel.upsert({}, { + const res3: [TestModel, boolean | null] = await TestModel.upsert({}, { benchmark: true, - fields: ['testField'], + fields: ['foo'], hooks: true, logging: true, + returning: ['foo'], searchPath: 'DEFAULT', transaction: trx, validate: true, - }).then((created: boolean) => {}); + }); }) diff --git a/types/test/validators.ts b/types/test/validators.ts new file mode 100644 index 000000000000..56254de307db --- /dev/null +++ b/types/test/validators.ts @@ -0,0 +1,22 @@ +import { DataTypes, Model, Sequelize } from 'sequelize'; + +const sequelize = new Sequelize('mysql://user:user@localhost:3306/mydb'); + +/** + * Test for isIn/notIn validation - should accept any[] + */ +class ValidatedUser extends Model {} +ValidatedUser.init({ + name: { + type: DataTypes.STRING, + validate: { + isIn: [['first', 1, null]] + } + }, + email: { + type: DataTypes.STRING, + validate: { + notIn: [['second', 2, null]] + } + }, +}, { sequelize }); \ No newline at end of file diff --git a/types/test/where.ts b/types/test/where.ts index 4db82b911ad2..037acbbe0b0b 100644 --- a/types/test/where.ts +++ b/types/test/where.ts @@ -1,118 +1,114 @@ +import { expectTypeOf } from "expect-type"; import { AndOperator, fn, Model, Op, OrOperator, Sequelize, WhereOperators, WhereOptions, literal, where as whereFn } from 'sequelize'; import Transaction from '../lib/transaction'; class MyModel extends Model { - public hi!: number; + public hi!: number; } -let where: WhereOptions; +// Simple options -// From https://sequelize.org/master/en/v4/docs/querying/ +expectTypeOf({ + string: 'foo', + strings: ['foo'], + number: 1, + numbers: [1], + boolean: true, + buffer: Buffer.alloc(0), + buffers: [Buffer.alloc(0)], + null: null, + date: new Date() +}).toMatchTypeOf(); -// Operators - -const and: AndOperator = { - [Op.and]: { a: 5 }, // AND (a = 5) -}; - -const or: OrOperator = { - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) -}; - -let operators: WhereOperators = { - [Op.gt]: 6, // > 6 - [Op.gte]: 6, // >= 6 - [Op.lt]: 10, // < 10 - [Op.lte]: 10, // <= 10 - [Op.ne]: 20, // != 20 - [Op.not]: true, // IS NOT TRUE - [Op.between]: [6, 10], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.startsWith]: 'hat', - [Op.endsWith]: 'hat', - [Op.substring]: 'hat', - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) - [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) - [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) - [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) - [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) -}; - -operators = { - [Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] - also works for iLike and notLike -}; - -// Combinations +// Optional values +expectTypeOf<{ needed: number; optional?: number }>().toMatchTypeOf(); -MyModel.findOne({ where: or }); -MyModel.findOne({ where: and }); +// Misusing optional values (typings allow this, sequelize will throw an error during runtime) +// This might be solved by updates to typescript itself (https://github.com/microsoft/TypeScript/issues/13195) +// expectTypeOf({ needed: 2, optional: undefined }).not.toMatchTypeOf(); -where = Sequelize.and(); - -where = Sequelize.or(); - -where = { [Op.and]: [] }; - -where = { - rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }), -}; - -where = { - rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }), -}; - -where = { - rank: { - [Op.or]: { - [Op.lt]: 1000, - [Op.eq]: null, - }, - }, -}; -// rank < 1000 OR rank IS NULL +// Operators -where = { +expectTypeOf({ + [Op.and]: { a: 5 }, // AND (a = 5) +}).toMatchTypeOf(); +expectTypeOf({ + [Op.and]: { a: 5 }, // AND (a = 5) +}).toMatchTypeOf>(); + +expectTypeOf({ + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) +}).toMatchTypeOf(); +expectTypeOf({ + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) +}).toMatchTypeOf>(); + +expectTypeOf({ + [Op.eq]: 6, // = 6 + [Op.gt]: 6, // > 6 + [Op.gte]: 6, // >= 6 + [Op.lt]: 10, // < 10 + [Op.lte]: 10, // <= 10 + [Op.ne]: 20, // != 20 + [Op.not]: true, // IS NOT TRUE + [Op.is]: null, // IS NULL + [Op.between]: [6, 10], // BETWEEN 6 AND 10 + [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 + [Op.in]: [1, 2], // IN [1, 2] + [Op.notIn]: [1, 2], // NOT IN [1, 2] + [Op.like]: '%hat', // LIKE '%hat' + [Op.notLike]: '%hat', // NOT LIKE '%hat' + [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) + [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) + [Op.startsWith]: 'hat', + [Op.endsWith]: 'hat', + [Op.substring]: 'hat', + [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) + [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) + [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) + [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + [Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only) + [Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only) + [Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only) + [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (PG only) +} as const).toMatchTypeOf(); + +expectTypeOf({ + [Op.like]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] + [Op.iLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] + [Op.notLike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] + [Op.notILike]: { [Op.any]: ['cat', 'hat'] }, // LIKE ANY ARRAY['cat', 'hat'] +}).toMatchTypeOf(); + +// Complex where options via combinations + +expectTypeOf([ + { [Op.or]: [{ a: 5 }, { a: 6 }] }, + Sequelize.and(), + Sequelize.or(), + { [Op.and]: [] }, + { rank: Sequelize.and({ [Op.lt]: 1000 }, { [Op.eq]: null }) }, + { rank: Sequelize.or({ [Op.lt]: 1000 }, { [Op.eq]: null }) }, + { rank: { [Op.or]: { [Op.lt]: 1000, [Op.eq]: null } } }, + { createdAt: { - [Op.lt]: new Date(), - [Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000), - }, -}; -// createdAt < [timestamp] AND createdAt > [timestamp] - -where = { + [Op.lt]: new Date(), + [Op.gt]: new Date(Date.now() - 24 * 60 * 60 * 1000), + } + }, + { [Op.or]: [ - { - title: { - [Op.like]: 'Boat%', - }, - }, - { - description: { - [Op.like]: '%boat%', - }, - }, - ], -}; -// title LIKE 'Boat%' OR description LIKE '%boat%' - -// Containment - -where = { + { title: { [Op.like]: 'Boat%' } }, + { description: { [Op.like]: '%boat%' } } + ] + }, + { meta: { - [Op.contains]: { - site: { - url: 'http://google.com', - }, - }, + [Op.contains]: { + site: { + url: 'https://sequelize.org/' + } + } }, meta2: { [Op.contains]: ['stringValue1', 'stringValue2', 'stringValue3'] @@ -120,192 +116,172 @@ where = { meta3: { [Op.contains]: [1, 2, 3, 4] }, -}; - -// Relations / Associations -// Find all projects with a least one task where task.state === project.task -MyModel.findAll({ - include: [ - { - model: MyModel, - where: { state: Sequelize.col('project.state') }, - }, - ], -}); - -MyModel.findOne({ - include: [ - { - include: [{ model: MyModel, where }], - model: MyModel, - where, - }, - ], - where, -}); -MyModel.destroy({ where }); -MyModel.update({ hi: 1 }, { where }); - -// From https://sequelize.org/master/en/v4/docs/models-usage/ - -// find multiple entries -MyModel.findAll().then(projects => { - // projects will be an array of all MyModel instances -}); - -// search for specific attributes - hash usage -MyModel.findAll({ where: { name: 'A MyModel', enabled: true } }).then(projects => { - // projects will be an array of MyModel instances with the specified name -}); - -// search within a specific range -MyModel.findAll({ where: { id: [1, 2, 3] } }).then(projects => { - // projects will be an array of MyModels having the id 1, 2 or 3 - // this is actually doing an IN query -}); - -// locks -MyModel.findAll({ lock: Transaction.LOCK.KEY_SHARE }).then(projects => { - // noop -}); - -// locks on model -MyModel.findAll({ lock: { level: Transaction.LOCK.KEY_SHARE, of: MyModel} }).then(projects => { - // noop -}); - -MyModel.findAll({ - where: { - id: { - // casting here to check a missing operator is not accepted as field name - [Op.and]: { a: 5 }, // AND (a = 5) - [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) - [Op.gt]: 6, // id > 6 - [Op.gte]: 6, // id >= 6 - [Op.lt]: 10, // id < 10 - [Op.lte]: 10, // id <= 10 - [Op.ne]: 20, // id != 20 - [Op.between]: [6, 10], // BETWEEN 6 AND 10 - [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 - [Op.in]: [1, 2], // IN [1, 2] - [Op.notIn]: [1, 2], // NOT IN [1, 2] - [Op.like]: '%hat', // LIKE '%hat' - [Op.notLike]: '%hat', // NOT LIKE '%hat' - [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) - [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) - [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) - [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) - [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) - [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) - [Op.adjacent]: [1, 2], - [Op.strictLeft]: [1, 2], - [Op.strictRight]: [1, 2], - [Op.noExtendLeft]: [1, 2], - [Op.noExtendRight]: [1, 2], - [Op.values]: [1, 2], - } as WhereOperators, - status: { - [Op.not]: false, // status NOT FALSE - }, - }, -}); - -// Complex filtering / NOT queries - -where = { + }, + { name: 'a project', - [Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }], -}; - -where = { + [Op.or]: [{ id: [1, 2, 3] }, { id: { [Op.gt]: 10 } }] + }, + { id: { - [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }], + [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }] }, - name: 'a project', -}; - -where = { + name: 'a project' + }, + { + id: { + [Op.or]: [[1, 2, 3], { [Op.gt]: 10 }] + }, + name: 'a project' + }, + { name: 'a project', type: { - [Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }], + [Op.and]: [['a', 'b'], { [Op.notLike]: '%z' }], }, -}; - -// [Op.not] example: -where = { + }, + { name: 'a project', [Op.not]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], -}; - -// JSONB - -// Nested object - -where = { + }, + { meta: { - video: { - url: { - [Op.ne]: null, - }, + video: { + url: { + [Op.ne]: null, }, + }, }, -}; - -// Nested key -where = { + }, + { 'meta.audio.length': { - [Op.gt]: 20, + [Op.gt]: 20, }, -}; - -// Operator symbols -where = { + }, + { [Op.and]: [{ id: [1, 2, 3] }, { array: { [Op.contains]: [3, 4, 5] } }], -}; - -// Fn as value -where = { + }, + { [Op.gt]: fn('NOW'), -}; + }, + whereFn('test', { [Op.gt]: new Date() }), + literal('true'), + fn('LOWER', 'asd'), + { [Op.lt]: Sequelize.literal('SOME_STRING') } +]).toMatchTypeOf(); -where = whereFn('test', { - [Op.gt]: new Date(), +// Relations / Associations +// Find all projects with a least one task where task.state === project.task +MyModel.findAll({ + include: [ + { + model: MyModel, + where: { state: Sequelize.col('project.state') }, + }, + ], }); -// Literal as where -where = literal('true') +{ + const where: WhereOptions = 0 as any; + MyModel.findOne({ + include: [ + { + include: [{ model: MyModel, where }], + model: MyModel, + where, + }, + ], + where, + }); + MyModel.destroy({ where }); + MyModel.update({ hi: 1 }, { where }); -MyModel.findAll({ - where: literal('true') -}) + // Where as having option + MyModel.findAll({ having: where }); +} -// Where as having option -MyModel.findAll({ - having: where -}); +// From https://sequelize.org/master/en/v4/docs/models-usage/ + +async function test() { + // find multiple entries + let projects: MyModel[] = await MyModel.findAll(); + + // search for specific attributes - hash usage + projects = await MyModel.findAll({ where: { name: 'A MyModel', enabled: true } }) + + // search within a specific range + projects = await MyModel.findAll({ where: { id: [1, 2, 3] } }); -where = { - [Op.lt]: Sequelize.literal('SOME_STRING') + // locks + projects = await MyModel.findAll({ lock: Transaction.LOCK.KEY_SHARE }); + + // locks on model + projects = await MyModel.findAll({ lock: { level: Transaction.LOCK.KEY_SHARE, of: MyModel} }); } +MyModel.findAll({ + where: { + id: { + // casting here to check a missing operator is not accepted as field name + [Op.and]: { a: 5 }, // AND (a = 5) + [Op.or]: [{ a: 5 }, { a: 6 }], // (a = 5 OR a = 6) + [Op.gt]: 6, // id > 6 + [Op.gte]: 6, // id >= 6 + [Op.lt]: 10, // id < 10 + [Op.lte]: 10, // id <= 10 + [Op.ne]: 20, // id != 20 + [Op.between]: [6, 10] || [new Date(), new Date()] || ["2020-01-01", "2020-12-31"], // BETWEEN 6 AND 10 + [Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15 + [Op.in]: [1, 2], // IN [1, 2] + [Op.notIn]: [1, 2], // NOT IN [1, 2] + [Op.like]: '%hat', // LIKE '%hat' + [Op.notLike]: '%hat', // NOT LIKE '%hat' + [Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only) + [Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only) + [Op.overlap]: [1, 2], // && [1, 2] (PG array overlap operator) + [Op.contains]: [1, 2], // @> [1, 2] (PG array contains operator) + [Op.contained]: [1, 2], // <@ [1, 2] (PG array contained by operator) + [Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (PG only) + [Op.adjacent]: [1, 2], + [Op.strictLeft]: [1, 2], + [Op.strictRight]: [1, 2], + [Op.noExtendLeft]: [1, 2], + [Op.noExtendRight]: [1, 2], + [Op.values]: [1, 2], + } as WhereOperators, + status: { + [Op.not]: false, // status NOT FALSE + }, + }, +}); + Sequelize.where( - Sequelize.cast(Sequelize.col('SOME_COL'), 'INTEGER'), { - [Op.lt]: Sequelize.literal('LIT'), - [Op.any]: Sequelize.literal('LIT'), - [Op.gte]: Sequelize.literal('LIT'), - [Op.lt]: Sequelize.literal('LIT'), - [Op.lte]: Sequelize.literal('LIT'), - [Op.ne]: Sequelize.literal('LIT'), - [Op.not]: Sequelize.literal('LIT'), - [Op.in]: Sequelize.literal('LIT'), - [Op.notIn]: Sequelize.literal('LIT'), - [Op.like]: Sequelize.literal('LIT'), - [Op.notLike]: Sequelize.literal('LIT'), - [Op.iLike]: Sequelize.literal('LIT'), - [Op.overlap]: Sequelize.literal('LIT'), - [Op.contains]: Sequelize.literal('LIT'), - [Op.contained]: Sequelize.literal('LIT'), - [Op.gt]: Sequelize.literal('LIT'), - [Op.notILike]: Sequelize.literal('LIT') - } + Sequelize.cast(Sequelize.col('SOME_COL'), 'INTEGER'), { + [Op.lt]: Sequelize.literal('LIT'), + [Op.any]: Sequelize.literal('LIT'), + [Op.gte]: Sequelize.literal('LIT'), + [Op.lt]: Sequelize.literal('LIT'), + [Op.lte]: Sequelize.literal('LIT'), + [Op.ne]: Sequelize.literal('LIT'), + [Op.not]: Sequelize.literal('LIT'), + [Op.in]: Sequelize.literal('LIT'), + [Op.notIn]: Sequelize.literal('LIT'), + [Op.like]: Sequelize.literal('LIT'), + [Op.notLike]: Sequelize.literal('LIT'), + [Op.iLike]: Sequelize.literal('LIT'), + [Op.overlap]: Sequelize.literal('LIT'), + [Op.contains]: Sequelize.literal('LIT'), + [Op.contained]: Sequelize.literal('LIT'), + [Op.gt]: Sequelize.literal('LIT'), + [Op.notILike]: Sequelize.literal('LIT'), + } ) + +Sequelize.where(Sequelize.col("ABS"), Op.is, null); + +Sequelize.where( + Sequelize.fn("ABS", Sequelize.col("age")), + Op.like, + Sequelize.fn("ABS", Sequelize.col("age")) +); + +Sequelize.where(Sequelize.col("ABS"), null); diff --git a/types/type-helpers/set-required.d.ts b/types/type-helpers/set-required.d.ts new file mode 100644 index 000000000000..db9109189b8a --- /dev/null +++ b/types/type-helpers/set-required.d.ts @@ -0,0 +1,16 @@ +/** + * Full credits to sindresorhus/type-fest + * + * https://github.com/sindresorhus/type-fest/blob/v0.8.1/source/set-required.d.ts + * + * Thank you! + */ +export type SetRequired = + // Pick just the keys that are not required from the base type. + Pick> & + // Pick the keys that should be required from the base type and make them required. + Required> extends + // If `InferredType` extends the previous, then for each key, use the inferred type key. + infer InferredType + ? {[KeyType in keyof InferredType]: InferredType[KeyType]} + : never; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000000..9ae66cb7e924 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,7913 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@azure/abort-controller@^1.0.0": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd" + integrity sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw== + dependencies: + tslib "^2.0.0" + +"@azure/core-auth@^1.1.4": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" + integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/ms-rest-azure-env@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz#8505873afd4a1227ec040894a64fdd736b4a101f" + integrity sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA== + +"@azure/ms-rest-js@^1.8.7": + version "1.11.2" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.11.2.tgz#e83d512b102c302425da5ff03a6d76adf2aa4ae6" + integrity sha512-2AyQ1IKmLGKW7DU3/x3TsTBzZLcbC9YRI+yuDPuXAQrv3zar340K9wsxU413kHFIDjkWNCo9T0w5VtwcyWxhbQ== + dependencies: + "@azure/core-auth" "^1.1.4" + axios "^0.21.1" + form-data "^2.3.2" + tough-cookie "^2.4.3" + tslib "^1.9.2" + tunnel "0.0.6" + uuid "^3.2.1" + xml2js "^0.4.19" + +"@azure/ms-rest-nodeauth@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-2.0.2.tgz#037e29540c5625eaec718b8fcc178dd7ad5dfb96" + integrity sha512-KmNNICOxt3EwViAJI3iu2VH8t8BQg5J2rSAyO4IUYLF9ZwlyYsP419pdvl4NBUhluAP2cgN7dfD2V6E6NOMZlQ== + dependencies: + "@azure/ms-rest-azure-env" "^1.1.2" + "@azure/ms-rest-js" "^1.8.7" + adal-node "^0.1.28" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== + dependencies: + "@babel/highlight" "^7.16.0" + +"@babel/compat-data@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.0.tgz#ea269d7f78deb3a7826c39a4048eecda541ebdaa" + integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew== + +"@babel/core@^7.7.5": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4" + integrity sha512-mYZEvshBRHGsIAiyH5PzCFTCfbWfoYbO/jcSdXQSUQu1/pW0xDZAUP7KEc32heqWTAfAHhV9j1vH8Sav7l+JNQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-compilation-targets" "^7.16.0" + "@babel/helper-module-transforms" "^7.16.0" + "@babel/helpers" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2" + integrity sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew== + dependencies: + "@babel/types" "^7.16.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-compilation-targets@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.0.tgz#01d615762e796c17952c29e3ede9d6de07d235a8" + integrity sha512-S7iaOT1SYlqK0sQaCi21RX4+13hmdmnxIEAnQUB/eh7GeAnRjOUgTYpLkUOiRXzD+yog1JxP0qyAQZ7ZxVxLVg== + dependencies: + "@babel/compat-data" "^7.16.0" + "@babel/helper-validator-option" "^7.14.5" + browserslist "^4.16.6" + semver "^6.3.0" + +"@babel/helper-function-name@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== + dependencies: + "@babel/helper-get-function-arity" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-get-function-arity@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-hoist-variables@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-member-expression-to-functions@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4" + integrity sha512-bsjlBFPuWT6IWhl28EdrQ+gTvSvj5tqVP5Xeftp07SEuz5pLnsXZuDkDD3Rfcxy0IsHmbZ+7B2/9SHzxO0T+sQ== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-imports@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-transforms@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.0.tgz#1c82a8dd4cb34577502ebd2909699b194c3e9bb5" + integrity sha512-My4cr9ATcaBbmaEa8M0dZNA74cfI6gitvUAskgDtAFmAqyFKDSHQo5YstxPbN+lzHl2D9l/YOEFqb2mtUh4gfA== + dependencies: + "@babel/helper-module-imports" "^7.16.0" + "@babel/helper-replace-supers" "^7.16.0" + "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-validator-identifier" "^7.15.7" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-optimise-call-expression@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" + integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-replace-supers@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.0.tgz#73055e8d3cf9bcba8ddb55cad93fedc860f68f17" + integrity sha512-TQxuQfSCdoha7cpRNJvfaYxxxzmbxXw/+6cS7V02eeDYyhxderSoMVALvwupA54/pZcOTtVeJ0xccp1nGWladA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.16.0" + "@babel/helper-optimise-call-expression" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-simple-access@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" + integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-split-export-declaration@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-validator-identifier@^7.15.7": + version "7.15.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== + +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helpers@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.0.tgz#875519c979c232f41adfbd43a3b0398c2e388183" + integrity sha512-dVRM0StFMdKlkt7cVcGgwD8UMaBfWJHl3A83Yfs8GQ3MO0LHIIIMvK7Fa0RGOGUQ10qikLaX6D7o5htcQWgTMQ== + dependencies: + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/highlight@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.0": + version "7.16.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.2.tgz#3723cd5c8d8773eef96ce57ea1d9b7faaccd12ac" + integrity sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw== + +"@babel/runtime@^7.11.2": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.0.tgz#e27b977f2e2088ba24748bf99b5e1dece64e4f0b" + integrity sha512-Nht8L0O8YCktmsDV6FqFue7vQLRx3Hb0B37lS5y0jDRqRxlBG4wIJHnf9/bgSE2UyipKFA01YtS+npRdTWBUyw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/traverse@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.0.tgz#965df6c6bfc0a958c1e739284d3c9fa4a6e3c45b" + integrity sha512-qQ84jIs1aRQxaGaxSysII9TuDaguZ5yVrEuC0BN2vcPlalwfLovVmCjbFDPECPXcYM/wLvNFfp8uDOliLxIoUQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + to-fast-properties "^2.0.0" + +"@commitlint/cli@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-11.0.0.tgz#698199bc52afed50aa28169237758fa14a67b5d3" + integrity sha512-YWZWg1DuqqO5Zjh7vUOeSX76vm0FFyz4y0cpGMFhrhvUi5unc4IVfCXZ6337R9zxuBtmveiRuuhQqnRRer+13g== + dependencies: + "@babel/runtime" "^7.11.2" + "@commitlint/format" "^11.0.0" + "@commitlint/lint" "^11.0.0" + "@commitlint/load" "^11.0.0" + "@commitlint/read" "^11.0.0" + chalk "4.1.0" + core-js "^3.6.1" + get-stdin "8.0.0" + lodash "^4.17.19" + resolve-from "5.0.0" + resolve-global "1.0.0" + yargs "^15.1.0" + +"@commitlint/config-angular-type-enum@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-angular-type-enum/-/config-angular-type-enum-11.0.0.tgz#7a7f6982e45d3696d72eb343a5d1dc23b2f003e0" + integrity sha512-dSyxdkU36aEgDUWBSiM5lsZ/h2K7uCyKf+A5Sf3+Z5JhcLD9GzTo5W+c8KgwTBdL39dkL7sN+EVgsXNjW99pJg== + +"@commitlint/config-angular@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-angular/-/config-angular-11.0.0.tgz#c1cc1dd902a4b9d2a5c072ff38e3ace9be7138e0" + integrity sha512-H8QSEOmfRsPW0Iehid5fY7NZ2HXmyKC6Q83MLFf9KRnmCcbgJtH+faECtqlvPntayO3CYbA4UenIerOaQ0vOAg== + dependencies: + "@commitlint/config-angular-type-enum" "^11.0.0" + +"@commitlint/ensure@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-11.0.0.tgz#3e796b968ab5b72bc6f8a6040076406306c987fb" + integrity sha512-/T4tjseSwlirKZdnx4AuICMNNlFvRyPQimbZIOYujp9DSO6XRtOy9NrmvWujwHsq9F5Wb80QWi4WMW6HMaENug== + dependencies: + "@commitlint/types" "^11.0.0" + lodash "^4.17.19" + +"@commitlint/execute-rule@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-11.0.0.tgz#3ed60ab7a33019e58d90e2d891b75d7df77b4b4d" + integrity sha512-g01p1g4BmYlZ2+tdotCavrMunnPFPhTzG1ZiLKTCYrooHRbmvqo42ZZn4QMStUEIcn+jfLb6BRZX3JzIwA1ezQ== + +"@commitlint/format@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-11.0.0.tgz#ac47b0b9ca46540c0082c721b290794e67bdc51b" + integrity sha512-bpBLWmG0wfZH/svzqD1hsGTpm79TKJWcf6EXZllh2J/LSSYKxGlv967lpw0hNojme0sZd4a/97R3qA2QHWWSLg== + dependencies: + "@commitlint/types" "^11.0.0" + chalk "^4.0.0" + +"@commitlint/is-ignored@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-11.0.0.tgz#7b803eda56276dbe7fec51eb1510676198468f39" + integrity sha512-VLHOUBN+sOlkYC4tGuzE41yNPO2w09sQnOpfS+pSPnBFkNUUHawEuA44PLHtDvQgVuYrMAmSWFQpWabMoP5/Xg== + dependencies: + "@commitlint/types" "^11.0.0" + semver "7.3.2" + +"@commitlint/lint@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-11.0.0.tgz#01e062cd1b0e7c3d756aa2c246462e0b6a3348a4" + integrity sha512-Q8IIqGIHfwKr8ecVZyYh6NtXFmKw4YSEWEr2GJTB/fTZXgaOGtGFZDWOesCZllQ63f1s/oWJYtVv5RAEuwN8BQ== + dependencies: + "@commitlint/is-ignored" "^11.0.0" + "@commitlint/parse" "^11.0.0" + "@commitlint/rules" "^11.0.0" + "@commitlint/types" "^11.0.0" + +"@commitlint/load@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-11.0.0.tgz#f736562f0ffa7e773f8808fea93319042ee18211" + integrity sha512-t5ZBrtgvgCwPfxmG811FCp39/o3SJ7L+SNsxFL92OR4WQxPcu6c8taD0CG2lzOHGuRyuMxZ7ps3EbngT2WpiCg== + dependencies: + "@commitlint/execute-rule" "^11.0.0" + "@commitlint/resolve-extends" "^11.0.0" + "@commitlint/types" "^11.0.0" + chalk "4.1.0" + cosmiconfig "^7.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + +"@commitlint/message@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-11.0.0.tgz#83554c3cbbc884fd07b473593bc3e94bcaa3ee05" + integrity sha512-01ObK/18JL7PEIE3dBRtoMmU6S3ecPYDTQWWhcO+ErA3Ai0KDYqV5VWWEijdcVafNpdeUNrEMigRkxXHQLbyJA== + +"@commitlint/parse@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-11.0.0.tgz#d18b08cf67c35d02115207d7009306a2e8e7c901" + integrity sha512-DekKQAIYWAXIcyAZ6/PDBJylWJ1BROTfDIzr9PMVxZRxBPc1gW2TG8fLgjZfBP5mc0cuthPkVi91KQQKGri/7A== + dependencies: + conventional-changelog-angular "^5.0.0" + conventional-commits-parser "^3.0.0" + +"@commitlint/read@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-11.0.0.tgz#f24240548c63587bba139fa5a364cab926077016" + integrity sha512-37V0V91GSv0aDzMzJioKpCoZw6l0shk7+tRG8RkW1GfZzUIytdg3XqJmM+IaIYpaop0m6BbZtfq+idzUwJnw7g== + dependencies: + "@commitlint/top-level" "^11.0.0" + fs-extra "^9.0.0" + git-raw-commits "^2.0.0" + +"@commitlint/resolve-extends@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-11.0.0.tgz#158ecbe27d4a2a51d426111a01478e216fbb1036" + integrity sha512-WinU6Uv6L7HDGLqn/To13KM1CWvZ09VHZqryqxXa1OY+EvJkfU734CwnOEeNlSCK7FVLrB4kmodLJtL1dkEpXw== + dependencies: + import-fresh "^3.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + +"@commitlint/rules@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-11.0.0.tgz#bdb310cc6fc55c9f8d7d917a22b69055c535c375" + integrity sha512-2hD9y9Ep5ZfoNxDDPkQadd2jJeocrwC4vJ98I0g8pNYn/W8hS9+/FuNpolREHN8PhmexXbkjrwyQrWbuC0DVaA== + dependencies: + "@commitlint/ensure" "^11.0.0" + "@commitlint/message" "^11.0.0" + "@commitlint/to-lines" "^11.0.0" + "@commitlint/types" "^11.0.0" + +"@commitlint/to-lines@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/to-lines/-/to-lines-11.0.0.tgz#86dea151c10eea41e39ea96fa4de07839258a7fe" + integrity sha512-TIDTB0Y23jlCNubDROUVokbJk6860idYB5cZkLWcRS9tlb6YSoeLn1NLafPlrhhkkkZzTYnlKYzCVrBNVes1iw== + +"@commitlint/top-level@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/top-level/-/top-level-11.0.0.tgz#bb2d1b6e5ed3be56874633b59e1f7de118c32783" + integrity sha512-O0nFU8o+Ws+py5pfMQIuyxOtfR/kwtr5ybqTvR+C2lUPer2x6lnQU+OnfD7hPM+A+COIUZWx10mYQvkR3MmtAA== + dependencies: + find-up "^5.0.0" + +"@commitlint/types@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-11.0.0.tgz#719cf05fcc1abb6533610a2e0f5dd1e61eac14fe" + integrity sha512-VoNqai1vR5anRF5Tuh/+SWDFk7xi7oMwHrHrbm1BprYXjB2RJsWLhUrStMssDxEl5lW/z3EUdg8RvH/IUBccSQ== + +"@gar/promisify@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== + +"@isaacs/string-locale-compare@*", "@isaacs/string-locale-compare@^1.0.1": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@js-joda/core@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-2.0.0.tgz#e9a351ee6feb91262770e2a3d085aa0219ad6afb" + integrity sha512-OWm/xa9O9e4ugzNHoRT3IsXZZYfaV6Ia1aRwctOmCQ2GYWMnhKBzMC1WomqCh/oGxEZKNtPy5xv5//VIAOgMqw== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/arborist@*", "@npmcli/arborist@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@npmcli/arborist/-/arborist-4.0.4.tgz#a532a7cc430ccbd87c0595a8828f9614f29d2dac" + integrity sha512-5hRkiHF9zu62z6a7CJqhVG5CFUVnbYqvrrcxxEmhxFgyH2ovICyULOrj7nF4VBlfzp7OPu/rveV2ts9iYrn74g== + dependencies: + "@isaacs/string-locale-compare" "^1.0.1" + "@npmcli/installed-package-contents" "^1.0.7" + "@npmcli/map-workspaces" "^2.0.0" + "@npmcli/metavuln-calculator" "^2.0.0" + "@npmcli/move-file" "^1.1.0" + "@npmcli/name-from-folder" "^1.0.1" + "@npmcli/node-gyp" "^1.0.1" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^2.0.0" + bin-links "^2.3.0" + cacache "^15.0.3" + common-ancestor-path "^1.0.1" + json-parse-even-better-errors "^2.3.1" + json-stringify-nice "^1.1.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.0" + npm-registry-fetch "^11.0.0" + pacote "^12.0.0" + parse-conflict-json "^1.1.1" + proc-log "^1.0.0" + promise-all-reject-late "^1.0.0" + promise-call-limit "^1.0.1" + read-package-json-fast "^2.0.2" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + treeverse "^1.0.4" + walk-up-path "^1.0.0" + +"@npmcli/ci-detect@*", "@npmcli/ci-detect@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@npmcli/ci-detect/-/ci-detect-1.4.0.tgz#18478bbaa900c37bfbd8a2006a6262c62e8b0fe1" + integrity sha512-3BGrt6FLjqM6br5AhWRKTr3u5GIVkjRYeAFrMp3HjnfICrg4xOrVRwFavKT6tsp++bq5dluL5t8ME/Nha/6c1Q== + +"@npmcli/config@*": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@npmcli/config/-/config-2.3.1.tgz#41d80ce272831461b5cb158afa110525d4be0fed" + integrity sha512-F/8R/Zqun8682TgaCILUNoaVfd1LVaYZ/jcVt9KWzfKpzcPus1zEApAl54PqVqVJbNq6f01QTDQHD6L/n56BXw== + dependencies: + ini "^2.0.0" + mkdirp-infer-owner "^2.0.0" + nopt "^5.0.0" + semver "^7.3.4" + walk-up-path "^1.0.0" + +"@npmcli/disparity-colors@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/disparity-colors/-/disparity-colors-1.0.1.tgz#b23c864c9658f9f0318d5aa6d17986619989535c" + integrity sha512-kQ1aCTTU45mPXN+pdAaRxlxr3OunkyztjbbxDY/aIcPS5CnCUrx+1+NvA6pTcYR7wmLZe37+Mi5v3nfbwPxq3A== + dependencies: + ansi-styles "^4.3.0" + +"@npmcli/fs@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" + integrity sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/git@^2.0.7", "@npmcli/git@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" + integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== + dependencies: + "@npmcli/promise-spawn" "^1.3.2" + lru-cache "^6.0.0" + mkdirp "^1.0.4" + npm-pick-manifest "^6.1.1" + promise-inflight "^1.0.1" + promise-retry "^2.0.1" + semver "^7.3.5" + which "^2.0.2" + +"@npmcli/installed-package-contents@^1.0.6", "@npmcli/installed-package-contents@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== + dependencies: + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +"@npmcli/map-workspaces@*", "@npmcli/map-workspaces@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz#e342efbbdd0dad1bba5d7723b674ca668bf8ac5a" + integrity sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g== + dependencies: + "@npmcli/name-from-folder" "^1.0.1" + glob "^7.1.6" + minimatch "^3.0.4" + read-package-json-fast "^2.0.1" + +"@npmcli/metavuln-calculator@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/metavuln-calculator/-/metavuln-calculator-2.0.0.tgz#70937b8b5a5cad5c588c8a7b38c4a8bd6f62c84c" + integrity sha512-VVW+JhWCKRwCTE+0xvD6p3uV4WpqocNYYtzyvenqL/u1Q3Xx6fGTJ+6UoIoii07fbuEO9U3IIyuGY0CYHDv1sg== + dependencies: + cacache "^15.0.5" + json-parse-even-better-errors "^2.3.1" + pacote "^12.0.0" + semver "^7.3.2" + +"@npmcli/move-file@^1.0.1", "@npmcli/move-file@^1.1.0": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@npmcli/name-from-folder@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== + +"@npmcli/node-gyp@^1.0.1", "@npmcli/node-gyp@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz#a912e637418ffc5f2db375e93b85837691a43a33" + integrity sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA== + +"@npmcli/package-json@*", "@npmcli/package-json@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/package-json/-/package-json-1.0.1.tgz#1ed42f00febe5293c3502fd0ef785647355f6e89" + integrity sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg== + dependencies: + json-parse-even-better-errors "^2.3.1" + +"@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" + integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== + dependencies: + infer-owner "^1.0.4" + +"@npmcli/run-script@*", "@npmcli/run-script@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-2.0.0.tgz#9949c0cab415b17aaac279646db4f027d6f1e743" + integrity sha512-fSan/Pu11xS/TdaTpTB0MRn9guwGU8dye+x56mEVgBEd/QsybBbYcAL0phPXi8SGWFEChkQd6M9qL4y6VOpFig== + dependencies: + "@npmcli/node-gyp" "^1.0.2" + "@npmcli/promise-spawn" "^1.3.2" + node-gyp "^8.2.0" + read-package-json-fast "^2.0.1" + +"@npmcli/run-script@^1.8.2": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@npmcli/run-script/-/run-script-1.8.6.tgz#18314802a6660b0d4baa4c3afe7f1ad39d8c28b7" + integrity sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g== + dependencies: + "@npmcli/node-gyp" "^1.0.2" + "@npmcli/promise-spawn" "^1.3.2" + node-gyp "^7.1.0" + read-package-json-fast "^2.0.1" + +"@octokit/auth-token@^2.4.4": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" + integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/core@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" + integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== + dependencies: + "@octokit/auth-token" "^2.4.4" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^5.6.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== + dependencies: + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^4.5.8": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" + integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== + dependencies: + "@octokit/request" "^5.6.0" + "@octokit/types" "^6.0.3" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-11.2.0.tgz#b38d7fc3736d52a1e96b230c1ccd4a58a2f400a6" + integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA== + +"@octokit/plugin-paginate-rest@^2.16.8": + version "2.17.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz#32e9c7cab2a374421d3d0de239102287d791bce7" + integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw== + dependencies: + "@octokit/types" "^6.34.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^5.12.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz#8c46109021a3412233f6f50d28786f8e552427ba" + integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA== + dependencies: + "@octokit/types" "^6.34.0" + deprecation "^2.3.1" + +"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== + dependencies: + "@octokit/types" "^6.0.3" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^5.6.0": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.2.tgz#1aa74d5da7b9e04ac60ef232edd9a7438dcf32d8" + integrity sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + universal-user-agent "^6.0.0" + +"@octokit/rest@^18.0.0": + version "18.12.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" + integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== + dependencies: + "@octokit/core" "^3.5.1" + "@octokit/plugin-paginate-rest" "^2.16.8" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^5.12.0" + +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0": + version "6.34.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.34.0.tgz#c6021333334d1ecfb5d370a8798162ddf1ae8218" + integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== + dependencies: + "@octokit/openapi-types" "^11.2.0" + +"@semantic-release/commit-analyzer@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz#5d2a37cd5a3312da0e3ac05b1ca348bf60b90bca" + integrity sha512-5bJma/oB7B4MtwUkZC2Bf7O1MHfi4gWe4mA+MIQ3lsEV0b422Bvl1z5HRpplDnMLHH3EXMoRdEng6Ds5wUqA3A== + dependencies: + conventional-changelog-angular "^5.0.0" + conventional-commits-filter "^2.0.0" + conventional-commits-parser "^3.0.7" + debug "^4.0.0" + import-from "^3.0.0" + lodash "^4.17.4" + micromatch "^4.0.2" + +"@semantic-release/error@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@semantic-release/error/-/error-2.2.0.tgz#ee9d5a09c9969eade1ec864776aeda5c5cddbbf0" + integrity sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg== + +"@semantic-release/github@^7.0.0": + version "7.2.3" + resolved "https://registry.yarnpkg.com/@semantic-release/github/-/github-7.2.3.tgz#20a83abd42dca43d97f03553de970eac72856c85" + integrity sha512-lWjIVDLal+EQBzy697ayUNN8MoBpp+jYIyW2luOdqn5XBH4d9bQGfTnjuLyzARZBHejqh932HVjiH/j4+R7VHw== + dependencies: + "@octokit/rest" "^18.0.0" + "@semantic-release/error" "^2.2.0" + aggregate-error "^3.0.0" + bottleneck "^2.18.1" + debug "^4.0.0" + dir-glob "^3.0.0" + fs-extra "^10.0.0" + globby "^11.0.0" + http-proxy-agent "^4.0.0" + https-proxy-agent "^5.0.0" + issue-parser "^6.0.0" + lodash "^4.17.4" + mime "^2.4.3" + p-filter "^2.0.0" + p-retry "^4.0.0" + url-join "^4.0.0" + +"@semantic-release/npm@^7.0.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@semantic-release/npm/-/npm-7.1.3.tgz#1d64c41ff31b100299029c766ecc4d1f03aa5f5b" + integrity sha512-x52kQ/jR09WjuWdaTEHgQCvZYMOTx68WnS+TZ4fya5ZAJw4oRtJETtrvUw10FdfM28d/keInQdc66R1Gw5+OEQ== + dependencies: + "@semantic-release/error" "^2.2.0" + aggregate-error "^3.0.0" + execa "^5.0.0" + fs-extra "^10.0.0" + lodash "^4.17.15" + nerf-dart "^1.0.0" + normalize-url "^6.0.0" + npm "^7.0.0" + rc "^1.2.8" + read-pkg "^5.0.0" + registry-auth-token "^4.0.0" + semver "^7.1.2" + tempy "^1.0.0" + +"@semantic-release/release-notes-generator@^9.0.0": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.3.tgz#d541221c6512e9619f25ba8079527e34288e6904" + integrity sha512-hMZyddr0u99OvM2SxVOIelHzly+PP3sYtJ8XOLHdMp8mrluN5/lpeTnIO27oeCYdupY/ndoGfvrqDjHqkSyhVg== + dependencies: + conventional-changelog-angular "^5.0.0" + conventional-changelog-writer "^4.0.0" + conventional-commits-filter "^2.0.0" + conventional-commits-parser "^3.0.0" + debug "^4.0.0" + get-stream "^6.0.0" + import-from "^3.0.0" + into-stream "^6.0.0" + lodash "^4.17.4" + read-pkg-up "^7.0.0" + +"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@sinonjs/samsam@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.1.tgz#375a45fe6ed4e92fca2fb920e007c48232a6507f" + integrity sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@types/geojson@^7946.0.7": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + +"@types/minimist@^1.2.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + +"@types/node@*": + version "16.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" + integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + +"@types/node@^12.12.42": + version "12.20.36" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.36.tgz#5bd54d2383e714fc4d2c258107ee70c5bad86d0c" + integrity sha512-+5haRZ9uzI7rYqzDznXgkuacqb6LJhAti8mzZKWxIXn/WEtvB+GHVJ7AuMwcN1HMvXOSJcrvA6PPoYHYOYYebA== + +"@types/node@^14.14.28": + version "14.17.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.32.tgz#2ca61c9ef8c77f6fa1733be9e623ceb0d372ad96" + integrity sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ== + +"@types/node@^8.0.47": + version "8.10.66" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" + integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/retry@^0.12.0": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" + integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== + +"@types/validator@^13.1.4": + version "13.6.6" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.6.tgz#6e6e2d086148db5ae14851614971b715670cbd52" + integrity sha512-+qogUELb4gMhrMjSh/seKmGVvN+uQLfyqJAqYRWqVHsvBsUO2xDBCL8CJ/ZSukbd8vXaoYbpIssAmfLEzzBHEw== + +JSONStream@^1.0.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +abab@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= + +abbrev@*, abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +acorn-globals@^1.0.4: + version "1.0.9" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" + integrity sha1-VbtemGkVB7dFedBRNBMhfDgMVM8= + dependencies: + acorn "^2.1.0" + +acorn-jsx@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^2.1.0, acorn@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" + integrity sha1-q259nYhqrKiwhbwzEreaGYQz8Oc= + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.0.4: + version "8.5.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + +adal-node@^0.1.28: + version "0.1.28" + resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485" + integrity sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU= + dependencies: + "@types/node" "^8.0.47" + async ">=0.6.0" + date-utils "*" + jws "3.x.x" + request ">= 2.52.0" + underscore ">= 1.3.1" + uuid "^3.1.0" + xmldom ">= 0.1.x" + xpath.js "~1.1.0" + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.1.4.tgz#d928028a4862cb11718e55227872e842a44c945b" + integrity sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-colors@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansicolors@*, ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= + +ansistyles@*: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" + integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk= + +any-promise@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + +anymatch@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + dependencies: + buffer-equal "^1.0.0" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +"aproba@^1.0.3 || ^2.0.0", aproba@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archy@*, archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@~1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" + integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +argv-formatter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/argv-formatter/-/argv-formatter-1.0.0.tgz#a0ca0cbc29a5b73e836eebe1cbf6c5e0e4eb82f9" + integrity sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk= + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async@>=0.6.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.2.tgz#2eb7671034bb2194d45d30e31e24ec7e7f9670cd" + integrity sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +axios@>=0.21.2: + version "0.24.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== + dependencies: + follow-redirects "^1.14.4" + +axios@^0.21.1: + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== + dependencies: + follow-redirects "^1.14.0" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-generator@6.11.4: + version "6.11.4" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.11.4.tgz#14f6933abb20c62666d27e3b7b9f5b9dc0712a9a" + integrity sha1-FPaTOrsgxiZm0n47e59bncBxKpo= + dependencies: + babel-messages "^6.8.0" + babel-runtime "^6.9.0" + babel-types "^6.10.2" + detect-indent "^3.0.1" + lodash "^4.2.0" + source-map "^0.5.0" + +babel-generator@6.26.1: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-messages@^6.23.0, babel-messages@^6.8.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= + dependencies: + babel-runtime "^6.22.0" + +babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-traverse@6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.10.2, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@6.18.0, babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + +bin-links@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-2.3.0.tgz#1ff241c86d2c29b24ae52f49544db5d78a4eb967" + integrity sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA== + dependencies: + cmd-shim "^4.0.1" + mkdirp-infer-owner "^2.0.0" + npm-normalize-package-bin "^1.0.0" + read-cmd-shim "^2.0.0" + rimraf "^3.0.0" + write-file-atomic "^3.0.3" + +binary-extensions@^2.0.0, binary-extensions@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bl@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" + integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== + dependencies: + readable-stream "^3.0.1" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +bottleneck@^2.18.1: + version "2.19.5" + resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" + integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +browserslist@^4.16.6: + version "4.17.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.6.tgz#c76be33e7786b497f66cad25a73756c8b938985d" + integrity sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw== + dependencies: + caniuse-lite "^1.0.30001274" + electron-to-chromium "^1.3.886" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= + +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + +builtins@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" + integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= + +cacache@*, cacache@^15.0.3, cacache@^15.0.5, cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001274: + version "1.0.30001278" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001278.tgz#51cafc858df77d966b17f59b5839250b24417fff" + integrity sha512-mpF9KeH8u5cMoEmIic/cr7PNS+F5LWBk0t2ekGT60lFf0Wq+n9LspAj0g3P+o7DQhD3sUdlMln4YFAWhFYn9jg== + +cardinal@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" + integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= + dependencies: + ansicolors "~0.3.2" + redeyed "~2.1.0" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chai-as-promised@^7.x: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + +chai-datetime@^1.6.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/chai-datetime/-/chai-datetime-1.8.0.tgz#95a1ff58130f60f16f6d882ec5c014e63aa6d75f" + integrity sha512-qBG84K8oQNz8LWacuzmCBfdoeG2UBFfbGKTSQj6lS+sjuzGUdBvjJxfZfGA4zDAMiCSqApKcuqSLO0lQQ25cHw== + dependencies: + chai ">1.9.0" + +chai@>1.9.0, chai@^4.x: + version "4.3.4" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" + integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.1" + type-detect "^4.0.5" + +chalk@*, chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +cheerio-select@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" + integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== + dependencies: + css-select "^4.1.3" + css-what "^5.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.7.0" + +cheerio@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.20.0.tgz#5c710f2bab95653272842ba01c6ea61b3545ec35" + integrity sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "~3.8.1" + lodash "^4.1.0" + optionalDependencies: + jsdom "^7.0.2" + +cheerio@0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" + integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash.assignin "^4.0.9" + lodash.bind "^4.1.4" + lodash.defaults "^4.0.1" + lodash.filter "^4.4.0" + lodash.flatten "^4.2.0" + lodash.foreach "^4.3.0" + lodash.map "^4.4.0" + lodash.merge "^4.4.0" + lodash.pick "^4.2.1" + lodash.reduce "^4.4.0" + lodash.reject "^4.4.0" + lodash.some "^4.4.0" + +cheerio@1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +cheerio@^1.0.0-rc.3: + version "1.0.0-rc.10" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + +chokidar@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + +chownr@*, chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chownr@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cidr-regex@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-3.1.1.tgz#ba1972c57c66f61875f18fd7dd487469770b571d" + integrity sha512-RBqYd32aDwbCMFJRL6wHOlDNYJsPNTt8vC82ErHF5vKt8QQzxm1FrkW8s/R5pVrXMf17sba09Uoy91PKiddAsw== + dependencies: + ip-regex "^4.1.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-columns@*: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646" + integrity sha512-XW2Vg+w+L9on9wtwKpyzluIPCWXjaBahI7mTcYjx+BVIYD9c3yqcv/yKC7CmdCZat4rq2yiE1UMSJC5ivKfMtQ== + dependencies: + string-width "^4.2.3" + strip-ansi "^6.0.1" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@*, cli-table3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== + dependencies: + object-assign "^4.1.0" + string-width "^4.2.0" + optionalDependencies: + colors "^1.1.2" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +clone@^2.1.1, clone@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +cmd-shim@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-4.1.0.tgz#b3a904a6743e9fede4148c6f3800bf2a08135bdd" + integrity sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw== + dependencies: + mkdirp-infer-owner "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-logger@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/color-logger/-/color-logger-0.0.3.tgz#d9b22dd1d973e166b18bf313f9f481bba4df2018" + integrity sha1-2bIt0dlz4Waxi/MT+fSBu6TfIBg= + +color-logger@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/color-logger/-/color-logger-0.0.6.tgz#e56245ef29822657110c7cb75a9cd786cb69ed1b" + integrity sha1-5WJF7ymCJlcRDHy3WpzXhstp7Rs= + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +columnify@*: + version "1.5.4" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= + dependencies: + strip-ansi "^3.0.0" + wcwidth "^1.0.0" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^6.2.0, commander@~6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +comment-parser@^0.7.2: + version "0.7.6" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.7.6.tgz#0e743a53c8e646c899a1323db31f6cd337b10f12" + integrity sha512-GKNxVA7/iuTnAqGADlTWX4tkhzxZKXp5fLJqKTlQLHkE65XDUKutZ3BHaJC5IGcper2tT3QRD1xr4o3jNpgXXg== + +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +conventional-changelog-angular@^5.0.0: + version "5.0.13" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" + integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== + dependencies: + compare-func "^2.0.0" + q "^1.5.1" + +conventional-changelog-writer@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" + integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== + dependencies: + compare-func "^2.0.0" + conventional-commits-filter "^2.0.7" + dateformat "^3.0.0" + handlebars "^4.7.6" + json-stringify-safe "^5.0.1" + lodash "^4.17.15" + meow "^8.0.0" + semver "^6.0.0" + split "^1.0.0" + through2 "^4.0.0" + +conventional-commits-filter@^2.0.0, conventional-commits-filter@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== + dependencies: + lodash.ismatch "^4.4.0" + modify-values "^1.0.0" + +conventional-commits-parser@^3.0.0, conventional-commits-parser@^3.0.7: + version "3.2.3" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.3.tgz#fc43704698239451e3ef35fd1d8ed644f46bd86e" + integrity sha512-YyRDR7On9H07ICFpRm/igcdjIqebXbvf4Cff+Pf0BrBys1i1EOzx9iFXNlAbdrLAR8jf7bkUYkDAr8pEy0q4Pw== + dependencies: + JSONStream "^1.0.4" + is-text-path "^1.0.1" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +convert-source-map@^1.5.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +core-js@^3.6.1: + version "3.19.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.1.tgz#f6f173cae23e73a7d88fa23b6e9da329276c6641" + integrity sha512-Tnc7E9iKd/b/ff7GFbhwPVzJzPztGrChB8X8GLqoYGdEOG8IpLnK1xPyo3ZoO3HsK6TodJS58VGPOxA+hLHQMg== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-env@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-select@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" + integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== + dependencies: + boolbase "^1.0.0" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" + nth-check "^2.0.0" + +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + +css-what@^5.0.0, css-what@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +"cssstyle@>= 0.2.29 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= + dependencies: + cssom "0.3.x" + +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +date-utils@*: + version "1.2.21" + resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64" + integrity sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q= + +dateformat@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== + +debug@3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debuglog@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + +decamelize-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.1.0, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +deep-extend@^0.6.0, deep-extend@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +default-require-extensions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" + integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== + dependencies: + strip-bom "^4.0.0" + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +del@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" + integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + +delay@^4.3.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/delay/-/delay-4.4.1.tgz#6e02d02946a1b6ab98b39262ced965acba2ac4d1" + integrity sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +denque@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" + integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== + +denque@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a" + integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== + +depd@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +depd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +detect-indent@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-3.0.1.tgz#9dc5e5ddbceef8325764b9451b02bc6d54084f75" + integrity sha1-ncXl3bzu+DJXZLlFGwK8bVQIT3U= + dependencies: + get-stdin "^4.0.1" + minimist "^1.1.0" + repeating "^1.1.0" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= + dependencies: + repeating "^2.0.0" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +dezalgo@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= + dependencies: + asap "^2.0.0" + wrappy "1" + +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +diff@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +diff@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +dir-glob@^3.0.0, dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@^1.0.1, dom-serializer@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-serializer@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domhandler@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + integrity sha1-LeWaCCLVAn+r/28DLCsloqir5zg= + dependencies: + domelementtype "1" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" + integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== + dependencies: + domelementtype "^2.2.0" + +domutils@1.5, domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-prop@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +dottie@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.2.tgz#cc91c0726ce3a054ebf11c55fbc92a7f266dd154" + integrity sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg== + +duplexer2@~0.1.0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + +duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +electron-to-chromium@^1.3.886: + version "1.3.890" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.890.tgz#e7143b659f73dc4d0512d1ae4baeb0fb9e7bc835" + integrity sha512-VWlVXSkv0cA/OOehrEyqjUTHwV8YXCPTfPvbtoeU2aHR21vI4Ejh5aC4AxUwOmbLbBgb6Gd3URZahoCxtBqCYQ== + +emitter-listener@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" + integrity sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY= + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" + integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + +env-ci@^5.0.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-5.4.1.tgz#814387ddd6857b37472ef612361f34d720c29a18" + integrity sha512-xyuCtyFZLpnW5aH0JstETKTSMwHHQX4m42juzEZzvbUCJX7RiPVlhASKM0f/cJ4vvI/+txMkZ7F5To6dCdPYhg== + dependencies: + execa "^5.0.0" + fromentries "^1.3.2" + java-properties "^1.0.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.6.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esdoc-accessor-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-accessor-plugin/-/esdoc-accessor-plugin-1.0.0.tgz#791ba4872e6c403515ce749b1348d6f0293ad9eb" + integrity sha1-eRukhy5sQDUVznSbE0jW8Ck62es= + +esdoc-brand-plugin@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esdoc-brand-plugin/-/esdoc-brand-plugin-1.0.1.tgz#7c0e1ae90e84c30b2d3369d3a6449f9dc9f8d511" + integrity sha512-Yv9j3M7qk5PSLmSeD6MbPsfIsEf8K43EdH8qZpE/GZwnJCRVmDPrZJ1cLDj/fPu6P35YqgcEaJK4E2NL/CKA7g== + dependencies: + cheerio "0.22.0" + +esdoc-coverage-plugin@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/esdoc-coverage-plugin/-/esdoc-coverage-plugin-1.1.0.tgz#3869869cd7f87891f972625787695a299aece45c" + integrity sha1-OGmGnNf4eJH5cmJXh2laKZrs5Fw= + +esdoc-ecmascript-proposal-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-ecmascript-proposal-plugin/-/esdoc-ecmascript-proposal-plugin-1.0.0.tgz#390dc5656ba8a2830e39dba3570d79138df2ffd9" + integrity sha1-OQ3FZWuoooMOOdujVw15E43y/9k= + +esdoc-external-ecmascript-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-external-ecmascript-plugin/-/esdoc-external-ecmascript-plugin-1.0.0.tgz#78f565d4a0c5185ac63152614dce1fe1a86688db" + integrity sha1-ePVl1KDFGFrGMVJhTc4f4ahmiNs= + dependencies: + fs-extra "1.0.0" + +esdoc-inject-style-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-inject-style-plugin/-/esdoc-inject-style-plugin-1.0.0.tgz#a13597368bb9fb89c365e066495caf97a4decbb1" + integrity sha1-oTWXNou5+4nDZeBmSVyvl6Tey7E= + dependencies: + cheerio "0.22.0" + fs-extra "1.0.0" + +esdoc-integrate-manual-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-integrate-manual-plugin/-/esdoc-integrate-manual-plugin-1.0.0.tgz#1854a6aa1c081035d7c8c51e3bdd4fb65aa4711c" + integrity sha1-GFSmqhwIEDXXyMUeO91PtlqkcRw= + +esdoc-integrate-test-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-integrate-test-plugin/-/esdoc-integrate-test-plugin-1.0.0.tgz#e2d0d00090f7f0c35e5d2f2c033327a79e53e409" + integrity sha1-4tDQAJD38MNeXS8sAzMnp55T5Ak= + +esdoc-lint-plugin@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/esdoc-lint-plugin/-/esdoc-lint-plugin-1.0.2.tgz#4962930c6dc5b25d80cf6eff1b0f3c24609077f7" + integrity sha512-24AYqD2WbZI9We02I7/6dzAa7yUliRTFUaJCZAcYJMQicJT5gUrNFVaI8XmWEN/mhF3szIn1uZBNWeLul4CmNw== + +esdoc-publish-html-plugin@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/esdoc-publish-html-plugin/-/esdoc-publish-html-plugin-1.1.2.tgz#bdece7bc7a0a3e419933503252db7a6772879dab" + integrity sha512-hG1fZmTcEp3P/Hv/qKiMdG1qSp8MjnVZMMkxL5P5ry7I2sX0HQ4P9lt2lms+90Lt0r340HHhSuVx107UL7dphg== + dependencies: + babel-generator "6.11.4" + cheerio "0.22.0" + escape-html "1.0.3" + fs-extra "1.0.0" + ice-cap "0.0.4" + marked "0.3.19" + taffydb "2.7.2" + +esdoc-standard-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-standard-plugin/-/esdoc-standard-plugin-1.0.0.tgz#661201cac7ef868924902446fdac1527253c5d4d" + integrity sha1-ZhIBysfvhokkkCRG/awVJyU8XU0= + dependencies: + esdoc-accessor-plugin "^1.0.0" + esdoc-brand-plugin "^1.0.0" + esdoc-coverage-plugin "^1.0.0" + esdoc-external-ecmascript-plugin "^1.0.0" + esdoc-integrate-manual-plugin "^1.0.0" + esdoc-integrate-test-plugin "^1.0.0" + esdoc-lint-plugin "^1.0.0" + esdoc-publish-html-plugin "^1.0.0" + esdoc-type-inference-plugin "^1.0.0" + esdoc-undocumented-identifier-plugin "^1.0.0" + esdoc-unexported-identifier-plugin "^1.0.0" + +esdoc-type-inference-plugin@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/esdoc-type-inference-plugin/-/esdoc-type-inference-plugin-1.0.2.tgz#916e3f756de1d81d9c0dbe1c008e8dafd322cfaf" + integrity sha512-tMIcEHNe1uhUGA7lT1UTWc9hs2dzthnTgmqXpmeUhurk7fL2tinvoH+IVvG/sLROzwOGZQS9zW/F9KWnpMzLIQ== + +esdoc-undocumented-identifier-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-undocumented-identifier-plugin/-/esdoc-undocumented-identifier-plugin-1.0.0.tgz#82e05d371c32d12871140f1d5c81ec99fd9cc2c8" + integrity sha1-guBdNxwy0ShxFA8dXIHsmf2cwsg= + +esdoc-unexported-identifier-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esdoc-unexported-identifier-plugin/-/esdoc-unexported-identifier-plugin-1.0.0.tgz#1f9874c6a7c2bebf9ad397c3ceb75c9c69dabab1" + integrity sha1-H5h0xqfCvr+a05fDzrdcnGnaurE= + +esdoc@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/esdoc/-/esdoc-1.1.0.tgz#07d40ebf791764cd537929c29111e20a857624f3" + integrity sha512-vsUcp52XJkOWg9m1vDYplGZN2iDzvmjDL5M/Mp8qkoDG3p2s0yIQCIjKR5wfPBaM3eV14a6zhQNYiNTCVzPnxA== + dependencies: + babel-generator "6.26.1" + babel-traverse "6.26.0" + babylon "6.18.0" + cheerio "1.0.0-rc.2" + color-logger "0.0.6" + escape-html "1.0.3" + fs-extra "5.0.0" + ice-cap "0.0.4" + marked "0.3.19" + minimist "1.2.0" + taffydb "2.7.3" + +eslint-plugin-jsdoc@^20.4.0: + version "20.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-20.4.0.tgz#ea6725c3d1e68cd1ac0e633d935aa7e932b624c2" + integrity sha512-c/fnEpwWLFeQn+A7pb1qLOdyhovpqGCWCeUv1wtzFNL5G+xedl9wHUnXtp3b1sGHolVimi9DxKVTuhK/snXoOw== + dependencies: + comment-parser "^0.7.2" + debug "^4.1.1" + jsdoctypeparser "^6.1.0" + lodash "^4.17.15" + object.entries-ponyfill "^1.0.1" + regextras "^0.7.0" + semver "^6.3.0" + spdx-expression-parse "^3.0.0" + +eslint-plugin-mocha@^6.2.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-6.3.0.tgz#72bfd06a5c4323e17e30ef41cd726030e8cdb8fd" + integrity sha512-Cd2roo8caAyG21oKaaNTj7cqeYRWW1I2B5SfpKRp0Ip1gkfwoR1Ow0IGlPWnNjzywdF4n+kHL8/9vM6zCJUxdg== + dependencies: + eslint-utils "^2.0.0" + ramda "^0.27.0" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +expect-type@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.11.0.tgz#bce1a3e283f0334eedb39699b57dd27be7009cc1" + integrity sha512-hkObxepDKhTYloH/UZoxYTT2uUzdhvDEwAi0oqdk29XEkHF8p+5ZRpX/BZES2PtGN9YgyEqutIjXfnL9iMflMw== + +extend@^3.0.0, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastest-levenshtein@*: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@3.0.0, find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-versions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" + integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== + dependencies: + semver-regex "^3.1.2" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flat@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" + integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== + dependencies: + is-buffer "~2.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flush-write-stream@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +follow-redirects@^1.14.0, follow-redirects@^1.14.4: + version "1.14.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" + integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +from2@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fromentries@^1.2.0, fromentries@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs-extra@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" + integrity sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA= + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + +fs-extra@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-jetpack@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fs-jetpack/-/fs-jetpack-4.2.0.tgz#a3efc00abdb36f0f43ebd44405a4826098399d97" + integrity sha512-b7kFUlXAA4Q12qENTdU5DCQkf8ojEk4fpaXXu/bqayobwm0EfjjlwBCFqRBM2t8I75s0ifk0ajRqddXy2bAHJg== + dependencies: + minimatch "^3.0.2" + rimraf "^2.6.3" + +fs-minipass@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + +fs-minipass@^2.0.0, fs-minipass@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gauge@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.1.tgz#4bea07bcde3782f06dced8950e51307aa0f4a346" + integrity sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^1.0.1 || ^2.0.0" + strip-ansi "^3.0.1 || ^4.0.0" + wide-align "^1.1.2" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stdin@8.0.0, get-stdin@~8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +git-log-parser@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/git-log-parser/-/git-log-parser-1.2.0.tgz#2e6a4c1b13fc00028207ba795a7ac31667b9fd4a" + integrity sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo= + dependencies: + argv-formatter "~1.0.0" + spawn-error-forwarder "~1.0.0" + split2 "~1.0.0" + stream-combiner2 "~1.1.1" + through2 "~2.0.0" + traverse "~0.6.6" + +git-raw-commits@^2.0.0: + version "2.0.10" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" + integrity sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ== + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob@*, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + 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" + +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + 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" + +glob@~7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + 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-dirs@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= + dependencies: + ini "^1.3.4" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== + +globby@^11.0.0, globby@^11.0.1: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +graceful-fs@*, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +handlebars@^4.7.6: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.0, has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hook-std@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hook-std/-/hook-std-2.0.0.tgz#ff9aafdebb6a989a354f729bb6445cf4a3a7077c" + integrity sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g== + +hosted-git-info@*, hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== + dependencies: + lru-cache "^6.0.0" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +htmlparser2@~3.8.1: + version "3.8.3" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" + integrity sha1-mWwosZFRaovoZQGn15dX5ccMEGg= + dependencies: + domelementtype "1" + domhandler "2.3" + domutils "1.5" + entities "1.0" + readable-stream "1.1" + +http-cache-semantics@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= + dependencies: + ms "^2.0.0" + +husky@^4.2.5: + version "4.3.8" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== + dependencies: + chalk "^4.0.0" + ci-info "^2.0.0" + compare-versions "^3.6.0" + cosmiconfig "^7.0.0" + find-versions "^4.0.0" + opencollective-postinstall "^2.0.2" + pkg-dir "^5.0.0" + please-upgrade-node "^3.2.0" + slash "^3.0.0" + which-pm-runs "^1.0.0" + +ice-cap@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/ice-cap/-/ice-cap-0.0.4.tgz#8a6d31ab4cac8d4b56de4fa946df3352561b6e18" + integrity sha1-im0xq0ysjUtW3k+pRt8zUlYbbhg= + dependencies: + cheerio "0.20.0" + color-logger "0.0.3" + +iconv-lite@^0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" + integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.2, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-walk@^3.0.1, ignore-walk@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== + dependencies: + minimatch "^3.0.4" + +ignore-walk@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-4.0.1.tgz#fc840e8346cf88a3a9380c5b17933cd8f4d39fa3" + integrity sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw== + dependencies: + minimatch "^3.0.4" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.4, ignore@~5.1.8: + version "5.1.9" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" + integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== + dependencies: + resolve-from "^5.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflection@1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.13.1.tgz#c5cadd80888a90cf84c2e96e340d7edc85d5f0cb" + integrity sha512-dldYtl2WlN0QDkIDtg8+xFwOS2Tbmp12t1cHa5/YClU6ZQjTFm7B66UcVbh9NQB+HvT5BAd2t5+yKsBkw5pcqA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@*, ini@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +init-package-json@*: + version "2.0.5" + resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-2.0.5.tgz#78b85f3c36014db42d8f32117252504f68022646" + integrity sha512-u1uGAtEFu3VA6HNl/yUWw57jmKEMx8SKOxHhxjGnOFUiIlFnohKDFg4ZrPpv9wWqk44nDxGJAtqjdQFm+9XXQA== + dependencies: + npm-package-arg "^8.1.5" + promzard "^0.3.0" + read "~1.0.1" + read-package-json "^4.1.1" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + validate-npm-package-name "^3.0.0" + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +into-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-6.0.0.tgz#4bfc1244c0128224e18b8870e85b2de8e66c6702" + integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA== + dependencies: + from2 "^2.3.0" + p-is-promise "^3.0.0" + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +ip-regex@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + +ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-buffer@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-cidr@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-4.0.2.tgz#94c7585e4c6c77ceabf920f8cde51b8c0fda8814" + integrity sha512-z4a1ENUajDbEl/Q6/pVBpTR1nBjjEE1X7qb7bmWYanNnPoKAvUCPFKeXV6Fe4mgTkWKBqiHIcwsI3SndiO5FeA== + dependencies: + cidr-regex "^3.1.1" + +is-core-module@^2.2.0, is-core-module@^2.5.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-text-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= + dependencies: + text-extensions "^1.0.0" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + +is-weakref@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2" + integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ== + dependencies: + call-bind "^1.0.0" + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +issue-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-6.0.0.tgz#b1edd06315d4f2044a9755daf85fdafde9b4014a" + integrity sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA== + dependencies: + lodash.capitalize "^4.2.1" + lodash.escaperegexp "^4.1.2" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.uniqby "^4.7.0" + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" + integrity sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.0" + istanbul-lib-coverage "^3.0.0-alpha.1" + make-dir "^3.0.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^3.3.3" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" + integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +java-properties@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211" + integrity sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ== + +js-combinatorics@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/js-combinatorics/-/js-combinatorics-0.5.5.tgz#78d68a6db24bbd58173ded714deee75bc4335e75" + integrity sha512-WglFY9EQvwndNhuJLxxyjnC16649lfZly/G3M3zgQMwcWlJDJ0Jn9niPWeYjnLXwWOEycYVxR2Tk98WLeFkrcw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +js-yaml@3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^3.13.1, js-yaml@~3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbi@^3.1.1: + version "3.2.5" + resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6" + integrity sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ== + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdoctypeparser@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-6.1.0.tgz#acfb936c26300d98f1405cb03e20b06748e512a8" + integrity sha512-UCQBZ3xCUBv/PLfwKAJhp6jmGOSLFNKzrotXGNgbKhWvz27wPsCsVeP7gIcHPElQw2agBmynAitXqhxR58XAmA== + +jsdom@^7.0.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-7.2.2.tgz#40b402770c2bda23469096bee91ab675e3b1fc6e" + integrity sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4= + dependencies: + abab "^1.0.0" + acorn "^2.4.0" + acorn-globals "^1.0.4" + cssom ">= 0.3.0 < 0.4.0" + cssstyle ">= 0.2.29 < 0.3.0" + escodegen "^1.6.1" + nwmatcher ">= 1.3.7 < 2.0.0" + parse5 "^1.5.1" + request "^2.55.0" + sax "^1.1.4" + symbol-tree ">= 3.1.0 < 4.0.0" + tough-cookie "^2.2.0" + webidl-conversions "^2.0.0" + whatwg-url-compat "~0.6.5" + xml-name-validator ">= 2.0.1 < 3.0.0" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@*, json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-nice@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +jsonc-parser@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonparse@^1.2.0, jsonparse@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +just-diff-apply@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/just-diff-apply/-/just-diff-apply-3.1.2.tgz#710d8cda00c65dc4e692df50dbe9bac5581c2193" + integrity sha512-TCa7ZdxCeq6q3Rgms2JCRHTCfWAETPZ8SzYUbkYF6KR3I03sN29DaOIC+xyWboIcMvjAsD5iG2u/RWzHD8XpgQ== + +just-diff@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-3.1.1.tgz#d50c597c6fd4776495308c63bdee1b6839082647" + integrity sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ== + +just-extend@^4.0.2: + version "4.2.1" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" + integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@3.x.x: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= + optionalDependencies: + graceful-fs "^4.1.9" + +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + +lcov-result-merger@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lcov-result-merger/-/lcov-result-merger-3.1.0.tgz#ae6d1be663dbf7d586d8004642359d39de72039e" + integrity sha512-vGXaMNGZRr4cYvW+xMVg+rg7qd5DX9SbGXl+0S3k85+gRZVK4K7UvxPWzKb/qiMwe+4bx3EOrW2o4mbdb1WnsA== + dependencies: + through2 "^2.0.3" + vinyl "^2.1.0" + vinyl-fs "^3.0.2" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + dependencies: + flush-write-stream "^1.0.2" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +libnpmaccess@*: + version "4.0.3" + resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-4.0.3.tgz#dfb0e5b0a53c315a2610d300e46b4ddeb66e7eec" + integrity sha512-sPeTSNImksm8O2b6/pf3ikv4N567ERYEpeKRPSmqlNt1dTZbvgpJIzg5vAhXHpw2ISBsELFRelk0jEahj1c6nQ== + dependencies: + aproba "^2.0.0" + minipass "^3.1.1" + npm-package-arg "^8.1.2" + npm-registry-fetch "^11.0.0" + +libnpmdiff@*: + version "2.0.4" + resolved "https://registry.yarnpkg.com/libnpmdiff/-/libnpmdiff-2.0.4.tgz#bb1687992b1a97a8ea4a32f58ad7c7f92de53b74" + integrity sha512-q3zWePOJLHwsLEUjZw3Kyu/MJMYfl4tWCg78Vl6QGSfm4aXBUSVzMzjJ6jGiyarsT4d+1NH4B1gxfs62/+y9iQ== + dependencies: + "@npmcli/disparity-colors" "^1.0.1" + "@npmcli/installed-package-contents" "^1.0.7" + binary-extensions "^2.2.0" + diff "^5.0.0" + minimatch "^3.0.4" + npm-package-arg "^8.1.1" + pacote "^11.3.0" + tar "^6.1.0" + +libnpmexec@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/libnpmexec/-/libnpmexec-3.0.1.tgz#bc2fddf1b7bd2c1b2c43b4b726ec4cf11920ad0a" + integrity sha512-VUZTpkKBRPv3Z9DIjbsiHhEQXmQ+OwSQ/yLCY9i6CFE8UIczWyE6wVxP5sJ5NSGtSTUs6I98WewQOL45OKMyxA== + dependencies: + "@npmcli/arborist" "^4.0.0" + "@npmcli/ci-detect" "^1.3.0" + "@npmcli/run-script" "^2.0.0" + chalk "^4.1.0" + mkdirp-infer-owner "^2.0.0" + npm-package-arg "^8.1.2" + pacote "^12.0.0" + proc-log "^1.0.0" + read "^1.0.7" + read-package-json-fast "^2.0.2" + walk-up-path "^1.0.0" + +libnpmfund@*: + version "2.0.1" + resolved "https://registry.yarnpkg.com/libnpmfund/-/libnpmfund-2.0.1.tgz#3c7e2be61e8c79e22c4918dde91ef57f64faf064" + integrity sha512-OhDbjB3gqdRyuQ56AhUtO49HZ7cZHSM7yCnhQa1lsNpmAmGPnjCImfx8SoWaAkUM7Ov8jngMR5JHKAr1ddjHTQ== + dependencies: + "@npmcli/arborist" "^4.0.0" + +libnpmhook@*: + version "6.0.3" + resolved "https://registry.yarnpkg.com/libnpmhook/-/libnpmhook-6.0.3.tgz#1d7f0d7e6a7932fbf7ce0881fdb0ed8bf8748a30" + integrity sha512-3fmkZJibIybzmAvxJ65PeV3NzRc0m4xmYt6scui5msocThbEp4sKFT80FhgrCERYDjlUuFahU6zFNbJDHbQ++g== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmorg@*: + version "2.0.3" + resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-2.0.3.tgz#4e605d4113dfa16792d75343824a0625c76703bc" + integrity sha512-JSGl3HFeiRFUZOUlGdiNcUZOsUqkSYrg6KMzvPZ1WVZ478i47OnKSS0vkPmX45Pai5mTKuwIqBMcGWG7O8HfdA== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmpack@*: + version "3.0.0" + resolved "https://registry.yarnpkg.com/libnpmpack/-/libnpmpack-3.0.0.tgz#b1cdf182106bc0d25910e79bb5c9b6c23cd71670" + integrity sha512-W6lt4blkR9YXu/qOrFknfnKBajz/1GvAc5q1XcWTGuBJn2DYKDWHtA7x1fuMQdn7hKDBOPlZ/Aqll+ZvAnrM6g== + dependencies: + "@npmcli/run-script" "^2.0.0" + npm-package-arg "^8.1.0" + pacote "^12.0.0" + +libnpmpublish@*: + version "4.0.2" + resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-4.0.2.tgz#be77e8bf5956131bcb45e3caa6b96a842dec0794" + integrity sha512-+AD7A2zbVeGRCFI2aO//oUmapCwy7GHqPXFJh3qpToSRNU+tXKJ2YFUgjt04LPPAf2dlEH95s6EhIHM1J7bmOw== + dependencies: + normalize-package-data "^3.0.2" + npm-package-arg "^8.1.2" + npm-registry-fetch "^11.0.0" + semver "^7.1.3" + ssri "^8.0.1" + +libnpmsearch@*: + version "3.1.2" + resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-3.1.2.tgz#aee81b9e4768750d842b627a3051abc89fdc15f3" + integrity sha512-BaQHBjMNnsPYk3Bl6AiOeVuFgp72jviShNBw5aHaHNKWqZxNi38iVNoXbo6bG/Ccc/m1To8s0GtMdtn6xZ1HAw== + dependencies: + npm-registry-fetch "^11.0.0" + +libnpmteam@*: + version "2.0.4" + resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-2.0.4.tgz#9dbe2e18ae3cb97551ec07d2a2daf9944f3edc4c" + integrity sha512-FPrVJWv820FZFXaflAEVTLRWZrerCvfe7ZHSMzJ/62EBlho2KFlYKjyNEsPW3JiV7TLSXi3vo8u0gMwIkXSMTw== + dependencies: + aproba "^2.0.0" + npm-registry-fetch "^11.0.0" + +libnpmversion@*: + version "2.0.1" + resolved "https://registry.yarnpkg.com/libnpmversion/-/libnpmversion-2.0.1.tgz#20b1425d88cd99c66806a54b458d2d654066b550" + integrity sha512-uFGtNTe/m0GOIBQCE4ryIsgGNJdeShW+qvYtKNLCCuiG7JY3YEslL/maFFZbaO4wlQa/oj1t0Bm9TyjahvtgQQ== + dependencies: + "@npmcli/git" "^2.0.7" + "@npmcli/run-script" "^2.0.0" + json-parse-even-better-errors "^2.3.1" + semver "^7.3.5" + stringify-package "^1.0.1" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +linkify-it@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== + dependencies: + uc.micro "^1.0.1" + +lint-staged@^10.2.6: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== + dependencies: + chalk "^4.1.0" + cli-truncate "^2.1.0" + commander "^6.2.0" + cosmiconfig "^7.0.0" + debug "^4.2.0" + dedent "^0.7.0" + enquirer "^2.3.6" + execa "^4.1.0" + listr2 "^3.2.2" + log-symbols "^4.0.0" + micromatch "^4.0.2" + normalize-path "^3.0.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" + stringify-object "^3.3.0" + +listr2@^3.2.2: + version "3.13.3" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.3.tgz#d8f6095c9371b382c9b1c2bc33c5941d8e177f11" + integrity sha512-VqAgN+XVfyaEjSaFewGPcDs5/3hBbWVaX1VgWv2f52MF7US45JuARlArULctiB44IIcEk3JF7GtoFCLqEdeuPA== + dependencies: + cli-truncate "^2.1.0" + clone "^2.1.2" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^7.4.0" + through "^2.3.8" + wrap-ansi "^7.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.assignin@^4.0.9: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" + integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= + +lodash.bind@^4.1.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= + +lodash.capitalize@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" + integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= + +lodash.defaults@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.differencewith@~4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7" + integrity sha1-uvr7yRi1UVTheRdqALsK76rIVLc= + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + +lodash.filter@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= + +lodash.flatten@^4.2.0, lodash.flatten@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.foreach@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.ismatch@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" + integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.map@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= + +lodash.merge@^4.4.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.pick@^4.2.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + +lodash.reduce@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= + +lodash.reject@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= + +lodash.some@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= + +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= + +lodash@^4.1.0, lodash@^4.15.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^4.1.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-fetch-happen@*, make-fetch-happen@^9.0.1, make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + +map-obj@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + +mariadb@^2.3.1: + version "2.5.5" + resolved "https://registry.yarnpkg.com/mariadb/-/mariadb-2.5.5.tgz#a9aff9f1e57231a415a21254489439beb501c803" + integrity sha512-6dklvcKWuuaV1JjAwnE2ezR+jTt7JrZHftgeHHBmjB0wgfaUpdxol1DPWclwMcCrsO9yoM0FuCOiCcCgXc//9Q== + dependencies: + "@types/geojson" "^7946.0.7" + "@types/node" "^14.14.28" + denque "^1.5.0" + iconv-lite "^0.6.3" + long "^4.0.0" + moment-timezone "^0.5.33" + please-upgrade-node "^3.2.0" + +markdown-it@12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.2.tgz#4401beae8df8aa2221fc6565a7188e60a06ef0ed" + integrity sha512-4Lkvjbv2kK+moL9TbeV+6/NHx+1Q+R/NIdUlFlkqkkzUcTod4uiyTJRiBidKR9qXSdkNFkgv+AELY8KN9vSgVA== + dependencies: + argparse "^2.0.1" + entities "~2.0.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +markdownlint-cli@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.26.0.tgz#cd89e3e39a049303ec125c8aa291da4f3325df29" + integrity sha512-biLfeGNZG9nw0yJbtFBzRlew2/P5w7JSseKwolSox3zejs7dLpGvPgqbC+iqJnqqGWcWLtXaXh8bBEKWmfl10A== + dependencies: + commander "~6.2.1" + deep-extend "~0.6.0" + get-stdin "~8.0.0" + glob "~7.1.6" + ignore "~5.1.8" + js-yaml "~3.14.1" + jsonc-parser "~3.0.0" + lodash.differencewith "~4.5.0" + lodash.flatten "~4.4.0" + markdownlint "~0.22.0" + markdownlint-rule-helpers "~0.13.0" + minimatch "~3.0.4" + minimist "~1.2.5" + rc "~1.2.8" + +markdownlint-rule-helpers@~0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.13.0.tgz#7cc6553bc7f8c4c8a43cf66fb2a3a652124f46f9" + integrity sha512-rRY0itbcHG4e+ntz0bbY3AIceSJMKS0TafEMgEtKVHRZ54/JUSy6/4ypCL618RlJvYRej+xMLxX5nkJqIeTZaQ== + +markdownlint@~0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.22.0.tgz#4ed95b61c17ae9f4dfca6a01f038c744846c0a72" + integrity sha512-J4B+iMc12pOdp/wfYi03W2qfAfEyiZzq3qvQh/8vOMNU8vXYY6Jg440EY7dWTBCqROhb1i4nAn3BTByJ5kdx1w== + dependencies: + markdown-it "12.0.2" + +marked-terminal@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-4.2.0.tgz#593734a53cf9a4bb01ea961aa579bd21889ce502" + integrity sha512-DQfNRV9svZf0Dm9Cf5x5xaVJ1+XjxQW6XjFJ5HFkVyK52SDpj5PCBzS5X5r2w9nHr3mlB0T5201UMLue9fmhUw== + dependencies: + ansi-escapes "^4.3.1" + cardinal "^2.1.1" + chalk "^4.1.0" + cli-table3 "^0.6.0" + node-emoji "^1.10.0" + supports-hyperlinks "^2.1.0" + +marked@0.3.19: + version "0.3.19" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790" + integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg== + +marked@^1.1.0: + version "1.2.9" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.9.tgz#53786f8b05d4c01a2a5a76b7d1ec9943d29d72dc" + integrity sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw== + +marked@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" + integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== + +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + +mime-db@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" + integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.33" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" + integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + dependencies: + mime-db "1.50.0" + +mime@^2.4.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + +minimist@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-json-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz#7edbb92588fbfc2ff1db2fc10397acb7b6b44aa7" + integrity sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg== + dependencies: + jsonparse "^1.3.1" + minipass "^3.0.0" + +minipass-pipeline@*, minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@*, minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732" + integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw== + dependencies: + yallist "^4.0.0" + +minipass@^2.6.0, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-infer-owner@*, mkdirp-infer-owner@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316" + integrity sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw== + dependencies: + chownr "^2.0.0" + infer-owner "^1.0.4" + mkdirp "^1.0.3" + +mkdirp@*, mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@0.5.5, mkdirp@^0.5.1, mkdirp@^0.5.5: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mocha@^7.1.2: + version "7.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" + integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== + dependencies: + ansi-colors "3.2.3" + browser-stdout "1.3.1" + chokidar "3.3.0" + debug "3.2.6" + diff "3.5.0" + escape-string-regexp "1.0.5" + find-up "3.0.0" + glob "7.1.3" + growl "1.10.5" + he "1.2.0" + js-yaml "3.13.1" + log-symbols "3.0.0" + minimatch "3.0.4" + mkdirp "0.5.5" + ms "2.1.1" + node-environment-flags "1.0.6" + object.assign "4.1.0" + strip-json-comments "2.0.1" + supports-color "6.0.0" + which "1.3.1" + wide-align "1.1.3" + yargs "13.3.2" + yargs-parser "13.1.2" + yargs-unparser "1.6.0" + +modify-values@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" + integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + +moment-timezone@^0.5.31, moment-timezone@^0.5.33: + version "0.5.33" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" + integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@^2.26.0: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + +ms@*, ms@^2.0.0, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8, mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +mysql2@^2.1.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.2.tgz#3efe9814dbf1c2a3d7c2a1fc4666235939943ff9" + integrity sha512-JUSA50rt/nSew8aq8xe3pRk5Q4y/M5QdSJn7Ey3ndOlPp2KXuialQ0sS35DNhPT5Z5PnOiIwSSQvKkl1WorqRA== + dependencies: + denque "^2.0.1" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^4.0.0" + lru-cache "^6.0.0" + named-placeholders "^1.1.2" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + +named-placeholders@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8" + integrity sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA== + dependencies: + lru-cache "^4.1.3" + +nan@^2.12.1: + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + +native-duplexpair@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/native-duplexpair/-/native-duplexpair-1.0.0.tgz#7899078e64bf3c8a3d732601b3d40ff05db58fa0" + integrity sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +needle@^2.2.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + +negotiator@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nerf-dart@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nerf-dart/-/nerf-dart-1.0.0.tgz#e6dab7febf5ad816ea81cf5c629c5a0ebde72c1a" + integrity sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +nise@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-4.1.0.tgz#8fb75a26e90b99202fa1e63f448f58efbcdedaf6" + integrity sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^6.0.0" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + +node-emoji@^1.10.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-environment-flags@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== + dependencies: + object.getownpropertydescriptors "^2.0.3" + semver "^5.7.0" + +node-fetch@^2.6.1: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + +node-gyp@*, node-gyp@^8.2.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.0.tgz#6e1112b10617f0f8559c64b3f737e8109e5a8338" + integrity sha512-Bi/oCm5bH6F+FmzfUxJpPaxMEyIhszULGR3TprmTeku8/dMFcdTcypk120NeZqEt54r1BrgEKtm2jJiuIKE28Q== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^4.1.2" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +node-gyp@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" + integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.3" + nopt "^5.0.0" + npmlog "^4.1.2" + request "^2.88.2" + rimraf "^3.0.2" + semver "^7.3.2" + tar "^6.0.2" + which "^2.0.2" + +node-pre-gyp@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + +nopt@*, nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-package-data@^3.0.0, normalize-package-data@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-url@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +now-and-later@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" + integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + dependencies: + once "^1.3.2" + +npm-audit-report@*: + version "2.1.5" + resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-2.1.5.tgz#a5b8850abe2e8452fce976c8960dd432981737b5" + integrity sha512-YB8qOoEmBhUH1UJgh1xFAv7Jg1d+xoNhsDYiFQlEFThEBui0W1vIz2ZK6FVg4WZjwEdl7uBQlm1jy3MUfyHeEw== + dependencies: + chalk "^4.0.0" + +npm-bundled@^1.0.1, npm-bundled@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-install-checks@*, npm-install-checks@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-4.0.0.tgz#a37facc763a2fde0497ef2c6d0ac7c3fbe00d7b4" + integrity sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w== + dependencies: + semver "^7.1.1" + +npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-package-arg@*, npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.1, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5: + version "8.1.5" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44" + integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q== + dependencies: + hosted-git-info "^4.0.1" + semver "^7.3.4" + validate-npm-package-name "^3.0.0" + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + +npm-packlist@^2.1.4: + version "2.2.2" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-2.2.2.tgz#076b97293fa620f632833186a7a8f65aaa6148c8" + integrity sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg== + dependencies: + glob "^7.1.6" + ignore-walk "^3.0.3" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +npm-packlist@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-3.0.0.tgz#0370df5cfc2fcc8f79b8f42b37798dd9ee32c2a9" + integrity sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ== + dependencies: + glob "^7.1.6" + ignore-walk "^4.0.1" + npm-bundled "^1.1.1" + npm-normalize-package-bin "^1.0.1" + +npm-pick-manifest@*, npm-pick-manifest@^6.0.0, npm-pick-manifest@^6.1.0, npm-pick-manifest@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz#7b5484ca2c908565f43b7f27644f36bb816f5148" + integrity sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA== + dependencies: + npm-install-checks "^4.0.0" + npm-normalize-package-bin "^1.0.1" + npm-package-arg "^8.1.2" + semver "^7.3.4" + +npm-profile@*: + version "5.0.4" + resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-5.0.4.tgz#73e5bd1d808edc2c382d7139049cc367ac43161b" + integrity sha512-OKtU7yoAEBOnc8zJ+/uo5E4ugPp09sopo+6y1njPp+W99P8DvQon3BJYmpvyK2Bf1+3YV5LN1bvgXRoZ1LUJBA== + dependencies: + npm-registry-fetch "^11.0.0" + +npm-registry-fetch@*, npm-registry-fetch@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz#68c1bb810c46542760d62a6a965f85a702d43a76" + integrity sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA== + dependencies: + make-fetch-happen "^9.0.1" + minipass "^3.1.3" + minipass-fetch "^1.3.0" + minipass-json-stream "^1.0.1" + minizlib "^2.0.0" + npm-package-arg "^8.0.0" + +npm-run-path@^4.0.0, npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npm-user-validate@*: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.1.tgz#31428fc5475fe8416023f178c0ab47935ad8c561" + integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw== + +npm@^7.0.0: + version "7.24.2" + resolved "https://registry.yarnpkg.com/npm/-/npm-7.24.2.tgz#861117af8241bea592289f22407230e5300e59ca" + integrity sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ== + dependencies: + "@isaacs/string-locale-compare" "^1.1.0" + "@npmcli/arborist" "^2.9.0" + "@npmcli/ci-detect" "^1.2.0" + "@npmcli/config" "^2.3.0" + "@npmcli/map-workspaces" "^1.0.4" + "@npmcli/package-json" "^1.0.1" + "@npmcli/run-script" "^1.8.6" + abbrev "~1.1.1" + ansicolors "~0.3.2" + ansistyles "~0.1.3" + archy "~1.0.0" + cacache "^15.3.0" + chalk "^4.1.2" + chownr "^2.0.0" + cli-columns "^3.1.2" + cli-table3 "^0.6.0" + columnify "~1.5.4" + fastest-levenshtein "^1.0.12" + glob "^7.2.0" + graceful-fs "^4.2.8" + hosted-git-info "^4.0.2" + ini "^2.0.0" + init-package-json "^2.0.5" + is-cidr "^4.0.2" + json-parse-even-better-errors "^2.3.1" + libnpmaccess "^4.0.2" + libnpmdiff "^2.0.4" + libnpmexec "^2.0.1" + libnpmfund "^1.1.0" + libnpmhook "^6.0.2" + libnpmorg "^2.0.2" + libnpmpack "^2.0.1" + libnpmpublish "^4.0.1" + libnpmsearch "^3.1.1" + libnpmteam "^2.0.3" + libnpmversion "^1.2.1" + make-fetch-happen "^9.1.0" + minipass "^3.1.3" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + mkdirp-infer-owner "^2.0.0" + ms "^2.1.2" + node-gyp "^7.1.2" + nopt "^5.0.0" + npm-audit-report "^2.1.5" + npm-install-checks "^4.0.0" + npm-package-arg "^8.1.5" + npm-pick-manifest "^6.1.1" + npm-profile "^5.0.3" + npm-registry-fetch "^11.0.0" + npm-user-validate "^1.0.1" + npmlog "^5.0.1" + opener "^1.5.2" + pacote "^11.3.5" + parse-conflict-json "^1.1.1" + qrcode-terminal "^0.12.0" + read "~1.0.7" + read-package-json "^4.1.1" + read-package-json-fast "^2.0.3" + readdir-scoped-modules "^1.1.0" + rimraf "^3.0.2" + semver "^7.3.5" + ssri "^8.0.1" + tar "^6.1.11" + text-table "~0.2.0" + tiny-relative-date "^1.3.0" + treeverse "^1.0.4" + validate-npm-package-name "~3.0.0" + which "^2.0.2" + write-file-atomic "^3.0.3" + +npmlog@*: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +npmlog@^4.0.2, npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@>=2.0.1, nth-check@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +"nwmatcher@>= 1.3.7 < 2.0.0": + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== + +nyc@^15.0.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.assign@^4.0.4, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries-ponyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.entries-ponyfill/-/object.entries-ponyfill-1.0.1.tgz#29abdf77cbfbd26566dd1aa24e9d88f65433d256" + integrity sha1-Kavfd8v70mVm3RqiTp2I9lQz0lY= + +object.getownpropertydescriptors@^2.0.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" + integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opencollective-postinstall@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + +opener@*: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +optionator@^0.8.1, optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + dependencies: + readable-stream "^2.0.1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + +p-filter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" + integrity sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw== + dependencies: + p-map "^2.0.0" + +p-is-promise@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971" + integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-props@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-props/-/p-props-4.0.0.tgz#f37c877a9a722057833e1dc38d43edf3906b3437" + integrity sha512-3iKFbPdoPG7Ne3cMA53JnjPsTMaIzE9gxKZnvKJJivTAeqLEZPBu6zfi6DYq9AsH1nYycWmo3sWCNI8Kz6T2Zg== + dependencies: + p-map "^4.0.0" + +p-reduce@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" + integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== + +p-reflect@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-reflect/-/p-reflect-2.1.0.tgz#5d67c7b3c577c4e780b9451fc9129675bd99fe67" + integrity sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg== + +p-retry@^4.0.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" + integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + dependencies: + "@types/retry" "^0.12.0" + retry "^0.13.1" + +p-settle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/p-settle/-/p-settle-4.1.1.tgz#37fbceb2b02c9efc28658fc8d36949922266035f" + integrity sha512-6THGh13mt3gypcNMm0ADqVNCcYa3BK6DWsuJWFCuEKP1rpY+OKGp7gaZwVmLspmic01+fsg/fN57MfvDzZ/PuQ== + dependencies: + p-limit "^2.2.2" + p-reflect "^2.1.0" + +p-timeout@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-4.1.0.tgz#788253c0452ab0ffecf18a62dff94ff1bd09ca0a" + integrity sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw== + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + +pacote@*, pacote@^12.0.0: + version "12.0.2" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-12.0.2.tgz#14ae30a81fe62ec4fc18c071150e6763e932527c" + integrity sha512-Ar3mhjcxhMzk+OVZ8pbnXdb0l8+pimvlsqBGRNkble2NVgyqOGE3yrCGi/lAYq7E7NRDMz89R1Wx5HIMCGgeYg== + dependencies: + "@npmcli/git" "^2.1.0" + "@npmcli/installed-package-contents" "^1.0.6" + "@npmcli/promise-spawn" "^1.2.0" + "@npmcli/run-script" "^2.0.0" + cacache "^15.0.5" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.3" + mkdirp "^1.0.3" + npm-package-arg "^8.0.1" + npm-packlist "^3.0.0" + npm-pick-manifest "^6.0.0" + npm-registry-fetch "^11.0.0" + promise-retry "^2.0.1" + read-package-json-fast "^2.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.1.0" + +pacote@^11.3.0: + version "11.3.5" + resolved "https://registry.yarnpkg.com/pacote/-/pacote-11.3.5.tgz#73cf1fc3772b533f575e39efa96c50be8c3dc9d2" + integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg== + dependencies: + "@npmcli/git" "^2.1.0" + "@npmcli/installed-package-contents" "^1.0.6" + "@npmcli/promise-spawn" "^1.2.0" + "@npmcli/run-script" "^1.8.2" + cacache "^15.0.5" + chownr "^2.0.0" + fs-minipass "^2.1.0" + infer-owner "^1.0.4" + minipass "^3.1.3" + mkdirp "^1.0.3" + npm-package-arg "^8.0.1" + npm-packlist "^2.1.4" + npm-pick-manifest "^6.0.0" + npm-registry-fetch "^11.0.0" + promise-retry "^2.0.1" + read-package-json-fast "^2.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.1.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-conflict-json@*, parse-conflict-json@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/parse-conflict-json/-/parse-conflict-json-1.1.1.tgz#54ec175bde0f2d70abf6be79e0e042290b86701b" + integrity sha512-4gySviBiW5TRl7XHvp1agcS7SOe0KZOjC//71dzZVWJrY9hCrgtvl5v3SyIxCZ4fZF47TxD9nfzmxcx76xmbUw== + dependencies: + json-parse-even-better-errors "^2.3.0" + just-diff "^3.0.1" + just-diff-apply "^3.0.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@>=1.0.7, path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pg-connection-string@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" + integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== + +pg-hstore@^2.x: + version "2.3.4" + resolved "https://registry.yarnpkg.com/pg-hstore/-/pg-hstore-2.3.4.tgz#4425e3e2a3e15d2a334c35581186c27cf2e9b8dd" + integrity sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA== + dependencies: + underscore "^1.13.1" + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" + integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== + +pg-protocol@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" + integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.2.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471" + integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.5.0" + pg-pool "^3.4.1" + pg-protocol "^1.5.0" + pg-types "^2.1.0" + pgpass "1.x" + +pgpass@1.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.4.tgz#85eb93a83800b20f8057a2b029bf05abaf94ea9c" + integrity sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w== + dependencies: + split2 "^3.1.1" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pkg-conf@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +proc-log@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-1.0.0.tgz#0d927307401f69ed79341e83a0b2c9a13395eb77" + integrity sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg== + +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process-on-spawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" + integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== + dependencies: + fromentries "^1.2.0" + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-all-reject-late@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== + +promise-call-limit@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +promzard@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" + integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4= + dependencies: + read "1" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qrcode-terminal@*: + version "0.12.0" + resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" + integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +ramda@^0.27.0: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + +rc@^1.2.7, rc@^1.2.8, rc@~1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-cmd-shim@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz#4a50a71d6f0965364938e9038476f7eede3928d9" + integrity sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw== + +read-package-json-fast@*, read-package-json-fast@^2.0.1, read-package-json-fast@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== + dependencies: + json-parse-even-better-errors "^2.3.0" + npm-normalize-package-bin "^1.0.1" + +read-package-json@*, read-package-json@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-4.1.1.tgz#153be72fce801578c1c86b8ef2b21188df1b9eea" + integrity sha512-P82sbZJ3ldDrWCOSKxJT0r/CXMWR0OR3KRh55SgKo3p91GSIEEC32v3lSHAvO/UcH3/IoL7uqhOFBduAnwdldw== + dependencies: + glob "^7.1.1" + json-parse-even-better-errors "^2.3.0" + normalize-package-data "^3.0.0" + npm-normalize-package-bin "^1.0.0" + +read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.0.0, read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +read@*, read@1, read@^1.0.7, read@~1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= + dependencies: + mute-stream "~0.0.4" + +readable-stream@1.1: + version "1.1.13" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" + integrity sha1-9u73ZPUUyJ4rniMUanW6EGdW0j4= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + 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" + +readdir-scoped-modules@*, readdir-scoped-modules@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== + dependencies: + debuglog "^1.0.1" + dezalgo "^1.0.0" + graceful-fs "^4.1.2" + once "^1.3.0" + +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + +redeyed@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" + integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= + dependencies: + esprima "~4.0.0" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regextras@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.7.1.tgz#be95719d5f43f9ef0b9fa07ad89b7c606995a3b2" + integrity sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w== + +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= + dependencies: + es6-error "^4.0.1" + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeating@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac" + integrity sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw= + dependencies: + is-finite "^1.0.0" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + +"request@>= 2.52.0", request@^2.55.0, request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-global@1.0.0, resolve-global@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" + integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== + dependencies: + global-dirs "^0.1.1" + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + dependencies: + value-or-function "^3.0.0" + +resolve@^1.10.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry-as-promised@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-3.2.0.tgz#769f63d536bec4783549db0777cb56dadd9d8543" + integrity sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg== + dependencies: + any-promise "^1.3.0" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@*, rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.6.1, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +rxjs@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" + integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== + dependencies: + tslib "~2.1.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@>=0.6.0, sax@^1.1.4, sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semantic-release-fail-on-major-bump@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semantic-release-fail-on-major-bump/-/semantic-release-fail-on-major-bump-1.0.0.tgz#a4fe055258415040f6170c175596cedb4d4ffb82" + integrity sha512-vFbUVEQC60p3n+0NJc4D+Z6TS+5Q4AdG72pe5hsmcVEcaK+w+nPxjefLl3bJjphxc6AVH9cAZM0ZTnmiTG6eLA== + +semantic-release@^17.3.0: + version "17.4.7" + resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-17.4.7.tgz#88e1dce7294cc43acc54c4e0a83f582264567206" + integrity sha512-3Ghu8mKCJgCG3QzE5xphkYWM19lGE3XjFdOXQIKBM2PBpBvgFQ/lXv31oX0+fuN/UjNFO/dqhNs8ATLBhg6zBg== + dependencies: + "@semantic-release/commit-analyzer" "^8.0.0" + "@semantic-release/error" "^2.2.0" + "@semantic-release/github" "^7.0.0" + "@semantic-release/npm" "^7.0.0" + "@semantic-release/release-notes-generator" "^9.0.0" + aggregate-error "^3.0.0" + cosmiconfig "^7.0.0" + debug "^4.0.0" + env-ci "^5.0.0" + execa "^5.0.0" + figures "^3.0.0" + find-versions "^4.0.0" + get-stream "^6.0.0" + git-log-parser "^1.2.0" + hook-std "^2.0.0" + hosted-git-info "^4.0.0" + lodash "^4.17.21" + marked "^2.0.0" + marked-terminal "^4.1.1" + micromatch "^4.0.2" + p-each-series "^2.1.0" + p-reduce "^2.0.0" + read-pkg-up "^7.0.0" + resolve-from "^5.0.0" + semver "^7.3.2" + semver-diff "^3.1.1" + signale "^1.2.1" + yargs "^16.2.0" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver-regex@>=3.1.3: + version "4.0.2" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-4.0.2.tgz#fd3124efe81647b33eb90a9de07cb72992424a02" + integrity sha512-xyuBZk1XYqQkB687hMQqrCP+J9bdJSjPpZwdmmNjyxKW1K3LDXxqxw91Egaqkh/yheBIVtKPt4/1eybKVdCx3g== + +semver-regex@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" + integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== + +semver@*, semver@^7.1.1, semver@^7.1.2, semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@^6.0.0, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= + +sequelize-pool@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-6.1.0.tgz#caaa0c1e324d3c2c3a399fed2c7998970925d668" + integrity sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg== + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.5" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" + integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== + +signale@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" + integrity sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w== + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + pkg-conf "^2.1.0" + +sinon-chai@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783" + integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g== + +sinon@^9.0.2: + version "9.2.4" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.4.tgz#e55af4d3b174a4443a8762fa8421c2976683752b" + integrity sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg== + dependencies: + "@sinonjs/commons" "^1.8.1" + "@sinonjs/fake-timers" "^6.0.1" + "@sinonjs/samsam" "^5.3.1" + diff "^4.0.2" + nise "^4.0.4" + supports-color "^7.1.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +smart-buffer@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3" + integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg== + dependencies: + agent-base "^6.0.2" + debug "^4.3.1" + socks "^2.6.1" + +socks@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.1.0" + +source-map@^0.5.0, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-error-forwarder@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz#1afd94738e999b0346d7b9fc373be55e07577029" + integrity sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk= + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== + +split2@^3.0.0, split2@^3.1.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + +split2@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-1.0.0.tgz#52e2e221d88c75f9a73f90556e263ff96772b314" + integrity sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ= + dependencies: + through2 "~2.0.0" + +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sqlite3@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901" + integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg== + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.11.0" + +sqlstring@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514" + integrity sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +ssri@*, ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + +stream-combiner2@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +string-argv@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.1 || ^2.0.0", "string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +stringify-package@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" + integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +"strip-ansi@^3.0.1 || ^4.0.0", strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@2.0.1, strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== + dependencies: + has-flag "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +"symbol-tree@>= 3.1.0 < 4.0.0": + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +taffydb@2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.7.2.tgz#7bf8106a5c1a48251b3e3bc0a0e1732489fd0dc8" + integrity sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg= + +taffydb@2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.7.3.tgz#2ad37169629498fca5bc84243096d3cde0ec3a34" + integrity sha1-KtNxaWKUmPylvIQkMJbTzeDsOjQ= + +tar@*, tar@>=4.4.18, tar@^6.0.2, tar@^6.1.0, tar@^6.1.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tar@^4: + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + +tedious@8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/tedious/-/tedious-8.3.0.tgz#74d3d434638b0bdd02b6266f041c003ceca93f67" + integrity sha512-v46Q9SRVgz6IolyPdlsxQtfm9q/sqDs+y4aRFK0ET1iKitbpzCCQRHb6rnVcR1FLnLR0Y7AgcqnWUoMPUXz9HA== + dependencies: + "@azure/ms-rest-nodeauth" "2.0.2" + "@js-joda/core" "^2.0.0" + bl "^3.0.0" + depd "^2.0.0" + iconv-lite "^0.5.0" + jsbi "^3.1.1" + native-duplexpair "^1.0.0" + punycode "^2.1.0" + readable-stream "^3.6.0" + sprintf-js "^1.1.2" + +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + +tempy@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-1.0.1.tgz#30fe901fd869cfb36ee2bd999805aa72fbb035de" + integrity sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w== + dependencies: + del "^6.0.0" + is-stream "^2.0.0" + temp-dir "^2.0.0" + type-fest "^0.16.0" + unique-string "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-extensions@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" + integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== + +text-table@*, text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tiny-relative-date@*: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" + integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + dependencies: + through2 "^2.0.3" + +toposort-class@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" + integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg= + +tough-cookie@^2.2.0, tough-cookie@^2.4.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@~0.0.1, tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +traverse@~0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" + integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= + +treeverse@*, treeverse@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-1.0.4.tgz#a6b0ebf98a1bca6846ddc7ecbc900df08cb9cd5f" + integrity sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g== + +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +tslib@^1.9.0, tslib@^1.9.2: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tunnel@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" + integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== + +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.0, type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.1.3: + version "4.4.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" + integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== + +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +uglify-js@^3.1.4: + version "3.14.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.3.tgz#c0f25dfea1e8e5323eccf59610be08b6043c15cf" + integrity sha512-mic3aOdiq01DuSVx0TseaEzMIVqebMZ0Z3vaeDhFEh9bsc24hV1TFvN74reA2vs08D0ZWfNjAcJ3UbVLaBss+g== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + +"underscore@>= 1.3.1", underscore@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1" + integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unique-stream@^2.0.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-join@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.1.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validate-npm-package-name@*, validate-npm-package-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" + integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= + dependencies: + builtins "^1.0.3" + +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vinyl-fs@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^2.0.0, vinyl@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.1.tgz#23cfb8bbab5ece3803aa2c0a1eb28af7cbba1974" + integrity sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +walk-up-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== + +wcwidth@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +webidl-conversions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506" + integrity sha1-O/glj30xjHRDw28uFpQCoaZwNQY= + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url-compat@~0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz#00898111af689bb097541cd5a45ca6c8798445bf" + integrity sha1-AImBEa9om7CXVBzVpFymyHmERb8= + dependencies: + tr46 "~0.0.1" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +which@*, which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +which@1.3.1, which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +wide-align@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wide-align@^1.1.0, wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wkx@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.5.0.tgz#c6c37019acf40e517cc6b94657a25a3d4aa33e8c" + integrity sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg== + dependencies: + "@types/node" "*" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@*, write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +"xml-name-validator@>= 2.0.1 < 3.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= + +xml2js@^0.4.19: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + +"xmldom@>= 0.1.x": + version "0.6.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== + +xpath.js@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1" + integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== + +xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yallist@^3.0.0, yallist@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@13.1.2, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== + dependencies: + flat "^4.1.0" + lodash "^4.17.15" + yargs "^13.3.0" + +yargs@13.3.2, yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + 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.1.2" + +yargs@^15.0.2, yargs@^15.1.0: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 0abe33a293df69f96826d277a205d73bb431c067 Mon Sep 17 00:00:00 2001 From: Vadim Kononov Date: Mon, 22 Nov 2021 17:35:27 +0200 Subject: [PATCH 408/414] customizing v6 --- lib/dialects/sqlite/query.js | 3 +++ lib/hooks.js | 3 ++- lib/sequelize.js | 4 +++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/dialects/sqlite/query.js b/lib/dialects/sqlite/query.js index 499cf21694f3..d0eeecdf1278 100644 --- a/lib/dialects/sqlite/query.js +++ b/lib/dialects/sqlite/query.js @@ -402,6 +402,9 @@ class Query extends AbstractQuery { return new sequelizeErrors.TimeoutError(err, { stack: errStack }); default: + if (err.message && err.message.includes("SQLITE_ERROR: no such table")) { + console.log(err.sql); + } return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } diff --git a/lib/hooks.js b/lib/hooks.js index 69602a048b4d..bbcd4a23aa57 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -48,7 +48,8 @@ const hookTypes = { beforeBulkSync: { params: 1 }, afterBulkSync: { params: 1 }, beforeQuery: { params: 2 }, - afterQuery: { params: 2 } + afterQuery: { params: 2 }, + beforeGetConnection: {params: 2} }; exports.hooks = hookTypes; diff --git a/lib/sequelize.js b/lib/sequelize.js index 3aa6cb9cd54e..cbd79d081d47 100644 --- a/lib/sequelize.js +++ b/lib/sequelize.js @@ -617,7 +617,9 @@ class Sequelize { checkTransaction(); - const connection = await (options.transaction ? options.transaction.connection : this.connectionManager.getConnection(options)); + const connection = await (options.transaction ? + options.transaction.connection : + this.runHooks('beforeGetConnection', options, sql).then(() => this.connectionManager.getConnection(options))); const query = new this.dialect.Query(connection, this, options); try { From 9bf8f865069e5aac07f83dcf79e781f3a8b32dcf Mon Sep 17 00:00:00 2001 From: vadimivymark Date: Wed, 28 Dec 2022 11:30:04 +0200 Subject: [PATCH 409/414] Update query.js --- lib/dialects/abstract/query.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 7e640b83fee4..d19a6644ce53 100644 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -331,6 +331,10 @@ class AbstractQuery { _logQuery(sql, debugContext, parameters) { const { connection, options } = this; const benchmark = this.sequelize.options.benchmark || options.benchmark; + + const instanceType = this.client.queryType === 'read' ? 'replica' : 'primary'; + const instanceTypeMessage = `on ${instanceType} DB`; + const logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters; const startTime = Date.now(); let logParameter = ''; @@ -355,7 +359,8 @@ class AbstractQuery { const afterMsg = `Executed ${fmt}`; debugContext(afterMsg); if (benchmark) { - this.sequelize.log(afterMsg, Date.now() - startTime, options); + const elapsedTime = Date.now() - startTime; + this.sequelize.log(`Executed ${elapsedTime > this.sequelize.options.queryThreshold ? 'SLOW QUERY ' : ''} (${instanceTypeMessage}, ${fmt}`, elapsedTime, options); } }; } From 0f12a11aeff6f3c4eb1eb224eb02b0bf8297efc6 Mon Sep 17 00:00:00 2001 From: vadimivymark Date: Wed, 28 Dec 2022 11:32:41 +0200 Subject: [PATCH 410/414] Update query.js --- lib/dialects/abstract/query.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index d19a6644ce53..646d1a715532 100644 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -332,7 +332,7 @@ class AbstractQuery { const { connection, options } = this; const benchmark = this.sequelize.options.benchmark || options.benchmark; - const instanceType = this.client.queryType === 'read' ? 'replica' : 'primary'; + const instanceType = connection.queryType === 'read' ? 'replica' : 'primary'; const instanceTypeMessage = `on ${instanceType} DB`; const logQueryParameters = this.sequelize.options.logQueryParameters || options.logQueryParameters; From 6a03e4e724fb21e13279686c1f6b70d3fb960681 Mon Sep 17 00:00:00 2001 From: Demitry Volovich <28387793+DemitryVolovich@users.noreply.github.com> Date: Tue, 17 Jan 2023 14:33:31 +0200 Subject: [PATCH 411/414] Update quote.js --- lib/dialects/abstract/query-generator/helpers/quote.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js index 19a1d983b5e5..ffba642ba8a4 100644 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ b/lib/dialects/abstract/query-generator/helpers/quote.js @@ -47,6 +47,7 @@ function quoteIdentifier(dialect, identifier, options) { return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`'); case 'postgres': + console.log(`Dima: ${identifier}`); const rawIdentifier = Utils.removeTicks(identifier, '"'); if ( From a104c06db2f66c86bb9ff33c356976755dae29c7 Mon Sep 17 00:00:00 2001 From: Demitry Volovich <28387793+DemitryVolovich@users.noreply.github.com> Date: Tue, 17 Jan 2023 14:46:09 +0200 Subject: [PATCH 412/414] Update quote.js --- lib/dialects/abstract/query-generator/helpers/quote.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dialects/abstract/query-generator/helpers/quote.js b/lib/dialects/abstract/query-generator/helpers/quote.js index ffba642ba8a4..19a1d983b5e5 100644 --- a/lib/dialects/abstract/query-generator/helpers/quote.js +++ b/lib/dialects/abstract/query-generator/helpers/quote.js @@ -47,7 +47,6 @@ function quoteIdentifier(dialect, identifier, options) { return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`'); case 'postgres': - console.log(`Dima: ${identifier}`); const rawIdentifier = Utils.removeTicks(identifier, '"'); if ( From 76e68cd6e7b5878f2d2bab1a2c245741a2c44166 Mon Sep 17 00:00:00 2001 From: vadimivymark Date: Wed, 16 Aug 2023 15:25:26 +0300 Subject: [PATCH 413/414] Update query.js --- lib/dialects/abstract/query.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dialects/abstract/query.js b/lib/dialects/abstract/query.js index 646d1a715532..b28a93d5e9e6 100644 --- a/lib/dialects/abstract/query.js +++ b/lib/dialects/abstract/query.js @@ -360,7 +360,7 @@ class AbstractQuery { debugContext(afterMsg); if (benchmark) { const elapsedTime = Date.now() - startTime; - this.sequelize.log(`Executed ${elapsedTime > this.sequelize.options.queryThreshold ? 'SLOW QUERY ' : ''} (${instanceTypeMessage}, ${fmt}`, elapsedTime, options); + this.sequelize.log(`Executed ${elapsedTime > this.sequelize.options.queryThreshold ? 'SLOW QUERY ' : ''}${instanceTypeMessage}, ${fmt}`, elapsedTime, options); } }; } From 51e120abc1ed5a99622082b9fd519504d395b7c3 Mon Sep 17 00:00:00 2001 From: vadimivymark Date: Thu, 21 Sep 2023 11:37:50 +0300 Subject: [PATCH 414/414] log failed query --- lib/dialects/postgres/query.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dialects/postgres/query.js b/lib/dialects/postgres/query.js index 3640bda38648..9024e5dea6e7 100644 --- a/lib/dialects/postgres/query.js +++ b/lib/dialects/postgres/query.js @@ -83,6 +83,8 @@ class Query extends AbstractQuery { connection._invalid = true; } + this.sequelize.log(`FAILED QUERY (${connection.uuid || 'default'}): ${this.sql}`, this.options); + err.sql = sql; err.parameters = parameters; throw this.formatError(err, errForStack.stack);
UNIQUECHECKDefault - MSSQL onlyPrimary KeyForeign KeyA syntax for automatically committing or rolling back based on the promise chain resolution is also supportedTo enable CLS, add it do your project, create a namespace and set it on the sequelize constructor: